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;
 
   51 import org.
sleuthkit.datamodel.CaseDbSchemaVersionNumber;
 
   70     private final static AtomicBoolean 
isEnabled = 
new AtomicBoolean(
false);
 
   85         timingInfoMap = 
new HashMap<>();
 
   89         userInfoList = 
new ArrayList<>();
 
   93             hostName = java.net.InetAddress.getLocalHost().getHostName();
 
   94         } 
catch (java.net.UnknownHostException ex) {
 
   96             hostName = UUID.randomUUID().toString();
 
   97             logger.log(Level.SEVERE, 
"Unable to look up host name - falling back to UUID " + hostName, ex);
 
  101         updateFromGlobalEnabledStatus();
 
  114     synchronized static HealthMonitor getInstance() throws HealthMonitorException {
 
  115         if (instance == null) {
 
  131         logger.log(Level.INFO, 
"Activating Servies Health Monitor");
 
  137             throw new HealthMonitorException(
"Multi user mode is not enabled - can not activate health monitor");
 
  143                 throw new HealthMonitorException(
"Error getting database lock");
 
  157             if (!CURRENT_DB_SCHEMA_VERSION.equals(
getVersion())) {
 
  162             throw new HealthMonitorException(
"Error releasing database lock", ex);
 
  166         timingInfoMap.clear();
 
  167         userInfoList.clear();
 
  175         logger.log(Level.INFO, 
"Upgrading Health Monitor database");
 
  176         CaseDbSchemaVersionNumber currentSchema = 
getVersion();
 
  180             throw new HealthMonitorException(
"Error getting database connection");
 
  183         try (Statement statement = conn.createStatement()) {
 
  184             conn.setAutoCommit(
false);
 
  188             if (currentSchema.compareTo(
new CaseDbSchemaVersionNumber(1, 1)) < 0) {
 
  191                 statement.execute(
"CREATE TABLE IF NOT EXISTS user_data (" 
  192                         + 
"id SERIAL PRIMARY KEY," 
  193                         + 
"host text NOT NULL," 
  194                         + 
"timestamp bigint NOT NULL," 
  195                         + 
"event_type int NOT NULL," 
  196                         + 
"is_examiner boolean NOT NULL," 
  197                         + 
"case_name text NOT NULL" 
  202             statement.execute(
"UPDATE db_info SET value='" + CURRENT_DB_SCHEMA_VERSION.getMajor() + 
"' WHERE name='SCHEMA_VERSION'");
 
  203             statement.execute(
"UPDATE db_info SET value='" + CURRENT_DB_SCHEMA_VERSION.getMinor() + 
"' WHERE name='SCHEMA_MINOR_VERSION'");
 
  206             logger.log(Level.INFO, 
"Health Monitor database upgraded to version {0}", CURRENT_DB_SCHEMA_VERSION.toString());
 
  207         } 
catch (SQLException ex) {
 
  210             } 
catch (SQLException ex2) {
 
  211                 logger.log(Level.SEVERE, 
"Rollback error");
 
  213             throw new HealthMonitorException(
"Error upgrading database", ex);
 
  217             } 
catch (SQLException ex) {
 
  218                 logger.log(Level.SEVERE, 
"Error closing connection.", ex);
 
  233         logger.log(Level.INFO, 
"Deactivating Servies Health Monitor");
 
  236         timingInfoMap.clear();
 
  250         healthMonitorOutputTimer = 
new ScheduledThreadPoolExecutor(1, 
new ThreadFactoryBuilder().setNameFormat(
"health_monitor_timer").build());
 
  251         healthMonitorOutputTimer.scheduleWithFixedDelay(
new PeriodicHealthMonitorTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES);
 
  258         if (healthMonitorOutputTimer != null) {
 
  269     static synchronized void startUpIfEnabled() throws HealthMonitorException {
 
  279     static synchronized void shutdown() throws HealthMonitorException {
 
  291     static synchronized void setEnabled(
boolean enabled) 
throws HealthMonitorException {
 
  292         if (enabled == isEnabled.get()) {
 
  304             if (isEnabled.get()) {
 
  308             isEnabled.set(
false);
 
  325         if (isEnabled.get()) {
 
  339         if (isEnabled.get() && (metric != null)) {
 
  343             } 
catch (HealthMonitorException ex) {
 
  345                 logger.log(Level.SEVERE, 
"Error adding timing metric", ex);
 
  362         if (isEnabled.get() && (metric != null)) {
 
  365                 metric.normalize(normalization);
 
  367             } 
catch (HealthMonitorException ex) {
 
  369                 logger.log(Level.SEVERE, 
"Error adding timing metric", ex);
 
  384         synchronized (
this) {
 
  391             if (timingInfoMap.containsKey(metric.getName())) {
 
  392                 timingInfoMap.get(metric.getName()).addMetric(metric);
 
  394                 timingInfoMap.put(metric.getName(), 
new TimingInfo(metric));
 
  405         UserData userInfo = 
new UserData(eventType);
 
  406         synchronized (
this) {
 
  407             userInfoList.add(userInfo);
 
  424             List<Image> images = skCase.getImages();
 
  428             long normalization = images.size();
 
  429             if (images.isEmpty()) {
 
  431             } 
else if (images.size() == 1) {
 
  433             } 
else if (images.size() < 10) {
 
  442         } 
catch (TskCoreException ex) {
 
  443             throw new HealthMonitorException(
"Error running getImages()", ex);
 
  463         Map<String, TimingInfo> timingMapCopy;
 
  464         List<UserData> userDataCopy;
 
  468         synchronized (
this) {
 
  469             if (!isEnabled.get()) {
 
  476             timingInfoMap.clear();
 
  479             userInfoList.clear();
 
  483         if (timingMapCopy.keySet().isEmpty() && userDataCopy.isEmpty()) {
 
  487         logger.log(Level.INFO, 
"Writing health monitor metrics to database");
 
  492                 throw new HealthMonitorException(
"Error getting database lock");
 
  497                 throw new HealthMonitorException(
"Error getting database connection");
 
  501             String addTimingInfoSql = 
"INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)";
 
  502             String addUserInfoSql = 
"INSERT INTO user_data (host, timestamp, event_type, is_examiner, case_name) VALUES (?, ?, ?, ?, ?)";
 
  503             try (PreparedStatement timingStatement = conn.prepareStatement(addTimingInfoSql);
 
  504                     PreparedStatement userStatement = conn.prepareStatement(addUserInfoSql)) {
 
  506                 for (String name : timingMapCopy.keySet()) {
 
  509                     timingStatement.setString(1, name);
 
  510                     timingStatement.setString(2, hostName);
 
  511                     timingStatement.setLong(3, System.currentTimeMillis());
 
  512                     timingStatement.setLong(4, info.getCount());
 
  513                     timingStatement.setDouble(5, info.getAverage());
 
  514                     timingStatement.setDouble(6, info.getMax());
 
  515                     timingStatement.setDouble(7, info.getMin());
 
  517                     timingStatement.execute();
 
  520                 for (UserData userInfo : userDataCopy) {
 
  521                     userStatement.setString(1, hostName);
 
  522                     userStatement.setLong(2, userInfo.getTimestamp());
 
  523                     userStatement.setInt(3, userInfo.getEventType().getEventValue());
 
  524                     userStatement.setBoolean(4, userInfo.isExaminerNode());
 
  525                     userStatement.setString(5, userInfo.getCaseName());
 
  526                     userStatement.execute();
 
  529             } 
catch (SQLException ex) {
 
  530                 throw new HealthMonitorException(
"Error saving metric data to database", ex);
 
  534                 } 
catch (SQLException ex) {
 
  535                     logger.log(Level.SEVERE, 
"Error closing Connection.", ex);
 
  539             throw new HealthMonitorException(
"Error releasing database lock", ex);
 
  555             Class.forName(
"org.postgresql.Driver"); 
 
  557             try (Connection connection = DriverManager.getConnection(
"jdbc:postgresql://" + db.getHost() + 
":" + db.getPort() + 
"/postgres", db.getUserName(), db.getPassword()); 
 
  558                     Statement statement = connection.createStatement();) {
 
  559                 String createCommand = 
"SELECT 1 AS result FROM pg_database WHERE datname='" + DATABASE_NAME + 
"'";
 
  560                 rs = statement.executeQuery(createCommand);
 
  570             throw new HealthMonitorException(
"Failed check for health monitor database", ex);
 
  584             Class.forName(
"org.postgresql.Driver"); 
 
  585             try (Connection connection = DriverManager.getConnection(
"jdbc:postgresql://" + db.getHost() + 
":" + db.getPort() + 
"/postgres", db.getUserName(), db.getPassword()); 
 
  586                     Statement statement = connection.createStatement();) {
 
  587                 String createCommand = 
"CREATE DATABASE \"" + DATABASE_NAME + 
"\" OWNER \"" + db.getUserName() + 
"\""; 
 
  588                 statement.execute(createCommand);
 
  590             logger.log(Level.INFO, 
"Created new health monitor database " + DATABASE_NAME);
 
  592             throw new HealthMonitorException(
"Failed to delete health monitor database", ex);
 
  604             connectionSettingsInUse = db;
 
  606             connectionPool = 
new BasicDataSource();
 
  607             connectionPool.setDriverClassName(
"org.postgresql.Driver");
 
  609             StringBuilder connectionURL = 
new StringBuilder();
 
  610             connectionURL.append(
"jdbc:postgresql://");
 
  611             connectionURL.append(db.getHost());
 
  612             connectionURL.append(
":");
 
  613             connectionURL.append(db.getPort());
 
  614             connectionURL.append(
"/");
 
  615             connectionURL.append(DATABASE_NAME);
 
  617             connectionPool.setUrl(connectionURL.toString());
 
  618             connectionPool.setUsername(db.getUserName());
 
  619             connectionPool.setPassword(db.getPassword());
 
  622             connectionPool.setInitialSize(3); 
 
  623             connectionPool.setMaxIdle(CONN_POOL_SIZE); 
 
  624             connectionPool.setValidationQuery(
"SELECT version()");
 
  626             throw new HealthMonitorException(
"Error loading database configuration", ex);
 
  637             synchronized (
this) {
 
  638                 if (connectionPool != null) {
 
  639                     connectionPool.close();
 
  640                     connectionPool = null; 
 
  643         } 
catch (SQLException ex) {
 
  644             throw new HealthMonitorException(
"Failed to close existing database connections.", ex); 
 
  655     private Connection 
connect() throws HealthMonitorException {
 
  656         synchronized (
this) {
 
  657             if (connectionPool == null) {
 
  663             return connectionPool.getConnection();
 
  664         } 
catch (SQLException ex) {
 
  665             throw new HealthMonitorException(
"Error getting connection from connection pool.", ex); 
 
  680             throw new HealthMonitorException(
"Error getting database connection");
 
  682         ResultSet resultSet = null;
 
  684         try (Statement statement = conn.createStatement()) {
 
  685             resultSet = statement.executeQuery(
"SELECT value FROM db_info WHERE name='SCHEMA_VERSION'");
 
  686             return resultSet.next();
 
  687         } 
catch (SQLException ex) {
 
  691             if (resultSet != null) {
 
  694                 } 
catch (SQLException ex) {
 
  695                     logger.log(Level.SEVERE, 
"Error closing result set", ex);
 
  700             } 
catch (SQLException ex) {
 
  701                 logger.log(Level.SEVERE, 
"Error closing Connection.", ex);
 
  712     static boolean monitorIsEnabled() {
 
  713         return isEnabled.get();
 
  722     synchronized void updateFromGlobalEnabledStatus() throws HealthMonitorException {
 
  724         boolean previouslyEnabled = monitorIsEnabled();
 
  727         if (!UserPreferences.getIsMultiUserModeEnabled()) {
 
  728             isEnabled.set(
false);
 
  730             if (previouslyEnabled) {
 
  739             isEnabled.set(
false);
 
  741             if (previouslyEnabled) {
 
  749         if (previouslyEnabled && (connectionSettingsInUse != null)) {
 
  751                 CaseDbConnectionInfo currentSettings = UserPreferences.getDatabaseConnectionInfo();
 
  752                 if (!(connectionSettingsInUse.getUserName().equals(currentSettings.getUserName())
 
  753                         && connectionSettingsInUse.getPassword().equals(currentSettings.getPassword())
 
  754                         && connectionSettingsInUse.getPort().equals(currentSettings.getPort())
 
  755                         && connectionSettingsInUse.getHost().equals(currentSettings.getHost()))) {
 
  758             } 
catch (UserPreferencesException ex) {
 
  759                 throw new HealthMonitorException(
"Error reading database connection info", ex);
 
  764         if (currentlyEnabled != previouslyEnabled) {
 
  765             if (!currentlyEnabled) {
 
  766                 isEnabled.set(
false);
 
  785         try (Connection conn = 
connect();
 
  786                 Statement statement = conn.createStatement();
 
  787                 ResultSet resultSet = statement.executeQuery(
"SELECT value FROM db_info WHERE name='MONITOR_ENABLED'")) {
 
  789             if (resultSet.next()) {
 
  790                 return (resultSet.getBoolean(
"value"));
 
  792             throw new HealthMonitorException(
"No enabled status found in database");
 
  793         } 
catch (SQLException ex) {
 
  794             throw new HealthMonitorException(
"Error initializing database", ex);
 
  805         try (Connection conn = 
connect();
 
  806                 Statement statement = conn.createStatement();) {
 
  807             statement.execute(
"UPDATE db_info SET value='" + status + 
"' WHERE name='MONITOR_ENABLED'");
 
  808         } 
catch (SQLException ex) {
 
  809             throw new HealthMonitorException(
"Error setting enabled status", ex);
 
  820     private CaseDbSchemaVersionNumber 
getVersion() throws HealthMonitorException {
 
  823             throw new HealthMonitorException(
"Error getting database connection");
 
  825         ResultSet resultSet = null;
 
  827         try (Statement statement = conn.createStatement()) {
 
  828             int minorVersion = 0;
 
  829             int majorVersion = 0;
 
  830             resultSet = statement.executeQuery(
"SELECT value FROM db_info WHERE name='SCHEMA_MINOR_VERSION'");
 
  831             if (resultSet.next()) {
 
  832                 String minorVersionStr = resultSet.getString(
"value");
 
  834                     minorVersion = Integer.parseInt(minorVersionStr);
 
  835                 } 
catch (NumberFormatException ex) {
 
  836                     throw new HealthMonitorException(
"Bad value for schema minor version (" + minorVersionStr + 
") - database is corrupt");
 
  840             resultSet = statement.executeQuery(
"SELECT value FROM db_info WHERE name='SCHEMA_VERSION'");
 
  841             if (resultSet.next()) {
 
  842                 String majorVersionStr = resultSet.getString(
"value");
 
  844                     majorVersion = Integer.parseInt(majorVersionStr);
 
  845                 } 
catch (NumberFormatException ex) {
 
  846                     throw new HealthMonitorException(
"Bad value for schema version (" + majorVersionStr + 
") - database is corrupt");
 
  850             return new CaseDbSchemaVersionNumber(majorVersion, minorVersion);
 
  851         } 
catch (SQLException ex) {
 
  852             throw new HealthMonitorException(
"Error initializing database", ex);
 
  854             if (resultSet != null) {
 
  857                 } 
catch (SQLException ex) {
 
  858                     logger.log(Level.SEVERE, 
"Error closing result set", ex);
 
  863             } 
catch (SQLException ex) {
 
  864                 logger.log(Level.SEVERE, 
"Error closing Connection.", ex);
 
  877             throw new HealthMonitorException(
"Error getting database connection");
 
  880         try (Statement statement = conn.createStatement()) {
 
  881             conn.setAutoCommit(
false);
 
  883             statement.execute(
"CREATE TABLE IF NOT EXISTS timing_data (" 
  884                     + 
"id SERIAL PRIMARY KEY," 
  885                     + 
"name text NOT NULL," 
  886                     + 
"host text NOT NULL," 
  887                     + 
"timestamp bigint NOT NULL," 
  888                     + 
"count bigint NOT NULL," 
  889                     + 
"average double precision NOT NULL," 
  890                     + 
"max double precision NOT NULL," 
  891                     + 
"min double precision NOT NULL" 
  894             statement.execute(
"CREATE TABLE IF NOT EXISTS db_info (" 
  895                     + 
"id SERIAL PRIMARY KEY NOT NULL," 
  896                     + 
"name text NOT NULL," 
  897                     + 
"value text NOT NULL" 
  900             statement.execute(
"CREATE TABLE IF NOT EXISTS user_data (" 
  901                     + 
"id SERIAL PRIMARY KEY," 
  902                     + 
"host text NOT NULL," 
  903                     + 
"timestamp bigint NOT NULL," 
  904                     + 
"event_type int NOT NULL," 
  905                     + 
"is_examiner BOOLEAN NOT NULL," 
  906                     + 
"case_name text NOT NULL" 
  909             statement.execute(
"INSERT INTO db_info (name, value) VALUES ('SCHEMA_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMajor() + 
"')");
 
  910             statement.execute(
"INSERT INTO db_info (name, value) VALUES ('SCHEMA_MINOR_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMinor() + 
"')");
 
  911             statement.execute(
"INSERT INTO db_info (name, value) VALUES ('MONITOR_ENABLED', 'true')");
 
  914         } 
catch (SQLException ex) {
 
  917             } 
catch (SQLException ex2) {
 
  918                 logger.log(Level.SEVERE, 
"Rollback error");
 
  920             throw new HealthMonitorException(
"Error initializing database", ex);
 
  924             } 
catch (SQLException ex) {
 
  925                 logger.log(Level.SEVERE, 
"Error closing connection.", ex);
 
  934     static final class PeriodicHealthMonitorTask 
implements Runnable {
 
  950             getInstance().updateFromGlobalEnabledStatus();
 
  951             if (monitorIsEnabled()) {
 
  955         } 
catch (HealthMonitorException ex) {
 
  956             logger.log(Level.SEVERE, 
"Error performing periodic task", ex); 
 
  963         switch (
Case.
Events.valueOf(evt.getPropertyName())) {
 
  966                 if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof 
Case)) {
 
  970                 } 
else if ((null == evt.getOldValue()) && (evt.getNewValue() instanceof 
Case)) {
 
  983     void populateDatabaseWithSampleData(
int nDays, 
int nNodes, 
boolean createVerificationData) 
throws HealthMonitorException {
 
  985         if (!isEnabled.get()) {
 
  986             throw new HealthMonitorException(
"Can't populate database - monitor not enabled");
 
  992             throw new HealthMonitorException(
"Error getting database lock");
 
  995         String[] metricNames = {
"Disk Reads: Hash calculation", 
"Database: getImages query", 
"Solr: Index chunk", 
"Solr: Connectivity check",
 
  996             "Correlation Engine: Notable artifact query", 
"Correlation Engine: Bulk insert"}; 
 
  998         Random rand = 
new Random();
 
 1000         long maxTimestamp = System.currentTimeMillis();
 
 1001         long millisPerHour = 1000 * 60 * 60;
 
 1002         long minTimestamp = maxTimestamp - (nDays * (millisPerHour * 24));
 
 1004         Connection conn = null;
 
 1008                 throw new HealthMonitorException(
"Error getting database connection");
 
 1011             try (Statement statement = conn.createStatement()) {
 
 1013                 statement.execute(
"DELETE FROM timing_data"); 
 
 1014             } 
catch (SQLException ex) {
 
 1015                 logger.log(Level.SEVERE, 
"Error clearing timing data", ex);
 
 1020             String addTimingInfoSql = 
"INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)";
 
 1021             try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) {
 
 1023                 for (String metricName : metricNames) {
 
 1025                     long baseIndex = rand.nextInt(900) + 100;
 
 1026                     int multiplier = rand.nextInt(5);
 
 1027                     long minIndexTimeNanos;
 
 1028                     switch (multiplier) {
 
 1030                             minIndexTimeNanos = baseIndex;
 
 1033                             minIndexTimeNanos = baseIndex * 1000;
 
 1036                             minIndexTimeNanos = baseIndex * 1000 * 1000;
 
 1040                     long maxIndexTimeOverMin = minIndexTimeNanos * 3;
 
 1042                     for (
int node = 0; node < nNodes; node++) {
 
 1044                         String host = 
"testHost" + node; 
 
 1047                         double maxCount = nDays * 24 + 1;
 
 1050                         for (
long timestamp = minTimestamp + rand.nextInt(1000 * 60 * 55); timestamp < maxTimestamp; timestamp += millisPerHour) {
 
 1057                             double slowNodeMultiplier = 1.0;
 
 1058                             if ((maxCount - count) <= 3 * 24) {
 
 1059                                 slowNodeMultiplier += (3 - (maxCount - count) / 24) * 0.33;
 
 1062                             if (!createVerificationData) {
 
 1065                                 int outlierVal = rand.nextInt(30);
 
 1066                                 long randVal = rand.nextLong();
 
 1070                                 if (outlierVal < 2) {
 
 1071                                     aveTime = minIndexTimeNanos + maxIndexTimeOverMin + randVal % maxIndexTimeOverMin;
 
 1072                                 } 
else if (outlierVal == 2) {
 
 1073                                     aveTime = (minIndexTimeNanos / 2) + randVal % (minIndexTimeNanos / 2);
 
 1074                                 } 
else if (outlierVal < 17) {
 
 1075                                     aveTime = minIndexTimeNanos + randVal % (maxIndexTimeOverMin / 2);
 
 1077                                     aveTime = minIndexTimeNanos + randVal % maxIndexTimeOverMin;
 
 1081                                     aveTime = aveTime * slowNodeMultiplier;
 
 1087                                 Calendar thisDate = 
new GregorianCalendar();
 
 1088                                 thisDate.setTimeInMillis(timestamp);
 
 1089                                 int day = thisDate.get(Calendar.DAY_OF_MONTH);
 
 1090                                 aveTime = day * 1000000;
 
 1093                             statement.setString(1, metricName);
 
 1094                             statement.setString(2, host);
 
 1095                             statement.setLong(3, timestamp);
 
 1096                             statement.setLong(4, 0);
 
 1097                             statement.setDouble(5, aveTime / 1000000);
 
 1098                             statement.setDouble(6, 0);
 
 1099                             statement.setDouble(7, 0);
 
 1101                             statement.execute();
 
 1105             } 
catch (SQLException ex) {
 
 1106                 throw new HealthMonitorException(
"Error saving metric data to database", ex);
 
 1113             } 
catch (SQLException ex) {
 
 1114                 logger.log(Level.SEVERE, 
"Error closing Connection.", ex);
 
 1118             } 
catch (CoordinationService.CoordinationServiceException ex) {
 
 1119                 throw new HealthMonitorException(
"Error releasing database lock", ex);
 
 1133     Map<String, List<DatabaseTimingResult>> getTimingMetricsFromDatabase(
long timeRange) 
throws HealthMonitorException {
 
 1138         if (!isEnabled.get()) {
 
 1139             throw new HealthMonitorException(
"Health Monitor is not enabled");
 
 1143         long minimumTimestamp = System.currentTimeMillis() - timeRange;
 
 1147                 throw new HealthMonitorException(
"Error getting database lock");
 
 1152                 throw new HealthMonitorException(
"Error getting database connection");
 
 1155             Map<String, List<DatabaseTimingResult>> resultMap = 
new HashMap<>();
 
 1157             try (Statement statement = conn.createStatement();
 
 1158                     ResultSet resultSet = statement.executeQuery(
"SELECT * FROM timing_data WHERE timestamp > " + minimumTimestamp)) {
 
 1160                 while (resultSet.next()) {
 
 1161                     String name = resultSet.getString(
"name");
 
 1162                     DatabaseTimingResult timingResult = 
new DatabaseTimingResult(resultSet);
 
 1164                     if (resultMap.containsKey(name)) {
 
 1165                         resultMap.get(name).add(timingResult);
 
 1167                         List<DatabaseTimingResult> resultList = 
new ArrayList<>();
 
 1168                         resultList.add(timingResult);
 
 1169                         resultMap.put(name, resultList);
 
 1173             } 
catch (SQLException ex) {
 
 1174                 throw new HealthMonitorException(
"Error reading timing metrics from database", ex);
 
 1178                 } 
catch (SQLException ex) {
 
 1179                     logger.log(Level.SEVERE, 
"Error closing Connection.", ex);
 
 1182         } 
catch (CoordinationService.CoordinationServiceException ex) {
 
 1183             throw new HealthMonitorException(
"Error getting database lock", ex);
 
 1196     List<UserData> getUserMetricsFromDatabase(
long timeRange) 
throws HealthMonitorException {
 
 1201         if (!isEnabled.get()) {
 
 1202             throw new HealthMonitorException(
"Health Monitor is not enabled");
 
 1206         long minimumTimestamp = System.currentTimeMillis() - timeRange;
 
 1210                 throw new HealthMonitorException(
"Error getting database lock");
 
 1213             List<UserData> resultList = 
new ArrayList<>();
 
 1215             try (Connection conn = 
connect();
 
 1216                     Statement statement = conn.createStatement();
 
 1217                     ResultSet resultSet = statement.executeQuery(
"SELECT * FROM user_data WHERE timestamp > " + minimumTimestamp)) {
 
 1219                 while (resultSet.next()) {
 
 1220                     resultList.add(
new UserData(resultSet));
 
 1223             } 
catch (SQLException ex) {
 
 1224                 throw new HealthMonitorException(
"Error reading user metrics from database", ex);
 
 1226         } 
catch (CoordinationService.CoordinationServiceException ex) {
 
 1227             throw new HealthMonitorException(
"Error getting database lock", ex);
 
 1246             throw new HealthMonitorException(
"Error acquiring database lock");
 
 1248             throw new HealthMonitorException(
"Error acquiring database lock", ex);
 
 1268             throw new HealthMonitorException(
"Error acquiring database lock");
 
 1270             throw new HealthMonitorException(
"Error acquiring database lock");
 
 1285         UserEvent(
int value) {
 
 1294         int getEventValue() {
 
 1307         static UserEvent valueOf(
int value) 
throws HealthMonitorException {
 
 1308             for (UserEvent v : UserEvent.values()) {
 
 1309                 if (v.value == value) {
 
 1313             throw new HealthMonitorException(
"Can not create UserEvent from unknown value " + value);
 
 1322         boolean caseIsOpen() {
 
 1323             return (this.equals(CASE_OPEN));
 
 1332         boolean userIsLoggedIn() {
 
 1335             return (!this.equals(LOG_OFF));
 
 1343     static class UserData {
 
 1345         private final UserEvent eventType;
 
 1346         private long timestamp;
 
 1347         private final boolean isExaminer;
 
 1348         private final String hostname;
 
 1349         private String caseName;
 
 1357         private UserData(UserEvent eventType) {
 
 1358             this.eventType = eventType;
 
 1359             this.timestamp = System.currentTimeMillis();
 
 1360             this.isExaminer = (UserPreferences.SelectedMode.STANDALONE == UserPreferences.getMode());
 
 1365                 this.caseName = Case.getCurrentCaseThrows().getDisplayName();
 
 1366             } 
catch (NoCurrentCaseException ex) {
 
 1380         UserData(ResultSet resultSet) 
throws SQLException, HealthMonitorException {
 
 1381             this.timestamp = resultSet.getLong(
"timestamp");
 
 1382             this.hostname = resultSet.getString(
"host");
 
 1383             this.eventType = UserEvent.valueOf(resultSet.getInt(
"event_type"));
 
 1384             this.isExaminer = resultSet.getBoolean(
"is_examiner");
 
 1385             this.caseName = resultSet.getString(
"case_name");
 
 1396         static UserData createDummyUserData(
long timestamp) {
 
 1397             UserData userData = 
new UserData(UserEvent.CASE_CLOSE);
 
 1398             userData.timestamp = timestamp;
 
 1407         long getTimestamp() {
 
 1416         String getHostname() {
 
 1425         UserEvent getEventType() {
 
 1434         boolean isExaminerNode() {
 
 1443         String getCaseName() {
 
 1464             sum = metric.getDuration();
 
 1465             max = metric.getDuration();
 
 1466             min = metric.getDuration();
 
 1479         void addMetric(
TimingMetric metric) 
throws HealthMonitorException {
 
 1483             sum += metric.getDuration();
 
 1486             if (max < metric.getDuration()) {
 
 1487                 max = metric.getDuration();
 
 1491             if (min > metric.getDuration()) {
 
 1492                 min = metric.getDuration();
 
 1501         double getAverage() {
 
 1537     static class DatabaseTimingResult {
 
 1539         private final long timestamp; 
 
 1540         private final String hostname; 
 
 1541         private final long count; 
 
 1542         private final double average;   
 
 1543         private final double max;   
 
 1544         private final double min;   
 
 1546         DatabaseTimingResult(ResultSet resultSet) 
throws SQLException {
 
 1547             this.timestamp = resultSet.getLong(
"timestamp");
 
 1548             this.hostname = resultSet.getString(
"host");
 
 1549             this.count = resultSet.getLong(
"count");
 
 1550             this.average = resultSet.getDouble(
"average");
 
 1551             this.max = resultSet.getDouble(
"max");
 
 1552             this.min = resultSet.getDouble(
"min");
 
 1560         long getTimestamp() {
 
 1569         double getAverage() {
 
 1605         String getHostName() {
 
void writeCurrentStateToDatabase()
BasicDataSource connectionPool
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)
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)