selectedVertices) {
+ super(selectedVertices.size() > 1 ? Bundle.VisualizationPanel_lockAction_pluralText() : Bundle.VisualizationPanel_lockAction_singularText(),
+ lockIcon);
+ this.selectedVertices = selectedVertices;
+ }
+
+ @Override
+ public void actionPerformed(final ActionEvent event) {
+ lockedVertexModel.lock(selectedVertices);
+ }
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java
index 7561e3fa75..ce2721e647 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java
@@ -503,7 +503,6 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
rtfbodyTextPane.setText("");
htmlbodyTextPane.setText("");
textbodyTextArea.setText("");
- drp.setNode(null);
showImagesToggleButton.setEnabled(false);
msgbodyTabbedPane.setEnabled(false);
}
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
index 535c88ce61..245fe1744d 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
@@ -24,7 +24,7 @@ DataContentViewerString.pageLabel2.text=Page
# Product Information panel
LBL_Description=\n Product Version: {0} ({9})
Sleuth Kit Version: {7}
Netbeans RCP Build: {8}
Java: {1}; {2}
System: {3}; {4}; {5}
Userdir: {6}
Format_OperatingSystem_Value={0} version {1} running on {2}
-LBL_Copyright=Autopsy™ is a digital forensics platform based on The Sleuth Kit™ and other tools.
Copyright © 2003-2017.
+LBL_Copyright=Autopsy™ is a digital forensics platform based on The Sleuth Kit™ and other tools.
Copyright © 2003-2018.
URL_ON_IMG=http://www.sleuthkit.org/
URL_ON_HELP=http://sleuthkit.org/autopsy/docs/user-docs/4.6.0/
FILE_FOR_LOCAL_HELP=file:///
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java
index 3b6878ce23..a06d3eeeea 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java
@@ -408,6 +408,7 @@ final public class Accounts implements AutopsyVisitableItem {
protected void addNotify() {
IngestManager.getInstance().addIngestJobEventListener(pcl);
IngestManager.getInstance().addIngestModuleEventListener(pcl);
+ Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
super.addNotify();
}
@@ -415,6 +416,7 @@ final public class Accounts implements AutopsyVisitableItem {
protected void removeNotify() {
IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(pcl);
+ Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
super.removeNotify();
}
@@ -569,6 +571,7 @@ final public class Accounts implements AutopsyVisitableItem {
protected void addNotify() {
IngestManager.getInstance().addIngestJobEventListener(pcl);
IngestManager.getInstance().addIngestModuleEventListener(pcl);
+ Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
super.addNotify();
}
@@ -576,6 +579,7 @@ final public class Accounts implements AutopsyVisitableItem {
protected void removeNotify() {
IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(pcl);
+ Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
super.removeNotify();
}
@@ -691,6 +695,7 @@ final public class Accounts implements AutopsyVisitableItem {
protected void addNotify() {
IngestManager.getInstance().addIngestJobEventListener(pcl);
IngestManager.getInstance().addIngestModuleEventListener(pcl);
+ Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
super.addNotify();
}
@@ -698,6 +703,7 @@ final public class Accounts implements AutopsyVisitableItem {
protected void removeNotify() {
IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(pcl);
+ Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
super.removeNotify();
}
@@ -901,6 +907,7 @@ final public class Accounts implements AutopsyVisitableItem {
protected void addNotify() {
IngestManager.getInstance().addIngestJobEventListener(pcl);
IngestManager.getInstance().addIngestModuleEventListener(pcl);
+ Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
super.addNotify();
}
@@ -908,6 +915,7 @@ final public class Accounts implements AutopsyVisitableItem {
protected void removeNotify() {
IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(pcl);
+ Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
super.removeNotify();
}
diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Bundle.properties b/Core/src/org/sleuthkit/autopsy/healthmonitor/Bundle.properties
index 4185424161..7c8a50c757 100644
--- a/Core/src/org/sleuthkit/autopsy/healthmonitor/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Bundle.properties
@@ -18,3 +18,4 @@ TestPanel.jLabel5.text=Number of nodes
TestPanel.nNodesTextField.text=
TestPanel.jButton2.text=Populate DB
TestPanel.newGraphButton.text=New graph!
+TestPanel.jButton3.text=Make blocky data
diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java
similarity index 81%
rename from Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java
rename to Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java
index c996f39752..6d7825c10d 100644
--- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java
+++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java
@@ -19,6 +19,8 @@
package org.sleuthkit.autopsy.healthmonitor;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
@@ -29,17 +31,24 @@ import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.Random;
import org.apache.commons.dbcp2.BasicDataSource;
+import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.core.UserPreferencesException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
+import org.sleuthkit.autopsy.coreutils.ThreadUtils;
import org.sleuthkit.datamodel.CaseDbConnectionInfo;
import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber;
@@ -50,30 +59,46 @@ import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber;
* Modules will call getTimingMetric() before the code to be timed to get a TimingMetric object
* Modules will call submitTimingMetric() with the obtained TimingMetric object to log it
*/
-public class ServicesHealthMonitor {
+public final class EnterpriseHealthMonitor implements PropertyChangeListener {
- private final static Logger logger = Logger.getLogger(ServicesHealthMonitor.class.getName());
- private final static String DATABASE_NAME = "ServicesHealthMonitor";
- private final static String MODULE_NAME = "ServicesHealthMonitor";
+ private final static Logger logger = Logger.getLogger(EnterpriseHealthMonitor.class.getName());
+ private final static String DATABASE_NAME = "EnterpriseHealthMonitor";
+ private final static String MODULE_NAME = "EnterpriseHealthMonitor";
private final static String IS_ENABLED_KEY = "is_enabled";
private final static long DATABASE_WRITE_INTERVAL = 1; // Minutes TODO - put back to an hour
public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION
= new CaseDbSchemaVersionNumber(1, 0);
private static final AtomicBoolean isEnabled = new AtomicBoolean(false);
- private static ServicesHealthMonitor instance;
+ private static EnterpriseHealthMonitor instance;
- private ScheduledThreadPoolExecutor periodicTasksExecutor;
+ private final ExecutorService healthMonitorExecutor;
+ private static final String HEALTH_MONITOR_EVENT_THREAD_NAME = "Health-Monitor-Event-Listener-%d";
+
+ private ScheduledThreadPoolExecutor healthMonitorOutputTimer;
private final Map timingInfoMap;
private static final int CONN_POOL_SIZE = 10;
private BasicDataSource connectionPool = null;
+ private String hostName;
- private ServicesHealthMonitor() throws HealthMonitorException {
+ private EnterpriseHealthMonitor() throws HealthMonitorException {
// Create the map to collect timing metrics. The map will exist regardless
// of whether the monitor is enabled.
timingInfoMap = new HashMap<>();
+ // Set up the executor to handle case events
+ healthMonitorExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(HEALTH_MONITOR_EVENT_THREAD_NAME).build());
+
+ // Get the host name
+ try {
+ hostName = java.net.InetAddress.getLocalHost().getHostName();
+ } catch (java.net.UnknownHostException ex) {
+ // Continue on, but log the error and generate a UUID to use for this session
+ hostName = UUID.randomUUID().toString();
+ logger.log(Level.SEVERE, "Unable to look up host name - falling back to UUID " + hostName, ex);
+ }
+
// Read from module settings to determine if the module is enabled
if (ModuleSettings.settingExists(MODULE_NAME, IS_ENABLED_KEY)) {
if(ModuleSettings.getConfigSetting(MODULE_NAME, IS_ENABLED_KEY).equals("true")){
@@ -93,13 +118,14 @@ public class ServicesHealthMonitor {
}
/**
- * Get the instance of the ServicesHealthMonitor
+ * Get the instance of the EnterpriseHealthMonitor
* @return the instance
* @throws HealthMonitorException
*/
- synchronized static ServicesHealthMonitor getInstance() throws HealthMonitorException {
+ synchronized static EnterpriseHealthMonitor getInstance() throws HealthMonitorException {
if (instance == null) {
- instance = new ServicesHealthMonitor();
+ instance = new EnterpriseHealthMonitor();
+ Case.addPropertyChangeListener(instance);
}
return instance;
}
@@ -118,15 +144,15 @@ public class ServicesHealthMonitor {
shutdownConnections();
if (!UserPreferences.getIsMultiUserModeEnabled()) {
- throw new HealthMonitorException("Multi user mode is not enabled - can not activate services health monitor");
- }
- // Set up database (if needed)
- CoordinationService.Lock lock = getExclusiveDbLock();
- if(lock == null) {
- throw new HealthMonitorException("Error getting database lock");
+ throw new HealthMonitorException("Multi user mode is not enabled - can not activate health monitor");
}
- try {
+ // Set up database (if needed)
+ try (CoordinationService.Lock lock = getExclusiveDbLock()) {
+ if(lock == null) {
+ throw new HealthMonitorException("Error getting database lock");
+ }
+
// Check if the database exists
if (! databaseExists()) {
@@ -138,12 +164,8 @@ public class ServicesHealthMonitor {
initializeDatabaseSchema();
}
- } finally {
- try {
- lock.release();
- } catch (CoordinationService.CoordinationServiceException ex) {
- throw new HealthMonitorException("Error releasing database lock", ex);
- }
+ } catch (CoordinationService.CoordinationServiceException ex) {
+ throw new HealthMonitorException("Error releasing database lock", ex);
}
// Clear out any old data
@@ -178,20 +200,19 @@ public class ServicesHealthMonitor {
* Start the ScheduledThreadPoolExecutor that will handle the database writes.
*/
private synchronized void startTimer() {
- if(periodicTasksExecutor != null) {
- // Make sure the previous executor (if it exists) has been stopped
- periodicTasksExecutor.shutdown();
- }
- periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("health_monitor_timer").build());
- periodicTasksExecutor.scheduleWithFixedDelay(new DatabaseWriteTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES);
+ // Make sure the previous executor (if it exists) has been stopped
+ stopTimer();
+
+ healthMonitorOutputTimer = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("health_monitor_timer").build());
+ healthMonitorOutputTimer.scheduleWithFixedDelay(new DatabaseWriteTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES);
}
/**
* Stop the ScheduledThreadPoolExecutor to prevent further database writes.
*/
private synchronized void stopTimer() {
- if(periodicTasksExecutor != null) {
- periodicTasksExecutor.shutdown();
+ if(healthMonitorOutputTimer != null) {
+ ThreadUtils.shutDownTaskExecutor(healthMonitorOutputTimer);
}
}
@@ -199,7 +220,7 @@ public class ServicesHealthMonitor {
* Called from the installer to set up the Health Monitor instance at startup.
* @throws HealthMonitorException
*/
- static synchronized void startUp() throws HealthMonitorException {
+ static synchronized void startUpIfEnabled() throws HealthMonitorException {
getInstance();
}
@@ -231,7 +252,7 @@ public class ServicesHealthMonitor {
* Get a metric that will measure the time to execute a section of code.
* Call this before the section of code to be timed and then
* submit it afterward using submitTimingMetric().
- * This method is safe to call regardless of whether the Services Health
+ * This method is safe to call regardless of whether the Enterprise Health
* Monitor is enabled.
* @param name A short but descriptive name describing the code being timed.
* This name will appear in the UI.
@@ -247,7 +268,7 @@ public class ServicesHealthMonitor {
/**
* Submit the metric that was previously obtained through getTimingMetric().
* Call this immediately after the section of code being timed.
- * This method is safe to call regardless of whether the Services Health
+ * This method is safe to call regardless of whether the Enterprise Health
* Monitor is enabled.
* @param metric The TimingMetric object obtained from getTimingMetric()
*/
@@ -306,29 +327,20 @@ public class ServicesHealthMonitor {
timingMapCopy = new HashMap<>(timingInfoMap);
timingInfoMap.clear();
}
- logger.log(Level.INFO, "Writing health monitor metrics to database");
-
- String hostName;
- try {
- hostName = java.net.InetAddress.getLocalHost().getHostName();
- } catch (java.net.UnknownHostException ex) {
- // Write it to the database but log a warning
- logger.log(Level.WARNING, "Unable to look up host name");
- hostName = "unknown";
- }
// Check if there's anything to report (right now we only have the timing map)
if(timingMapCopy.keySet().isEmpty()) {
return;
}
- // Write to the database
- CoordinationService.Lock lock = getSharedDbLock();
- if(lock == null) {
- throw new HealthMonitorException("Error getting database lock");
- }
+ logger.log(Level.INFO, "Writing health monitor metrics to database");
- try {
+ // Write to the database
+ try (CoordinationService.Lock lock = getSharedDbLock()) {
+ if(lock == null) {
+ throw new HealthMonitorException("Error getting database lock");
+ }
+
Connection conn = connect();
if(conn == null) {
throw new HealthMonitorException("Error getting database connection");
@@ -361,12 +373,8 @@ public class ServicesHealthMonitor {
logger.log(Level.SEVERE, "Error closing Connection.", ex);
}
}
- } finally {
- try {
- lock.release();
- } catch (CoordinationService.CoordinationServiceException ex) {
- throw new HealthMonitorException("Error releasing database lock", ex);
- }
+ } catch (CoordinationService.CoordinationServiceException ex) {
+ throw new HealthMonitorException("Error releasing database lock", ex);
}
}
@@ -385,10 +393,9 @@ public class ServicesHealthMonitor {
try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
Statement statement = connection.createStatement();) {
String createCommand = "SELECT 1 AS result FROM pg_database WHERE datname='" + DATABASE_NAME + "'";
- System.out.println(" query: " + createCommand);
rs = statement.executeQuery(createCommand);
if(rs.next()) {
- logger.log(Level.INFO, "Existing Services Health Monitor database found");
+ logger.log(Level.INFO, "Existing Enterprise Health Monitor database found");
return true;
}
} finally {
@@ -593,26 +600,26 @@ public class ServicesHealthMonitor {
try (Statement statement = conn.createStatement()) {
conn.setAutoCommit(false);
- StringBuilder createTimingTable = new StringBuilder();
- createTimingTable.append("CREATE TABLE IF NOT EXISTS timing_data (");
- createTimingTable.append("id SERIAL PRIMARY KEY,");
- createTimingTable.append("name text NOT NULL,");
- createTimingTable.append("host text NOT NULL,");
- createTimingTable.append("timestamp bigint NOT NULL,");
- createTimingTable.append("count bigint NOT NULL,");
- createTimingTable.append("average bigint NOT NULL,");
- createTimingTable.append("max bigint NOT NULL,");
- createTimingTable.append("min bigint NOT NULL");
- createTimingTable.append(")");
- statement.execute(createTimingTable.toString());
+ String createTimingTable =
+ "CREATE TABLE IF NOT EXISTS timing_data (" +
+ "id SERIAL PRIMARY KEY," +
+ "name text NOT NULL," +
+ "host text NOT NULL," +
+ "timestamp bigint NOT NULL," +
+ "count bigint NOT NULL," +
+ "average bigint NOT NULL," +
+ "max bigint NOT NULL," +
+ "min bigint NOT NULL" +
+ ")";
+ statement.execute(createTimingTable);
- StringBuilder createDbInfoTable = new StringBuilder();
- createDbInfoTable.append("CREATE TABLE IF NOT EXISTS db_info (");
- createDbInfoTable.append("id SERIAL PRIMARY KEY NOT NULL,");
- createDbInfoTable.append("name text NOT NULL,");
- createDbInfoTable.append("value text NOT NULL");
- createDbInfoTable.append(")");
- statement.execute(createDbInfoTable.toString());
+ String createDbInfoTable =
+ "CREATE TABLE IF NOT EXISTS db_info (" +
+ "id SERIAL PRIMARY KEY NOT NULL," +
+ "name text NOT NULL," +
+ "value text NOT NULL" +
+ ")";
+ statement.execute(createDbInfoTable);
statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "')");
statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_MINOR_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMinor() + "')");
@@ -653,11 +660,25 @@ public class ServicesHealthMonitor {
}
}
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+
+ switch (Case.Events.valueOf(evt.getPropertyName())) {
+
+ case CURRENT_CASE:
+ if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof Case)) {
+ // When a case is closed, write the current metrics to the database
+ healthMonitorExecutor.submit(new EnterpriseHealthMonitor.DatabaseWriteTask());
+ }
+ break;
+ }
+ }
+
/**
* TODO: remove this - testing only
* Will put a bunch of sample data into the database
*/
- final void populateDatabase(int nDays, int nNodes) throws HealthMonitorException {
+ final void populateDatabase(int nDays, int nNodes, boolean createVerificationData) throws HealthMonitorException {
if(! isEnabled.get()) {
throw new HealthMonitorException("Can't populate database - monitor not enabled");
@@ -708,16 +729,27 @@ public class ServicesHealthMonitor {
long aveTime;
- // Make different cases
- int outlierVal = rand.nextInt(30);
- if(outlierVal < 2){
- aveTime = minIndexTime + maxIndexTimeOverMin + rand.nextInt(maxIndexTimeOverMin);
- } else if(outlierVal == 2){
- aveTime = (minIndexTime / 2) + rand.nextInt(minIndexTime / 2);
- } else if(outlierVal < 17) {
- aveTime = minIndexTime + (rand.nextInt(maxIndexTimeOverMin) / 2);
+ if( ! createVerificationData ) {
+ // Try to make a reasonable sample data set, with most points in a small range
+ // but some higher and lower
+ int outlierVal = rand.nextInt(30);
+ if(outlierVal < 2){
+ aveTime = minIndexTime + maxIndexTimeOverMin + rand.nextInt(maxIndexTimeOverMin);
+ } else if(outlierVal == 2){
+ aveTime = (minIndexTime / 2) + rand.nextInt(minIndexTime / 2);
+ } else if(outlierVal < 17) {
+ aveTime = minIndexTime + (rand.nextInt(maxIndexTimeOverMin) / 2);
+ } else {
+ aveTime = minIndexTime + rand.nextInt(maxIndexTimeOverMin);
+ }
} else {
- aveTime = minIndexTime + rand.nextInt(maxIndexTimeOverMin);
+ // Create a data set strictly for testing that the display is working
+ // correctly. The average time will equal the day of the month from
+ // the timestamp (in milliseconds)
+ Calendar thisDate = new GregorianCalendar();
+ thisDate.setTimeInMillis(timestamp);
+ int day = thisDate.get(Calendar.DAY_OF_MONTH);
+ aveTime = day * 1000000;
}
@@ -736,14 +768,27 @@ public class ServicesHealthMonitor {
// Record index chunk every hour
for(long timestamp = minTimestamp + rand.nextInt(1000 * 60 * 55);timestamp < maxTimestamp;timestamp += (1 + rand.nextInt(10)) * millisPerHour) {
- long aveTime = minConnTime + rand.nextInt(maxConnTimeOverMin);
-
- // Check if we should make an outlier
- int outlierVal = rand.nextInt(30);
- if(outlierVal < 2){
- aveTime = minConnTime + maxConnTimeOverMin + rand.nextInt(maxConnTimeOverMin);
- } else if(outlierVal == 8){
- aveTime = (minConnTime / 2) + rand.nextInt(minConnTime / 2);
+ long aveTime;
+ if( ! createVerificationData ) {
+ // Try to make a reasonable sample data set, with most points in a small range
+ // but some higher and lower
+ aveTime = minConnTime + rand.nextInt(maxConnTimeOverMin);
+
+ // Check if we should make an outlier
+ int outlierVal = rand.nextInt(30);
+ if(outlierVal < 2){
+ aveTime = minConnTime + maxConnTimeOverMin + rand.nextInt(maxConnTimeOverMin);
+ } else if(outlierVal == 8){
+ aveTime = (minConnTime / 2) + rand.nextInt(minConnTime / 2);
+ }
+ } else {
+ // Create a data set strictly for testing that the display is working
+ // correctly. The average time will equal the day of the month from
+ // the timestamp (in milliseconds)
+ Calendar thisDate = new GregorianCalendar();
+ thisDate.setTimeInMillis(timestamp);
+ int day = thisDate.get(Calendar.DAY_OF_MONTH);
+ aveTime = day * 1000000;
}
statement.setString(1, "Solr: Connectivity check");
@@ -857,15 +902,14 @@ public class ServicesHealthMonitor {
*/
private CoordinationService.Lock getExclusiveDbLock() throws HealthMonitorException{
try {
- String databaseNodeName = DATABASE_NAME;
- CoordinationService.Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.HEALTH_MONITOR, databaseNodeName, 5, TimeUnit.MINUTES);
+ CoordinationService.Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.HEALTH_MONITOR, DATABASE_NAME, 5, TimeUnit.MINUTES);
if(lock != null){
return lock;
}
throw new HealthMonitorException("Error acquiring database lock");
} catch (InterruptedException | CoordinationService.CoordinationServiceException ex){
- throw new HealthMonitorException("Error acquiring database lock");
+ throw new HealthMonitorException("Error acquiring database lock", ex);
}
}
@@ -973,14 +1017,16 @@ public class ServicesHealthMonitor {
*/
static class DatabaseTimingResult {
private long timestamp; // Time the metric was recorded
+ private String hostname; // Host that recorded the metric
private long count; // Number of metrics collected
private double average; // Average of the durations collected (milliseconds)
private double max; // Maximum value found (milliseconds)
private double min; // Minimum value found (milliseconds)
// TODO - maybe delete this
- DatabaseTimingResult(long timestamp, long count, double average, double max, double min) {
+ DatabaseTimingResult(long timestamp, String hostname, long count, double average, double max, double min) {
this.timestamp = timestamp;
+ this.hostname = hostname;
this.count = count;
this.average = average;
this.max = max;
@@ -989,6 +1035,7 @@ public class ServicesHealthMonitor {
DatabaseTimingResult(ResultSet resultSet) throws SQLException {
this.timestamp = resultSet.getLong("timestamp");
+ this.hostname = resultSet.getString("host");
this.count = resultSet.getLong("count");
this.average = resultSet.getLong("average") / 1000000;
this.max = resultSet.getLong("max") / 1000000;
@@ -1034,5 +1081,13 @@ public class ServicesHealthMonitor {
long getCount() {
return count;
}
+
+ /**
+ * Get the name of the host that recorded this metric
+ * @return the host
+ */
+ String getHostName() {
+ return hostname;
+ }
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java
deleted file mode 100644
index 5c8a6f675d..0000000000
--- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Autopsy Forensic Browser
- *
- * Copyright 2018 Basis Technology Corp.
- * Contact: carrier sleuthkit org
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.sleuthkit.autopsy.healthmonitor;
-
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import org.sleuthkit.autopsy.casemodule.Case;
-
-/**
- * Listener for case events
- */
-final class HealthMonitorCaseEventListener implements PropertyChangeListener {
-
- private final ExecutorService jobProcessingExecutor;
- private static final String CASE_EVENT_THREAD_NAME = "Health-Monitor-Event-Listener-%d";
-
- HealthMonitorCaseEventListener() {
- jobProcessingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(CASE_EVENT_THREAD_NAME).build());
- }
-
- @Override
- public void propertyChange(PropertyChangeEvent evt) {
-
- switch (Case.Events.valueOf(evt.getPropertyName())) {
-
- case CURRENT_CASE:
- if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof Case)) {
- // When a case is closed, write the current metrics to the database
- jobProcessingExecutor.submit(new ServicesHealthMonitor.DatabaseWriteTask());
- }
- break;
- }
- }
-}
diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java
index 189373c32d..49fe2251c3 100644
--- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java
+++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java
@@ -19,17 +19,25 @@
package org.sleuthkit.autopsy.healthmonitor;
import com.mchange.v2.cfg.DelayedLogItem;
+import java.awt.Component;
import java.awt.Dimension;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.HashMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.Font;
+import javax.swing.Box;
import javax.swing.JDialog;
import javax.swing.JComboBox;
+import javax.swing.JSeparator;
+import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
@@ -48,51 +56,96 @@ public class HealthMonitorDashboard {
private final static Logger logger = Logger.getLogger(HealthMonitorDashboard.class.getName());
- //Map> timingData;
+ Map> timingData;
private JPanel timingMetricPanel = null;
private JPanel timingButtonPanel = null;
private JComboBox dateComboBox = null;
+ private JComboBox hostComboBox = null;
+ private JCheckBox hostCheckBox = null;
HealthMonitorDashboard() {
-
+ timingData = new HashMap<>();
}
void display() throws HealthMonitorException {
- // Initialize and populate the timing metric panel
- populateTimingMetricPanel();
+ // Get a copy of the timing data from the database
+ timingData = EnterpriseHealthMonitor.getInstance().getTimingMetricsFromDatabase();
+
+ // Set up the buttons
+ setupTimingButtonPanel();
addActionListeners();
- System.out.println("Creating dialog");
+ // Initialize and populate the timing metric panel
+ populateTimingMetricPanel();
+
JScrollPane scrollPane = new JScrollPane(timingMetricPanel);
JDialog dialog = new JDialog();
- dialog.setPreferredSize(new Dimension(1500, 800));
- dialog.setTitle("Services Health Monitor");
+ //dialog.setPreferredSize(new Dimension(1500, 800));
+ dialog.setTitle("Enterprise Health Monitor");
//dialog.add(graphPanel);
dialog.add(scrollPane);
dialog.pack();
dialog.setVisible(true);
- System.out.println("Done displaying dialog");
}
/**
- * Initialize the panel holding the timing metric controls,
- * if it has not already been initialized.
+ * Initialize the panel holding the timing metric controls and
+ * update components
*/
- private void initializeTimingButtonPanel() throws HealthMonitorException {
+ private void setupTimingButtonPanel() throws HealthMonitorException {
if(timingButtonPanel == null) {
+ timingButtonPanel = new JPanel();
+ timingButtonPanel.setBorder(BorderFactory.createEtchedBorder());
+ //timingButtonPanel.setPreferredSize(new Dimension(1500, 100));
+ } else {
+ timingButtonPanel.removeAll();
+ }
+
+ // The contents of the date combo box will never change, so we only need to
+ // do this once
+ if(dateComboBox == null) {
// Create the combo box for selecting how much data to display
String[] dateOptionStrings = Arrays.stream(DateRange.values()).map(e -> e.getLabel()).toArray(String[]::new);
dateComboBox = new JComboBox(dateOptionStrings);
dateComboBox.setSelectedItem(DateRange.TWO_WEEKS.getLabel());
-
- // Add the date range button and label to the panel
- timingButtonPanel = new JPanel();
- timingButtonPanel.setBorder(BorderFactory.createEtchedBorder());
- timingButtonPanel.add(new JLabel("Max days to display"));
- timingButtonPanel.add(dateComboBox);
}
+
+ // Create an array of host names
+ Set hostNameSet = new HashSet<>();
+ for(String metricType:timingData.keySet()) {
+ for(EnterpriseHealthMonitor.DatabaseTimingResult result: timingData.get(metricType)) {
+ hostNameSet.add(result.getHostName());
+ }
+ }
+
+ // Load the host names into the combo box
+ hostComboBox = new JComboBox(hostNameSet.toArray(new String[hostNameSet.size()]));
+
+ // Create the checkbox (if needed)
+ if(hostCheckBox == null) {
+ hostCheckBox = new JCheckBox("Filter by host");
+ hostCheckBox.setSelected(false);
+ hostComboBox.setEnabled(false);
+ }
+
+ // Create the panel
+ timingButtonPanel = new JPanel();
+ timingButtonPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ //timingButtonPanel.setBorder(BorderFactory.createEtchedBorder());
+
+ // Add the date range combo box and label to the panel
+ timingButtonPanel.add(new JLabel("Max days to display"));
+ timingButtonPanel.add(dateComboBox);
+
+ // Put some space between the elements
+ timingButtonPanel.add(Box.createHorizontalStrut(100));
+
+ // Add the host combo box and checkbox to the panel
+ timingButtonPanel.add(hostCheckBox);
+ //timingButtonPanel.add(new JLabel("Host to display"));
+ timingButtonPanel.add(hostComboBox);
}
/**
@@ -100,18 +153,52 @@ public class HealthMonitorDashboard {
* Call this after all components are initialized.
*/
private void addActionListeners() {
+
// Set up a listener on the combo box that will update the timing
// metric graphs
- dateComboBox.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent arg0) {
- try {
- populateTimingMetricPanel();
- } catch (HealthMonitorException ex) {
- logger.log(Level.SEVERE, "Error populating timing metric panel", ex);
+ if((dateComboBox != null) && (dateComboBox.getActionListeners().length == 0)) {
+ dateComboBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ try {
+ populateTimingMetricPanel();
+ } catch (HealthMonitorException ex) {
+ logger.log(Level.SEVERE, "Error populating timing metric panel", ex);
+ }
}
- }
- });
+ });
+ }
+
+ // Set up a listener on the host name combo box
+ if((hostComboBox != null) && (hostComboBox.getActionListeners().length == 0)) {
+ hostComboBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ try {
+ if((hostCheckBox != null) && hostCheckBox.isSelected()) {
+ populateTimingMetricPanel();
+ }
+ } catch (HealthMonitorException ex) {
+ logger.log(Level.SEVERE, "Error populating timing metric panel", ex);
+ }
+ }
+ });
+ }
+
+ // Set up a listener on the host name check box
+ if((hostCheckBox != null) && (hostCheckBox.getActionListeners().length == 0)) {
+ hostCheckBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ try {
+ hostComboBox.setEnabled(hostCheckBox.isSelected()); // Why isn't this working?
+ populateTimingMetricPanel();
+ } catch (HealthMonitorException ex) {
+ logger.log(Level.SEVERE, "Error populating timing metric panel", ex);
+ }
+ }
+ });
+ }
}
/**
@@ -131,38 +218,52 @@ public class HealthMonitorDashboard {
}
private void populateTimingMetricPanel() throws HealthMonitorException {
- initializeTimingButtonPanel();
+
initializeTimingMetricPanel();
-
- // Get a fresh copy of the timing data from the database
- Map> timingData = ServicesHealthMonitor.getInstance().getTimingMetricsFromDatabase();
+
+ // Add title
+ JLabel timingMetricTitle = new JLabel("Timing Metrics");
+ timingMetricTitle.setFont(new Font("Serif", Font.BOLD, 20));
+ timingMetricPanel.add(timingMetricTitle);
// Add the button controls
timingMetricPanel.add(timingButtonPanel);
+ timingMetricPanel.add(new JSeparator());
for(String name:timingData.keySet()) {
// Add the metric name
- JLabel label = new JLabel(name);
- timingMetricPanel.add(label);
+ JLabel metricNameLabel = new JLabel(name);
+ metricNameLabel.setFont(new Font("Serif", Font.BOLD, 12));
+ timingMetricPanel.add(metricNameLabel);
// If necessary, trim down the list of results to fit the selected time range
- List timingDataForDisplay;
+ List intermediateTimingDataForDisplay;
if(dateComboBox.getSelectedItem() != null) {
DateRange selectedDateRange = DateRange.fromLabel(dateComboBox.getSelectedItem().toString());
if(selectedDateRange != DateRange.ALL) {
long threshold = System.currentTimeMillis() - selectedDateRange.getTimestampRange();
- timingDataForDisplay = timingData.get(name).stream()
+ intermediateTimingDataForDisplay = timingData.get(name).stream()
.filter(t -> t.getTimestamp() > threshold)
.collect(Collectors.toList());
} else {
- timingDataForDisplay = timingData.get(name);
+ intermediateTimingDataForDisplay = timingData.get(name);
}
} else {
- timingDataForDisplay = timingData.get(name);
+ intermediateTimingDataForDisplay = timingData.get(name);
+ }
+
+ // If necessary, trim down the resulting list to only one host name
+ List timingDataForDisplay;
+ if(hostCheckBox.isSelected() && (hostComboBox.getSelectedItem() != null)) {
+ timingDataForDisplay = intermediateTimingDataForDisplay.stream()
+ .filter(t -> t.getHostName().equals(hostComboBox.getSelectedItem().toString()))
+ .collect(Collectors.toList());
+ } else {
+ timingDataForDisplay = intermediateTimingDataForDisplay;
}
TimingMetricGraphPanel singleTimingGraphPanel = new TimingMetricGraphPanel(timingDataForDisplay, TimingMetricGraphPanel.TimingMetricType.AVERAGE, true);
- singleTimingGraphPanel.setPreferredSize(new Dimension(800,200));
+ singleTimingGraphPanel.setPreferredSize(new Dimension(900,250));
timingMetricPanel.add(singleTimingGraphPanel);
}
diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java
index 5d294aca31..61ea5a5244 100644
--- a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java
+++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java
@@ -26,7 +26,6 @@ import org.sleuthkit.autopsy.coreutils.Logger;
public class Installer extends ModuleInstall {
private static final Logger logger = Logger.getLogger(Installer.class.getName());
- private final HealthMonitorCaseEventListener pcl = new HealthMonitorCaseEventListener();
private static final long serialVersionUID = 1L;
private static Installer instance;
@@ -45,10 +44,8 @@ public class Installer extends ModuleInstall {
@Override
public void restored() {
- Case.addPropertyChangeListener(pcl);
-
try {
- ServicesHealthMonitor.startUp();
+ EnterpriseHealthMonitor.startUpIfEnabled();
} catch (HealthMonitorException ex) {
logger.log(Level.SEVERE, "Error starting health services monitor", ex);
}
diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.form b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.form
index 62e0a5eb17..2ed5b4a0cc 100644
--- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.form
@@ -73,7 +73,7 @@
-
+
@@ -84,7 +84,10 @@
-
+
+
+
+
@@ -132,7 +135,9 @@
-
+
+
+
@@ -334,5 +339,15 @@