19 package org.sleuthkit.autopsy.healthmonitor;
 
   21 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 
   22 import java.beans.PropertyChangeEvent;
 
   23 import java.beans.PropertyChangeListener;
 
   24 import java.sql.Connection;
 
   25 import java.sql.DriverManager;
 
   26 import java.sql.PreparedStatement;
 
   27 import java.sql.ResultSet;
 
   28 import java.sql.SQLException;
 
   29 import java.sql.Statement;
 
   31 import java.util.HashMap;
 
   32 import java.util.List;
 
   33 import java.util.ArrayList;
 
   34 import java.util.Calendar;
 
   35 import java.util.GregorianCalendar;
 
   36 import java.util.UUID;
 
   37 import java.util.concurrent.ScheduledThreadPoolExecutor;
 
   38 import java.util.concurrent.TimeUnit;
 
   39 import java.util.concurrent.atomic.AtomicBoolean;
 
   40 import java.util.logging.Level;
 
   41 import java.util.Random;
 
   42 import org.apache.commons.dbcp2.BasicDataSource;
 
   70     private final static AtomicBoolean 
isEnabled = 
new AtomicBoolean(
false);
 
   86         timingInfoMap = 
new HashMap<>();
 
   90         userInfoList = 
new ArrayList<>();
 
   94             hostName = java.net.InetAddress.getLocalHost().getHostName();
 
   95         } 
