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.List;
32 import java.util.HashMap;
33 import java.util.UUID;
34 import java.util.concurrent.ExecutorService;
35 import java.util.concurrent.Executors;
36 import java.util.concurrent.ScheduledThreadPoolExecutor;
37 import java.util.concurrent.TimeUnit;
38 import java.util.concurrent.atomic.AtomicBoolean;
39 import java.util.logging.Level;
40 import org.apache.commons.dbcp2.BasicDataSource;
50 import org.
sleuthkit.datamodel.CaseDbSchemaVersionNumber;
67 private final static String
MODULE_NAME =
"EnterpriseHealthMonitor";
71 =
new CaseDbSchemaVersionNumber(1, 0);
73 private static final AtomicBoolean
isEnabled =
new AtomicBoolean(
false);
89 timingInfoMap =
new HashMap<>();
92 healthMonitorExecutor = Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder().setNameFormat(HEALTH_MONITOR_EVENT_THREAD_NAME).build());
96 hostName = java.net.InetAddress.getLocalHost().getHostName();
97 }
catch (java.net.UnknownHostException ex) {
99 hostName = UUID.randomUUID().toString();
100 logger.log(Level.SEVERE,
"Unable to look up host name - falling back to UUID " + hostName, ex);
109 }
catch (HealthMonitorException ex) {
111 logger.log(Level.SEVERE,
"Health monitor activation failed - disabling health monitor");
118 isEnabled.set(
false);
127 if (instance == null) {
142 logger.log(Level.INFO,
"Activating Servies Health Monitor");
145 throw new HealthMonitorException(
"Multi user mode is not enabled - can not activate health monitor");
151 throw new HealthMonitorException(
"Error getting database lock");
166 throw new HealthMonitorException(
"Error releasing database lock", ex);
170 timingInfoMap.clear();
185 logger.log(Level.INFO,
"Deactivating Servies Health Monitor");
188 timingInfoMap.clear();
204 healthMonitorOutputTimer =
new ScheduledThreadPoolExecutor(1,
new ThreadFactoryBuilder().setNameFormat(
"health_monitor_timer").build());
205 healthMonitorOutputTimer.scheduleWithFixedDelay(
new DatabaseWriteTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES);
212 if(healthMonitorOutputTimer != null) {
221 static synchronized void startUpIfEnabled() throws HealthMonitorException {
230 static synchronized void setEnabled(
boolean enabled)
throws HealthMonitorException {
231 if(enabled == isEnabled.get()) {
240 ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY,
"true");
243 ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY,
"false");
244 isEnabled.set(
false);
260 if(isEnabled.get()) {
274 if(isEnabled.get() && (metric != null)) {
278 }
catch (HealthMonitorException ex) {
280 logger.log(Level.SEVERE,
"Error adding timing metric", ex);
295 if(isEnabled.get() && (metric != null)) {
298 metric.normalize(normalization);
300 }
catch (HealthMonitorException ex) {
302 logger.log(Level.SEVERE,
"Error adding timing metric", ex);
322 if(timingInfoMap.containsKey(metric.getName())) {
323 timingInfoMap.get(metric.getName()).addMetric(metric);
325 timingInfoMap.put(metric.getName(),
new TimingInfo(metric));
343 List<Image> images = skCase.getImages();
347 long normalization = images.size();
348 if (images.isEmpty()) {
350 }
else if (images.size() == 1){
352 }
else if (images.size() < 10) {
361 }
catch (TskCoreException ex) {
362 throw new HealthMonitorException(
"Error running getImages()", ex);
381 Map<String, TimingInfo> timingMapCopy;
386 if(! isEnabled.get()) {
393 timingInfoMap.clear();
397 if(timingMapCopy.keySet().isEmpty()) {
401 logger.log(Level.INFO,
"Writing health monitor metrics to database");
406 throw new HealthMonitorException(
"Error getting database lock");
411 throw new HealthMonitorException(
"Error getting database connection");
415 String addTimingInfoSql =
"INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)";
416 try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) {
418 for(String name:timingMapCopy.keySet()) {
421 statement.setString(1, name);
422 statement.setString(2, hostName);
423 statement.setLong(3, System.currentTimeMillis());
424 statement.setLong(4, info.getCount());
425 statement.setDouble(5, info.getAverage());
426 statement.setDouble(6, info.getMax());
427 statement.setDouble(7, info.getMin());
432 }
catch (SQLException ex) {
433 throw new HealthMonitorException(
"Error saving metric data to database", ex);
437 }
catch (SQLException ex) {
438 logger.log(Level.SEVERE,
"Error closing Connection.", ex);
442 throw new HealthMonitorException(
"Error releasing database lock", ex);
456 Class.forName(
"org.postgresql.Driver");
458 try (Connection connection = DriverManager.getConnection(
"jdbc:postgresql://" + db.getHost() +
":" + db.getPort() +
"/postgres", db.getUserName(), db.getPassword());
459 Statement statement = connection.createStatement();) {
460 String createCommand =
"SELECT 1 AS result FROM pg_database WHERE datname='" + DATABASE_NAME +
"'";
461 rs = statement.executeQuery(createCommand);
463 logger.log(Level.INFO,
"Existing Enterprise Health Monitor database found");
472 throw new HealthMonitorException(
"Failed check for health monitor database", ex);
485 Class.forName(
"org.postgresql.Driver");
486 try (Connection connection = DriverManager.getConnection(
"jdbc:postgresql://" + db.getHost() +
":" + db.getPort() +
"/postgres", db.getUserName(), db.getPassword());
487 Statement statement = connection.createStatement();) {
488 String createCommand =
"CREATE DATABASE \"" + DATABASE_NAME +
"\" OWNER \"" + db.getUserName() +
"\"";
489 statement.execute(createCommand);
491 logger.log(Level.INFO,
"Created new health monitor database " + DATABASE_NAME);
493 throw new HealthMonitorException(
"Failed to delete health monitor database", ex);
505 connectionPool =
new BasicDataSource();
506 connectionPool.setDriverClassName(
"org.postgresql.Driver");
508 StringBuilder connectionURL =
new StringBuilder();
509 connectionURL.append(
"jdbc:postgresql://");
510 connectionURL.append(db.getHost());
511 connectionURL.append(
":");
512 connectionURL.append(db.getPort());
513 connectionURL.append(
"/");
514 connectionURL.append(DATABASE_NAME);
516 connectionPool.setUrl(connectionURL.toString());
517 connectionPool.setUsername(db.getUserName());
518 connectionPool.setPassword(db.getPassword());
521 connectionPool.setInitialSize(3);
522 connectionPool.setMaxIdle(CONN_POOL_SIZE);
523 connectionPool.setValidationQuery(
"SELECT version()");
525 throw new HealthMonitorException(
"Error loading database configuration", ex);
536 if(connectionPool != null){
537 connectionPool.close();
538 connectionPool = null;
541 }
catch (SQLException ex) {
542 throw new HealthMonitorException(
"Failed to close existing database connections.", ex);
552 private Connection
connect() throws HealthMonitorException {
553 synchronized (
this) {
554 if (connectionPool == null) {
560 return connectionPool.getConnection();
561 }
catch (SQLException ex) {
562 throw new HealthMonitorException(
"Error getting connection from connection pool.", ex);
575 throw new HealthMonitorException(
"Error getting database connection");
577 ResultSet resultSet = null;
579 try (Statement statement = conn.createStatement()) {
580 resultSet = statement.executeQuery(
"SELECT value FROM db_info WHERE name='SCHEMA_VERSION'");
581 return resultSet.next();
582 }
catch (SQLException ex) {
586 if(resultSet != null) {
589 }
catch (SQLException ex) {
590 logger.log(Level.SEVERE,
"Error closing result set", ex);
595 }
catch (SQLException ex) {
596 logger.log(Level.SEVERE,
"Error closing Connection.", ex);
606 private CaseDbSchemaVersionNumber
getVersion() throws HealthMonitorException {
609 throw new HealthMonitorException(
"Error getting database connection");
611 ResultSet resultSet = null;
613 try (Statement statement = conn.createStatement()) {
614 int minorVersion = 0;
615 int majorVersion = 0;
616 resultSet = statement.executeQuery(
"SELECT value FROM db_info WHERE name='SCHEMA_MINOR_VERSION'");
617 if (resultSet.next()) {
618 String minorVersionStr = resultSet.getString(
"value");
620 minorVersion = Integer.parseInt(minorVersionStr);
621 }
catch (NumberFormatException ex) {
622 throw new HealthMonitorException(
"Bad value for schema minor version (" + minorVersionStr +
") - database is corrupt");
626 resultSet = statement.executeQuery(
"SELECT value FROM db_info WHERE name='SCHEMA_VERSION'");
627 if (resultSet.next()) {
628 String majorVersionStr = resultSet.getString(
"value");
630 majorVersion = Integer.parseInt(majorVersionStr);
631 }
catch (NumberFormatException ex) {
632 throw new HealthMonitorException(
"Bad value for schema version (" + majorVersionStr +
") - database is corrupt");
636 return new CaseDbSchemaVersionNumber(majorVersion, minorVersion);
637 }
catch (SQLException ex) {
638 throw new HealthMonitorException(
"Error initializing database", ex);
640 if(resultSet != null) {
643 }
catch (SQLException ex) {
644 logger.log(Level.SEVERE,
"Error closing result set", ex);
649 }
catch (SQLException ex) {
650 logger.log(Level.SEVERE,
"Error closing Connection.", ex);
662 throw new HealthMonitorException(
"Error getting database connection");
665 try (Statement statement = conn.createStatement()) {
666 conn.setAutoCommit(
false);
668 String createTimingTable =
669 "CREATE TABLE IF NOT EXISTS timing_data (" +
670 "id SERIAL PRIMARY KEY," +
671 "name text NOT NULL," +
672 "host text NOT NULL," +
673 "timestamp bigint NOT NULL," +
674 "count bigint NOT NULL," +
675 "average double precision NOT NULL," +
676 "max double precision NOT NULL," +
677 "min double precision NOT NULL" +
679 statement.execute(createTimingTable);
681 String createDbInfoTable =
682 "CREATE TABLE IF NOT EXISTS db_info (" +
683 "id SERIAL PRIMARY KEY NOT NULL," +
684 "name text NOT NULL," +
685 "value text NOT NULL" +
687 statement.execute(createDbInfoTable);
689 statement.execute(
"INSERT INTO db_info (name, value) VALUES ('SCHEMA_VERSION', '" +
CURRENT_DB_SCHEMA_VERSION.getMajor() +
"')");
690 statement.execute(
"INSERT INTO db_info (name, value) VALUES ('SCHEMA_MINOR_VERSION', '" +
CURRENT_DB_SCHEMA_VERSION.getMinor() +
"')");
693 }
catch (SQLException ex) {
696 }
catch (SQLException ex2) {
697 logger.log(Level.SEVERE,
"Rollback error");
699 throw new HealthMonitorException(
"Error initializing database", ex);
703 }
catch (SQLException ex) {
704 logger.log(Level.SEVERE,
"Error closing connection.", ex);
713 static final class DatabaseWriteTask
implements Runnable {
721 getInstance().gatherTimerBasedMetrics();
722 getInstance().writeCurrentStateToDatabase();
723 }
catch (HealthMonitorException ex) {
724 logger.log(Level.SEVERE,
"Error writing current metrics to database", ex);
732 switch (
Case.
Events.valueOf(evt.getPropertyName())) {
735 if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof
Case)) {
756 throw new HealthMonitorException(
"Error acquiring database lock");
758 throw new HealthMonitorException(
"Error acquiring database lock", ex);
776 throw new HealthMonitorException(
"Error acquiring database lock");
778 throw new HealthMonitorException(
"Error acquiring database lock");
798 sum = metric.getDuration();
799 max = metric.getDuration();
800 min = metric.getDuration();
810 void addMetric(
TimingMetric metric)
throws HealthMonitorException {
814 sum += metric.getDuration();
817 if(max < metric.getDuration()) {
818 max = metric.getDuration();
822 if(min > metric.getDuration()) {
823 min = metric.getDuration();
831 double getAverage() {
static final String IS_ENABLED_KEY
BasicDataSource connectionPool
synchronized void activateMonitor()
static void submitTimingMetric(TimingMetric metric)
CoordinationService.Lock getExclusiveDbLock()
void performDatabaseQuery()
static Case getOpenCase()
synchronized void stopTimer()
static final String HEALTH_MONITOR_EVENT_THREAD_NAME
EnterpriseHealthMonitor()
static final String MODULE_NAME
static boolean getIsMultiUserModeEnabled()
final ExecutorService healthMonitorExecutor
static CaseDbConnectionInfo getDatabaseConnectionInfo()
void addTimingMetric(TimingMetric metric)
void initializeDatabaseSchema()
static void shutDownTaskExecutor(ExecutorService executor)
final Map< String, TimingInfo > timingInfoMap
void propertyChange(PropertyChangeEvent evt)
static void submitNormalizedTimingMetric(TimingMetric metric, long normalization)
void writeCurrentStateToDatabase()
static final String DATABASE_NAME
CoordinationService.Lock getSharedDbLock()
static TimingMetric getTimingMetric(String name)
CaseDbSchemaVersionNumber getVersion()
synchronized void deactivateMonitor()
static final Logger logger
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
ScheduledThreadPoolExecutor healthMonitorOutputTimer
SleuthkitCase getSleuthkitCase()
static final AtomicBoolean isEnabled
static void addPropertyChangeListener(PropertyChangeListener listener)
static final int CONN_POOL_SIZE
void shutdownConnections()
static final long DATABASE_WRITE_INTERVAL
synchronized void startTimer()
static String getConfigSetting(String moduleName, String settingName)
static EnterpriseHealthMonitor instance
synchronized static Logger getLogger(String name)
void gatherTimerBasedMetrics()
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
static synchronized CoordinationService getInstance()
boolean databaseIsInitialized()
static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION
void setupConnectionPool()
static boolean settingExists(String moduleName, String settingName)