From a673dff63da0f28d55b82b773de928570567816a Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 6 Apr 2018 13:40:50 -0400 Subject: [PATCH 01/63] Testing graphs for dashboard. --- .../healthmonitor/ServicesHealthMonitor.java | 148 +++++++++++++- .../healthmonitor/TimingMetricGraphPanel.java | 187 ++++++++++++++++++ 2 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index ebed5d5f61..82533086aa 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -27,6 +27,8 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.Map; import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -53,7 +55,7 @@ public class ServicesHealthMonitor { private final static String DATABASE_NAME = "ServicesHealthMonitor"; private final static String MODULE_NAME = "ServicesHealthMonitor"; private final static String IS_ENABLED_KEY = "is_enabled"; - private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes + 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); @@ -111,6 +113,9 @@ public class ServicesHealthMonitor { logger.log(Level.INFO, "Activating Servies Health Monitor"); + // Make sure there are no left over connections to an old database + shutdownConnections(); + if (!UserPreferences.getIsMultiUserModeEnabled()) { throw new HealthMonitorException("Multi user mode is not enabled - can not activate services health monitor"); } @@ -523,7 +528,7 @@ public class ServicesHealthMonitor { ResultSet resultSet = null; try (Statement statement = conn.createStatement()) { - int minorVersion = 0; + int minorVersion = 0; int majorVersion = 0; resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_MINOR_VERSION'"); if (resultSet.next()) { @@ -636,6 +641,76 @@ public class ServicesHealthMonitor { } } + /** + * Get all timing metrics currently stored in the database. This also converts + * the times to milliseconds (from nanoseconds). + * @return A map with metric name mapped to a list of data + * @throws HealthMonitorException + */ + Map> getTimingMetricsFromDatabase() throws HealthMonitorException { + + // Make sure the monitor is enabled. It could theoretically get disabled after this + // check but it doesn't seem worth holding a lock to ensure that it doesn't since that + // may slow down ingest. + if(! isEnabled.get()) { + throw new HealthMonitorException("Health Monitor is not enabled"); + } + + CoordinationService.Lock lock = getSharedDbLock(); + if(lock == null) { + throw new HealthMonitorException("Error getting database lock"); + } + + try{ + Connection conn = connect(); + if(conn == null) { + throw new HealthMonitorException("Error getting database connection"); + } + ResultSet resultSet = null; + + Map> resultMap = new HashMap<>(); + + try (Statement statement = conn.createStatement()) { + + resultSet = statement.executeQuery("SELECT * FROM timing_data"); + while (resultSet.next()) { + String name = resultSet.getString("name"); + DatabaseTimingResult timingResult = new DatabaseTimingResult(resultSet); + + if(resultMap.containsKey(name)) { + resultMap.get(name).add(timingResult); + } else { + List resultList = new ArrayList<>(); + resultList.add(timingResult); + resultMap.put(name, resultList); + } + } + return resultMap; + } catch (SQLException ex) { + throw new HealthMonitorException("Error reading timing metrics from database", ex); + } finally { + if(resultSet != null) { + try { + resultSet.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing result set", ex); + } + } + try { + conn.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing Connection.", ex); + } + } + } finally { + try { + lock.release(); + } catch (CoordinationService.CoordinationServiceException ex) { + throw new HealthMonitorException("Error releasing database lock", ex); + } + } + } + /** * Get an exclusive lock for the health monitor database. * Acquire this before creating, initializing, or updating the database schema. @@ -753,4 +828,73 @@ public class ServicesHealthMonitor { return count; } } + + /** + * Class for retrieving timing metrics from the database to display to the user. + * All times will be in milliseconds. + */ + static class DatabaseTimingResult { + private long timestamp; // Time the metric was recorded + 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) { + this.timestamp = timestamp; + this.count = count; + this.average = average; + this.max = max; + this.min = min; + } + + DatabaseTimingResult(ResultSet resultSet) throws SQLException { + this.timestamp = resultSet.getLong("timestamp"); + this.count = resultSet.getLong("count"); + this.average = resultSet.getLong("average") / 1000000; + this.max = resultSet.getLong("max") / 1000000; + this.min = resultSet.getLong("min") / 1000000; + } + + /** + * Get the timestamp for when the metric was recorded + * @return + */ + long getTimestamp() { + return timestamp; + } + + /** + * Get the average duration + * @return average duration (milliseconds) + */ + double getAverage() { + return average; + } + + /** + * Get the maximum duration + * @return maximum duration (milliseconds) + */ + double getMax() { + return max; + } + + /** + * Get the minimum duration + * @return minimum duration (milliseconds) + */ + double getMin() { + return min; + } + + /** + * Get the total number of metrics collected + * @return number of metrics collected + */ + long getCount() { + return count; + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java new file mode 100644 index 0000000000..955ffde780 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -0,0 +1,187 @@ +/* + * 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 java.awt.BasicStroke; +import java.awt.Color; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.RenderingHints; +import java.awt.Stroke; +import java.util.ArrayList; +import java.util.List; +import javax.swing.JPanel; +import org.sleuthkit.autopsy.healthmonitor.ServicesHealthMonitor.DatabaseTimingResult; + +/** + * + */ +class TimingMetricGraphPanel extends JPanel { + + private int width = 800; + private int heigth = 400; + private int padding = 25; + private int labelPadding = 25; + private Color lineColor = new Color(44, 102, 230, 180); + private Color pointColor = new Color(100, 100, 100, 180); + private Color gridColor = new Color(200, 200, 200, 200); + private static final Stroke GRAPH_STROKE = new BasicStroke(2f); + private int pointWidth = 4; + private int numberYDivisions = 10; + private List timingResults; + + TimingMetricGraphPanel(List timingResults) { + this.timingResults = timingResults; + } + + private double getMaxScore() { + // Find the highest of the max values + double maxScore = Double.MIN_VALUE; + for (DatabaseTimingResult score : timingResults) { + maxScore = Math.max(maxScore, score.getMax()); + } + return maxScore; + } + + private double getMinScore() { + // Find the highest of the max values + double minScore = Double.MAX_VALUE; + for (DatabaseTimingResult score : timingResults) { + minScore = Math.min(minScore, score.getMin()); + } + return minScore; + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + double maxScore = getMaxScore(); + double minScore = getMinScore(); + + + double xScale = ((double) getWidth() - (2 * padding) - labelPadding) / (timingResults.size() - 1); // TODO: make this based on timestamps + double yScale = ((double) getHeight() - 2 * padding - labelPadding) / (maxScore - minScore); + + //List graphPoints = new ArrayList<>(); + List averageGraphPoints = new ArrayList<>(); + List maxGraphPoints = new ArrayList<>(); + List minGraphPoints = new ArrayList<>(); + for (int i = 0; i < timingResults.size(); i++) { + int x1 = (int) (i * xScale + padding + labelPadding); + int yAve = (int) ((maxScore - timingResults.get(i).getAverage()) * yScale + padding); + int yMax = (int) ((maxScore - timingResults.get(i).getMax()) * yScale + padding); + int yMin = (int) ((maxScore - timingResults.get(i).getMin()) * yScale + padding); + averageGraphPoints.add(new Point(x1, yAve)); + maxGraphPoints.add(new Point(x1, yMax)); + minGraphPoints.add(new Point(x1, yMin)); + } + + // draw white background + g2.setColor(Color.WHITE); + g2.fillRect(padding + labelPadding, padding, getWidth() - (2 * padding) - labelPadding, getHeight() - 2 * padding - labelPadding); + g2.setColor(Color.BLACK); + + // create hatch marks and grid lines for y axis. + for (int i = 0; i < numberYDivisions + 1; i++) { + int x0 = padding + labelPadding; + int x1 = pointWidth + padding + labelPadding; + int y0 = getHeight() - ((i * (getHeight() - padding * 2 - labelPadding)) / numberYDivisions + padding + labelPadding); + int y1 = y0; + if (timingResults.size() > 0) { + g2.setColor(gridColor); + g2.drawLine(padding + labelPadding + 1 + pointWidth, y0, getWidth() - padding, y1); + g2.setColor(Color.BLACK); + String yLabel = ((int) ((getMinScore() + (maxScore - minScore) * ((i * 1.0) / numberYDivisions)) * 100)) / 100.0 + ""; + FontMetrics metrics = g2.getFontMetrics(); + int labelWidth = metrics.stringWidth(yLabel); + g2.drawString(yLabel, x0 - labelWidth - 5, y0 + (metrics.getHeight() / 2) - 3); + } + g2.drawLine(x0, y0, x1, y1); + } + + // and for x axis + for (int i = 0; i < timingResults.size(); i++) { + if (timingResults.size() > 1) { + int x0 = i * (getWidth() - padding * 2 - labelPadding) / (timingResults.size() - 1) + padding + labelPadding; + int x1 = x0; + int y0 = getHeight() - padding - labelPadding; + int y1 = y0 - pointWidth; + if ((i % ((int) ((timingResults.size() / 20.0)) + 1)) == 0) { + g2.setColor(gridColor); + g2.drawLine(x0, getHeight() - padding - labelPadding - 1 - pointWidth, x1, padding); + g2.setColor(Color.BLACK); + String xLabel = i + ""; + FontMetrics metrics = g2.getFontMetrics(); + int labelWidth = metrics.stringWidth(xLabel); + g2.drawString(xLabel, x0 - labelWidth / 2, y0 + metrics.getHeight() + 3); + } + g2.drawLine(x0, y0, x1, y1); + } + } + + // create x and y axes + g2.drawLine(padding + labelPadding, getHeight() - padding - labelPadding, padding + labelPadding, padding); + g2.drawLine(padding + labelPadding, getHeight() - padding - labelPadding, getWidth() - padding, getHeight() - padding - labelPadding); + + Stroke oldStroke = g2.getStroke(); + g2.setColor(lineColor); + g2.setStroke(GRAPH_STROKE); + for (int i = 0; i < averageGraphPoints.size() - 1; i++) { + int x1 = averageGraphPoints.get(i).x; + int y1 = averageGraphPoints.get(i).y; + int x2 = averageGraphPoints.get(i + 1).x; + int y2 = averageGraphPoints.get(i + 1).y; + g2.drawLine(x1, y1, x2, y2); + } + + g2.setColor(new Color(10, 10, 200)); + for (int i = 0; i < maxGraphPoints.size() - 1; i++) { + int x1 = maxGraphPoints.get(i).x; + int y1 = maxGraphPoints.get(i).y; + int x2 = maxGraphPoints.get(i + 1).x; + int y2 = maxGraphPoints.get(i + 1).y; + g2.drawLine(x1, y1, x2, y2); + } + + g2.setColor(new Color(10, 200, 20)); + for (int i = 0; i < minGraphPoints.size() - 1; i++) { + int x1 = minGraphPoints.get(i).x; + int y1 = minGraphPoints.get(i).y; + int x2 = minGraphPoints.get(i + 1).x; + int y2 = minGraphPoints.get(i + 1).y; + g2.drawLine(x1, y1, x2, y2); + } + + g2.setStroke(oldStroke); + g2.setColor(pointColor); + /*for (int i = 0; i < graphPoints.size(); i++) { + int x = graphPoints.get(i).x - pointWidth / 2; + int y = graphPoints.get(i).y - pointWidth / 2; + int ovalW = pointWidth; + int ovalH = pointWidth; + g2.fillOval(x, y, ovalW, ovalH); + }*/ + } + +} From 38676acd57c5cfe05332c20198d20a275728b292 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 9 Apr 2018 13:19:12 -0400 Subject: [PATCH 02/63] Continuing work on graphing the timing metrics --- .../healthmonitor/ServicesHealthMonitor.java | 124 +++++++++ .../healthmonitor/TimingMetricGraphPanel.java | 243 ++++++++++++++---- 2 files changed, 315 insertions(+), 52 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index 64722237d9..be02c588d7 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -33,6 +33,7 @@ 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.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.core.UserPreferences; @@ -652,6 +653,129 @@ public class ServicesHealthMonitor { } } + /** + * TODO: remove this - testing only + * Will put a bunch of sample data into the database + */ + final void populateDatabase(int nDays, int nNodes) throws HealthMonitorException { + + if(! isEnabled.get()) { + throw new HealthMonitorException("Can't populate database - monitor not enabled"); + } + + // Write to the database + CoordinationService.Lock lock = getSharedDbLock(); + if(lock == null) { + throw new HealthMonitorException("Error getting database lock"); + } + + int minIndexTime = 9000000; + int maxIndexTimeOverMin = 50000000; + + int minConnTime = 15000000; + int maxConnTimeOverMin = 18000000; + + Random rand = new Random(); + + long maxTimestamp = System.currentTimeMillis(); + long millisPerHour = 1000 * 60 * 60; + long minTimestamp = maxTimestamp - (nDays * (millisPerHour * 24)); + + + try { + Connection conn = connect(); + if(conn == null) { + throw new HealthMonitorException("Error getting database connection"); + } + + try (Statement statement = conn.createStatement()) { + + statement.execute("DELETE FROM timing_data"); + } catch (SQLException ex) { + ex.printStackTrace(); + } + + for(int node = 0;node < nNodes; node++) { + + String host = "testHost" + node; + + // Add timing metrics to the database + String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)"; + try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) { + + // Record index chunk every hour + for(long timestamp = minTimestamp;timestamp < maxTimestamp;timestamp += millisPerHour) { + + 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); + } else { + aveTime = minIndexTime + rand.nextInt(maxIndexTimeOverMin); + } + + + statement.setString(1, "Solr: Index chunk"); + statement.setString(2, host); + statement.setLong(3, timestamp); + statement.setLong(4, 0); + statement.setLong(5, aveTime); + statement.setLong(6, 0); + statement.setLong(7, 0); + + statement.execute(); + } + + // Make some case opening ones + // Record index chunk every hour + for(long timestamp = minTimestamp;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); + } + + statement.setString(1, "Solr: Connectivity check"); + statement.setString(2, host); + statement.setLong(3, timestamp); + statement.setLong(4, 0); + statement.setLong(5, aveTime); + statement.setLong(6, 0); + statement.setLong(7, 0); + + statement.execute(); + } + + } catch (SQLException ex) { + throw new HealthMonitorException("Error saving metric data to database", ex); + } finally { + try { + conn.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing Connection.", ex); + } + } + } + } finally { + try { + lock.release(); + } catch (CoordinationService.CoordinationServiceException ex) { + throw new HealthMonitorException("Error releasing database lock", ex); + } + } + } + /** * Get all timing metrics currently stored in the database. This also converts * the times to milliseconds (from nanoseconds). diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index 955ffde780..16d9d82228 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -28,6 +28,8 @@ import java.awt.RenderingHints; import java.awt.Stroke; import java.util.ArrayList; import java.util.List; +import java.util.Calendar; +import java.util.GregorianCalendar; import javax.swing.JPanel; import org.sleuthkit.autopsy.healthmonitor.ServicesHealthMonitor.DatabaseTimingResult; @@ -46,29 +48,84 @@ class TimingMetricGraphPanel extends JPanel { private static final Stroke GRAPH_STROKE = new BasicStroke(2f); private int pointWidth = 4; private int numberYDivisions = 10; - private List timingResults; + private final List timingResults; + private final TimingMetricType timingMetricType; + private boolean doLineGraph = false; + private final long MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24; - TimingMetricGraphPanel(List timingResults) { + TimingMetricGraphPanel(List timingResults, TimingMetricType timingMetricType) { this.timingResults = timingResults; + this.timingMetricType = timingMetricType; } - private double getMaxScore() { + private double getMaxMetricTime() { // Find the highest of the max values double maxScore = Double.MIN_VALUE; for (DatabaseTimingResult score : timingResults) { - maxScore = Math.max(maxScore, score.getMax()); + // Use only the data we're graphing to determing the max + switch (timingMetricType) { + case MAX: + case ALL: + maxScore = Math.max(maxScore, score.getMax()); + break; + case AVERAGE: + maxScore = Math.max(maxScore, score.getAverage()); + break; + case MIN: + maxScore = Math.max(maxScore, score.getMin()); + break; + } } return maxScore; } - private double getMinScore() { + private double getMinMetricTime() { // Find the highest of the max values double minScore = Double.MAX_VALUE; for (DatabaseTimingResult score : timingResults) { - minScore = Math.min(minScore, score.getMin()); + // Use only the data we're graphing to determing the min + switch (timingMetricType) { + case MAX: + minScore = Math.min(minScore, score.getMax()); + break; + case AVERAGE: + minScore = Math.min(minScore, score.getAverage()); + break; + case MIN: + case ALL: + minScore = Math.min(minScore, score.getMin()); + break; + } } return minScore; } + + private long getMaxTimestamp() { + long maxTimestamp = Long.MIN_VALUE; + for (DatabaseTimingResult score : timingResults) { + maxTimestamp = Math.max(maxTimestamp, score.getTimestamp()); + } + return maxTimestamp; + } + + private long getMinTimestamp() { + long minTimestamp = Long.MAX_VALUE; + for (DatabaseTimingResult score : timingResults) { + minTimestamp = Math.min(minTimestamp, score.getTimestamp()); + } + return minTimestamp; + } + + /* + private double convertToDaysAgo(long thisTimestamp, long maxTimestamp) { + long diffInMilliSecs = maxTimestamp - thisTimestamp; + //System.out.println("Diff between " + maxTimestamp + " and " + thisTimestamp + " = " + diffInMilliSecs + " (milliseconds)"); + double diffInSeconds = diffInMilliSecs / (1000L); + //System.out.println(" Diff in seconds: " + diffInSeconds); + double result = diffInSeconds / (60 * 60 * 24); + //System.out.println(" Diff in days: " + result); + return (diffInSeconds / (60 * 60 * 24)); + }*/ @Override protected void paintComponent(Graphics g) { @@ -76,26 +133,26 @@ class TimingMetricGraphPanel extends JPanel { Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - double maxScore = getMaxScore(); - double minScore = getMinScore(); + // Get the max and min timestamps and convert to days to create the x-axis + long maxTimestamp = getMaxTimestamp(); + long minTimestamp = getMinTimestamp(); + //minTimestamp = minTimestamp * 0.995; // This is to get a small gap before the first data points + //double maxDaysAgo = convertToDaysAgo(getMinTimestamp(), maxTimestamp); + //maxDaysAgo = maxDaysAgo * 1.005; + //double minDaysAgo = 0; + // Get the max and min times to create the y-axis + double maxMetricTime = getMaxMetricTime(); + double minMetricTime = getMinMetricTime(); + minMetricTime = Math.max(0, minMetricTime - (maxMetricTime * 0.1)); + maxMetricTime = maxMetricTime * 1.1; - double xScale = ((double) getWidth() - (2 * padding) - labelPadding) / (timingResults.size() - 1); // TODO: make this based on timestamps - double yScale = ((double) getHeight() - 2 * padding - labelPadding) / (maxScore - minScore); - - //List graphPoints = new ArrayList<>(); - List averageGraphPoints = new ArrayList<>(); - List maxGraphPoints = new ArrayList<>(); - List minGraphPoints = new ArrayList<>(); - for (int i = 0; i < timingResults.size(); i++) { - int x1 = (int) (i * xScale + padding + labelPadding); - int yAve = (int) ((maxScore - timingResults.get(i).getAverage()) * yScale + padding); - int yMax = (int) ((maxScore - timingResults.get(i).getMax()) * yScale + padding); - int yMin = (int) ((maxScore - timingResults.get(i).getMin()) * yScale + padding); - averageGraphPoints.add(new Point(x1, yAve)); - maxGraphPoints.add(new Point(x1, yMax)); - minGraphPoints.add(new Point(x1, yMin)); - } + + //System.out.println("maxDaysAgo: " + minDaysAgo + ", minDaysAgo: " + maxDaysAgo); + double xScale = ((double) getWidth() - (2 * padding) - labelPadding) / (maxTimestamp - minTimestamp); + double yScale = ((double) getHeight() - 2 * padding - labelPadding) / (maxMetricTime - minMetricTime); + + System.out.println("xScale: " + xScale + ", yScale: " + yScale); // draw white background g2.setColor(Color.WHITE); @@ -112,7 +169,7 @@ class TimingMetricGraphPanel extends JPanel { g2.setColor(gridColor); g2.drawLine(padding + labelPadding + 1 + pointWidth, y0, getWidth() - padding, y1); g2.setColor(Color.BLACK); - String yLabel = ((int) ((getMinScore() + (maxScore - minScore) * ((i * 1.0) / numberYDivisions)) * 100)) / 100.0 + ""; + String yLabel = ((int) ((getMinMetricTime() + (maxMetricTime - minMetricTime) * ((i * 1.0) / numberYDivisions)) * 100)) / 100.0 + ""; FontMetrics metrics = g2.getFontMetrics(); int labelWidth = metrics.stringWidth(yLabel); g2.drawString(yLabel, x0 - labelWidth - 5, y0 + (metrics.getHeight() / 2) - 3); @@ -121,13 +178,43 @@ class TimingMetricGraphPanel extends JPanel { } // and for x axis - for (int i = 0; i < timingResults.size(); i++) { - if (timingResults.size() > 1) { - int x0 = i * (getWidth() - padding * 2 - labelPadding) / (timingResults.size() - 1) + padding + labelPadding; + + // Calculate the number of divisions for the X axis - this will be + // the number of days between the first and last metrics + //int numberOfXDivisions = -1 * (int)(maxDaysAgo); + + // What we want is midnight preceding the last recorded value + Calendar maxDate = new GregorianCalendar(); + maxDate.setTimeInMillis(maxTimestamp); + maxDate.set(Calendar.HOUR_OF_DAY, 0); + maxDate.set(Calendar.MINUTE, 0); + maxDate.set(Calendar.SECOND, 0); + maxDate.set(Calendar.MILLISECOND, 0); + + long lastMidnightInMillis = maxDate.getTimeInMillis(); + System.out.println("Last timestamp: " + maxTimestamp + ", last midnight: " + lastMidnightInMillis); + + // We don't want to display more than 20 lines + long totalMidnights = (lastMidnightInMillis - getMinTimestamp()) / MILLISECONDS_PER_DAY; + System.out.println(" Total midnights: " + totalMidnights); + long daysPerDivision; + if(totalMidnights <= 20) { + daysPerDivision = 1; + } else { + daysPerDivision = (totalMidnights / 20); + if((totalMidnights % 20) != 0) { + daysPerDivision++; + } + } + + for (long currentDivision = lastMidnightInMillis;currentDivision > 0;currentDivision -= MILLISECONDS_PER_DAY * daysPerDivision) { + + /* + int x0 = i * (getWidth() - padding * 2 - labelPadding) / numberOfXDivisions + padding + labelPadding; int x1 = x0; int y0 = getHeight() - padding - labelPadding; int y1 = y0 - pointWidth; - if ((i % ((int) ((timingResults.size() / 20.0)) + 1)) == 0) { + if ((i % ((int) ((numberOfXDivisions / 20.0)) + 1)) == 0) { g2.setColor(gridColor); g2.drawLine(x0, getHeight() - padding - labelPadding - 1 - pointWidth, x1, padding); g2.setColor(Color.BLACK); @@ -137,45 +224,91 @@ class TimingMetricGraphPanel extends JPanel { g2.drawString(xLabel, x0 - labelWidth / 2, y0 + metrics.getHeight() + 3); } g2.drawLine(x0, y0, x1, y1); - } + */ } // create x and y axes g2.drawLine(padding + labelPadding, getHeight() - padding - labelPadding, padding + labelPadding, padding); g2.drawLine(padding + labelPadding, getHeight() - padding - labelPadding, getWidth() - padding, getHeight() - padding - labelPadding); + Stroke oldStroke = g2.getStroke(); - g2.setColor(lineColor); g2.setStroke(GRAPH_STROKE); - for (int i = 0; i < averageGraphPoints.size() - 1; i++) { - int x1 = averageGraphPoints.get(i).x; - int y1 = averageGraphPoints.get(i).y; - int x2 = averageGraphPoints.get(i + 1).x; - int y2 = averageGraphPoints.get(i + 1).y; - g2.drawLine(x1, y1, x2, y2); + + // Plot the average timing points + if(timingMetricType.equals(TimingMetricType.ALL) || timingMetricType.equals(TimingMetricType.AVERAGE)) { + List averageGraphPoints = new ArrayList<>(); + for (int i = 0; i < timingResults.size(); i++) { + int x1 = (int) ((double)(maxTimestamp - timingResults.get(i).getTimestamp()) * xScale + padding + labelPadding); + //int x1 = (int) ((maxDaysAgo - convertToDaysAgo(timingResults.get(i).getTimestamp(), maxTimestamp)) * xScale + padding + labelPadding); + int yAve = (int) ((maxMetricTime - timingResults.get(i).getAverage()) * yScale + padding); + System.out.println("Adding point " + x1 + ", " + yAve); + averageGraphPoints.add(new Point(x1, yAve)); + } + + g2.setColor(lineColor); + if(doLineGraph) { + for (int i = 0; i < averageGraphPoints.size() - 1; i++) { + int x1 = averageGraphPoints.get(i).x; + int y1 = averageGraphPoints.get(i).y; + int x2 = averageGraphPoints.get(i + 1).x; + int y2 = averageGraphPoints.get(i + 1).y; + g2.drawLine(x1, y1, x2, y2); + } + } else { + for (int i = 0; i < averageGraphPoints.size(); i++) { + int x = averageGraphPoints.get(i).x - pointWidth / 2; + int y = averageGraphPoints.get(i).y - pointWidth / 2; + int ovalW = pointWidth; + int ovalH = pointWidth; + g2.fillOval(x, y, ovalW, ovalH); + } + } } - g2.setColor(new Color(10, 10, 200)); - for (int i = 0; i < maxGraphPoints.size() - 1; i++) { - int x1 = maxGraphPoints.get(i).x; - int y1 = maxGraphPoints.get(i).y; - int x2 = maxGraphPoints.get(i + 1).x; - int y2 = maxGraphPoints.get(i + 1).y; - g2.drawLine(x1, y1, x2, y2); + // Plot the maximum timing points + if(timingMetricType.equals(TimingMetricType.ALL) || timingMetricType.equals(TimingMetricType.MIN)) { + List minGraphPoints = new ArrayList<>(); + for (int i = 0; i < timingResults.size(); i++) { + int x1 = (int) (i * xScale + padding + labelPadding); + int yMin = (int) ((maxMetricTime - timingResults.get(i).getMin()) * yScale + padding); + minGraphPoints.add(new Point(x1, yMin)); + } + + g2.setColor(new Color(10, 200, 20)); + for (int i = 0; i < minGraphPoints.size() - 1; i++) { + int x1 = minGraphPoints.get(i).x; + int y1 = minGraphPoints.get(i).y; + int x2 = minGraphPoints.get(i + 1).x; + int y2 = minGraphPoints.get(i + 1).y; + g2.drawLine(x1, y1, x2, y2); + } } - g2.setColor(new Color(10, 200, 20)); - for (int i = 0; i < minGraphPoints.size() - 1; i++) { - int x1 = minGraphPoints.get(i).x; - int y1 = minGraphPoints.get(i).y; - int x2 = minGraphPoints.get(i + 1).x; - int y2 = minGraphPoints.get(i + 1).y; - g2.drawLine(x1, y1, x2, y2); + // Plot the minimum timing points + if(timingMetricType.equals(TimingMetricType.ALL) || timingMetricType.equals(TimingMetricType.MIN)) { + List maxGraphPoints = new ArrayList<>(); + for (int i = 0; i < timingResults.size(); i++) { + int x1 = (int) (i * xScale + padding + labelPadding); + int yMax = (int) ((maxMetricTime - timingResults.get(i).getMax()) * yScale + padding); + maxGraphPoints.add(new Point(x1, yMax)); + } + + g2.setColor(new Color(10, 10, 200)); + for (int i = 0; i < maxGraphPoints.size() - 1; i++) { + int x1 = maxGraphPoints.get(i).x; + int y1 = maxGraphPoints.get(i).y; + int x2 = maxGraphPoints.get(i + 1).x; + int y2 = maxGraphPoints.get(i + 1).y; + g2.drawLine(x1, y1, x2, y2); + } } + /* + // To draw points g2.setStroke(oldStroke); g2.setColor(pointColor); - /*for (int i = 0; i < graphPoints.size(); i++) { + for (int i = 0; i < graphPoints.size(); i++) { int x = graphPoints.get(i).x - pointWidth / 2; int y = graphPoints.get(i).y - pointWidth / 2; int ovalW = pointWidth; @@ -184,4 +317,10 @@ class TimingMetricGraphPanel extends JPanel { }*/ } + enum TimingMetricType { + AVERAGE, + MAX, + MIN, + ALL; + } } From f82cfe9c85e433091a1c80f933b837a5c6bb67f2 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 10 Apr 2018 07:26:12 -0400 Subject: [PATCH 03/63] Changed x-axis to timestamps --- .../healthmonitor/TimingMetricGraphPanel.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index 16d9d82228..cdb5cc44c8 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -134,8 +134,10 @@ class TimingMetricGraphPanel extends JPanel { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Get the max and min timestamps and convert to days to create the x-axis - long maxTimestamp = getMaxTimestamp(); - long minTimestamp = getMinTimestamp(); + long originalMaxTimestamp = getMaxTimestamp(); + double maxTimestamp = (double) originalMaxTimestamp + 1000 * 60 *60 * 4; // Four hour buffer + double minTimestamp = getMinTimestamp() - 1000 * 60 * 60 * 4; // Four hour buffer + System.out.println("originalMax: " + originalMaxTimestamp + " new max: " + maxTimestamp + " new min: " + minTimestamp); //minTimestamp = minTimestamp * 0.995; // This is to get a small gap before the first data points //double maxDaysAgo = convertToDaysAgo(getMinTimestamp(), maxTimestamp); //maxDaysAgo = maxDaysAgo * 1.005; @@ -150,7 +152,7 @@ class TimingMetricGraphPanel extends JPanel { //System.out.println("maxDaysAgo: " + minDaysAgo + ", minDaysAgo: " + maxDaysAgo); double xScale = ((double) getWidth() - (2 * padding) - labelPadding) / (maxTimestamp - minTimestamp); - double yScale = ((double) getHeight() - 2 * padding - labelPadding) / (maxMetricTime - minMetricTime); + double yScale = ((double) getHeight() - (2 * padding) - labelPadding) / (maxMetricTime - minMetricTime); System.out.println("xScale: " + xScale + ", yScale: " + yScale); @@ -185,14 +187,14 @@ class TimingMetricGraphPanel extends JPanel { // What we want is midnight preceding the last recorded value Calendar maxDate = new GregorianCalendar(); - maxDate.setTimeInMillis(maxTimestamp); + maxDate.setTimeInMillis(originalMaxTimestamp); maxDate.set(Calendar.HOUR_OF_DAY, 0); maxDate.set(Calendar.MINUTE, 0); maxDate.set(Calendar.SECOND, 0); maxDate.set(Calendar.MILLISECOND, 0); long lastMidnightInMillis = maxDate.getTimeInMillis(); - System.out.println("Last timestamp: " + maxTimestamp + ", last midnight: " + lastMidnightInMillis); + System.out.println("Last timestamp: " + originalMaxTimestamp + ", last midnight: " + lastMidnightInMillis); // We don't want to display more than 20 lines long totalMidnights = (lastMidnightInMillis - getMinTimestamp()) / MILLISECONDS_PER_DAY; @@ -209,11 +211,13 @@ class TimingMetricGraphPanel extends JPanel { for (long currentDivision = lastMidnightInMillis;currentDivision > 0;currentDivision -= MILLISECONDS_PER_DAY * daysPerDivision) { - /* - int x0 = i * (getWidth() - padding * 2 - labelPadding) / numberOfXDivisions + padding + labelPadding; + + //int x0 = i * (getWidth() - padding * 2 - labelPadding) / numberOfXDivisions + padding + labelPadding; + int x0 = (int) ((double)(currentDivision) * xScale + padding + labelPadding); int x1 = x0; int y0 = getHeight() - padding - labelPadding; int y1 = y0 - pointWidth; + /* if ((i % ((int) ((numberOfXDivisions / 20.0)) + 1)) == 0) { g2.setColor(gridColor); g2.drawLine(x0, getHeight() - padding - labelPadding - 1 - pointWidth, x1, padding); @@ -222,9 +226,9 @@ class TimingMetricGraphPanel extends JPanel { FontMetrics metrics = g2.getFontMetrics(); int labelWidth = metrics.stringWidth(xLabel); g2.drawString(xLabel, x0 - labelWidth / 2, y0 + metrics.getHeight() + 3); - } + }*/ g2.drawLine(x0, y0, x1, y1); - */ + } // create x and y axes @@ -242,7 +246,7 @@ class TimingMetricGraphPanel extends JPanel { int x1 = (int) ((double)(maxTimestamp - timingResults.get(i).getTimestamp()) * xScale + padding + labelPadding); //int x1 = (int) ((maxDaysAgo - convertToDaysAgo(timingResults.get(i).getTimestamp(), maxTimestamp)) * xScale + padding + labelPadding); int yAve = (int) ((maxMetricTime - timingResults.get(i).getAverage()) * yScale + padding); - System.out.println("Adding point " + x1 + ", " + yAve); + //System.out.println("Adding point " + x1 + ", " + yAve); averageGraphPoints.add(new Point(x1, yAve)); } From c698e54365cbd443d532b31b1cd5347cdaeb739a Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 11 Apr 2018 11:09:48 -0400 Subject: [PATCH 04/63] Adding trend line to graph. --- .../autopsy/healthmonitor/Bundle.properties | 19 + .../healthmonitor/MonitorTestAction.java | 82 ++++ .../healthmonitor/ServicesHealthMonitor.java | 21 +- .../autopsy/healthmonitor/TestPanel.form | 325 +++++++++++++ .../autopsy/healthmonitor/TestPanel.java | 442 ++++++++++++++++++ .../healthmonitor/TimingMetricGraphPanel.java | 268 +++++------ .../autopsy/healthmonitor/TrendLine.java | 78 ++++ 7 files changed, 1083 insertions(+), 152 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/Bundle.properties create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/MonitorTestAction.java create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.form create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/TrendLine.java diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Bundle.properties b/Core/src/org/sleuthkit/autopsy/healthmonitor/Bundle.properties new file mode 100644 index 0000000000..42be62eba7 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Bundle.properties @@ -0,0 +1,19 @@ +TestPanel.jLabel1.text=Name +TestPanel.jLabel2.text=Duration (ms) +TestPanel.nameTextField.text= +TestPanel.submitMetricButton.text=Submit +TestPanel.closeButton.text=Close +TestPanel.printMapButton.text=Print Map +TestPanel.durationTextField.text= +TestPanel.jLabel3.text=Set enabled: +TestPanel.enabledCheckBox.text=Enabled +TestPanel.jButton1.text=Write data +TestPanel.deleteButton.text=Delete db +TestPanel.updateUIButton.text=Update UI +TestPanel.textExistButton.text=Test db +TestPanel.graphButton.text=Graph! +TestPanel.jLabel4.text=Number of days +TestPanel.nDaysTextField.text= +TestPanel.jLabel5.text=Number of nodes +TestPanel.nNodesTextField.text= +TestPanel.jButton2.text=Populate DB diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/MonitorTestAction.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/MonitorTestAction.java new file mode 100644 index 0000000000..f17fd15fb2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/MonitorTestAction.java @@ -0,0 +1,82 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.healthmonitor; + +import java.awt.Frame; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionRegistration; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; +import org.openide.util.actions.CallableSystemAction; +import org.openide.windows.WindowManager; + +/* +// TODO: debug + synchronized void printCurrentState() { + System.out.println("\nTiming Info Map:"); + for(String name:timingInfoMap.keySet()) { + System.out.print(name + "\t"); + timingInfoMap.get(name).print(); + } + } + +private void deleteDatabase() { + try { + // Use the same database settings as the case + CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); + Class.forName("org.postgresql.Driver"); //NON-NLS + try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS + Statement statement = connection.createStatement();) { + String deleteCommand = "DROP DATABASE \"" + DATABASE_NAME + "\""; //NON-NLS + statement.execute(deleteCommand); + } + } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { + logger.log(Level.SEVERE, "Failed to delete health monitor database", ex); + } + } +*/ + + +@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.healthmonitor.MonitorTestAction") +@ActionReference(path = "Menu/Tools", position = 7014) +@ActionRegistration(displayName = "#CTL_MonitorTestAction", lazy = false) +@NbBundle.Messages({"CTL_MonitorTestAction=Test health monitor"}) +public final class MonitorTestAction extends CallableSystemAction { + + private static final String DISPLAY_NAME = "Test health monitor"; + + @Override + public boolean isEnabled() { + return true; + } + + @Override + @SuppressWarnings("fallthrough") + public void performAction() { + + Frame mainWindow = WindowManager.getDefault().getMainWindow(); + + TestPanel panel = new TestPanel(mainWindow, false); + panel.setVisible(true); + + } + + @Override + public String getName() { + return DISPLAY_NAME; + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + public boolean asynchronous() { + return false; // run on edt + } +} diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index be02c588d7..c996f39752 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -681,9 +681,9 @@ public class ServicesHealthMonitor { long millisPerHour = 1000 * 60 * 60; long minTimestamp = maxTimestamp - (nDays * (millisPerHour * 24)); - + Connection conn = null; try { - Connection conn = connect(); + conn = connect(); if(conn == null) { throw new HealthMonitorException("Error getting database connection"); } @@ -704,7 +704,7 @@ public class ServicesHealthMonitor { try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) { // Record index chunk every hour - for(long timestamp = minTimestamp;timestamp < maxTimestamp;timestamp += millisPerHour) { + for(long timestamp = minTimestamp + rand.nextInt(1000 * 60 * 55);timestamp < maxTimestamp;timestamp += millisPerHour) { long aveTime; @@ -734,7 +734,7 @@ public class ServicesHealthMonitor { // Make some case opening ones // Record index chunk every hour - for(long timestamp = minTimestamp;timestamp < maxTimestamp;timestamp += (1 + rand.nextInt(10)) * millisPerHour) { + for(long timestamp = minTimestamp + rand.nextInt(1000 * 60 * 55);timestamp < maxTimestamp;timestamp += (1 + rand.nextInt(10)) * millisPerHour) { long aveTime = minConnTime + rand.nextInt(maxConnTimeOverMin); @@ -760,14 +760,17 @@ public class ServicesHealthMonitor { } catch (SQLException ex) { throw new HealthMonitorException("Error saving metric data to database", ex); } finally { - try { - conn.close(); - } catch (SQLException ex) { - logger.log(Level.SEVERE, "Error closing Connection.", ex); - } + } } } finally { + try { + if(conn != null) { + conn.close(); + } + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing Connection.", ex); + } try { lock.release(); } catch (CoordinationService.CoordinationServiceException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.form b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.form new file mode 100644 index 0000000000..a9fa334f6c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.form @@ -0,0 +1,325 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java new file mode 100644 index 0000000000..74a84f71ce --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java @@ -0,0 +1,442 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.healthmonitor; + +import java.awt.Dimension; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import javax.swing.JDialog; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.BorderFactory; +import java.util.Map; +import javax.swing.BoxLayout; +import org.sleuthkit.autopsy.healthmonitor.ServicesHealthMonitor.DatabaseTimingResult; // TEMP TEMP + +/** + * This is for testing the Health Monitor code + */ +public class TestPanel extends javax.swing.JDialog { + + /** + * Creates new form TestPanel + */ + public TestPanel(java.awt.Frame parent, boolean modal) { + super(parent, modal); + initComponents(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jLabel1 = new javax.swing.JLabel(); + jLabel2 = new javax.swing.JLabel(); + nameTextField = new javax.swing.JTextField(); + durationTextField = new javax.swing.JTextField(); + submitMetricButton = new javax.swing.JButton(); + closeButton = new javax.swing.JButton(); + printMapButton = new javax.swing.JButton(); + jLabel3 = new javax.swing.JLabel(); + enabledCheckBox = new javax.swing.JCheckBox(); + jButton1 = new javax.swing.JButton(); + deleteButton = new javax.swing.JButton(); + updateUIButton = new javax.swing.JButton(); + textExistButton = new javax.swing.JButton(); + graphButton = new javax.swing.JButton(); + nDaysTextField = new javax.swing.JTextField(); + jLabel4 = new javax.swing.JLabel(); + jLabel5 = new javax.swing.JLabel(); + nNodesTextField = new javax.swing.JTextField(); + jButton2 = new javax.swing.JButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.jLabel1.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.jLabel2.text")); // NOI18N + + nameTextField.setText(org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.nameTextField.text")); // NOI18N + + durationTextField.setText(org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.durationTextField.text")); // NOI18N + durationTextField.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + durationTextFieldActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(submitMetricButton, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.submitMetricButton.text")); // NOI18N + submitMetricButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + submitMetricButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.closeButton.text")); // NOI18N + closeButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + closeButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(printMapButton, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.printMapButton.text")); // NOI18N + printMapButton.setEnabled(false); + printMapButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + printMapButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.jLabel3.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(enabledCheckBox, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.enabledCheckBox.text")); // NOI18N + enabledCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + enabledCheckBoxActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jButton1, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.jButton1.text")); // NOI18N + jButton1.setEnabled(false); + jButton1.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton1ActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(deleteButton, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.deleteButton.text")); // NOI18N + deleteButton.setEnabled(false); + deleteButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + deleteButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(updateUIButton, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.updateUIButton.text")); // NOI18N + updateUIButton.setEnabled(false); + updateUIButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + updateUIButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(textExistButton, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.textExistButton.text")); // NOI18N + textExistButton.setEnabled(false); + textExistButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + textExistButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(graphButton, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.graphButton.text")); // NOI18N + graphButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + graphButtonActionPerformed(evt); + } + }); + + nDaysTextField.setText(org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.nDaysTextField.text")); // NOI18N + nDaysTextField.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + nDaysTextFieldActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel4, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.jLabel4.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel5, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.jLabel5.text")); // NOI18N + + nNodesTextField.setText(org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.nNodesTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jButton2, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.jButton2.text")); // NOI18N + jButton2.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton2ActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(deleteButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(closeButton)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel1) + .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 87, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(durationTextField) + .addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(18, 18, 18) + .addComponent(submitMetricButton)) + .addGroup(layout.createSequentialGroup() + .addComponent(printMapButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jButton1)) + .addGroup(layout.createSequentialGroup() + .addComponent(enabledCheckBox) + .addGap(18, 18, 18) + .addComponent(updateUIButton)) + .addComponent(textExistButton)) + .addGap(0, 225, Short.MAX_VALUE))) + .addContainerGap()) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel3) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(graphButton) + .addGap(142, 142, 142)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(nDaysTextField)) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel5, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(nNodesTextField)) + .addGap(18, 18, 18) + .addComponent(jButton2) + .addGap(0, 0, Short.MAX_VALUE)))) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(40, 40, 40) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(jLabel2)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(durationTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(submitMetricButton)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(printMapButton) + .addComponent(jButton1)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel3) + .addComponent(graphButton)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(enabledCheckBox) + .addComponent(updateUIButton)) + .addGap(31, 31, 31) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel4) + .addComponent(jLabel5)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(nDaysTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(nNodesTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jButton2)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 66, Short.MAX_VALUE) + .addComponent(textExistButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(closeButton) + .addContainerGap()) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(deleteButton) + .addGap(23, 23, 23)))) + ); + + pack(); + }// //GEN-END:initComponents + + private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed + dispose(); + }//GEN-LAST:event_closeButtonActionPerformed + + private void durationTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_durationTextFieldActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_durationTextFieldActionPerformed + + private void printMapButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_printMapButtonActionPerformed + /*try { + ServicesHealthMonitor.getInstance().printCurrentState(); + } catch (HealthMonitorException ex){ + ex.printStackTrace(); + }*/ + }//GEN-LAST:event_printMapButtonActionPerformed + + private void submitMetricButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_submitMetricButtonActionPerformed + + if(nameTextField.getText().isEmpty() || durationTextField.getText().isEmpty()) { + return; + } + TimingMetric m = ServicesHealthMonitor.getTimingMetric(nameTextField.getText()); + try { + Thread.sleep(Long.parseLong(durationTextField.getText())); + } catch (Exception ex) { + ex.printStackTrace(); + } + ServicesHealthMonitor.submitTimingMetric(m); + }//GEN-LAST:event_submitMetricButtonActionPerformed + + private void enabledCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_enabledCheckBoxActionPerformed + try { + ServicesHealthMonitor.setEnabled(enabledCheckBox.isSelected()); + } catch (HealthMonitorException ex) { + ex.printStackTrace(); + } + }//GEN-LAST:event_enabledCheckBoxActionPerformed + + private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed + /*try { + ServicesHealthMonitor.getInstance().writeCurrentStateToDatabase(); + } catch (HealthMonitorException ex){ + ex.printStackTrace(); + }*/ + }//GEN-LAST:event_jButton1ActionPerformed + + private void deleteButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteButtonActionPerformed + /*try { + ServicesHealthMonitor.getInstance().deleteDatabase(); + } catch (HealthMonitorException ex){ + ex.printStackTrace(); + }*/ + }//GEN-LAST:event_deleteButtonActionPerformed + + private void updateUIButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_updateUIButtonActionPerformed + if (ModuleSettings.settingExists("ServicesHealthMonitor", "is_enabled")) { + if(ModuleSettings.getConfigSetting("ServicesHealthMonitor", "is_enabled").equals("true")){ + enabledCheckBox.setSelected(true); + return; + } + } + enabledCheckBox.setSelected(false); + }//GEN-LAST:event_updateUIButtonActionPerformed + + private void textExistButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_textExistButtonActionPerformed + /*try { + ServicesHealthMonitor.getInstance().databaseExists(); + } catch (Exception ex) { + ex.printStackTrace(); + }*/ + }//GEN-LAST:event_textExistButtonActionPerformed + + private void graphButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_graphButtonActionPerformed + + + // TEMP TEMP + + try { + Map> timingData = ServicesHealthMonitor.getInstance().getTimingMetricsFromDatabase(); + + String[] dateOptionStrings = {"All", "Two weeks", "One week"}; + JComboBox dateComboBox = new JComboBox(dateOptionStrings); + dateComboBox.setSelectedItem("Two weeks"); + + JPanel timingButtonPanel = new JPanel(); + timingButtonPanel.setBorder(BorderFactory.createEtchedBorder()); + //timingButtonPanel.setLayout(new FlowLayout()); + timingButtonPanel.add(new JLabel("Max days to display")); + timingButtonPanel.add(dateComboBox); + + int numberOfMetrics = timingData.keySet().size(); + System.out.println("\n### Number of metric types: " + numberOfMetrics); + + JPanel graphPanel = new JPanel(); + graphPanel.setLayout(new BoxLayout(graphPanel, BoxLayout.PAGE_AXIS)); + graphPanel.setBorder(BorderFactory.createEtchedBorder()); + graphPanel.add(timingButtonPanel); + + for(String name:timingData.keySet()) { + System.out.println(" Making panel for " + name); + JLabel label = new JLabel(name); + graphPanel.add(label); + TimingMetricGraphPanel timingGraphPanel = new TimingMetricGraphPanel(timingData.get(name), TimingMetricGraphPanel.TimingMetricType.AVERAGE, true); + timingGraphPanel.setPreferredSize(new Dimension(800,200)); + graphPanel.add(timingGraphPanel); + } + + graphPanel.revalidate(); + graphPanel.repaint(); + + + JScrollPane scrollPane = new JScrollPane(graphPanel); + + System.out.println("Creating dialog"); + JDialog dialog = new JDialog(); + dialog.setPreferredSize(new Dimension(1500, 800)); + dialog.setTitle("Services Health Monitor"); + //dialog.add(graphPanel); + dialog.add(scrollPane); + dialog.pack(); + dialog.setVisible(true); + System.out.println("Done displaying dialog"); + } catch (Exception ex) { + ex.printStackTrace(); + } + + + }//GEN-LAST:event_graphButtonActionPerformed + + private void nDaysTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nDaysTextFieldActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_nDaysTextFieldActionPerformed + + private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed + + if(nDaysTextField.getText().isEmpty() || nNodesTextField.getText().isEmpty()) { + System.out.println("Missing fields"); + return; + } + + try { + int nDays = Integer.valueOf(nDaysTextField.getText()); + int nNodes = Integer.valueOf(nNodesTextField.getText()); + ServicesHealthMonitor.getInstance().populateDatabase(nDays, nNodes); // TEMP TEMP + } catch (Exception ex) { + ex.printStackTrace(); + } + + }//GEN-LAST:event_jButton2ActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton closeButton; + private javax.swing.JButton deleteButton; + private javax.swing.JTextField durationTextField; + private javax.swing.JCheckBox enabledCheckBox; + private javax.swing.JButton graphButton; + private javax.swing.JButton jButton1; + private javax.swing.JButton jButton2; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JLabel jLabel5; + private javax.swing.JTextField nDaysTextField; + private javax.swing.JTextField nNodesTextField; + private javax.swing.JTextField nameTextField; + private javax.swing.JButton printMapButton; + private javax.swing.JButton submitMetricButton; + private javax.swing.JButton textExistButton; + private javax.swing.JButton updateUIButton; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index cdb5cc44c8..71ce2008eb 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -18,19 +18,25 @@ */ package org.sleuthkit.autopsy.healthmonitor; +import com.mchange.v2.cfg.DelayedLogItem; import java.awt.BasicStroke; import java.awt.Color; +import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.RenderingHints; import java.awt.Stroke; +import java.util.Collections; +import java.util.Comparator; import java.util.ArrayList; import java.util.List; import java.util.Calendar; import java.util.GregorianCalendar; import javax.swing.JPanel; +import org.sleuthkit.autopsy.coreutils.Logger; +import java.util.logging.Level; import org.sleuthkit.autopsy.healthmonitor.ServicesHealthMonitor.DatabaseTimingResult; /** @@ -38,49 +44,61 @@ import org.sleuthkit.autopsy.healthmonitor.ServicesHealthMonitor.DatabaseTimingR */ class TimingMetricGraphPanel extends JPanel { + private final static Logger logger = Logger.getLogger(TimingMetricGraphPanel.class.getName()); + private int width = 800; private int heigth = 400; private int padding = 25; private int labelPadding = 25; - private Color lineColor = new Color(44, 102, 230, 180); + private Color lineColor = new Color(0x12, 0x20, 0xdb, 180); private Color pointColor = new Color(100, 100, 100, 180); private Color gridColor = new Color(200, 200, 200, 200); + private Color trendLineColor = new Color(150, 10, 10, 200); private static final Stroke GRAPH_STROKE = new BasicStroke(2f); private int pointWidth = 4; private int numberYDivisions = 10; private final List timingResults; private final TimingMetricType timingMetricType; - private boolean doLineGraph = false; + private final boolean doLineGraph; + private TrendLine trendLine; private final long MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24; - TimingMetricGraphPanel(List timingResults, TimingMetricType timingMetricType) { + TimingMetricGraphPanel(List timingResults, TimingMetricType timingMetricType, boolean doLineGraph) { this.timingResults = timingResults; this.timingMetricType = timingMetricType; + this.doLineGraph = doLineGraph; + try { + trendLine = new TrendLine(timingResults, timingMetricType); + } catch (HealthMonitorException ex) { + // Log it, set trendLine to null and continue on + logger.log(Level.WARNING, "Can not generate a trend line on empty data set"); + trendLine = null; + } } private double getMaxMetricTime() { - // Find the highest of the max values + // Find the highest of the values being graphed double maxScore = Double.MIN_VALUE; for (DatabaseTimingResult score : timingResults) { // Use only the data we're graphing to determing the max switch (timingMetricType) { case MAX: - case ALL: maxScore = Math.max(maxScore, score.getMax()); break; - case AVERAGE: - maxScore = Math.max(maxScore, score.getAverage()); - break; case MIN: maxScore = Math.max(maxScore, score.getMin()); break; + case AVERAGE: + default: + maxScore = Math.max(maxScore, score.getAverage()); + break; } } return maxScore; } private double getMinMetricTime() { - // Find the highest of the max values + // Find the lowest of the values being graphed double minScore = Double.MAX_VALUE; for (DatabaseTimingResult score : timingResults) { // Use only the data we're graphing to determing the min @@ -88,13 +106,13 @@ class TimingMetricGraphPanel extends JPanel { case MAX: minScore = Math.min(minScore, score.getMax()); break; - case AVERAGE: - minScore = Math.min(minScore, score.getAverage()); - break; case MIN: - case ALL: minScore = Math.min(minScore, score.getMin()); break; + case AVERAGE: + default: + minScore = Math.min(minScore, score.getAverage()); + break; } } return minScore; @@ -115,17 +133,6 @@ class TimingMetricGraphPanel extends JPanel { } return minTimestamp; } - - /* - private double convertToDaysAgo(long thisTimestamp, long maxTimestamp) { - long diffInMilliSecs = maxTimestamp - thisTimestamp; - //System.out.println("Diff between " + maxTimestamp + " and " + thisTimestamp + " = " + diffInMilliSecs + " (milliseconds)"); - double diffInSeconds = diffInMilliSecs / (1000L); - //System.out.println(" Diff in seconds: " + diffInSeconds); - double result = diffInSeconds / (60 * 60 * 24); - //System.out.println(" Diff in days: " + result); - return (diffInSeconds / (60 * 60 * 24)); - }*/ @Override protected void paintComponent(Graphics g) { @@ -138,10 +145,6 @@ class TimingMetricGraphPanel extends JPanel { double maxTimestamp = (double) originalMaxTimestamp + 1000 * 60 *60 * 4; // Four hour buffer double minTimestamp = getMinTimestamp() - 1000 * 60 * 60 * 4; // Four hour buffer System.out.println("originalMax: " + originalMaxTimestamp + " new max: " + maxTimestamp + " new min: " + minTimestamp); - //minTimestamp = minTimestamp * 0.995; // This is to get a small gap before the first data points - //double maxDaysAgo = convertToDaysAgo(getMinTimestamp(), maxTimestamp); - //maxDaysAgo = maxDaysAgo * 1.005; - //double minDaysAgo = 0; // Get the max and min times to create the y-axis double maxMetricTime = getMaxMetricTime(); @@ -149,8 +152,6 @@ class TimingMetricGraphPanel extends JPanel { minMetricTime = Math.max(0, minMetricTime - (maxMetricTime * 0.1)); maxMetricTime = maxMetricTime * 1.1; - - //System.out.println("maxDaysAgo: " + minDaysAgo + ", minDaysAgo: " + maxDaysAgo); double xScale = ((double) getWidth() - (2 * padding) - labelPadding) / (maxTimestamp - minTimestamp); double yScale = ((double) getHeight() - (2 * padding) - labelPadding) / (maxMetricTime - minMetricTime); @@ -178,153 +179,134 @@ class TimingMetricGraphPanel extends JPanel { } g2.drawLine(x0, y0, x1, y1); } - - // and for x axis - // Calculate the number of divisions for the X axis - this will be - // the number of days between the first and last metrics - //int numberOfXDivisions = -1 * (int)(maxDaysAgo); - - // What we want is midnight preceding the last recorded value + // On the x-axis, the farthest right grid line should represent midnight preceding the last recorded value Calendar maxDate = new GregorianCalendar(); maxDate.setTimeInMillis(originalMaxTimestamp); maxDate.set(Calendar.HOUR_OF_DAY, 0); maxDate.set(Calendar.MINUTE, 0); maxDate.set(Calendar.SECOND, 0); maxDate.set(Calendar.MILLISECOND, 0); + long maxMidnightInMillis = maxDate.getTimeInMillis(); + System.out.println("Last timestamp: " + originalMaxTimestamp + ", last midnight: " + maxMidnightInMillis); - long lastMidnightInMillis = maxDate.getTimeInMillis(); - System.out.println("Last timestamp: " + originalMaxTimestamp + ", last midnight: " + lastMidnightInMillis); - - // We don't want to display more than 20 lines - long totalMidnights = (lastMidnightInMillis - getMinTimestamp()) / MILLISECONDS_PER_DAY; - System.out.println(" Total midnights: " + totalMidnights); + // We don't want to display more than 20 grid lines + long totalDays = (maxMidnightInMillis - getMinTimestamp()) / MILLISECONDS_PER_DAY; + System.out.println(" Total days: " + totalDays); long daysPerDivision; - if(totalMidnights <= 20) { + if(totalDays <= 20) { daysPerDivision = 1; } else { - daysPerDivision = (totalMidnights / 20); - if((totalMidnights % 20) != 0) { + daysPerDivision = (totalDays / 20); + if((totalDays % 20) != 0) { daysPerDivision++; } } - for (long currentDivision = lastMidnightInMillis;currentDivision > 0;currentDivision -= MILLISECONDS_PER_DAY * daysPerDivision) { + // Draw the vertical grid lines and labels + for (long currentDivision = maxMidnightInMillis; currentDivision >= minTimestamp; currentDivision -= MILLISECONDS_PER_DAY * daysPerDivision) { + int x0 = (int) ((double)(currentDivision - minTimestamp) * xScale + padding + labelPadding); + int x1 = x0; + int y0 = getHeight() - padding - labelPadding; + int y1 = y0 - pointWidth; - //int x0 = i * (getWidth() - padding * 2 - labelPadding) / numberOfXDivisions + padding + labelPadding; - int x0 = (int) ((double)(currentDivision) * xScale + padding + labelPadding); - int x1 = x0; - int y0 = getHeight() - padding - labelPadding; - int y1 = y0 - pointWidth; - /* - if ((i % ((int) ((numberOfXDivisions / 20.0)) + 1)) == 0) { - g2.setColor(gridColor); - g2.drawLine(x0, getHeight() - padding - labelPadding - 1 - pointWidth, x1, padding); - g2.setColor(Color.BLACK); - String xLabel = i + ""; - FontMetrics metrics = g2.getFontMetrics(); - int labelWidth = metrics.stringWidth(xLabel); - g2.drawString(xLabel, x0 - labelWidth / 2, y0 + metrics.getHeight() + 3); - }*/ - g2.drawLine(x0, y0, x1, y1); - + // Draw the light grey grid line + g2.setColor(gridColor); + g2.drawLine(x0, getHeight() - padding - labelPadding - 1 - pointWidth, x1, padding); + + // Draw the hatch mark + g2.setColor(Color.BLACK); + g2.drawLine(x0, y0, x1, y1); + + // Draw the label + Calendar thisDate = new GregorianCalendar(); + thisDate.setTimeInMillis(currentDivision); + int month = thisDate.get(Calendar.MONTH) + 1; + int day = thisDate.get(Calendar.DAY_OF_MONTH); + String xLabel = month + "/" + day; + FontMetrics metrics = g2.getFontMetrics(); + int labelWidth = metrics.stringWidth(xLabel); + g2.drawString(xLabel, x0 - labelWidth / 2, y0 + metrics.getHeight() + 3); } // create x and y axes g2.drawLine(padding + labelPadding, getHeight() - padding - labelPadding, padding + labelPadding, padding); g2.drawLine(padding + labelPadding, getHeight() - padding - labelPadding, getWidth() - padding, getHeight() - padding - labelPadding); - - - Stroke oldStroke = g2.getStroke(); - g2.setStroke(GRAPH_STROKE); - - // Plot the average timing points - if(timingMetricType.equals(TimingMetricType.ALL) || timingMetricType.equals(TimingMetricType.AVERAGE)) { - List averageGraphPoints = new ArrayList<>(); - for (int i = 0; i < timingResults.size(); i++) { - int x1 = (int) ((double)(maxTimestamp - timingResults.get(i).getTimestamp()) * xScale + padding + labelPadding); - //int x1 = (int) ((maxDaysAgo - convertToDaysAgo(timingResults.get(i).getTimestamp(), maxTimestamp)) * xScale + padding + labelPadding); - int yAve = (int) ((maxMetricTime - timingResults.get(i).getAverage()) * yScale + padding); - //System.out.println("Adding point " + x1 + ", " + yAve); - averageGraphPoints.add(new Point(x1, yAve)); - } - g2.setColor(lineColor); - if(doLineGraph) { - for (int i = 0; i < averageGraphPoints.size() - 1; i++) { - int x1 = averageGraphPoints.get(i).x; - int y1 = averageGraphPoints.get(i).y; - int x2 = averageGraphPoints.get(i + 1).x; - int y2 = averageGraphPoints.get(i + 1).y; - g2.drawLine(x1, y1, x2, y2); + // Plot the timing points + g2.setStroke(GRAPH_STROKE); + List graphPoints = new ArrayList<>(); + for (int i = 0; i < timingResults.size(); i++) { + double metricTime; + switch (timingMetricType) { + case MAX: + metricTime = timingResults.get(i).getMax(); + break; + case MIN: + metricTime = timingResults.get(i).getMin(); + break; + case AVERAGE: + default: + metricTime = timingResults.get(i).getAverage(); + break; + + } + + int x1 = (int) ((double)(maxTimestamp - timingResults.get(i).getTimestamp()) * xScale + padding + labelPadding); + int yAve = (int) ((maxMetricTime - metricTime) * yScale + padding); + graphPoints.add(new Point(x1, yAve)); + } + + // Sort the points + Collections.sort(graphPoints, new Comparator() { + @Override + public int compare(Point o1, Point o2) { + if(o1.getX() > o2.getX()) { + return 1; + } else if (o1.getX() < o2.getX()) { + return -1; } - } else { - for (int i = 0; i < averageGraphPoints.size(); i++) { - int x = averageGraphPoints.get(i).x - pointWidth / 2; - int y = averageGraphPoints.get(i).y - pointWidth / 2; + return 0; + } + }); + + System.out.println("points: "); + for(Point p:graphPoints){ + System.out.println(p.getX() + ", " + p.getY()); + } + + g2.setColor(lineColor); + if(doLineGraph) { + for (int i = 0; i < graphPoints.size() - 1; i++) { + int x1 = graphPoints.get(i).x; + int y1 = graphPoints.get(i).y; + int x2 = graphPoints.get(i + 1).x; + int y2 = graphPoints.get(i + 1).y; + g2.drawLine(x1, y1, x2, y2); + } + } else { + for (int i = 0; i < graphPoints.size(); i++) { + int x = graphPoints.get(i).x - pointWidth / 2; + int y = graphPoints.get(i).y - pointWidth / 2; int ovalW = pointWidth; int ovalH = pointWidth; g2.fillOval(x, y, ovalW, ovalH); } - } } - // Plot the maximum timing points - if(timingMetricType.equals(TimingMetricType.ALL) || timingMetricType.equals(TimingMetricType.MIN)) { - List minGraphPoints = new ArrayList<>(); - for (int i = 0; i < timingResults.size(); i++) { - int x1 = (int) (i * xScale + padding + labelPadding); - int yMin = (int) ((maxMetricTime - timingResults.get(i).getMin()) * yScale + padding); - minGraphPoints.add(new Point(x1, yMin)); - } - - g2.setColor(new Color(10, 200, 20)); - for (int i = 0; i < minGraphPoints.size() - 1; i++) { - int x1 = minGraphPoints.get(i).x; - int y1 = minGraphPoints.get(i).y; - int x2 = minGraphPoints.get(i + 1).x; - int y2 = minGraphPoints.get(i + 1).y; - g2.drawLine(x1, y1, x2, y2); - } - } - - // Plot the minimum timing points - if(timingMetricType.equals(TimingMetricType.ALL) || timingMetricType.equals(TimingMetricType.MIN)) { - List maxGraphPoints = new ArrayList<>(); - for (int i = 0; i < timingResults.size(); i++) { - int x1 = (int) (i * xScale + padding + labelPadding); - int yMax = (int) ((maxMetricTime - timingResults.get(i).getMax()) * yScale + padding); - maxGraphPoints.add(new Point(x1, yMax)); - } - - g2.setColor(new Color(10, 10, 200)); - for (int i = 0; i < maxGraphPoints.size() - 1; i++) { - int x1 = maxGraphPoints.get(i).x; - int y1 = maxGraphPoints.get(i).y; - int x2 = maxGraphPoints.get(i + 1).x; - int y2 = maxGraphPoints.get(i + 1).y; - g2.drawLine(x1, y1, x2, y2); - } - } - - /* - // To draw points - g2.setStroke(oldStroke); - g2.setColor(pointColor); - for (int i = 0; i < graphPoints.size(); i++) { - int x = graphPoints.get(i).x - pointWidth / 2; - int y = graphPoints.get(i).y - pointWidth / 2; - int ovalW = pointWidth; - int ovalH = pointWidth; - g2.fillOval(x, y, ovalW, ovalH); - }*/ + // Draw the trend line + int x0 = (int) ((double)(maxTimestamp - minTimestamp) * xScale + padding + labelPadding); + int y0 = (int) ((double)(maxMetricTime - trendLine.getExpectedValueAt(minTimestamp)) * yScale + padding); + int x1 = (int) ((double)(0) * xScale + padding + labelPadding); + int y1 = (int) ((double)(maxMetricTime - trendLine.getExpectedValueAt(maxTimestamp)) * yScale + padding); + g2.setColor(trendLineColor); + g2.drawLine(x0, y0, x1, y1); } enum TimingMetricType { AVERAGE, MAX, - MIN, - ALL; + MIN; } } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TrendLine.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TrendLine.java new file mode 100644 index 0000000000..3cabc2cdae --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TrendLine.java @@ -0,0 +1,78 @@ +/* + * 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 java.util.List; +import org.sleuthkit.autopsy.healthmonitor.ServicesHealthMonitor.DatabaseTimingResult; + +/** + * + */ +class TrendLine { + + double slope; + double yInt; + + TrendLine(List timingResults, TimingMetricGraphPanel.TimingMetricType timingMetricType) throws HealthMonitorException { + + if((timingResults == null) || timingResults.isEmpty()) { + throw new HealthMonitorException("Can not generate trend line for empty/null data set"); + } + + // Calculate intermediate values + int n = timingResults.size(); + double sumX = 0; + double sumY = 0; + double sumXY = 0; + double sumXsquared = 0; + for(int i = 0;i < n;i++) { + double x = timingResults.get(i).getTimestamp(); + double y; + switch (timingMetricType) { + case MAX: + y = timingResults.get(i).getMax(); + break; + case MIN: + y = timingResults.get(i).getMin(); + break; + case AVERAGE: + default: + y = timingResults.get(i).getAverage(); + break; + } + + sumX += x; + sumY += y; + sumXY += x * y; + sumXsquared += x * x; + } + + // Calculate slope + slope = (n * sumXY - sumX * sumY) / (n * sumXsquared - sumX * sumX); + + // Calculate y intercept + yInt = (sumY - slope * sumX) / n; + + System.out.println("Trend line: y = " + slope + " * x + " + yInt); + } + + double getExpectedValueAt(double x) { + return (slope * x + yInt); + } +} From 0633a871308c903c24885e9572bca73fc07c3b75 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 11 Apr 2018 14:03:49 -0400 Subject: [PATCH 05/63] Added the health dashboard class --- .../autopsy/healthmonitor/Bundle.properties | 1 + .../healthmonitor/HealthMonitorDashboard.java | 219 ++++++++++++++++++ .../autopsy/healthmonitor/TestPanel.form | 15 +- .../autopsy/healthmonitor/TestPanel.java | 25 +- .../healthmonitor/TimingMetricGraphPanel.java | 28 ++- 5 files changed, 282 insertions(+), 6 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Bundle.properties b/Core/src/org/sleuthkit/autopsy/healthmonitor/Bundle.properties index 42be62eba7..4185424161 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Bundle.properties @@ -17,3 +17,4 @@ TestPanel.nDaysTextField.text= TestPanel.jLabel5.text=Number of nodes TestPanel.nNodesTextField.text= TestPanel.jButton2.text=Populate DB +TestPanel.newGraphButton.text=New graph! diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java new file mode 100644 index 0000000000..189373c32d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java @@ -0,0 +1,219 @@ +/* + * 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.mchange.v2.cfg.DelayedLogItem; +import java.awt.Dimension; +import java.util.List; +import java.util.Map; +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 javax.swing.JDialog; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.BorderFactory; +import java.util.Map; +import javax.swing.BoxLayout; +import java.util.logging.Level; +import java.util.stream.Collectors; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * + */ +// TODO: keep public? +public class HealthMonitorDashboard { + + private final static Logger logger = Logger.getLogger(HealthMonitorDashboard.class.getName()); + + //Map> timingData; + + private JPanel timingMetricPanel = null; + private JPanel timingButtonPanel = null; + private JComboBox dateComboBox = null; + + HealthMonitorDashboard() { + + } + + void display() throws HealthMonitorException { + + // Initialize and populate the timing metric panel + populateTimingMetricPanel(); + addActionListeners(); + + System.out.println("Creating dialog"); + JScrollPane scrollPane = new JScrollPane(timingMetricPanel); + JDialog dialog = new JDialog(); + dialog.setPreferredSize(new Dimension(1500, 800)); + dialog.setTitle("Services 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. + */ + private void initializeTimingButtonPanel() throws HealthMonitorException { + if(timingButtonPanel == 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); + } + } + + /** + * Add any needed action listeners. + * 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); + } + } + }); + } + + /** + * Initialize the panel holding the timing metrics. + * If it has not been initialized, create and set up the panel. + * Otherwise, clear any existing components out of the panel. + */ + private void initializeTimingMetricPanel() { + if(timingMetricPanel == null) { + timingMetricPanel = new JPanel(); + timingMetricPanel.setLayout(new BoxLayout(timingMetricPanel, BoxLayout.PAGE_AXIS)); + timingMetricPanel.setBorder(BorderFactory.createEtchedBorder()); + } else { + // Clear out any existing components + timingMetricPanel.removeAll(); + } + } + + private void populateTimingMetricPanel() throws HealthMonitorException { + initializeTimingButtonPanel(); + initializeTimingMetricPanel(); + + // Get a fresh copy of the timing data from the database + Map> timingData = ServicesHealthMonitor.getInstance().getTimingMetricsFromDatabase(); + + // Add the button controls + timingMetricPanel.add(timingButtonPanel); + + for(String name:timingData.keySet()) { + // Add the metric name + JLabel label = new JLabel(name); + timingMetricPanel.add(label); + + // If necessary, trim down the list of results to fit the selected time range + List timingDataForDisplay; + 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() + .filter(t -> t.getTimestamp() > threshold) + .collect(Collectors.toList()); + } else { + timingDataForDisplay = timingData.get(name); + } + } else { + timingDataForDisplay = timingData.get(name); + } + + TimingMetricGraphPanel singleTimingGraphPanel = new TimingMetricGraphPanel(timingDataForDisplay, TimingMetricGraphPanel.TimingMetricType.AVERAGE, true); + singleTimingGraphPanel.setPreferredSize(new Dimension(800,200)); + timingMetricPanel.add(singleTimingGraphPanel); + } + + timingMetricPanel.revalidate(); + timingMetricPanel.repaint(); + } + + enum DateRange { + ALL("All", 0), + TWO_WEEKS("Two weeks", 14), + ONE_WEEK("One week", 7); + + private final String label; + private final long numberOfDays; + private static final long MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24; + + DateRange(String label, long numberOfDays) { + this.label = label; + this.numberOfDays = numberOfDays; + } + + /** + * Get the name for display in the UI + * @return the name + */ + String getLabel() { + return label; + } + + /** + * Get the number of milliseconds represented by this date range. + * Compare the timestamps to ((current time in millis) - (this value)) to + * determine if they are in the range + * @return the time range in milliseconds + */ + long getTimestampRange() { + if (numberOfDays > 0) { + return numberOfDays * MILLISECONDS_PER_DAY; + } else { + return Long.MAX_VALUE; + } + } + + public static DateRange fromLabel(String text) { + for (DateRange dateRange : DateRange.values()) { + if (dateRange.label.equalsIgnoreCase(text)) { + return dateRange; + } + } + return ALL; // If the comparison failed, return the default + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.form b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.form index a9fa334f6c..62e0a5eb17 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.form +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.form @@ -60,7 +60,9 @@ - + + + @@ -117,6 +119,7 @@ + @@ -321,5 +324,15 @@ + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java index 74a84f71ce..bca5a729d8 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java @@ -61,6 +61,7 @@ public class TestPanel extends javax.swing.JDialog { jLabel5 = new javax.swing.JLabel(); nNodesTextField = new javax.swing.JTextField(); jButton2 = new javax.swing.JButton(); + newGraphButton = new javax.swing.JButton(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); @@ -167,6 +168,13 @@ public class TestPanel extends javax.swing.JDialog { } }); + org.openide.awt.Mnemonics.setLocalizedText(newGraphButton, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.newGraphButton.text")); // NOI18N + newGraphButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + newGraphButtonActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( @@ -201,7 +209,9 @@ public class TestPanel extends javax.swing.JDialog { .addGap(18, 18, 18) .addComponent(updateUIButton)) .addComponent(textExistButton)) - .addGap(0, 225, Short.MAX_VALUE))) + .addGap(28, 28, 28) + .addComponent(newGraphButton) + .addGap(0, 108, Short.MAX_VALUE))) .addContainerGap()) .addGroup(layout.createSequentialGroup() .addComponent(jLabel3) @@ -243,7 +253,8 @@ public class TestPanel extends javax.swing.JDialog { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(enabledCheckBox) - .addComponent(updateUIButton)) + .addComponent(updateUIButton) + .addComponent(newGraphButton)) .addGap(31, 31, 31) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel4) @@ -418,6 +429,15 @@ public class TestPanel extends javax.swing.JDialog { }//GEN-LAST:event_jButton2ActionPerformed + private void newGraphButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newGraphButtonActionPerformed + HealthMonitorDashboard dashboard = new HealthMonitorDashboard(); + try { + dashboard.display(); + } catch (Exception ex) { + ex.printStackTrace(); + } + }//GEN-LAST:event_newGraphButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton closeButton; private javax.swing.JButton deleteButton; @@ -434,6 +454,7 @@ public class TestPanel extends javax.swing.JDialog { private javax.swing.JTextField nDaysTextField; private javax.swing.JTextField nNodesTextField; private javax.swing.JTextField nameTextField; + private javax.swing.JButton newGraphButton; private javax.swing.JButton printMapButton; private javax.swing.JButton submitMetricButton; private javax.swing.JButton textExistButton; diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index 71ce2008eb..c83497aa5c 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -57,9 +57,9 @@ class TimingMetricGraphPanel extends JPanel { private static final Stroke GRAPH_STROKE = new BasicStroke(2f); private int pointWidth = 4; private int numberYDivisions = 10; - private final List timingResults; - private final TimingMetricType timingMetricType; - private final boolean doLineGraph; + private List timingResults; + private TimingMetricType timingMetricType; + private boolean doLineGraph; private TrendLine trendLine; private final long MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24; @@ -76,6 +76,21 @@ class TimingMetricGraphPanel extends JPanel { } } + /*void loadNewData(List timingResults, TimingMetricType timingMetricType, boolean doLineGraph) { + this.timingResults = timingResults; + this.timingMetricType = timingMetricType; + this.doLineGraph = doLineGraph; + try { + trendLine = new TrendLine(timingResults, timingMetricType); + } catch (HealthMonitorException ex) { + // Log it, set trendLine to null and continue on + logger.log(Level.WARNING, "Can not generate a trend line on empty data set"); + trendLine = null; + } + this.revalidate(); + this.repaint(); + }*/ + private double getMaxMetricTime() { // Find the highest of the values being graphed double maxScore = Double.MIN_VALUE; @@ -134,6 +149,10 @@ class TimingMetricGraphPanel extends JPanel { return minTimestamp; } + /** + * Origin (0,0) is at the top left corner + * @param g + */ @Override protected void paintComponent(Graphics g) { super.paintComponent(g); @@ -302,6 +321,9 @@ class TimingMetricGraphPanel extends JPanel { int y1 = (int) ((double)(maxMetricTime - trendLine.getExpectedValueAt(maxTimestamp)) * yScale + padding); g2.setColor(trendLineColor); g2.drawLine(x0, y0, x1, y1); + + // TODO - temp testing where origin is + g2.fillOval(0, 0, 20, 20); } enum TimingMetricType { From 0cbd4e67119003938cabb1ec2bd1dd652591523d Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 13 Apr 2018 08:02:55 -0400 Subject: [PATCH 06/63] Added millisecond label --- .../autopsy/healthmonitor/HealthMonitorDashboard.java | 2 ++ .../autopsy/healthmonitor/TimingMetricGraphPanel.java | 11 +++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java index 49fe2251c3..2a8d84df61 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java @@ -235,6 +235,8 @@ public class HealthMonitorDashboard { JLabel metricNameLabel = new JLabel(name); metricNameLabel.setFont(new Font("Serif", Font.BOLD, 12)); timingMetricPanel.add(metricNameLabel); + JLabel timingLabel = new JLabel("Displaying time in milliseconds"); + timingMetricPanel.add(timingLabel); // If necessary, trim down the list of results to fit the selected time range List intermediateTimingDataForDisplay; diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index 487d059405..e7e0e96379 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -257,8 +257,7 @@ class TimingMetricGraphPanel extends JPanel { // At present we use GMT because of some complications with daylight savings time. for (long currentDivision = maxMidnightInMillis; currentDivision >= minValueOnXAxis; currentDivision -= MILLISECONDS_PER_DAY * daysPerDivision) { - //long currentDivision = - int x0 = (int) ((double)(currentDivision - minValueOnXAxis) * xScale + leftGraphPadding); + int x0 = (int) ((currentDivision - minValueOnXAxis) * xScale + leftGraphPadding); int x1 = x0; int y0 = getHeight() - bottomGraphPadding; int y1 = y0 - pointWidth; @@ -350,10 +349,10 @@ class TimingMetricGraphPanel extends JPanel { // Draw the trend line // Don't draw anything if there's only one data point if(trendLine != null && (timingResults.size() > 1)) { - int x0 = (int) ((double)(0) * xScale + padding + labelPadding); - int y0 = (int) ((double)(maxValueOnYAxis - trendLine.getExpectedValueAt(minValueOnXAxis)) * yScale + padding); - int x1 = (int) ((double)(maxValueOnXAxis - minValueOnXAxis) * xScale + padding + labelPadding); - int y1 = (int) ((double)(maxValueOnYAxis - trendLine.getExpectedValueAt(maxValueOnXAxis)) * yScale + padding); + int x0 = (int) (padding + labelPadding); + int y0 = (int) ((maxValueOnYAxis - trendLine.getExpectedValueAt(minValueOnXAxis)) * yScale + padding); + int x1 = (int) ((maxValueOnXAxis - minValueOnXAxis) * xScale + padding + labelPadding); + int y1 = (int) ((maxValueOnYAxis - trendLine.getExpectedValueAt(maxValueOnXAxis)) * yScale + padding); g2.setStroke(GRAPH_STROKE); g2.setColor(trendLineColor); g2.drawLine(x0, y0, x1, y1); From d1db85de89668883f9b1ecc86bceac3139a755e0 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 16 Apr 2018 13:17:41 -0400 Subject: [PATCH 07/63] Graph is now a consistent scale for different hosts. --- .../EnterpriseHealthMonitor.java | 24 ++- .../healthmonitor/HealthMonitorDashboard.java | 29 +-- .../autopsy/healthmonitor/TestPanel.java | 2 +- .../healthmonitor/TimingMetricGraphPanel.java | 165 ++++++++++++------ 4 files changed, 141 insertions(+), 79 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index 6d7825c10d..373c9e4274 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -675,8 +675,10 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } /** - * TODO: remove this - testing only - * Will put a bunch of sample data into the database + * Debugging method to generate sample data for the database. + * It will delete all current timing data and replace it with randomly generated values. + * If it is set to generate data for more than one node, the second node may be set to + * take longer than the others. */ final void populateDatabase(int nDays, int nNodes, boolean createVerificationData) throws HealthMonitorException { @@ -692,9 +694,11 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { int minIndexTime = 9000000; int maxIndexTimeOverMin = 50000000; + long indexMultiplier = 1; // To test different scales int minConnTime = 15000000; int maxConnTimeOverMin = 18000000; + long connTimeMultiplier = 1; // To test different scales Random rand = new Random(); @@ -724,11 +728,22 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)"; try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) { + double count = 0; + double maxCount = nDays * 24 + 1; + // Record index chunk every hour for(long timestamp = minTimestamp + rand.nextInt(1000 * 60 * 55);timestamp < maxTimestamp;timestamp += millisPerHour) { long aveTime; + // This creates data that increases in the last couple of days of the simulated + // collection + count++; + double slowNodeMultiplier = 1.0; + if((maxCount - count) <= 3 * 24) { + slowNodeMultiplier += (3 - (maxCount - count) / 24) * 0.33; + } + if( ! createVerificationData ) { // Try to make a reasonable sample data set, with most points in a small range // but some higher and lower @@ -742,6 +757,10 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } else { aveTime = minIndexTime + rand.nextInt(maxIndexTimeOverMin); } + aveTime = aveTime * indexMultiplier; + if(node == 1) { + aveTime = (long)((double)aveTime * slowNodeMultiplier); + } } 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 @@ -781,6 +800,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } else if(outlierVal == 8){ aveTime = (minConnTime / 2) + rand.nextInt(minConnTime / 2); } + aveTime *= connTimeMultiplier; } 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 diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java index 2a8d84df61..30c3c39a6a 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java @@ -18,18 +18,13 @@ */ 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; @@ -84,7 +79,6 @@ public class HealthMonitorDashboard { JDialog dialog = new JDialog(); //dialog.setPreferredSize(new Dimension(1500, 800)); dialog.setTitle("Enterprise Health Monitor"); - //dialog.add(graphPanel); dialog.add(scrollPane); dialog.pack(); dialog.setVisible(true); @@ -231,12 +225,6 @@ public class HealthMonitorDashboard { timingMetricPanel.add(new JSeparator()); for(String name:timingData.keySet()) { - // Add the metric name - JLabel metricNameLabel = new JLabel(name); - metricNameLabel.setFont(new Font("Serif", Font.BOLD, 12)); - timingMetricPanel.add(metricNameLabel); - JLabel timingLabel = new JLabel("Displaying time in milliseconds"); - timingMetricPanel.add(timingLabel); // If necessary, trim down the list of results to fit the selected time range List intermediateTimingDataForDisplay; @@ -254,17 +242,18 @@ public class HealthMonitorDashboard { intermediateTimingDataForDisplay = timingData.get(name); } - // If necessary, trim down the resulting list to only one host name - List timingDataForDisplay; + // Get the name of the selected host, if there is one + String hostToDisplay = null; if(hostCheckBox.isSelected() && (hostComboBox.getSelectedItem() != null)) { - timingDataForDisplay = intermediateTimingDataForDisplay.stream() - .filter(t -> t.getHostName().equals(hostComboBox.getSelectedItem().toString())) - .collect(Collectors.toList()); - } else { - timingDataForDisplay = intermediateTimingDataForDisplay; + hostToDisplay = hostComboBox.getSelectedItem().toString(); } - TimingMetricGraphPanel singleTimingGraphPanel = new TimingMetricGraphPanel(timingDataForDisplay, TimingMetricGraphPanel.TimingMetricType.AVERAGE, true); + TimingMetricGraphPanel singleTimingGraphPanel = new TimingMetricGraphPanel(intermediateTimingDataForDisplay, + TimingMetricGraphPanel.TimingMetricType.AVERAGE, hostToDisplay, true); + // Add the metric name + JLabel metricNameLabel = new JLabel(name); + metricNameLabel.setFont(new Font("Serif", Font.BOLD, 12)); + timingMetricPanel.add(metricNameLabel); singleTimingGraphPanel.setPreferredSize(new Dimension(900,250)); timingMetricPanel.add(singleTimingGraphPanel); } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java index c657bba12f..cce9ada1cd 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java @@ -393,7 +393,7 @@ public class TestPanel extends javax.swing.JDialog { System.out.println(" Making panel for " + name); JLabel label = new JLabel(name); graphPanel.add(label); - TimingMetricGraphPanel timingGraphPanel = new TimingMetricGraphPanel(timingData.get(name), TimingMetricGraphPanel.TimingMetricType.AVERAGE, true); + TimingMetricGraphPanel timingGraphPanel = new TimingMetricGraphPanel(timingData.get(name), TimingMetricGraphPanel.TimingMetricType.AVERAGE, null, true); timingGraphPanel.setPreferredSize(new Dimension(800,200)); graphPanel.add(timingGraphPanel); } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index e7e0e96379..bef761667a 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -27,6 +27,7 @@ import java.awt.Point; import java.awt.RenderingHints; import java.awt.Stroke; import java.util.Collections; +import java.util.stream.Collectors; import java.util.Comparator; import java.util.ArrayList; import java.util.List; @@ -36,6 +37,7 @@ import javax.swing.JPanel; import org.sleuthkit.autopsy.coreutils.Logger; import java.util.logging.Level; import java.util.TimeZone; +import java.util.concurrent.TimeUnit; import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor.DatabaseTimingResult; /** @@ -57,13 +59,26 @@ class TimingMetricGraphPanel extends JPanel { private List timingResults; private TimingMetricType timingMetricType; private boolean doLineGraph; + private String yUnitString; private TrendLine trendLine; private final long MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24; + long maxTimestamp; + long minTimestamp; + double maxMetricTime; + double minMetricTime; + + TimingMetricGraphPanel(List timingResultsFull, TimingMetricType timingMetricType, String hostName, boolean doLineGraph) { - TimingMetricGraphPanel(List timingResults, TimingMetricType timingMetricType, boolean doLineGraph) { - this.timingResults = timingResults; this.timingMetricType = timingMetricType; this.doLineGraph = doLineGraph; + if(hostName == null || hostName.isEmpty()) { + timingResults = timingResultsFull; + } else { + timingResults = timingResultsFull.stream() + .filter(t -> t.getHostName().equals(hostName)) + .collect(Collectors.toList()); + } + try { trendLine = new TrendLine(timingResults, timingMetricType); } catch (HealthMonitorException ex) { @@ -71,80 +86,81 @@ class TimingMetricGraphPanel extends JPanel { logger.log(Level.WARNING, "Can not generate a trend line on empty data set"); trendLine = null; } + + // Calculate these using the full data set, to make it easier to compare the results for + // individual hosts + calcMaxTimestamp(timingResultsFull); + calcMinTimestamp(timingResultsFull); + calcMaxMetricTime(timingResultsFull); + calcMinMetricTime(timingResultsFull); + + } /** - * Get the highest metric time for the given type - * @return the highest metric time + * Set the highest metric time for the given type */ - private double getMaxMetricTime() { + private void calcMaxMetricTime(List timingResultsFull) { // Find the highest of the values being graphed - double maxScore = Double.MIN_VALUE; - for (DatabaseTimingResult score : timingResults) { + maxMetricTime = Double.MIN_VALUE; + for (DatabaseTimingResult score : timingResultsFull) { // Use only the data we're graphing to determing the max switch (timingMetricType) { case MAX: - maxScore = Math.max(maxScore, score.getMax()); + maxMetricTime = Math.max(maxMetricTime, score.getMax()); break; case MIN: - maxScore = Math.max(maxScore, score.getMin()); + maxMetricTime = Math.max(maxMetricTime, score.getMin()); break; case AVERAGE: default: - maxScore = Math.max(maxScore, score.getAverage()); + maxMetricTime = Math.max(maxMetricTime, score.getAverage()); break; } } - return maxScore; } /** - * Get the lowest metric time for the given type - * @return the lowest metric time + * Set the lowest metric time for the given type */ - private double getMinMetricTime() { + private void calcMinMetricTime(List timingResultsFull) { // Find the lowest of the values being graphed - double minScore = Double.MAX_VALUE; - for (DatabaseTimingResult score : timingResults) { + minMetricTime = Double.MAX_VALUE; + for (DatabaseTimingResult result : timingResultsFull) { // Use only the data we're graphing to determing the min switch (timingMetricType) { case MAX: - minScore = Math.min(minScore, score.getMax()); + minMetricTime = Math.min(minMetricTime, result.getMax()); break; case MIN: - minScore = Math.min(minScore, score.getMin()); + minMetricTime = Math.min(minMetricTime, result.getMin()); break; case AVERAGE: default: - minScore = Math.min(minScore, score.getAverage()); + minMetricTime = Math.min(minMetricTime, result.getAverage()); break; } } - return minScore; } /** - * Get the largest timestamp in the data collection - * @return the largest timestamp + * Set the largest timestamp in the data collection */ - private long getMaxTimestamp() { - long maxTimestamp = Long.MIN_VALUE; - for (DatabaseTimingResult score : timingResults) { + private void calcMaxTimestamp(List timingResultsFull) { + maxTimestamp = Long.MIN_VALUE; + for (DatabaseTimingResult score : timingResultsFull) { maxTimestamp = Math.max(maxTimestamp, score.getTimestamp()); } - return maxTimestamp; } /** - * Get the smallest timestamp in the data collection - * @return the minimum timestamp + * Set the smallest timestamp in the data collection */ - private long getMinTimestamp() { - long minTimestamp = Long.MAX_VALUE; - for (DatabaseTimingResult score : timingResults) { + private void calcMinTimestamp(List timingResultsFull) { + minTimestamp = Long.MAX_VALUE; + for (DatabaseTimingResult score : timingResultsFull) { minTimestamp = Math.min(minTimestamp, score.getTimestamp()); } - return minTimestamp; } /** @@ -166,25 +182,24 @@ class TimingMetricGraphPanel extends JPanel { // Get the max and min timestamps to create the x-axis. // We add a small buffer to each side so the data won't overwrite the axes. - long originalMaxTimestamp = getMaxTimestamp(); - double maxValueOnXAxis = (double) originalMaxTimestamp + 1000 * 60 *60 * 2; // Two hour buffer - double minValueOnXAxis = getMinTimestamp() - 1000 * 60 * 60 * 2; // Two hour buffer + double maxValueOnXAxis = maxTimestamp + 1000 * 60 *60 * 2; // Two hour buffer + double minValueOnXAxis = minTimestamp - 1000 * 60 * 60 * 2; // Two hour buffer // Get the max and min times to create the y-axis // We add a small buffer to each side so the data won't overwrite the axes. - double maxValueOnYAxis = getMaxMetricTime(); - double minValueOnYAxis = getMinMetricTime(); + double maxValueOnYAxis = maxMetricTime; + double minValueOnYAxis = minMetricTime; minValueOnYAxis = Math.max(0, minValueOnYAxis - (maxValueOnYAxis * 0.1)); maxValueOnYAxis = maxValueOnYAxis * 1.1; // The graph itself has the following corners: - // (padding + label padding, padding) - top left + // (padding + label padding, padding + font height) - top left // (padding + label padding, getHeight() - label padding - padding x 2) - bottom left - // (getWidth() - padding, getHeight() - label padding - padding x 2) - top right ?? + // (getWidth() - padding, padding + font height) - top right // (padding + label padding, getHeight() - label padding - padding x 2) - bottom right int leftGraphPadding = padding + labelPadding; int rightGraphPadding = padding; - int topGraphPadding = padding; + int topGraphPadding = padding + g2.getFontMetrics().getHeight(); int bottomGraphPadding = padding + labelPadding; // Calculate the scale for each axis. @@ -198,13 +213,42 @@ class TimingMetricGraphPanel extends JPanel { int graphWidth = getWidth() - leftGraphPadding - rightGraphPadding; int graphHeight = getHeight() - topGraphPadding - bottomGraphPadding; double xScale = ((double) graphWidth) / (maxValueOnXAxis - minValueOnXAxis); - double yScale = ((double) graphHeight) / (maxValueOnYAxis - minValueOnYAxis); + double yScale = ((double) graphHeight) / (maxValueOnYAxis - minValueOnYAxis); - // draw white background - g2.setColor(Color.WHITE); - g2.fillRect(leftGraphPadding, topGraphPadding, graphWidth, graphHeight); + // Check if we should use a scale other than milliseconds + long middleOfGraphNano = (long)(minValueOnYAxis + (maxValueOnYAxis - minValueOnYAxis) / 2) * 1000000; + double yLabelScale; + + // The idea here is to pick the scale that would most commonly be used to + // represent the middle of our data. For example, if the middle of the graph + // would be 45,000,000 nanoseconds, then we would use milliseconds for the + // y-axis. + if(middleOfGraphNano < 1000) { + yUnitString = "nanoseconds"; + yLabelScale = 1000000; + } else if (TimeUnit.NANOSECONDS.toMicros(middleOfGraphNano) < 1000) { + yUnitString = "microseconds"; + yLabelScale = 1000; + } else if (TimeUnit.NANOSECONDS.toMillis(middleOfGraphNano) < 1000) { + yUnitString = "milliseconds"; + yLabelScale = 1; + } else if (TimeUnit.NANOSECONDS.toSeconds(middleOfGraphNano) < 60) { + yUnitString = "seconds"; + yLabelScale = 1.0 / 1000; + } else if (TimeUnit.NANOSECONDS.toMinutes(middleOfGraphNano) < 60) { + yUnitString = "minutes"; + yLabelScale = 1.0 / (1000 * 60); + } else { + yUnitString = "hours"; + yLabelScale = 1.0 / (1000 * 60 * 60); + } - // create hatch marks and grid lines for y axis. + // Draw white background + g2.setColor(Color.WHITE); + g2.fillRect(leftGraphPadding, topGraphPadding, graphWidth, graphHeight); + + // Create hatch marks and grid lines for y axis. + int labelWidth; for (int i = 0; i < numberYDivisions + 1; i++) { int x0 = leftGraphPadding; int x1 = pointWidth + leftGraphPadding; @@ -219,10 +263,19 @@ class TimingMetricGraphPanel extends JPanel { // Create the label g2.setColor(Color.BLACK); double yValue = minValueOnYAxis + ((maxValueOnYAxis - minValueOnYAxis) * ((i * 1.0) / numberYDivisions)); - String yLabel = ((int) (yValue * 100)) / 100.0 + ""; + String yLabel = ((int) (yValue * 100 * yLabelScale)) / 100.0 + ""; FontMetrics fontMetrics = g2.getFontMetrics(); - int labelWidth = fontMetrics.stringWidth(yLabel); + labelWidth = fontMetrics.stringWidth(yLabel); g2.drawString(yLabel, x0 - labelWidth - 5, y0 + (fontMetrics.getHeight() / 2) - 3); + + // The nicest looking alignment for this label seems to be left-aligned with the top + // y-axis label + if (i == numberYDivisions) { + // Write the scale + g2.setColor(Color.BLACK); + String scaleStr = "Displaying time in " + yUnitString; + g2.drawString(scaleStr, x0 - labelWidth - 5, padding); + } } // Draw the small hatch mark @@ -232,7 +285,7 @@ class TimingMetricGraphPanel extends JPanel { // On the x-axis, the farthest right grid line should represent midnight preceding the last recorded value Calendar maxDate = new GregorianCalendar(); - maxDate.setTimeInMillis(originalMaxTimestamp); + maxDate.setTimeInMillis(maxTimestamp); maxDate.set(Calendar.HOUR_OF_DAY, 0); maxDate.set(Calendar.MINUTE, 0); maxDate.set(Calendar.SECOND, 0); @@ -279,7 +332,7 @@ class TimingMetricGraphPanel extends JPanel { String xLabel = month + "/" + day; FontMetrics metrics = g2.getFontMetrics(); - int labelWidth = metrics.stringWidth(xLabel); + labelWidth = metrics.stringWidth(xLabel); g2.drawString(xLabel, x0 - labelWidth / 2, y0 + metrics.getHeight() + 3); } @@ -325,7 +378,7 @@ class TimingMetricGraphPanel extends JPanel { }); // Draw the selected type of graph. If there's only one data point, - // draw it. + // draw that single point. g2.setStroke(NARROW_STROKE); g2.setColor(lineColor); if(doLineGraph && graphPoints.size() > 1) { @@ -346,13 +399,13 @@ class TimingMetricGraphPanel extends JPanel { } } - // Draw the trend line - // Don't draw anything if there's only one data point + // Draw the trend line. + // Don't draw anything if we don't have at least two data points. if(trendLine != null && (timingResults.size() > 1)) { - int x0 = (int) (padding + labelPadding); - int y0 = (int) ((maxValueOnYAxis - trendLine.getExpectedValueAt(minValueOnXAxis)) * yScale + padding); - int x1 = (int) ((maxValueOnXAxis - minValueOnXAxis) * xScale + padding + labelPadding); - int y1 = (int) ((maxValueOnYAxis - trendLine.getExpectedValueAt(maxValueOnXAxis)) * yScale + padding); + int x0 = (int) (leftGraphPadding); + int y0 = (int) ((maxValueOnYAxis - trendLine.getExpectedValueAt(minValueOnXAxis)) * yScale + topGraphPadding); + int x1 = (int) ((maxValueOnXAxis - minValueOnXAxis) * xScale + leftGraphPadding); + int y1 = (int) ((maxValueOnYAxis - trendLine.getExpectedValueAt(maxValueOnXAxis)) * yScale + topGraphPadding); g2.setStroke(GRAPH_STROKE); g2.setColor(trendLineColor); g2.drawLine(x0, y0, x1, y1); From 2200a41d74b289c536bd4a8a34058421acd08934 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 26 Apr 2018 07:54:15 -0400 Subject: [PATCH 08/63] Added code to enable and disable monitoring. Added button to auto ingest dashboard. Improved trend line. --- .../EnterpriseHealthMonitor.java | 174 ++++++++++----- .../healthmonitor/HealthMonitorDashboard.java | 198 +++++++++++++++--- .../autopsy/healthmonitor/TestPanel.java | 2 +- .../healthmonitor/TimingMetricGraphPanel.java | 91 ++++++-- .../autoingest/AutoIngestDashboard.form | 22 ++ .../autoingest/AutoIngestDashboard.java | 22 +- .../experimental/autoingest/Bundle.properties | 1 + 7 files changed, 419 insertions(+), 91 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index d2cf27162a..221792cef8 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -63,8 +63,6 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { 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); @@ -100,21 +98,12 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } // 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")){ - isEnabled.set(true); - try { - activateMonitor(); - } catch (HealthMonitorException ex) { - // If we failed to activate it, then disable the monitor - logger.log(Level.SEVERE, "Health monitor activation failed - disabling health monitor"); - setEnabled(false); - throw ex; - } - return; - } - } - isEnabled.set(false); + System.out.println("\n### Checking if monitor is enabled..."); + + updateFromGlobalEnabledStatus(); + + // Start the timer for database checks and writes + startTimer(); } /** @@ -136,7 +125,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { * out of the maps, and sets up the timer for writing to the database. * @throws HealthMonitorException */ - private synchronized void activateMonitor() throws HealthMonitorException { + private synchronized void activateMonitorLocally() throws HealthMonitorException { logger.log(Level.INFO, "Activating Servies Health Monitor"); @@ -164,15 +153,15 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { initializeDatabaseSchema(); } + // Set the enabled status in the databse to true + setGlobalEnabledStatusInDB(true); + } catch (CoordinationService.CoordinationServiceException ex) { throw new HealthMonitorException("Error releasing database lock", ex); } // Clear out any old data timingInfoMap.clear(); - - // Start the timer for database writes - startTimer(); } /** @@ -182,16 +171,13 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { * and shuts down the connection pool. * @throws HealthMonitorException */ - private synchronized void deactivateMonitor() throws HealthMonitorException { + private synchronized void deactivateMonitorLocally() throws HealthMonitorException { logger.log(Level.INFO, "Deactivating Servies Health Monitor"); - + // Clear out the collected data timingInfoMap.clear(); - // Stop the timer - stopTimer(); - // Shut down the connection pool shutdownConnections(); } @@ -204,7 +190,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { stopTimer(); healthMonitorOutputTimer = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("health_monitor_timer").build()); - healthMonitorOutputTimer.scheduleWithFixedDelay(new DatabaseWriteTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES); + healthMonitorOutputTimer.scheduleWithFixedDelay(new PeriodicHealthMonitorTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES); } /** @@ -236,15 +222,18 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } if(enabled) { - getInstance().activateMonitor(); + getInstance().activateMonitorLocally(); - // If activateMonitor fails, we won't update either of these - ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY, "true"); + // If activateMonitor fails, we won't update this + getInstance().setGlobalEnabledStatusInDB(true); isEnabled.set(true); } else { - ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY, "false"); + if(isEnabled.get()) { + // If we were enabled before, set the global state to disabled + getInstance().setGlobalEnabledStatusInDB(false); + } isEnabled.set(false); - getInstance().deactivateMonitor(); + getInstance().deactivateMonitorLocally(); } } @@ -395,7 +384,6 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { String createCommand = "SELECT 1 AS result FROM pg_database WHERE datname='" + DATABASE_NAME + "'"; rs = statement.executeQuery(createCommand); if(rs.next()) { - logger.log(Level.INFO, "Existing Enterprise Health Monitor database found"); return true; } } finally { @@ -533,6 +521,95 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } + /** + * Return whether the health monitor is locally enabled. + * This does not query the database. + * @return true if it is enabled, false otherwise + */ + static boolean monitorIsEnabled() { + return isEnabled.get(); + } + + /** + * Check whether monitoring should be enabled from the monitor database + * and enable/disable as needed. + * @throws HealthMonitorException + */ + final synchronized void updateFromGlobalEnabledStatus() throws HealthMonitorException { + + boolean previouslyEnabled = monitorIsEnabled(); + + // We can't even check the database if multi user settings aren't enabled. + if (!UserPreferences.getIsMultiUserModeEnabled()) { + isEnabled.set(false); + + if(previouslyEnabled) { + deactivateMonitorLocally(); + } + return; + } + + // If the health monitor database doesn't exist or if it is not initialized, + // then monitoring isn't enabled + if ((! databaseExists()) || (! databaseIsInitialized())) { + isEnabled.set(false); + + if(previouslyEnabled) { + deactivateMonitorLocally(); + } + return; + } + + boolean currentlyEnabled = getGlobalEnabledStatusFromDB(); + if( currentlyEnabled == previouslyEnabled) { + // Nothing needs to be done + } else { + if(currentlyEnabled == false) { + isEnabled.set(false); + deactivateMonitorLocally(); + } else { + isEnabled.set(true); + activateMonitorLocally(); + } + } + } + + /** + * Read the enabled status from the database. + * Check that the health monitor database exists before calling this. + * @return true if the database is enabled, false otherwise + * @throws HealthMonitorException + */ + private boolean getGlobalEnabledStatusFromDB() throws HealthMonitorException { + + try (Connection conn = connect(); + Statement statement = conn.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='MONITOR_ENABLED'")) { + + if (resultSet.next()) { + return(resultSet.getBoolean("value")); + } + throw new HealthMonitorException("No enabled status found in database"); + } catch (SQLException ex) { + throw new HealthMonitorException("Error initializing database", ex); + } + } + + /** + * Set the global enabled status in the database. + * @throws HealthMonitorException + */ + private void setGlobalEnabledStatusInDB(boolean status) throws HealthMonitorException { + + try (Connection conn = connect(); + Statement statement = conn.createStatement();) { + //statement.execute("INSERT INTO db_info (name, value) VALUES ('MONITOR_ENABLED', '" + status + "')"); + statement.execute("UPDATE db_info SET value='" + status + "' WHERE name='MONITOR_ENABLED'"); + } catch (SQLException ex) { + throw new HealthMonitorException("Error setting enabled status", ex); + } + } + /** * Get the current schema version * @return the current schema version @@ -643,19 +720,22 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { /** * The task called by the ScheduledThreadPoolExecutor to handle - * the database writes. + * the database checks/writes. */ - static final class DatabaseWriteTask implements Runnable { + static final class PeriodicHealthMonitorTask implements Runnable { /** - * Write current metric data to the database + * Perform all periodic tasks: + * - Check if monitoring has been enabled / disabled in the database + * - Write current metric data to the database */ @Override public void run() { try { + getInstance().updateFromGlobalEnabledStatus(); getInstance().writeCurrentStateToDatabase(); } catch (HealthMonitorException ex) { - logger.log(Level.SEVERE, "Error writing current metrics to database", ex); //NON-NLS + logger.log(Level.SEVERE, "Error performing periodic task", ex); //NON-NLS } } } @@ -668,7 +748,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { 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()); + healthMonitorExecutor.submit(new EnterpriseHealthMonitor.PeriodicHealthMonitorTask()); } break; } @@ -776,9 +856,9 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { statement.setString(2, host); statement.setLong(3, timestamp); statement.setLong(4, 0); - statement.setLong(5, aveTime); - statement.setLong(6, 0); - statement.setLong(7, 0); + statement.setDouble(5, aveTime); + statement.setDouble(6, 0); + statement.setDouble(7, 0); statement.execute(); } @@ -815,9 +895,9 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { statement.setString(2, host); statement.setLong(3, timestamp); statement.setLong(4, 0); - statement.setLong(5, aveTime); - statement.setLong(6, 0); - statement.setLong(7, 0); + statement.setDouble(5, aveTime); + statement.setDouble(6, 0); + statement.setDouble(7, 0); statement.execute(); } @@ -1057,9 +1137,9 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { 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; - this.min = resultSet.getLong("min") / 1000000; + this.average = resultSet.getDouble("average"); + this.max = resultSet.getDouble("max"); + this.min = resultSet.getDouble("min"); } /** diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java index 30c3c39a6a..1ff64c3a7e 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java @@ -19,6 +19,8 @@ package org.sleuthkit.autopsy.healthmonitor; import java.awt.Component; +import java.awt.Container; +import java.awt.Cursor; import java.awt.Dimension; import java.util.Set; import java.util.HashSet; @@ -28,7 +30,9 @@ import java.util.List; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.Font; +import java.io.File; import javax.swing.Box; +import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JComboBox; import javax.swing.JSeparator; @@ -41,49 +45,131 @@ import java.util.Map; import javax.swing.BoxLayout; import java.util.logging.Level; import java.util.stream.Collectors; +import org.openide.modules.Places; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; /** * */ -// TODO: keep public? public class HealthMonitorDashboard { private final static Logger logger = Logger.getLogger(HealthMonitorDashboard.class.getName()); + private final static String ADMIN_ACCESS_FILE_NAME = "adminAccess"; + private final static String ADMIN_ACCESS_FILE_PATH = Places.getUserDirectory().getAbsolutePath() + File.separator + ADMIN_ACCESS_FILE_NAME; + Map> timingData; private JPanel timingMetricPanel = null; private JPanel timingButtonPanel = null; + private JPanel adminPanel = null; private JComboBox dateComboBox = null; private JComboBox hostComboBox = null; private JCheckBox hostCheckBox = null; + private JButton enableButton = null; + private JButton disableButton = null; + private JDialog dialog = null; + private final Container parentWindow; - HealthMonitorDashboard() { + public HealthMonitorDashboard(Container parent) { timingData = new HashMap<>(); + parentWindow = parent; } - void display() throws HealthMonitorException { + + private void refreshPanels() throws HealthMonitorException { + // Update the monitor status + EnterpriseHealthMonitor.getInstance().updateFromGlobalEnabledStatus(); - // Get a copy of the timing data from the database - timingData = EnterpriseHealthMonitor.getInstance().getTimingMetricsFromDatabase(); + if(EnterpriseHealthMonitor.monitorIsEnabled()) { + // Get a copy of the timing data from the database + timingData = EnterpriseHealthMonitor.getInstance().getTimingMetricsFromDatabase(); + } - // Set up the buttons + // Set up the UI + setupAdminPanel(); setupTimingButtonPanel(); addActionListeners(); // Initialize and populate the timing metric panel - populateTimingMetricPanel(); + initializeTimingMetricPanel(); + } + + /** + * Display the Health Monitor dashboard. + */ + public void display() { + if(dialog != null) { + dialog.setVisible(false); + dialog.dispose(); + } + + try { + refreshPanels(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error creating panels for health monitor dashboard", ex); + MessageNotifyUtil.Message.error("TEMP"); + } JScrollPane scrollPane = new JScrollPane(timingMetricPanel); - JDialog dialog = new JDialog(); + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS)); + mainPanel.add(scrollPane); + File adminFile = new File(ADMIN_ACCESS_FILE_PATH); + System.out.println("admin file: " + adminFile.getAbsolutePath()); + if(adminFile.exists()) { + mainPanel.add(adminPanel); + } + dialog = new JDialog(); //dialog.setPreferredSize(new Dimension(1500, 800)); dialog.setTitle("Enterprise Health Monitor"); - dialog.add(scrollPane); + + dialog.add(mainPanel); + dialog.pack(); + dialog.setLocationRelativeTo(parentWindow); dialog.setVisible(true); } + /** + * Initialize the admin panel, which allows the monitor to be enabled + * or diabled + * @throws HealthMonitorException + */ + private void setupAdminPanel() throws HealthMonitorException { + if (adminPanel == null) { + adminPanel = new JPanel(); + adminPanel.setBorder(BorderFactory.createEtchedBorder()); + } else { + adminPanel.removeAll(); + } + + if (enableButton == null) { + enableButton = new JButton("Enable monitor"); + } + + if(disableButton == null) { + disableButton = new JButton("Disable monitor"); + } + + updateEnableButtons(); + + adminPanel.add(enableButton); + adminPanel.add(Box.createHorizontalStrut(25)); + adminPanel.add(disableButton); + + } + + /** + * Update the enable and disable buttons + */ + private void updateEnableButtons() { + boolean isEnabled = EnterpriseHealthMonitor.monitorIsEnabled(); + enableButton.setEnabled(! isEnabled); + disableButton.setEnabled(isEnabled); + } + /** * Initialize the panel holding the timing metric controls and * update components @@ -97,6 +183,11 @@ public class HealthMonitorDashboard { timingButtonPanel.removeAll(); } + // If the monitor is not enabled, don't add any components + if(! EnterpriseHealthMonitor.monitorIsEnabled()) { + return; + } + // The contents of the date combo box will never change, so we only need to // do this once if(dateComboBox == null) { @@ -125,8 +216,8 @@ public class HealthMonitorDashboard { } // Create the panel - timingButtonPanel = new JPanel(); - timingButtonPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + //timingButtonPanel = new JPanel(); + //timingButtonPanel.setAlignmentX(Component.LEFT_ALIGNMENT); //timingButtonPanel.setBorder(BorderFactory.createEtchedBorder()); // Add the date range combo box and label to the panel @@ -155,7 +246,7 @@ public class HealthMonitorDashboard { @Override public void actionPerformed(ActionEvent arg0) { try { - populateTimingMetricPanel(); + initializeTimingMetricPanel(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error populating timing metric panel", ex); } @@ -170,7 +261,7 @@ public class HealthMonitorDashboard { public void actionPerformed(ActionEvent arg0) { try { if((hostCheckBox != null) && hostCheckBox.isSelected()) { - populateTimingMetricPanel(); + initializeTimingMetricPanel(); } } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error populating timing metric panel", ex); @@ -186,40 +277,85 @@ public class HealthMonitorDashboard { public void actionPerformed(ActionEvent arg0) { try { hostComboBox.setEnabled(hostCheckBox.isSelected()); // Why isn't this working? - populateTimingMetricPanel(); + initializeTimingMetricPanel(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error populating timing metric panel", ex); } } }); } + + // Set up a listener on the enable button + if((enableButton != null) && (enableButton.getActionListeners().length == 0)) { + enableButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + System.out.println("\n### In action listener for enable button"); + try { + dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + //WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + EnterpriseHealthMonitor.setEnabled(true); + HealthMonitorDashboard.this.updateEnableButtons(); + display(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error enabling monitoring", ex); + } finally { + //WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + dialog.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + } + }); + } + + // Set up a listener on the disable button + if((disableButton != null) && (disableButton.getActionListeners().length == 0)) { + disableButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + System.out.println("\n### In action listener for disable button"); + try { + dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + //WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + EnterpriseHealthMonitor.setEnabled(false); + HealthMonitorDashboard.this.updateEnableButtons(); + display(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error disabling monitoring", ex); + } finally { + //WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + dialog.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + } + }); + } } /** * Initialize the panel holding the timing metrics. - * If it has not been initialized, create and set up the panel. - * Otherwise, clear any existing components out of the panel. */ - private void initializeTimingMetricPanel() { - if(timingMetricPanel == null) { - timingMetricPanel = new JPanel(); - timingMetricPanel.setLayout(new BoxLayout(timingMetricPanel, BoxLayout.PAGE_AXIS)); - timingMetricPanel.setBorder(BorderFactory.createEtchedBorder()); - } else { - // Clear out any existing components - timingMetricPanel.removeAll(); - } - } + private void initializeTimingMetricPanel() throws HealthMonitorException { - private void populateTimingMetricPanel() throws HealthMonitorException { - - initializeTimingMetricPanel(); + timingMetricPanel = new JPanel(); + timingMetricPanel.setLayout(new BoxLayout(timingMetricPanel, BoxLayout.PAGE_AXIS)); + timingMetricPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + timingMetricPanel.setBorder(BorderFactory.createEtchedBorder()); // Add title JLabel timingMetricTitle = new JLabel("Timing Metrics"); timingMetricTitle.setFont(new Font("Serif", Font.BOLD, 20)); timingMetricPanel.add(timingMetricTitle); + // If the monitor isn't enabled, just add a message + if(! EnterpriseHealthMonitor.monitorIsEnabled()) { + timingMetricPanel.setPreferredSize(new Dimension(400,100)); + timingMetricPanel.add(new JLabel(" ")); + timingMetricPanel.add(new JLabel("No data to display - monitor is not enabled")); + + timingMetricPanel.revalidate(); + timingMetricPanel.repaint(); + return; + } + // Add the button controls timingMetricPanel.add(timingButtonPanel); timingMetricPanel.add(new JSeparator()); @@ -260,6 +396,10 @@ public class HealthMonitorDashboard { timingMetricPanel.revalidate(); timingMetricPanel.repaint(); + + if(dialog != null) { + dialog.pack(); + } } enum DateRange { diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java index cce9ada1cd..63ebf575c6 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java @@ -442,7 +442,7 @@ public class TestPanel extends javax.swing.JDialog { }//GEN-LAST:event_jButton2ActionPerformed private void newGraphButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newGraphButtonActionPerformed - HealthMonitorDashboard dashboard = new HealthMonitorDashboard(); + HealthMonitorDashboard dashboard = new HealthMonitorDashboard(null); try { dashboard.display(); } catch (Exception ex) { diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index bef761667a..29a4fc4d44 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -402,10 +402,60 @@ class TimingMetricGraphPanel extends JPanel { // Draw the trend line. // Don't draw anything if we don't have at least two data points. if(trendLine != null && (timingResults.size() > 1)) { - int x0 = (int) (leftGraphPadding); - int y0 = (int) ((maxValueOnYAxis - trendLine.getExpectedValueAt(minValueOnXAxis)) * yScale + topGraphPadding); - int x1 = (int) ((maxValueOnXAxis - minValueOnXAxis) * xScale + leftGraphPadding); - int y1 = (int) ((maxValueOnYAxis - trendLine.getExpectedValueAt(maxValueOnXAxis)) * yScale + topGraphPadding); + double x0value = minValueOnXAxis; + double y0value = trendLine.getExpectedValueAt(x0value); + if (y0value < minValueOnYAxis) { + try { + y0value = minValueOnYAxis; + x0value = trendLine.getXGivenY(y0value); + } catch (HealthMonitorException ex) { + // The exception is caused by a slope of zero on the trend line, which + // shouldn't be able to happen at the same time as having a trend line that dips below the y-axis. + // If it does, log a warning but continue on with the original values. + logger.log(Level.WARNING, "Error plotting trend line", ex); + } + } else if (y0value > maxValueOnYAxis) { + try { + y0value = minValueOnYAxis; + x0value = trendLine.getXGivenY(y0value); + } catch (HealthMonitorException ex) { + // The exception is caused by a slope of zero on the trend line, which + // shouldn't be able to happen at the same time as having a trend line that dips below the y-axis. + // If it does, log a warning but continue on with the original values. + logger.log(Level.WARNING, "Error plotting trend line", ex); + } + } + + int x0 = (int) ((x0value - minValueOnXAxis) * xScale) + leftGraphPadding; + int y0 = (int) ((maxValueOnYAxis - y0value) * yScale + topGraphPadding); + + double x1value = maxValueOnXAxis; + double y1value = trendLine.getExpectedValueAt(maxValueOnXAxis); + if (y1value < minValueOnYAxis) { + try { + y1value = minValueOnYAxis; + x1value = trendLine.getXGivenY(y1value); + } catch (HealthMonitorException ex) { + // The exception is caused by a slope of zero on the trend line, which + // shouldn't be able to happen at the same time as having a trend line that dips below the y-axis. + // If it does, log a warning but continue on with the original values. + logger.log(Level.WARNING, "Error plotting trend line", ex); + } + } else if (y1value > maxValueOnYAxis) { + try { + y1value = maxValueOnYAxis; + x1value = trendLine.getXGivenY(y1value); + } catch (HealthMonitorException ex) { + // The exception is caused by a slope of zero on the trend line, which + // shouldn't be able to happen at the same time as having a trend line that dips below the y-axis. + // If it does, log a warning but continue on with the original values. + logger.log(Level.WARNING, "Error plotting trend line", ex); + } + } + + int x1 = (int) ((x1value - minValueOnXAxis) * xScale) + leftGraphPadding; + int y1 = (int) ((maxValueOnYAxis - y1value) * yScale + topGraphPadding); + g2.setStroke(GRAPH_STROKE); g2.setColor(trendLineColor); g2.drawLine(x0, y0, x1, y1); @@ -483,16 +533,31 @@ class TimingMetricGraphPanel extends JPanel { // Calculate y intercept yInt = (sumY - slope * sumX) / n; - } + } - /** - * Get the expected y value for a given x - * @param x x coordinate of the point on the trend line - * @return expected y coordinate of this point on the trend line - */ - double getExpectedValueAt(double x) { - return (slope * x + yInt); - } + /** + * Get the expected y value for a given x + * @param x x coordinate of the point on the trend line + * @return expected y coordinate of this point on the trend line + */ + double getExpectedValueAt(double x) { + return (slope * x + yInt); + } + + /** + * Get the x-coordinate for a given Y-coordinate. + * Should only be necessary when the trend line does not fit on the graph + * @param y the y coordinate + * @return expected x coordinate for the given y + * @throws HealthMonitorException + */ + double getXGivenY(double y) throws HealthMonitorException { + if (slope != 0.0) { + return (y - yInt / slope); + } else { + throw new HealthMonitorException("Attempted division by zero in trend line calculation"); + } + } } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form index 214254e032..5e4da63ab8 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form @@ -47,6 +47,8 @@ + + @@ -78,6 +80,7 @@ + @@ -191,5 +194,24 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 482742d7de..1fae8760d2 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -36,6 +36,7 @@ import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.core.ServicesMonitor; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitorDashboard; /** * A dashboard for monitoring an automated ingest cluster. @@ -302,6 +303,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { lbServicesStatus = new javax.swing.JLabel(); tbServicesStatusMessage = new javax.swing.JTextField(); clusterMetricsButton = new javax.swing.JButton(); + healthMonitorButton = new javax.swing.JButton(); org.openide.awt.Mnemonics.setLocalizedText(jButton1, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.jButton1.text")); // NOI18N @@ -342,6 +344,16 @@ final class AutoIngestDashboard extends JPanel implements Observer { } }); + org.openide.awt.Mnemonics.setLocalizedText(healthMonitorButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.healthMonitorButton.text")); // NOI18N + healthMonitorButton.setMaximumSize(new java.awt.Dimension(133, 23)); + healthMonitorButton.setMinimumSize(new java.awt.Dimension(133, 23)); + healthMonitorButton.setPreferredSize(new java.awt.Dimension(133, 23)); + healthMonitorButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + healthMonitorButtonActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -365,6 +377,8 @@ final class AutoIngestDashboard extends JPanel implements Observer { .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() .addComponent(refreshButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(healthMonitorButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(clusterMetricsButton))) .addContainerGap()) ); @@ -393,7 +407,8 @@ final class AutoIngestDashboard extends JPanel implements Observer { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(refreshButton) - .addComponent(clusterMetricsButton)) + .addComponent(clusterMetricsButton) + .addComponent(healthMonitorButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addContainerGap()) ); }// //GEN-END:initComponents @@ -415,9 +430,14 @@ final class AutoIngestDashboard extends JPanel implements Observer { new AutoIngestMetricsDialog(this.getTopLevelAncestor()); }//GEN-LAST:event_clusterMetricsButtonActionPerformed + private void healthMonitorButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_healthMonitorButtonActionPerformed + new HealthMonitorDashboard(this.getTopLevelAncestor()).display(); + }//GEN-LAST:event_healthMonitorButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton clusterMetricsButton; private javax.swing.JScrollPane completedScrollPane; + private javax.swing.JButton healthMonitorButton; private javax.swing.JButton jButton1; private javax.swing.JLabel lbCompleted; private javax.swing.JLabel lbPending; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties index 6194815f69..f53fccc8eb 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties @@ -258,3 +258,4 @@ AutoIngestControlPanel.bnPrioritizeJob.toolTipText=Move this folder to the top o AutoIngestControlPanel.bnPrioritizeCase.toolTipText=Move all images associated with a case to top of Pending queue. AutoIngestControlPanel.bnPrioritizeJob.actionCommand=Prioritize Job AutoIngestControlPanel.bnDeprioritizeJob.actionCommand=Deprioritize Job +AutoIngestDashboard.healthMonitorButton.text=Health Monitor From e8d5de97cc9ef520d6ae272ef6fbc520b896701b Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 26 Apr 2018 10:06:08 -0400 Subject: [PATCH 09/63] Refactored dashboard code --- .../healthmonitor/HealthMonitorDashboard.java | 341 +++++++++++++++++- 1 file changed, 321 insertions(+), 20 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java index 1ff64c3a7e..1c94a16f38 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java @@ -60,25 +60,326 @@ public class HealthMonitorDashboard { private final static String ADMIN_ACCESS_FILE_PATH = Places.getUserDirectory().getAbsolutePath() + File.separator + ADMIN_ACCESS_FILE_NAME; Map> timingData; - - private JPanel timingMetricPanel = null; - private JPanel timingButtonPanel = null; - private JPanel adminPanel = null; + private JComboBox dateComboBox = null; private JComboBox hostComboBox = null; private JCheckBox hostCheckBox = null; - private JButton enableButton = null; - private JButton disableButton = null; + private JPanel graphPanel = null; private JDialog dialog = null; private final Container parentWindow; + /** + * Create an instance of the dashboard. + * Call display() after creation to show the dashboard. + * @param parent The parent container (for centering the UI) + */ public HealthMonitorDashboard(Container parent) { timingData = new HashMap<>(); parentWindow = parent; } + /** + * Display the dashboard. + */ + public void display() { + + // Update the enabled status and get the timing data, then create all + // the sub panels. + JPanel timingPanel; + JPanel adminPanel; + try { + updateData(); + timingPanel = createTimingPanel(); + adminPanel = createAdminPanel(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error creating panels for health monitor dashboard", ex); + MessageNotifyUtil.Message.error("TEMP"); + return; + } + + // Create the main panel for the dialog + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS)); + + // Put the timing data in a scroll pane and then add to the main panel + JScrollPane scrollPane = new JScrollPane(timingPanel); + mainPanel.add(scrollPane); + + // Add the admin panel if the admin file is present + File adminFile = new File(ADMIN_ACCESS_FILE_PATH); + if(adminFile.exists()) { + mainPanel.add(adminPanel); + } + + // Create and show the dialog + dialog = new JDialog(); + dialog.setTitle("Enterprise Health Monitor"); + dialog.add(mainPanel); + dialog.pack(); + dialog.setLocationRelativeTo(parentWindow); + dialog.setVisible(true); + } - private void refreshPanels() throws HealthMonitorException { + /** + * Delete the current dialog and create a new one. This should only be + * called after enabling or disabling the health monitor. + */ + private void redisplay() { + if (dialog != null) { + dialog.setVisible(false); + dialog.dispose(); + } + display(); + } + + /** + * Check the monitor enabled status and, if enabled, get the timing data. + * @throws HealthMonitorException + */ + private void updateData() throws HealthMonitorException { + + // Update the monitor status + EnterpriseHealthMonitor.getInstance().updateFromGlobalEnabledStatus(); + + if(EnterpriseHealthMonitor.monitorIsEnabled()) { + // Get a copy of the timing data from the database + timingData = EnterpriseHealthMonitor.getInstance().getTimingMetricsFromDatabase(); + } + } + + /** + * Create the panel holding the timing graphs and the controls for them. + * @return The timing panel + * @throws HealthMonitorException + */ + private JPanel createTimingPanel() throws HealthMonitorException { + JPanel timingMetricPanel = new JPanel(); + timingMetricPanel.setLayout(new BoxLayout(timingMetricPanel, BoxLayout.PAGE_AXIS)); + timingMetricPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + timingMetricPanel.setBorder(BorderFactory.createEtchedBorder()); + + // Add title + JLabel timingMetricTitle = new JLabel("Timing Metrics"); + timingMetricTitle.setFont(new Font("Serif", Font.BOLD, 20)); + timingMetricPanel.add(timingMetricTitle); + + // If the monitor isn't enabled, just add a message + if(! EnterpriseHealthMonitor.monitorIsEnabled()) { + timingMetricPanel.setPreferredSize(new Dimension(400,100)); + timingMetricPanel.add(new JLabel(" ")); + timingMetricPanel.add(new JLabel("No data to display - monitor is not enabled")); + + timingMetricPanel.revalidate(); + timingMetricPanel.repaint(); + return timingMetricPanel; + } + + // Add the controls + timingMetricPanel.add(createTimingControlPanel()); + timingMetricPanel.add(new JSeparator()); + + // Create panel to hold graphs + graphPanel = new JPanel(); + graphPanel.setLayout(new BoxLayout(graphPanel, BoxLayout.PAGE_AXIS)); + + // Update the graph panel and add to the timing metric panel + updateTimingMetricGraphs(); + timingMetricPanel.add(graphPanel); + timingMetricPanel.revalidate(); + timingMetricPanel.repaint(); + + return timingMetricPanel; + } + + private JPanel createTimingControlPanel() { + JPanel timingButtonPanel = new JPanel(); + timingButtonPanel.setBorder(BorderFactory.createEtchedBorder()); + //timingButtonPanel.setPreferredSize(new Dimension(1500, 100)); + + // If the monitor is not enabled, don't add any components + if(! EnterpriseHealthMonitor.monitorIsEnabled()) { + return timingButtonPanel; + } + + // 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()); + + // Set up the listener on the date combo box + dateComboBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + try { + updateTimingMetricGraphs(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error updating timing metric panel", ex); + } + } + }); + + // 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()])); + + // Set up the listener on the combo box + hostComboBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + try { + if((hostCheckBox != null) && hostCheckBox.isSelected()) { + updateTimingMetricGraphs(); + } + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error populating timing metric panel", ex); + } + } + }); + + // Create the checkbox + hostCheckBox = new JCheckBox("Filter by host"); + hostCheckBox.setSelected(false); + hostComboBox.setEnabled(false); + + // Set up the listener on the checkbox + hostCheckBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + try { + hostComboBox.setEnabled(hostCheckBox.isSelected()); // Why isn't this working? + updateTimingMetricGraphs(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error populating timing metric panel", ex); + } + } + }); + + // 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); + + return timingButtonPanel; + } + + private void updateTimingMetricGraphs() throws HealthMonitorException { + + // Clear out any old graphs + graphPanel.removeAll(); + + for(String name:timingData.keySet()) { + + // If necessary, trim down the list of results to fit the selected time range + List intermediateTimingDataForDisplay; + if(dateComboBox.getSelectedItem() != null) { + DateRange selectedDateRange = DateRange.fromLabel(dateComboBox.getSelectedItem().toString()); + System.out.println("Using date range " + selectedDateRange.getLabel()); + if(selectedDateRange != DateRange.ALL) { + long threshold = System.currentTimeMillis() - selectedDateRange.getTimestampRange(); + intermediateTimingDataForDisplay = timingData.get(name).stream() + .filter(t -> t.getTimestamp() > threshold) + .collect(Collectors.toList()); + } else { + intermediateTimingDataForDisplay = timingData.get(name); + } + } else { + intermediateTimingDataForDisplay = timingData.get(name); + } + + // Get the name of the selected host, if there is one + String hostToDisplay = null; + if(hostCheckBox.isSelected() && (hostComboBox.getSelectedItem() != null)) { + hostToDisplay = hostComboBox.getSelectedItem().toString(); + } + + TimingMetricGraphPanel singleTimingGraphPanel = new TimingMetricGraphPanel(intermediateTimingDataForDisplay, + TimingMetricGraphPanel.TimingMetricType.AVERAGE, hostToDisplay, true); + // Add the metric name + JLabel metricNameLabel = new JLabel(name); + metricNameLabel.setFont(new Font("Serif", Font.BOLD, 12)); + graphPanel.add(metricNameLabel); + singleTimingGraphPanel.setPreferredSize(new Dimension(900,250)); + graphPanel.add(singleTimingGraphPanel); + } + System.out.println("Repainting panel"); + graphPanel.revalidate(); + graphPanel.repaint(); + } + + private JPanel createAdminPanel() { + + JPanel adminPanel = new JPanel(); + adminPanel.setBorder(BorderFactory.createEtchedBorder()); + + // Create the buttons for enabling/disabling the monitor + JButton enableButton = new JButton("Enable monitor"); + JButton disableButton = new JButton("Disable monitor"); + + boolean isEnabled = EnterpriseHealthMonitor.monitorIsEnabled(); + enableButton.setEnabled(! isEnabled); + disableButton.setEnabled(isEnabled); + + // Set up a listener on the enable button + enableButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + System.out.println("\n### In action listener for enable button"); + try { + dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + EnterpriseHealthMonitor.setEnabled(true); + redisplay(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error enabling monitoring", ex); + } finally { + dialog.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + } + }); + + // Set up a listener on the disable button + disableButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + System.out.println("\n### In action listener for disable button"); + try { + dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + EnterpriseHealthMonitor.setEnabled(false); + redisplay(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error disabling monitoring", ex); + } finally { + dialog.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + } + }); + + adminPanel.add(enableButton); + adminPanel.add(Box.createHorizontalStrut(25)); + adminPanel.add(disableButton); + + return adminPanel; + } + + /*private void refreshPanels() throws HealthMonitorException { // Update the monitor status EnterpriseHealthMonitor.getInstance().updateFromGlobalEnabledStatus(); @@ -94,12 +395,12 @@ public class HealthMonitorDashboard { // Initialize and populate the timing metric panel initializeTimingMetricPanel(); - } + }*/ /** * Display the Health Monitor dashboard. */ - public void display() { + /*public void display() { if(dialog != null) { dialog.setVisible(false); dialog.dispose(); @@ -130,14 +431,14 @@ public class HealthMonitorDashboard { dialog.pack(); dialog.setLocationRelativeTo(parentWindow); dialog.setVisible(true); - } + }*/ /** * Initialize the admin panel, which allows the monitor to be enabled * or diabled * @throws HealthMonitorException */ - private void setupAdminPanel() throws HealthMonitorException { + /*private void setupAdminPanel() throws HealthMonitorException { if (adminPanel == null) { adminPanel = new JPanel(); adminPanel.setBorder(BorderFactory.createEtchedBorder()); @@ -159,22 +460,22 @@ public class HealthMonitorDashboard { adminPanel.add(Box.createHorizontalStrut(25)); adminPanel.add(disableButton); - } + }*/ /** * Update the enable and disable buttons */ - private void updateEnableButtons() { + /*private void updateEnableButtons() { boolean isEnabled = EnterpriseHealthMonitor.monitorIsEnabled(); enableButton.setEnabled(! isEnabled); disableButton.setEnabled(isEnabled); - } + }*/ /** * Initialize the panel holding the timing metric controls and * update components */ - private void setupTimingButtonPanel() throws HealthMonitorException { + /*private void setupTimingButtonPanel() throws HealthMonitorException { if(timingButtonPanel == null) { timingButtonPanel = new JPanel(); timingButtonPanel.setBorder(BorderFactory.createEtchedBorder()); @@ -231,13 +532,13 @@ public class HealthMonitorDashboard { timingButtonPanel.add(hostCheckBox); //timingButtonPanel.add(new JLabel("Host to display")); timingButtonPanel.add(hostComboBox); - } + }*/ /** * Add any needed action listeners. * Call this after all components are initialized. */ - private void addActionListeners() { + /*private void addActionListeners() { // Set up a listener on the combo box that will update the timing // metric graphs @@ -328,12 +629,12 @@ public class HealthMonitorDashboard { } }); } - } + }*/ /** * Initialize the panel holding the timing metrics. */ - private void initializeTimingMetricPanel() throws HealthMonitorException { + /*private void initializeTimingMetricPanel() throws HealthMonitorException { timingMetricPanel = new JPanel(); timingMetricPanel.setLayout(new BoxLayout(timingMetricPanel, BoxLayout.PAGE_AXIS)); @@ -400,7 +701,7 @@ public class HealthMonitorDashboard { if(dialog != null) { dialog.pack(); } - } + }*/ enum DateRange { ALL("All", 0), From f496a20295d5bba93a4e756d1453565ca17c1604 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 26 Apr 2018 11:22:14 -0400 Subject: [PATCH 10/63] Change to use bundle messages --- .../healthmonitor/HealthMonitorDashboard.java | 402 +++--------------- 1 file changed, 50 insertions(+), 352 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java index 1c94a16f38..6f324442df 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java @@ -46,11 +46,12 @@ import javax.swing.BoxLayout; import java.util.logging.Level; import java.util.stream.Collectors; import org.openide.modules.Places; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; /** - * + * Dashboard for viewing metrics and controlling the health monitor. */ public class HealthMonitorDashboard { @@ -81,6 +82,8 @@ public class HealthMonitorDashboard { /** * Display the dashboard. */ + @NbBundle.Messages({"HealthMonitorDashboard.display.errorCreatingDashboard=Error creating health monitor dashboard", + "HealthMonitorDashboard.display.dashboardTitle=Enterprise Health Monitor"}) public void display() { // Update the enabled status and get the timing data, then create all @@ -93,7 +96,7 @@ public class HealthMonitorDashboard { adminPanel = createAdminPanel(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error creating panels for health monitor dashboard", ex); - MessageNotifyUtil.Message.error("TEMP"); + MessageNotifyUtil.Message.error(Bundle.HealthMonitorDashboard_display_errorCreatingDashboard()); return; } @@ -113,7 +116,7 @@ public class HealthMonitorDashboard { // Create and show the dialog dialog = new JDialog(); - dialog.setTitle("Enterprise Health Monitor"); + dialog.setTitle(Bundle.HealthMonitorDashboard_display_dashboardTitle()); dialog.add(mainPanel); dialog.pack(); dialog.setLocationRelativeTo(parentWindow); @@ -152,6 +155,8 @@ public class HealthMonitorDashboard { * @return The timing panel * @throws HealthMonitorException */ + @NbBundle.Messages({"HealthMonitorDashboard.createTimingPanel.noData=No data to display - monitor is not enabled", + "HealthMonitorDashboard.createTimingPanel.timingMetricsTitle=Timing Metrics"}) private JPanel createTimingPanel() throws HealthMonitorException { JPanel timingMetricPanel = new JPanel(); timingMetricPanel.setLayout(new BoxLayout(timingMetricPanel, BoxLayout.PAGE_AXIS)); @@ -159,15 +164,15 @@ public class HealthMonitorDashboard { timingMetricPanel.setBorder(BorderFactory.createEtchedBorder()); // Add title - JLabel timingMetricTitle = new JLabel("Timing Metrics"); + JLabel timingMetricTitle = new JLabel(Bundle.HealthMonitorDashboard_createTimingPanel_timingMetricsTitle()); timingMetricTitle.setFont(new Font("Serif", Font.BOLD, 20)); timingMetricPanel.add(timingMetricTitle); // If the monitor isn't enabled, just add a message if(! EnterpriseHealthMonitor.monitorIsEnabled()) { timingMetricPanel.setPreferredSize(new Dimension(400,100)); - timingMetricPanel.add(new JLabel(" ")); - timingMetricPanel.add(new JLabel("No data to display - monitor is not enabled")); + timingMetricPanel.add(new JLabel("")); + timingMetricPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createTimingPanel_noData())); timingMetricPanel.revalidate(); timingMetricPanel.repaint(); @@ -191,14 +196,19 @@ public class HealthMonitorDashboard { return timingMetricPanel; } + /** + * Create the panel with combo boxes for date range and host. + * @return the control panel + */ + @NbBundle.Messages({"HealthMonitorDashboard.createTimingControlPanel.filterByHost=Filter by host", + "HealthMonitorDashboard.createTimingControlPanel.maxDays=Max days to display"}) private JPanel createTimingControlPanel() { - JPanel timingButtonPanel = new JPanel(); - timingButtonPanel.setBorder(BorderFactory.createEtchedBorder()); - //timingButtonPanel.setPreferredSize(new Dimension(1500, 100)); + JPanel timingControlPanel = new JPanel(); + timingControlPanel.setBorder(BorderFactory.createEtchedBorder()); // If the monitor is not enabled, don't add any components if(! EnterpriseHealthMonitor.monitorIsEnabled()) { - return timingButtonPanel; + return timingControlPanel; } // Create the combo box for selecting how much data to display @@ -244,7 +254,7 @@ public class HealthMonitorDashboard { }); // Create the checkbox - hostCheckBox = new JCheckBox("Filter by host"); + hostCheckBox = new JCheckBox(Bundle.HealthMonitorDashboard_createTimingControlPanel_filterByHost()); hostCheckBox.setSelected(false); hostComboBox.setEnabled(false); @@ -253,7 +263,7 @@ public class HealthMonitorDashboard { @Override public void actionPerformed(ActionEvent arg0) { try { - hostComboBox.setEnabled(hostCheckBox.isSelected()); // Why isn't this working? + hostComboBox.setEnabled(hostCheckBox.isSelected()); updateTimingMetricGraphs(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error populating timing metric panel", ex); @@ -267,20 +277,23 @@ public class HealthMonitorDashboard { //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); + timingControlPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createTimingControlPanel_maxDays())); + timingControlPanel.add(dateComboBox); // Put some space between the elements - timingButtonPanel.add(Box.createHorizontalStrut(100)); + timingControlPanel.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); + timingControlPanel.add(hostCheckBox); + timingControlPanel.add(hostComboBox); - return timingButtonPanel; + return timingControlPanel; } + /** + * Update the timing graphs. + * @throws HealthMonitorException + */ private void updateTimingMetricGraphs() throws HealthMonitorException { // Clear out any old graphs @@ -292,7 +305,6 @@ public class HealthMonitorDashboard { List intermediateTimingDataForDisplay; if(dateComboBox.getSelectedItem() != null) { DateRange selectedDateRange = DateRange.fromLabel(dateComboBox.getSelectedItem().toString()); - System.out.println("Using date range " + selectedDateRange.getLabel()); if(selectedDateRange != DateRange.ALL) { long threshold = System.currentTimeMillis() - selectedDateRange.getTimestampRange(); intermediateTimingDataForDisplay = timingData.get(name).stream() @@ -320,19 +332,25 @@ public class HealthMonitorDashboard { singleTimingGraphPanel.setPreferredSize(new Dimension(900,250)); graphPanel.add(singleTimingGraphPanel); } - System.out.println("Repainting panel"); graphPanel.revalidate(); graphPanel.repaint(); } + /** + * Create the admin panel. + * This allows the health monitor to be enabled and disabled. + * @return + */ + @NbBundle.Messages({"HealthMonitorDashboard.createAdminPanel.enableButton=Enable monitor", + "HealthMonitorDashboard.createAdminPanel.disableButton=Disable monitor"}) private JPanel createAdminPanel() { JPanel adminPanel = new JPanel(); adminPanel.setBorder(BorderFactory.createEtchedBorder()); // Create the buttons for enabling/disabling the monitor - JButton enableButton = new JButton("Enable monitor"); - JButton disableButton = new JButton("Disable monitor"); + JButton enableButton = new JButton(Bundle.HealthMonitorDashboard_createAdminPanel_enableButton()); + JButton disableButton = new JButton(Bundle.HealthMonitorDashboard_createAdminPanel_disableButton()); boolean isEnabled = EnterpriseHealthMonitor.monitorIsEnabled(); enableButton.setEnabled(! isEnabled); @@ -342,7 +360,6 @@ public class HealthMonitorDashboard { enableButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { - System.out.println("\n### In action listener for enable button"); try { dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); EnterpriseHealthMonitor.setEnabled(true); @@ -359,7 +376,6 @@ public class HealthMonitorDashboard { disableButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { - System.out.println("\n### In action listener for disable button"); try { dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); EnterpriseHealthMonitor.setEnabled(false); @@ -379,334 +395,16 @@ public class HealthMonitorDashboard { return adminPanel; } - /*private void refreshPanels() throws HealthMonitorException { - // Update the monitor status - EnterpriseHealthMonitor.getInstance().updateFromGlobalEnabledStatus(); - - if(EnterpriseHealthMonitor.monitorIsEnabled()) { - // Get a copy of the timing data from the database - timingData = EnterpriseHealthMonitor.getInstance().getTimingMetricsFromDatabase(); - } - - // Set up the UI - setupAdminPanel(); - setupTimingButtonPanel(); - addActionListeners(); - - // Initialize and populate the timing metric panel - initializeTimingMetricPanel(); - }*/ - /** - * Display the Health Monitor dashboard. + * Possible date ranges for the metrics in the UI */ - /*public void display() { - if(dialog != null) { - dialog.setVisible(false); - dialog.dispose(); - } - - try { - refreshPanels(); - } catch (HealthMonitorException ex) { - logger.log(Level.SEVERE, "Error creating panels for health monitor dashboard", ex); - MessageNotifyUtil.Message.error("TEMP"); - } - - JScrollPane scrollPane = new JScrollPane(timingMetricPanel); - JPanel mainPanel = new JPanel(); - mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS)); - mainPanel.add(scrollPane); - File adminFile = new File(ADMIN_ACCESS_FILE_PATH); - System.out.println("admin file: " + adminFile.getAbsolutePath()); - if(adminFile.exists()) { - mainPanel.add(adminPanel); - } - dialog = new JDialog(); - //dialog.setPreferredSize(new Dimension(1500, 800)); - dialog.setTitle("Enterprise Health Monitor"); - - dialog.add(mainPanel); - - dialog.pack(); - dialog.setLocationRelativeTo(parentWindow); - dialog.setVisible(true); - }*/ - - /** - * Initialize the admin panel, which allows the monitor to be enabled - * or diabled - * @throws HealthMonitorException - */ - /*private void setupAdminPanel() throws HealthMonitorException { - if (adminPanel == null) { - adminPanel = new JPanel(); - adminPanel.setBorder(BorderFactory.createEtchedBorder()); - } else { - adminPanel.removeAll(); - } - - if (enableButton == null) { - enableButton = new JButton("Enable monitor"); - } - - if(disableButton == null) { - disableButton = new JButton("Disable monitor"); - } - - updateEnableButtons(); - - adminPanel.add(enableButton); - adminPanel.add(Box.createHorizontalStrut(25)); - adminPanel.add(disableButton); - - }*/ - - /** - * Update the enable and disable buttons - */ - /*private void updateEnableButtons() { - boolean isEnabled = EnterpriseHealthMonitor.monitorIsEnabled(); - enableButton.setEnabled(! isEnabled); - disableButton.setEnabled(isEnabled); - }*/ - - /** - * Initialize the panel holding the timing metric controls and - * update components - */ - /*private void setupTimingButtonPanel() throws HealthMonitorException { - if(timingButtonPanel == null) { - timingButtonPanel = new JPanel(); - timingButtonPanel.setBorder(BorderFactory.createEtchedBorder()); - //timingButtonPanel.setPreferredSize(new Dimension(1500, 100)); - } else { - timingButtonPanel.removeAll(); - } - - // If the monitor is not enabled, don't add any components - if(! EnterpriseHealthMonitor.monitorIsEnabled()) { - return; - } - - // 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()); - } - - // 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); - }*/ - - /** - * Add any needed action listeners. - * 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 - if((dateComboBox != null) && (dateComboBox.getActionListeners().length == 0)) { - dateComboBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - try { - initializeTimingMetricPanel(); - } 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()) { - initializeTimingMetricPanel(); - } - } 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? - initializeTimingMetricPanel(); - } catch (HealthMonitorException ex) { - logger.log(Level.SEVERE, "Error populating timing metric panel", ex); - } - } - }); - } - - // Set up a listener on the enable button - if((enableButton != null) && (enableButton.getActionListeners().length == 0)) { - enableButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - System.out.println("\n### In action listener for enable button"); - try { - dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - //WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - EnterpriseHealthMonitor.setEnabled(true); - HealthMonitorDashboard.this.updateEnableButtons(); - display(); - } catch (HealthMonitorException ex) { - logger.log(Level.SEVERE, "Error enabling monitoring", ex); - } finally { - //WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - dialog.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - } - } - }); - } - - // Set up a listener on the disable button - if((disableButton != null) && (disableButton.getActionListeners().length == 0)) { - disableButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - System.out.println("\n### In action listener for disable button"); - try { - dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - //WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - EnterpriseHealthMonitor.setEnabled(false); - HealthMonitorDashboard.this.updateEnableButtons(); - display(); - } catch (HealthMonitorException ex) { - logger.log(Level.SEVERE, "Error disabling monitoring", ex); - } finally { - //WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - dialog.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - } - } - }); - } - }*/ - - /** - * Initialize the panel holding the timing metrics. - */ - /*private void initializeTimingMetricPanel() throws HealthMonitorException { - - timingMetricPanel = new JPanel(); - timingMetricPanel.setLayout(new BoxLayout(timingMetricPanel, BoxLayout.PAGE_AXIS)); - timingMetricPanel.setAlignmentX(Component.LEFT_ALIGNMENT); - timingMetricPanel.setBorder(BorderFactory.createEtchedBorder()); - - // Add title - JLabel timingMetricTitle = new JLabel("Timing Metrics"); - timingMetricTitle.setFont(new Font("Serif", Font.BOLD, 20)); - timingMetricPanel.add(timingMetricTitle); - - // If the monitor isn't enabled, just add a message - if(! EnterpriseHealthMonitor.monitorIsEnabled()) { - timingMetricPanel.setPreferredSize(new Dimension(400,100)); - timingMetricPanel.add(new JLabel(" ")); - timingMetricPanel.add(new JLabel("No data to display - monitor is not enabled")); - - timingMetricPanel.revalidate(); - timingMetricPanel.repaint(); - return; - } - - // Add the button controls - timingMetricPanel.add(timingButtonPanel); - timingMetricPanel.add(new JSeparator()); - - for(String name:timingData.keySet()) { - - // If necessary, trim down the list of results to fit the selected time range - List intermediateTimingDataForDisplay; - if(dateComboBox.getSelectedItem() != null) { - DateRange selectedDateRange = DateRange.fromLabel(dateComboBox.getSelectedItem().toString()); - if(selectedDateRange != DateRange.ALL) { - long threshold = System.currentTimeMillis() - selectedDateRange.getTimestampRange(); - intermediateTimingDataForDisplay = timingData.get(name).stream() - .filter(t -> t.getTimestamp() > threshold) - .collect(Collectors.toList()); - } else { - intermediateTimingDataForDisplay = timingData.get(name); - } - } else { - intermediateTimingDataForDisplay = timingData.get(name); - } - - // Get the name of the selected host, if there is one - String hostToDisplay = null; - if(hostCheckBox.isSelected() && (hostComboBox.getSelectedItem() != null)) { - hostToDisplay = hostComboBox.getSelectedItem().toString(); - } - - TimingMetricGraphPanel singleTimingGraphPanel = new TimingMetricGraphPanel(intermediateTimingDataForDisplay, - TimingMetricGraphPanel.TimingMetricType.AVERAGE, hostToDisplay, true); - // Add the metric name - JLabel metricNameLabel = new JLabel(name); - metricNameLabel.setFont(new Font("Serif", Font.BOLD, 12)); - timingMetricPanel.add(metricNameLabel); - singleTimingGraphPanel.setPreferredSize(new Dimension(900,250)); - timingMetricPanel.add(singleTimingGraphPanel); - } - - timingMetricPanel.revalidate(); - timingMetricPanel.repaint(); - - if(dialog != null) { - dialog.pack(); - } - }*/ - - enum DateRange { - ALL("All", 0), - TWO_WEEKS("Two weeks", 14), - ONE_WEEK("One week", 7); + @NbBundle.Messages({"HealthMonitorDashboard.DateRange.all=All", + "HealthMonitorDashboard.DateRange.twoWeeks=Two weeks", + "HealthMonitorDashboard.DateRange.oneWeek=One week"}) + private enum DateRange { + ALL(Bundle.HealthMonitorDashboard_DateRange_all(), 0), + TWO_WEEKS(Bundle.HealthMonitorDashboard_DateRange_twoWeeks(), 14), + ONE_WEEK(Bundle.HealthMonitorDashboard_DateRange_oneWeek(), 7); private final String label; private final long numberOfDays; @@ -739,7 +437,7 @@ public class HealthMonitorDashboard { } } - public static DateRange fromLabel(String text) { + static DateRange fromLabel(String text) { for (DateRange dateRange : DateRange.values()) { if (dateRange.label.equalsIgnoreCase(text)) { return dateRange; From 3fe761185ca9ffe839692541a5fb6a0fbfb3d6af Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 26 Apr 2018 14:57:10 -0400 Subject: [PATCH 11/63] Bundle messages and cleanup --- .../EnterpriseHealthMonitor.java | 204 ++++++++---------- .../healthmonitor/HealthMonitorDashboard.java | 20 +- .../autopsy/healthmonitor/TestPanel.java | 4 +- .../healthmonitor/TimingMetricGraphPanel.java | 58 ++--- 4 files changed, 133 insertions(+), 153 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index 4067fca549..42d78f0e47 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -800,6 +800,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { /** * Perform all periodic tasks: * - Check if monitoring has been enabled / disabled in the database + * - Gather any additional metrics * - Write current metric data to the database */ @Override @@ -831,28 +832,21 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { /** * Debugging method to generate sample data for the database. * It will delete all current timing data and replace it with randomly generated values. - * If it is set to generate data for more than one node, the second node may be set to - * take longer than the others. + * If there is more than one node, the second node's times will trend upwards in the last few days. */ - final void populateDatabase(int nDays, int nNodes, boolean createVerificationData) throws HealthMonitorException { + final void populateDatabaseWithSampleData(int nDays, int nNodes, boolean createVerificationData) throws HealthMonitorException { if(! isEnabled.get()) { throw new HealthMonitorException("Can't populate database - monitor not enabled"); } - // Write to the database + // Get the database lock CoordinationService.Lock lock = getSharedDbLock(); if(lock == null) { throw new HealthMonitorException("Error getting database lock"); } - int minIndexTime = 9000000; - int maxIndexTimeOverMin = 50000000; - long indexMultiplier = 1; // To test different scales - - int minConnTime = 15000000; - int maxConnTimeOverMin = 18000000; - long connTimeMultiplier = 1; // To test different scales + String[] metricNames = {"Disk Reads: Hash calculation", "Database: getImages query", "Solr: Index chunk", "Solr: Connectivity check"}; // NON-NLS Random rand = new Random(); @@ -869,118 +863,102 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { try (Statement statement = conn.createStatement()) { - statement.execute("DELETE FROM timing_data"); + statement.execute("DELETE FROM timing_data"); // NON-NLS } catch (SQLException ex) { - ex.printStackTrace(); + logger.log(Level.SEVERE, "Error clearing timing data", ex); + return; } - for(int node = 0;node < nNodes; node++) { + + + // Add timing metrics to the database + String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)"; + try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) { + + double count = 0; + double maxCount = nDays * 24 + 1; + + for(String metricName:metricNames) { + + long baseIndex = rand.nextInt(900) + 100; + int multiplier = rand.nextInt(5); + long minIndexTimeNanos; + switch(multiplier) { + case 0: + minIndexTimeNanos = baseIndex; + break; + case 1: + minIndexTimeNanos = baseIndex * 1000; + break; + default: + minIndexTimeNanos = baseIndex * 1000 * 1000; + } + + long maxIndexTimeOverMin = minIndexTimeNanos * 3; + + for(int node = 0;node < nNodes; node++) { - String host = "testHost" + node; - - // Add timing metrics to the database - String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)"; - try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) { - - double count = 0; - double maxCount = nDays * 24 + 1; - - // Record index chunk every hour - for(long timestamp = minTimestamp + rand.nextInt(1000 * 60 * 55);timestamp < maxTimestamp;timestamp += millisPerHour) { - - long aveTime; + String host = "testHost" + node; // NON-NLS - // This creates data that increases in the last couple of days of the simulated - // collection - count++; - double slowNodeMultiplier = 1.0; - if((maxCount - count) <= 3 * 24) { - slowNodeMultiplier += (3 - (maxCount - count) / 24) * 0.33; - } - - 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); + // Record data every hour, with a small amount of randomness about when it starts + for(long timestamp = minTimestamp + rand.nextInt(1000 * 60 * 55);timestamp < maxTimestamp;timestamp += millisPerHour) { + + double aveTime; + + // This creates data that increases in the last couple of days of the simulated + // collection + count++; + double slowNodeMultiplier = 1.0; + if((maxCount - count) <= 3 * 24) { + slowNodeMultiplier += (3 - (maxCount - count) / 24) * 0.33; + } + + 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); + long randVal = rand.nextLong(); + if(randVal < 0) { + randVal *= -1; + } + if(outlierVal < 2){ + aveTime = minIndexTimeNanos + maxIndexTimeOverMin + randVal % maxIndexTimeOverMin; + } else if(outlierVal == 2){ + aveTime = (minIndexTimeNanos / 2) + randVal % (minIndexTimeNanos / 2); + } else if(outlierVal < 17) { + aveTime = minIndexTimeNanos + randVal % (maxIndexTimeOverMin / 2); + } else { + aveTime = minIndexTimeNanos + randVal % maxIndexTimeOverMin; + } + + if(node == 1) { + aveTime = (long)((double)aveTime * slowNodeMultiplier); + } } 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; } - aveTime = aveTime * indexMultiplier; - if(node == 1) { - aveTime = (long)((double)aveTime * slowNodeMultiplier); - } - } 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, metricName); + statement.setString(2, host); + statement.setLong(3, timestamp); + statement.setLong(4, 0); + statement.setDouble(5, aveTime / 1000000); + statement.setDouble(6, 0); + statement.setDouble(7, 0); + + statement.execute(); } - - - statement.setString(1, "Solr: Index chunk"); - statement.setString(2, host); - statement.setLong(3, timestamp); - statement.setLong(4, 0); - statement.setDouble(5, aveTime); - statement.setDouble(6, 0); - statement.setDouble(7, 0); - - statement.execute(); } - - // Make some case opening ones - // Record index chunk every hour - for(long timestamp = minTimestamp + rand.nextInt(1000 * 60 * 55);timestamp < maxTimestamp;timestamp += (1 + rand.nextInt(10)) * millisPerHour) { - - 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); - } - aveTime *= connTimeMultiplier; - } 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"); - statement.setString(2, host); - statement.setLong(3, timestamp); - statement.setLong(4, 0); - statement.setDouble(5, aveTime); - statement.setDouble(6, 0); - statement.setDouble(7, 0); - - statement.execute(); - } - - } catch (SQLException ex) { - throw new HealthMonitorException("Error saving metric data to database", ex); - } finally { - } + } catch (SQLException ex) { + throw new HealthMonitorException("Error saving metric data to database", ex); } } finally { try { diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java index 6f324442df..1dec3c60b3 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java @@ -29,7 +29,6 @@ import java.util.Arrays; import java.util.List; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.Font; import java.io.File; import javax.swing.Box; import javax.swing.JButton; @@ -57,7 +56,7 @@ public class HealthMonitorDashboard { private final static Logger logger = Logger.getLogger(HealthMonitorDashboard.class.getName()); - private final static String ADMIN_ACCESS_FILE_NAME = "adminAccess"; + private final static String ADMIN_ACCESS_FILE_NAME = "adminAccess"; // NON-NLS private final static String ADMIN_ACCESS_FILE_PATH = Places.getUserDirectory().getAbsolutePath() + File.separator + ADMIN_ACCESS_FILE_NAME; Map> timingData; @@ -165,8 +164,8 @@ public class HealthMonitorDashboard { // Add title JLabel timingMetricTitle = new JLabel(Bundle.HealthMonitorDashboard_createTimingPanel_timingMetricsTitle()); - timingMetricTitle.setFont(new Font("Serif", Font.BOLD, 20)); timingMetricPanel.add(timingMetricTitle); + timingMetricPanel.add(new JSeparator()); // If the monitor isn't enabled, just add a message if(! EnterpriseHealthMonitor.monitorIsEnabled()) { @@ -204,7 +203,7 @@ public class HealthMonitorDashboard { "HealthMonitorDashboard.createTimingControlPanel.maxDays=Max days to display"}) private JPanel createTimingControlPanel() { JPanel timingControlPanel = new JPanel(); - timingControlPanel.setBorder(BorderFactory.createEtchedBorder()); + //timingControlPanel.setBorder(BorderFactory.createEtchedBorder()); // If the monitor is not enabled, don't add any components if(! EnterpriseHealthMonitor.monitorIsEnabled()) { @@ -271,11 +270,6 @@ public class HealthMonitorDashboard { } }); - // 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 timingControlPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createTimingControlPanel_maxDays())); timingControlPanel.add(dateComboBox); @@ -323,11 +317,12 @@ public class HealthMonitorDashboard { hostToDisplay = hostComboBox.getSelectedItem().toString(); } + // Generate the graph TimingMetricGraphPanel singleTimingGraphPanel = new TimingMetricGraphPanel(intermediateTimingDataForDisplay, TimingMetricGraphPanel.TimingMetricType.AVERAGE, hostToDisplay, true); - // Add the metric name + + // Add the metric name and the graph to the panel JLabel metricNameLabel = new JLabel(name); - metricNameLabel.setFont(new Font("Serif", Font.BOLD, 12)); graphPanel.add(metricNameLabel); singleTimingGraphPanel.setPreferredSize(new Dimension(900,250)); graphPanel.add(singleTimingGraphPanel); @@ -388,6 +383,7 @@ public class HealthMonitorDashboard { } }); + // Add the buttons adminPanel.add(enableButton); adminPanel.add(Box.createHorizontalStrut(25)); adminPanel.add(disableButton); @@ -443,7 +439,7 @@ public class HealthMonitorDashboard { return dateRange; } } - return ALL; // If the comparison failed, return the default + return ALL; // If the comparison failed, return a default } } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java index 63ebf575c6..d8143aaa5c 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java @@ -434,7 +434,7 @@ public class TestPanel extends javax.swing.JDialog { try { int nDays = Integer.valueOf(nDaysTextField.getText()); int nNodes = Integer.valueOf(nNodesTextField.getText()); - EnterpriseHealthMonitor.getInstance().populateDatabase(nDays, nNodes, false); // TEMP TEMP + EnterpriseHealthMonitor.getInstance().populateDatabaseWithSampleData(nDays, nNodes, false); // TEMP TEMP } catch (Exception ex) { ex.printStackTrace(); } @@ -459,7 +459,7 @@ public class TestPanel extends javax.swing.JDialog { try { int nDays = Integer.valueOf(nDaysTextField.getText()); int nNodes = Integer.valueOf(nNodesTextField.getText()); - EnterpriseHealthMonitor.getInstance().populateDatabase(nDays, nNodes, true); // TEMP TEMP + EnterpriseHealthMonitor.getInstance().populateDatabaseWithSampleData(nDays, nNodes, true); // TEMP TEMP } catch (Exception ex) { ex.printStackTrace(); } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index 29a4fc4d44..88eb47d437 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -38,6 +38,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import java.util.logging.Level; import java.util.TimeZone; import java.util.concurrent.TimeUnit; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor.DatabaseTimingResult; /** @@ -62,6 +63,7 @@ class TimingMetricGraphPanel extends JPanel { private String yUnitString; private TrendLine trendLine; private final long MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24; + private final long NANOSECONDS_PER_MILLISECOND = 1000 * 1000; long maxTimestamp; long minTimestamp; double maxMetricTime; @@ -93,8 +95,6 @@ class TimingMetricGraphPanel extends JPanel { calcMinTimestamp(timingResultsFull); calcMaxMetricTime(timingResultsFull); calcMinMetricTime(timingResultsFull); - - } /** @@ -174,6 +174,13 @@ class TimingMetricGraphPanel extends JPanel { * For plotting data on the y-axis, we subtract from the max value in the graph and then scale to the size of the graph * @param g */ + @NbBundle.Messages({"TimingMetricGraphPanel.paintComponent.nanoseconds=nanoseconds", + "TimingMetricGraphPanel.paintComponent.microseconds=microseconds", + "TimingMetricGraphPanel.paintComponent.milliseconds=milliseconds", + "TimingMetricGraphPanel.paintComponent.seconds=seconds", + "TimingMetricGraphPanel.paintComponent.minutes=minutes", + "TimingMetricGraphPanel.paintComponent.hours=hours", + "TimingMetricGraphPanel.paintComponent.displayingTime=Displaying time in "}) @Override protected void paintComponent(Graphics g) { super.paintComponent(g); @@ -182,8 +189,8 @@ class TimingMetricGraphPanel extends JPanel { // Get the max and min timestamps to create the x-axis. // We add a small buffer to each side so the data won't overwrite the axes. - double maxValueOnXAxis = maxTimestamp + 1000 * 60 *60 * 2; // Two hour buffer - double minValueOnXAxis = minTimestamp - 1000 * 60 * 60 * 2; // Two hour buffer + double maxValueOnXAxis = maxTimestamp + TimeUnit.HOURS.toMillis(2); // Two hour buffer + double minValueOnXAxis = minTimestamp - TimeUnit.HOURS.toMillis(2); // Two hour buffer // Get the max and min times to create the y-axis // We add a small buffer to each side so the data won't overwrite the axes. @@ -216,31 +223,30 @@ class TimingMetricGraphPanel extends JPanel { double yScale = ((double) graphHeight) / (maxValueOnYAxis - minValueOnYAxis); // Check if we should use a scale other than milliseconds - long middleOfGraphNano = (long)(minValueOnYAxis + (maxValueOnYAxis - minValueOnYAxis) / 2) * 1000000; - double yLabelScale; - // The idea here is to pick the scale that would most commonly be used to // represent the middle of our data. For example, if the middle of the graph // would be 45,000,000 nanoseconds, then we would use milliseconds for the // y-axis. - if(middleOfGraphNano < 1000) { - yUnitString = "nanoseconds"; - yLabelScale = 1000000; - } else if (TimeUnit.NANOSECONDS.toMicros(middleOfGraphNano) < 1000) { - yUnitString = "microseconds"; - yLabelScale = 1000; - } else if (TimeUnit.NANOSECONDS.toMillis(middleOfGraphNano) < 1000) { - yUnitString = "milliseconds"; + long middleOfGraphNano = (long)((minValueOnYAxis + (maxValueOnYAxis - minValueOnYAxis) / 2.0) * NANOSECONDS_PER_MILLISECOND); + double yLabelScale; + if(middleOfGraphNano < TimeUnit.MICROSECONDS.toNanos(1)) { + yUnitString = Bundle.TimingMetricGraphPanel_paintComponent_nanoseconds(); + yLabelScale = TimeUnit.MILLISECONDS.toNanos(1); + } else if (TimeUnit.NANOSECONDS.toMicros(middleOfGraphNano) < TimeUnit.MILLISECONDS.toMicros(1)) { + yUnitString = Bundle.TimingMetricGraphPanel_paintComponent_microseconds(); + yLabelScale = TimeUnit.MILLISECONDS.toMicros(1); + } else if (TimeUnit.NANOSECONDS.toMillis(middleOfGraphNano) < TimeUnit.SECONDS.toMillis(1)) { + yUnitString = Bundle.TimingMetricGraphPanel_paintComponent_milliseconds(); yLabelScale = 1; - } else if (TimeUnit.NANOSECONDS.toSeconds(middleOfGraphNano) < 60) { - yUnitString = "seconds"; - yLabelScale = 1.0 / 1000; - } else if (TimeUnit.NANOSECONDS.toMinutes(middleOfGraphNano) < 60) { - yUnitString = "minutes"; - yLabelScale = 1.0 / (1000 * 60); + } else if (TimeUnit.NANOSECONDS.toSeconds(middleOfGraphNano) < TimeUnit.MINUTES.toSeconds(1)) { + yUnitString = Bundle.TimingMetricGraphPanel_paintComponent_seconds(); + yLabelScale = 1.0 / TimeUnit.SECONDS.toMillis(1); + } else if (TimeUnit.NANOSECONDS.toMinutes(middleOfGraphNano) < TimeUnit.HOURS.toMinutes(1)) { + yUnitString = Bundle.TimingMetricGraphPanel_paintComponent_minutes(); + yLabelScale = 1.0 / (TimeUnit.MINUTES.toMillis(1)); } else { - yUnitString = "hours"; - yLabelScale = 1.0 / (1000 * 60 * 60); + yUnitString = Bundle.TimingMetricGraphPanel_paintComponent_hours(); + yLabelScale = 1.0 / (TimeUnit.HOURS.toMillis(1)); } // Draw white background @@ -273,7 +279,7 @@ class TimingMetricGraphPanel extends JPanel { if (i == numberYDivisions) { // Write the scale g2.setColor(Color.BLACK); - String scaleStr = "Displaying time in " + yUnitString; + String scaleStr = Bundle.TimingMetricGraphPanel_paintComponent_displayingTime() + yUnitString; g2.drawString(scaleStr, x0 - labelWidth - 5, padding); } } @@ -325,7 +331,7 @@ class TimingMetricGraphPanel extends JPanel { // Draw the label Calendar thisDate = new GregorianCalendar(); - thisDate.setTimeZone(TimeZone.getTimeZone("GMT")); + thisDate.setTimeZone(TimeZone.getTimeZone("GMT")); // Stick with GMT to avoid daylight savings issues thisDate.setTimeInMillis(currentDivision); int month = thisDate.get(Calendar.MONTH) + 1; int day = thisDate.get(Calendar.DAY_OF_MONTH); @@ -482,7 +488,7 @@ class TimingMetricGraphPanel extends JPanel { * * y intercept = ( Σy - (slope) * Σx ) / n */ - class TrendLine { + private class TrendLine { double slope; double yInt; From 871ce3ead150fb6abcf27521c61f48b0266adbc1 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 26 Apr 2018 15:09:38 -0400 Subject: [PATCH 12/63] Remove testing files and cleanup --- .../autopsy/healthmonitor/Bundle.properties | 21 - .../EnterpriseHealthMonitor.java | 27 +- .../healthmonitor/MonitorTestAction.java | 82 --- .../autopsy/healthmonitor/TestPanel.form | 353 ------------- .../autopsy/healthmonitor/TestPanel.java | 491 ------------------ 5 files changed, 7 insertions(+), 967 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/Bundle.properties delete mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/MonitorTestAction.java delete mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.form delete mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Bundle.properties b/Core/src/org/sleuthkit/autopsy/healthmonitor/Bundle.properties deleted file mode 100644 index 7c8a50c757..0000000000 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/Bundle.properties +++ /dev/null @@ -1,21 +0,0 @@ -TestPanel.jLabel1.text=Name -TestPanel.jLabel2.text=Duration (ms) -TestPanel.nameTextField.text= -TestPanel.submitMetricButton.text=Submit -TestPanel.closeButton.text=Close -TestPanel.printMapButton.text=Print Map -TestPanel.durationTextField.text= -TestPanel.jLabel3.text=Set enabled: -TestPanel.enabledCheckBox.text=Enabled -TestPanel.jButton1.text=Write data -TestPanel.deleteButton.text=Delete db -TestPanel.updateUIButton.text=Update UI -TestPanel.textExistButton.text=Test db -TestPanel.graphButton.text=Graph! -TestPanel.jLabel4.text=Number of days -TestPanel.nDaysTextField.text= -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/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index 42d78f0e47..0b369cf91c 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -103,9 +103,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { 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 - System.out.println("\n### Checking if monitor is enabled..."); - + // Read from the database to determine if the module is enabled updateFromGlobalEnabledStatus(); // Start the timer for database checks and writes @@ -159,7 +157,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { initializeDatabaseSchema(); } - // Set the enabled status in the databse to true + // Set the enabled status in the database to true setGlobalEnabledStatusInDB(true); } catch (CoordinationService.CoordinationServiceException ex) { @@ -832,7 +830,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { /** * Debugging method to generate sample data for the database. * It will delete all current timing data and replace it with randomly generated values. - * If there is more than one node, the second node's times will trend upwards in the last few days. + * If there is more than one node, the second node's times will trend upwards. */ final void populateDatabaseWithSampleData(int nDays, int nNodes, boolean createVerificationData) throws HealthMonitorException { @@ -875,9 +873,6 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)"; try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) { - double count = 0; - double maxCount = nDays * 24 + 1; - for(String metricName:metricNames) { long baseIndex = rand.nextInt(900) + 100; @@ -900,6 +895,9 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { String host = "testHost" + node; // NON-NLS + double count = 0; + double maxCount = nDays * 24 + 1; + // Record data every hour, with a small amount of randomness about when it starts for(long timestamp = minTimestamp + rand.nextInt(1000 * 60 * 55);timestamp < maxTimestamp;timestamp += millisPerHour) { @@ -977,8 +975,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } /** - * Get all timing metrics currently stored in the database. This also converts - * the times to milliseconds (from nanoseconds). + * Get all timing metrics currently stored in the database. * @return A map with metric name mapped to a list of data * @throws HealthMonitorException */ @@ -1175,16 +1172,6 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { private double max; // Maximum value found (milliseconds) private double min; // Minimum value found (milliseconds) - // TODO - maybe delete this - 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; - this.min = min; - } - DatabaseTimingResult(ResultSet resultSet) throws SQLException { this.timestamp = resultSet.getLong("timestamp"); this.hostname = resultSet.getString("host"); diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/MonitorTestAction.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/MonitorTestAction.java deleted file mode 100644 index f17fd15fb2..0000000000 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/MonitorTestAction.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.sleuthkit.autopsy.healthmonitor; - -import java.awt.Frame; -import org.openide.awt.ActionID; -import org.openide.awt.ActionReference; -import org.openide.awt.ActionRegistration; -import org.openide.util.HelpCtx; -import org.openide.util.NbBundle; -import org.openide.util.actions.CallableSystemAction; -import org.openide.windows.WindowManager; - -/* -// TODO: debug - synchronized void printCurrentState() { - System.out.println("\nTiming Info Map:"); - for(String name:timingInfoMap.keySet()) { - System.out.print(name + "\t"); - timingInfoMap.get(name).print(); - } - } - -private void deleteDatabase() { - try { - // Use the same database settings as the case - CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); - Class.forName("org.postgresql.Driver"); //NON-NLS - try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS - Statement statement = connection.createStatement();) { - String deleteCommand = "DROP DATABASE \"" + DATABASE_NAME + "\""; //NON-NLS - statement.execute(deleteCommand); - } - } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { - logger.log(Level.SEVERE, "Failed to delete health monitor database", ex); - } - } -*/ - - -@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.healthmonitor.MonitorTestAction") -@ActionReference(path = "Menu/Tools", position = 7014) -@ActionRegistration(displayName = "#CTL_MonitorTestAction", lazy = false) -@NbBundle.Messages({"CTL_MonitorTestAction=Test health monitor"}) -public final class MonitorTestAction extends CallableSystemAction { - - private static final String DISPLAY_NAME = "Test health monitor"; - - @Override - public boolean isEnabled() { - return true; - } - - @Override - @SuppressWarnings("fallthrough") - public void performAction() { - - Frame mainWindow = WindowManager.getDefault().getMainWindow(); - - TestPanel panel = new TestPanel(mainWindow, false); - panel.setVisible(true); - - } - - @Override - public String getName() { - return DISPLAY_NAME; - } - - @Override - public HelpCtx getHelpCtx() { - return HelpCtx.DEFAULT_HELP; - } - - @Override - public boolean asynchronous() { - return false; // run on edt - } -} diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.form b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.form deleted file mode 100644 index 2ed5b4a0cc..0000000000 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.form +++ /dev/null @@ -1,353 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java deleted file mode 100644 index d8143aaa5c..0000000000 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TestPanel.java +++ /dev/null @@ -1,491 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.sleuthkit.autopsy.healthmonitor; - -import java.awt.Dimension; -import org.sleuthkit.autopsy.coreutils.ModuleSettings; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import javax.swing.JDialog; -import javax.swing.JComboBox; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.BorderFactory; -import java.util.Map; -import javax.swing.BoxLayout; -import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor.DatabaseTimingResult; // TEMP TEMP - -/** - * This is for testing the Health Monitor code - */ -public class TestPanel extends javax.swing.JDialog { - - /** - * Creates new form TestPanel - */ - public TestPanel(java.awt.Frame parent, boolean modal) { - super(parent, modal); - initComponents(); - } - - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - - jLabel1 = new javax.swing.JLabel(); - jLabel2 = new javax.swing.JLabel(); - nameTextField = new javax.swing.JTextField(); - durationTextField = new javax.swing.JTextField(); - submitMetricButton = new javax.swing.JButton(); - closeButton = new javax.swing.JButton(); - printMapButton = new javax.swing.JButton(); - jLabel3 = new javax.swing.JLabel(); - enabledCheckBox = new javax.swing.JCheckBox(); - jButton1 = new javax.swing.JButton(); - deleteButton = new javax.swing.JButton(); - updateUIButton = new javax.swing.JButton(); - textExistButton = new javax.swing.JButton(); - graphButton = new javax.swing.JButton(); - nDaysTextField = new javax.swing.JTextField(); - jLabel4 = new javax.swing.JLabel(); - jLabel5 = new javax.swing.JLabel(); - nNodesTextField = new javax.swing.JTextField(); - jButton2 = new javax.swing.JButton(); - newGraphButton = new javax.swing.JButton(); - jButton3 = new javax.swing.JButton(); - - setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); - - org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.jLabel1.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.jLabel2.text")); // NOI18N - - nameTextField.setText(org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.nameTextField.text")); // NOI18N - - durationTextField.setText(org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.durationTextField.text")); // NOI18N - durationTextField.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - durationTextFieldActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(submitMetricButton, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.submitMetricButton.text")); // NOI18N - submitMetricButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - submitMetricButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.closeButton.text")); // NOI18N - closeButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - closeButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(printMapButton, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.printMapButton.text")); // NOI18N - printMapButton.setEnabled(false); - printMapButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - printMapButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.jLabel3.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(enabledCheckBox, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.enabledCheckBox.text")); // NOI18N - enabledCheckBox.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - enabledCheckBoxActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(jButton1, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.jButton1.text")); // NOI18N - jButton1.setEnabled(false); - jButton1.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - jButton1ActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(deleteButton, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.deleteButton.text")); // NOI18N - deleteButton.setEnabled(false); - deleteButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - deleteButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(updateUIButton, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.updateUIButton.text")); // NOI18N - updateUIButton.setEnabled(false); - updateUIButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - updateUIButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(textExistButton, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.textExistButton.text")); // NOI18N - textExistButton.setEnabled(false); - textExistButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - textExistButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(graphButton, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.graphButton.text")); // NOI18N - graphButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - graphButtonActionPerformed(evt); - } - }); - - nDaysTextField.setText(org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.nDaysTextField.text")); // NOI18N - nDaysTextField.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - nDaysTextFieldActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(jLabel4, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.jLabel4.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(jLabel5, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.jLabel5.text")); // NOI18N - - nNodesTextField.setText(org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.nNodesTextField.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(jButton2, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.jButton2.text")); // NOI18N - jButton2.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - jButton2ActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(newGraphButton, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.newGraphButton.text")); // NOI18N - newGraphButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - newGraphButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(jButton3, org.openide.util.NbBundle.getMessage(TestPanel.class, "TestPanel.jButton3.text")); // NOI18N - jButton3.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - jButton3ActionPerformed(evt); - } - }); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); - getContentPane().setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(deleteButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(closeButton)) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jLabel1) - .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 87, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(durationTextField) - .addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGap(18, 18, 18) - .addComponent(submitMetricButton)) - .addGroup(layout.createSequentialGroup() - .addComponent(printMapButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jButton1)) - .addGroup(layout.createSequentialGroup() - .addComponent(enabledCheckBox) - .addGap(18, 18, 18) - .addComponent(updateUIButton)) - .addComponent(textExistButton)) - .addGap(28, 28, 28) - .addComponent(newGraphButton) - .addGap(0, 108, Short.MAX_VALUE))) - .addContainerGap()) - .addGroup(layout.createSequentialGroup() - .addComponent(jLabel3) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(graphButton) - .addGap(142, 142, 142)) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(jLabel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(nDaysTextField)) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(jLabel5, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(nNodesTextField)) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jButton3) - .addComponent(jButton2)) - .addGap(0, 0, Short.MAX_VALUE)))) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(40, 40, 40) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel1) - .addComponent(jLabel2)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(durationTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(submitMetricButton)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(printMapButton) - .addComponent(jButton1)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel3) - .addComponent(graphButton)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(enabledCheckBox) - .addComponent(updateUIButton) - .addComponent(newGraphButton)) - .addGap(31, 31, 31) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel4) - .addComponent(jLabel5)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(nDaysTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(nNodesTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jButton2)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jButton3) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 37, Short.MAX_VALUE) - .addComponent(textExistButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(closeButton) - .addContainerGap()) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(deleteButton) - .addGap(23, 23, 23)))) - ); - - pack(); - }// //GEN-END:initComponents - - private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed - dispose(); - }//GEN-LAST:event_closeButtonActionPerformed - - private void durationTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_durationTextFieldActionPerformed - // TODO add your handling code here: - }//GEN-LAST:event_durationTextFieldActionPerformed - - private void printMapButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_printMapButtonActionPerformed - /*try { - ServicesHealthMonitor.getInstance().printCurrentState(); - } catch (HealthMonitorException ex){ - ex.printStackTrace(); - }*/ - }//GEN-LAST:event_printMapButtonActionPerformed - - private void submitMetricButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_submitMetricButtonActionPerformed - - if(nameTextField.getText().isEmpty() || durationTextField.getText().isEmpty()) { - return; - } - TimingMetric m = EnterpriseHealthMonitor.getTimingMetric(nameTextField.getText()); - try { - Thread.sleep(Long.parseLong(durationTextField.getText())); - } catch (Exception ex) { - ex.printStackTrace(); - } - EnterpriseHealthMonitor.submitTimingMetric(m); - }//GEN-LAST:event_submitMetricButtonActionPerformed - - private void enabledCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_enabledCheckBoxActionPerformed - try { - EnterpriseHealthMonitor.setEnabled(enabledCheckBox.isSelected()); - } catch (HealthMonitorException ex) { - ex.printStackTrace(); - } - }//GEN-LAST:event_enabledCheckBoxActionPerformed - - private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed - /*try { - ServicesHealthMonitor.getInstance().writeCurrentStateToDatabase(); - } catch (HealthMonitorException ex){ - ex.printStackTrace(); - }*/ - }//GEN-LAST:event_jButton1ActionPerformed - - private void deleteButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteButtonActionPerformed - /*try { - ServicesHealthMonitor.getInstance().deleteDatabase(); - } catch (HealthMonitorException ex){ - ex.printStackTrace(); - }*/ - }//GEN-LAST:event_deleteButtonActionPerformed - - private void updateUIButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_updateUIButtonActionPerformed - if (ModuleSettings.settingExists("ServicesHealthMonitor", "is_enabled")) { - if(ModuleSettings.getConfigSetting("ServicesHealthMonitor", "is_enabled").equals("true")){ - enabledCheckBox.setSelected(true); - return; - } - } - enabledCheckBox.setSelected(false); - }//GEN-LAST:event_updateUIButtonActionPerformed - - private void textExistButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_textExistButtonActionPerformed - /*try { - ServicesHealthMonitor.getInstance().databaseExists(); - } catch (Exception ex) { - ex.printStackTrace(); - }*/ - }//GEN-LAST:event_textExistButtonActionPerformed - - private void graphButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_graphButtonActionPerformed - - - // TEMP TEMP - - try { - Map> timingData = EnterpriseHealthMonitor.getInstance().getTimingMetricsFromDatabase(); - - String[] dateOptionStrings = {"All", "Two weeks", "One week"}; - JComboBox dateComboBox = new JComboBox(dateOptionStrings); - dateComboBox.setSelectedItem("Two weeks"); - - JPanel timingButtonPanel = new JPanel(); - timingButtonPanel.setBorder(BorderFactory.createEtchedBorder()); - //timingButtonPanel.setLayout(new FlowLayout()); - timingButtonPanel.add(new JLabel("Max days to display")); - timingButtonPanel.add(dateComboBox); - - int numberOfMetrics = timingData.keySet().size(); - System.out.println("\n### Number of metric types: " + numberOfMetrics); - - JPanel graphPanel = new JPanel(); - graphPanel.setLayout(new BoxLayout(graphPanel, BoxLayout.PAGE_AXIS)); - graphPanel.setBorder(BorderFactory.createEtchedBorder()); - graphPanel.add(timingButtonPanel); - - for(String name:timingData.keySet()) { - System.out.println(" Making panel for " + name); - JLabel label = new JLabel(name); - graphPanel.add(label); - TimingMetricGraphPanel timingGraphPanel = new TimingMetricGraphPanel(timingData.get(name), TimingMetricGraphPanel.TimingMetricType.AVERAGE, null, true); - timingGraphPanel.setPreferredSize(new Dimension(800,200)); - graphPanel.add(timingGraphPanel); - } - - graphPanel.revalidate(); - graphPanel.repaint(); - - - JScrollPane scrollPane = new JScrollPane(graphPanel); - - System.out.println("Creating dialog"); - JDialog dialog = new JDialog(); - dialog.setPreferredSize(new Dimension(1500, 800)); - dialog.setTitle("Services Health Monitor"); - //dialog.add(graphPanel); - dialog.add(scrollPane); - dialog.pack(); - dialog.setVisible(true); - System.out.println("Done displaying dialog"); - } catch (Exception ex) { - ex.printStackTrace(); - } - - - }//GEN-LAST:event_graphButtonActionPerformed - - private void nDaysTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nDaysTextFieldActionPerformed - // TODO add your handling code here: - }//GEN-LAST:event_nDaysTextFieldActionPerformed - - private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed - - if(nDaysTextField.getText().isEmpty() || nNodesTextField.getText().isEmpty()) { - System.out.println("Missing fields"); - return; - } - - try { - int nDays = Integer.valueOf(nDaysTextField.getText()); - int nNodes = Integer.valueOf(nNodesTextField.getText()); - EnterpriseHealthMonitor.getInstance().populateDatabaseWithSampleData(nDays, nNodes, false); // TEMP TEMP - } catch (Exception ex) { - ex.printStackTrace(); - } - - }//GEN-LAST:event_jButton2ActionPerformed - - private void newGraphButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newGraphButtonActionPerformed - HealthMonitorDashboard dashboard = new HealthMonitorDashboard(null); - try { - dashboard.display(); - } catch (Exception ex) { - ex.printStackTrace(); - } - }//GEN-LAST:event_newGraphButtonActionPerformed - - private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton3ActionPerformed - if(nDaysTextField.getText().isEmpty() || nNodesTextField.getText().isEmpty()) { - System.out.println("Missing fields"); - return; - } - - try { - int nDays = Integer.valueOf(nDaysTextField.getText()); - int nNodes = Integer.valueOf(nNodesTextField.getText()); - EnterpriseHealthMonitor.getInstance().populateDatabaseWithSampleData(nDays, nNodes, true); // TEMP TEMP - } catch (Exception ex) { - ex.printStackTrace(); - } - }//GEN-LAST:event_jButton3ActionPerformed - - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton closeButton; - private javax.swing.JButton deleteButton; - private javax.swing.JTextField durationTextField; - private javax.swing.JCheckBox enabledCheckBox; - private javax.swing.JButton graphButton; - private javax.swing.JButton jButton1; - private javax.swing.JButton jButton2; - private javax.swing.JButton jButton3; - private javax.swing.JLabel jLabel1; - private javax.swing.JLabel jLabel2; - private javax.swing.JLabel jLabel3; - private javax.swing.JLabel jLabel4; - private javax.swing.JLabel jLabel5; - private javax.swing.JTextField nDaysTextField; - private javax.swing.JTextField nNodesTextField; - private javax.swing.JTextField nameTextField; - private javax.swing.JButton newGraphButton; - private javax.swing.JButton printMapButton; - private javax.swing.JButton submitMetricButton; - private javax.swing.JButton textExistButton; - private javax.swing.JButton updateUIButton; - // End of variables declaration//GEN-END:variables -} From c30120f437dc17285bfc003a6dbb80ae929b00e1 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 27 Apr 2018 11:46:47 -0400 Subject: [PATCH 13/63] Fixing disabled panel. cleanup. --- .../EnterpriseHealthMonitor.java | 11 +--- .../healthmonitor/HealthMonitorDashboard.java | 60 +++++++++---------- .../healthmonitor/TimingMetricGraphPanel.java | 23 ++++--- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index 0b369cf91c..b28958a3a8 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -28,7 +28,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Map; -import java.util.List; import java.util.HashMap; import java.util.List; import java.util.ArrayList; @@ -49,7 +48,6 @@ 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; @@ -69,7 +67,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { private final static Logger logger = Logger.getLogger(EnterpriseHealthMonitor.class.getName()); private final static String DATABASE_NAME = "EnterpriseHealthMonitor"; - private final static long DATABASE_WRITE_INTERVAL = 1; // Minutes TODO - put back to an hour + private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 0); @@ -157,9 +155,6 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { initializeDatabaseSchema(); } - // Set the enabled status in the database to true - setGlobalEnabledStatusInDB(true); - } catch (CoordinationService.CoordinationServiceException ex) { throw new HealthMonitorException("Error releasing database lock", ex); } @@ -674,7 +669,6 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { try (Connection conn = connect(); Statement statement = conn.createStatement();) { - //statement.execute("INSERT INTO db_info (name, value) VALUES ('MONITOR_ENABLED', '" + status + "')"); statement.execute("UPDATE db_info SET value='" + status + "' WHERE name='MONITOR_ENABLED'"); } catch (SQLException ex) { throw new HealthMonitorException("Error setting enabled status", ex); @@ -771,6 +765,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { 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() + "')"); + statement.execute("INSERT INTO db_info (name, value) VALUES ('MONITOR_ENABLED', 'true')"); conn.commit(); } catch (SQLException ex) { @@ -930,7 +925,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } if(node == 1) { - aveTime = (long)((double)aveTime * slowNodeMultiplier); + aveTime = aveTime * slowNodeMultiplier; } } else { // Create a data set strictly for testing that the display is working diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java index 1dec3c60b3..6c6ea9f825 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java @@ -42,6 +42,7 @@ import javax.swing.JScrollPane; import javax.swing.BorderFactory; import java.util.Map; import javax.swing.BoxLayout; +import java.awt.GridLayout; import java.util.logging.Level; import java.util.stream.Collectors; import org.openide.modules.Places; @@ -61,8 +62,8 @@ public class HealthMonitorDashboard { Map> timingData; - private JComboBox dateComboBox = null; - private JComboBox hostComboBox = null; + private JComboBox dateComboBox = null; + private JComboBox hostComboBox = null; private JCheckBox hostCheckBox = null; private JPanel graphPanel = null; private JDialog dialog = null; @@ -101,11 +102,10 @@ public class HealthMonitorDashboard { // Create the main panel for the dialog JPanel mainPanel = new JPanel(); - mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS)); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); - // Put the timing data in a scroll pane and then add to the main panel - JScrollPane scrollPane = new JScrollPane(timingPanel); - mainPanel.add(scrollPane); + // Add the timing panel + mainPanel.add(timingPanel); // Add the admin panel if the admin file is present File adminFile = new File(ADMIN_ACCESS_FILE_PATH); @@ -157,26 +157,28 @@ public class HealthMonitorDashboard { @NbBundle.Messages({"HealthMonitorDashboard.createTimingPanel.noData=No data to display - monitor is not enabled", "HealthMonitorDashboard.createTimingPanel.timingMetricsTitle=Timing Metrics"}) private JPanel createTimingPanel() throws HealthMonitorException { + + // If the monitor isn't enabled, just add a message + if(! EnterpriseHealthMonitor.monitorIsEnabled()) { + //timingMetricPanel.setPreferredSize(new Dimension(400,100)); + JPanel emptyTimingMetricPanel = new JPanel(); + emptyTimingMetricPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createTimingPanel_timingMetricsTitle())); + emptyTimingMetricPanel.add(new JLabel(" ")); + emptyTimingMetricPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createTimingPanel_noData())); + + //timingMetricPanel.revalidate(); + //timingMetricPanel.repaint(); + return emptyTimingMetricPanel; + } + JPanel timingMetricPanel = new JPanel(); timingMetricPanel.setLayout(new BoxLayout(timingMetricPanel, BoxLayout.PAGE_AXIS)); - timingMetricPanel.setAlignmentX(Component.LEFT_ALIGNMENT); timingMetricPanel.setBorder(BorderFactory.createEtchedBorder()); // Add title JLabel timingMetricTitle = new JLabel(Bundle.HealthMonitorDashboard_createTimingPanel_timingMetricsTitle()); timingMetricPanel.add(timingMetricTitle); - timingMetricPanel.add(new JSeparator()); - - // If the monitor isn't enabled, just add a message - if(! EnterpriseHealthMonitor.monitorIsEnabled()) { - timingMetricPanel.setPreferredSize(new Dimension(400,100)); - timingMetricPanel.add(new JLabel("")); - timingMetricPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createTimingPanel_noData())); - - timingMetricPanel.revalidate(); - timingMetricPanel.repaint(); - return timingMetricPanel; - } + timingMetricPanel.add(new JSeparator()); // Add the controls timingMetricPanel.add(createTimingControlPanel()); @@ -184,11 +186,12 @@ public class HealthMonitorDashboard { // Create panel to hold graphs graphPanel = new JPanel(); - graphPanel.setLayout(new BoxLayout(graphPanel, BoxLayout.PAGE_AXIS)); + graphPanel.setLayout(new GridLayout(0,2)); - // Update the graph panel and add to the timing metric panel + // Update the graph panel, put it in a scroll pane, and add to the timing metric panel updateTimingMetricGraphs(); - timingMetricPanel.add(graphPanel); + JScrollPane scrollPane = new JScrollPane(graphPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + timingMetricPanel.add(scrollPane); timingMetricPanel.revalidate(); timingMetricPanel.repaint(); @@ -203,7 +206,6 @@ public class HealthMonitorDashboard { "HealthMonitorDashboard.createTimingControlPanel.maxDays=Max days to display"}) private JPanel createTimingControlPanel() { JPanel timingControlPanel = new JPanel(); - //timingControlPanel.setBorder(BorderFactory.createEtchedBorder()); // If the monitor is not enabled, don't add any components if(! EnterpriseHealthMonitor.monitorIsEnabled()) { @@ -212,7 +214,7 @@ public class HealthMonitorDashboard { // 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 = new JComboBox<>(dateOptionStrings); dateComboBox.setSelectedItem(DateRange.TWO_WEEKS.getLabel()); // Set up the listener on the date combo box @@ -236,7 +238,7 @@ public class HealthMonitorDashboard { } // Load the host names into the combo box - hostComboBox = new JComboBox(hostNameSet.toArray(new String[hostNameSet.size()])); + hostComboBox = new JComboBox<>(hostNameSet.toArray(new String[hostNameSet.size()])); // Set up the listener on the combo box hostComboBox.addActionListener(new ActionListener() { @@ -319,12 +321,10 @@ public class HealthMonitorDashboard { // Generate the graph TimingMetricGraphPanel singleTimingGraphPanel = new TimingMetricGraphPanel(intermediateTimingDataForDisplay, - TimingMetricGraphPanel.TimingMetricType.AVERAGE, hostToDisplay, true); + TimingMetricGraphPanel.TimingMetricType.AVERAGE, hostToDisplay, true, name); + //singleTimingGraphPanel.setBorder(BorderFactory.createEtchedBorder()); + singleTimingGraphPanel.setPreferredSize(new Dimension(700,200)); - // Add the metric name and the graph to the panel - JLabel metricNameLabel = new JLabel(name); - graphPanel.add(metricNameLabel); - singleTimingGraphPanel.setPreferredSize(new Dimension(900,250)); graphPanel.add(singleTimingGraphPanel); } graphPanel.revalidate(); diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index 88eb47d437..c03596a2ef 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -59,20 +59,23 @@ class TimingMetricGraphPanel extends JPanel { private int numberYDivisions = 10; private List timingResults; private TimingMetricType timingMetricType; + private String metricName; private boolean doLineGraph; private String yUnitString; private TrendLine trendLine; private final long MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24; private final long NANOSECONDS_PER_MILLISECOND = 1000 * 1000; - long maxTimestamp; - long minTimestamp; - double maxMetricTime; - double minMetricTime; + private long maxTimestamp; + private long minTimestamp; + private double maxMetricTime; + private double minMetricTime; - TimingMetricGraphPanel(List timingResultsFull, TimingMetricType timingMetricType, String hostName, boolean doLineGraph) { + TimingMetricGraphPanel(List timingResultsFull, TimingMetricType timingMetricType, + String hostName, boolean doLineGraph, String metricName) { this.timingMetricType = timingMetricType; this.doLineGraph = doLineGraph; + this.metricName = metricName; if(hostName == null || hostName.isEmpty()) { timingResults = timingResultsFull; } else { @@ -180,7 +183,7 @@ class TimingMetricGraphPanel extends JPanel { "TimingMetricGraphPanel.paintComponent.seconds=seconds", "TimingMetricGraphPanel.paintComponent.minutes=minutes", "TimingMetricGraphPanel.paintComponent.hours=hours", - "TimingMetricGraphPanel.paintComponent.displayingTime=Displaying time in "}) + "TimingMetricGraphPanel.paintComponent.displayingTime=displaying time in "}) @Override protected void paintComponent(Graphics g) { super.paintComponent(g); @@ -207,7 +210,8 @@ class TimingMetricGraphPanel extends JPanel { int leftGraphPadding = padding + labelPadding; int rightGraphPadding = padding; int topGraphPadding = padding + g2.getFontMetrics().getHeight(); - int bottomGraphPadding = padding + labelPadding; + //int bottomGraphPadding = padding + labelPadding; + int bottomGraphPadding = labelPadding; // Calculate the scale for each axis. // The size of the graph area is the width/height of the panel minus any padding. @@ -248,7 +252,7 @@ class TimingMetricGraphPanel extends JPanel { yUnitString = Bundle.TimingMetricGraphPanel_paintComponent_hours(); yLabelScale = 1.0 / (TimeUnit.HOURS.toMillis(1)); } - + // Draw white background g2.setColor(Color.WHITE); g2.fillRect(leftGraphPadding, topGraphPadding, graphWidth, graphHeight); @@ -280,7 +284,8 @@ class TimingMetricGraphPanel extends JPanel { // Write the scale g2.setColor(Color.BLACK); String scaleStr = Bundle.TimingMetricGraphPanel_paintComponent_displayingTime() + yUnitString; - g2.drawString(scaleStr, x0 - labelWidth - 5, padding); + String titleStr = metricName + " - " + scaleStr; + g2.drawString(titleStr, x0 - labelWidth - 5, padding); } } From e3dc9f2f55977a5fc4ee4eede7ce10d83d2d72e3 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 27 Apr 2018 14:00:50 -0400 Subject: [PATCH 14/63] Add message if there is no timing data. Improve some try with resources blocks. --- .../EnterpriseHealthMonitor.java | 33 ++++++------------- .../healthmonitor/HealthMonitorDashboard.java | 25 ++++++++------ .../healthmonitor/TimingMetricGraphPanel.java | 9 +++-- 3 files changed, 29 insertions(+), 38 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index b28958a3a8..2280bf3b7e 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -983,23 +983,21 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { throw new HealthMonitorException("Health Monitor is not enabled"); } - CoordinationService.Lock lock = getSharedDbLock(); - if(lock == null) { - throw new HealthMonitorException("Error getting database lock"); - } - - try{ + 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"); } - ResultSet resultSet = null; Map> resultMap = new HashMap<>(); - try (Statement statement = conn.createStatement()) { - - resultSet = statement.executeQuery("SELECT * FROM timing_data"); + try (Statement statement = conn.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT * FROM timing_data")) { + while (resultSet.next()) { String name = resultSet.getString("name"); DatabaseTimingResult timingResult = new DatabaseTimingResult(resultSet); @@ -1016,25 +1014,14 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } catch (SQLException ex) { throw new HealthMonitorException("Error reading timing metrics from database", ex); } finally { - if(resultSet != null) { - try { - resultSet.close(); - } catch (SQLException ex) { - logger.log(Level.SEVERE, "Error closing result set", ex); - } - } try { conn.close(); } catch (SQLException ex) { 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 getting database lock", ex); } } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java index 6c6ea9f825..706680b591 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.healthmonitor; -import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; @@ -166,8 +165,6 @@ public class HealthMonitorDashboard { emptyTimingMetricPanel.add(new JLabel(" ")); emptyTimingMetricPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createTimingPanel_noData())); - //timingMetricPanel.revalidate(); - //timingMetricPanel.repaint(); return emptyTimingMetricPanel; } @@ -290,12 +287,19 @@ public class HealthMonitorDashboard { * Update the timing graphs. * @throws HealthMonitorException */ + @NbBundle.Messages({"HealthMonitorDashboard.updateTimingMetricGraphs.noData=No data to display"}) private void updateTimingMetricGraphs() throws HealthMonitorException { // Clear out any old graphs graphPanel.removeAll(); - for(String name:timingData.keySet()) { + if(timingData.keySet().isEmpty()) { + // There are no timing metrics in the database + graphPanel.add(new JLabel(Bundle.HealthMonitorDashboard_updateTimingMetricGraphs_noData())); + return; + } + + for(String metricName:timingData.keySet()) { // If necessary, trim down the list of results to fit the selected time range List intermediateTimingDataForDisplay; @@ -303,17 +307,19 @@ public class HealthMonitorDashboard { DateRange selectedDateRange = DateRange.fromLabel(dateComboBox.getSelectedItem().toString()); if(selectedDateRange != DateRange.ALL) { long threshold = System.currentTimeMillis() - selectedDateRange.getTimestampRange(); - intermediateTimingDataForDisplay = timingData.get(name).stream() + intermediateTimingDataForDisplay = timingData.get(metricName).stream() .filter(t -> t.getTimestamp() > threshold) .collect(Collectors.toList()); } else { - intermediateTimingDataForDisplay = timingData.get(name); + intermediateTimingDataForDisplay = timingData.get(metricName); } } else { - intermediateTimingDataForDisplay = timingData.get(name); + intermediateTimingDataForDisplay = timingData.get(metricName); } - // Get the name of the selected host, if there is one + // Get the name of the selected host, if there is one. + // The graph always uses the data from all hosts to generate the x and y scales + // so we don't filter anything out here. String hostToDisplay = null; if(hostCheckBox.isSelected() && (hostComboBox.getSelectedItem() != null)) { hostToDisplay = hostComboBox.getSelectedItem().toString(); @@ -321,8 +327,7 @@ public class HealthMonitorDashboard { // Generate the graph TimingMetricGraphPanel singleTimingGraphPanel = new TimingMetricGraphPanel(intermediateTimingDataForDisplay, - TimingMetricGraphPanel.TimingMetricType.AVERAGE, hostToDisplay, true, name); - //singleTimingGraphPanel.setBorder(BorderFactory.createEtchedBorder()); + TimingMetricGraphPanel.TimingMetricType.AVERAGE, hostToDisplay, true, metricName); singleTimingGraphPanel.setPreferredSize(new Dimension(700,200)); graphPanel.add(singleTimingGraphPanel); diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index c03596a2ef..0e0b63b7a5 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -203,14 +203,13 @@ class TimingMetricGraphPanel extends JPanel { maxValueOnYAxis = maxValueOnYAxis * 1.1; // The graph itself has the following corners: - // (padding + label padding, padding + font height) - top left - // (padding + label padding, getHeight() - label padding - padding x 2) - bottom left - // (getWidth() - padding, padding + font height) - top right - // (padding + label padding, getHeight() - label padding - padding x 2) - bottom right + // (padding + label padding, padding + font height) -> top left + // (padding + label padding, getHeight() - label padding - padding) -> bottom left + // (getWidth() - padding, padding + font height) -> top right + // (padding + label padding, getHeight() - label padding - padding) -> bottom right int leftGraphPadding = padding + labelPadding; int rightGraphPadding = padding; int topGraphPadding = padding + g2.getFontMetrics().getHeight(); - //int bottomGraphPadding = padding + labelPadding; int bottomGraphPadding = labelPadding; // Calculate the scale for each axis. From c1ecc347734df4235ed53a28b7008da7d702e8fe Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 27 Apr 2018 14:39:34 -0400 Subject: [PATCH 15/63] Added code to check for changed multiuser settings. --- .../healthmonitor/EnterpriseHealthMonitor.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index 2280bf3b7e..687b0a6fe5 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -81,6 +81,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { private final Map timingInfoMap; private static final int CONN_POOL_SIZE = 10; private BasicDataSource connectionPool = null; + private CaseDbConnectionInfo connectionSettingsInUse = null; private String hostName; private EnterpriseHealthMonitor() throws HealthMonitorException { @@ -490,6 +491,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { private void setupConnectionPool() throws HealthMonitorException { try { CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); + connectionSettingsInUse = db; connectionPool = new BasicDataSource(); connectionPool.setDriverClassName("org.postgresql.Driver"); @@ -626,6 +628,22 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { return; } + // If we're currently enabled, check whether the multiuser settings have changed. + // If they have, force a reset on the connection pool. + if(previouslyEnabled && (connectionSettingsInUse != null)) { + try { + CaseDbConnectionInfo currentSettings = UserPreferences.getDatabaseConnectionInfo(); + if(! (connectionSettingsInUse.getUserName().equals(currentSettings.getUserName()) + && connectionSettingsInUse.getPassword().equals(currentSettings.getPassword()) + && connectionSettingsInUse.getPort().equals(currentSettings.getPort()) + && connectionSettingsInUse.getHost().equals(currentSettings.getHost()) )) { + shutdownConnections(); + } + } catch (UserPreferencesException ex) { + throw new HealthMonitorException("Error reading database connection info", ex); + } + } + boolean currentlyEnabled = getGlobalEnabledStatusFromDB(); if( currentlyEnabled == previouslyEnabled) { // Nothing needs to be done From 2ff7ccc62bd472379b1d6ef3abdd9fa7dcdb1a46 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 30 Apr 2018 15:58:38 -0400 Subject: [PATCH 16/63] Don't plot lines above the top of the graph --- .../healthmonitor/TimingMetricGraphPanel.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index 0e0b63b7a5..0f39a553de 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -165,6 +165,8 @@ class TimingMetricGraphPanel extends JPanel { minTimestamp = Math.min(minTimestamp, score.getTimestamp()); } } + + /** * Setup of the graphics panel: @@ -258,6 +260,7 @@ class TimingMetricGraphPanel extends JPanel { // Create hatch marks and grid lines for y axis. int labelWidth; + int positionForMetricNameLabel = 0; for (int i = 0; i < numberYDivisions + 1; i++) { int x0 = leftGraphPadding; int x1 = pointWidth + leftGraphPadding; @@ -278,13 +281,9 @@ class TimingMetricGraphPanel extends JPanel { g2.drawString(yLabel, x0 - labelWidth - 5, y0 + (fontMetrics.getHeight() / 2) - 3); // The nicest looking alignment for this label seems to be left-aligned with the top - // y-axis label + // y-axis label. Save this position to be used to write the label later. if (i == numberYDivisions) { - // Write the scale - g2.setColor(Color.BLACK); - String scaleStr = Bundle.TimingMetricGraphPanel_paintComponent_displayingTime() + yUnitString; - String titleStr = metricName + " - " + scaleStr; - g2.drawString(titleStr, x0 - labelWidth - 5, padding); + positionForMetricNameLabel = x0 - labelWidth - 5; } } @@ -470,6 +469,17 @@ class TimingMetricGraphPanel extends JPanel { g2.setColor(trendLineColor); g2.drawLine(x0, y0, x1, y1); } + + // The graph lines may have extended up past the bounds of the graph. Overwrite that + // area with the original background color. + g2.setColor(this.getBackground()); + g2.fillRect(leftGraphPadding, 0, graphWidth, topGraphPadding); + + // Write the scale. Do this after we erase the top block of the graph. + g2.setColor(Color.BLACK); + String scaleStr = Bundle.TimingMetricGraphPanel_paintComponent_displayingTime() + yUnitString; + String titleStr = metricName + " - " + scaleStr; + g2.drawString(titleStr, positionForMetricNameLabel, padding); } /** From fd025de1553320faf1f358ca623565c6109688aa Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 1 May 2018 08:22:08 -0400 Subject: [PATCH 17/63] Add metrics to correlation engine --- .../centralrepository/datamodel/AbstractSqlEamDb.java | 6 ++++++ .../centralrepository/ingestmodule/IngestModule.java | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index 37cb78a51e..d8f19be997 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -39,6 +39,8 @@ import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil.updateSchemaVersion; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; import org.sleuthkit.datamodel.TskData; @@ -934,6 +936,8 @@ public abstract class AbstractSqlEamDb implements EamDb { if (bulkArtifactsCount == 0) { return; } + + TimingMetric timingMetric = EnterpriseHealthMonitor.getTimingMetric("Correlation Engine: Bulk insert"); for (CorrelationAttribute.Type type : artifactTypes) { @@ -984,6 +988,8 @@ public abstract class AbstractSqlEamDb implements EamDb { bulkPs.executeBatch(); bulkArtifacts.get(type.getDbTableName()).clear(); } + + EnterpriseHealthMonitor.submitTimingMetric(timingMetric); // Reset state bulkArtifactsCount = 0; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java index 991da1ad58..3de7e25e78 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java @@ -49,6 +49,8 @@ import org.sleuthkit.datamodel.HashUtility; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.eventlisteners.IngestEventsListener; +import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.TimingMetric; /** * Ingest module for inserting entries into the Central Repository database on @@ -129,7 +131,9 @@ final class IngestModule implements FileIngestModule { */ if (abstractFile.getKnown() != TskData.FileKnown.KNOWN && flagTaggedNotableItems) { try { + TimingMetric timingMetric = EnterpriseHealthMonitor.getTimingMetric("Correlation Engine: Notable artifact query"); List caseDisplayNamesList = dbManager.getListCasesHavingArtifactInstancesKnownBad(filesType, md5); + EnterpriseHealthMonitor.submitTimingMetric(timingMetric); if (!caseDisplayNamesList.isEmpty()) { postCorrelatedBadFileToBlackboard(abstractFile, caseDisplayNamesList); } From f0c017f35adc1ee6e7dce7159e4956a561e1003a Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 1 May 2018 12:24:10 -0400 Subject: [PATCH 18/63] Added checkboxes for showing less data and hiding the trend line. Removed code to graph the min and max values since it's unlikely to be used. --- .../healthmonitor/HealthMonitorDashboard.java | 54 +++++- .../healthmonitor/TimingMetricGraphPanel.java | 159 +++++------------- 2 files changed, 94 insertions(+), 119 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java index 706680b591..292a38eae8 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java @@ -64,6 +64,8 @@ public class HealthMonitorDashboard { private JComboBox dateComboBox = null; private JComboBox hostComboBox = null; private JCheckBox hostCheckBox = null; + private JCheckBox showTrendLineCheckBox = null; + private JCheckBox skipOutliersCheckBox = null; private JPanel graphPanel = null; private JDialog dialog = null; private final Container parentWindow; @@ -200,7 +202,9 @@ public class HealthMonitorDashboard { * @return the control panel */ @NbBundle.Messages({"HealthMonitorDashboard.createTimingControlPanel.filterByHost=Filter by host", - "HealthMonitorDashboard.createTimingControlPanel.maxDays=Max days to display"}) + "HealthMonitorDashboard.createTimingControlPanel.maxDays=Max days to display", + "HealthMonitorDashboard.createTimingControlPanel.skipOutliers=Do not plot outliers", + "HealthMonitorDashboard.createTimingControlPanel.showTrendLine=Show trend line"}) private JPanel createTimingControlPanel() { JPanel timingControlPanel = new JPanel(); @@ -251,7 +255,7 @@ public class HealthMonitorDashboard { } }); - // Create the checkbox + // Create the host checkbox hostCheckBox = new JCheckBox(Bundle.HealthMonitorDashboard_createTimingControlPanel_filterByHost()); hostCheckBox.setSelected(false); hostComboBox.setEnabled(false); @@ -269,6 +273,38 @@ public class HealthMonitorDashboard { } }); + // Create the checkbox for showing the trend line + showTrendLineCheckBox = new JCheckBox(Bundle.HealthMonitorDashboard_createTimingControlPanel_showTrendLine()); + showTrendLineCheckBox.setSelected(true); + + // Set up the listener on the checkbox + showTrendLineCheckBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + try { + updateTimingMetricGraphs(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error populating timing metric panel", ex); + } + } + }); + + // Create the checkbox for omitting outliers + skipOutliersCheckBox = new JCheckBox(Bundle.HealthMonitorDashboard_createTimingControlPanel_skipOutliers()); + skipOutliersCheckBox.setSelected(false); + + // Set up the listener on the checkbox + skipOutliersCheckBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + try { + updateTimingMetricGraphs(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error populating timing metric panel", ex); + } + } + }); + // Add the date range combo box and label to the panel timingControlPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createTimingControlPanel_maxDays())); timingControlPanel.add(dateComboBox); @@ -280,6 +316,18 @@ public class HealthMonitorDashboard { timingControlPanel.add(hostCheckBox); timingControlPanel.add(hostComboBox); + // Put some space between the elements + timingControlPanel.add(Box.createHorizontalStrut(100)); + + // Add the skip outliers checkbox + timingControlPanel.add(this.showTrendLineCheckBox); + + // Put some space between the elements + timingControlPanel.add(Box.createHorizontalStrut(100)); + + // Add the skip outliers checkbox + timingControlPanel.add(this.skipOutliersCheckBox); + return timingControlPanel; } @@ -327,7 +375,7 @@ public class HealthMonitorDashboard { // Generate the graph TimingMetricGraphPanel singleTimingGraphPanel = new TimingMetricGraphPanel(intermediateTimingDataForDisplay, - TimingMetricGraphPanel.TimingMetricType.AVERAGE, hostToDisplay, true, metricName); + hostToDisplay, true, metricName, skipOutliersCheckBox.isSelected(), showTrendLineCheckBox.isSelected()); singleTimingGraphPanel.setPreferredSize(new Dimension(700,200)); graphPanel.add(singleTimingGraphPanel); diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index 0f39a553de..5b6083146a 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -58,9 +58,10 @@ class TimingMetricGraphPanel extends JPanel { private int pointWidth = 4; private int numberYDivisions = 10; private List timingResults; - private TimingMetricType timingMetricType; private String metricName; private boolean doLineGraph; + private boolean skipOutliers; + private boolean showTrendLine; private String yUnitString; private TrendLine trendLine; private final long MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24; @@ -70,11 +71,12 @@ class TimingMetricGraphPanel extends JPanel { private double maxMetricTime; private double minMetricTime; - TimingMetricGraphPanel(List timingResultsFull, TimingMetricType timingMetricType, - String hostName, boolean doLineGraph, String metricName) { + TimingMetricGraphPanel(List timingResultsFull, + String hostName, boolean doLineGraph, String metricName, boolean skipOutliers, boolean showTrendLine) { - this.timingMetricType = timingMetricType; this.doLineGraph = doLineGraph; + this.skipOutliers = skipOutliers; + this.showTrendLine = showTrendLine; this.metricName = metricName; if(hostName == null || hostName.isEmpty()) { timingResults = timingResultsFull; @@ -84,89 +86,48 @@ class TimingMetricGraphPanel extends JPanel { .collect(Collectors.toList()); } - try { - trendLine = new TrendLine(timingResults, timingMetricType); - } catch (HealthMonitorException ex) { - // Log it, set trendLine to null and continue on - logger.log(Level.WARNING, "Can not generate a trend line on empty data set"); - trendLine = null; + if(showTrendLine) { + try { + trendLine = new TrendLine(timingResults); + } catch (HealthMonitorException ex) { + // Log it, set trendLine to null and continue on + logger.log(Level.WARNING, "Can not generate a trend line on empty data set"); + trendLine = null; + } } // Calculate these using the full data set, to make it easier to compare the results for - // individual hosts - calcMaxTimestamp(timingResultsFull); - calcMinTimestamp(timingResultsFull); - calcMaxMetricTime(timingResultsFull); - calcMinMetricTime(timingResultsFull); - } - - /** - * Set the highest metric time for the given type - */ - private void calcMaxMetricTime(List timingResultsFull) { - // Find the highest of the values being graphed + // individual hosts. Calculate the average at the same time. maxMetricTime = Double.MIN_VALUE; - for (DatabaseTimingResult score : timingResultsFull) { - // Use only the data we're graphing to determing the max - switch (timingMetricType) { - case MAX: - maxMetricTime = Math.max(maxMetricTime, score.getMax()); - break; - case MIN: - maxMetricTime = Math.max(maxMetricTime, score.getMin()); - break; - case AVERAGE: - default: - maxMetricTime = Math.max(maxMetricTime, score.getAverage()); - break; - } - } - } - - /** - * Set the lowest metric time for the given type - */ - private void calcMinMetricTime(List timingResultsFull) { - // Find the lowest of the values being graphed minMetricTime = Double.MAX_VALUE; - for (DatabaseTimingResult result : timingResultsFull) { - // Use only the data we're graphing to determing the min - switch (timingMetricType) { - case MAX: - minMetricTime = Math.min(minMetricTime, result.getMax()); - break; - case MIN: - minMetricTime = Math.min(minMetricTime, result.getMin()); - break; - case AVERAGE: - default: - minMetricTime = Math.min(minMetricTime, result.getAverage()); - break; - } - } - } - - /** - * Set the largest timestamp in the data collection - */ - private void calcMaxTimestamp(List timingResultsFull) { maxTimestamp = Long.MIN_VALUE; - for (DatabaseTimingResult score : timingResultsFull) { - maxTimestamp = Math.max(maxTimestamp, score.getTimestamp()); - } - } - - /** - * Set the smallest timestamp in the data collection - */ - private void calcMinTimestamp(List timingResultsFull) { minTimestamp = Long.MAX_VALUE; - for (DatabaseTimingResult score : timingResultsFull) { - minTimestamp = Math.min(minTimestamp, score.getTimestamp()); + double averageMetricTime = 0.0; + for (DatabaseTimingResult result : timingResultsFull) { + + maxMetricTime = Math.max(maxMetricTime, result.getAverage()); + minMetricTime = Math.min(minMetricTime, result.getAverage()); + + maxTimestamp = Math.max(maxTimestamp, result.getTimestamp()); + minTimestamp = Math.min(minTimestamp, result.getTimestamp()); + + averageMetricTime += result.getAverage(); + } + averageMetricTime = averageMetricTime / timingResultsFull.size(); + + // If we're omitting outliers, we may use a different maxMetricTime. + // If the max time is reasonably close to the average, do nothing + if (this.skipOutliers && (maxMetricTime > (averageMetricTime * 5))) { + // Calculate the standard deviation + double intermediateValue = 0.0; + for (DatabaseTimingResult result : timingResultsFull) { + double diff = result.getAverage() - averageMetricTime; + intermediateValue += diff * diff; + } + double standardDeviation = Math.sqrt(intermediateValue / timingResultsFull.size()); + maxMetricTime = averageMetricTime + standardDeviation; } } - - /** * Setup of the graphics panel: @@ -353,20 +314,7 @@ class TimingMetricGraphPanel extends JPanel { // Create the points to plot List graphPoints = new ArrayList<>(); for (int i = 0; i < timingResults.size(); i++) { - double metricTime; - switch (timingMetricType) { - case MAX: - metricTime = timingResults.get(i).getMax(); - break; - case MIN: - metricTime = timingResults.get(i).getMin(); - break; - case AVERAGE: - default: - metricTime = timingResults.get(i).getAverage(); - break; - - } + double metricTime = timingResults.get(i).getAverage(); int x1 = (int) ((timingResults.get(i).getTimestamp() - minValueOnXAxis) * xScale + leftGraphPadding); int y1 = (int) ((maxValueOnYAxis - metricTime) * yScale + topGraphPadding); @@ -410,7 +358,7 @@ class TimingMetricGraphPanel extends JPanel { // Draw the trend line. // Don't draw anything if we don't have at least two data points. - if(trendLine != null && (timingResults.size() > 1)) { + if(showTrendLine && (trendLine != null) && (timingResults.size() > 1)) { double x0value = minValueOnXAxis; double y0value = trendLine.getExpectedValueAt(x0value); if (y0value < minValueOnYAxis) { @@ -482,15 +430,6 @@ class TimingMetricGraphPanel extends JPanel { g2.drawString(titleStr, positionForMetricNameLabel, padding); } - /** - * The metric field we want to graph - */ - enum TimingMetricType { - AVERAGE, - MAX, - MIN; - } - /** * Class to generate a linear trend line from timing metric data. * @@ -507,7 +446,7 @@ class TimingMetricGraphPanel extends JPanel { double slope; double yInt; - TrendLine(List timingResults, TimingMetricGraphPanel.TimingMetricType timingMetricType) throws HealthMonitorException { + TrendLine(List timingResults) throws HealthMonitorException { if((timingResults == null) || timingResults.isEmpty()) { throw new HealthMonitorException("Can not generate trend line for empty/null data set"); @@ -521,19 +460,7 @@ class TimingMetricGraphPanel extends JPanel { double sumXsquared = 0; for(int i = 0;i < n;i++) { double x = timingResults.get(i).getTimestamp(); - double y; - switch (timingMetricType) { - case MAX: - y = timingResults.get(i).getMax(); - break; - case MIN: - y = timingResults.get(i).getMin(); - break; - case AVERAGE: - default: - y = timingResults.get(i).getAverage(); - break; - } + double y = timingResults.get(i).getAverage(); sumX += x; sumY += y; From 62a6e4e3eb7a328e07c49fe28cd7afc77af9800a Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 2 May 2018 09:54:50 -0400 Subject: [PATCH 19/63] Add missing parens --- .../sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index 0e0b63b7a5..907be0df96 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -563,7 +563,7 @@ class TimingMetricGraphPanel extends JPanel { */ double getXGivenY(double y) throws HealthMonitorException { if (slope != 0.0) { - return (y - yInt / slope); + return ((y - yInt) / slope); } else { throw new HealthMonitorException("Attempted division by zero in trend line calculation"); } From e6e1739b2b466a05fbbc73a4f19ae6aab6bd4b40 Mon Sep 17 00:00:00 2001 From: esaunders Date: Wed, 2 May 2018 12:09:53 -0400 Subject: [PATCH 20/63] Initial version of cancel and reprocess job. --- .../autoingest/AutoIngestAdminActions.java | 99 +++++++++++++++++-- .../autoingest/AutoIngestDashboard.java | 8 ++ .../autoingest/AutoIngestJobCancelEvent.java | 32 ++++++ .../AutoIngestJobReprocessEvent.java | 32 ++++++ .../autoingest/AutoIngestJobsNode.java | 6 +- .../autoingest/AutoIngestManager.java | 45 ++++++++- .../autoingest/AutoIngestMonitor.java | 69 +++++++++++++ 7 files changed, 277 insertions(+), 14 deletions(-) create mode 100644 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobCancelEvent.java create mode 100644 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobReprocessEvent.java diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java index 617cf18e3a..e011e2b0a5 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java @@ -21,21 +21,27 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.awt.Cursor; import java.awt.EventQueue; import java.awt.event.ActionEvent; +import java.nio.file.Path; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.JOptionPane; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.AutoIngestNodeState; import org.sleuthkit.autopsy.ingest.IngestProgressSnapshotDialog; final class AutoIngestAdminActions { + private static final Logger logger = Logger.getLogger(AutoIngestAdminActions.class.getName()); + static abstract class AutoIngestNodeControlAction extends AbstractAction { private final AutoIngestNodeState nodeState; - private final Logger logger = Logger.getLogger(AutoIngestNodeControlAction.class.getName()); AutoIngestNodeControlAction(AutoIngestNodeState nodeState, String title) { super(title); @@ -165,14 +171,50 @@ final class AutoIngestAdminActions { static final class CancelJobAction extends AbstractAction { private static final long serialVersionUID = 1L; + private final AutoIngestJob job; - CancelJobAction() { + CancelJobAction(AutoIngestJob job) { super(Bundle.AutoIngestAdminActions_cancelJobAction_title()); + this.job = job; } @Override public void actionPerformed(ActionEvent e) { - //TODO JIRA-3738 + if (job == null) { + return; + } + + final AutoIngestDashboardTopComponent tc = (AutoIngestDashboardTopComponent) WindowManager.getDefault().findTopComponent(AutoIngestDashboardTopComponent.PREFERRED_ID); + if (tc == null) { + return; + } + + AutoIngestDashboard dashboard = tc.getAutoIngestDashboard(); + if (dashboard != null) { + Object[] options = { + org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.CancelJob"), + org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.DoNotCancelJob")}; + int reply = JOptionPane.showOptionDialog(dashboard.getRunningJobsPanel(), + NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.CancelJobAreYouSure"), + NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.ConfirmCancellationHeader"), + JOptionPane.DEFAULT_OPTION, + JOptionPane.WARNING_MESSAGE, + null, + options, + options[1]); + if (reply == 0) { + /* + * Call setCursor on this to ensure it appears (if there is + * time to see it). + */ + dashboard.getRunningJobsPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + dashboard.getMonitor().cancelJob(job); + EventQueue.invokeLater(() -> { + dashboard.getRunningJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot()); + dashboard.getRunningJobsPanel().setCursor(Cursor.getDefaultCursor()); + }); + } + } } @Override @@ -201,18 +243,47 @@ final class AutoIngestAdminActions { } } - @NbBundle.Messages({"AutoIngestAdminActions.reprocessJobAction.title=Reprocess Job"}) + @NbBundle.Messages({"AutoIngestAdminActions.reprocessJobAction.title=Reprocess Job", + "AutoIngestAdminActions.reprocessJobAction.error=Failed to reprocess job"}) static final class ReprocessJobAction extends AbstractAction { private static final long serialVersionUID = 1L; + private final AutoIngestJob job; - ReprocessJobAction() { + ReprocessJobAction(AutoIngestJob job) { super(Bundle.AutoIngestAdminActions_reprocessJobAction_title()); + this.job = job; } @Override public void actionPerformed(ActionEvent e) { - //TODO JIRA-3739 + if (job == null) { + return; + } + + final AutoIngestDashboardTopComponent tc = (AutoIngestDashboardTopComponent) WindowManager.getDefault().findTopComponent(AutoIngestDashboardTopComponent.PREFERRED_ID); + if (tc == null) { + return; + } + + AutoIngestDashboard dashboard = tc.getAutoIngestDashboard(); + if (dashboard != null) { + try { + /* + * Call setCursor on this to ensure it appears (if there is + * time to see it). + */ + dashboard.getCompletedJobsPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + dashboard.getMonitor().reprocessJob(job); + EventQueue.invokeLater(() -> { + dashboard.getCompletedJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot()); + dashboard.getCompletedJobsPanel().setCursor(Cursor.getDefaultCursor()); + }); + } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { + logger.log(Level.SEVERE, Bundle.AutoIngestAdminActions_reprocessJobAction_error(), ex); + MessageNotifyUtil.Message.error(Bundle.AutoIngestAdminActions_reprocessJobAction_error()); + } + } } @Override @@ -221,18 +292,28 @@ final class AutoIngestAdminActions { } } - @NbBundle.Messages({"AutoIngestAdminActions.deleteCaseAction.title=Delete Case"}) + @NbBundle.Messages({"AutoIngestAdminActions.deleteCaseAction.title=Delete Case", + "AutoIngestAdminActions.deleteCaseAction.error=Failed to delete case."}) static final class DeleteCaseAction extends AbstractAction { private static final long serialVersionUID = 1L; + private final AutoIngestJob job; - DeleteCaseAction() { + DeleteCaseAction(AutoIngestJob selectedJob) { super(Bundle.AutoIngestAdminActions_deleteCaseAction_title()); + this.job = selectedJob; } @Override public void actionPerformed(ActionEvent e) { - //TODO JIRA-3740 + try { + String caseName = job.getManifest().getCaseName(); + Path metadataFilePath = job.getCaseDirectoryPath().resolve(caseName + CaseMetadata.getFileExtension()); + Case.deleteCase(new CaseMetadata(metadataFilePath)); + } catch (CaseMetadata.CaseMetadataException | CaseActionException ex) { + logger.log(Level.SEVERE, Bundle.AutoIngestAdminActions_deleteCaseAction_error(), ex); + MessageNotifyUtil.Message.error(Bundle.AutoIngestAdminActions_deleteCaseAction_error()); + } } @Override diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 3704bf7a7e..ebe5de4b00 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -122,6 +122,14 @@ final class AutoIngestDashboard extends JPanel implements Observer { return pendingJobsPanel; } + AutoIngestJobsPanel getRunningJobsPanel() { + return runningJobsPanel; + } + + AutoIngestJobsPanel getCompletedJobsPanel() { + return completedJobsPanel; + } + /** * Update status of the services on the dashboard */ diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobCancelEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobCancelEvent.java new file mode 100644 index 0000000000..a264a202e3 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobCancelEvent.java @@ -0,0 +1,32 @@ +/* + * 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.experimental.autoingest; + +import java.io.Serializable; + +/** + * Event published to remotely cancel an AutoIngestJob. + */ +public final class AutoIngestJobCancelEvent extends AutoIngestJobEvent implements Serializable{ + private static final long serialVersionUID = 1L; + + public AutoIngestJobCancelEvent(AutoIngestJob job) { + super(AutoIngestManager.Event.CANCEL_JOB, job); + } +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobReprocessEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobReprocessEvent.java new file mode 100644 index 0000000000..817e153c7e --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobReprocessEvent.java @@ -0,0 +1,32 @@ +/* + * 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.experimental.autoingest; + +import java.io.Serializable; + +/** + * Event published to reprocess an AutoIngestJob. + */ +public final class AutoIngestJobReprocessEvent extends AutoIngestJobEvent implements Serializable{ + private static final long serialVersionUID = 1L; + + public AutoIngestJobReprocessEvent(AutoIngestJob job) { + super(AutoIngestManager.Event.REPROCESS_JOB, job); + } +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index 60f84e566c..2e229b4a10 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -202,12 +202,12 @@ final class AutoIngestJobsNode extends AbstractNode { break; case RUNNING_JOB: actions.add(new AutoIngestAdminActions.ProgressDialogAction()); - actions.add(new AutoIngestAdminActions.CancelJobAction()); + actions.add(new AutoIngestAdminActions.CancelJobAction(autoIngestJob)); actions.add(new AutoIngestAdminActions.CancelModuleAction()); break; case COMPLETED_JOB: - actions.add(new AutoIngestAdminActions.ReprocessJobAction()); - actions.add(new AutoIngestAdminActions.DeleteCaseAction()); + actions.add(new AutoIngestAdminActions.ReprocessJobAction(autoIngestJob)); + actions.add(new AutoIngestAdminActions.DeleteCaseAction(autoIngestJob)); actions.add(new AutoIngestAdminActions.ShowCaseLogAction()); break; default: diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 163fec6d4a..0dd643e0e9 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -137,7 +137,9 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen Event.REPORT_STATE.toString(), ControlEventType.PAUSE.toString(), ControlEventType.RESUME.toString(), - ControlEventType.SHUTDOWN.toString()})); + ControlEventType.SHUTDOWN.toString(), + Event.CANCEL_JOB.toString(), + Event.REPROCESS_JOB.toString()})); private static final long JOB_STATUS_EVENT_INTERVAL_SECONDS = 10; private static final String JOB_STATUS_PUBLISHING_THREAD_NAME = "AIM-job-status-event-publisher-%d"; private static final long MAX_MISSED_JOB_STATUS_UPDATES = 10; @@ -287,6 +289,10 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen handleRemoteRequestNodeStateEvent(); } else if (event instanceof AutoIngestNodeControlEvent) { handleRemoteNodeControlEvent((AutoIngestNodeControlEvent) event); + } else if (event instanceof AutoIngestJobCancelEvent) { + handleRemoteJobCancelledEvent((AutoIngestJobCancelEvent) event); + } else if (event instanceof AutoIngestJobReprocessEvent) { + handleRemoteJobReprocessEvent((AutoIngestJobReprocessEvent) event); } } } @@ -374,6 +380,38 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen notifyObservers(Event.JOB_COMPLETED); } + /** + * Processes a job cancellation request from the dashboard. + * + * @param event + */ + private void handleRemoteJobCancelledEvent(AutoIngestJobCancelEvent event) { + AutoIngestJob job = event.getJob(); + if (job != null && job.getProcessingHostName().compareToIgnoreCase(LOCAL_HOST_NAME) == 0) { + if (currentJob == event.getJob()) { + cancelCurrentJob(); + } + } + } + + /** + * Process a job reprocess event from a remote host. + * + * @param event + */ + private void handleRemoteJobReprocessEvent(AutoIngestJobReprocessEvent event) { + synchronized (jobsLock) { + AutoIngestJob job = event.getJob(); + if (completedJobs.contains(job)) { + // Remove from completed jobs table. + completedJobs.remove(job); + // Add to pending jobs table and re-sort. + pendingJobs.add(job); + Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); + } + } + } + /** * Processes a job/case prioritization event from another node by triggering * an immediate input directory scan. @@ -3111,7 +3149,10 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen RUNNING, SHUTTING_DOWN, SHUTDOWN, - REPORT_STATE + REPORT_STATE, + CANCEL_JOB, + REPROCESS_JOB, + DELETE_CASE } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index 87adb717b7..f3f427e59e 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -21,9 +21,14 @@ package org.sleuthkit.autopsy.experimental.autoingest; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.nio.file.Path; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Date; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Observable; @@ -34,6 +39,9 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.stream.Collectors; import javax.annotation.concurrent.GuardedBy; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; import org.sleuthkit.autopsy.coreutils.Logger; @@ -41,6 +49,7 @@ import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.autopsy.events.AutopsyEventException; import org.sleuthkit.autopsy.events.AutopsyEventPublisher; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus; +import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus.PENDING; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestManager.Event; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeControlEvent.ControlEventType; /** @@ -526,6 +535,66 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } } + /** + * Send an event to tell a remote node to cancel the given job. + * + * @param job + */ + void cancelJob(AutoIngestJob job) { + new Thread(() -> { + eventPublisher.publishRemotely(new AutoIngestJobCancelEvent(job)); + }).start(); + } + + /** + * Send an event to tell a remote node to reprocess the given job. + * + * @param job + */ + void reprocessJob(AutoIngestJob job) throws AutoIngestMonitorException { + + synchronized (jobsLock) { + /* + * Find the job in the completed jobs list. + */ + for (AutoIngestJob completedJob : jobsSnapshot.getCompletedJobs()) { + if (completedJob.equals(job)) { + break; + } + } + + /* + * Add the job to the pending jobs queue and update the coordination + * service manifest node data for the job. + */ + if (null != job && !job.getCaseDirectoryPath().toString().isEmpty()) { + /** + * We reset the status, completion date and processing stage but + * we keep the original priority. + */ + job.setErrorsOccurred(false); + job.setCompletedDate(new Date(0)); + job.setProcessingStatus(PENDING); + job.setProcessingStage(AutoIngestJob.Stage.PENDING, Date.from(Instant.now())); + String manifestNodePath = job.getManifest().getFilePath().toString(); + try { + AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(job); + coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath, nodeData.toArray()); + } catch (CoordinationServiceException | InterruptedException ex) { + throw new AutoIngestMonitorException("Error reprocessing job " + job.toString(), ex); + } + + /* + * Publish a reprocess event. + */ + new Thread(() -> { + eventPublisher.publishRemotely(new AutoIngestJobReprocessEvent(job)); + }).start(); + + } + } + } + /** * Send the given control event to the given node. * From 506a70e51baba7b9d9dae8e3d4eff91e0590cdfa Mon Sep 17 00:00:00 2001 From: esaunders Date: Wed, 2 May 2018 14:09:07 -0400 Subject: [PATCH 21/63] Fix job reprocessing logic. --- .../autoingest/AutoIngestAdminActions.java | 31 ++++++++++--------- .../autoingest/AutoIngestControlPanel.java | 1 + .../autoingest/AutoIngestManager.java | 2 ++ .../autoingest/AutoIngestMonitor.java | 21 ++++++++----- 4 files changed, 34 insertions(+), 21 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java index e011e2b0a5..cc2854989e 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java @@ -25,6 +25,7 @@ import java.nio.file.Path; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.JOptionPane; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; @@ -268,21 +269,23 @@ final class AutoIngestAdminActions { AutoIngestDashboard dashboard = tc.getAutoIngestDashboard(); if (dashboard != null) { - try { - /* - * Call setCursor on this to ensure it appears (if there is - * time to see it). - */ - dashboard.getCompletedJobsPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - dashboard.getMonitor().reprocessJob(job); - EventQueue.invokeLater(() -> { - dashboard.getCompletedJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot()); + /* + * Call setCursor on this to ensure it appears (if there is + * time to see it). + */ + dashboard.getCompletedJobsPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + EventQueue.invokeLater(() -> { + try { + dashboard.getMonitor().reprocessJob(job); + dashboard.refreshTables(); dashboard.getCompletedJobsPanel().setCursor(Cursor.getDefaultCursor()); - }); - } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { - logger.log(Level.SEVERE, Bundle.AutoIngestAdminActions_reprocessJobAction_error(), ex); - MessageNotifyUtil.Message.error(Bundle.AutoIngestAdminActions_reprocessJobAction_error()); - } + } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { + logger.log(Level.SEVERE, Bundle.AutoIngestAdminActions_reprocessJobAction_error(), ex); + MessageNotifyUtil.Message.error(Bundle.AutoIngestAdminActions_reprocessJobAction_error()); + } finally { + dashboard.getCompletedJobsPanel().setCursor(Cursor.getDefaultCursor()); + } + }); } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java index a71107d197..82d1873565 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java @@ -850,6 +850,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { case JOB_STARTED: case JOB_COMPLETED: case CASE_DELETED: + case REPROCESS_JOB: updateExecutor.submit(new UpdateAllJobsTablesTask()); break; case PAUSED_BY_USER_REQUEST: diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 0dd643e0e9..24e075e58f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -408,6 +408,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen // Add to pending jobs table and re-sort. pendingJobs.add(job); Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); + + notifyObservers(Event.REPROCESS_JOB); } } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index f3f427e59e..0bbfce05c4 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -52,6 +52,7 @@ import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingSta import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus.PENDING; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestManager.Event; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeControlEvent.ControlEventType; + /** * An auto ingest monitor responsible for monitoring and reporting the * processing of auto ingest jobs. @@ -262,7 +263,8 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen /** * Gets the current state of known AIN's in the system. - * @return + * + * @return */ List getNodeStates() { return nodeStates.values().stream().collect(Collectors.toList()); @@ -559,6 +561,8 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen */ for (AutoIngestJob completedJob : jobsSnapshot.getCompletedJobs()) { if (completedJob.equals(job)) { + // Remove from the completed jobs collection. + jobsSnapshot.removeCompletedJob(job); break; } } @@ -584,6 +588,9 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen throw new AutoIngestMonitorException("Error reprocessing job " + job.toString(), ex); } + // Add to pending jobs collection. + jobsSnapshot.addOrReplacePendingJob(job); + /* * Publish a reprocess event. */ @@ -660,8 +667,8 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } /** - * A snapshot of the pending jobs queue, running jobs list and completed jobs - * list for an auto ingest cluster. + * A snapshot of the pending jobs queue, running jobs list and completed + * jobs list for an auto ingest cluster. */ static final class JobsSnapshot { @@ -714,7 +721,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * Removes a job, if present, in the snapshot of the pending jobs queue * for an auto ingest cluster. * - * @param job The auot ingest job. + * @param job The auto ingest job. */ private void removePendingJob(AutoIngestJob job) { this.pendingJobs.remove(job); @@ -734,7 +741,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * Removes a job, if present, in the snapshot of the running jobs list * for an auto ingest cluster. * - * @param job The auot ingest job. + * @param job The auto ingest job. */ private void removeRunningJob(AutoIngestJob job) { this.runningJobs.remove(job); @@ -755,10 +762,10 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * Removes a job, if present, in the snapshot of the completed jobs list * for an auto ingest cluster. * - * @param job The auot ingest job. + * @param job The auto ingest job. */ private void removeCompletedJob(AutoIngestJob job) { - this.pendingJobs.remove(job); + this.completedJobs.remove(job); } /** From de5e0f81ff06f206a98372c43d305a0816ab867a Mon Sep 17 00:00:00 2001 From: esaunders Date: Mon, 7 May 2018 12:28:42 -0400 Subject: [PATCH 22/63] Don't throw an exception when deleting a core if the core is already closed. --- .../src/org/sleuthkit/autopsy/keywordsearch/Server.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 23f82c25e9..4d36cd1164 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -753,7 +753,11 @@ public class Server { org.apache.solr.client.solrj.request.CoreAdminRequest.unloadCore(coreName, true, true, solrServer); } } catch (SolrServerException | HttpSolrServer.RemoteSolrException | IOException ex) { - throw new KeywordSearchServiceException(Bundle.Server_deleteCore_exception_msg(coreName), ex); + // We will get a RemoteSolrException with cause == null and detailsMessage + // == "Already closed" if the core is not loaded. This is not an error in this scenario. + if (!ex.getMessage().equals("Already closed")) { // NON-NLS + throw new KeywordSearchServiceException(Bundle.Server_deleteCore_exception_msg(coreName), ex); + } } } From 56b917cc1dae87e3b300aa2896aa9ddaae63c2f2 Mon Sep 17 00:00:00 2001 From: esaunders Date: Mon, 7 May 2018 12:31:35 -0400 Subject: [PATCH 23/63] Fix error message. --- .../autopsy/experimental/autoingest/AutoIngestControlPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java index 82d1873565..c49e605e2d 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java @@ -1682,7 +1682,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); if (CaseDeletionResult.FAILED == result) { JOptionPane.showMessageDialog(this, - String.format("Could not delete case %s. It may be in in use.", caseName), + String.format("Could not delete case %s. It may be in use.", caseName), org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.DeletionFailed"), JOptionPane.INFORMATION_MESSAGE); } else if (CaseDeletionResult.PARTIALLY_DELETED == result) { From 8051b726edd92292643eeff4d9df9120e6e5c4cc Mon Sep 17 00:00:00 2001 From: esaunders Date: Tue, 8 May 2018 11:07:09 -0400 Subject: [PATCH 24/63] Added case deletion support to dashboard and fixed bug in reprocessJob. --- .../autoingest/AinStatusNode.java | 1 + .../autoingest/AutoIngestAdminActions.java | 54 +++++++++++--- .../autoingest/AutoIngestMonitor.java | 73 ++++++++++++++++--- 3 files changed, 106 insertions(+), 22 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusNode.java index ff4cd3c478..ef8e280d1e 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusNode.java @@ -146,6 +146,7 @@ final class AinStatusNode extends AbstractNode { List actions = new ArrayList<>(); if (AutoIngestDashboard.isAdminAutoIngestDashboard()) { if (nodeState.getState() == AutoIngestNodeState.State.PAUSED_BY_REQUEST + || nodeState.getState() == AutoIngestNodeState.State.PAUSE_REQUESTED || nodeState.getState() == AutoIngestNodeState.State.PAUSED_DUE_TO_SYSTEM_ERROR || nodeState.getState() == AutoIngestNodeState.State.RUNNING) { actions.add(new AutoIngestAdminActions.AutoIngestNodeControlAction.PauseResumeAction(nodeState)); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java index cc2854989e..ac7a9d9da9 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java @@ -21,16 +21,11 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.awt.Cursor; import java.awt.EventQueue; import java.awt.event.ActionEvent; -import java.nio.file.Path; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.JOptionPane; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.CaseActionException; -import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.AutoIngestNodeState; @@ -309,13 +304,50 @@ final class AutoIngestAdminActions { @Override public void actionPerformed(ActionEvent e) { - try { + if (job == null) { + return; + } + + final AutoIngestDashboardTopComponent tc = (AutoIngestDashboardTopComponent) WindowManager.getDefault().findTopComponent(AutoIngestDashboardTopComponent.PREFERRED_ID); + if (tc == null) { + return; + } + + AutoIngestDashboard dashboard = tc.getAutoIngestDashboard(); + if (dashboard != null) { String caseName = job.getManifest().getCaseName(); - Path metadataFilePath = job.getCaseDirectoryPath().resolve(caseName + CaseMetadata.getFileExtension()); - Case.deleteCase(new CaseMetadata(metadataFilePath)); - } catch (CaseMetadata.CaseMetadataException | CaseActionException ex) { - logger.log(Level.SEVERE, Bundle.AutoIngestAdminActions_deleteCaseAction_error(), ex); - MessageNotifyUtil.Message.error(Bundle.AutoIngestAdminActions_deleteCaseAction_error()); + + Object[] options = { + org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.Delete"), + org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.DoNotDelete") + }; + Object[] msgContent = {org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.DeleteAreYouSure") + "\"" + caseName + "\"?"}; + int reply = JOptionPane.showOptionDialog(dashboard, + msgContent, + org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.ConfirmDeletionHeader"), + JOptionPane.DEFAULT_OPTION, + JOptionPane.WARNING_MESSAGE, + null, + options, + options[JOptionPane.NO_OPTION]); + if (reply == JOptionPane.YES_OPTION) { + dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + AutoIngestManager.CaseDeletionResult result = dashboard.getMonitor().deleteCase(job); + + dashboard.getCompletedJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot()); + dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + if (AutoIngestManager.CaseDeletionResult.FAILED == result) { + JOptionPane.showMessageDialog(dashboard, + String.format("Could not delete case %s. It may be in use.", caseName), + org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.DeletionFailed"), + JOptionPane.INFORMATION_MESSAGE); + } else if (AutoIngestManager.CaseDeletionResult.PARTIALLY_DELETED == result) { + JOptionPane.showMessageDialog(dashboard, + String.format("Could not fully delete case %s. See log for details.", caseName), + org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.DeletionFailed"), + JOptionPane.INFORMATION_MESSAGE); + } + } } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index 0bbfce05c4..39b8f25f61 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -39,6 +39,7 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.stream.Collectors; import javax.annotation.concurrent.GuardedBy; +import org.openide.util.Exceptions; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseMetadata; @@ -49,7 +50,9 @@ import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.autopsy.events.AutopsyEventException; import org.sleuthkit.autopsy.events.AutopsyEventPublisher; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus; +import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus.DELETED; import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus.PENDING; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestManager.CaseDeletionResult; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestManager.Event; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeControlEvent.ControlEventType; @@ -549,24 +552,18 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } /** - * Send an event to tell a remote node to reprocess the given job. + * Reprocess the given job. * * @param job */ void reprocessJob(AutoIngestJob job) throws AutoIngestMonitorException { - synchronized (jobsLock) { - /* - * Find the job in the completed jobs list. - */ - for (AutoIngestJob completedJob : jobsSnapshot.getCompletedJobs()) { - if (completedJob.equals(job)) { - // Remove from the completed jobs collection. - jobsSnapshot.removeCompletedJob(job); - break; - } + if (!jobsSnapshot.getCompletedJobs().contains(job)) { + return; } + jobsSnapshot.removeCompletedJob(job); + /* * Add the job to the pending jobs queue and update the coordination * service manifest node data for the job. @@ -602,6 +599,60 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } } + /** + * Deletes a case. This includes deleting the case directory, the text + * index, and the case database. This does not include the directories + * containing the data sources and their manifests. + * + * @param job The job whose case you want to delete + * + * @return A result code indicating success, partial success, or failure. + */ + CaseDeletionResult deleteCase(AutoIngestJob job) { + CaseDeletionResult result = CaseDeletionResult.FULLY_DELETED; + + synchronized (jobsLock) { + String caseName = job.getManifest().getCaseName(); + Path metadataFilePath = job.getCaseDirectoryPath().resolve(caseName + CaseMetadata.getFileExtension()); + + try { + CaseMetadata metadata = new CaseMetadata(metadataFilePath); + Case.deleteCase(metadata); + + } catch (CaseMetadata.CaseMetadataException ex) { + LOGGER.log(Level.SEVERE, String.format("Failed to get case metadata file %s for case %s at %s", metadataFilePath.toString(), caseName, job.getCaseDirectoryPath().toString()), ex); + return CaseDeletionResult.FAILED; + } catch (CaseActionException ex) { + LOGGER.log(Level.SEVERE, String.format("Failed to physically delete case %s at %s", caseName, job.getCaseDirectoryPath().toString()), ex); + return CaseDeletionResult.FAILED; + } + + // Update the state of completed jobs associated with this case to indicate + // that the case has been deleted + for (AutoIngestJob completedJob : jobsSnapshot.getCompletedJobs()) { + if (caseName.equals(completedJob.getManifest().getCaseName())) { + try { + completedJob.setProcessingStatus(DELETED); + AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(completedJob); + coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, completedJob.getManifest().getFilePath().toString(), nodeData.toArray()); + } catch (CoordinationServiceException | InterruptedException ex) { + LOGGER.log(Level.SEVERE, String.format("Failed to update completed job node data for %s when deleting case %s", completedJob.getManifest().getFilePath().toString(), caseName), ex); + return CaseDeletionResult.PARTIALLY_DELETED; + } + } + } + + // Remove jobs associated with this case from the completed jobs collection. + jobsSnapshot.completedJobs.removeIf((AutoIngestJob completedJob) -> + completedJob.getManifest().getCaseName().equals(caseName)); + + // Publish a message to update auto ingest nodes. + eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME)); + } + + return result; + } + /** * Send the given control event to the given node. * From 225406905f9d908a7331ece38eae90c57876e0b1 Mon Sep 17 00:00:00 2001 From: esaunders Date: Tue, 8 May 2018 12:02:14 -0400 Subject: [PATCH 25/63] Fix equality check. --- .../autopsy/experimental/autoingest/AutoIngestManager.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 371626a6dc..9fbe4d8318 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -388,7 +388,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen private void handleRemoteJobCancelledEvent(AutoIngestJobCancelEvent event) { AutoIngestJob job = event.getJob(); if (job != null && job.getProcessingHostName().compareToIgnoreCase(LOCAL_HOST_NAME) == 0) { - if (currentJob == event.getJob()) { + if (event.getJob().equals(currentJob)) { cancelCurrentJob(); } } @@ -3147,8 +3147,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen SHUTDOWN, REPORT_STATE, CANCEL_JOB, - REPROCESS_JOB, - DELETE_CASE + REPROCESS_JOB } /** From 9cfb2ac560782aa43505e3abd99502f9609786a0 Mon Sep 17 00:00:00 2001 From: esaunders Date: Tue, 8 May 2018 16:45:37 -0400 Subject: [PATCH 26/63] Attempt to get a wait cursor and fix for case deletion events not being handled. --- .../autoingest/AutoIngestAdminActions.java | 34 ++++++++++--------- .../autoingest/AutoIngestManager.java | 2 +- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java index ac7a9d9da9..0a5e6d0ac0 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java @@ -204,8 +204,8 @@ final class AutoIngestAdminActions { * time to see it). */ dashboard.getRunningJobsPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - dashboard.getMonitor().cancelJob(job); EventQueue.invokeLater(() -> { + dashboard.getMonitor().cancelJob(job); dashboard.getRunningJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot()); dashboard.getRunningJobsPanel().setCursor(Cursor.getDefaultCursor()); }); @@ -331,22 +331,24 @@ final class AutoIngestAdminActions { options, options[JOptionPane.NO_OPTION]); if (reply == JOptionPane.YES_OPTION) { - dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - AutoIngestManager.CaseDeletionResult result = dashboard.getMonitor().deleteCase(job); + EventQueue.invokeLater(() -> { + dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + AutoIngestManager.CaseDeletionResult result = dashboard.getMonitor().deleteCase(job); - dashboard.getCompletedJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot()); - dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - if (AutoIngestManager.CaseDeletionResult.FAILED == result) { - JOptionPane.showMessageDialog(dashboard, - String.format("Could not delete case %s. It may be in use.", caseName), - org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.DeletionFailed"), - JOptionPane.INFORMATION_MESSAGE); - } else if (AutoIngestManager.CaseDeletionResult.PARTIALLY_DELETED == result) { - JOptionPane.showMessageDialog(dashboard, - String.format("Could not fully delete case %s. See log for details.", caseName), - org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.DeletionFailed"), - JOptionPane.INFORMATION_MESSAGE); - } + dashboard.getCompletedJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot()); + dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + if (AutoIngestManager.CaseDeletionResult.FAILED == result) { + JOptionPane.showMessageDialog(dashboard, + String.format("Could not delete case %s. It may be in use.", caseName), + org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.DeletionFailed"), + JOptionPane.INFORMATION_MESSAGE); + } else if (AutoIngestManager.CaseDeletionResult.PARTIALLY_DELETED == result) { + JOptionPane.showMessageDialog(dashboard, + String.format("Could not fully delete case %s. See log for details.", caseName), + org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.DeletionFailed"), + JOptionPane.INFORMATION_MESSAGE); + } + }); } } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 9fbe4d8318..8acc0f07ff 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -63,7 +63,6 @@ import org.sleuthkit.autopsy.casemodule.Case.CaseType; import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseDetails; import org.sleuthkit.autopsy.casemodule.CaseMetadata; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; @@ -133,6 +132,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen Event.JOB_STATUS_UPDATED.toString(), Event.JOB_COMPLETED.toString(), Event.CASE_PRIORITIZED.toString(), + Event.CASE_DELETED.toString(), Event.JOB_STARTED.toString(), Event.REPORT_STATE.toString(), ControlEventType.PAUSE.toString(), From 544c0524850d21073cc0d0b0add355a44385ee74 Mon Sep 17 00:00:00 2001 From: esaunders Date: Tue, 8 May 2018 17:55:51 -0400 Subject: [PATCH 27/63] Remove unused imports. --- .../autopsy/experimental/autoingest/AutoIngestMonitor.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index 39b8f25f61..b69ea10354 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -25,10 +25,8 @@ import java.nio.file.Path; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Date; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Observable; @@ -39,7 +37,6 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.stream.Collectors; import javax.annotation.concurrent.GuardedBy; -import org.openide.util.Exceptions; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseMetadata; From 4e35ec69beff97c4e171004c2160795bf7450727 Mon Sep 17 00:00:00 2001 From: esaunders Date: Tue, 8 May 2018 18:05:36 -0400 Subject: [PATCH 28/63] Lunacy fix. --- .../autopsy/experimental/autoingest/AutoIngestMonitor.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index b69ea10354..08272a27fb 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -606,8 +606,6 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * @return A result code indicating success, partial success, or failure. */ CaseDeletionResult deleteCase(AutoIngestJob job) { - CaseDeletionResult result = CaseDeletionResult.FULLY_DELETED; - synchronized (jobsLock) { String caseName = job.getManifest().getCaseName(); Path metadataFilePath = job.getCaseDirectoryPath().resolve(caseName + CaseMetadata.getFileExtension()); @@ -647,7 +645,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME)); } - return result; + return CaseDeletionResult.FULLY_DELETED; } /** From 8a8484db68a74d9aab96af37277b80131ceab4bd Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 9 May 2018 12:54:43 -0400 Subject: [PATCH 29/63] Add option to display one day of data --- .../autopsy/healthmonitor/HealthMonitorDashboard.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java index 706680b591..daa6caa588 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java @@ -212,7 +212,7 @@ public class HealthMonitorDashboard { // 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()); + dateComboBox.setSelectedItem(DateRange.ONE_DAY.getLabel()); // Set up the listener on the date combo box dateComboBox.addActionListener(new ActionListener() { @@ -401,11 +401,13 @@ public class HealthMonitorDashboard { */ @NbBundle.Messages({"HealthMonitorDashboard.DateRange.all=All", "HealthMonitorDashboard.DateRange.twoWeeks=Two weeks", - "HealthMonitorDashboard.DateRange.oneWeek=One week"}) + "HealthMonitorDashboard.DateRange.oneWeek=One week", + "HealthMonitorDashboard.DateRange.oneDay=One day"}) private enum DateRange { ALL(Bundle.HealthMonitorDashboard_DateRange_all(), 0), TWO_WEEKS(Bundle.HealthMonitorDashboard_DateRange_twoWeeks(), 14), - ONE_WEEK(Bundle.HealthMonitorDashboard_DateRange_oneWeek(), 7); + ONE_WEEK(Bundle.HealthMonitorDashboard_DateRange_oneWeek(), 7), + ONE_DAY(Bundle.HealthMonitorDashboard_DateRange_oneDay(), 1); private final String label; private final long numberOfDays; From aed9b69538b96781af79e46c91a0b9c491c3c305 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Wed, 9 May 2018 13:23:09 -0400 Subject: [PATCH 30/63] SQLCipher detection functionality and test finished. --- Core/build.xml | 1 + .../EncryptionDetectionFileIngestModule.java | 58 +++++++--- .../EncryptionDetectionTest.java | 108 ++++++++++++++---- .../autopsy/testutils/CaseUtils.java | 62 +++++----- .../autopsy/testutils/IngestUtils.java | 65 ++++++----- 5 files changed, 207 insertions(+), 87 deletions(-) diff --git a/Core/build.xml b/Core/build.xml index 25215d493f..8c57de11c4 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -84,6 +84,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java index fa7961db8b..9784f5b06a 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java @@ -37,6 +37,7 @@ import org.apache.tika.metadata.Metadata; import org.apache.tika.parser.AutoDetectParser; import org.apache.tika.parser.ParseContext; import org.apache.tika.sax.BodyContentHandler; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.Blackboard; @@ -50,21 +51,23 @@ import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; - - /** * File ingest module to detect encryption and password protection. */ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter { private static final int FILE_SIZE_MODULUS = 512; - + + private static final String DATABASE_FILE_EXTENSION = "db"; + private static final int MINIMUM_DATABASE_FILE_SIZE = 65536; //64 KB + private static final String MIME_TYPE_OOXML_PROTECTED = "application/x-ooxml-protected"; private static final String MIME_TYPE_MSWORD = "application/msword"; private static final String MIME_TYPE_MSEXCEL = "application/vnd.ms-excel"; @@ -110,6 +113,11 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter } } + @Messages({ + "EncryptionDetectionFileIngestModule.artifactComment.password=Password protection detected.", + "EncryptionDetectionFileIngestModule.artifactComment.sqlCipher=Suspected SQLCipher encryption due to high entropy (%f).", + "EncryptionDetectionFileIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f)." + }) @Override public IngestModule.ProcessResult process(AbstractFile file) { @@ -132,11 +140,17 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter String mimeType = fileTypeDetector.getMIMEType(file); if (mimeType.equals("application/octet-stream")) { if (isFileEncryptionSuspected(file)) { - return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED); + String comment; + if (file.getNameExtension().equalsIgnoreCase(DATABASE_FILE_EXTENSION)) { + comment = Bundle.EncryptionDetectionFileIngestModule_artifactComment_sqlCipher(); + } else { + comment = Bundle.EncryptionDetectionFileIngestModule_artifactComment_suspected(); + } + return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, String.format(comment, calculatedEntropy)); } } else { if (isFilePasswordProtected(file)) { - return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED); + return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, Bundle.EncryptionDetectionFileIngestModule_artifactComment_password()); } } } @@ -168,13 +182,17 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter * * @param file The file to be processed. * @param artifactType The type of artifact to create. + * @param comment A comment to be attached to the artifact. * * @return 'OK' if the file was processed successfully, or 'ERROR' if there * was a problem. */ - private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType) { + private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType, String comment) { try { BlackboardArtifact artifact = file.newArtifact(artifactType); + artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, + EncryptionDetectionModuleFactory.getModuleName(), comment) + ); try { /* @@ -352,18 +370,30 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter /* * Qualify the size. */ + boolean fileSizeQualified = false; + String fileExtension = file.getNameExtension(); long contentSize = file.getSize(); - if (contentSize >= minimumFileSize) { + // Database files qualify at 64 KB minimum for SQLCipher detection. + if (fileExtension.equalsIgnoreCase(DATABASE_FILE_EXTENSION)) { + if (contentSize >= MINIMUM_DATABASE_FILE_SIZE) { + fileSizeQualified = true; + } + } else if (contentSize >= minimumFileSize) { if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) { - /* - * Qualify the entropy. - */ - calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file); - if (calculatedEntropy >= minimumEntropy) { - possiblyEncrypted = true; - } + fileSizeQualified = true; } } + + if (fileSizeQualified) { + /* + * Qualify the entropy. + */ + calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file); + if (calculatedEntropy >= minimumEntropy) { + possiblyEncrypted = true; + } + } + return possiblyEncrypted; } } diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java index 41844fb62f..fad8a81df4 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java @@ -45,9 +45,11 @@ import org.sleuthkit.datamodel.TskData; public class EncryptionDetectionTest extends NbTestCase { - private static final String CASE_NAME = "EncryptionDetectionTest"; - private static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), CASE_NAME); - private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(), "password_detection_test.img"); + private static final String PASSWORD_DETECTION_CASE_NAME = "PasswordDetectionTest"; + private static final String SQLCIPHER_DETECTION_CASE_NAME = "SQLCipherDetectionTest"; + + private final Path PASSWORD_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "password_detection_test.img"); + private final Path SQLCIPHER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "encryption_detection_sqlcipher_test.vhd"); public static Test suite() { NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(EncryptionDetectionTest.class). @@ -60,25 +62,21 @@ public class EncryptionDetectionTest extends NbTestCase { super(name); } - @Override - public void setUp() { - CaseUtils.createCase(CASE_DIRECTORY_PATH, CASE_NAME); - ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); - IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); - } - @Override public void tearDown() { CaseUtils.closeCase(); } /** - * Test the Encryption Detection module's password protection detection. + * Test the Encryption Detection module's SQLCipher encryption detection. */ - public void testPasswordProtection() { + public void testSqlCipherEncryption() { try { + CaseUtils.createCase(SQLCIPHER_DETECTION_CASE_NAME); + ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); + IngestUtils.addDataSource(dataSourceProcessor, SQLCIPHER_DETECTION_IMAGE_PATH); Case openCase = Case.getCurrentCaseThrows(); - + /* * Create ingest job settings. */ @@ -90,28 +88,96 @@ public class EncryptionDetectionTest extends NbTestCase { templates.add(template); IngestJobSettings ingestJobSettings = new IngestJobSettings(EncryptionDetectionTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates); IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); + + /* + * Purge specific files to be tested. + */ + FileManager fileManager = openCase.getServices().getFileManager(); + List results = fileManager.findFiles("%%", "sqlcipher"); + assertEquals("Unexpected number of SQLCipher results.", 15, results.size()); + + for (AbstractFile file : results) { + /* + * Process only non-slack files. + */ + if (file.isFile() && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK)) { + /* + * Determine which assertions to use for the file based on + * its name. + */ + List artifactsList = file.getAllArtifacts(); + String[] splitNameArray = file.getName().split("\\."); + if (splitNameArray[0].startsWith("sqlcipher-") && splitNameArray[splitNameArray.length - 1].equals("db")) { + /* + * Check that the SQLCipher database file has one + * TSK_ENCRYPTION_SUSPECTED artifact. + */ + int artifactsListSize = artifactsList.size(); + String errorMessage = String.format("File '%s' (objId=%d) has %d artifacts, but 1 was expected.", file.getName(), file.getId(), artifactsListSize); + assertEquals(errorMessage, 1, artifactsListSize); + + String artifactTypeName = artifactsList.get(0).getArtifactTypeName(); + errorMessage = String.format("File '%s' (objId=%d) has an unexpected '%s' artifact.", file.getName(), file.getId(), artifactTypeName); + assertEquals(errorMessage, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED.toString(), artifactTypeName); + } else { + /* + * Check that the file has no artifacts. + */ + int artifactsListSize = artifactsList.size(); + String errorMessage = String.format("File '%s' (objId=%d) has %d artifacts, but none were expected.", file.getName(), file.getId(), artifactsListSize); + assertEquals(errorMessage, 0, artifactsListSize); + } + } + } + } catch (NoCurrentCaseException | TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + /** + * Test the Encryption Detection module's password protection detection. + */ + public void testPasswordProtection() { + try { + CaseUtils.createCase(PASSWORD_DETECTION_CASE_NAME); + ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); + List errorMessages = IngestUtils.addDataSource(dataSourceProcessor, PASSWORD_DETECTION_IMAGE_PATH); + String joinedErrors = String.join(System.lineSeparator(), errorMessages); + assertEquals(joinedErrors, 0, errorMessages.size()); + + Case openCase = Case.getCurrentCaseThrows(); + + /* + * Create ingest job settings. + */ + ArrayList templates = new ArrayList<>(); + templates.add(IngestUtils.getIngestModuleTemplate(new EncryptionDetectionModuleFactory())); + IngestJobSettings ingestJobSettings = new IngestJobSettings(PASSWORD_DETECTION_CASE_NAME, IngestType.FILES_ONLY, templates); + IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); + /* * Purge specific files to be tested. */ FileManager fileManager = openCase.getServices().getFileManager(); List> allResults = new ArrayList<>(0); - + List ole2Results = fileManager.findFiles("%%", "ole2"); assertEquals("Unexpected number of OLE2 results.", 11, ole2Results.size()); - + List ooxmlResults = fileManager.findFiles("%%", "ooxml"); assertEquals("Unexpected number of OOXML results.", 13, ooxmlResults.size()); - + List pdfResults = fileManager.findFiles("%%", "pdf"); assertEquals("Unexpected number of PDF results.", 6, pdfResults.size()); - + List mdbResults = fileManager.findFiles("%%", "mdb"); assertEquals("Unexpected number of MDB results.", 25, mdbResults.size()); - + List accdbResults = fileManager.findFiles("%%", "accdb"); assertEquals("Unexpected number of ACCDB results.", 10, accdbResults.size()); - + allResults.add(ole2Results); allResults.add(ooxmlResults); allResults.add(pdfResults); @@ -125,8 +191,8 @@ public class EncryptionDetectionTest extends NbTestCase { */ if (file.isFile() && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK)) { /* - * Determine which assertions to use for the file based on - * its name. + * Determine which assertions to use for the file based + * on its name. */ boolean fileProtected = file.getName().split("\\.")[0].endsWith("-protected"); List artifactsList = file.getAllArtifacts(); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java index 5eecf425bd..8e96c4e5c4 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java @@ -18,8 +18,10 @@ */ package org.sleuthkit.autopsy.testutils; +import java.io.File; import java.io.IOException; import java.nio.file.Path; +import java.nio.file.Paths; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import org.apache.commons.io.FileUtils; @@ -30,7 +32,8 @@ import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseDetails; /** - * Common case utility methods. + * Class with common methods for testing related to the creation and elimination + * of cases. */ public final class CaseUtils { @@ -42,32 +45,37 @@ public final class CaseUtils { } /** - * Create a new case. If the case already exists at the specified path, the - * existing case will be removed prior to creation of the new case. + * Create a case case directory and case for the given case name. * - * @param caseDirectoryPath The path to the case data. - * @param caseDisplayName The display name for the case. + * @param caseName the name for the case and case directory to have */ - public static void createCase(Path caseDirectoryPath, String caseDisplayName) { - //Make sure the test is starting with a clean state. So delete the test directory, if it exists. - deleteCaseDir(caseDirectoryPath); - assertFalse("Unable to delete existing test directory", caseDirectoryPath.toFile().exists()); - + public static void createCase(String caseName) { + //Make sure the case is starting with a clean state. So delete the case directory, if it exists. + Path caseDirectoryPath = Paths.get(System.getProperty("java.io.tmpdir"), caseName); + File caseDir = new File(caseDirectoryPath.toString()); + try { + deleteCaseDir(caseDir); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + assertFalse("Unable to delete existing test directory", caseDir.exists()); // Create the test directory - caseDirectoryPath.toFile().mkdirs(); - assertTrue("Unable to create test directory", caseDirectoryPath.toFile().exists()); + caseDir.mkdirs(); + assertTrue("Unable to create test directory", caseDir.exists()); try { - Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, caseDirectoryPath.toString(), new CaseDetails(caseDisplayName)); + Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, caseDirectoryPath.toString(), new CaseDetails(caseName)); } catch (CaseActionException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - assertTrue(caseDirectoryPath.toFile().exists()); + + assertTrue(caseDir.exists()); } /** - * Close the currently opened case. + * Close the current case, fails test if case was unable to be closed. */ public static void closeCase() { try { @@ -85,20 +93,22 @@ public final class CaseUtils { } /** - * Delete a case at the specified path. + * Delete the case directory if it exists, thows exception if unable to + * delete case dir to allow the user to determine failure with. * - * @param caseDirectoryPath The path to the case to be removed. + * @param caseDirectory the case directory to delete + * + * @throws IOException thrown if there was an problem deleting the case + * directory */ - public static void deleteCaseDir(Path caseDirectoryPath) { - if (!caseDirectoryPath.toFile().exists()) { + public static void deleteCaseDir(File caseDirectory) throws IOException { + if (!caseDirectory.exists()) { return; } - try { - FileUtils.deleteDirectory(caseDirectoryPath.toFile()); - } catch (IOException ex) { - //We just want to make sure the case directory doesn't exist when the test starts. It shouldn't cause failure if the case directory couldn't be deleted after a test finished. - System.out.println("INFO: Unable to delete case directory: " + caseDirectoryPath.toString()); - } + //We should determine whether the test fails or passes where this is called + //It will usually be a test failure when the case can not be deleted + //but sometimes we might be alright if we are unable to delete it. + FileUtils.deleteDirectory(caseDirectory); } -} +} \ No newline at end of file diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java index fba62cdbde..d26edf0eaa 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.testutils; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import static junit.framework.Assert.assertEquals; import org.openide.util.Exceptions; @@ -26,13 +27,14 @@ import org.python.icu.impl.Assert; import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; import org.sleuthkit.autopsy.ingest.IngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestModuleError; -import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleFactory; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestModuleTemplate; import org.sleuthkit.datamodel.Content; /** - * Common image utility methods. + * Class with common methods for testing related to adding and ingesting + * datasources. */ public final class IngestUtils { @@ -44,38 +46,48 @@ public final class IngestUtils { } /** - * Add a data source for the data source processor. + * Add the specified datasource to the case current case and processes it. + * Causes failure if it was unable to add and process the datasource. * - * @param dataSourceProcessor The data source processor. - * @param dataSourcePath The path to the data source to be added. + * @param dataSourceProcessor the datasource processer to use to process the + * datasource + * @param dataSourcePath the path to the datasource which is being + * added + * + * @return errorMessages a list of all error messages as strings which + * encountered while processing the data source */ - public static void addDataSource(AutoIngestDataSourceProcessor dataSourceProcessor, Path dataSourcePath) { + public static List addDataSource(AutoIngestDataSourceProcessor dataSourceProcessor, Path dataSourcePath) { + List errorMessages = new ArrayList<>(); try { + if (!dataSourcePath.toFile().exists()) { + Assert.fail("Data source not found: " + dataSourcePath.toString()); + } DataSourceProcessorRunner.ProcessorCallback callBack = DataSourceProcessorRunner.runDataSourceProcessor(dataSourceProcessor, dataSourcePath); - List callbackErrorMessageList = callBack.getErrorMessages(); - String errorMessage = String.format("The data source processor callback produced %d error messages.", callbackErrorMessageList.size()); - assertEquals(errorMessage, 0, callbackErrorMessageList.size()); + errorMessages = callBack.getErrorMessages(); } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | InterruptedException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); - } + return errorMessages; } /** - * Run an ingest job. + * Run ingest on the specified datasources with the specified ingest job + * settings. Causes failure if there are any errors or other problems while + * running ingest. * - * @param dataSourceList The list of data sources to process. - * @param ingestJobSettings The ingest job settings to use for ingest. + * @param datasources - the datasources to run ingest on + * @param ingestJobSettings - the ingest job settings to use for ingest */ - public static void runIngestJob(List dataSourceList, IngestJobSettings ingestJobSettings) { + public static void runIngestJob(List datasources, IngestJobSettings ingestJobSettings) { try { - List ingestModuleErrorsList = IngestJobRunner.runIngestJob(dataSourceList, ingestJobSettings); - for (IngestModuleError err : ingestModuleErrorsList) { - System.out.println(String.format("Error: %s: %s.", err.getModuleDisplayName(), err.toString())); - } - String errorMessage = String.format("The ingest job runner produced %d error messages.", ingestModuleErrorsList.size()); - assertEquals(errorMessage, 0, ingestModuleErrorsList.size()); + List errs = IngestJobRunner.runIngestJob(datasources, ingestJobSettings); + StringBuilder joinedErrors = new StringBuilder(""); + errs.forEach((err) -> { + joinedErrors.append(String.format("Error: %s: %s.", err.getModuleDisplayName(), err.toString())).append(System.lineSeparator()); + }); + assertEquals(joinedErrors.toString(), 0, errs.size()); } catch (InterruptedException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); @@ -83,17 +95,18 @@ public final class IngestUtils { } /** - * Build a new ingest module template based on the given factory. + * Get the ingest module template for the the specified factories default + * ingest job settings. * - * @param factory The ingest module factory. + * @param factory the factory to get the ingest job settings from * - * @return The ingest module template. + * @return template - the IngestModuleTemplate created with the factory and + * it's default settings. */ - public static IngestModuleTemplate getIngestModuleTemplate(IngestModuleFactoryAdapter factory) { + public static IngestModuleTemplate getIngestModuleTemplate(IngestModuleFactory factory) { IngestModuleIngestJobSettings settings = factory.getDefaultIngestJobSettings(); IngestModuleTemplate template = new IngestModuleTemplate(factory, settings); template.setEnabled(true); return template; } - -} +} \ No newline at end of file From d606c99305b5e5d825ebb186a0e066e63ce7c6f5 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 9 May 2018 14:23:29 -0400 Subject: [PATCH 31/63] Change max date range to one month. Only get entries from data base that are less than a month old. --- .../EnterpriseHealthMonitor.java | 10 +++-- .../healthmonitor/HealthMonitorDashboard.java | 44 ++++++++++++------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index 3510f953ef..d0c9b50320 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -988,11 +988,12 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } /** - * Get all timing metrics currently stored in the database. + * Get timing metrics currently stored in the database. + * @param timeRange Maximum age for returned metrics (in milliseconds) * @return A map with metric name mapped to a list of data * @throws HealthMonitorException */ - Map> getTimingMetricsFromDatabase() throws HealthMonitorException { + Map> getTimingMetricsFromDatabase(long timeRange) throws HealthMonitorException { // Make sure the monitor is enabled. It could theoretically get disabled after this // check but it doesn't seem worth holding a lock to ensure that it doesn't since that @@ -1000,6 +1001,9 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { if(! isEnabled.get()) { throw new HealthMonitorException("Health Monitor is not enabled"); } + + // Calculate the smallest timestamp we should return + long minimumTimestamp = System.currentTimeMillis() - timeRange; try (CoordinationService.Lock lock = getSharedDbLock()) { if(lock == null) { @@ -1014,7 +1018,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { Map> resultMap = new HashMap<>(); try (Statement statement = conn.createStatement(); - ResultSet resultSet = statement.executeQuery("SELECT * FROM timing_data")) { + ResultSet resultSet = statement.executeQuery("SELECT * FROM timing_data WHERE timestamp > " + minimumTimestamp)) { while (resultSet.next()) { String name = resultSet.getString("name"); diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java index daa6caa588..2818e94e96 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java @@ -42,6 +42,7 @@ import javax.swing.BorderFactory; import java.util.Map; import javax.swing.BoxLayout; import java.awt.GridLayout; +import java.nio.file.Paths; import java.util.logging.Level; import java.util.stream.Collectors; import org.openide.modules.Places; @@ -57,7 +58,7 @@ public class HealthMonitorDashboard { private final static Logger logger = Logger.getLogger(HealthMonitorDashboard.class.getName()); private final static String ADMIN_ACCESS_FILE_NAME = "adminAccess"; // NON-NLS - private final static String ADMIN_ACCESS_FILE_PATH = Places.getUserDirectory().getAbsolutePath() + File.separator + ADMIN_ACCESS_FILE_NAME; + private final static String ADMIN_ACCESS_FILE_PATH = Paths.get(Places.getUserDirectory().getAbsolutePath(), ADMIN_ACCESS_FILE_NAME).toString(); Map> timingData; @@ -144,7 +145,7 @@ public class HealthMonitorDashboard { if(EnterpriseHealthMonitor.monitorIsEnabled()) { // Get a copy of the timing data from the database - timingData = EnterpriseHealthMonitor.getInstance().getTimingMetricsFromDatabase(); + timingData = EnterpriseHealthMonitor.getInstance().getTimingMetricsFromDatabase(DateRange.getMaximumTimestampRange()); } } @@ -305,14 +306,10 @@ public class HealthMonitorDashboard { List intermediateTimingDataForDisplay; if(dateComboBox.getSelectedItem() != null) { DateRange selectedDateRange = DateRange.fromLabel(dateComboBox.getSelectedItem().toString()); - if(selectedDateRange != DateRange.ALL) { - long threshold = System.currentTimeMillis() - selectedDateRange.getTimestampRange(); - intermediateTimingDataForDisplay = timingData.get(metricName).stream() - .filter(t -> t.getTimestamp() > threshold) - .collect(Collectors.toList()); - } else { - intermediateTimingDataForDisplay = timingData.get(metricName); - } + long threshold = System.currentTimeMillis() - selectedDateRange.getTimestampRange(); + intermediateTimingDataForDisplay = timingData.get(metricName).stream() + .filter(t -> t.getTimestamp() > threshold) + .collect(Collectors.toList()); } else { intermediateTimingDataForDisplay = timingData.get(metricName); } @@ -399,16 +396,16 @@ public class HealthMonitorDashboard { /** * Possible date ranges for the metrics in the UI */ - @NbBundle.Messages({"HealthMonitorDashboard.DateRange.all=All", + @NbBundle.Messages({"HealthMonitorDashboard.DateRange.oneMonth=One month", "HealthMonitorDashboard.DateRange.twoWeeks=Two weeks", "HealthMonitorDashboard.DateRange.oneWeek=One week", "HealthMonitorDashboard.DateRange.oneDay=One day"}) private enum DateRange { - ALL(Bundle.HealthMonitorDashboard_DateRange_all(), 0), - TWO_WEEKS(Bundle.HealthMonitorDashboard_DateRange_twoWeeks(), 14), + ONE_DAY(Bundle.HealthMonitorDashboard_DateRange_oneDay(), 1), ONE_WEEK(Bundle.HealthMonitorDashboard_DateRange_oneWeek(), 7), - ONE_DAY(Bundle.HealthMonitorDashboard_DateRange_oneDay(), 1); - + TWO_WEEKS(Bundle.HealthMonitorDashboard_DateRange_twoWeeks(), 14), + ONE_MONTH(Bundle.HealthMonitorDashboard_DateRange_oneMonth(), 31); + private final String label; private final long numberOfDays; private static final long MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24; @@ -440,13 +437,28 @@ public class HealthMonitorDashboard { } } + /** + * Get the maximum range for this enum. + * This should be used for querying the database for the timing metrics to display. + * @return the maximum range in milliseconds + */ + static long getMaximumTimestampRange() { + long maxRange = Long.MIN_VALUE; + for (DateRange dateRange : DateRange.values()) { + if (dateRange.getTimestampRange() > maxRange) { + maxRange = dateRange.getTimestampRange(); + } + } + return maxRange; + } + static DateRange fromLabel(String text) { for (DateRange dateRange : DateRange.values()) { if (dateRange.label.equalsIgnoreCase(text)) { return dateRange; } } - return ALL; // If the comparison failed, return a default + return ONE_DAY; // If the comparison failed, return a default } } From 1580a07d7747e7810d5b70db5f91cde26d14a299 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Wed, 9 May 2018 16:23:29 -0400 Subject: [PATCH 32/63] Removed SQLCipher encryption TSK_COMMENT. --- .../EncryptionDetectionFileIngestModule.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java index 9784f5b06a..0f9987afc7 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java @@ -115,7 +115,6 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter @Messages({ "EncryptionDetectionFileIngestModule.artifactComment.password=Password protection detected.", - "EncryptionDetectionFileIngestModule.artifactComment.sqlCipher=Suspected SQLCipher encryption due to high entropy (%f).", "EncryptionDetectionFileIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f)." }) @Override @@ -140,13 +139,8 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter String mimeType = fileTypeDetector.getMIMEType(file); if (mimeType.equals("application/octet-stream")) { if (isFileEncryptionSuspected(file)) { - String comment; - if (file.getNameExtension().equalsIgnoreCase(DATABASE_FILE_EXTENSION)) { - comment = Bundle.EncryptionDetectionFileIngestModule_artifactComment_sqlCipher(); - } else { - comment = Bundle.EncryptionDetectionFileIngestModule_artifactComment_suspected(); - } - return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, String.format(comment, calculatedEntropy)); + return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, + String.format(Bundle.EncryptionDetectionFileIngestModule_artifactComment_suspected(), calculatedEntropy)); } } else { if (isFilePasswordProtected(file)) { From 8495e6e20400c6a4328a875ac5ea0dc7f8e60e7a Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 10 May 2018 08:04:53 -0400 Subject: [PATCH 33/63] Codacy fixes --- .../EnterpriseHealthMonitor.java | 23 +++++++++--------- .../healthmonitor/TimingMetricGraphPanel.java | 24 +++++++++---------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index d0c9b50320..d28ef1b475 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -603,7 +603,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { * and enable/disable as needed. * @throws HealthMonitorException */ - final synchronized void updateFromGlobalEnabledStatus() throws HealthMonitorException { + synchronized void updateFromGlobalEnabledStatus() throws HealthMonitorException { boolean previouslyEnabled = monitorIsEnabled(); @@ -645,10 +645,8 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } boolean currentlyEnabled = getGlobalEnabledStatusFromDB(); - if( currentlyEnabled == previouslyEnabled) { - // Nothing needs to be done - } else { - if(currentlyEnabled == false) { + if( currentlyEnabled != previouslyEnabled) { + if( ! currentlyEnabled ) { isEnabled.set(false); deactivateMonitorLocally(); } else { @@ -845,7 +843,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { * It will delete all current timing data and replace it with randomly generated values. * If there is more than one node, the second node's times will trend upwards. */ - final void populateDatabaseWithSampleData(int nDays, int nNodes, boolean createVerificationData) throws HealthMonitorException { + void populateDatabaseWithSampleData(int nDays, int nNodes, boolean createVerificationData) throws HealthMonitorException { if(! isEnabled.get()) { throw new HealthMonitorException("Can't populate database - monitor not enabled"); @@ -900,6 +898,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { break; default: minIndexTimeNanos = baseIndex * 1000 * 1000; + break; } long maxIndexTimeOverMin = minIndexTimeNanos * 3; @@ -1169,12 +1168,12 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { * All times will be in milliseconds. */ 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) + private final long timestamp; // Time the metric was recorded + private final String hostname; // Host that recorded the metric + private final long count; // Number of metrics collected + private final double average; // Average of the durations collected (milliseconds) + private final double max; // Maximum value found (milliseconds) + private final double min; // Minimum value found (milliseconds) DatabaseTimingResult(ResultSet resultSet) throws SQLException { this.timestamp = resultSet.getLong("timestamp"); diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index 907be0df96..5e94471ff8 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -48,19 +48,19 @@ class TimingMetricGraphPanel extends JPanel { private final static Logger logger = Logger.getLogger(TimingMetricGraphPanel.class.getName()); - private int padding = 25; - private int labelPadding = 25; - private Color lineColor = new Color(0x12, 0x20, 0xdb, 180); - private Color gridColor = new Color(200, 200, 200, 200); - private Color trendLineColor = new Color(150, 10, 10, 200); + private final int padding = 25; + private final int labelPadding = 25; + private final Color lineColor = new Color(0x12, 0x20, 0xdb, 180); + private final Color gridColor = new Color(200, 200, 200, 200); + private final Color trendLineColor = new Color(150, 10, 10, 200); private static final Stroke GRAPH_STROKE = new BasicStroke(2f); private static final Stroke NARROW_STROKE = new BasicStroke(1f); - private int pointWidth = 4; - private int numberYDivisions = 10; + private final int pointWidth = 4; + private final int numberYDivisions = 10; private List timingResults; - private TimingMetricType timingMetricType; - private String metricName; - private boolean doLineGraph; + private final TimingMetricType timingMetricType; + private final String metricName; + private final boolean doLineGraph; private String yUnitString; private TrendLine trendLine; private final long MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24; @@ -264,7 +264,7 @@ class TimingMetricGraphPanel extends JPanel { int y0 = getHeight() - ((i * graphHeight) / numberYDivisions + bottomGraphPadding); int y1 = y0; - if (timingResults.size() > 0) { + if ( ! timingResults.isEmpty()) { // Draw the grid line g2.setColor(gridColor); g2.drawLine(leftGraphPadding + 1 + pointWidth, y0, getWidth() - rightGraphPadding, y1); @@ -272,7 +272,7 @@ class TimingMetricGraphPanel extends JPanel { // Create the label g2.setColor(Color.BLACK); double yValue = minValueOnYAxis + ((maxValueOnYAxis - minValueOnYAxis) * ((i * 1.0) / numberYDivisions)); - String yLabel = ((int) (yValue * 100 * yLabelScale)) / 100.0 + ""; + String yLabel = Double.toString(((int) (yValue * 100 * yLabelScale)) / 100.0); FontMetrics fontMetrics = g2.getFontMetrics(); labelWidth = fontMetrics.stringWidth(yLabel); g2.drawString(yLabel, x0 - labelWidth - 5, y0 + (fontMetrics.getHeight() / 2) - 3); From 25e5d77ca1b6625e7f555fb00181f720788e97ec Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 14 May 2018 12:50:30 -0400 Subject: [PATCH 34/63] 3815 fix saving/restoring of selected node to always be same node --- .../autopsy/experimental/autoingest/AutoIngestJobsNode.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index 60f84e566c..deed0cfa96 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -128,8 +128,8 @@ final class AutoIngestJobsNode extends AbstractNode { super(Children.LEAF); jobStatus = status; autoIngestJob = job; - setName(autoIngestJob.getManifest().getCaseName()); - setDisplayName(autoIngestJob.getManifest().getCaseName()); + setName(autoIngestJob.toString()); //alows job to be uniquely found by name since it will involve a hash of the AutoIngestJob + setDisplayName(autoIngestJob.getManifest().getCaseName()); //displays user friendly case name as name } /** From 7b2080272ae01869b0ef46e6709ef8ed32b7ea7a Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 14 May 2018 18:00:59 -0400 Subject: [PATCH 35/63] 3815 Aid now refreshes without losing selection and scroll location --- .../autoingest/AutoIngestDashboard.java | 6 +-- .../autoingest/AutoIngestJobsNode.java | 41 +++++++++++++++---- .../autoingest/AutoIngestJobsPanel.java | 18 ++++---- .../autoingest/PrioritizationAction.java | 2 +- 4 files changed, 45 insertions(+), 22 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 3704bf7a7e..88ff204696 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -261,9 +261,9 @@ final class AutoIngestDashboard extends JPanel implements Observer { * @param nodeStateSnapshot The jobs snapshot. */ void refreshTables() { - pendingJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); - runningJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); - completedJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); + pendingJobsPanel.refresh(autoIngestMonitor); + runningJobsPanel.refresh(autoIngestMonitor); + completedJobsPanel.refresh(autoIngestMonitor); } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index deed0cfa96..d5d7d86586 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; import javax.swing.Action; import java.time.Instant; import java.util.ArrayList; @@ -40,6 +42,9 @@ import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; */ final class AutoIngestJobsNode extends AbstractNode { + private final static EventBus refreshChildrenEventBus = new EventBus("AutoIngestJobsNodeEventBus"); + private final static short REFRESH_EVENT = 1; // + @Messages({ "AutoIngestJobsNode.caseName.text=Case Name", "AutoIngestJobsNode.dataSource.text=Data Source", @@ -55,17 +60,21 @@ final class AutoIngestJobsNode extends AbstractNode { /** * Construct a new AutoIngestJobsNode. */ - AutoIngestJobsNode(JobsSnapshot jobsSnapshot, AutoIngestJobStatus status) { - super(Children.create(new AutoIngestNodeChildren(jobsSnapshot, status), false)); + AutoIngestJobsNode(AutoIngestMonitor autoIngestMonitor, AutoIngestJobStatus status) { + super(Children.create(new AutoIngestNodeChildren(autoIngestMonitor, status), false)); + } + + void refresh() { + refreshChildrenEventBus.post(REFRESH_EVENT); } /** * A ChildFactory for generating JobNodes. */ - static class AutoIngestNodeChildren extends ChildFactory { + static final class AutoIngestNodeChildren extends ChildFactory { private final AutoIngestJobStatus autoIngestJobStatus; - private final JobsSnapshot jobsSnapshot; + private final AutoIngestMonitor autoIngestMonitor; /** * Create children nodes for the AutoIngestJobsNode which will each @@ -74,14 +83,16 @@ final class AutoIngestJobsNode extends AbstractNode { * @param snapshot the snapshot which contains the AutoIngestJobs * @param status the status of the jobs being displayed */ - AutoIngestNodeChildren(JobsSnapshot snapshot, AutoIngestJobStatus status) { - jobsSnapshot = snapshot; + AutoIngestNodeChildren(AutoIngestMonitor monitor, AutoIngestJobStatus status) { + autoIngestMonitor = monitor; autoIngestJobStatus = status; + refreshChildrenEventBus.register(this); } @Override protected boolean createKeys(List list) { List jobs; + JobsSnapshot jobsSnapshot = autoIngestMonitor.getJobsSnapshot(); switch (autoIngestJobStatus) { case PENDING_JOB: jobs = jobsSnapshot.getPendingJobs(); @@ -107,6 +118,13 @@ final class AutoIngestJobsNode extends AbstractNode { return new JobNode(key, autoIngestJobStatus); } + @Subscribe + private void subscribeToRefresh(short refreshEvent) { + if (refreshEvent == REFRESH_EVENT) { + refresh(true); + } + } + } /** @@ -130,6 +148,7 @@ final class AutoIngestJobsNode extends AbstractNode { autoIngestJob = job; setName(autoIngestJob.toString()); //alows job to be uniquely found by name since it will involve a hash of the AutoIngestJob setDisplayName(autoIngestJob.getManifest().getCaseName()); //displays user friendly case name as name + refreshChildrenEventBus.register(this); } /** @@ -185,8 +204,16 @@ final class AutoIngestJobsNode extends AbstractNode { return s; } + @Subscribe + private void subscribeToRefresh(short refreshEvent) { + if (refreshEvent == REFRESH_EVENT) { + this.setSheet(createSheet()); + } + } + @Override - public Action[] getActions(boolean context) { + public Action[] getActions(boolean context + ) { List actions = new ArrayList<>(); if (AutoIngestDashboard.isAdminAutoIngestDashboard()) { switch (jobStatus) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index 3c777f5bc0..403487f208 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import com.google.common.eventbus.EventBus; import java.awt.Dimension; import java.beans.PropertyVetoException; import javax.swing.ListSelectionModel; @@ -163,20 +164,15 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa * @param jobsSnapshot - the JobsSnapshot which will provide the new * contents */ - void refresh(JobsSnapshot jobsSnapshot) { + void refresh(AutoIngestMonitor autoIngestMonitor) { synchronized (this) { outline.setRowSelectionAllowed(false); - Node[] selectedNodes = explorerManager.getSelectedNodes(); - AutoIngestJobsNode autoIngestNode = new AutoIngestJobsNode(jobsSnapshot, status); - explorerManager.setRootContext(autoIngestNode); - outline.setRowSelectionAllowed(true); - if (selectedNodes.length > 0 && autoIngestNode.getChildren().findChild(selectedNodes[0].getName()) != null && outline.isFocusable()) { //don't allow saved selections of empty nodes to be restored - try { - explorerManager.setSelectedNodes(new Node[]{autoIngestNode.getChildren().findChild(selectedNodes[0].getName())}); - } catch (PropertyVetoException ignore) { - //Unable to select previously selected node - } + if (explorerManager.getRootContext() instanceof AutoIngestJobsNode) { + ((AutoIngestJobsNode) explorerManager.getRootContext()).refresh(); + } else { + explorerManager.setRootContext(new AutoIngestJobsNode(autoIngestMonitor, status)); } + outline.setRowSelectionAllowed(true); outline.setFocusable(true); } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java index d50a85f906..4e362f4cb5 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java @@ -88,7 +88,7 @@ abstract class PrioritizationAction extends AbstractAction { EventQueue.invokeLater(() -> { try { modifyPriority(dashboard.getMonitor()); - dashboard.getPendingJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot()); + dashboard.getPendingJobsPanel().refresh(dashboard.getMonitor()); } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { String errorMessage = getErrorMessage(); logger.log(Level.SEVERE, errorMessage, ex); From 9fa58b354c372a7f300a4a866fa16a1db829e6a2 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Thu, 10 May 2018 15:53:51 -0400 Subject: [PATCH 36/63] Switched TSK_NAME to TSK_COMMENT. --- .../embeddedfileextractor/SevenZipExtractor.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index d09c170110..9e78e0d58e 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -218,7 +218,7 @@ class SevenZipExtractor { */ private ArchiveFormat get7ZipOptions(AbstractFile archiveFile) { // try to get the file type from the BB - String detectedFormat = null; + String detectedFormat; detectedFormat = archiveFile.getMIMEType(); if (detectedFormat == null) { @@ -434,11 +434,11 @@ class SevenZipExtractor { result = item.extractSlow(unpackStream, password); } if (result != ExtractOperationResult.OK) { - logger.log(Level.WARNING, "Extraction of : " + localAbsPath + " encountered error " + result); //NON-NLS + logger.log(Level.WARNING, "Extraction of : {0} encountered error {1}", new Object[]{localAbsPath, result}); //NON-NLS return null; } - } catch (Exception e) { + } catch (SevenZipException e) { //could be something unexpected with this file, move on logger.log(Level.WARNING, "Could not extract file from archive: " + localAbsPath, e); //NON-NLS } finally { @@ -492,7 +492,7 @@ class SevenZipExtractor { final ProgressHandle progress = ProgressHandle.createHandle(Bundle.EmbeddedFileExtractorIngestModule_ArchiveExtractor_moduleName()); //recursion depth check for zip bomb final long archiveId = archiveFile.getId(); - SevenZipExtractor.ArchiveDepthCountTree.Archive parentAr = null; + SevenZipExtractor.ArchiveDepthCountTree.Archive parentAr; try { blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard(); } catch (NoCurrentCaseException ex) { @@ -717,7 +717,7 @@ class SevenZipExtractor { String encryptionType = fullEncryption ? ENCRYPTION_FULL : ENCRYPTION_FILE_LEVEL; try { BlackboardArtifact artifact = archiveFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED); - artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, EmbeddedFileExtractorModuleFactory.getModuleName(), encryptionType)); + artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, EmbeddedFileExtractorModuleFactory.getModuleName(), encryptionType)); try { // index the artifact for keyword search From fe99177a5d998ab958d0844030e23c1ebd4595e5 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 15 May 2018 07:56:04 -0400 Subject: [PATCH 37/63] Cleanup --- .../core.jar/org/netbeans/core/startup/Bundle.properties | 4 ++-- .../org/netbeans/core/windows/view/ui/Bundle.properties | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index b734326e07..e07bce67f1 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Wed, 28 Feb 2018 09:59:56 -0500 +#Mon, 19 Mar 2018 11:17:11 -0700 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 @@ -8,4 +8,4 @@ SplashRunningTextBounds=0,289,538,18 SplashRunningTextColor=0x0 SplashRunningTextFontSize=19 -currentVersion=Autopsy 4.6.0 +currentVersion=Autopsy 4.7.0 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index 3cf76dea10..3f9f5b49c7 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script -#Wed, 28 Feb 2018 09:59:56 -0500 -CTL_MainWindow_Title=Autopsy 4.6.0 -CTL_MainWindow_Title_No_Project=Autopsy 4.6.0 +#Fri, 09 Mar 2018 13:03:41 -0700 +CTL_MainWindow_Title=Autopsy 4.7.0 +CTL_MainWindow_Title_No_Project=Autopsy 4.7.0 From 3860abba053ba0ed7401af8ff579f89fbcb26176 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 15 May 2018 07:57:34 -0400 Subject: [PATCH 38/63] Cleanup --- .../core/core.jar/org/netbeans/core/startup/Bundle.properties | 2 +- .../org/netbeans/core/windows/view/ui/Bundle.properties | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index e07bce67f1..b1adb5d40b 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -8,4 +8,4 @@ SplashRunningTextBounds=0,289,538,18 SplashRunningTextColor=0x0 SplashRunningTextFontSize=19 -currentVersion=Autopsy 4.7.0 +currentVersion=Autopsy 4.6.0 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index 3f9f5b49c7..6cb9d4bdea 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script #Fri, 09 Mar 2018 13:03:41 -0700 -CTL_MainWindow_Title=Autopsy 4.7.0 -CTL_MainWindow_Title_No_Project=Autopsy 4.7.0 +CTL_MainWindow_Title=Autopsy 4.6.0 +CTL_MainWindow_Title_No_Project=Autopsy 4.6.0 From 115cfde0f9c23f410e0a4a82d7dd2c9b1906d270 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 15 May 2018 10:25:51 -0400 Subject: [PATCH 39/63] Added CVT visualization docs. Fixed some doxygen warnings. --- .../corecomponentinterfaces/DataResult.java | 2 +- .../corecomponents/DataResultPanel.java | 2 +- .../datamodel/AutopsyVisitableItem.java | 2 +- .../autopsy/datamodel/ContentNode.java | 2 +- docs/doxygen-user/communications.dox | 26 ++++++++++++++++-- docs/doxygen-user/images/cvt_links.png | Bin 0 -> 63426 bytes docs/doxygen-user/images/cvt_main.png | Bin 86648 -> 115021 bytes docs/doxygen-user/images/cvt_messages.png | Bin 53782 -> 50122 bytes .../images/cvt_select_account.png | Bin 0 -> 19233 bytes docs/doxygen-user/images/cvt_visualize.png | Bin 0 -> 38493 bytes 10 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 docs/doxygen-user/images/cvt_links.png create mode 100644 docs/doxygen-user/images/cvt_select_account.png create mode 100644 docs/doxygen-user/images/cvt_visualize.png diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java index 6af30b8729..76d76a2bb4 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java @@ -63,7 +63,7 @@ public interface DataResult { * Sets the descriptive text about the source of the nodes displayed in this * result view component. * - * @param description The text to display. + * @param pathText The text to display. */ public void setPath(String pathText); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index eea1a61f6e..4aa7a63117 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -734,7 +734,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C * * @return True or false. * - * @Deprecated This method has no valid use case. + * @deprecated This method has no valid use case. */ @Deprecated @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java index 4092321ca9..af868a68c4 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java @@ -28,7 +28,7 @@ public interface AutopsyVisitableItem { /** * visitor pattern support * - * @param v visitor + * @param visitor visitor * * @return visitor return value */ diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNode.java index d3531068ee..1bf43f5e79 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNode.java @@ -40,7 +40,7 @@ abstract class ContentNode extends DisplayableItemNode { /** * Visitor pattern support. * - * @param v visitor + * @param visitor visitor * * @return visitor's visit return value */ diff --git a/docs/doxygen-user/communications.dox b/docs/doxygen-user/communications.dox index ced0a044d3..1e89a28d8e 100644 --- a/docs/doxygen-user/communications.dox +++ b/docs/doxygen-user/communications.dox @@ -20,8 +20,30 @@ The middle column displays each account, its device and type, and the number of Selecting an account in the middle column will bring up the messages for that account in the right hand column. Here data about each message is displayed in the top section, and the messages itself can be seen in the bottom section (if applicable). -The middle column and the right hand column both have a \ref ui_quick_search feature which can be used to quickly find a visible item in their section's table. - \image html cvt_messages.png +The middle column and the right hand column both have a \ref ui_quick_search feature which can be used to quickly find a visible item in their section's table. + +\section cvt_viz Visualization + +The Visualize tab in the middle panel will show a graph of one or more accounts selected in the Browse tab. + +To start, right click the first account you want to view. + +\image html cvt_select_account.png + +There are two options, which are equivalent when no accounts have previously been selected: +
    +
  • Add Selected Account to Visualization - Adds this account and its connections to the graph +
  • Visualize Only Selected Account - Clears the graph and only displays the connections for this account +
+ +After selecting either option, the middle tab will switch to the Visualize view and the graph will be displayed. + +\image html cvt_visualize.png + +The options at the top allow you to clear the graph, try different graph layouts, and resize the graph. The nodes in the graph can be dragged around and nodes and edges can be selected to display their messages or relationships in the right side tab. For example, in the image below the link between two email addresses has been selected so the Messages viewer is displaying the single email between those two email addresses. + +\image html cvt_links.png + */ \ No newline at end of file diff --git a/docs/doxygen-user/images/cvt_links.png b/docs/doxygen-user/images/cvt_links.png new file mode 100644 index 0000000000000000000000000000000000000000..1aa4db903341012f0a0406919d5371f21072a14c GIT binary patch literal 63426 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�x#qv|g6>Ja6kkl(;nI5{0$mGn zUWhud`^E|^A`5quqz9?;3VMtn?51ThF}#C~;!8*(ED?FexQ)Ty8k=_djFkqI8C!0X zum!-v%+C&#BJ+$HcQ9_EQRM;Q+8G_}t#L?h zI3%9nnP_HY#^zJ+wox_=oDBgx`rJw&BnzbqR#hP%>67c-4~<8Hx0a?Y0gWK#NdUl= z?-pR-Pk7m%_t!>mC0lpGwZZMonx--_&*+)I1w_0*Bj~QMI>#Rm%Hez-86;&@bY9AE z2c~hO5tc>@hcUXQvPBqI@}TLd7q$F1OW_lGnG6{GDH@#yd__z_Ov~akI4hkkz!l-M z{xkXy-*;O`x-AQq-g^KkoGU6us)+1v+?i^7n;iEI$W8z@$7CuA7=RDZ@NQaLlIyl4 z1*YiI5-zF@)_Cx^3s@Th-e74YB(^)L{a)zf|Z`EXB=vhpp5|;|gN=Mb_zQVpinh6ya{lBaPAUhaM+;Kec z`VpqkSpz_^hNcuiyX4PL(RcC5W=|muGUxcQTy!W0j{}4bPuBjv(ku|d7?T4a`T-mE zDt%G(rMqhsm<(jfQL>FVCYmGN>ic8y=>y|0fQPL$0=6^!rM$}um?<)2F@)Z0H^aw8-~@UGw;|1&Xe<9k0o7;Bdaa{h{;+-rsj2&=;+vE6 zrRoZ8cvZ2B2Sw(DL!l~*gWxkD(9pQNRPED< z7tSuGoCVo3fNvBvyaUF&FCa{9caf~si~4Cd;3^=m43t7i=gJh6hZ@5_xIuP{SS!{C zCiT7eWm&>yX${@fAqPnE3{Au0D%%#HL3DC`Ekr}tax>uqFh^o4%-Wc$1_WS_P+D8M z_dt#VaeNg+4VB*+0{Yv6=ZBa72ExhSNb4&eW|~h}xi4NwJQhior4hbjZ30aDZ9!Ra+#w0%9FtnmNv6zLF0Gj9)3 z6;l}-K^d-SL_-&Y=ppN%f-$NnVL?r%iyeD+!)CaGeZ17c>x?6!6pjLmoq4PNuf^8z z*RcUXCm%{G3vSeel-78>kik#ARz*ryLIL>Z$y7N^+D#2i+A=ujB6$+QtLSRZkqtAQ z`oLbgfuU^Gz;!71w;M3qeQB7Kk=!>k%5RugMvDyTKN~LwbPoytR03kzjLrJ zHL&-r2hO!9PETsf4f|AvAdkatXi(qZ(ztaBEc8Y3bBV9AWVIiA08RHS%GqXN z>q}AemYKli-52!CbmZ)l8D2$@cmWawCd_nB#m9Ti(Mre)e6um0_F_KCQQ_PX!EUlF zi0uj$v&Kw{6%V83Wc!JM(F`ExMiWX%#KzBmf$2alQ`IcWa33OU{{TWqMM#Fj^;Y7W zEX)oogloN%mB^DP;l+kc71JDmbRNod8F$_Qj2K^h07v;`Ag>h_%lG;~zbhcVCz1El zh14|Z3zp$ADcOy4J7ov`DgC%-86zCaiAaX@<0LOSyf#&hlvbbJ>Ua8kzI&6a zcwV5o}3t8d1Ac^1-lS7-aeA#m^3Fodr>u^7jYWKJ1m%1EJOh zs!-k%ib%FC$=@v-PA1m!bNMQd>CMoJR@|E3+cR`#nbpsLNNgL(haY(-js%aQMLOK*#t!!?Wb&gvoD$>5-m=O zX?A}bul(^g}|SgCx{(z zT={5UqUkERoAP1nvY{%g(#Fqh9;3l>>msF>W9xnv>q_O+h@h;SykBp<(ap$F4w}7O z{dwX@CgSOE#0~lF?DcJ#3TXfO_`&_wT`WLrc~cjNQvNty%lf=>ZK!M7n7VJ51lPjp z{$kJMG;2ng>SRv()%8-sOsf7}lAS*pxAUzxxXeL30L4*q(}SMs(|#u7tK`yo9jj~` z!^-3+mpxZB#k_Cfe%4sX#`?8meJSjKNLD_0ucfr))CBFHB`4bq{D-^E>MfXBG{o|! zt)j&8mc05TKoZ#w2Wxxq&O^ltyHsadDWsOK5ozV z5stl~m?!>2rePZ2MhcV}$5)0Mktf4SEKiuPxr`!y&bxo1weq-3v>E5PfY3p(m^RRI zNG!M9e8VyEj#Oqlmg&GJ<_+)HQOdEaCLTm1CnUp*c#Jc#Cbm{R8BQKK z@Ref_)^}e!e5&2%ECUc!h)X8{2Tv2|BQ5oS@_4ziZosc$q^)CxFslT^ za|_4kr4ow+?6=Z>f1cl#n8O`C8O>X)G*odh>vU7^8%JI^#It@ikU4vRFU@jF={9IS zm9o&{B1fHrO@8xcqD;#ll_S_d*{`O{+-pvS!oM~=%I8@P4Zq-5JEKRcpPIr(J*(;Y z@xH-yB(Kj}USOkUw6TcieHo`veDtF-9f|E2E4%j$@o04~8=cYH4zr6~hUdptek})# zju`?Pgpj<~g(1x^KVgd+rjfCJ;;uc)df#tl#RS^1Kj9O@prHN$3XlOv{`C+~*8kvAXPM!;+2TwDOJ#)J$Sg_0`I56z3Kb zgk9Y7i&>*F9NtA$Bd0%tM{KLs*YVBKbE&zzF;b_SYA(f36|u>gPduECJ~=u4V3O3@ z{~c#jVJ7QjxLeKb@Cf0urtcYk*o~Jj!y8I_qQ7~-2bJAmf3}DLx^Z6zF2*~-5b{@6ldSE=id|4`m?2Sau%z@1YOcDe#g6fr@xHr(zJDyFagu zZgsze+70~3#i?1^NZOYC=dFD*^H2@vVBF%sB11dLAdlW2y>hpw)zme~zWX$VgyCpZ z+SNWN)6JFfi7tiK70KXeI?M3X!0r0sh-`A3I@y3I$EwAAJFPjfUDi>lAL*GvWWKVc zT)(^#!zmpk#qFS9 zTh4#JD;S_uYX8D;ZkRIv^6V>fODUqt6It-!<~dz&uDtp@H{kAk^>N6y$+Q#%AI~eM z!CHvbUt;=Fp|t%n+i@MwTFFQeg4pX>(a}*ZMJE=e_?EhDo)rt(LARoVDs9(lOgQ~z za;W=$vSg1(&s#+=F=u4p_5r4u# z9PD?Q=hE>@T5`9(Vaf?kA^EU!t^J8pSSqiT~F<_s#V{u!$%5*gH@k6+{y{*O5x z1@_&$CIKvh+(oG4Vls36QY*9+pJ{-R>0;-M^6ZQ(2R3=V_G~Ze^$pT9Pjkg&y*=x* z7xj;H5=tt2QA;VCs#>LGh%W%=E_V=ud*J(CjQ=zIbaaE?8s&c&b-7v1=@89BjW01zg4pj; znyDNb^4Fmzmr8bcIj>J%Z)$tgbo$3;^sAHcgM}7S`&_<&jYLk3DpCg zztW`F4dZ^@OwiM9+-xzA?&)e-%4o&CfNuHK=NP=-n@QQff?{wG!l7dR&Qs*{1I;a}=#OG1zu4e@@J7RoC7h+&rMPR3WVqoqhbQY@hW- zLC*S!e}xjk-ppiJv$p*`!0@z~vo!K^27g{#D6pEnOlH%02M1;`dPHU9JYc6) zZm31#B&am8Y&8P1-chEIE1?k!uI(0{Qrgp&Uq-AM6Jh)0jf-DETD4W_S3ba4DI3ot~KI=5fb{StoozHdCM zK*f|HqI`lUnpVUDQd+3vq5`FL<0vLcp@IAKWVaeI7on@AV8cq0e0hB8o6nP=$_}Ux zEZca1$5hPv=bxU9YvP>SGg8$jh>3%B?+KO&QIrHL?^iCz9lG#yW&_a(X^*WaXM~f{ z=BZgZ!!M<6o?;hYBJmFuX?^KF{GTlgAVJx&psf2ue_)gNy<$IvMTCcA>_?Iqx!c%E zi3M`q&o2>{v;*NJf^EK|dxgzX8YT7W3;t|~oR$zF?{k(TgyH!M+eu_~i>H5I3AP=<(`sFopS?xDj zN}Mrw00>=g#N3mDAFCwW-6ADjEKtk+(r!XZ9(wzf-4R$QI{hBL3{_5+-X1<0x4jMN zhW+&6@{+{ZSXJF&40}Pr7+4Ed#i!wZtAq!5y`9lc|Ef$m=5m^yo;$;gLDzk*ukaQ( zxB$fp`t3S|aX^HY-AfMknhr+tCm&7)!zhGK7TdmLzYuAxxag~sG{MuaW=QjtdqOks zDmf?ytMb(SJVIAroMkuRAD~Pw=tUfTWy(8H;5_9@FSemx-Dhi%>co(cd^Z9;%y7x` zxlYfFKngkK3Wuyx=cyw$$*E%d!-%#q-4@!ueirH|jG|?bu1T(``qAIYI_JLy<%sA3 zMaRftnyFfLfy<9xijpZ(LTaWhpe625GQtUe+u7x=082@jQKz-Sy|b@>s?W`+@i0a3 z%S9}4skF<)r&f_=k&5gF+04kID6P`d!vLtE__k^UcPj&D1M-L)7^YRBzd~R8K9GgP z>Nv*Y4tr2YD2{ZCh6?1*f%kYZC5kqX*{D~IOF3khZ7C!1fA&bBr>4}4N3t+td(iJ= zD5;I>To}bWsoFal)@SLm%eBJ;bdQ>FcNYW;X>eD_Ee59|5Nq4r-# zCcL`34YFjYiK#{FV^k1CYR1gv?K))l-xWyl&12-Z&z<15kZZW5%4UFa540M=V%~j{ zP{3ehGpGJvHO*s45E#J%y@T$;+<>N^CD_#^Ux!j&C~0Sp&leSoUtzA`gZAQhbO)H*r;pv@ z|MI09*lU}jQ3Qa%vW@zgt&vvpyr1<*-2Qo#7SD9ifhX@FGf?|eFl^x8E?^2Mu7I&B zg8N_pksIj^vE5*1Dg+801j#U;1tKU~LLp%kBqJXG)<=b8Mu355r^R{17!pxtL`K*P zRUvLsoGisG#X#aB7onGu@X8Vl)&Cv}AV}+XLNZD=26yWiv|~VLv=d<9-ACWX4GSD4 zi~4$fT^%D+f>hiL*i}D5rmdmJ+65(_YS`Yuz)yf<)Xp*wcZTu_(mRz_2uIT%mIR9J zW{yw@s2F+;CBqh-v(nXo@g2!}t#0EyG8%(lwAIdJ!%Y?%N6uVCp#6jk=-z`I6$Qny zr`dXs%*07jsYXsLFib3&PTbzpQyez>uK23!cmC4ij4(PV#(ZLL@4p~Oi#OrXFPExP zO`)S6=t`{k_xWKKb6U)wXfT6TY}#BVi#$ARn?LnPwkAOfLsD1F z54&*oUI1J;-tYUk+qhw~p(Y=e@kdn9ap#qOATJX24Ge?SkaG_hTRC+-lYb zKsXQL$$6vAZ+|e03ulFt4n~1dfp)Y1_)^0bd$82qmEb{$_; zyRAM2^xS%{>mi)Pk{V!^5wk)!L13|Y>yv~`(X%LBT3b-hf|O8TZd@qPDL-?+?i0IG z+{-o-&pcQE*?PCj3wJTPatKsIU}0<*QqM&9P%1&?jwX6rr$8|bdPtYT=8i0lfK1u_RocQfNeL3AX+7#}&xHxAk7(PST zMvp0xgu&TKXP3eltwkz@5Q^6HB&nop)cBrGF&HN>--nBP93Y#9N2Gj&-z2`lao#zR z!r5$9#E!O2&rUw9<|$=x!Z8M=#4Wsr2KPgHOr(VxTY9IxJ{)JN>_0p~1^NAhI!h2L z7#SYX2NElSIu{lR1O=pu2mkKZvnL=PCt^Jll$U|SuVbjV(ck$OPq>f6x6fkk=>^q2 zs7+eSx7W&dC>x;)OXZv}MUm#%$ntWm?9})%^YL>XksX=oG`r|39w} zJ97?*owH~-6J3&FLRXB5g;qX37{pf1Q*M!k)D3qA#ve2_XRZ=VphgAyCk&JI%(d@j zeRji#MixP}e&`G%O2NzWkga5=_QLo-jCYIwznJ(W>$89=U%AP;6=5I@6Fk9Pah(Km z+G_Dq5Mv3`+JOE(W$v9%5*`i4dt)N+BaoWg6#j_sc<~I=6(>b!W}~zu=uN!Q8g+L5 zG525sg-wfS<)$bK#I0%l>_18pXlScZm<^AsAFP2Q{QG}ThMDfDA{NP6LC}PXa~(9q zaXcAmCUkfO5+CC*>PV<)RY<^-F`h0QD#U^5McX0?Abb@%3Z{`2RCO6!bJpYH>c!LL z&z8rh4=H~N^EeAeP_KO~V+_GFXuBCU5>ygnMR?0#7RmNU5pB5}{)$c*{^E_8{c9 zbNKI+(>cIg2!dH8{A!*FZO9QALsO6@I>>#9hMw6BnE$m8B&$I1p51W{7Ugd|Bi-tM z4RC7}p(-`d&DF;3{%RH0=Skz`4PX_{c7z&Y3@)^0Dz6lX;8~#`c=xT>dJgAM4ZEks z%Vfy3_S6kLj}*svd<+~J6qcYnn%$H68{P2qCZ9hw<=*LIk_s~pcZSZZ9JQMy#QgvJ zH0ZO>&X1lHGt?@fzzvuJ>(4Xr^LTf9F|d+2$?-X*JF()2mhQ8T-C}hWddNK50ey2IFoBM=Pfd(M zfsp0xEu__A(+^uTH8(~P<(t*0p;CZ&OoLn^W% zUYCyc^jP}az6LMrZ(s0v{4}AFO-bIYXg2=Rjazi-)=Eoc&I)RF+1h*Dg&BkbT!jxl zaTlIb=D9LvEtKWq(S*I)VC~+c#jvpLFzX*;IHTo$E68(`ixvcoY)3~a@Nz-lBe~0X zRzI!E-(%`ZRF4h(`r}w32-w@{^+gDgH;F_BV_L@Y`@=c;c+5$sAnAg_H_LPHD|jh* zH0Nw>Ro-XFW{h#GJR{5J;^c&~LfzCKG}`j;hOa>4`!p>DY;uvnmv(_c3LRZ1ysr~0 zL<1>ssvZd8y6fFPi6|O*j8sf+GpBzbMS*}RBNb^d+CaH6I zCc}YDaUr4oeOgt^%3M&!&Br2YO(@O$1ShTD;*y7##VPh0Z0BpY!q z1QKpxt*;WI;ih7mr@A?oVH{#AMbMFWc=+RQTvLhqgS#-*_Dkkk;*a=CbZw~#ps3`LOp{JLL<@k$Ma>WahHNBS z1APgmg|-UHZ$da(pt%NQ2M7%S)(46>ceOn=p1UD|@ZfULVdm!MZf#jrx|A@u03(|x zZ&buxK8Aw5I66L*Xj)!eBz3AaszTxW*5$~@jjc>ix4#o*BKCd4z`(%8bzFzXgLx~$ zmFRQs(+lyax7XPuhbP#7jA($YC?FP}GuB@na1=yuF!ue$J-&+{`0wL-Flt5Ss1@%H zE!&ijt>g|F**ZKeA9Dfa?D9x478VxTst);73N>ZLdOw{h@*Du$>cl%6FPX`4CLbch<5!yy)!|)107&ZJd*3}ZQCx9fwf>c3|gAI*;N@#-@ zT7Z^1I;=h|FD~9)#@xx4u8s{??I|cj_xAToRXP`}aIvO&UeLNyur1*#q&V2O2#JgP z`S^&6ib_}YCW_E}9HG1D&?LC8mdW@m3S0>Es&{^;SJO^{Xx_O#JY}eN;fNEm9-?_7 zxpQ|#^+gV2R-NW&nuCh3MF%p3wS4z|CKRZlshKJkHD^=X)Fh~o>q<`S5msYTj8`Dc z{3OH8%?%^^44g4z6>$Ed4riJT1R~~_A<1&FZw3CSX=SBttVqJ^o=U2WM=|45%U#(!D+y-Awxq1u9(CmFvT+V-FOTYY zjy_wGpd@*?K>`#FeTK)*T(Aopiki+bViR(s=Iav z4#|F548S!&qXV>_Ad>lxNAvOUZ6eV^_ooft4{5R)IG6bZ1sy+p_*hr#X&g>fR_<6) zQPFGA5|HvR6<;lvKbMiL=0S`ldG#4pq4Sfs#;)CTh&L?F(Iv#jrH;r4?#+p zt(WnG-b9h@5xgql8x}1@cB|iuYfOc}b=|Yn!1xN9bD|e*(3EI!639IvcZ*jXk=w z+gn?J?Lfbz{8t;~sE6fZ28R-I2$TeNiaTM=#XQ{((#!vxTY+_>8-lM-+f3b*iz;`6 z5KF~mFJSQd`oMmh`VZ;!J)!_}`YD1tIib;&GiGnW4nSJfkr}tV5WiA1=ugNrTI9|Q zO`|SCh1EHfIhQ_q)@d=&V*?p{{``5sekQ_ERaIpkVJTP(sbdSTUlMPR)|dSG^-V%N znqF-cgtAo;w)EDr?^I9vOj^w@>4QeY|AnWY_je4DfC2U0`>P!WG5VW1vf2w3l`9AD z{yC{KBx`9qoXJ=*5z)@d%eN_UC)&bfur=TQ*j^XC30o7q$-K&COa(997~rYsL_~J1 zm1|QF+ZaU@18a5m;t`fMGmWQ#6i8&VZijHHg!h5Nkd9QEw8rqu8wAO!Wzt-hDQmlO zzIN76%Ew*^1qvqvh{{}eDFDIc^dwUjJN|tdsUrL8cuxwWC3l6<36@}W;RwlRQ)s4A zD`2JKZi!n5+7)o!qlZvyY@XR$EK=(wmH`5liYJtwP!|hH z4*!{hAiI@51)JW+&lg5mlYr$F92QpX1QM-N7*vq!po1vM5s*u~KYZSfe;YZ->ncO; z&qm?z0lzma(WfGzeG(~SZ9SrXed@~ohGC1 z+f%cmMxFknC7hWo@_ce&@{;uah1el`2iUHE$@-a!BKzsSsc6`bnakqsQ~6Lvvo&v! z9$iM&ioiF9)j7#TW z6_wo;AG!VskWZX^pJ}1Enwl)T$U+4PPK}HIp4+bEmy%}f)q*-6fYM;IyLzOOy9DW{h$drGN#cHU?Z6gr+an(FNhEh+-94^1wl$K(b@0lU1jnYJ%P9#^U^k!8f< zsx%si}1918a@O463^;6Se7`Y3d^Y|FnrCxKXq$9A1%pOYUXzt*h$uhu0sY+2&i5fio}1i$oFq zPDL8TgExz|bTY-YBoG8Q(bdvx9}^$-5O(NYl=*0G-I3TVETPks7fQfIo0W9GxjGpH zr4H=(^f5v`Qx4d)r>S^%xf%ORtHG|Z@P(ldBEO4N-}KU;-zV*z>^F^tgY#pf)OkY} z)az5YGkJHt;q6safN=>n}Wii?EzFjP+ZcnxQ(Z5A@p zW}F8i{Z&Z4^nog(eeNiGP>0s=laf+fnj z&gw;~PN{rt1rPh{KG26@J2q`LCYQ7?XaxHoDE;ZE*94%jpZRXrC3GS}Q{mc?! zrliu@x;e4ypMjXVZywHAmgwque0PAcQSeQ5vX>tOYA<2cZs z6o+}#_#CaSyttLYUE01jClN-h1cC9cvW|#|uAe_6S69bzarHujdcy(@KZuA|Xi6LZ zc!?B<`N=qiL?Sa)dyUznWH~Ld*fj5()_48x4$0)td~ZitJ7~-uY>ttc`H+Ld635q9 z0(--MYI1VZwf^G7u7scS=!Mv2wd?hpsM?j?YeTC1lG8txe^i8WKHnIOyCmg**jRK# zWpIN}Io|wQ7!j(Sf(Cd>X+S=gp*h3X>>r9oVcj?cI5Wy6@^Kvv(~Xvb>lo zQ_}8e5e@164Z9p=+O~H)_6a+`uPK%B%6u1MCO=I*sp_U*R`# zp^ptsL5oP~!zdMD`(b*SVQl2+F^ZTwz>m@C_m8K>4hazv3YRLCq&fTzsW;a;4b2Lf zw`p^cxRS;IOiWMM^}Xps3cZiDMyNL{YVKy!()d(P0Nn>dOM9x(K1Y?# z+P1)(y(MtD0nNj$v2vVek_Dav=e6ApIqWr4XC9Wo_G_}K{T@ePI8sb5Y>XEPHEbi7hC=i59qeKzW}!FcljrH&L?~##%@^)>H@;!Zix@3 zMLH$tPTmQhgOk4i9X2H0{Ml(6b~>wQ`s4ooN>%Ay~V_>epMO{5=Tz>ZFj=;U}@029>$$HKMvE{dx5AK3__sVH7^pA|DAa%FevgN<@ z=CG}cRQDJibVod1SJzs&Ud{+~&$p}nu*fC8>YA|nSAw^ytJKu%=eD_mnBbe{w=UoN z1e!nN4I1=dSrR*q5vYvR+cZ$%&g|`Z@uRzK3X?lb?=hsIUQSk6k20o>@Kh~XrRd0n z-jS1l6{B>u6?RIdDDoLj?XZhpHBX{%x2DEs08ylNdP-tr|D=W*kw;`Onk80~?H89Ay^knAK09#!$;e)l% z>WN_dAn_7{fh2<1)PYk5;k%N{G{W0%) zTWctEYPWVdOmLU>lpr;NvagHP-+#m3zlb~OmoYJSQql7O z^O=S}ZK5{`{w5%!{zqt=U)69}BYD0*qu>725A$x3>qlY3vH*f!_8U4Ovm9zy@?LgN zX|wj1E8#KY)lMfR^ZiWQ;|#@uEfR8BWix;%@GdoPh9Sxg$+eP-pOWc-O@?;4SWxOY zSG#9R)41S8B)i<{CFeY?h}+&tBTXeDSfX096 zDolGv&jf4ws;w79;-nUGZl`DK+fu$9!Ylzb7{-=k^Q~*j!`hl5MQHNqWn+RV-r! z7JJ@JExHLV|9uhGh=&<0mZsNy>DIe8XO&)a?PcH#_{j&(j*>txl@{!HV=YE>c-)UjG z0G^`B7!l5W`ywhSIp?&pE<@w>ZI}*EN?J7xKP93vy>)cA$?mFqQ^TKh|?P$d{uR(1RSRNV=6U7l@Ype0c@doT$Ow)PA|m(i z-Vy%2))cufg$X;G_u=cQeufhvUy8pHM6TiqhkFE<%W+m$C?z2Ns$TG_83;{>KKg?c zi07j{V@rLue>bB*G&&Ve)|ha`AFWA0sxzk46NmPOfJ|6Xtp@#pgZ7b7=h4v7i+b#m zl6{UKh=+7QLkpA|L50N*Ox=4Rta=M3hSWdxa)^-x8Fl!13Dws-<0(#$W*{6qP@Z&>iOypo}zNKNG;Y+AJ>* zEnxWUkPKYY#!1gg-a7iVmvrZsoSP4910MIbQlp<$Vsd>g_%u6%; zX*;|Ga>K3HsMdiBS|g?)k#_Khn6YCB`%_Zzov3;Q$GKyAY)1*ZW2QTds^N7bg-YE^ z7Kr83G&rYaJv)&aHk&+{jib;|=mLRm(~N2PM2=_mrt9&32zL*2inJ)LeB2**Cq0=0OBmOen%>9d1x7rql1QML6w9#1=o<+eK%&>qtSs@UC>xB62w zU0nomEOjN{uEy=w_`=%*zkU^>@)4!#9w6V5dHxt2`?lPd5XDIZD%Ic;S$WHFY4cgg z#kiM&D>2H0)S)`1nJm1AK~O2#Npm0&=}Fo%t+b@lQt=sY>u{2`y$r47jg!x1AOvAo zGVOV#EyYR{wxCe^jY8LLz&_G8am(tc{~Cc}Vw^$cj*>Hen7ki9hM^;ii3!d=02gMC zSI*_-$+zv(Qlr{(;h3}j{FxMR6jp@?doDO= zeILOV2EsRI2*}oCyF)T^U~C|?yLe$a$6zB?4yGd2+p^2Rq7y z+$iP@*EY>P8@$+Q-f5bws^UJ$NqZABS(=bMq-GbTdvz%_Nzqu(#_?L?8hPWsp>%K# zIkA=;RJl6caaY}xZE{&W*p8&Q9yhtCcPT6}Vg<3$_|17;!@q}U&LM2_xH`zxd403s zkHF+EkUe#Jgf^_Gsv3I%3T9HG#lrbsSBIH;wjM_=CBA|7%gxa@ce2IqJa)aOIUMlx zq@)tFv@k8LZ^f(&X6I7JNgoV%e~5|FRV2a4jxr-2FQRvE>$6~u4^3<0WjCbGSb7lP z%f}|F?D|PdvW!d$Bza{(&BoCR(>R5=wVuSNQQ6#m_fxUZ{h|c&qdxVRw9&z9X|nyz z{IQ>(boIqz777(9W_GWo0$Dd?pYO&NQj=B41T1iw=$j}etB)^H{ zWxq>NaSpi91GJaF-zKB|vK}!PK>oc#R_Ye?%OkTJ`1T2;6%`j==}2?OWFKP?+qsxa zNQ9`MxTA}-spG^2rzJKHGf`vObHmHioD-;oa9rL@_%fUgvN?V zSeKYMO4G!j;@TzmoZZ<48wUpmm)=omxyv$wUZhL>VMxcvwn>?X{f}?H*jXv^@Z<;V z5`cbAZ`_yaz^T?U2HSOB}pj}n7(Oy)>{89KF$+_H}K zi9+9?$*3tNIIkY&Et^OOIFmuRG_3X|A&l>|!<0bKEAO`!gh4a5>xSq$%OhjYFWtExivB^@A@VZp;o2xlI+KCli z6)IOUpM`v*SzLKNM-Dczx-a)7?~FOJcIAgFBDyfY9<|$k=24Ww->l#yW-t`^TeNib zPHW8FI|oBO6@1-;&4N&~5p#~jzLgC-?HXqeCN4@oN*Yzlqq=G(VQa#nRgZHnvXn-e z1N9!AN;->;{R!+DsKoAlwD^vyM#aRIaH}Rpan+w&G1TMFy{XJvlM40^qS{486Oa8i zZAz$VSvl!hb`NIzLuzgh2L;nrV}7`dOR%kHOLSO-LH&#s!6Kus#=haLFpT`oC}tYC zsNUZwUHzztd(5=FVA~nI1m>-3K0c#*8OlM}ul*%K#M2C*&G4ODI|9Z$Bkq*Ni}CFL0D8+YpGjIxLNv>&HCS4dYNf@75$0Sc;?| zEFu^7i_ZuBvdXy`Q$~??pIp{F9%2gmh&TS~X{HZ&91`WfAyLG1(onKsqEUP-p5_6) zrKkLN!|!v;Eqjx4%`x83joLP(O#2bxkMc1N?IRbcA{QmxwgIy~f1q;=G;q z`RGsSXq|up_K*3I?X5*NnLl}v0?lk6^c8({QZqKd5=26Ajr{(*cR(E69v~K zGm@OKJFHdVAG`tj@SR%@XYy0}EVv!;9bPLh-R{ONwx2zeqvD5%h5UDjHCT!GCCy{V zr#ie={n#e=)2B#^=4?7i;uptw)vO$1k;D{h0^5@u=E^Q>97@!@@Y^AQWvge}9dAUW zMcteho2%)WKGOeUW|6r(iK0g>p>(#yW^YL$w;K8KciD7x+z7DIEBy5Sq*t&~>ZQS; zKXU258iAQ++vm1S%M!P+_upq7i)>bnkyokjGk2s*%XTrP&0Z|G-Jjz2M}L4IpjI7+ zoY9!|h5ef&Flza$wud1+)1N+<23sA$J_wrS-PLC_1{@?@8@lo4_)g~$ui@nX2g)TbEDBq!5pBd78FY_M1J*5?z??m_2NVGx^!~bL)`C0mtHdEuvL#eA72gZ&7m) zFTBhN^2O@(yJ#|ZQdAjOuC8P8lssCY1`Qc=V_jpTeJNjU>}?w17VPy+`=2VGC~)x+XtdVJsei4;pxy)9)Ap73I#=`S0Ji1`(7Q&Lfd7UKJA z4;Bx`kMxIFQeoJEU&Eu}LyMb|L1_)%C!5BW`;v7k?{7D(KDib*{r-h^u6HelNY{En!Kan13GKK zI=&DQvYR}Z(v_6bhnFPKd|I^r?4;u5RTdBbI6TAJ*7ZMc)-qB%#Qu5y_tl-EG`GJX z+UjY>q^9C=`9Os2Lf`6AZj;a!#`|oX$<2_b3!avtb(VW z---_er?+22QzH-v*V7^lJcg$zV20Bf$??D^UohWnYwpA@l{=)~8Vsb#61X^eUi~v-@I50l z&MAbpYFh1&s{a|w+ShMfVv-z%?0@b7%k$+ctsM7&UgYno3jaUTfAn+m?{LVvliDT5 z63*bfTd9e(qM^wg!gS?rGi!Qr9s<&EQg77=tR7sq-sf4bW1KCvs@~?l77-mEvmww` z;EE8A1g|^bz)juSGPpoWO7ng+>jqg3@pu^0#M0jLn};!_l2v+dbGn!>bmb|l@V_!I zkdKUv1fr{ShhIg<9TnG>5zf>*9NQNpHsYL+Mj0n9O58YM#rOZl%XUvw>8QOJge$0> zl&2}fJy}snUDE&m|7bVCrWb1++jcB@&`cV^gBWk}RUk7ZOk~rM2iyPs=5LM=lUnc* zc^=mbCXu8BstNVP#8f=rP-1^|JjL)L^BTTcl7)@LbKt5nf^MG&c%#(XTt48`)iq9! zRV@Upyik|3{EBx^!PI!;>NikTI0l$Q zy9UV#fQMouZmiAU_WDj$ z@%n5Jpt$yoXU-$78u3~17~c!sv}sR?VIJdPHTa+y8mlaFw5_l^W1A+%&!O#O|I&`_ zIkUCh%6$w4a}h7yA^q~nZ+%a0l?3rHbHZy|3Q-+b$D}%TGWt6?9)y(i$6K$SKaQVo zs4?P7X<`9DVJcKB2~Gc8@kd_(YeKMS-#vWxvj6ACxNhCp=Z9~9KHjR!dXw9fGQHHwH%(;0PK+kmQOjq(+Dja$U&lek0Wbh$r||8V zO+z94lSd;6muMZ3dSI}6=-k?`+Myp;en>Ej-Ij$9)n3z7dnRz3)RvB(p6X*dB zSbxTAKtu8;F*J3pW^^uxRZ#DCf~n~52?}{HORSNwbE8HF5aF3YX!`um5gIq*7P?+# z<<|+>jS&WyjLLYn9!{^e!C~b%?WmY_`>`?ZAOAdB9cc5qwPFKeeU0t;e(&`l9bkwj zDtPHo`n&yM09!!D-K!bXLYV&t+#b7*Px^(9I-iIBz)>h<^aO4JWK64BIaGfx>0D%? zR&hUO$^U^eFv{7{2_8)q58+QH-+`9~ibn@O$J;x|JdG{jO5Bp&N^A#`6T2M&P^R}1 zJceiUmyK37ADUvkKYioQy^UU*FSr1)8C);zMgEBO0~dC_ECdiEc$RNqfI~<-^Q{q+ z9I5|qa|>E1dL7M54wLKtE_ukm#98W(uyYgQ(xnuac#!n&Ak%aBJK(O}-Vo^AxxxW> z=HU<|fD2i)Vm2D7vA5mozn8*#D zm)){rl;?zr<)bLUotDF{C8)&y*m;Y=>pz~fZ2O|n1!Q5aS(WPD4j z;trm-((|2FT_$-)#Dg!hoeG!JOOCQiAIbx|5pg8`8$uU-UDzM@eWDoD>_zY8U|pYD zVz)Y!tyI}*zT7~`e~L$3pYAXp zGtZRGAB_Yn<+QmS95%bDDeZRDCoSi|J@UN@H(%IdSzU$=Pp>z^t{k(I;qZq_qdKEc zgmcx?J{s2?2O-{YQVO$J{M4}dEe!jo8$Y-6B>>oSP!pWLQ2e1@ljnRK=~fC)S0B;x&|*W&9D6S#mq3~OuEd2EGBYGrmmA zp7`GR?LX*(2XNnKo0ZWqN?=-s3SIdtk*O^2a3+bwUh1m~#+?Fq3Dr9X2u7M=_>UOc zvqjFWkPyzBqvk$Kp}+Wbd@Ts$+cJV^Tg2#d%GfZI!5F)nqmv;Wd5=(X-0*L8b^h?n ztHPdtaPy`F99C?9H~53FC7(xa&a!)noykk@FK=|he|oJkiuM!bvCfhKr;9T%_=xJ0=oCV4gAzb4BA|#;pJtkbc*Bgo92%C6Sb1PGHMY zItL3RqAeI4-1>kd^EjFHwRlJsh-4_8jUy5+H6|%dg>bn04@AxHO+r))*cB_GSCFMd zQsU+|41hs?E}#6%5jSGSTmW5Ze3=0f=b~P--q%#lc#K-%pP;3^)#`gOvECqr#pB%) z>uyDc(d)=H?jg^U@A3*CrM8mY-4mz;=If6_3vQ5z6alrThvZh}w0V`D1rfk9cm6Lf zyc2cK)Hzr>^YB^`h>#Z5ibIIMCfu#0;~Bffm$e1|1K_}IbAIO{6C9qap3mi^x0Y1^ zS>0Rud)|884mbcIsl?4#xuL=pMzaty5#NK*X4iUpdPG2+1gj6j=@gY9$`#kn1W{G& z{uyJG+s8~6b*_8=Upe9ywo(26uno%4^h{xf^bDl-ZG>$6=;Y?Mi8915mOW4`|6y$~ z3;H=Er4_F07n~ib?-#;njH}8h@6xCelha~{#rd1%NYP&Srj}qF%KB&uuQ#**0Z9%H zmw}?x{(}4%@CJUISVFnIE`(NAMZCG!OkX;lHLsD)_{!Qx1 zUwhu|{-J9Bol)xm?%Nnk(tlh~^Z^tb{>ucwQ0bCu>~E6%Y{zr6MaTusTot7h3uT^n zvj!I=c>Vn|RbtUxu)Rh>MRk?Cw3-mwTm49HD*SaEd|K@<(vJ84sy#n5SRG^)%GB|` zrus`LaQ-*ZJRwLh#o&6J{0vV>Xw1FH1`bOm%%M{ck04xu9ZO3(f1~E75_lyhY+WUn=se_?S6zGSF-e#S%$Dfl7ZSOA{8u2yRbbN}zPWQ6kxhpDL zdj8mX#(z79pEbSC2U*g3R+{#5P!cQm0Fe5WBkN~?&(t@7kaeOM!VrB1 z!#qPcgkg2K?9<$|H`%dyd~puaeNCG}l=bdK!3uS!*mr~hI@A9ZqAJHPfEz_i4TN~r ztUQ6uKN9BqX8Y*fY%*!VX#SwNh=|CdW9_>y9+%{CAPYOIAi}3%)Y8Bsc$G3T^{QbsFoqKD@mV1e087T+(B(9eMH`QukvLl)Wt z(F@A{rk59ehtzi<6Q*>=fih4vVqZ>1ikZOlR(aoOafWO-G)a7PM zt?<-2tOlZ5S4I0SDQ%MAbz;5yLpXe)&erpg)+J@zCUi5LA|cY|*bE>h(* zASHSI+0E0I5#95Hc-5!gpjt3DC2fiF@Vm{j@89$J_3SMPE?81XCc*!vBUpa1PRW1g zC9mwf2YKfNU#4a3mRVj^S{7TgOjsTD2=s+PZ8&9ZGXD7z;}qg~QL`!Ix>@m4d8oRA zyy#L4G2lD_idM))PynE1>2Y-mN+rQy^>DZ?Y#(t>xHes=OqJnuvpe^K>bxA=)?vlg z3A~!!?TB!odpMI9rMPHpjyqdF~~B3EZ8N2b{#$ zP-Q&@>M5;|8%8s73uSfPx;_Y>urT`hBeYqIAtbNYrQg7TMeCkCY->&Z1NbX^Rk7Su zFn5(GFD&TwVdKaZ?Tf_YWUxR+BxB6#pZI}Y=IaDa)Py-Hmqsh&To5J=yC!da$`bGP z#F|j364~`TN#%xShfV&WK0#WB-hkZNtgDF0X|3JnaTb~kfe1D}j8=Q?4cO&Q8V-}k z#^PVDio7K*iUtwUaeJDEvHdWBk*LRu(vcuQ$>1uT@cw$JRlqCKM*G!;D%z z$Az0VLrS(mZ_A3GX8<*s;MGx0DK9ZuMj? zu;aTJRf`*$=^c%0I$sBOgTn8jvtr)OLE1J$c2Ym$@f&6+1a&>}4 zFb^VRi|GFScT2T$(kMJiiAcoZolhDTP zEAf@@2{*^K42L%-Y`e_6l9C02wX{(V^65qX5s-Pl7lc}Poy#qA&LUQ4H#zA$X7s}b`93uQiZjMdX_Ov?K=?>m8}WM^R(KgP(8OA+4GN zWCGi3+P%Bq9HqbLeEwD(*(aAZAV-FSw5y|(m4%)b3oZZ6GwK|u#3=WIP>c)SZd&|r zUx0(nBa&j%&M*pL*Uhp?C%wB_8nHKD&?KCmOB_dpE#D7=$3=`W6h#D8u^=M8`P-9r zmaX@$X!>cyK1UyCiLKJzV2vQNz%~8HZrR$}3XC@nQQ)_p6%TRElx5G@Ame%}fq{>F zS3mQT^QVJ>;RMhEKE*SERnY!`iyK{!#L#Ls|zI11inURCY7p6!}+Rpo&7Emlo+ zYrvRL$j#05x(QorJs{9&`Po0x;q+DX=Ng|3{)&MDW(BBNgf)_K)cBhDAmI;`3#^Ee zr^nr@+`;+F=A2Gr1R}vw?EP01fD;$H|K@FWQGNa+Av6gAm_kQ>EHpuM>|gBII^ZVDJ|{% zu@0Fa1o_XBJY9@5Z)&sO4{X;bX)Q|=nw0~|0;9>-*7)|vM(^$eIa_sP>+3AmE8+pv zVDf1MbTO0f*Q@!2SP#u9ItG?k(?a=N>PJN@1^LCfeDBqj2}>E>~ zn99u>Yp7DiNI%DC3xK_)k4H^GJw~Y5|7ILck|W_+^3YDwQqAc7L@JAPbw*Q-SnUN( zTb@lm+fk)$`;faPo3}LGbp}@oMTg||@K?6m1NJ|_A#^~nfER|`RMU_XRfbjc2!_+e zNOk7$mh>>@f4>T`m0k~1t@Xt;5fg*DWf>Kmjxz|oU0OlR-*+`AEOqL#^pCvdSYw~R zf${HgTg92GaDBS;gIyp|O`AEgo!-dWzCi8e6x5%~6U~9Et&V`|gc!UP_=!%dQhopR zRD~K@QO^98T?`vWXCA3!^Itb-;uC@>z?h+*X-XB|v_ehl$s933;B z*@mj$JRdt)(b6hO=5?44oV2CTY6Txf4%9g`%Zyb1$t58kA=)Y&wIv(H!otGDw7AfE zI|fRbd=B%T1v9m5D_YKVRRuPgud3y*kAn_u+)u)Gho26ektEmJuJ~O#utcYDc^o(H z$Z<*%^u-Aos%c47`uO-%h1(l`Fpi*ftsl3=S`RKPp9pV-PZVig9>3@< zsB9YDYgr)O<)05#$=ljan%f)HU3nK)M@!Gfl*&OU!`Lfs%BWTCp2|th7`(t6yHwLv zb7~$kK#%VZx!Y2KoqTCW7cn&Q0?q8E^Ob!&TE8kTQ8Di zXQlA3oBq6wHisJ^O;B`#IB8MBuPMK@vnxzh<0X>oNJ_Rs$D^PdYV;&3AxdIo`uVC7 z`TY0O#`BZ+v<>;|=8YGR_;^Nfw2;KgwSbeqo#$w(b+VOqt2#H+w{>k^ShJafi|o~@ z69$?*n-hOWpr0M=DosySMGba!G(K9n^-7d9r%6*Fveav%7npn<8}xf#Z_nRX9I-{H z^L^kP^mK|pAt`X!1jj9vBM>&w1bM5kpS;r`4xAZfeG_$CC#neCndag^#_9bF5R*eGLenbTE?Es5;RM={&dj`u8$xB6$7!r+LMdrrqU%6@FG$A46AQ-~j5U)5`K| zi%9!l$~xcp7+_7`q*DYp2J0q|8cK3_=&8Q)?nAo|;K^^@Dj|2{M#?tr?O0xC(?{as z9;G0&KiyF@tD5ae)#wwM5v`v>cc>hr&hXbfH%_Q zw`vQj{Ovi}^XZV+Lqj8}1+oS~V|E&elTK>Ujd7SS>bZ@a26i!3qESxz z#>Uk27Ou(tmZf~hUAADu{sLreRD=RfD}T{T>ie&fS#@L#HSv=AO-t3`gc|#0_7e>Q zQ!1vJDfcM{C0VFVi=N<$fo_n3(RIQZ=jmQG0O?A0b<~`K0<|{q`9kqk=wdis*@{qT zvmEz2)xG)>7Tn*`=EUa;vCtYN>o|mD^NDQ6Ix5m9X-!(#_;u3C*%N3REc_T_>lHuJ zoxm~?8*Cyi*=q@?KJ(Yp-5}!3Z`Qt1xx&8LEm?L8F<`g2z(UP*3Kw7D91PgaT(>qS z3hZ%HDdbLAlM!>vZ*gDPIOSxe238}8dIOcO2W(Hgdb_$`pTIT!Zx zMtMGT&B>bCkfE$@JVbqg(-~bI&j+5L#|nrNVY_(^8292+C);jvCvNHXf_18%Sn8+u zF|O(EwJ1GF4!sHMadYUUdeE#4S@?|Ui?Jb*TYa(^H%`VBC8Izzc#?#GA%%KwwD4b8 zRBB3OmPtFl+B)|N>Cmr!y48xm0}&HdP#$Qg=z0c)HNWQk>dpdF=xhkj?CO4EOh4;@ zr|G;)M-c2cd1FmU#q2289eBx`q|?ghYG=*U82T6|LIYJ&eL59aIyew9qbtRz7|zGZ zY;bB@%_r!~RmM5?g1dZbVNk$@;4PL3i^&TU>5nL3eS;dDdn^2w+y;17*iQ)*m4_I` z;5N#HFH(|IN!wk}Gu1uf`?sN@(_y?fx}|>y_=McM`rvt;JA5q;qB8J)>ngs~&&N-2 zXhVaCtRx<=|B#LM_8zva2AV}>2spFY0g?|qMU?4-ra>u7G8nDujQA^A#Gs+& zUP(dI5|%>txSDes%g%zI3?%IQCkA zuCUBNVO<1U2||$;T_uHpe@K@e+J)L&^=C^~;-LGQmXBH*<+vt6rwX%<;xXEST9LcYKk0u*Y9s0W!cS)TFGNioZHQMZ|nDl z;?(iVSf%F#Qu9bkwd(|HKI<}qs&Oi7Qh)aNMgW{6eygy>mVCXTq?8@Q?2me(;nUty zoCFqYEp}HozwIFc7t1xLjj<*A-_AUtYtnxZxkZI*Ehzs z5_Tt~#~24HbZ-umqbW{_&bDo5Rtl5o{m}pyg`cypC=!S%+YOiLnAP)i?Z{H>L*v;iR%A#&t0`dVhprX&{((bf#S;LW7^&_*Lo1~BR+~!qraZ#9k8zYYh4u{{w%0{zL2oZ zzvdCFeT6q>L6tYZwa`LxjZtKtqMV!zRCb~Ov`FF5u3O56K)2UwG5G!2e17a6xSB9r6%4N z9e*FU!|x%xs5)(Ybj0?q3uE=nH-Nwn>u2G~P8ldv;qcy>&9m1|Zg&(V#knP~vxepj zDj2qoPC?i)swmBd|E5)j$NfIeLq@+2ru1S{SzJ$dDR@$cGDS&7bm1<3b(Oj7)tuG29Pqmp)24c-&!H#{&MW7X6S7>WZ7iVW zq{Pk=X~Gx#sn*2S%W9_oN(KGDf(dA#ELJFp#OP4#gi%e3% z&mK2Gh7aH}=KaYA=3Rv4yC*%%VEdNsaZmTg@mRce%jvq`6HFML5hv-vYM-{Ywu6;V zoLY>Gv?W^4o)etDbs*m>C7Vm85Omkein$PcBsypKLB85Hbj0PbaX`lB7hPzypN5QS zXBX|epGj?RlP@3VOjuViw?TrOm8&5<<|auX%z@=tzcH}k}Rs(vo>$OrWE7C-o`L%PSm(`y57S#*M6Ik zDpcV5J##tLNr>i7Gm}k}&B4*l(QgUZkvvqv0KnHf2lW@23A#KC{M+l!oUSiI-5K7^ zB%iu1SvIfy(5lUAsrGyLv5I^&PrE$%zU8~c8jDc`f?zh9lz@%)%_PK40`XA-@fZ{$ zQs0h~5++Y^T)$~;GZ~N_9)&{ZhO#<3I{dPkjXoPjsvq^2gn5DY$c#RJdnwb_W|AFb z2UUX3MNU0OK!~Q*$jJv>czC$Eb(^L?vrsWfGNM0)?kZDkCrMCCBM#JWa-q463hgF~ z2x&Q)-pBPCPAFnSS_ef%8TqThbt`1Y%Y>>yh+h0^bVJ45vUJha!}=42U4AOu+|^zp zt`F0Nn;3)k5NRtbtDyIE<1U9~3h8qJ#l`k6y)GfsE1$$^5UE)%u7y%Nc1FoJJJ(-` zZr}nGN;L;?4Bz{2AN^oI>$=kA4lL=kTD7cmR$kUq}fb9XY=PzpzQ6t>sxPyWie9;@G=>w&7;j zXtl3$V18?ZgUD~yV6u&&EqT2oBMcA$8$v_p}?>es!r5l+3K%P&u_I)4F$=Ao!XGDqU+NbO0bgG%)d~4FGJ*4YX|wY zW81Ja3Bkk8UpkL#x~R!(&nw0b;3PbhW$X=K328zb7O?m8^8l|7=h>?)08Kd#d^AK`b;4@G8^cG3H4+VQO!Tc>JNh?3`uvedLF@$Ow7lc zMMhB#N}WEg-taMU^J$c}a{4so0XpYWOtA=8*vN7_Oh7xeUs(H`hsmyT(epG(mceUb zVmZbL*Um`~DQvaT9CLUri3|{ApGfU2>$kPJZ2|+$kX{AW^2?Le#{>)v>&QuhxVR|A zwW_wv)eXWWtb`$jixr>|-O~qhZtYlEh>a$NF`LSPG>#7Lmp;N|8-n8t+~D5b9regNv}Q^*1nP|Xa$|>dc+(A8&o6Q!X!EOsVCB?97E<4A z*JiDajAxfl5Z;%IC#P|jv@tg~cXf3g^10n@C@ahN8*dX!ESa+m$0|8`#iY+=O_ULF z>po=KEar1gvNpk3EZK;AV?9XnjPhG0wMo`Gk6Uf0R2rJub}_vdcJA`AOj;i%7iKjm zE_jv9eyHw-Znb$#9ZXLeH}6|*tL-p3*_9vN*l!!_xbNo_>D)ebo7L$AW4_4vVJw|Sag$*WFW6ORg$PG6B9P)Np^Rr9BqOrYKy&gM_ zahw7tEUs~gRXw(|=j?a<2EX>n!ypBfN)nW|oHPHL6sFX&1k-IH zI_Br!*Ce%>dXda=zC@D3mTos)DkX~DqSPVi7Zgjrwt2%>w~nN$;zMw-g=}$ z?H^mSO8%67(5?qM+mME(@%^TGoORj9mnW{CJ9^hgYQpX(EH3;h8zfjbjW^d9Ue|e7 zt8pGPe@AC#$lqQhMMOBP=1|}cE=>Kz)k}k|_%iJ}5Ow$bT>SN`ZP}ce!@5 zSACb9;(tvV*HOI^&C|#08@f+l%=N+E+}xvdXEiUPlbO9|wlEqSViiOI^~Dd`(sUBQ z@kM`N6&RU1T|%7Tkx#F+C=eaGCYcP`Jr7CzR&iz>F6y^r)>Tr0;m$N3qF!8y2^h?` zE1&^SCJizYc)X5Ij~^RB(cVAx=J*WAKW5nCh0`*lK<=Qm!-(g8Xn5 zd+zI;L*pI5QD`SsO#qCHFZ+O-y7h8`m&qpi-tD4Ur%f#tNBhzA#7(h;1|R-0RmbFB zqA*0=q1j=BGD#2ZLT0oz)3JQyP^(F`x2VqTkl=9f?JqWT32SaU6zb+5_WVEpWUJ#L zYf=lLeRX%}ed5{;WMS701q1V_H7)ip+&(}7=olidkK8vohk+8!5@t5c0^O@K`?IrN zIbFgNTmb{{(J}T{6@8m_DiqwPZC{IzoG%{_SRBT2L#_uu=@49c=YJ_>PF1DYf+%bN zz<=RO)6E0`ba@T91WX-IT|Yy#{+{BkOC|iy8Y3^*R?vEOZE8b2264%$iLJ_>ERIk4 z4yRh^q!Y{_moDF28INM%yeyA;@{#?Uj<-9m;Je+fK`6M41F_JZvTHosUkJ=2O{w2X zUgN&bWmtsS@F5{ydbGZo;2QH199xA$ZL7!ohs4$=8}mqq^M}ak?91Gy?P}W8`dDHS z@UL8g0H5XiWc|X;OEFwe_mF85wiQFIU{nCk{O}Z_hU|!6<9^B-(I3$gPCsFTm1%vDobG_M z1nN-J7w& zLX$5ZrU`mdm~VXk=iHE0k63LOxZ(tPtTKKg6~f@KvSNoQM8Kma>4YDxxm+?V*ZnW~ zKLMvp0mGVU)<7~CfMoyrhs4|I{Vc#%n8szUbLonH;x~g6(m=;Op>LCwL4*rT#dj|v zE)?3vU3$Bv9~(on6N~C`C>JJR3~L61#_V=N5&)jm$;1fUlV=%1F3i6DgRHGr1~F@o z?x#>!d79+eH#_G_kDwmltd-A-00IoSKx6$vPs2_oPx%qRL0x`jR{2di9OySb(sHN; zKx#7|DFhjTFGIa%+ZCg|pF&XYd={#Kz1xoBcQVw?SofVUq;a%5H&_mF+r~2sO9i;v z>bnd_1m_2}+h%YhzItAn8&xYiBSlE(Wct`*=nOK?>P{8Jk+^<9H;xhpfbIa|!O3+N zfF}Qdavy&$X-6~Q%lTm=wIdd^p|+y#%1W9PLh+-SDC}n|XvU4Vltd}wh3w61hwO62 z45tnT^#h*}A-5ssae3W|fpj#?pGz%B;olptsrL-mBo@=4C_1|uM8uU_$%cJ4H)e}` z5+7QBU5%_99XTYkbUsThE-aq?X@cvla?kLHg^ff3!}GnNt44b*JT|1@^3z<&qo(vx zj1!anf_W^~*_-6#d#HfLT99!mXB;sXz#fvE`{NNI^^aMRvx~&Z-1Ez^#XH#Uo`&w@ zgoc-R9MrPB6m@lQ_@-6!ps!K-XzAL{eaqQK9&vHxco~PzcV9y#8EAfxQ493uOG_J4 zj=Qt7uTGwI75Ko=ZCAbF)lt6B40R*)uUBpF_$UP;P8-Wn6!`vNiFBhZ!r5q|FpM-7 zI}zKmuJrq%V1MAPmSG>%%LmF6x~A5F=6*;%a3o}AZ|-Px(a0X6MXa4>PQpSQTRh2Dzdt277zM-{zC65LY6c`-r^q3jaJ0Z^@VEO!`5z|lJM?x}X zbwZ7HkfzMvCPyf$YjwCp28m1e?HJp++FP! zbDwoLY=4KeiEQbE=G!>3BU$cp!m?E}S!M0pGsMH`^6<@Wcuc*&S}3LUd3^iQz`UaA zZneDt9$O~p1Z}%YznA3f_XR>>`3eA|8hXR#s*+Nf&ix_K^MZ~)s7=wrDP&j4T3Yt@ z7Yr%VF{m6(3W_VGXx!4GYSoR@fI*eM04prEemI!fPXMfpku*occiTaIg9*coNM2Y(A@^xvK}+jt9= zOnqTFZAF==x>axf;r-F9n@umE@CkL6Z`ia6x~XxiDxJA~X7{ZLe6@X3*LbzVjOASC zg{jbxmozhB(}%c;M8Bd_5<(#Ubsjhc!vMAg#z(NV0vWGAfE$tkEJt1@@?Im093ZwX zaOb8d0mqK5WTu6#g_MXx&p%w!n{a>TLGQ1#vVKI&EH=e7EeCxtXKtn?q`1~PL;&fN}kqPY&F$3JT`XU}LRQF{RCV=LV0 zywiOI#Lp={m{&hnDHe{O@<-K)bvn_%_G{z)AjzzE1a=TrBF|EQ_HwK&oB$#V_^tsR zDOZ{`-_?InxLsS^4Cz; zfac*HyL*Ke_&aO^cS?fK2T^^KVJY+U+9uE{F!cXceqRP`cplfI2*Bq^B;niBs_#yo zTqImJ%q$!pvID?Bz!fvUw^k=&&gM#?#=H5%4z<4KLfQW2G>o`T6=d2?G;4OaWyUIU zhNvD@H)3==3> zV7`rk<}$Od{`&X?w<=!#<)CMNZ+8;be)gh*{e?5cXILVxT?m4lk@*_+H0oTgX!!VQ zN%x8Y!&g1WD&>~}1rw{1u~X{ztDbQTRgt4_{wiRgrD!&gynxi8ka|Fq0=f2NcAD;~ zb862%mT75mQT&`k3gYdqEj^pCaNX9r7+XC4$F69dOWv4HC2&h6_ek9@&MNIO~G+a(JRg1c@O7RQDrs)${f5-rP=HD zk7r7Se2brPXGFgpHkbO zDkT^sQC3a*40-HrbrNRsbU6gwp4d+=_&)XwodY8#tl2VQ4)E|O7ivSBUIBFiu>)># zHI`3K!i!f*I)Tq%i*H4NJr&?lVmsb_~&q8h#z4B;D6#2%hH57jSf7Z8Z372-( z&gmFY<6F|e#G4J+P%^bxotWrIOU|GlJ6|NRSMtE7LSu6xnJvNaXw@>+o}HqdM(8<0 zjFpXVVfe61U3y@ywiUS764?5cphbV*dOf&Iah-a#L2}g>&f(2(6}jtrq)ici)-)q? zeD!`uyw^luQr?YAV6F%>wkJof#FUWyyLyhF>}Fqdf&7OI6;;OP#babsuXak*?hH=e z8ii%U*U}EEf?PVXLQ1SyH0A!hzD#c2pbbsO68WOC%77eMZ#$iUkOOA+gVb66d>@Qm zZX4{E^FTY_j#ZBU32`ODd)<2eM=uXgwbkGU1?e0X&kYf;U}v(noB5nvE6-gci{5@R)<*x zI?gvxC*b1T`sr=pqY?uy3%M(F$r9V&3N@J+@`v2!XA4`E?0YH6soRpb2%$5LTeCQc zj?|7aho6n#FK2atkCl)NSR#V~JU`zA5A*Ws|& zuua-}E2aN>V)r;&<>dsa81E7{p9>?(<>VL75(m%eOJU9`ay0R}8tVqZzt(O~%kS*O z8pt-M#ET#G%-P#>adGX`j(OZ=9h3RledLzaW186r9s`+bUuUV5O&Dtv%Fy883mQ37 z1_f+@m%hIKmcU>TLZd3?B`91IZu6G2*)DeWSE^y_NeI3C7}#2EO|eg+O)q6}P)YU) z{IG7xGA1UD^U!QJ=a~O1&axbkw&Y1mhLj{}>im`T#l0+@ChPDw*E8`xR4PoVsb8Wh zJuOZB$iC`96F_26WFD^ZbD-ABf#=C9>0Djv@HwaShkX6l9rVzOhJ~!hVWD{(hl%`< z-QzY37l(Y?SI1u$`=d65qktR$9JEa(O}iT0wv~SpYYDx^8qjaYF$O|q%MNJR#qKQj z@*f4ga%SbNmNH~^J|ebih)6DA6jmkRfPaEn1BCO`$H3AGZBBEg^ zRxeLvzHFw64i{VWNdzNJmcFHC-tzop3)QZ6D&hl8%D{KLfJOh}#t38bNdQ6_z8XEl z)f?tDf<%TGZhv{rLL)jeSt#t=XF*lG_T5dy$KKkA^@U7QY9Q?IJO>JGwG$kNR@ zBjQnAz+t~r1R)n*{SLRD58ji^mu|5*Tj%!7D!*|4D-KZzKmNa6qdmnScZv2{f^dL z!3i~dvh?wK<(HWo+jFHgdK1Ivq}A-eXq;=CWal?<2NEw^+!p*TW+WFP=st^8ffTLR zl8*PzZEu~RWnXN%pbodsP^jDB$3L&L2@B@aoHV~3XbVL={juk*vWow;7A$jXdi>zM zh^|)NrQG=RMw8lFq+e2u-qdydd`)~vA`N8?Nnr!LsX7}OdI_f0f$d`GZ!sls%q)q$ zGGJG2Z9HjaCW+Rjc39?6>QDZMhDQpzJPca^`BG9UVCp7N=81ZJ|4g)9u{C~oZTEEz zHZ?`JAg^HZw{wLN_ejG~=HmO6OYgPp-71Z^vx=c(2Ogir8+3(aGF*40vT{RQY%>uE z3#gh8J*uD}rqtrHy%GJybYS~E%RXzvfd%Jt3S5Z~sDE&XNt zy49G%X_ZE}dq*H`!;G3R2~C*0xGp47$J;j&5p`qo9%M}4*Jt8v3&cSOyY)`M*?fL3 zdI&qx@@vw7W9X{9+u?^;$*Jo`TpLHN!Vx`2|BM!2zzQ9T=a^K84}R73csc@SIF*9s z;a9pDVjg6aD};EUEz6U4R0@hs?lk2F7>QT)+S2~A*&B2#93HZ9Ku-&JHfvRhiOww~ zfj0zj>O*I{ed$bS=Ww(&sgtrEzILSsb)N*pNJcD^fs;NS$J8Yhx4TfoM z3{~MYEaUfNW^2?HE}Y+auA{F@WuRBuGZZ5_9XLJL+Hx*|DyDB(#5{re{+O4eTMohk z+e#VU%~`g3L13g~_?-0j@errB*vdp7NJPZL#Z^*EgjNlHbpw}*^*Opip}+m?)ss)p zcNc+4PA(YXy^%U&=*vE*pbFjIjPC6M@xf?a7C-PAUP;{EF$#CdQY&RG8!t6q;Y9@_ zqs1H;Uw$B2j`?PjJnoQm=hq}F!k0=k_J+(f8}@|vdxdC*Qr*>4xjFeYN+UpncI-}@ zGQRD@MTx4jRO)$Dqb%of7ct}-0zvg8L?VdP4 z67Zl&Zf9AvF)_Bl#DL2Eu|@a#cd6RpE36QKMCi{Xc}1B}yBVF4(#+9~66b@_@KRPq z=m|FA9)8^OZ_<>;$XAEEJniy=e z*WOfhF+ccy^yBz=!HI;V+-7qcIdZI^w|MIM((Y?z>mRYTDnDPSxRG)uctO&phY6x2 zJdz15mU3&%`-z#>I`17vtyJ5OL!SemGGF_setA)uN_g8~h_={%qjjLRM&U(k+;>R- z@QLt#g+q6c$JfZVFf!wR0tR|U&1I0=ed_Kx_JR4sfk*DU*I8auiFv>OBOxgV+@^qr zc?1uJ_SLUkJnLe`Mjp@SIBLpD9v_>q>-c)wCkJ;ifvbCM7k>o5WA}O7^Y*>M68);% z&f-kr9r;kTa@Oi_MDutuha{zVAm-T%WVlIqd<+Z31ZDbjnD$4yjYaD~=|y*AxYL|P z_-vOXMWZFzLv)Jh7;}V#v#V~{gN8~sETh7+T^=dO`&bOe_U{~!su3kL-E^@z7^?WI za&;FUt+6;_&JY#tQ6_N6EjEiD?dLa)7+Eeha8nsLg!?w%KpRh5KpjKlzAeo~eZi*R z^44&9(s8MBvf6d0fq{ZN9rtc>MQf{@JwJas+=Y=tU(@XczO-_UE0&T;Y4EV*x2Lg+ z%t&TQH`$FIvd?J?s=L~{^9tc}*2VTA_vG>u=!V8dYbsew4cKrK#j(ucP0Nc6KwY!H zwCzZ|5l_(3y=qLt+AL_~oShXl6yxWM5@&BeoSS>;F`7L-uRW3bxpm+? zg9uJ`)I;7zB8+U`vI$4)*W+96-fNhuc)WsV%aTu0A=S-Qa#ONHtFDw)#eUj?R(bth zU>8kA*Qs^<5o1`}^3J`?u9U88Dg#jut4+YjLKrkABD>Vq8N>!hFF(q!Pf~bv@RB3V zBGBtAFMKO5-qNqhX9;@2l#&0f%EWfo<&HV5`DC{8RAqTUBgvA@d`>&l{Txbh zX*uSV7v;1AVdbsVtISe_g{tr#K$Qo!i)V_ojA3loF2qUO_&fm0ry{svM2(D4MxH+h z3u&a31DYgmI!*}FNit*nBb)bZfb~dG-mL8ti$(Ded&VSd_l2lUTz$Gb7hi9tvuv}d zR>tdvsD-t6!xw8GJ4+oJ|6Br|v}}P-r^W&#CCPiVt6mO-XYX5gf7Iudelajz1A#nT zPV=o z3+Yp*NLaDz=&<21u0g*yZH}*RxN(21kpJL$S03eSuAlt9nJZq!UOOEexLKJTTq#a{ z9tKXI(y&6(t&RrU4`)(D%n%4nrBmhV6GyH5r;{5cmh?XJSS;9e$Pb%PjMNlk4}EpQ z1#+Jn7RoB7c-f~Rz9DbUqsQi#>Np}`CraegV&*m*AHS`aey=88%s{!r{G}B+IhCr6 z%1CsTyz7i>N$!n03HNrDq%~c}3Yptad<~eYmj1Ir=457NsU?fJ*_pS7&YC=2e~@wT-UyuY!r;7Hv$EUWg>u=3#3K>Y0dC#M9ZU%&l&*7raDL-ly= zQM%Fu$NRd#N5dr-rSDUz$cR5Sm=Mr~PVQT?(#V%}nO4cm$Rr-kr6l)8llTf){8B|1 zI`8MnV(C(9?UwvJ!oFIusPVLi9F)Cb6*UfTE4O&7<&k9{m^Ykr}w){*2)!L0UhM=G*&*$af9zVe26J;##PgMXYACHK-A{2(Nf4Ov%B8O(L|48DswxTZ6q*K0A zma+I=?Y&!lzL^;r)rXs%Ge@f)(HfQZm`Zz^fC4)1GSOLb65_X#l7eiWon7=Lb-!!m z?=kK#piK35=9wy|R{PlKDfEjiRrV}>3%?isZj;EUCbG9PLG8}91Pdf`0b7Z=^_uPA zqwb`17Vki$HDlOPY#p5S-gQe$%b=&2FdHKt`{QoEFgTlynR^h{_A!|0vNAVbiTwR67hEvvF`smX&C%oTm8B2MM|Jdk1IWX|!(*nJdm5FmEqI|{ z`s{?wraGaKyW4Xw@_f*yV7pHNL16lNUm)4Is;#0B*$E>i$9h4j#|g`&5;`pRAFzC&u)ujGZrCM0=u z7#1D5l6wRs?KV3dD^Xt^yEgvEjM^BzcbwC4`Qxmr9AUmY*jw=~y-7>4U9bZL!Y*FI zR;ewqVDi>c#%G;USGOFCmuR7TMf5);tk=$Aulze&a<(^cxq(Yvs}kV~1Mod9`(Uey z2~ySMoI0Jz@125hBo2=3n(on?+1sV$j^$i^CsN3Vc}gHslhj}|3I`%t_4#3bkn{<5 z3|AN}R}Kv`@gBqQOQ}h&=ZOzizcjJ+4$g=dGEazOZe7{p++EZAnIr6k!&qezpD~uv zh|CZ3u{gw-{Tk`WJ*VVe8M7@_vYJkKQE&vDTsc2_-?!@PdKgm@Y`LB~Agw0}D&$gn zyDjCxJzvbanV-uF?GH}TDcxl{?$jLH@;(e%%_Fx%OrHGWbVPiL%!AWYB+)hOvd2OQ zUG)Azs%mmU-91Q14^qx5l3tt}a#k~vKUkJlF1IZUGGlJj`;(`RaDWkGAi<9U$HSfIK{Eo7?g*d$cy#`)Pp}se%75zrZG>P52jy@5zm5E- z{(n0?t?!Ly&jjkL8BIW1lo>{jZt7V;8kr&qk*Sn`B517kz*=)8M3VW_a-|u26EE=@ zU*>BUFqIY0IAo*$48Bhoq(544zH;l)H}gUSd$4R&5gC2lm*s%##RHnO0pBMllcM8g zEB;jenEgFnz79m4jO8}1#M$V_>sM4%L|y(f=D=IR&OhTGSVTlb+>gOkusu@IRJ2Z@ zs+Add6xJr)^6y-g!}AF4d?Cj6J@tQMZn%m$_9O`xS5%B0im&2#t9 zj8v+Q?D*hu+B!N{>4kWCcu*io75G_FQnK5RTqi_;B1lK#tb*mM@9zi-dTjn|mz9;Z z93ON`{J@Cts(9>u zjp>(g2PUrdT#_1^`X!uCEGuyK{6|%+1vs_q~76Juj!|+e~62uhcU%`1IT`@n|~(V z=Wz?<&&=8v)N_nD^@H9BqnrHtme2jRHRYdLQ$uB-bE4Px9goe^{XPS_rE3qkLdQ-_ zwCyze(*b1gWQKx&vpQBz*>?acx99b4Jo85UMcOGn|1V_s|I_+kGW`#&|0Qcl@}ipZ zeL<~L=IPsdyVW3HCw-a>fk-U$Hkz<_|5pdwl%*>-LhhYbd!1DJyn*=3>E9Tq6Y|YK Wv5FfkP63236l7Ip3Z6Xw=l=nM$=NXg literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/images/cvt_main.png b/docs/doxygen-user/images/cvt_main.png index 26b763319be66b2df6f9521c072e6002ce15b211..be2c390f66a5853ce206211e723e232dfa705104 100644 GIT binary patch literal 115021 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 a@5p4<3wBEXrRT)CH#9K5R(#bi;(q`X5wM>C literal 86648 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 diff --git a/docs/doxygen-user/images/cvt_messages.png b/docs/doxygen-user/images/cvt_messages.png index df26ce71ef9211606e41b88b813adbc2b0609ded..956e8a87c6c733fda3ee304525bdfeb7252b9e94 100644 GIT binary patch literal 50122 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%0DHwWt7_DpHMv@md*NdcuN&YQ_I2)|Ty$(K&FwRoA zq-0RR0k2|V_ZX3W>_`MxPCZ}iG>Y|ip8SSxt>E0TqU4nsg#kgI2Ndp(aSj z>D*j%w2ra?Gdl`(|JV}|`Zo2ww43Q=WMt^IHzf*jpgs3N2q`Tcl2?k{&FyUq4u0C2 zwyaqQtj<@=dWdh7*v}LsF$L5P8t6-20mB$7y?ugppIoAdm@Ejp4U=j+{u|{Ux5j*A zW2c}+Y=qKCT(_QiSO&nK--u4UWnBS=U)yoj%IoFum%wF#>NJOc&MeQl-Z=nvqyeTn zkITku2CA;JN=UNl7%nxS&!~Pf8!@>}iHpssU?5vPIYS0k=5o(jba= zjnel^~gs5Dgeb=5zRyI+xKQ3xDH*{`_=A*M|r1aJi{{DQOEs&DFC$+vf2CXv3m-f zFrfUZs(L#-vp_RBD~bt<07xG8NAEp0id)$vffPkFjKG{&vPfRZf~?yv@&`5guZ*R2 z9(^Jv0QDHBEBjOum~|Wk73952B9f9A-D%p>{-}ZJuS8XT^ODyl5BlaAiB=5~V$|Bo zsG@^HA9w*-rOh{-AtnEp4h*f$q=Tb$U>wmXtXyBMr`#oqvfyVrCY zC?>rH#lqWk1qT)a+%`-~nyHra%lx~m-$i+)1VJqfz;J->R5@IAhHMe6Nc z;qAM3p)QDQRx{l8STa7cgwvo^Q8HlHR~Y`kO3>7;V^o81wnK=(%wgM}0VMZoh8Uhf z5G1+>2O@9sPQCJT>C6$9Z{M4eT-a&V*J?e<_cOy32xM+B2+%KjuDiEAA=^xi_tO@h zR+}&Q9f75Xg$EIHW6Aj(_g-Q<>`bDsJ*pJ4wJ5w^`Ow-6dZjh+E?yaR7aA1=VOX$dAs;jN=f$f=fic%-P38rhqyJZB?;Y0E(zcJX?TCn|bSZ+0N)zeQ z6e%JC(xoH4h0t3dY!T^2q<2Mnm6AjV5EN ztPi^z2M!KD zvo1v=L}{R#y|vo*B>*MZL)SyD_?pDt5G8vFFFN8|jBpt8`4-hCr+ z#r0w_6_vW>+=*;heg(yES`B+@oi<}9*kxK%+4i8MYW3HTLa$+x*&d9RR&RR`1#)MwgWx>w=y<$OUH)+4Ok<#3GspT3m zcG;Fr(9Jf1SVo=M6=UV5xn@OZP$J_{@=Ck$+<=7mRAk{m)ufePt%mMdqGlhM@xRVeWrbM=cRMQTF@8!e=LED zxsAREXkR@VFVOdLd4MMl-}R(9yqsZF$od)Vxb)50qhFD_^4xXO4D0jlJwkejtUX=f zR8#rvU~jfUSzAn9n$+U?uE?2RiotZY-A%6X;U0bw2z?((9IV_#V!$% z{gkic9%$6Uuh=M-y-qB10E)@MmWJj+-+u=MF7^_ShpwnYLgzW$h{DYaom|b$_@Q#6 zP1d>j?Vx3TNNN-O^;kGx9+-!BmYi_ssH|m;4dc0Q<7Lqjcq;*@ffjQ0WI%a4>efS7 zaKP5mJd#ZjF>jjC7eHBZ35Xq4+*^?8clgQd>R57LwUBI@{hZtj(GFUUx8xK!=+4M% z+MC+3fY>t6p#o<|LkcaM;jfu(pf}E#!wX*^cD4?CBKvoTfd&L&?du$9vYA1wU6;Wn?~m-x8& zkK;~acj9X%BO`Mj$om8ek|HCSDr|(tIJ^rf3wfF6FN#(AnF-3ca$K{)pGLNfby_df zF&vQ~E7P%2Y-~2~F3e6^ql&C;1cQT_d3hlTUc65tZ+xD~?u<_SFh3VT&m)tkq}z-l|_dJRrqPA^;C6H1K^AvI4=PQDOnN#D%}#YAZN_-H!s_eHlC zaz_df_=CrPWX-csjkJu6Smw|WA!g4S1HVk*3JmSyNp2Y`SXACPg6g_BCF?s#UN(n& z&vhpF36EQ?3wk$~m&b@gr&UVtXj@S9*H6b>Bc)pgt?VRK`}xpM{K%`6L_Qk`20KrW zIB=#xNq$~#l2CmkG!xs@h{ue8homHFvF;*!`5+EEItpF_4qb4t_!!HE>~dmlWuQWH z(3~nM&&33QXJoU7Q&(}Lqr~LOLGa$n9D`P{&&a|4D$-~6)ts77An%ySh3hxXkb41X z88vUdZd~@A64LD|%NgNV4df0IlSOj zFRj8Z&Q&OdkjM+R#Qbb=ugxBCv-Y(SnA0^7Q?mHugp3O2hgpq{jhdFe?aC@KJ(fOW zBCrLZt2)sF_^RFRbPN;zJk*=sd>q~MyD-Ku;APB9tegAvcB_04JOF!IM5Yh7P58uJ zh;bOp#i6X#r)s54E!6Frh*AuP=UGUx*QLCX23RmuWiplWoqr%ZAT)10H@78~E3gG0 zg)~=5x(uw+(>^yT8ZvA4TQ0!<+|YS(+wd)`xKobq7{B9~8ZY3b4Xshq*|GtX;zca_dT*uqgFvv^1QS@tvfJhRM{TavXg-!Ij}T0!6_x@*=pfeHtI z4l1~MB(Qc*5syXVnP%YTxUb`wfNS$}cSO3P+~mc4J*|632Y%e#E?G#=9S~-7=;io!*b8?iklhIo;^>r6c=QC#ceCyx}!6JNFs9V9wWHNz=Vna164+ViZ?#_O{o(= zUc9s2b@m}^A}EC#LO1gE;=D*Re} zwU~{gy+d0&cRgWe`XE`9HyUME7W4hewPMJ)#Z`Xm6+(S)asr5BW1-Z`{=+8xy1b^K zYHzY>zQTSdPFRdLIx`dY;7&Ae03stYvXg)ESKxZIyGDh(&}%|Pyr0Fsm`ygntzfPM zoYbS>wzniO@-;T$>!+1{VED{+90^;eI2qazQ2J~^DBWHB^%JkC-9JpA2T8x zu8`A0lFL$y;xjWdB<{tRm|i*4k$u9f_bgR8k z*du8-IRNnj6`&Nd!xG%RNot%MGo39V#cYOJS?0R;jAtNzFfx*Td>F}f3*C!;w*A>M zGSQKdu|^6Bsn_o)#Mcrwr1Mv6^d8pTVK9ztxkrqj*rqQf#n9GFHt?>)t+$}iLMq{& z$7!bl1bL#ff9_gw(DqQqa!fI;<#s3TA^4M|o}d-FxELAMuaQO(qKb=^?ethYX+73X z@tmpoc;qH_A~L^;Q@56gQ~kW_3CKm1w|?nq%M@1xw(RjIR5-b~=xNUYv3K*PHcv60 zt+J|ahR!N-9j;$MF9U=xw0h`5{pelu)GZ6y5Q46m{n($bo6B)TZrIMWT!@avbLvE$ z(LQUP)c0_5xx4QU9=$Hhw!_e}R{{SP5s(dSA;U#>ZE) zqvRFwM{9ys%x&bhU9qwy$Vtj0Tph3luY9-yieRsDG+c{JRm(Z6_gXK)-IS3G9UI=t z*mSmm=#4xs^Fxu^n{T?7ggFqM- zK(G}9;|<-T4V>9L^Z3Rr@WT~H>OP8kyvB1&i-P#OyZ7%qW9pq0_l6R}lW6V#p*rOe z4g+7))ARH5f#o(oODJ0gt+#)A8j*{wbC}~x_>YnXXTHDD<3l|n(bCe&%gZ~`5B-R` z7P9u1`v4tsh<1NC^pW#8hpn!X+S$@I2n37fYcv)s>oKcbHzAk(;vfGWjq)eTrZdzr z={4a$)@(SF*~ZvUuv@|W^muv)D`xOXN#8MT%>U?Eanm~QXi7WNTeoxj(pS=inAvY- zv)^sJ-J8YWi*3EV;BZ{QO$qovN;fO%PjH?K7e8=9^dY;*aFHc@(f$N^03yvO^vfjj zTxes%=90S@Pg`!=lvMzzXg>52q5hKM@3HYY{lqp6r-P*?RS;lVz8N^?!du-#HEy47 z$tLt_F-1T(eM$Y-_=cq{T9pwo1y6SKKIm1r-m?%Z+D|WQn-WQKu79C$Rey) z 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 literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/images/cvt_visualize.png b/docs/doxygen-user/images/cvt_visualize.png new file mode 100644 index 0000000000000000000000000000000000000000..e5d56950b514041d3f79e1275c896f26681b64af GIT binary patch literal 38493 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⪼9$O$~%8e)R3ti@w?YiUXS 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 M^*yrg Date: Tue, 15 May 2018 10:31:48 -0400 Subject: [PATCH 40/63] Update functional test code from develop branch --- .../datamodel/CentralRepoDatamodelTest.java | 0 .../autopsy/ingest/EmbeddedFileTest.java | 3 +-- .../autopsy/ingest/IngestFileFiltersTest.java | 21 +++++++------------ .../EncryptionDetectionTest.java | 4 +++- .../autopsy/testutils/CaseUtils.java | 16 ++++++-------- 5 files changed, 17 insertions(+), 27 deletions(-) mode change 100644 => 100755 Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java mode change 100644 => 100755 Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java old mode 100644 new mode 100755 diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java index 215d1fe4da..6af0ec81f9 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java @@ -62,7 +62,7 @@ public class EmbeddedFileTest extends NbTestCase { @Override public void setUp() { - CaseUtils.createCase(CASE_DIRECTORY_PATH, CASE_NAME); + CaseUtils.createCase(CASE_NAME); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); @@ -92,7 +92,6 @@ public class EmbeddedFileTest extends NbTestCase { @Override public void tearDown() { CaseUtils.closeCase(); - CaseUtils.deleteCaseDir(CASE_DIRECTORY_PATH); } public void testEncryption() { diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java old mode 100644 new mode 100755 index 810c682798..7be3e4f794 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java @@ -72,8 +72,7 @@ public class IngestFileFiltersTest extends NbTestCase { } public void testBasicDir() { - Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testBasicDir"); - CaseUtils.createCase(casePath, "testBasicDir"); + CaseUtils.createCase("testBasicDir"); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); @@ -115,8 +114,7 @@ public class IngestFileFiltersTest extends NbTestCase { } public void testExtAndDirWithOneRule() { - Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testExtAndDirWithOneRule"); - CaseUtils.createCase(casePath, "testExtAndDirWithOneRule"); + CaseUtils.createCase("testExtAndDirWithOneRule"); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); @@ -151,8 +149,7 @@ public class IngestFileFiltersTest extends NbTestCase { } public void testExtAndDirWithTwoRules() { - Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testExtAndDirWithTwoRules"); - CaseUtils.createCase(casePath, "testExtAndDirWithTwoRules"); + CaseUtils.createCase("testExtAndDirWithTwoRules"); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); @@ -196,8 +193,7 @@ public class IngestFileFiltersTest extends NbTestCase { } public void testFullFileNameRule() { - Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testFullFileNameRule"); - CaseUtils.createCase(casePath, "testFullFileNameRule"); + CaseUtils.createCase("testFullFileNameRule"); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); @@ -232,8 +228,7 @@ public class IngestFileFiltersTest extends NbTestCase { } public void testCarvingWithExtRuleAndUnallocSpace() { - Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testCarvingWithExtRuleAndUnallocSpace"); - CaseUtils.createCase(casePath, "testCarvingWithExtRuleAndUnallocSpace"); + CaseUtils.createCase("testCarvingWithExtRuleAndUnallocSpace"); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); @@ -281,8 +276,7 @@ public class IngestFileFiltersTest extends NbTestCase { } public void testCarvingNoUnallocatedSpace() { - Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testCarvingNoUnallocatedSpace"); - CaseUtils.createCase(casePath, "testCarvingNoUnallocatedSpace"); + CaseUtils.createCase("testCarvingNoUnallocatedSpace"); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); @@ -315,8 +309,7 @@ public class IngestFileFiltersTest extends NbTestCase { } public void testEmbeddedModule() { - Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testEmbeddedModule"); - CaseUtils.createCase(casePath, "testEmbeddedModule"); + CaseUtils.createCase("testEmbeddedModule"); LocalFilesDSProcessor dataSourceProcessor = new LocalFilesDSProcessor(); IngestUtils.addDataSource(dataSourceProcessor, ZIPFILE_PATH); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java index c1e71822a4..0463642f90 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java @@ -33,6 +33,8 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.FileManager; import org.sleuthkit.autopsy.ingest.IngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType; +import org.sleuthkit.autopsy.ingest.IngestModuleFactory; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestModuleTemplate; import org.sleuthkit.autopsy.testutils.CaseUtils; import org.sleuthkit.autopsy.testutils.IngestUtils; @@ -84,7 +86,7 @@ public class EncryptionDetectionTest extends NbTestCase { */ public void testBitlockerEncryption() { try { - CaseUtils.createCase(BITLOCKER_CASE_DIRECTORY_PATH, BITLOCKER_CASE_NAME); + CaseUtils.createCase(BITLOCKER_CASE_NAME); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); IngestUtils.addDataSource(dataSourceProcessor, BITLOCKER_IMAGE_PATH); Case openCase = Case.getCurrentCaseThrows(); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java index 500aaa6258..17b0c2168b 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java @@ -37,13 +37,6 @@ import org.sleuthkit.autopsy.casemodule.CaseDetails; */ public final class CaseUtils { - /** - * CaseUtils constructor. Since this class is not meant to allow for - * instantiation, this constructor is 'private'. - */ - private CaseUtils() { - } - /** * Create a case case directory and case for the given case name. * @@ -105,10 +98,13 @@ public final class CaseUtils { if (!caseDirectory.exists()) { return; } - //We should determine whether the test fails or passes where this is called - //It will usually be a test failure when the case can not be deleted - //but sometimes we might be alright if we are unable to delete it. FileUtils.deleteDirectory(caseDirectory); } + /** + * Private constructor to prevent utility class instantiation. + */ + private CaseUtils() { + } + } From c2b6b5854b53397cc6522ceed66bd0d4c4ea8956 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Tue, 8 May 2018 18:22:08 -0400 Subject: [PATCH 41/63] Bitlocker detection implemented. --- Core/build.xml | 2 +- .../BitlockerDetection.java | 10 +++ ...yptionDetectionDataSourceIngestModule.java | 4 + .../autopsy/ingest/EmbeddedFileTest.java | 4 + .../EncryptionDetectionTest.java | 89 +++++++++++++++++++ 5 files changed, 108 insertions(+), 1 deletion(-) diff --git a/Core/build.xml b/Core/build.xml index 65f26273a7..65eeb92964 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -84,7 +84,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/BitlockerDetection.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/BitlockerDetection.java index 1fb32b92e7..75c974b6de 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/BitlockerDetection.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/BitlockerDetection.java @@ -25,7 +25,11 @@ import org.sleuthkit.datamodel.Volume; * Addendum class for the Encryption Detection data source module to detect * Bitlocker volumes. */ +<<<<<<< HEAD final class BitlockerDetection { +======= +class BitlockerDetection { +>>>>>>> b8ec65adb... Bitlocker detection implemented. private static final int BITLOCKER_BIOS_PARAMETER_BLOCK_SIZE = 0x54; private static final byte[] BITLOCKER_SIGNATURE_BYTES = {'-', 'F', 'V', 'E', '-', 'F', 'S', '-'}; @@ -37,12 +41,15 @@ final class BitlockerDetection { private static final int BITLOCKER_ADDRESS_SECTORS = 0x13; private static final int BITLOCKER_ADDRESS_SECTORS_PER_FAT = 0x16; private static final int BITLOCKER_ADDRESS_LARGE_SECTORS = 0x20; +<<<<<<< HEAD /** * Private constructor to prevent instantiation. */ private BitlockerDetection() { } +======= +>>>>>>> b8ec65adb... Bitlocker detection implemented. /** * This method checks if the Volume input has been encrypted with Bitlocker. @@ -107,11 +114,14 @@ final class BitlockerDetection { && sectors == 0 && sectorsPerFat == 0 && largeSectors == 0) { bitlockerVolume = true; } +<<<<<<< HEAD break; default: // Invalid value. This is not a Bitlocker volume. +======= +>>>>>>> b8ec65adb... Bitlocker detection implemented. } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java index 7782357471..a964d43ef4 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java @@ -148,7 +148,11 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges StringBuilder detailsSb = new StringBuilder(""); detailsSb.append("File: ").append(volume.getParent().getUniquePath()).append(volume.getName()); if (artifactType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED)) { +<<<<<<< HEAD detailsSb.append("
\nEntropy: ").append(calculatedEntropy); +======= + detailsSb.append("
\n").append("Entropy: ").append(calculatedEntropy); +>>>>>>> b8ec65adb... Bitlocker detection implemented. } services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(), diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java index 6af0ec81f9..687526303f 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java @@ -62,7 +62,11 @@ public class EmbeddedFileTest extends NbTestCase { @Override public void setUp() { +<<<<<<< HEAD CaseUtils.createCase(CASE_NAME); +======= + CaseUtils.createCase(CASE_DIRECTORY_PATH, CASE_NAME); +>>>>>>> b8ec65adb... Bitlocker detection implemented. ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java index 0463642f90..fa04d4487a 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java @@ -44,7 +44,10 @@ import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +<<<<<<< HEAD import org.sleuthkit.datamodel.Content; +======= +>>>>>>> b8ec65adb... Bitlocker detection implemented. import org.sleuthkit.datamodel.Volume; import org.sleuthkit.datamodel.VolumeSystem; @@ -58,6 +61,7 @@ public class EncryptionDetectionTest extends NbTestCase { private final Path BITLOCKER_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "encryption_detection_bitlocker_test.vhd"); private final Path PASSWORD_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "password_detection_test.img"); +<<<<<<< HEAD private static final String PASSWORD_DETECTION_CASE_NAME = "PasswordDetectionTest"; private static final String VERACRYPT_DETECTION_CASE_NAME = "VeraCryptDetectionTest"; @@ -65,6 +69,9 @@ public class EncryptionDetectionTest extends NbTestCase { private final Path PASSWORD_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "password_detection_test.img"); private final Path VERACRYPT_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "veracrypt_detection_test.vhd"); +======= + +>>>>>>> b8ec65adb... Bitlocker detection implemented. public static Test suite() { NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(EncryptionDetectionTest.class). clusters(".*"). @@ -83,14 +90,96 @@ public class EncryptionDetectionTest extends NbTestCase { /** * Test the Encryption Detection module's volume encryption detection. +<<<<<<< HEAD +======= */ public void testBitlockerEncryption() { try { + CaseUtils.createCase(BITLOCKER_CASE_DIRECTORY_PATH, BITLOCKER_CASE_NAME); + ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); + IngestUtils.addDataSource(dataSourceProcessor, BITLOCKER_IMAGE_PATH); + Case openCase = Case.getCurrentCaseThrows(); + + /* + * Create ingest job settings. + */ + IngestModuleFactory ingestModuleFactory = new EncryptionDetectionModuleFactory(); + IngestModuleIngestJobSettings settings = ingestModuleFactory.getDefaultIngestJobSettings(); + IngestModuleTemplate template = new IngestModuleTemplate(ingestModuleFactory, settings); + template.setEnabled(true); + List templates = new ArrayList<>(); + templates.add(template); + IngestJobSettings ingestJobSettings = new IngestJobSettings(EncryptionDetectionTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates); + IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); + + /* + * Process each volume. + */ + boolean vol2Found = false; + + String errorMessage; + + Image dataSource = (Image) openCase.getDataSources().get(0); + List volumeSystems = dataSource.getVolumeSystems(); + for (VolumeSystem volumeSystem : volumeSystems) { + for (Volume volume : volumeSystem.getVolumes()) { + List artifactsList = volume.getAllArtifacts(); + + if (volume.getName().equals("vol2")) { + vol2Found = true; + + errorMessage = String.format("Expected one artifact for '%s', but found %d.", + volume.getName(), artifactsList.size()); + assertEquals(errorMessage, 1, artifactsList.size()); + + String artifactTypeName = artifactsList.get(0).getArtifactTypeName(); + errorMessage = String.format("Unexpected '%s' artifact for '%s'.", + artifactTypeName, volume.getName()); + assertEquals(errorMessage, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.toString(), artifactTypeName); + + BlackboardAttribute attribute = artifactsList.get(0).getAttribute( + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT)); + errorMessage = String.format("Expected a TSK_COMMENT attribute for '%s', but found none.", + volume.getName()); + assertNotNull(errorMessage, attribute); + + errorMessage = String.format("Unexpected attribute value: \"%s\"", attribute.getValueString()); + assertEquals(errorMessage, "Bitlocker encryption detected.", attribute.getValueString()); + } else { + errorMessage = String.format("Expected no artifacts for '%s', but found %d.", + volume.getName(), artifactsList.size()); + assertEquals(errorMessage, 0, artifactsList.size()); + } + } + } + + errorMessage = "Expected to find 'vol2', but no such volume exists."; + assertEquals(errorMessage, true, vol2Found); + } catch (NoCurrentCaseException | TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + /** + * Test the Encryption Detection module's password protection detection. +>>>>>>> b8ec65adb... Bitlocker detection implemented. + */ + public void testBitlockerEncryption() { + try { +<<<<<<< HEAD CaseUtils.createCase(BITLOCKER_CASE_NAME); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); IngestUtils.addDataSource(dataSourceProcessor, BITLOCKER_IMAGE_PATH); Case openCase = Case.getCurrentCaseThrows(); +======= + CaseUtils.createCase(PASSWORD_CASE_DIRECTORY_PATH, PASSWORD_CASE_NAME); + ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); + IngestUtils.addDataSource(dataSourceProcessor, PASSWORD_IMAGE_PATH); + Case openCase = Case.getCurrentCaseThrows(); + +>>>>>>> b8ec65adb... Bitlocker detection implemented. /* * Create ingest job settings. */ From 4676df8ab9e47366e3737bbb50224aea18a8386d Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 15 May 2018 11:19:50 -0400 Subject: [PATCH 42/63] Adding BitLocker detection to custom release branch --- .../BitlockerDetection.java | 10 ---------- ...ryptionDetectionDataSourceIngestModule.java | 4 ---- .../autopsy/ingest/EmbeddedFileTest.java | 4 ---- .../EncryptionDetectionTest.java | 18 ------------------ 4 files changed, 36 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/BitlockerDetection.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/BitlockerDetection.java index 75c974b6de..1fb32b92e7 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/BitlockerDetection.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/BitlockerDetection.java @@ -25,11 +25,7 @@ import org.sleuthkit.datamodel.Volume; * Addendum class for the Encryption Detection data source module to detect * Bitlocker volumes. */ -<<<<<<< HEAD final class BitlockerDetection { -======= -class BitlockerDetection { ->>>>>>> b8ec65adb... Bitlocker detection implemented. private static final int BITLOCKER_BIOS_PARAMETER_BLOCK_SIZE = 0x54; private static final byte[] BITLOCKER_SIGNATURE_BYTES = {'-', 'F', 'V', 'E', '-', 'F', 'S', '-'}; @@ -41,15 +37,12 @@ class BitlockerDetection { private static final int BITLOCKER_ADDRESS_SECTORS = 0x13; private static final int BITLOCKER_ADDRESS_SECTORS_PER_FAT = 0x16; private static final int BITLOCKER_ADDRESS_LARGE_SECTORS = 0x20; -<<<<<<< HEAD /** * Private constructor to prevent instantiation. */ private BitlockerDetection() { } -======= ->>>>>>> b8ec65adb... Bitlocker detection implemented. /** * This method checks if the Volume input has been encrypted with Bitlocker. @@ -114,14 +107,11 @@ class BitlockerDetection { && sectors == 0 && sectorsPerFat == 0 && largeSectors == 0) { bitlockerVolume = true; } -<<<<<<< HEAD break; default: // Invalid value. This is not a Bitlocker volume. -======= ->>>>>>> b8ec65adb... Bitlocker detection implemented. } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java index a964d43ef4..7782357471 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java @@ -148,11 +148,7 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges StringBuilder detailsSb = new StringBuilder(""); detailsSb.append("File: ").append(volume.getParent().getUniquePath()).append(volume.getName()); if (artifactType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED)) { -<<<<<<< HEAD detailsSb.append("
\nEntropy: ").append(calculatedEntropy); -======= - detailsSb.append("
\n").append("Entropy: ").append(calculatedEntropy); ->>>>>>> b8ec65adb... Bitlocker detection implemented. } services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(), diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java index 687526303f..6af0ec81f9 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java @@ -62,11 +62,7 @@ public class EmbeddedFileTest extends NbTestCase { @Override public void setUp() { -<<<<<<< HEAD CaseUtils.createCase(CASE_NAME); -======= - CaseUtils.createCase(CASE_DIRECTORY_PATH, CASE_NAME); ->>>>>>> b8ec65adb... Bitlocker detection implemented. ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java index fa04d4487a..06038a6a67 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java @@ -44,10 +44,7 @@ import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; -<<<<<<< HEAD import org.sleuthkit.datamodel.Content; -======= ->>>>>>> b8ec65adb... Bitlocker detection implemented. import org.sleuthkit.datamodel.Volume; import org.sleuthkit.datamodel.VolumeSystem; @@ -61,7 +58,6 @@ public class EncryptionDetectionTest extends NbTestCase { private final Path BITLOCKER_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "encryption_detection_bitlocker_test.vhd"); private final Path PASSWORD_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "password_detection_test.img"); -<<<<<<< HEAD private static final String PASSWORD_DETECTION_CASE_NAME = "PasswordDetectionTest"; private static final String VERACRYPT_DETECTION_CASE_NAME = "VeraCryptDetectionTest"; @@ -69,9 +65,6 @@ public class EncryptionDetectionTest extends NbTestCase { private final Path PASSWORD_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "password_detection_test.img"); private final Path VERACRYPT_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "veracrypt_detection_test.vhd"); -======= - ->>>>>>> b8ec65adb... Bitlocker detection implemented. public static Test suite() { NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(EncryptionDetectionTest.class). clusters(".*"). @@ -90,8 +83,6 @@ public class EncryptionDetectionTest extends NbTestCase { /** * Test the Encryption Detection module's volume encryption detection. -<<<<<<< HEAD -======= */ public void testBitlockerEncryption() { try { @@ -163,23 +154,14 @@ public class EncryptionDetectionTest extends NbTestCase { /** * Test the Encryption Detection module's password protection detection. ->>>>>>> b8ec65adb... Bitlocker detection implemented. */ public void testBitlockerEncryption() { try { -<<<<<<< HEAD CaseUtils.createCase(BITLOCKER_CASE_NAME); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); IngestUtils.addDataSource(dataSourceProcessor, BITLOCKER_IMAGE_PATH); Case openCase = Case.getCurrentCaseThrows(); -======= - CaseUtils.createCase(PASSWORD_CASE_DIRECTORY_PATH, PASSWORD_CASE_NAME); - ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); - IngestUtils.addDataSource(dataSourceProcessor, PASSWORD_IMAGE_PATH); - Case openCase = Case.getCurrentCaseThrows(); - ->>>>>>> b8ec65adb... Bitlocker detection implemented. /* * Create ingest job settings. */ From 17df5f03a4c7ec46e798289fddd9e3d5604c55d0 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 15 May 2018 11:38:30 -0400 Subject: [PATCH 43/63] Fix merge error in BitLocker detection cherry-pick --- .../EncryptionDetectionTest.java | 71 ------------------- 1 file changed, 71 deletions(-) diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java index 06038a6a67..61590a262c 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java @@ -81,77 +81,6 @@ public class EncryptionDetectionTest extends NbTestCase { CaseUtils.closeCase(); } - /** - * Test the Encryption Detection module's volume encryption detection. - */ - public void testBitlockerEncryption() { - try { - CaseUtils.createCase(BITLOCKER_CASE_DIRECTORY_PATH, BITLOCKER_CASE_NAME); - ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); - IngestUtils.addDataSource(dataSourceProcessor, BITLOCKER_IMAGE_PATH); - Case openCase = Case.getCurrentCaseThrows(); - - /* - * Create ingest job settings. - */ - IngestModuleFactory ingestModuleFactory = new EncryptionDetectionModuleFactory(); - IngestModuleIngestJobSettings settings = ingestModuleFactory.getDefaultIngestJobSettings(); - IngestModuleTemplate template = new IngestModuleTemplate(ingestModuleFactory, settings); - template.setEnabled(true); - List templates = new ArrayList<>(); - templates.add(template); - IngestJobSettings ingestJobSettings = new IngestJobSettings(EncryptionDetectionTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates); - IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); - - /* - * Process each volume. - */ - boolean vol2Found = false; - - String errorMessage; - - Image dataSource = (Image) openCase.getDataSources().get(0); - List volumeSystems = dataSource.getVolumeSystems(); - for (VolumeSystem volumeSystem : volumeSystems) { - for (Volume volume : volumeSystem.getVolumes()) { - List artifactsList = volume.getAllArtifacts(); - - if (volume.getName().equals("vol2")) { - vol2Found = true; - - errorMessage = String.format("Expected one artifact for '%s', but found %d.", - volume.getName(), artifactsList.size()); - assertEquals(errorMessage, 1, artifactsList.size()); - - String artifactTypeName = artifactsList.get(0).getArtifactTypeName(); - errorMessage = String.format("Unexpected '%s' artifact for '%s'.", - artifactTypeName, volume.getName()); - assertEquals(errorMessage, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.toString(), artifactTypeName); - - BlackboardAttribute attribute = artifactsList.get(0).getAttribute( - new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT)); - errorMessage = String.format("Expected a TSK_COMMENT attribute for '%s', but found none.", - volume.getName()); - assertNotNull(errorMessage, attribute); - - errorMessage = String.format("Unexpected attribute value: \"%s\"", attribute.getValueString()); - assertEquals(errorMessage, "Bitlocker encryption detected.", attribute.getValueString()); - } else { - errorMessage = String.format("Expected no artifacts for '%s', but found %d.", - volume.getName(), artifactsList.size()); - assertEquals(errorMessage, 0, artifactsList.size()); - } - } - } - - errorMessage = "Expected to find 'vol2', but no such volume exists."; - assertEquals(errorMessage, true, vol2Found); - } catch (NoCurrentCaseException | TskCoreException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - } - /** * Test the Encryption Detection module's password protection detection. */ From 387f5f697da1b61e9de0c0afc3dc5c554b5b95a6 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 15 May 2018 11:56:21 -0400 Subject: [PATCH 44/63] EncryptionDetectionTest merge fixes --- .../encryptiondetection/EncryptionDetectionTest.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java index 61590a262c..0edd140bd1 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java @@ -51,13 +51,7 @@ import org.sleuthkit.datamodel.VolumeSystem; public class EncryptionDetectionTest extends NbTestCase { private static final String BITLOCKER_CASE_NAME = "testBitlockerEncryption"; - private static final String PASSWORD_CASE_NAME = "testPasswordProtection"; - - private static final Path BITLOCKER_CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), BITLOCKER_CASE_NAME); - private static final Path PASSWORD_CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), PASSWORD_CASE_NAME); - private final Path BITLOCKER_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "encryption_detection_bitlocker_test.vhd"); - private final Path PASSWORD_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "password_detection_test.img"); private static final String PASSWORD_DETECTION_CASE_NAME = "PasswordDetectionTest"; private static final String VERACRYPT_DETECTION_CASE_NAME = "VeraCryptDetectionTest"; @@ -82,7 +76,7 @@ public class EncryptionDetectionTest extends NbTestCase { } /** - * Test the Encryption Detection module's password protection detection. + * Test the Encryption Detection module's volume encryption detection. */ public void testBitlockerEncryption() { try { From 862c6e4e7b6b8cfb0795b14eeb6df550f24d057f Mon Sep 17 00:00:00 2001 From: esaunders Date: Tue, 15 May 2018 13:00:11 -0400 Subject: [PATCH 45/63] Added ingest progress snapshot support to AID. --- .../autopsy/ingest/DataSourceIngestJob.java | 115 +++++++++++------- .../autopsy/ingest/IngestManager.java | 16 ++- .../ingest/IngestProgressSnapshotDialog.java | 19 ++- .../ingest/IngestProgressSnapshotPanel.java | 12 +- .../IngestProgressSnapshotProvider.java | 49 ++++++++ .../autopsy/ingest/IngestTasksScheduler.java | 22 ++-- .../autoingest/AutoIngestAdminActions.java | 7 +- .../autoingest/AutoIngestJob.java | 72 ++++++++++- .../autoingest/AutoIngestJobsNode.java | 6 +- .../autoingest/AutoIngestManager.java | 3 + .../autoingest/AutoIngestMonitor.java | 12 +- 11 files changed, 257 insertions(+), 76 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotProvider.java diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java index c59b3f20bf..8ed0b26b60 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.ingest; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -37,6 +38,9 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.NetworkUtils; +import org.sleuthkit.autopsy.ingest.DataSourceIngestPipeline.PipelineModule; +import org.sleuthkit.autopsy.ingest.IngestJob.CancellationReason; +import org.sleuthkit.autopsy.ingest.IngestTasksScheduler.IngestJobTasksSnapshot; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.IngestJobInfo; @@ -51,7 +55,7 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSet; * Encapsulates a data source and the ingest module pipelines used to process * it. */ -final class DataSourceIngestJob { +public final class DataSourceIngestJob { private static final Logger logger = Logger.getLogger(DataSourceIngestJob.class.getName()); @@ -1079,71 +1083,90 @@ final class DataSourceIngestJob { * @return An ingest job statistics object. */ Snapshot getSnapshot(boolean getIngestTasksSnapshot) { - return new Snapshot(getIngestTasksSnapshot); + /** + * Determine whether file ingest is running at the time of this snapshot + * and determine the earliest file ingest level pipeline start time, if + * file ingest was started at all. + */ + boolean fileIngestRunning = false; + Date fileIngestStartTime = null; + + for (FileIngestPipeline pipeline : this.fileIngestPipelines) { + if (pipeline.isRunning()) { + fileIngestRunning = true; + } + Date pipelineStartTime = pipeline.getStartTime(); + if (null != pipelineStartTime && (null == fileIngestStartTime || pipelineStartTime.before(fileIngestStartTime))) { + fileIngestStartTime = pipelineStartTime; + } + } + + long processedFilesCount = 0; + long estimatedFilesToProcessCount = 0; + long snapShotTime = new Date().getTime(); + IngestJobTasksSnapshot tasksSnapshot = null; + + if (getIngestTasksSnapshot) { + synchronized (fileIngestProgressLock) { + processedFilesCount = this.processedFiles; + estimatedFilesToProcessCount = this.estimatedFilesToProcess; + snapShotTime = new Date().getTime(); + } + tasksSnapshot = DataSourceIngestJob.taskScheduler.getTasksSnapshotForJob(id); + + } + + return new Snapshot(this.dataSource.getName(), id, createTime, + getCurrentDataSourceIngestModule(), fileIngestRunning, fileIngestStartTime, + cancelled, cancellationReason, cancelledDataSourceIngestModules, + processedFilesCount, estimatedFilesToProcessCount, snapShotTime, tasksSnapshot); } /** * Stores basic diagnostic statistics for a data source ingest job. */ - final class Snapshot { + public static final class Snapshot implements Serializable { + + private static final long serialVersionUID = 1L; private final String dataSource; private final long jobId; private final long jobStartTime; private final long snapShotTime; - private final DataSourceIngestPipeline.PipelineModule dataSourceLevelIngestModule; - private boolean fileIngestRunning; - private Date fileIngestStartTime; + transient private final PipelineModule dataSourceLevelIngestModule; + private final boolean fileIngestRunning; + private final Date fileIngestStartTime; private final long processedFiles; private final long estimatedFilesToProcess; - private final IngestTasksScheduler.IngestJobTasksSnapshot tasksSnapshot; - private final boolean jobCancelled; - private final IngestJob.CancellationReason jobCancellationReason; - private final List cancelledDataSourceModules; + private final IngestJobTasksSnapshot tasksSnapshot; + transient private final boolean jobCancelled; + transient private final CancellationReason jobCancellationReason; + transient private final List cancelledDataSourceModules; /** * Constructs an object to store basic diagnostic statistics for a data * source ingest job. */ - Snapshot(boolean getIngestTasksSnapshot) { - this.dataSource = DataSourceIngestJob.this.dataSource.getName(); - this.jobId = DataSourceIngestJob.this.id; - this.jobStartTime = DataSourceIngestJob.this.createTime; - this.dataSourceLevelIngestModule = DataSourceIngestJob.this.getCurrentDataSourceIngestModule(); + Snapshot(String dataSourceName, long jobId, long jobStartTime, PipelineModule dataSourceIngestModule, + boolean fileIngestRunning, Date fileIngestStartTime, + boolean jobCancelled, CancellationReason cancellationReason, List cancelledModules, + long processedFiles, long estimatedFilesToProcess, + long snapshotTime, IngestJobTasksSnapshot tasksSnapshot) { + this.dataSource = dataSourceName; + this.jobId = jobId; + this.jobStartTime = jobStartTime; + this.dataSourceLevelIngestModule = dataSourceIngestModule; - /** - * Determine whether file ingest is running at the time of this - * snapshot and determine the earliest file ingest level pipeline - * start time, if file ingest was started at all. - */ - for (FileIngestPipeline pipeline : DataSourceIngestJob.this.fileIngestPipelines) { - if (pipeline.isRunning()) { - this.fileIngestRunning = true; - } - Date pipelineStartTime = pipeline.getStartTime(); - if (null != pipelineStartTime && (null == this.fileIngestStartTime || pipelineStartTime.before(this.fileIngestStartTime))) { - this.fileIngestStartTime = pipelineStartTime; - } - } - - this.jobCancelled = cancelled; + this.fileIngestRunning = fileIngestRunning; + this.fileIngestStartTime = fileIngestStartTime; + this.jobCancelled = jobCancelled; this.jobCancellationReason = cancellationReason; - this.cancelledDataSourceModules = new ArrayList<>(DataSourceIngestJob.this.cancelledDataSourceIngestModules); + this.cancelledDataSourceModules = cancelledModules; - if (getIngestTasksSnapshot) { - synchronized (DataSourceIngestJob.this.fileIngestProgressLock) { - this.processedFiles = DataSourceIngestJob.this.processedFiles; - this.estimatedFilesToProcess = DataSourceIngestJob.this.estimatedFilesToProcess; - this.snapShotTime = new Date().getTime(); - } - this.tasksSnapshot = DataSourceIngestJob.taskScheduler.getTasksSnapshotForJob(this.jobId); - - } else { - this.processedFiles = 0; - this.estimatedFilesToProcess = 0; - this.snapShotTime = new Date().getTime(); - this.tasksSnapshot = null; - } + this.processedFiles = processedFiles; + this.estimatedFilesToProcess = estimatedFilesToProcess; + this.snapShotTime = snapshotTime; + this.tasksSnapshot = tasksSnapshot; } /** diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java index 28a7c44474..fabf317187 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.awt.EventQueue; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -107,7 +108,7 @@ import org.sleuthkit.datamodel.Content; * job progress, and ingest module run times. */ @ThreadSafe -public class IngestManager { +public class IngestManager implements IngestProgressSnapshotProvider { private final static Logger logger = Logger.getLogger(IngestManager.class.getName()); private final static String INGEST_JOB_EVENT_CHANNEL_NAME = "%s-Ingest-Job-Events"; //NON-NLS @@ -756,7 +757,8 @@ public class IngestManager { * * @return Map of module name to run time (in milliseconds) */ - Map getModuleRunTimes() { + @Override + public Map getModuleRunTimes() { synchronized (ingestModuleRunTimes) { Map times = new HashMap<>(ingestModuleRunTimes); return times; @@ -769,7 +771,8 @@ public class IngestManager { * * @return A collection of ingest manager ingest task snapshots. */ - List getIngestThreadActivitySnapshots() { + @Override + public List getIngestThreadActivitySnapshots() { return new ArrayList<>(ingestThreadActivitySnapshots.values()); } @@ -778,7 +781,8 @@ public class IngestManager { * * @return A list of ingest job state snapshots. */ - List getIngestJobSnapshots() { + @Override + public List getIngestJobSnapshots() { List snapShots = new ArrayList<>(); synchronized (ingestJobsById) { ingestJobsById.values().forEach((job) -> { @@ -916,7 +920,9 @@ public class IngestManager { * running in an ingest thread. */ @Immutable - static final class IngestThreadActivitySnapshot { + public static final class IngestThreadActivitySnapshot implements Serializable { + + private static final long serialVersionUID = 1L; private final long threadId; private final Date startTime; diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java index bf28a611a4..eddebd59c4 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java @@ -50,12 +50,14 @@ public final class IngestProgressSnapshotDialog extends JDialog { /** * Constructs an instance of the dialog with its own frame. Could be modal. + * Uses the given provider as the source of data for the dialog. * * @param owner - the owner of this dialog. If this dialog should be * modal, the owner gets set to non modal. * @param shouldBeModal - true if this should be modal, false otherwise. + * @param provider - the provider to use as the source of data. */ - public IngestProgressSnapshotDialog(Container owner, Boolean shouldBeModal) { + public IngestProgressSnapshotDialog(Container owner, Boolean shouldBeModal, IngestProgressSnapshotProvider provider) { super((Window) owner, TITLE, ModalityType.MODELESS); if (shouldBeModal && owner instanceof JDialog) { // if called from a modal dialog, manipulate the parent be just under this in z order, and not modal. final JDialog pseudoOwner = (JDialog) owner; @@ -82,7 +84,7 @@ public final class IngestProgressSnapshotDialog extends JDialog { this.getRootPane().registerKeyboardAction(e -> { this.dispose(); }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); - add(new IngestProgressSnapshotPanel(this)); + add(new IngestProgressSnapshotPanel(this, provider)); pack(); setResizable(false); if (shouldBeModal) { // if called from a modal dialog, become modal, otherwise don't. @@ -90,4 +92,17 @@ public final class IngestProgressSnapshotDialog extends JDialog { } setVisible(true); } + + /** + * Constructs an instance of the dialog with its own frame. Could be modal. + * Uses the internal IngestManager instance as the source of data for the + * dialog + * + * @param owner - the owner of this dialog. If this dialog should be + * modal, the owner gets set to non modal. + * @param shouldBeModal - true if this should be modal, false otherwise. + */ + public IngestProgressSnapshotDialog(Container owner, Boolean shouldBeModal) { + this(owner, shouldBeModal, IngestManager.getInstance()); + } } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotPanel.java index 65cd40a9b2..be4661ce5b 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotPanel.java @@ -33,15 +33,17 @@ import org.openide.util.NbBundle; /** * A panel that displays ingest task progress snapshots. */ -public class IngestProgressSnapshotPanel extends javax.swing.JPanel { +class IngestProgressSnapshotPanel extends javax.swing.JPanel { private final JDialog parent; + private final IngestProgressSnapshotProvider snapshotProvider; private final IngestThreadActivitySnapshotsTableModel threadActivityTableModel; private final IngestJobTableModel jobTableModel; private final ModuleTableModel moduleTableModel; - IngestProgressSnapshotPanel(JDialog parent) { + IngestProgressSnapshotPanel(JDialog parent, IngestProgressSnapshotProvider snapshotProvider) { this.parent = parent; + this.snapshotProvider = snapshotProvider; threadActivityTableModel = new IngestThreadActivitySnapshotsTableModel(); jobTableModel = new IngestJobTableModel(); moduleTableModel = new ModuleTableModel(); @@ -105,7 +107,7 @@ public class IngestProgressSnapshotPanel extends javax.swing.JPanel { } private void refresh() { - snapshots = IngestManager.getInstance().getIngestThreadActivitySnapshots(); + snapshots = snapshotProvider.getIngestThreadActivitySnapshots(); fireTableDataChanged(); } @@ -187,7 +189,7 @@ public class IngestProgressSnapshotPanel extends javax.swing.JPanel { } private void refresh() { - jobSnapshots = IngestManager.getInstance().getIngestJobSnapshots(); + jobSnapshots = snapshotProvider.getIngestJobSnapshots(); fireTableDataChanged(); } @@ -299,7 +301,7 @@ public class IngestProgressSnapshotPanel extends javax.swing.JPanel { } private void refresh() { - Map moduleStatMap = IngestManager.getInstance().getModuleRunTimes(); + Map moduleStatMap = snapshotProvider.getModuleRunTimes(); moduleStats.clear(); totalTime = 0; for (String k : moduleStatMap.keySet()) { diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotProvider.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotProvider.java new file mode 100644 index 0000000000..646c09bbf7 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotProvider.java @@ -0,0 +1,49 @@ +/* + * 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.ingest; + +import java.util.List; +import java.util.Map; + +/** + * Interface that provides a snapshot of ingest progress. + */ +public interface IngestProgressSnapshotProvider { + + /** + * Get a snapshot of the state of ingest threads. + * + * @return A list of IngestThreadActivitySnapshot + */ + public List getIngestThreadActivitySnapshots(); + + /** + * Get a snapshot of the state of ingest jobs. + * + * @return A list of ingest job snapshots. + */ + public List getIngestJobSnapshots(); + + /** + * Gets the cumulative run times for the ingest module. + * + * @return Map of module name to run time (in milliseconds) + */ + public Map getModuleRunTimes(); +} diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java index 26e603e55b..f63ed1b885 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.ingest; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -555,7 +556,11 @@ final class IngestTasksScheduler { * @return */ synchronized IngestJobTasksSnapshot getTasksSnapshotForJob(long jobId) { - return new IngestJobTasksSnapshot(jobId); + return new IngestJobTasksSnapshot(jobId, this.dataSourceIngestThreadQueue.countQueuedTasksForJob(jobId), + countTasksForJob(this.rootFileTaskQueue, jobId), + countTasksForJob(this.pendingFileTaskQueue, jobId), + this.fileIngestThreadsQueue.countQueuedTasksForJob(jobId), + this.dataSourceIngestThreadQueue.countRunningTasksForJob(jobId) + this.fileIngestThreadsQueue.countRunningTasksForJob(jobId)); } /** @@ -825,8 +830,9 @@ final class IngestTasksScheduler { /** * A snapshot of ingest tasks data for an ingest job. */ - class IngestJobTasksSnapshot { + public static final class IngestJobTasksSnapshot implements Serializable { + private static final long serialVersionUID = 1L; private final long jobId; private final long dsQueueSize; private final long rootQueueSize; @@ -839,13 +845,13 @@ final class IngestTasksScheduler { * * @param jobId The identifier associated with the job. */ - IngestJobTasksSnapshot(long jobId) { + IngestJobTasksSnapshot(long jobId, long dsQueueSize, long rootQueueSize, long dirQueueSize, long fileQueueSize, long runningListSize) { this.jobId = jobId; - this.dsQueueSize = IngestTasksScheduler.this.dataSourceIngestThreadQueue.countQueuedTasksForJob(jobId); - this.rootQueueSize = countTasksForJob(IngestTasksScheduler.this.rootFileTaskQueue, jobId); - this.dirQueueSize = countTasksForJob(IngestTasksScheduler.this.pendingFileTaskQueue, jobId); - this.fileQueueSize = IngestTasksScheduler.this.fileIngestThreadsQueue.countQueuedTasksForJob(jobId);; - this.runningListSize = IngestTasksScheduler.this.dataSourceIngestThreadQueue.countRunningTasksForJob(jobId) + IngestTasksScheduler.this.fileIngestThreadsQueue.countRunningTasksForJob(jobId); + this.dsQueueSize = dsQueueSize; + this.rootQueueSize = rootQueueSize; + this.dirQueueSize = dirQueueSize; + this.fileQueueSize = fileQueueSize; + this.runningListSize = runningListSize; } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java index 0a5e6d0ac0..8892829344 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java @@ -140,19 +140,20 @@ final class AutoIngestAdminActions { static final class ProgressDialogAction extends AbstractAction { private static final long serialVersionUID = 1L; + private final AutoIngestJob job; - ProgressDialogAction() { + ProgressDialogAction(AutoIngestJob job) { super(Bundle.AutoIngestAdminActions_progressDialogAction_title()); + this.job = job; } @Override public void actionPerformed(ActionEvent e) { - //TODO JIRA-3734 final AutoIngestDashboardTopComponent tc = (AutoIngestDashboardTopComponent) WindowManager.getDefault().findTopComponent(AutoIngestDashboardTopComponent.PREFERRED_ID); if (tc != null) { AutoIngestDashboard dashboard = tc.getAutoIngestDashboard(); if (dashboard != null) { - new IngestProgressSnapshotDialog(dashboard.getTopLevelAncestor(), true); + IngestProgressSnapshotDialog ingestProgressSnapshotDialog = new IngestProgressSnapshotDialog(dashboard.getTopLevelAncestor(), true, job); } } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java index 40537d3b83..25a3b3d495 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,23 +25,28 @@ import java.time.Instant; import java.util.Collections; import java.util.Comparator; import java.util.Date; +import java.util.List; +import java.util.Map; import java.util.Objects; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.NetworkUtils; +import org.sleuthkit.autopsy.ingest.DataSourceIngestJob.Snapshot; import org.sleuthkit.autopsy.ingest.IngestJob; +import org.sleuthkit.autopsy.ingest.IngestManager.IngestThreadActivitySnapshot; +import org.sleuthkit.autopsy.ingest.IngestProgressSnapshotProvider; /** * An automated ingest job, which is an ingest job performed by the automated * ingest service. */ @ThreadSafe -final class AutoIngestJob implements Comparable, Serializable { +final class AutoIngestJob implements Comparable, IngestProgressSnapshotProvider, Serializable { private static final long serialVersionUID = 1L; - private static final int CURRENT_VERSION = 2; + private static final int CURRENT_VERSION = 3; private static final int DEFAULT_PRIORITY = 0; private static final String LOCAL_HOST_NAME = NetworkUtils.getLocalHostName(); @@ -89,6 +94,13 @@ final class AutoIngestJob implements Comparable, Serializable { @GuardedBy("this") private long dataSourceSize; + /* + * Version 3 fields. + */ + private List ingestThreadsSnapshot; + private List ingestJobsSnapshot; + private Map moduleRunTimesSnapshot; + /** * Constructs a new automated ingest job. All job state not specified in the * job manifest is set to the default state for a new job. @@ -125,6 +137,13 @@ final class AutoIngestJob implements Comparable, Serializable { * Version 2 fields. */ this.dataSourceSize = 0; + + /* + * Version 3 fields. + */ + this.ingestThreadsSnapshot = Collections.emptyList(); + this.ingestJobsSnapshot = Collections.emptyList(); + this.moduleRunTimesSnapshot = Collections.emptyMap(); } catch (Exception ex) { throw new AutoIngestJobException(String.format("Error creating automated ingest job"), ex); } @@ -167,6 +186,13 @@ final class AutoIngestJob implements Comparable, Serializable { * Version 2 fields. */ this.dataSourceSize = nodeData.getDataSourceSize(); + + /* + * Version 3 fields + */ + this.ingestThreadsSnapshot = Collections.emptyList(); + this.ingestJobsSnapshot = Collections.emptyList(); + this.moduleRunTimesSnapshot = Collections.emptyMap(); } catch (Exception ex) { throw new AutoIngestJobException(String.format("Error creating automated ingest job"), ex); } @@ -340,6 +366,31 @@ final class AutoIngestJob implements Comparable, Serializable { return this.ingestJob; } + /** + * Sets the ingest thread snapshot for the auto ingest job. + * + * @param snapshot + */ + synchronized void setIngestThreadSnapshot(List snapshot) { + this.ingestThreadsSnapshot = snapshot; + } + + /** + * Sets the ingest job snapshot for the auto ingest job. + * @param snapshot + */ + synchronized void setIngestJobsSnapshot(List snapshot) { + this.ingestJobsSnapshot = snapshot; + } + + /** + * Sets the module run times snapshot for the auto ingest job. + * @param snapshot + */ + synchronized void setModuleRuntimesSnapshot(Map snapshot) { + this.moduleRunTimesSnapshot = snapshot; + } + /** * Cancels the job. */ @@ -541,6 +592,21 @@ final class AutoIngestJob implements Comparable, Serializable { return -this.getManifest().getDateFileCreated().compareTo(otherJob.getManifest().getDateFileCreated()); } + @Override + public List getIngestThreadActivitySnapshots() { + return this.ingestThreadsSnapshot; + } + + @Override + public List getIngestJobSnapshots() { + return this.ingestJobsSnapshot; + } + + @Override + public Map getModuleRunTimes() { + return this.moduleRunTimesSnapshot; + } + /** * Comparator that supports doing a descending sort of jobs based on job * completion date. diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index 2e229b4a10..d4e47c0891 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -201,14 +201,14 @@ final class AutoIngestJobsNode extends AbstractNode { actions.add(deprioritizeCaseAction); break; case RUNNING_JOB: - actions.add(new AutoIngestAdminActions.ProgressDialogAction()); + actions.add(new AutoIngestAdminActions.ProgressDialogAction(autoIngestJob)); actions.add(new AutoIngestAdminActions.CancelJobAction(autoIngestJob)); - actions.add(new AutoIngestAdminActions.CancelModuleAction()); +// actions.add(new AutoIngestAdminActions.CancelModuleAction()); break; case COMPLETED_JOB: actions.add(new AutoIngestAdminActions.ReprocessJobAction(autoIngestJob)); actions.add(new AutoIngestAdminActions.DeleteCaseAction(autoIngestJob)); - actions.add(new AutoIngestAdminActions.ShowCaseLogAction()); +// actions.add(new AutoIngestAdminActions.ShowCaseLogAction()); break; default: } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 8acc0f07ff..b8efd6cf4a 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -3050,6 +3050,9 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen synchronized (jobsLock) { if (currentJob != null) { currentJob.getProcessingStageDetails(); + currentJob.setIngestThreadSnapshot(IngestManager.getInstance().getIngestThreadActivitySnapshots()); + currentJob.setIngestJobsSnapshot(IngestManager.getInstance().getIngestJobSnapshots()); + currentJob.setModuleRuntimesSnapshot(IngestManager.getInstance().getModuleRunTimes()); setChanged(); notifyObservers(Event.JOB_STATUS_UPDATED); updateCoordinationServiceManifestNode(currentJob); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index 08272a27fb..09e6d1a861 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -188,7 +188,17 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen */ AutoIngestJob job = event.getJob(); jobsSnapshot.removePendingJob(job); - jobsSnapshot.addOrReplaceRunningJob(job); + + // Update the state of the existing job in the running jobs table + for (AutoIngestJob runningJob : jobsSnapshot.getRunningJobs()) { + if (runningJob.equals(job)) { + runningJob.setIngestJobsSnapshot(job.getIngestJobSnapshots()); + runningJob.setIngestThreadSnapshot(job.getIngestThreadActivitySnapshots()); + runningJob.setModuleRuntimesSnapshot(job.getModuleRunTimes()); + runningJob.setProcessingStage(job.getProcessingStage(), job.getProcessingStageStartDate()); + runningJob.setProcessingStatus(job.getProcessingStatus()); + } + } setChanged(); notifyObservers(jobsSnapshot); } From e397f4508b38178cb9c070ef168dcc57bf66088e Mon Sep 17 00:00:00 2001 From: esaunders Date: Tue, 15 May 2018 14:40:05 -0400 Subject: [PATCH 46/63] Fix for job reprocessing events not being handled correctly. --- .../autopsy/experimental/autoingest/AutoIngestManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 8acc0f07ff..8f83756588 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -409,6 +409,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen pendingJobs.add(job); Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); + setChanged(); notifyObservers(Event.REPROCESS_JOB); } } From 3bf5e8e5467f6afe529ba97cc921c64c86e13d3f Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 15 May 2018 15:22:30 -0400 Subject: [PATCH 47/63] Add documentation for password protected archives --- docs/doxygen-user/archive_extractor.dox | 30 ++++++++++++------ .../images/zipped_context_menu.png | Bin 0 -> 38583 bytes .../images/zipped_encryption_detected.png | Bin 0 -> 9693 bytes docs/doxygen-user/images/zipped_tree.png | Bin 0 -> 123222 bytes 4 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 docs/doxygen-user/images/zipped_context_menu.png create mode 100644 docs/doxygen-user/images/zipped_encryption_detected.png create mode 100644 docs/doxygen-user/images/zipped_tree.png diff --git a/docs/doxygen-user/archive_extractor.dox b/docs/doxygen-user/archive_extractor.dox index d3c455695a..3bc606038f 100644 --- a/docs/doxygen-user/archive_extractor.dox +++ b/docs/doxygen-user/archive_extractor.dox @@ -1,7 +1,7 @@ /*! \page embedded_file_extractor_page Embedded File Extraction Module -What Does It Do -======== +\section embedded_files_overview What Does It Do + The Embedded File Extractor module opens ZIP, RAR, other archive formats, Doc, Docx, PPT, PPTX, XLS, and XLSX and sends the derived files from those files back through the ingest pipeline for analysis. @@ -9,21 +9,17 @@ This module expands archive files to enable Autopsy to analyze all files on the NOTE: Certain media content embedded inside Doc, Docx, PPT, PPTX, XLS, and XLSX might not be extracted. -Configuration -======= +\section embedded_files_config Configuration There is no configuration required. -Using the Module -====== +\section embedded_files_usage Using the Module Select the checkbox in the Ingest Modules settings screen to enable the Archive Extractor. -Ingest Settings ------- +\subsection embedded_files_settings Ingest Settings There are no runtime ingest settings required. -Seeing Results ------- +\subsection embedded_files_results Seeing Results Each file extracted shows up in the data source tree view as a child of the archive containing it, \image html zipped_children_1.PNG @@ -32,4 +28,18 @@ Each file extracted shows up in the data source tree view as a child of the arch and as an archive under "Views", "File Types", "Archives". \image html zipped_children_2.PNG +\subsection embedded_files_encryption Encrypted Archives + +When the Embedded File Extractor module encounters an encrypted archive, it will generate a warning bubble in the bottom right of the main screen: + +\image html zipped_encryption_detected.png + +After ingest, you can attempt to decrypt these archives if you know the password. Find the archive (either in the \ref tree_viewer_page "tree view" or \ref result_viewer_page "result view") and right-click on it, then select "Unzip contents with password". + +\image html zipped_context_menu.png + +After entering the password, you can select which ingest modules to run on the newly extracted files. When finished, you can browse to the encrypted archive in the tree view to see the newly extracted files. If the archive was already open in the tree, you may have to close and open the case in order to see the new data. + +\image html zipped_tree.png + */ diff --git a/docs/doxygen-user/images/zipped_context_menu.png b/docs/doxygen-user/images/zipped_context_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..848ac8ea2e9866f2d5a1b4847b814f0ab06a3c31 GIT binary patch literal 38583 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

Id@JAq@mjE zR%?ZX(ic6r)Xe!_OsLOn){L79kkPZS=o+U!2Ov-spHP#j_xKP1+W|2b~ z9WzYZ2;Xkgj1XNnjR!2<@~q8MtYJdma|w^D(I+e67PWbD}b;s>1X*zdI z((t9KE1_dg2pQp%2wK{bA-x5A{g^OL0tC1Z#z|CE5+0Mc-{jQ)A>kR1f0J+}`fCZV z(tWZ^;akZgj`4u>RSJ629B~Iv)`$sT_Oly%BgHPBx~aNQ(vF&<~$UXv+tDFV>5dbwni!ZWqq!BG5{VRL|dD8dOja2t9*$2qY`Pr`Y4&TA@q4 zvf!esI+q)}l)Vx+B!wCVoi3MKiO9F1i5}M#-3k-mq(0L2PwkE%#}ab;<08diG0|XCx_k)^1yV(o3QXjp>GKQ(CGV}9#Y98n=m)^eYcjlV!P~i*3!Q-V`EMPXmDR>!K zW|Ellya8lW9)%{6uGadCKcicGD_{q(vTD*$p5YY_4dkwm19=toN}Or~7dWL;%|6$# zza`opipEE46)x2Yp{2}BpB4+BceL)(IIbNx(}!L8;bWf6{85)H9_vSz zNEnQ=>!3I&0qA-9Irox1cf!w>P&qlKx)fq{v$Hy5apCS?zGXg)ix)GJAgG1t8{n;L zMa6$CI=M_WNnU2qnh(}fSP8)B***5vryJsjNYki9WTjkw-7dG83@aJ+c1b|eG!CO!UF zi|@GgF$ksy78&jyfI8)XM)^@pkNA2uYhis~)w~AGK2+vSdHHMHyV$t>C{qTp35K}d z&jL!tdv52@FP$-puIN-adh z^$vf~pU65O(0!d>UIDE7%>|uoZIR~|8epsppCvScw;QWaps&i$AVB{U^SGUVmGe&^ zGSGDfhI|QdeCq4TTyaaxQcol4pB+H?{YqkdoVzz&S;^U<4&1&3>7w7A8vz89s(e$MO?rhHzHwiFlB-o!X%l134Z>gIMD*oq&0DK}97fEn0E+|vyNpLe;PVnJtE<2KtIM5%BwF@*0^2T{D zH*@dwJMTd<&DN%8KL9+Z==0RyE=^Z;9A_96FC)$HPZk}zQh(|@E+H&sbgXJNAAqF# z-Jy%KQ5(F_`7!4R(F5BoLc&7SvF6SMwcQ4I)cH=H5 zahh}=DXkA9r%xe`LIpH4Q&9byDP>f6%=4AJL~mi|%aTnq*g#a#O03M*g3&(gMN?WP z-rLvM`x_q7pl*#4bXXbU8dqMv2L!&y$II(Y@lw;khstMX_ZwH8oWR#qCUl+rzNy%o zr>s1GG-#vLaqtDl`)#`Q+`^phR=@hwp$iH2_PLVM44s??-#WEYIPY2LnSCRNlZGk{ zLi1a-%L==(MCOo#xpw&gOl~JA0Tw69g;#qiLMYv4TY7v1d`o3a2&e2{I4}%>LhTs` zOV29Q2GJlC=4WzAD!U(YM1F7NLS4l4*Z5)*GNXFX0@qMCg^?SFfO#j(?JI2r=Ja^l zDmnDfgRJ1IM;VRr&m=VD5`)~`L@`hQa5GWk(~lni`BYQSzW NR>$ql@1QZi{1XYPSE&F1 literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/images/zipped_encryption_detected.png b/docs/doxygen-user/images/zipped_encryption_detected.png new file mode 100644 index 0000000000000000000000000000000000000000..cfd4b88953a63593c333f81c518bf1dba8d81737 GIT binary patch literal 9693 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

pZk&u)w)(}w5?(R;r0 z6QpHj$v?pyT5P0F5PzHLS1yKt*7K)Xf}8yPv{FLzXw*ANDe{OLg5sa2@?Wa{x7zBxS>8J0To30z5uG z1_E!d+T`1QciB__eFEs!`VPTHzNTf0j*bp;D%L7DgCe^`r)3=Lh{h4x4_Q5|AqZkwPc$=`p1*h{B`6ZLICe12`uvYg5; zpLicKVDAhVaDJ!5sXYL!o0{5II_>{yOsJcW%92dW@AcY~`jfZue}whQoaKO32ncek^h-^B;B8<+LH8% zVZQ)|j*tw?6Od1hI4S^%2sY(}5?}cEPaR~!#c*%nz)P{BY z35^vX>hGsbFX1BQa@OM)nWUr4&si}&Z70Z%o<(DUl7O`>X+l{m%BFRa?Cbd3-nMNm zwMb?wpI3;x!b7TK1X!{T1TcWB64JO;kj6><+Z~P!CM zSJ8LS)(qGbZ}s2$e$UUZnfP`jYp6|zOZ+J3lV?C{$ZZ-XV>M$qI5H9taFyH8wg&#+ zdupU|=4G3^)$I*>eoD6y=FevgsYTp>?WteyHvn!2KHV!{k}!*e`!Kg`;4=;IaPN7( z3#f8|h1?zlc%&WuJ%z|~KkC-^_P8BxbkmbYkob>Z*4}P!yycA;w70opR2 literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/images/zipped_tree.png b/docs/doxygen-user/images/zipped_tree.png new file mode 100644 index 0000000000000000000000000000000000000000..6afc67ecfb73f16a9bfcc870e53b0749f5e5c5e0 GIT binary patch literal 123222 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 literal 0 HcmV?d00001 From a3d6895bf4eed74e2f376eea1d7ea9171e7d7f6c Mon Sep 17 00:00:00 2001 From: esaunders Date: Tue, 15 May 2018 16:03:22 -0400 Subject: [PATCH 48/63] Added support for showing case log from dashboard. --- .../autoingest/AutoIngestAdminActions.java | 56 +++++++++++++++++-- .../autoingest/AutoIngestJobsNode.java | 2 +- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java index 0a5e6d0ac0..32d2f0021e 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java @@ -19,8 +19,11 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.awt.Cursor; +import java.awt.Desktop; import java.awt.EventQueue; import java.awt.event.ActionEvent; +import java.io.IOException; +import java.nio.file.Path; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.JOptionPane; @@ -265,8 +268,8 @@ final class AutoIngestAdminActions { AutoIngestDashboard dashboard = tc.getAutoIngestDashboard(); if (dashboard != null) { /* - * Call setCursor on this to ensure it appears (if there is - * time to see it). + * Call setCursor on this to ensure it appears (if there is time + * to see it). */ dashboard.getCompletedJobsPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); EventQueue.invokeLater(() -> { @@ -359,18 +362,61 @@ final class AutoIngestAdminActions { } } - @NbBundle.Messages({"AutoIngestAdminActions.showCaseLogAction.title=Show Case Log"}) + @NbBundle.Messages({"AutoIngestAdminActions.showCaseLogAction.title=Show Case Log", + "AutoIngestAdminActions.showCaseLogActionFailed.title=Unable to display case log", + "AutoIngestAdminActions.showCaseLogActionFailed.message=Case log file does not exist", + "AutoIngestAdminActions.showCaseLogActionDialog.ok=Okay", + "AutoIngestAdminActions.showCaseLogActionDialog.cannotFindLog=Unable to find the selected case log file", + "AutoIngestAdminActions.showCaseLogActionDialog.unableToShowLogFile=Unable to show log file"}) static final class ShowCaseLogAction extends AbstractAction { private static final long serialVersionUID = 1L; + private final AutoIngestJob job; - ShowCaseLogAction() { + ShowCaseLogAction(AutoIngestJob job) { super(Bundle.AutoIngestAdminActions_showCaseLogAction_title()); + this.job = job; } @Override public void actionPerformed(ActionEvent e) { - //TODO JIRA- + if (job == null) { + return; + } + + final AutoIngestDashboardTopComponent tc = (AutoIngestDashboardTopComponent) WindowManager.getDefault().findTopComponent(AutoIngestDashboardTopComponent.PREFERRED_ID); + if (tc == null) { + return; + } + + AutoIngestDashboard dashboard = tc.getAutoIngestDashboard(); + if (dashboard != null) { + try { + Path caseDirectoryPath = job.getCaseDirectoryPath(); + if (null != caseDirectoryPath) { + Path pathToLog = AutoIngestJobLogger.getLogPath(caseDirectoryPath); + if (pathToLog.toFile().exists()) { + Desktop.getDesktop().edit(pathToLog.toFile()); + } else { + JOptionPane.showMessageDialog(dashboard, Bundle.AutoIngestAdminActions_showCaseLogActionFailed_message(), + Bundle.AutoIngestAdminActions_showCaseLogAction_title(), JOptionPane.ERROR_MESSAGE); + } + } else { + MessageNotifyUtil.Message.warn("The case directory for this job has been deleted."); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, "Dashboard error attempting to display case auto ingest log", ex); + Object[] options = {Bundle.AutoIngestAdminActions_showCaseLogActionDialog_ok()}; + JOptionPane.showOptionDialog(dashboard, + Bundle.AutoIngestAdminActions_showCaseLogActionDialog_cannotFindLog(), + Bundle.AutoIngestAdminActions_showCaseLogActionDialog_unableToShowLogFile(), + JOptionPane.DEFAULT_OPTION, + JOptionPane.PLAIN_MESSAGE, + null, + options, + options[0]); + } + } } @Override diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index 2e229b4a10..feea741374 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -208,7 +208,7 @@ final class AutoIngestJobsNode extends AbstractNode { case COMPLETED_JOB: actions.add(new AutoIngestAdminActions.ReprocessJobAction(autoIngestJob)); actions.add(new AutoIngestAdminActions.DeleteCaseAction(autoIngestJob)); - actions.add(new AutoIngestAdminActions.ShowCaseLogAction()); + actions.add(new AutoIngestAdminActions.ShowCaseLogAction(autoIngestJob)); break; default: } From 574ca6c0cc035c6667d4a0a319c06d90de1204d8 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 15 May 2018 17:29:05 -0400 Subject: [PATCH 49/63] Cosmetic change to EncryptionDetectionTest for travis rebuild --- .../modules/encryptiondetection/EncryptionDetectionTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java index 0edd140bd1..2fa219a86d 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java @@ -52,11 +52,9 @@ public class EncryptionDetectionTest extends NbTestCase { private static final String BITLOCKER_CASE_NAME = "testBitlockerEncryption"; private final Path BITLOCKER_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "encryption_detection_bitlocker_test.vhd"); - private static final String PASSWORD_DETECTION_CASE_NAME = "PasswordDetectionTest"; - private static final String VERACRYPT_DETECTION_CASE_NAME = "VeraCryptDetectionTest"; - private final Path PASSWORD_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "password_detection_test.img"); + private static final String VERACRYPT_DETECTION_CASE_NAME = "VeraCryptDetectionTest"; private final Path VERACRYPT_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "veracrypt_detection_test.vhd"); public static Test suite() { From 4480f4c910cdd980dda2f21834b9424c736a2b32 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 15 May 2018 17:31:32 -0400 Subject: [PATCH 50/63] 3815 fix netbeans warnings regarding access and EventBus use --- .../autoingest/AutoIngestJobsNode.java | 52 ++++++++++++++----- .../autoingest/AutoIngestJobsPanel.java | 6 +-- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index d5d7d86586..14e942bec0 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -42,8 +42,8 @@ import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; */ final class AutoIngestJobsNode extends AbstractNode { - private final static EventBus refreshChildrenEventBus = new EventBus("AutoIngestJobsNodeEventBus"); - private final static short REFRESH_EVENT = 1; // + private final static String REFRESH_EVENT = "Refresh_Nodes"; // + private final EventBus refreshChildrenEventBus; @Messages({ "AutoIngestJobsNode.caseName.text=Case Name", @@ -60,10 +60,14 @@ final class AutoIngestJobsNode extends AbstractNode { /** * Construct a new AutoIngestJobsNode. */ - AutoIngestJobsNode(AutoIngestMonitor autoIngestMonitor, AutoIngestJobStatus status) { - super(Children.create(new AutoIngestNodeChildren(autoIngestMonitor, status), false)); + AutoIngestJobsNode(AutoIngestMonitor autoIngestMonitor, AutoIngestJobStatus status, EventBus eventBus) { + super(Children.create(new AutoIngestNodeChildren(autoIngestMonitor, status, eventBus), false)); + refreshChildrenEventBus = eventBus; } + /** + * Refresh the contents of the AutoIngestJobsNode and all of its children. + */ void refresh() { refreshChildrenEventBus.post(REFRESH_EVENT); } @@ -75,6 +79,8 @@ final class AutoIngestJobsNode extends AbstractNode { private final AutoIngestJobStatus autoIngestJobStatus; private final AutoIngestMonitor autoIngestMonitor; + private final EventBus refreshEventBus; + private boolean registered = false; /** * Create children nodes for the AutoIngestJobsNode which will each @@ -83,10 +89,10 @@ final class AutoIngestJobsNode extends AbstractNode { * @param snapshot the snapshot which contains the AutoIngestJobs * @param status the status of the jobs being displayed */ - AutoIngestNodeChildren(AutoIngestMonitor monitor, AutoIngestJobStatus status) { + AutoIngestNodeChildren(AutoIngestMonitor monitor, AutoIngestJobStatus status, EventBus eventBus) { autoIngestMonitor = monitor; autoIngestJobStatus = status; - refreshChildrenEventBus.register(this); + refreshEventBus = eventBus; } @Override @@ -110,17 +116,30 @@ final class AutoIngestJobsNode extends AbstractNode { if (jobs != null && jobs.size() > 0) { list.addAll(jobs); } + if (!registered) { + refreshEventBus.register(this); //register for refreshes + registered = true; + } return true; } @Override protected Node createNodeForKey(AutoIngestJob key) { - return new JobNode(key, autoIngestJobStatus); + JobNode jobNode = new JobNode(key, autoIngestJobStatus); + refreshEventBus.register(jobNode); + return jobNode; } + /** + * Receive events of type String from the EventBus which this class is + * registered to, and refresh the children created by this factory if + * the event matches the REFRESH_EVENT. + * + * @param refreshEvent the String which was received + */ @Subscribe - private void subscribeToRefresh(short refreshEvent) { - if (refreshEvent == REFRESH_EVENT) { + private void subscribeToRefresh(String refreshEvent) { + if (refreshEvent.equals(REFRESH_EVENT)) { refresh(true); } } @@ -148,7 +167,6 @@ final class AutoIngestJobsNode extends AbstractNode { autoIngestJob = job; setName(autoIngestJob.toString()); //alows job to be uniquely found by name since it will involve a hash of the AutoIngestJob setDisplayName(autoIngestJob.getManifest().getCaseName()); //displays user friendly case name as name - refreshChildrenEventBus.register(this); } /** @@ -204,16 +222,22 @@ final class AutoIngestJobsNode extends AbstractNode { return s; } + /** + * Receive events of type String from the EventBus which this class is + * registered to, and refresh the node's properties if the event matches + * the REFRESH_EVENT. + * + * @param refreshEvent the String which was received + */ @Subscribe - private void subscribeToRefresh(short refreshEvent) { - if (refreshEvent == REFRESH_EVENT) { + private void subscribeToRefresh(String refreshEvent) { + if (refreshEvent.equals(REFRESH_EVENT)) { this.setSheet(createSheet()); } } @Override - public Action[] getActions(boolean context - ) { + public Action[] getActions(boolean context) { List actions = new ArrayList<>(); if (AutoIngestDashboard.isAdminAutoIngestDashboard()) { switch (jobStatus) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index 403487f208..e900d19a92 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.experimental.autoingest; import com.google.common.eventbus.EventBus; import java.awt.Dimension; -import java.beans.PropertyVetoException; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionListener; import org.netbeans.swing.outline.DefaultOutlineModel; @@ -31,7 +30,6 @@ import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.AutoIngestJobStatus; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.JobNode; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; /** * A panel which displays an outline view with all jobs for a specified status. @@ -168,9 +166,9 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa synchronized (this) { outline.setRowSelectionAllowed(false); if (explorerManager.getRootContext() instanceof AutoIngestJobsNode) { - ((AutoIngestJobsNode) explorerManager.getRootContext()).refresh(); + ((AutoIngestJobsNode)explorerManager.getRootContext()).refresh(); } else { - explorerManager.setRootContext(new AutoIngestJobsNode(autoIngestMonitor, status)); + explorerManager.setRootContext(new AutoIngestJobsNode(autoIngestMonitor, status, new EventBus("AutoIngestJobsNodeEventBus"))); } outline.setRowSelectionAllowed(true); outline.setFocusable(true); From 26cfb15f6be7743003dff37ba31279f74ee0f7e9 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 16 May 2018 13:19:53 -0400 Subject: [PATCH 51/63] 3815 make registration and subscription occur in non static inner classes --- .../autoingest/AutoIngestJobsNode.java | 79 +++++++++++-------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index 14e942bec0..4db9dbf4b7 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -79,8 +79,8 @@ final class AutoIngestJobsNode extends AbstractNode { private final AutoIngestJobStatus autoIngestJobStatus; private final AutoIngestMonitor autoIngestMonitor; + private final RefreshChildrenSubscriber refreshChildrenSubscriber = new RefreshChildrenSubscriber(); private final EventBus refreshEventBus; - private boolean registered = false; /** * Create children nodes for the AutoIngestJobsNode which will each @@ -93,6 +93,7 @@ final class AutoIngestJobsNode extends AbstractNode { autoIngestMonitor = monitor; autoIngestJobStatus = status; refreshEventBus = eventBus; + refreshChildrenSubscriber.register(refreshEventBus); } @Override @@ -116,30 +117,32 @@ final class AutoIngestJobsNode extends AbstractNode { if (jobs != null && jobs.size() > 0) { list.addAll(jobs); } - if (!registered) { - refreshEventBus.register(this); //register for refreshes - registered = true; - } return true; } @Override protected Node createNodeForKey(AutoIngestJob key) { - JobNode jobNode = new JobNode(key, autoIngestJobStatus); - refreshEventBus.register(jobNode); - return jobNode; + return new JobNode(key, autoIngestJobStatus, refreshEventBus); } - /** - * Receive events of type String from the EventBus which this class is - * registered to, and refresh the children created by this factory if - * the event matches the REFRESH_EVENT. - * - * @param refreshEvent the String which was received - */ - @Subscribe - private void subscribeToRefresh(String refreshEvent) { - if (refreshEvent.equals(REFRESH_EVENT)) { + private class RefreshChildrenSubscriber { + + private RefreshChildrenSubscriber() { + } + + private void register(EventBus eventBus) { + eventBus.register(this); + } + + /** + * Receive events of type String from the EventBus which this class + * is registered to, and refresh the children created by this + * factory if the event matches the REFRESH_EVENT. + * + * @param refreshEvent the String which was received + */ + @Subscribe + private void subscribeToRefresh(String refreshEvent) { refresh(true); } } @@ -153,6 +156,7 @@ final class AutoIngestJobsNode extends AbstractNode { private final AutoIngestJob autoIngestJob; private final AutoIngestJobStatus jobStatus; + private final RefreshNodeSubscriber refreshNodeSubscriber = new RefreshNodeSubscriber(); /** * Construct a new JobNode to represent an AutoIngestJob and its status. @@ -161,12 +165,13 @@ final class AutoIngestJobsNode extends AbstractNode { * @param status - the current status of the AutoIngestJob being * represented */ - JobNode(AutoIngestJob job, AutoIngestJobStatus status) { + JobNode(AutoIngestJob job, AutoIngestJobStatus status, EventBus eventBus) { super(Children.LEAF); jobStatus = status; autoIngestJob = job; setName(autoIngestJob.toString()); //alows job to be uniquely found by name since it will involve a hash of the AutoIngestJob setDisplayName(autoIngestJob.getManifest().getCaseName()); //displays user friendly case name as name + refreshNodeSubscriber.register(eventBus); } /** @@ -222,20 +227,6 @@ final class AutoIngestJobsNode extends AbstractNode { return s; } - /** - * Receive events of type String from the EventBus which this class is - * registered to, and refresh the node's properties if the event matches - * the REFRESH_EVENT. - * - * @param refreshEvent the String which was received - */ - @Subscribe - private void subscribeToRefresh(String refreshEvent) { - if (refreshEvent.equals(REFRESH_EVENT)) { - this.setSheet(createSheet()); - } - } - @Override public Action[] getActions(boolean context) { List actions = new ArrayList<>(); @@ -266,6 +257,28 @@ final class AutoIngestJobsNode extends AbstractNode { } return actions.toArray(new Action[actions.size()]); } + + private class RefreshNodeSubscriber { + + private RefreshNodeSubscriber() { + } + + private void register(EventBus eventBus) { + eventBus.register(this); + } + + /** + * Receive events of type String from the EventBus which this class + * is registered to, and refresh the node's properties if the event + * matches the REFRESH_EVENT. + * + * @param refreshEvent the String which was received + */ + @Subscribe + private void subscribeToRefresh(String refreshEvent) { + setSheet(createSheet()); + } + } } /** From 4a757752571e7098304cac3733cb74ad5521cfe5 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 16 May 2018 13:43:08 -0400 Subject: [PATCH 52/63] 3815 change arguement to AutoIngestJobsNode back to snapshot from monitor --- .../experimental/autoingest/AutoIngestDashboard.java | 6 +++--- .../experimental/autoingest/AutoIngestJobsNode.java | 11 +++++------ .../experimental/autoingest/AutoIngestJobsPanel.java | 5 +++-- .../experimental/autoingest/PrioritizationAction.java | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 88ff204696..3704bf7a7e 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -261,9 +261,9 @@ final class AutoIngestDashboard extends JPanel implements Observer { * @param nodeStateSnapshot The jobs snapshot. */ void refreshTables() { - pendingJobsPanel.refresh(autoIngestMonitor); - runningJobsPanel.refresh(autoIngestMonitor); - completedJobsPanel.refresh(autoIngestMonitor); + pendingJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); + runningJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); + completedJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index 4db9dbf4b7..f7913f5803 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -60,8 +60,8 @@ final class AutoIngestJobsNode extends AbstractNode { /** * Construct a new AutoIngestJobsNode. */ - AutoIngestJobsNode(AutoIngestMonitor autoIngestMonitor, AutoIngestJobStatus status, EventBus eventBus) { - super(Children.create(new AutoIngestNodeChildren(autoIngestMonitor, status, eventBus), false)); + AutoIngestJobsNode(JobsSnapshot jobsSnapshot, AutoIngestJobStatus status, EventBus eventBus) { + super(Children.create(new AutoIngestNodeChildren(jobsSnapshot, status, eventBus), false)); refreshChildrenEventBus = eventBus; } @@ -78,7 +78,7 @@ final class AutoIngestJobsNode extends AbstractNode { static final class AutoIngestNodeChildren extends ChildFactory { private final AutoIngestJobStatus autoIngestJobStatus; - private final AutoIngestMonitor autoIngestMonitor; + private final JobsSnapshot jobsSnapshot; private final RefreshChildrenSubscriber refreshChildrenSubscriber = new RefreshChildrenSubscriber(); private final EventBus refreshEventBus; @@ -89,8 +89,8 @@ final class AutoIngestJobsNode extends AbstractNode { * @param snapshot the snapshot which contains the AutoIngestJobs * @param status the status of the jobs being displayed */ - AutoIngestNodeChildren(AutoIngestMonitor monitor, AutoIngestJobStatus status, EventBus eventBus) { - autoIngestMonitor = monitor; + AutoIngestNodeChildren(JobsSnapshot snapshot, AutoIngestJobStatus status, EventBus eventBus) { + jobsSnapshot = snapshot; autoIngestJobStatus = status; refreshEventBus = eventBus; refreshChildrenSubscriber.register(refreshEventBus); @@ -99,7 +99,6 @@ final class AutoIngestJobsNode extends AbstractNode { @Override protected boolean createKeys(List list) { List jobs; - JobsSnapshot jobsSnapshot = autoIngestMonitor.getJobsSnapshot(); switch (autoIngestJobStatus) { case PENDING_JOB: jobs = jobsSnapshot.getPendingJobs(); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index e900d19a92..f3b0c907ad 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -30,6 +30,7 @@ import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.AutoIngestJobStatus; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.JobNode; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; /** * A panel which displays an outline view with all jobs for a specified status. @@ -162,13 +163,13 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa * @param jobsSnapshot - the JobsSnapshot which will provide the new * contents */ - void refresh(AutoIngestMonitor autoIngestMonitor) { + void refresh(JobsSnapshot jobsSnapshot) { synchronized (this) { outline.setRowSelectionAllowed(false); if (explorerManager.getRootContext() instanceof AutoIngestJobsNode) { ((AutoIngestJobsNode)explorerManager.getRootContext()).refresh(); } else { - explorerManager.setRootContext(new AutoIngestJobsNode(autoIngestMonitor, status, new EventBus("AutoIngestJobsNodeEventBus"))); + explorerManager.setRootContext(new AutoIngestJobsNode(jobsSnapshot, status, new EventBus("AutoIngestJobsNodeEventBus"))); } outline.setRowSelectionAllowed(true); outline.setFocusable(true); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java index 4e362f4cb5..d50a85f906 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java @@ -88,7 +88,7 @@ abstract class PrioritizationAction extends AbstractAction { EventQueue.invokeLater(() -> { try { modifyPriority(dashboard.getMonitor()); - dashboard.getPendingJobsPanel().refresh(dashboard.getMonitor()); + dashboard.getPendingJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot()); } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { String errorMessage = getErrorMessage(); logger.log(Level.SEVERE, errorMessage, ex); From 1a319d89a50764022a0dc7e521a2a7eb1d0de835 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 16 May 2018 14:12:55 -0400 Subject: [PATCH 53/63] 3815 make refresh event carry information on what to refresh --- .../autoingest/AutoIngestDashboard.java | 9 ++-- .../autoingest/AutoIngestJobsNode.java | 13 +++--- .../autoingest/AutoIngestJobsPanel.java | 20 +++++--- .../AutoIngestNodeRefreshEvent.java | 46 +++++++++++++++++++ .../autoingest/PrioritizationAction.java | 2 +- 5 files changed, 74 insertions(+), 16 deletions(-) create mode 100644 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvent.java diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 3704bf7a7e..c58b01be6d 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -230,6 +230,9 @@ final class AutoIngestDashboard extends JPanel implements Observer { new Thread(() -> { try { autoIngestMonitor.startUp(); + pendingJobsPanel.createAutoIngestJobsNode(autoIngestMonitor.getJobsSnapshot()); + runningJobsPanel.createAutoIngestJobsNode(autoIngestMonitor.getJobsSnapshot()); + completedJobsPanel.createAutoIngestJobsNode(autoIngestMonitor.getJobsSnapshot()); } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { LOGGER.log(Level.SEVERE, "Unable to start up Auto Ingest Monitor", ex); } @@ -261,9 +264,9 @@ final class AutoIngestDashboard extends JPanel implements Observer { * @param nodeStateSnapshot The jobs snapshot. */ void refreshTables() { - pendingJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); - runningJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); - completedJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); + pendingJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot(), new AutoIngestNodeRefreshEvent()); + runningJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot(), new AutoIngestNodeRefreshEvent()); + completedJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot(), new AutoIngestNodeRefreshEvent()); } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index f7913f5803..c3493ce14e 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -42,7 +42,6 @@ import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; */ final class AutoIngestJobsNode extends AbstractNode { - private final static String REFRESH_EVENT = "Refresh_Nodes"; // private final EventBus refreshChildrenEventBus; @Messages({ @@ -68,8 +67,8 @@ final class AutoIngestJobsNode extends AbstractNode { /** * Refresh the contents of the AutoIngestJobsNode and all of its children. */ - void refresh() { - refreshChildrenEventBus.post(REFRESH_EVENT); + void refresh(AutoIngestNodeRefreshEvent refreshEvent) { + refreshChildrenEventBus.post(refreshEvent); } /** @@ -141,8 +140,10 @@ final class AutoIngestJobsNode extends AbstractNode { * @param refreshEvent the String which was received */ @Subscribe - private void subscribeToRefresh(String refreshEvent) { - refresh(true); + private void subscribeToRefresh(AutoIngestNodeRefreshEvent refreshEvent) { + if (refreshEvent.shouldRefreshChildren()) { + refresh(true); + } } } @@ -274,7 +275,7 @@ final class AutoIngestJobsNode extends AbstractNode { * @param refreshEvent the String which was received */ @Subscribe - private void subscribeToRefresh(String refreshEvent) { + private void subscribeToRefresh(AutoIngestNodeRefreshEvent refreshEvent) { setSheet(createSheet()); } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index f3b0c907ad..9da0489d67 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -163,14 +163,22 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa * @param jobsSnapshot - the JobsSnapshot which will provide the new * contents */ - void refresh(JobsSnapshot jobsSnapshot) { + void refresh(JobsSnapshot jobsSnapshot, AutoIngestNodeRefreshEvent refreshEvent) { + synchronized (this) { + if (explorerManager.getRootContext() instanceof AutoIngestJobsNode) { + outline.setRowSelectionAllowed(false); + ((AutoIngestJobsNode) explorerManager.getRootContext()).refresh(refreshEvent); + outline.setRowSelectionAllowed(true); + outline.setFocusable(true); + } + + } + } + + void createAutoIngestJobsNode(JobsSnapshot jobsSnapshot) { synchronized (this) { outline.setRowSelectionAllowed(false); - if (explorerManager.getRootContext() instanceof AutoIngestJobsNode) { - ((AutoIngestJobsNode)explorerManager.getRootContext()).refresh(); - } else { - explorerManager.setRootContext(new AutoIngestJobsNode(jobsSnapshot, status, new EventBus("AutoIngestJobsNodeEventBus"))); - } + explorerManager.setRootContext(new AutoIngestJobsNode(jobsSnapshot, status, new EventBus("AutoIngestJobsNodeEventBus"))); outline.setRowSelectionAllowed(true); outline.setFocusable(true); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvent.java new file mode 100644 index 0000000000..e788d2a705 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvent.java @@ -0,0 +1,46 @@ +/* + * 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.experimental.autoingest; + +class AutoIngestNodeRefreshEvent { + private final String caseName; + AutoIngestJob job; + + AutoIngestNodeRefreshEvent(){ + caseName = ""; + job = null; + + } + + AutoIngestNodeRefreshEvent(String name){ + caseName = name; + job = null; + } + + AutoIngestNodeRefreshEvent(AutoIngestJob autoIngestJob){ + caseName = ""; + job = autoIngestJob; + } + + boolean shouldRefreshChildren(){ + return (caseName.isEmpty() && job == null); + } + + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java index d50a85f906..c7f2180718 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java @@ -88,7 +88,7 @@ abstract class PrioritizationAction extends AbstractAction { EventQueue.invokeLater(() -> { try { modifyPriority(dashboard.getMonitor()); - dashboard.getPendingJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot()); + dashboard.getPendingJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot(), new AutoIngestNodeRefreshEvent()); } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { String errorMessage = getErrorMessage(); logger.log(Level.SEVERE, errorMessage, ex); From 3b5dd768eb6608ff2af723038e9f5206f2530146 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 16 May 2018 15:32:11 -0400 Subject: [PATCH 54/63] 3815 make only necessary portions of AutoIngestJobsNode refresh --- .../autoingest/AutoIngestDashboard.java | 10 ++-- .../autoingest/AutoIngestJobsNode.java | 37 ++++++++++--- .../autoingest/AutoIngestJobsPanel.java | 22 +++----- ....java => AutoIngestNodeRefreshEvents.java} | 53 ++++++++++++------- .../autoingest/PrioritizationAction.java | 26 ++++++++- 5 files changed, 101 insertions(+), 47 deletions(-) rename Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/{AutoIngestNodeRefreshEvent.java => AutoIngestNodeRefreshEvents.java} (50%) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index c58b01be6d..7b653f66bc 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -39,6 +39,7 @@ import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.core.ServicesMonitor; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeRefreshEvents.RefreshChildrenEvent; /** * A dashboard for monitoring an automated ingest cluster. @@ -230,9 +231,6 @@ final class AutoIngestDashboard extends JPanel implements Observer { new Thread(() -> { try { autoIngestMonitor.startUp(); - pendingJobsPanel.createAutoIngestJobsNode(autoIngestMonitor.getJobsSnapshot()); - runningJobsPanel.createAutoIngestJobsNode(autoIngestMonitor.getJobsSnapshot()); - completedJobsPanel.createAutoIngestJobsNode(autoIngestMonitor.getJobsSnapshot()); } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { LOGGER.log(Level.SEVERE, "Unable to start up Auto Ingest Monitor", ex); } @@ -264,9 +262,9 @@ final class AutoIngestDashboard extends JPanel implements Observer { * @param nodeStateSnapshot The jobs snapshot. */ void refreshTables() { - pendingJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot(), new AutoIngestNodeRefreshEvent()); - runningJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot(), new AutoIngestNodeRefreshEvent()); - completedJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot(), new AutoIngestNodeRefreshEvent()); + pendingJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot(), new RefreshChildrenEvent()); + runningJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot(), new RefreshChildrenEvent()); + completedJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot(), new RefreshChildrenEvent()); } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index c3493ce14e..e77213e545 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.experimental.autoingest; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; +import com.google.common.eventbus.DeadEvent; import javax.swing.Action; import java.time.Instant; import java.util.ArrayList; @@ -67,7 +68,7 @@ final class AutoIngestJobsNode extends AbstractNode { /** * Refresh the contents of the AutoIngestJobsNode and all of its children. */ - void refresh(AutoIngestNodeRefreshEvent refreshEvent) { + void refresh(AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent refreshEvent) { refreshChildrenEventBus.post(refreshEvent); } @@ -140,10 +141,12 @@ final class AutoIngestJobsNode extends AbstractNode { * @param refreshEvent the String which was received */ @Subscribe - private void subscribeToRefresh(AutoIngestNodeRefreshEvent refreshEvent) { - if (refreshEvent.shouldRefreshChildren()) { - refresh(true); - } + private void subscribeToRefresh(AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent refreshEvent) { + refresh(true); + } + + @Subscribe + private void subscribeToIgnoreDeadEvents(DeadEvent ignored){ } } @@ -275,8 +278,28 @@ final class AutoIngestJobsNode extends AbstractNode { * @param refreshEvent the String which was received */ @Subscribe - private void subscribeToRefresh(AutoIngestNodeRefreshEvent refreshEvent) { - setSheet(createSheet()); + private void subscribeToRefreshJob(AutoIngestNodeRefreshEvents.RefreshJobEvent refreshEvent) { + if (getAutoIngestJob().equals(refreshEvent.getJobToRefresh())) { + setSheet(createSheet()); + } + } + + /** + * Receive events of type String from the EventBus which this class + * is registered to, and refresh the node's properties if the event + * matches the REFRESH_EVENT. + * + * @param refreshEvent the String which was received + */ + @Subscribe + private void subscribeToRefreshCase(AutoIngestNodeRefreshEvents.RefreshCaseEvent refreshEvent) { + if (getAutoIngestJob().getManifest().getCaseName().equals(refreshEvent.getCaseToRefresh())) { + setSheet(createSheet()); + } + } + + @Subscribe + private void subscribeToIgnoreDeadEvents(DeadEvent ignored){ } } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index 9da0489d67..9820f08e74 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -31,6 +31,7 @@ import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.AutoIngestJobStatus; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.JobNode; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent; /** * A panel which displays an outline view with all jobs for a specified status. @@ -163,24 +164,17 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa * @param jobsSnapshot - the JobsSnapshot which will provide the new * contents */ - void refresh(JobsSnapshot jobsSnapshot, AutoIngestNodeRefreshEvent refreshEvent) { - synchronized (this) { - if (explorerManager.getRootContext() instanceof AutoIngestJobsNode) { - outline.setRowSelectionAllowed(false); - ((AutoIngestJobsNode) explorerManager.getRootContext()).refresh(refreshEvent); - outline.setRowSelectionAllowed(true); - outline.setFocusable(true); - } - - } - } - - void createAutoIngestJobsNode(JobsSnapshot jobsSnapshot) { + void refresh(JobsSnapshot jobsSnapshot, AutoIngestRefreshEvent refreshEvent) { synchronized (this) { outline.setRowSelectionAllowed(false); - explorerManager.setRootContext(new AutoIngestJobsNode(jobsSnapshot, status, new EventBus("AutoIngestJobsNodeEventBus"))); + if (explorerManager.getRootContext() instanceof AutoIngestJobsNode) { + ((AutoIngestJobsNode) explorerManager.getRootContext()).refresh(refreshEvent); + } else { + explorerManager.setRootContext(new AutoIngestJobsNode(jobsSnapshot, status, new EventBus("AutoIngestJobsNodeEventBus"))); + } outline.setRowSelectionAllowed(true); outline.setFocusable(true); + } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvents.java similarity index 50% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvent.java rename to Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvents.java index e788d2a705..13856bfebe 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvents.java @@ -18,29 +18,44 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; -class AutoIngestNodeRefreshEvent { - private final String caseName; - AutoIngestJob job; +class AutoIngestNodeRefreshEvents { - AutoIngestNodeRefreshEvent(){ - caseName = ""; - job = null; + interface AutoIngestRefreshEvent { } - - AutoIngestNodeRefreshEvent(String name){ - caseName = name; - job = null; + + static final class RefreshChildrenEvent implements AutoIngestRefreshEvent{ + + RefreshChildrenEvent() { + + } } - - AutoIngestNodeRefreshEvent(AutoIngestJob autoIngestJob){ - caseName = ""; - job = autoIngestJob; + + static final class RefreshCaseEvent implements AutoIngestRefreshEvent{ + + private final String caseName; + + RefreshCaseEvent(String name) { + caseName = name; + } + + String getCaseToRefresh() { + return caseName; + } + } - - boolean shouldRefreshChildren(){ - return (caseName.isEmpty() && job == null); + + static final class RefreshJobEvent implements AutoIngestRefreshEvent{ + + private final AutoIngestJob autoIngestJob; + + RefreshJobEvent(AutoIngestJob job) { + autoIngestJob = job; + } + + AutoIngestJob getJobToRefresh() { + return autoIngestJob; + } } - - + } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java index c7f2180718..371fe71ad3 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java @@ -27,6 +27,7 @@ import org.openide.util.NbBundle.Messages; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeRefreshEvents; /** * Abstract actions which are for the modification of AutoIngestJob or Case @@ -38,6 +39,7 @@ abstract class PrioritizationAction extends AbstractAction { private static final Logger logger = Logger.getLogger(PrioritizationAction.class.getName()); private final AutoIngestJob job; + /** * Construct a new Prioritization action for the selected job * @@ -88,7 +90,7 @@ abstract class PrioritizationAction extends AbstractAction { EventQueue.invokeLater(() -> { try { modifyPriority(dashboard.getMonitor()); - dashboard.getPendingJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot(), new AutoIngestNodeRefreshEvent()); + dashboard.getPendingJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot(), getRefreshEvent()); } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { String errorMessage = getErrorMessage(); logger.log(Level.SEVERE, errorMessage, ex); @@ -107,6 +109,8 @@ abstract class PrioritizationAction extends AbstractAction { return super.clone(); //To change body of generated methods, choose Tools | Templates. } + abstract AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent(); + /** * Action to prioritize the specified AutoIngestJob */ @@ -139,6 +143,11 @@ abstract class PrioritizationAction extends AbstractAction { public Object clone() throws CloneNotSupportedException { return super.clone(); //To change body of generated methods, choose Tools | Templates. } + + @Override + AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent() { + return new AutoIngestNodeRefreshEvents.RefreshJobEvent(getJob()); + } } /** @@ -173,6 +182,11 @@ abstract class PrioritizationAction extends AbstractAction { public Object clone() throws CloneNotSupportedException { return super.clone(); //To change body of generated methods, choose Tools | Templates. } + + @Override + AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent() { + return new AutoIngestNodeRefreshEvents.RefreshJobEvent(getJob()); + } } /** @@ -209,6 +223,11 @@ abstract class PrioritizationAction extends AbstractAction { public Object clone() throws CloneNotSupportedException { return super.clone(); //To change body of generated methods, choose Tools | Templates. } + + @Override + AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent() { + return new AutoIngestNodeRefreshEvents.RefreshCaseEvent(getJob().getManifest().getCaseName()); + } } /** @@ -245,5 +264,10 @@ abstract class PrioritizationAction extends AbstractAction { public Object clone() throws CloneNotSupportedException { return super.clone(); //To change body of generated methods, choose Tools | Templates. } + + @Override + AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent() { + return new AutoIngestNodeRefreshEvents.RefreshCaseEvent(getJob().getManifest().getCaseName()); + } } } From 304d27790e815f1bd68b78035cdc299e59b46c16 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 16 May 2018 15:49:43 -0400 Subject: [PATCH 55/63] 3815 add comments to new methods and classes --- .../autoingest/AutoIngestJobsNode.java | 57 ++++++++++++++++--- .../AutoIngestNodeRefreshEvents.java | 50 ++++++++++++++-- 2 files changed, 93 insertions(+), 14 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index e77213e545..e6cada2fe7 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -124,29 +124,50 @@ final class AutoIngestJobsNode extends AbstractNode { return new JobNode(key, autoIngestJobStatus, refreshEventBus); } + /** + * Class which registers with EventBus and causes child nodes which + * exist to be refreshed. + */ private class RefreshChildrenSubscriber { + /** + * Construct a RefreshChildrenSubscriber + */ private RefreshChildrenSubscriber() { } + /** + * Registers this subscriber with the specified EventBus to receive + * events posted to it. + * + * @param eventBus - the EventBus to register this subscriber to + */ private void register(EventBus eventBus) { eventBus.register(this); } /** - * Receive events of type String from the EventBus which this class - * is registered to, and refresh the children created by this - * factory if the event matches the REFRESH_EVENT. + * Receive events which implement the AutoIngestRefreshEvent + * interface from the EventBus which this class is registered to, + * and refresh the children created by this factory. + * * * @param refreshEvent the String which was received */ @Subscribe private void subscribeToRefresh(AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent refreshEvent) { + //RefreshChildrenEvents can change which children are present however + //RefreshJobEvents and RefreshCaseEvents can still change the order we want to display them in refresh(true); } - - @Subscribe - private void subscribeToIgnoreDeadEvents(DeadEvent ignored){ + + /** + * Ignores other events sent over the registered EventBus + * + * @param ignored the event which is being ignored + */ + @Subscribe + private void subscribeToIgnoreDeadEvents(DeadEvent ignored) { } } @@ -261,11 +282,24 @@ final class AutoIngestJobsNode extends AbstractNode { return actions.toArray(new Action[actions.size()]); } + /** + * Class which registers with EventBus and causes specific nodes to have + * their properties to be refreshed. + */ private class RefreshNodeSubscriber { + /** + * Constructs a RefreshNodeSubscriber + */ private RefreshNodeSubscriber() { } + /** + * Registers this subscriber with the specified EventBus to receive + * events posted to it. + * + * @param eventBus - the EventBus to register this subscriber to + */ private void register(EventBus eventBus) { eventBus.register(this); } @@ -297,9 +331,14 @@ final class AutoIngestJobsNode extends AbstractNode { setSheet(createSheet()); } } - - @Subscribe - private void subscribeToIgnoreDeadEvents(DeadEvent ignored){ + + /** + * Ignores other events sent over the registered EventBus + * + * @param ignored the event which is being ignored + */ + @Subscribe + private void subscribeToIgnoreDeadEvents(DeadEvent ignored) { } } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvents.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvents.java index 13856bfebe..c01158f44a 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvents.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvents.java @@ -18,41 +18,81 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +/** + * Class which contains events to identify what should be refreshed in the + * AutoIngestJobsNode + */ class AutoIngestNodeRefreshEvents { - + + /** + * An empty interface for all refresh events to implement. + */ interface AutoIngestRefreshEvent { - + } - static final class RefreshChildrenEvent implements AutoIngestRefreshEvent{ + /** + * An event to denote that the children of the AutoIngestJobsNode should be + * refreshed but no specific nodes need their properties refreshed. + */ + static final class RefreshChildrenEvent implements AutoIngestRefreshEvent { + /** + * Constructs a RefreshChildrenEvent. + */ RefreshChildrenEvent() { } } - static final class RefreshCaseEvent implements AutoIngestRefreshEvent{ + /** + * An event to denote that all nodes which represent jobs which are part of + * the specified case should be refreshed. + */ + static final class RefreshCaseEvent implements AutoIngestRefreshEvent { private final String caseName; + /** + * Constructs a RefreshCaseEvent. + */ RefreshCaseEvent(String name) { caseName = name; } + /** + * Get the case name which should have all it's jobs have their node + * refreshed. + * + * @return caseName - the case which contains the jobs which should have + * their nodes refreshed + */ String getCaseToRefresh() { return caseName; } } - static final class RefreshJobEvent implements AutoIngestRefreshEvent{ + /** + * An event to denote that a node for a specific job should be refreshed. + */ + static final class RefreshJobEvent implements AutoIngestRefreshEvent { private final AutoIngestJob autoIngestJob; + /** + * Constructs a RefreshJobEvent. + */ RefreshJobEvent(AutoIngestJob job) { autoIngestJob = job; } + /** + * Get the AutoIngestJob which should have it's node refresheds. + * + * @return autoIngestJob - the AutoIngestJob which should have it's node + * refreshed + */ AutoIngestJob getJobToRefresh() { return autoIngestJob; } From f78010a3d1bdc25d9e7f66b834acae9b01c4a940 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 16 May 2018 16:10:12 -0400 Subject: [PATCH 56/63] 3815 remove deadEvent sub, add sub to refreshChildren for running jobs --- .../autoingest/AutoIngestJobsNode.java | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index e6cada2fe7..091a593e74 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.experimental.autoingest; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; -import com.google.common.eventbus.DeadEvent; import javax.swing.Action; import java.time.Instant; import java.util.ArrayList; @@ -161,14 +160,6 @@ final class AutoIngestJobsNode extends AbstractNode { refresh(true); } - /** - * Ignores other events sent over the registered EventBus - * - * @param ignored the event which is being ignored - */ - @Subscribe - private void subscribeToIgnoreDeadEvents(DeadEvent ignored) { - } } } @@ -333,13 +324,18 @@ final class AutoIngestJobsNode extends AbstractNode { } /** - * Ignores other events sent over the registered EventBus + * Refresh the properties of all running jobs anytime a + * RefreshChildrenEvent is received so that stages and times stay up + * to date. * - * @param ignored the event which is being ignored + * @param refreshEvent - the RefreshChildrenEvent which was received */ - @Subscribe - private void subscribeToIgnoreDeadEvents(DeadEvent ignored) { + private void subscribeToRefreshChildren(AutoIngestNodeRefreshEvents.RefreshChildrenEvent refreshEvent) { + if (jobStatus == AutoIngestJobStatus.RUNNING_JOB) { + setSheet(createSheet()); + } } + } } From 8ac29a33cf32af78e9681b25fcff01b54221179d Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 16 May 2018 16:20:51 -0400 Subject: [PATCH 57/63] 3815 adjust comments regarding for clarity and accuracy regarding the EventBus in AutoIngestJobsNode --- .../autoingest/AutoIngestJobsNode.java | 25 +++++++++++-------- .../autoingest/AutoIngestJobsPanel.java | 1 + 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index 091a593e74..d0da9028c0 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -41,7 +41,8 @@ import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; * Each job with the specified status will have a child node representing it. */ final class AutoIngestJobsNode extends AbstractNode { - + + //Event bus is non static so that each instance of this will only listen to events sent to that instance private final EventBus refreshChildrenEventBus; @Messages({ @@ -151,10 +152,11 @@ final class AutoIngestJobsNode extends AbstractNode { * and refresh the children created by this factory. * * - * @param refreshEvent the String which was received + * @param refreshEvent the AutoIngestRefreshEvent which was received */ @Subscribe private void subscribeToRefresh(AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent refreshEvent) { + //Ignore netbeans suggesting this isn't being used, it is used behind the scenes by the EventBus //RefreshChildrenEvents can change which children are present however //RefreshJobEvents and RefreshCaseEvents can still change the order we want to display them in refresh(true); @@ -296,28 +298,30 @@ final class AutoIngestJobsNode extends AbstractNode { } /** - * Receive events of type String from the EventBus which this class - * is registered to, and refresh the node's properties if the event - * matches the REFRESH_EVENT. + * Receive events of type RefreshJobEvent from the EventBus which + * this class is registered to and refresh the nodes properties if + * it is the node for the job specified in the event. * - * @param refreshEvent the String which was received + * @param refreshEvent the RefreshJobEvent which was received */ @Subscribe private void subscribeToRefreshJob(AutoIngestNodeRefreshEvents.RefreshJobEvent refreshEvent) { + //Ignore netbeans suggesting this isn't being used, it is used behind the scenes by the EventBus if (getAutoIngestJob().equals(refreshEvent.getJobToRefresh())) { setSheet(createSheet()); } } /** - * Receive events of type String from the EventBus which this class - * is registered to, and refresh the node's properties if the event - * matches the REFRESH_EVENT. + * Receive events of type RefreshCaseEvent from the EventBus which + * this class is registered to and refresh the nodes which have jobs + * which are members of case specified in the event. * - * @param refreshEvent the String which was received + * @param refreshEvent the RefreshCaseEvent which was received */ @Subscribe private void subscribeToRefreshCase(AutoIngestNodeRefreshEvents.RefreshCaseEvent refreshEvent) { + //Ignore netbeans suggesting this isn't being used, it is used behind the scenes by the EventBus if (getAutoIngestJob().getManifest().getCaseName().equals(refreshEvent.getCaseToRefresh())) { setSheet(createSheet()); } @@ -331,6 +335,7 @@ final class AutoIngestJobsNode extends AbstractNode { * @param refreshEvent - the RefreshChildrenEvent which was received */ private void subscribeToRefreshChildren(AutoIngestNodeRefreshEvents.RefreshChildrenEvent refreshEvent) { + //Ignore netbeans suggesting this isn't being used, it is used behind the scenes by the EventBus if (jobStatus == AutoIngestJobStatus.RUNNING_JOB) { setSheet(createSheet()); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index 9820f08e74..561864df59 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -170,6 +170,7 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa if (explorerManager.getRootContext() instanceof AutoIngestJobsNode) { ((AutoIngestJobsNode) explorerManager.getRootContext()).refresh(refreshEvent); } else { + //Make a new AutoIngestJobsNode with it's own EventBus and set it as the root context explorerManager.setRootContext(new AutoIngestJobsNode(jobsSnapshot, status, new EventBus("AutoIngestJobsNodeEventBus"))); } outline.setRowSelectionAllowed(true); From ca6df4867101cd4a6d3fcebb6f00303e447f51e9 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 16 May 2018 16:46:11 -0400 Subject: [PATCH 58/63] 3815 resolve merge conflicts with latest from custom-release-may-2018 --- .../experimental/autoingest/AutoIngestAdminActions.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java index 32d2f0021e..5d8768c25c 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java @@ -209,7 +209,6 @@ final class AutoIngestAdminActions { dashboard.getRunningJobsPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); EventQueue.invokeLater(() -> { dashboard.getMonitor().cancelJob(job); - dashboard.getRunningJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot()); dashboard.getRunningJobsPanel().setCursor(Cursor.getDefaultCursor()); }); } @@ -338,7 +337,7 @@ final class AutoIngestAdminActions { dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); AutoIngestManager.CaseDeletionResult result = dashboard.getMonitor().deleteCase(job); - dashboard.getCompletedJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot()); + dashboard.getCompletedJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot(), new AutoIngestNodeRefreshEvents.RefreshChildrenEvent()); dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); if (AutoIngestManager.CaseDeletionResult.FAILED == result) { JOptionPane.showMessageDialog(dashboard, From 901364a6dca8aff928cbedd0efbf7f54c09f012d Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 16 May 2018 17:18:13 -0400 Subject: [PATCH 59/63] 3815 add missing @Subscribe annotation to RefreshNodeSubscriber --- .../autopsy/experimental/autoingest/AutoIngestJobsNode.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index 50d7257ff6..916245d412 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -334,6 +334,7 @@ final class AutoIngestJobsNode extends AbstractNode { * * @param refreshEvent - the RefreshChildrenEvent which was received */ + @Subscribe private void subscribeToRefreshChildren(AutoIngestNodeRefreshEvents.RefreshChildrenEvent refreshEvent) { //Ignore netbeans suggesting this isn't being used, it is used behind the scenes by the EventBus if (jobStatus == AutoIngestJobStatus.RUNNING_JOB) { From 41cfd733f9c5c05e1699d1fc9fc29a1336456864 Mon Sep 17 00:00:00 2001 From: esaunders Date: Wed, 16 May 2018 17:43:23 -0400 Subject: [PATCH 60/63] Lunacy cleanup. --- .../org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java | 4 ++-- Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java | 4 ++-- .../autopsy/ingest/IngestProgressSnapshotProvider.java | 6 +++--- .../experimental/autoingest/AutoIngestAdminActions.java | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java index 8ed0b26b60..93a1f1505d 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java @@ -1213,11 +1213,11 @@ public final class DataSourceIngestJob { return this.dataSourceLevelIngestModule; } - boolean fileIngestIsRunning() { + boolean getFileIngestIsRunning() { return this.fileIngestRunning; } - Date fileIngestStartTime() { + Date getFileIngestStartTime() { return this.fileIngestStartTime; } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java index 33d767fb00..720313ba15 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java @@ -355,10 +355,10 @@ public final class IngestJob { dataSourceModule = new DataSourceIngestModuleHandle(dataSourceJobs.get(snapshot.getJobId()), module); } } - if (snapshot.fileIngestIsRunning()) { + if (snapshot.getFileIngestIsRunning()) { fileIngestRunning = true; } - Date childFileIngestStartTime = snapshot.fileIngestStartTime(); + Date childFileIngestStartTime = snapshot.getFileIngestStartTime(); if (null != childFileIngestStartTime && (null == fileIngestStartTime || childFileIngestStartTime.before(fileIngestStartTime))) { fileIngestStartTime = childFileIngestStartTime; } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotProvider.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotProvider.java index 646c09bbf7..511939e1f9 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotProvider.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotProvider.java @@ -31,19 +31,19 @@ public interface IngestProgressSnapshotProvider { * * @return A list of IngestThreadActivitySnapshot */ - public List getIngestThreadActivitySnapshots(); + List getIngestThreadActivitySnapshots(); /** * Get a snapshot of the state of ingest jobs. * * @return A list of ingest job snapshots. */ - public List getIngestJobSnapshots(); + List getIngestJobSnapshots(); /** * Gets the cumulative run times for the ingest module. * * @return Map of module name to run time (in milliseconds) */ - public Map getModuleRunTimes(); + Map getModuleRunTimes(); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java index b4bbb61562..f17372f055 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java @@ -156,7 +156,7 @@ final class AutoIngestAdminActions { if (tc != null) { AutoIngestDashboard dashboard = tc.getAutoIngestDashboard(); if (dashboard != null) { - IngestProgressSnapshotDialog ingestProgressSnapshotDialog = new IngestProgressSnapshotDialog(dashboard.getTopLevelAncestor(), true, job); + new IngestProgressSnapshotDialog(dashboard.getTopLevelAncestor(), true, job); } } } From a2f5a9a0877f205cf4741884a8e168b5e508ba5e Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 17 May 2018 19:21:31 -0400 Subject: [PATCH 61/63] Remove public access specifier of IngestTasksScheduler.IngestJobTasksSnapshot --- Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java index f63ed1b885..0c3647dd16 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java @@ -830,7 +830,7 @@ final class IngestTasksScheduler { /** * A snapshot of ingest tasks data for an ingest job. */ - public static final class IngestJobTasksSnapshot implements Serializable { + static final class IngestJobTasksSnapshot implements Serializable { private static final long serialVersionUID = 1L; private final long jobId; From 63a07cf1e1614b7c6f6e8b4c28508035f513fc5b Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 18 May 2018 15:58:52 -0400 Subject: [PATCH 62/63] Update encryption detection module settings --- docs/doxygen-user/encryption_detection.dox | 17 ++++++++++++++--- docs/doxygen-user/images/encrypt_tree.png | Bin 11528 -> 25926 bytes 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/doxygen-user/encryption_detection.dox b/docs/doxygen-user/encryption_detection.dox index 923e2727e5..e415c5065b 100644 --- a/docs/doxygen-user/encryption_detection.dox +++ b/docs/doxygen-user/encryption_detection.dox @@ -2,19 +2,30 @@ \section encrypt_overview Overview -The Encryption Detection Module searches for files that could be encrypted using an entropy calculation. +The Encryption Detection Module searches for files that could be encrypted using both a general entropy calculation and more specialized tests for certain file types. \section encrypt_running Running the module -The module's settings can be configured at runtime. +The module's settings can be configured at runtime. These settings only effect the tests that are based on entropy. \image html encrypt_module.png Minimum entropy can be set higher or lower, depending on how many false hits are being produced. There is also an option to only run the test on files whose size is a multiple of 512, which is useful for finding certain encryption algorithms. +The module looks for the following types of encryption: +
    +
  • Any file that has a entropy above the threshold in the module settings and that fits the file size constraints +
  • Password protected Office files, PDF files, and Access database files +
  • BitLocker volumes +
  • SQLCipher (uses the minimum entropy from the module settings) +
  • VeraCrypt (uses the minimum entropy from the module settings) +
+ \section encrypt_results Viewing results -Files that pass the test are shown in the Results tree under "Encryption Suspected". +Files that pass the tests are shown in the Results tree under "Encryption Detected" or "Encryption Suspected". Generally, if the test used involved looking for a +specific header/file structure, the result will be "Encryption Detected" and the type of encryption will be displayed in the Comment field. If the test was based on the entropy of the file, +the result will be "Encryption Suspected" and the calculated entropy will be displayed in the Comment field. \image html encrypt_tree.png diff --git a/docs/doxygen-user/images/encrypt_tree.png b/docs/doxygen-user/images/encrypt_tree.png index b7e509ab61f9f2f4aa7f0d4e46f1cab40ba83b81..5766e75ed33275b7cad772f8e427a1ba00ddd894 100644 GIT binary patch literal 25926 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 b4Ma(pQ&)bia#lD10hf}TnrykWdC>m?P&uc1 literal 11528 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 Is8R6$0O^}_$p8QV From 69c2af727bc04ba0df357bd9c4af10708fd0bea1 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 21 May 2018 08:40:12 -0400 Subject: [PATCH 63/63] Fixing typos --- docs/doxygen-user/encryption_detection.dox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/doxygen-user/encryption_detection.dox b/docs/doxygen-user/encryption_detection.dox index e415c5065b..34ba378b64 100644 --- a/docs/doxygen-user/encryption_detection.dox +++ b/docs/doxygen-user/encryption_detection.dox @@ -14,7 +14,7 @@ Minimum entropy can be set higher or lower, depending on how many false hits are The module looks for the following types of encryption:
    -
  • Any file that has a entropy above the threshold in the module settings and that fits the file size constraints +
  • Any file that has an entropy equal to or greater than the threshold in the module settings and that fits the file size constraints
  • Password protected Office files, PDF files, and Access database files
  • BitLocker volumes
  • SQLCipher (uses the minimum entropy from the module settings)