catch (java.net.UnknownHostException ex) {
 
   97             hostName = UUID.randomUUID().toString();
 
   98             logger.log(Level.SEVERE, 
"Unable to look up host name - falling back to UUID " + hostName, ex);
 
  102         username = System.getProperty(
"user.name");
 
  105         updateFromGlobalEnabledStatus();
 
  118     synchronized static HealthMonitor getInstance() throws HealthMonitorException {
 
  119         if (instance == null) {
 
  135         logger.log(Level.INFO, 
"Activating Servies Health Monitor");
 
  141             throw new HealthMonitorException(
"Multi user mode is not enabled - can not activate health monitor");
 
  147                 throw new HealthMonitorException(
"Error getting database lock");
 
  161             if (
getVersion().compareTo(CURRENT_DB_SCHEMA_VERSION) < 0) {
 
  166             throw new HealthMonitorException(
"Error releasing database lock", ex);
 
  170         timingInfoMap.clear();
 
  171         userInfoList.clear();
 
  179         logger.log(Level.INFO, 
"Upgrading Health Monitor database");
 
  184             throw new HealthMonitorException(
"Error getting database connection");
 
  186         ResultSet resultSet = null;
 
  188         try (Statement statement = conn.createStatement()) {
 
  189             conn.setAutoCommit(
false);
 
  200                 statement.execute(
"CREATE TABLE IF NOT EXISTS user_data (" 
  201                         + 
"id SERIAL PRIMARY KEY," 
  202                         + 
"host text NOT NULL," 
  203                         + 
"timestamp bigint NOT NULL," 
  204                         + 
"event_type int NOT NULL," 
  205                         + 
"is_examiner boolean NOT NULL," 
  206                         + 
"case_name text NOT NULL" 
  214                 resultSet = statement.executeQuery(
"SELECT column_name " +
 
  215                         "FROM information_schema.columns " +
 
  216                         "WHERE table_name='user_data' and column_name='username'");
 
  217                 if (! resultSet.next()) {
 
  219                     statement.execute(
"ALTER TABLE user_data ADD COLUMN username text");
 
  224             statement.execute(
"UPDATE db_info SET value='" + CURRENT_DB_SCHEMA_VERSION.
getMajor() + 
"' WHERE name='SCHEMA_VERSION'");
 
  225             statement.execute(
"UPDATE db_info SET value='" + CURRENT_DB_SCHEMA_VERSION.
getMinor() + 
"' WHERE name='SCHEMA_MINOR_VERSION'");
 
  228             logger.log(Level.INFO, 
"Health Monitor database upgraded to version {0}", CURRENT_DB_SCHEMA_VERSION.
toString());
 
  229         } 
catch (SQLException ex) {
 
  232             } 
catch (SQLException ex2) {
 
  233                 logger.log(Level.SEVERE, 
"Rollback error");
 
  235             throw new HealthMonitorException(
"Error upgrading database", ex);
 
  237             if (resultSet != null) {
 
  240                 } 
catch (SQLException ex2) {
 
  241                     logger.log(Level.SEVERE, 
"Error closing result set");
 
  246             } 
catch (SQLException ex) {
 
  247                 logger.log(Level.SEVERE, 
"Error closing connection.", ex);
 
  262         logger.log(Level.INFO, 
"Deactivating Servies Health Monitor");
 
  265         timingInfoMap.clear();
 
  279         healthMonitorOutputTimer = 
new ScheduledThreadPoolExecutor(1, 
new ThreadFactoryBuilder().setNameFormat(
"health_monitor_timer").build());
 
  280         healthMonitorOutputTimer.scheduleWithFixedDelay(
new PeriodicHealthMonitorTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES);
 
  287         if (healthMonitorOutputTimer != null) {
 
  298     static synchronized void startUpIfEnabled() throws HealthMonitorException {
 
  308     static synchronized void shutdown() throws HealthMonitorException {
 
  320     static synchronized void setEnabled(
boolean enabled) 
throws HealthMonitorException {
 
  321         if (enabled == isEnabled.get()) {
 
  333             if (isEnabled.get()) {
 
  337             isEnabled.set(
false);
 
  354         if (isEnabled.get()) {
 
  368         if (isEnabled.get() && (metric != null)) {
 
  372             } 
catch (HealthMonitorException ex) {
 
  374                 logger.log(Level.SEVERE, 
"Error adding timing metric", ex);
 
  391         if (isEnabled.get() && (metric != null)) {
 
  394                 metric.normalize(normalization);
 
  396             } 
catch (HealthMonitorException ex) {
 
  398                 logger.log(Level.SEVERE, 
"Error adding timing metric", ex);
 
  413         synchronized (
this) {
 
  420             if (timingInfoMap.containsKey(metric.getName())) {
 
  421                 timingInfoMap.get(metric.getName()).addMetric(metric);
 
  423                 timingInfoMap.put(metric.getName(), 
new TimingInfo(metric));
 
  434         UserData userInfo = 
new UserData(eventType);
 
  435         synchronized (
this) {
 
  436             userInfoList.add(userInfo);
 
  457             long normalization = images.size();
 
  458             if (images.isEmpty()) {
 
  460             } 
else if (images.size() == 1) {
 
  462             } 
else if (images.size() < 10) {
 
  472             throw new HealthMonitorException(
"Error running getImages()", ex);
 
  492         Map<String, TimingInfo> timingMapCopy;
 
  493         List<UserData> userDataCopy;
 
  497         synchronized (
this) {
 
  498             if (!isEnabled.get()) {
 
  505             timingInfoMap.clear();
 
  508             userInfoList.clear();
 
  512         if (timingMapCopy.keySet().isEmpty() && userDataCopy.isEmpty()) {
 
  516         logger.log(Level.INFO, 
"Writing health monitor metrics to database");
 
  521                 throw new HealthMonitorException(
"Error getting database lock");
 
  526                 throw new HealthMonitorException(
"Error getting database connection");
 
  530             String addTimingInfoSql = 
"INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)";
 
  531             String addUserInfoSql = 
"INSERT INTO user_data (host, username, timestamp, event_type, is_examiner, case_name) VALUES (?, ?, ?, ?, ?, ?)";
 
  532             try (PreparedStatement timingStatement = conn.prepareStatement(addTimingInfoSql);
 
  533                     PreparedStatement userStatement = conn.prepareStatement(addUserInfoSql)) {
 
  535                 for (String name : timingMapCopy.keySet()) {
 
  538                     timingStatement.setString(1, name);
 
  539                     timingStatement.setString(2, hostName);
 
  540                     timingStatement.setLong(3, System.currentTimeMillis());
 
  541                     timingStatement.setLong(4, info.getCount());
 
  542                     timingStatement.setDouble(5, info.getAverage());
 
  543                     timingStatement.setDouble(6, info.getMax());
 
  544                     timingStatement.setDouble(7, info.getMin());
 
  546                     timingStatement.execute();
 
  549                 for (UserData userInfo : userDataCopy) {
 
  550                     userStatement.setString(1, hostName);
 
  551                     userStatement.setString(2, username);
 
  552                     userStatement.setLong(3, userInfo.getTimestamp());
 
  553                     userStatement.setInt(4, userInfo.getEventType().getEventValue());
 
  554                     userStatement.setBoolean(5, userInfo.isExaminerNode());
 
  555                     userStatement.setString(6, userInfo.getCaseName());
 
  556                     userStatement.execute();
 
  559             } 
catch (SQLException ex) {
 
  560                 throw new HealthMonitorException(
"Error saving metric data to database", ex);
 
  564                 } 
catch (SQLException ex) {
 
  565                     logger.log(Level.SEVERE, 
"Error closing Connection.", ex);
 
  569             throw new HealthMonitorException(
"Error releasing database lock", ex);
 
  585             Class.forName(
"org.postgresql.Driver"); 
 
  588                     Statement statement = connection.createStatement();) {
 
  589                 String createCommand = 
"SELECT 1 AS result FROM pg_database WHERE datname='" + DATABASE_NAME + 
"'";
 
  590                 rs = statement.executeQuery(createCommand);
 
  600             throw new HealthMonitorException(
"Failed check for health monitor database", ex);
 
  614             Class.forName(
"org.postgresql.Driver"); 
 
  616                     Statement statement = connection.createStatement();) {
 
  617                 String createCommand = 
"CREATE DATABASE \"" + DATABASE_NAME + 
"\" OWNER \"" + db.
getUserName() + 
"\""; 
 
  618                 statement.execute(createCommand);
 
  620             logger.log(Level.INFO, 
"Created new health monitor database " + DATABASE_NAME);
 
  622             throw new HealthMonitorException(
"Failed to delete health monitor database", ex);
 
  634             connectionSettingsInUse = db;
 
  636             connectionPool = 
new BasicDataSource();
 
  637             connectionPool.setDriverClassName(
"org.postgresql.Driver");
 
  639             StringBuilder connectionURL = 
new StringBuilder();
 
  640             connectionURL.append(
"jdbc:postgresql://");
 
  641             connectionURL.append(db.
getHost());
 
  642             connectionURL.append(
":");
 
  643             connectionURL.append(db.
getPort());
 
  644             connectionURL.append(
"/");
 
  645             connectionURL.append(DATABASE_NAME);
 
  647             connectionPool.setUrl(connectionURL.toString());
 
  652             connectionPool.setInitialSize(3); 
 
  653             connectionPool.setMaxIdle(CONN_POOL_SIZE); 
 
  654             connectionPool.setValidationQuery(
"SELECT version()");
 
  656             throw new HealthMonitorException(
"Error loading database configuration", ex);
 
  667             synchronized (
this) {
 
  668                 if (connectionPool != null) {
 
  669                     connectionPool.close();
 
  670                     connectionPool = null; 
 
  673         } 
catch (SQLException ex) {
 
  674             throw new HealthMonitorException(
"Failed to close existing database connections.", ex); 
 
  685     private Connection 
connect() throws HealthMonitorException {
 
  686         synchronized (
this) {
 
  687             if (connectionPool == null) {
 
  693             return connectionPool.getConnection();
 
  694         } 
catch (SQLException ex) {
 
  695             throw new HealthMonitorException(
"Error getting connection from connection pool.", ex); 
 
  710             throw new HealthMonitorException(
"Error getting database connection");
 
  712         ResultSet resultSet = null;
 
  714         try (Statement statement = conn.createStatement()) {
 
  715             resultSet = statement.executeQuery(
"SELECT value FROM db_info WHERE name='SCHEMA_VERSION'");
 
  716             return resultSet.next();
 
  717         } 
catch (SQLException ex) {
 
  721             if (resultSet != null) {
 
  724                 } 
catch (SQLException ex) {
 
  725                     logger.log(Level.SEVERE, 
"Error closing result set", ex);
 
  730             } 
catch (SQLException ex) {
 
  731                 logger.log(Level.SEVERE, 
"Error closing Connection.", ex);
 
  742     static boolean monitorIsEnabled() {
 
  743         return isEnabled.get();
 
  752     synchronized void updateFromGlobalEnabledStatus() throws HealthMonitorException {
 
  754         boolean previouslyEnabled = monitorIsEnabled();
 
  757         if (!UserPreferences.getIsMultiUserModeEnabled()) {
 
  758             isEnabled.set(
false);
 
  760             if (previouslyEnabled) {
 
  769             isEnabled.set(
false);
 
  771             if (previouslyEnabled) {
 
  779         if (previouslyEnabled && (connectionSettingsInUse != null)) {
 
  781                 CaseDbConnectionInfo currentSettings = UserPreferences.getDatabaseConnectionInfo();
 
  782                 if (!(connectionSettingsInUse.
getUserName().equals(currentSettings.getUserName())
 
  783                         && connectionSettingsInUse.
getPassword().equals(currentSettings.getPassword())
 
  784                         && connectionSettingsInUse.
getPort().equals(currentSettings.getPort())
 
  785                         && connectionSettingsInUse.
getHost().equals(currentSettings.getHost()))) {
 
  788             } 
catch (UserPreferencesException ex) {
 
  789                 throw new HealthMonitorException(
"Error reading database connection info", ex);
 
  794         if (currentlyEnabled != previouslyEnabled) {
 
  795             if (!currentlyEnabled) {
 
  796                 isEnabled.set(
false);
 
  815         try (Connection conn = 
connect();
 
  816                 Statement statement = conn.createStatement();
 
  817                 ResultSet resultSet = statement.executeQuery(
"SELECT value FROM db_info WHERE name='MONITOR_ENABLED'")) {
 
  819             if (resultSet.next()) {
 
  820                 return (resultSet.getBoolean(
"value"));
 
  822             throw new HealthMonitorException(
"No enabled status found in database");
 
  823         } 
catch (SQLException ex) {
 
  824             throw new HealthMonitorException(
"Error initializing database", ex);
 
  835         try (Connection conn = 
connect();
 
  836                 Statement statement = conn.createStatement();) {
 
  837             statement.execute(
"UPDATE db_info SET value='" + status + 
"' WHERE name='MONITOR_ENABLED'");
 
  838         } 
catch (SQLException ex) {
 
  839             throw new HealthMonitorException(
"Error setting enabled status", ex);
 
  853             throw new HealthMonitorException(
"Error getting database connection");
 
  855         ResultSet resultSet = null;
 
  857         try (Statement statement = conn.createStatement()) {
 
  858             int minorVersion = 0;
 
  859             int majorVersion = 0;
 
  860             resultSet = statement.executeQuery(
"SELECT value FROM db_info WHERE name='SCHEMA_MINOR_VERSION'");
 
  861             if (resultSet.next()) {
 
  862                 String minorVersionStr = resultSet.getString(
"value");
 
  864                     minorVersion = Integer.parseInt(minorVersionStr);
 
  865                 } 
catch (NumberFormatException ex) {
 
  866                     throw new HealthMonitorException(
"Bad value for schema minor version (" + minorVersionStr + 
") - database is corrupt");
 
  870             resultSet = statement.executeQuery(
"SELECT value FROM db_info WHERE name='SCHEMA_VERSION'");
 
  871             if (resultSet.next()) {
 
  872                 String majorVersionStr = resultSet.getString(
"value");
 
  874                     majorVersion = Integer.parseInt(majorVersionStr);
 
  875                 } 
catch (NumberFormatException ex) {
 
  876                     throw new HealthMonitorException(
"Bad value for schema version (" + majorVersionStr + 
") - database is corrupt");
 
  881         } 
catch (SQLException ex) {
 
  882             throw new HealthMonitorException(
"Error initializing database", ex);
 
  884             if (resultSet != null) {
 
  887                 } 
catch (SQLException ex) {
 
  888                     logger.log(Level.SEVERE, 
"Error closing result set", ex);
 
  893             } 
catch (SQLException ex) {
 
  894                 logger.log(Level.SEVERE, 
"Error closing Connection.", ex);
 
  907             throw new HealthMonitorException(
"Error getting database connection");
 
  910         try (Statement statement = conn.createStatement()) {
 
  911             conn.setAutoCommit(
false);
 
  913             statement.execute(
"CREATE TABLE IF NOT EXISTS timing_data (" 
  914                     + 
"id SERIAL PRIMARY KEY," 
  915                     + 
"name text NOT NULL," 
  916                     + 
"host text NOT NULL," 
  917                     + 
"timestamp bigint NOT NULL," 
  918                     + 
"count bigint NOT NULL," 
  919                     + 
"average double precision NOT NULL," 
  920                     + 
"max double precision NOT NULL," 
  921                     + 
"min double precision NOT NULL" 
  924             statement.execute(
"CREATE TABLE IF NOT EXISTS db_info (" 
  925                     + 
"id SERIAL PRIMARY KEY NOT NULL," 
  926                     + 
"name text NOT NULL," 
  927                     + 
"value text NOT NULL" 
  930             statement.execute(
"CREATE TABLE IF NOT EXISTS user_data (" 
  931                     + 
"id SERIAL PRIMARY KEY," 
  932                     + 
"host text NOT NULL," 
  933                     + 
"timestamp bigint NOT NULL," 
  934                     + 
"event_type int NOT NULL," 
  935                     + 
"is_examiner BOOLEAN NOT NULL," 
  936                     + 
"case_name text NOT NULL," 
  940             statement.execute(
"INSERT INTO db_info (name, value) VALUES ('SCHEMA_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.
getMajor() + 
"')");
 
  941             statement.execute(
"INSERT INTO db_info (name, value) VALUES ('SCHEMA_MINOR_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.
getMinor() + 
"')");
 
  942             statement.execute(
"INSERT INTO db_info (name, value) VALUES ('MONITOR_ENABLED', 'true')");
 
  945         } 
catch (SQLException ex) {
 
  948             } 
catch (SQLException ex2) {
 
  949                 logger.log(Level.SEVERE, 
"Rollback error");
 
  951             throw new HealthMonitorException(
"Error initializing database", ex);
 
  955             } 
catch (SQLException ex) {
 
  956                 logger.log(Level.SEVERE, 
"Error closing connection.", ex);
 
  965     static final class PeriodicHealthMonitorTask 
implements Runnable {
 
  981             getInstance().updateFromGlobalEnabledStatus();
 
  982             if (monitorIsEnabled()) {
 
  986         } 
catch (HealthMonitorException ex) {
 
  987             logger.log(Level.SEVERE, 
"Error recording health monitor metrics", ex); 
 
  994         switch (
Case.
Events.valueOf(evt.getPropertyName())) {
 
  997                 if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof 
Case)) {
 
 1001                 } 
else if ((null == evt.getOldValue()) && (evt.getNewValue() instanceof 
Case)) {
 
 1014     void populateDatabaseWithSampleData(
int nDays, 
int nNodes, 
boolean createVerificationData) 
throws HealthMonitorException {
 
 1016         if (!isEnabled.get()) {
 
 1017             throw new HealthMonitorException(
"Can't populate database - monitor not enabled");
 
 1023             throw new HealthMonitorException(
"Error getting database lock");
 
 1026         String[] metricNames = {
"Disk Reads: Hash calculation", 
"Database: getImages query", 
"Solr: Index chunk", 
"Solr: Connectivity check",
 
 1027             "Central Repository: Notable artifact query", 
"Central Repository: Bulk insert"}; 
 
 1029         Random rand = 
new Random();
 
 1031         long maxTimestamp = System.currentTimeMillis();
 
 1032         long millisPerHour = 1000 * 60 * 60;
 
 1033         long minTimestamp = maxTimestamp - (nDays * (millisPerHour * 24));
 
 1035         Connection conn = null;
 
 1039                 throw new HealthMonitorException(
"Error getting database connection");
 
 1042             try (Statement statement = conn.createStatement()) {
 
 1044                 statement.execute(
"DELETE FROM timing_data"); 
 
 1045             } 
catch (SQLException ex) {
 
 1046                 logger.log(Level.SEVERE, 
"Error clearing timing data", ex);
 
 1051             String addTimingInfoSql = 
"INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)";
 
 1052             try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) {
 
 1054                 for (String metricName : metricNames) {
 
 1056                     long baseIndex = rand.nextInt(900) + 100;
 
 1057                     int multiplier = rand.nextInt(5);
 
 1058                     long minIndexTimeNanos;
 
 1059                     switch (multiplier) {
 
 1061                             minIndexTimeNanos = baseIndex;
 
 1064                             minIndexTimeNanos = baseIndex * 1000;
 
 1067                             minIndexTimeNanos = baseIndex * 1000 * 1000;
 
 1071                     long maxIndexTimeOverMin = minIndexTimeNanos * 3;
 
 1073                     for (
int node = 0; node < nNodes; node++) {
 
 1075                         String host = 
"testHost" + node; 
 
 1078                         double maxCount = nDays * 24 + 1;
 
 1081                         for (
long timestamp = minTimestamp + rand.nextInt(1000 * 60 * 55); timestamp < maxTimestamp; timestamp += millisPerHour) {
 
 1088                             double slowNodeMultiplier = 1.0;
 
 1089                             if ((maxCount - count) <= 3 * 24) {
 
 1090                                 slowNodeMultiplier += (3 - (maxCount - count) / 24) * 0.33;
 
 1093                             if (!createVerificationData) {
 
 1096                                 int outlierVal = rand.nextInt(30);
 
 1097                                 long randVal = rand.nextLong();
 
 1101                                 if (outlierVal < 2) {
 
 1102                                     aveTime = minIndexTimeNanos + maxIndexTimeOverMin + randVal % maxIndexTimeOverMin;
 
 1103                                 } 
else if (outlierVal == 2) {
 
 1104                                     aveTime = (minIndexTimeNanos / 2) + randVal % (minIndexTimeNanos / 2);
 
 1105                                 } 
else if (outlierVal < 17) {
 
 1106                                     aveTime = minIndexTimeNanos + randVal % (maxIndexTimeOverMin / 2);
 
 1108                                     aveTime = minIndexTimeNanos + randVal % maxIndexTimeOverMin;
 
 1112                                     aveTime = aveTime * slowNodeMultiplier;
 
 1118                                 Calendar thisDate = 
new GregorianCalendar();
 
 1119                                 thisDate.setTimeInMillis(timestamp);
 
 1120                                 int day = thisDate.get(Calendar.DAY_OF_MONTH);
 
 1121                                 aveTime = day * 1000000;
 
 1124                             statement.setString(1, metricName);
 
 1125                             statement.setString(2, host);
 
 1126                             statement.setLong(3, timestamp);
 
 1127                             statement.setLong(4, 0);
 
 1128                             statement.setDouble(5, aveTime / 1000000);
 
 1129                             statement.setDouble(6, 0);
 
 1130                             statement.setDouble(7, 0);
 
 1132                             statement.execute();
 
 1136             } 
catch (SQLException ex) {
 
 1137                 throw new HealthMonitorException(
"Error saving metric data to database", ex);
 
 1144             } 
catch (SQLException ex) {
 
 1145                 logger.log(Level.SEVERE, 
"Error closing Connection.", ex);
 
 1149             } 
catch (CoordinationService.CoordinationServiceException ex) {
 
 1150                 throw new HealthMonitorException(
"Error releasing database lock", ex);
 
 1164     Map<String, List<DatabaseTimingResult>> getTimingMetricsFromDatabase(
long timeRange) 
throws HealthMonitorException {
 
 1169         if (!isEnabled.get()) {
 
 1170             throw new HealthMonitorException(
"Health Monitor is not enabled");
 
 1174         long minimumTimestamp = System.currentTimeMillis() - timeRange;
 
 1178                 throw new HealthMonitorException(
"Error getting database lock");
 
 1183                 throw new HealthMonitorException(
"Error getting database connection");
 
 1186             Map<String, List<DatabaseTimingResult>> resultMap = 
new HashMap<>();
 
 1188             try (Statement statement = conn.createStatement();
 
 1189                     ResultSet resultSet = statement.executeQuery(
"SELECT * FROM timing_data WHERE timestamp > " + minimumTimestamp)) {
 
 1191                 while (resultSet.next()) {
 
 1192                     String name = resultSet.getString(
"name");
 
 1193                     DatabaseTimingResult timingResult = 
new DatabaseTimingResult(resultSet);
 
 1195                     if (resultMap.containsKey(name)) {
 
 1196                         resultMap.get(name).add(timingResult);
 
 1198                         List<DatabaseTimingResult> resultList = 
new ArrayList<>();
 
 1199                         resultList.add(timingResult);
 
 1200                         resultMap.put(name, resultList);
 
 1204             } 
catch (SQLException ex) {
 
 1205                 throw new HealthMonitorException(
"Error reading timing metrics from database", ex);
 
 1209                 } 
catch (SQLException ex) {
 
 1210                     logger.log(Level.SEVERE, 
"Error closing Connection.", ex);
 
 1213         } 
catch (CoordinationService.CoordinationServiceException ex) {
 
 1214             throw new HealthMonitorException(
"Error getting database lock", ex);
 
 1227     List<UserData> getUserMetricsFromDatabase(
long timeRange) 
throws HealthMonitorException {
 
 1232         if (!isEnabled.get()) {
 
 1233             throw new HealthMonitorException(
"Health Monitor is not enabled");
 
 1237         long minimumTimestamp = System.currentTimeMillis() - timeRange;
 
 1241                 throw new HealthMonitorException(
"Error getting database lock");
 
 1244             List<UserData> resultList = 
new ArrayList<>();
 
 1246             try (Connection conn = 
connect();
 
 1247                     Statement statement = conn.createStatement();
 
 1248                     ResultSet resultSet = statement.executeQuery(
"SELECT * FROM user_data WHERE timestamp > " + minimumTimestamp)) {
 
 1250                 while (resultSet.next()) {
 
 1251                     resultList.add(
new UserData(resultSet));
 
 1254             } 
catch (SQLException ex) {
 
 1255                 throw new HealthMonitorException(
"Error reading user metrics from database", ex);
 
 1257         } 
catch (CoordinationService.CoordinationServiceException ex) {
 
 1258             throw new HealthMonitorException(
"Error getting database lock", ex);
 
 1277             throw new HealthMonitorException(
"Error acquiring database lock");
 
 1279             throw new HealthMonitorException(
"Error acquiring database lock", ex);
 
 1299             throw new HealthMonitorException(
"Error acquiring database lock");
 
 1301             throw new HealthMonitorException(
"Error acquiring database lock");
 
 1316         UserEvent(
int value) {
 
 1325         int getEventValue() {
 
 1338         static UserEvent valueOf(
int value) 
throws HealthMonitorException {
 
 1339             for (UserEvent v : UserEvent.values()) {
 
 1340                 if (v.value == value) {
 
 1344             throw new HealthMonitorException(
"Can not create UserEvent from unknown value " + value);
 
 1353         boolean caseIsOpen() {
 
 1354             return (this.equals(CASE_OPEN));
 
 1363         boolean userIsLoggedIn() {
 
 1366             return (!this.equals(LOG_OFF));
 
 1374     static class UserData 
implements Comparable<UserData> {
 
 1376         private final UserEvent eventType;
 
 1377         private long timestamp;
 
 1378         private final boolean isExaminer;
 
 1379         private final String hostname;
 
 1381         private String caseName;
 
 1389         private UserData(UserEvent eventType) {
 
 1390             this.eventType = eventType;
 
 1391             this.timestamp = System.currentTimeMillis();
 
 1392             this.isExaminer = (UserPreferences.SelectedMode.STANDALONE == UserPreferences.getMode());
 
 1398                 this.caseName = Case.getCurrentCaseThrows().getDisplayName();
 
 1399             } 
catch (NoCurrentCaseException ex) {
 
 1413         UserData(ResultSet resultSet) 
throws SQLException, HealthMonitorException {
 
 1414             this.timestamp = resultSet.getLong(
"timestamp");
 
 1415             this.hostname = resultSet.getString(
"host");
 
 1416             this.eventType = UserEvent.valueOf(resultSet.getInt(
"event_type"));
 
 1417             this.isExaminer = resultSet.getBoolean(
"is_examiner");
 
 1418             this.caseName = resultSet.getString(
"case_name");
 
 1419             this.username = resultSet.getString(
"username");
 
 1420             if (this.username == null) {
 
 1433         static UserData createDummyUserData(
long timestamp) {
 
 1434             UserData userData = 
new UserData(UserEvent.CASE_CLOSE);
 
 1435             userData.timestamp = timestamp;
 
 1444         long getTimestamp() {
 
 1453         String getHostname() {
 
 1462         UserEvent getEventType() {
 
 1471         boolean isExaminerNode() {
 
 1480         String getCaseName() {
 
 1489         String getUserName() {
 
 1494         public int compareTo(UserData otherData) {
 
 1495             return Long.compare(getTimestamp(), otherData.getTimestamp());
 
 1515             sum = metric.getDuration();
 
 1516             max = metric.getDuration();
 
 1517             min = metric.getDuration();
 
 1530         void addMetric(
TimingMetric metric) 
throws HealthMonitorException {
 
 1534             sum += metric.getDuration();
 
 1537             if (max < metric.getDuration()) {
 
 1538                 max = metric.getDuration();
 
 1542             if (min > metric.getDuration()) {
 
 1543                 min = metric.getDuration();
 
 1552         double getAverage() {
 
 1588     static class DatabaseTimingResult {
 
 1590         private final long timestamp; 
 
 1591         private final String hostname; 
 
 1592         private final long count; 
 
 1593         private final double average;   
 
 1594         private final double max;   
 
 1595         private final double min;   
 
 1597         DatabaseTimingResult(ResultSet resultSet) 
throws SQLException {
 
 1598             this.timestamp = resultSet.getLong(
"timestamp");
 
 1599             this.hostname = resultSet.getString(
"host");
 
 1600             this.count = resultSet.getLong(
"count");
 
 1601             this.average = resultSet.getDouble(
"average");
 
 1602             this.max = resultSet.getDouble(
"max");
 
 1603             this.min = resultSet.getDouble(
"min");
 
 1611         long getTimestamp() {
 
 1620         double getAverage() {
 
 1656         String getHostName() {
 
void writeCurrentStateToDatabase()
BasicDataSource connectionPool
int compareTo(VersionNumber vs)
CoordinationService.Lock getSharedDbLock()
CaseDbSchemaVersionNumber getVersion()
static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION
static void recordMetrics()
synchronized void startTimer()
static final String DATABASE_NAME
synchronized void deactivateMonitorLocally()
void performDatabaseQuery()
static boolean getIsMultiUserModeEnabled()
static CaseDbConnectionInfo getDatabaseConnectionInfo()
final Map< String, TimingInfo > timingInfoMap
boolean databaseIsInitialized()
CaseDbConnectionInfo connectionSettingsInUse
static TimingMetric getTimingMetric(String name)
boolean getGlobalEnabledStatusFromDB()
static final Logger logger
static void shutDownTaskExecutor(ExecutorService executor)
List< Image > getImages()
void initializeDatabaseSchema()
synchronized void activateMonitorLocally()
ScheduledThreadPoolExecutor healthMonitorOutputTimer
void setGlobalEnabledStatusInDB(boolean status)
void addTimingMetric(TimingMetric metric)
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static final int CONN_POOL_SIZE
SleuthkitCase getSleuthkitCase()
static void addPropertyChangeListener(PropertyChangeListener listener)
void setupConnectionPool()
static final long DATABASE_WRITE_INTERVAL
static final AtomicBoolean isEnabled
static void submitTimingMetric(TimingMetric metric)
static HealthMonitor instance
final List< UserData > userInfoList
void shutdownConnections()
synchronized static Logger getLogger(String name)
synchronized void stopTimer()
void upgradeDatabaseSchema()
CoordinationService.Lock getExclusiveDbLock()
static Case getCurrentCaseThrows()
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static synchronized CoordinationService getInstance()
static void submitNormalizedTimingMetric(TimingMetric metric, long normalization)
void gatherTimerBasedMetrics()
void propertyChange(PropertyChangeEvent evt)
void addUserEvent(UserEvent eventType)