19 package org.sleuthkit.autopsy.healthmonitor;
21 import java.awt.Color;
22 import java.awt.FontMetrics;
23 import java.awt.Graphics;
24 import java.awt.Graphics2D;
25 import java.awt.RenderingHints;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.ArrayList;
29 import java.util.List;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.TreeSet;
35 import java.util.Calendar;
36 import java.util.GregorianCalendar;
37 import javax.swing.JPanel;
38 import java.util.TimeZone;
39 import java.util.concurrent.TimeUnit;
40 import org.openide.util.NbBundle;
46 class UserMetricGraphPanel
extends JPanel {
48 private static final int padding = 25;
49 private static final int labelPadding = 25;
50 private final Color examinerColor =
new Color(0x12, 0x20, 0xdb, 255);
51 private final Color autoIngestColor =
new Color(0x12, 0x80, 0x20, 255);
52 private final Color gridColor =
new Color(200, 200, 200, 200);
53 private static final int pointWidth = 4;
54 private static final int numberYDivisions = 10;
55 private final List<UserCount> dataToPlot;
56 private final String graphLabel;
57 private final long dataInterval;
58 private final long MILLISECONDS_PER_HOUR = 1000 * 60 * 60;
59 private final long MILLISECONDS_PER_DAY = MILLISECONDS_PER_HOUR * 24;
60 private final long maxTimestamp;
61 private final long minTimestamp;
63 private static final int minCount = 0;
65 @NbBundle.Messages({
"UserMetricGraphPanel.constructor.casesOpen=Cases open",
66 "UserMetricGraphPanel.constructor.loggedIn=Users logged in - examiner nodes in blue, auto ingest nodes in green"
68 UserMetricGraphPanel(List<UserData> userResults,
long timestampThreshold,
boolean plotCases) {
70 maxTimestamp = System.currentTimeMillis();
71 minTimestamp = timestampThreshold;
75 graphLabel = Bundle.UserMetricGraphPanel_constructor_casesOpen();
77 graphLabel = Bundle.UserMetricGraphPanel_constructor_loggedIn();
81 Comparator<UserData> sortOnTimestamp =
new Comparator<UserData>() {
83 public int compare(UserData o1, UserData o2) {
84 return Long.compare(o1.getTimestamp(), o2.getTimestamp());
90 Map<String, TreeSet<UserData>> userDataMap =
new HashMap<>();
91 for(UserData result:userResults) {
92 if(userDataMap.containsKey(result.getHostname())) {
93 userDataMap.get(result.getHostname()).add(result);
95 TreeSet<UserData> resultTreeSet =
new TreeSet<>(sortOnTimestamp);
96 resultTreeSet.add(result);
97 userDataMap.put(result.getHostname(), resultTreeSet);
107 dataToPlot =
new ArrayList<>();
108 dataInterval = MILLISECONDS_PER_HOUR;
109 maxCount = Integer.MIN_VALUE;
110 for (
long timestamp = maxTimestamp;timestamp > minTimestamp;timestamp -= dataInterval) {
114 UserCount openCaseCount =
new UserCount(timestamp);
115 UserCount loggedInUserCount =
new UserCount(timestamp);
117 Set<String> openCaseNames =
new HashSet<>();
118 UserData timestampUserData = UserData.createDummyUserData(timestamp);
120 for (String hostname:userDataMap.keySet()) {
122 UserData lastRecord = userDataMap.get(hostname).floor(timestampUserData);
124 if (lastRecord != null) {
127 if (lastRecord.getEventType().caseIsOpen()) {
130 if ( ! openCaseNames.contains(lastRecord.getCaseName())) {
135 openCaseCount.addExaminer();
136 openCaseNames.add(lastRecord.getCaseName());
141 if (lastRecord.getEventType().userIsLoggedIn()) {
142 if(lastRecord.isExaminerNode()) {
143 loggedInUserCount.addExaminer();
145 loggedInUserCount.addAutoIngestNode();
154 maxCount = Integer.max(maxCount, openCaseCount.getTotalNodeCount());
155 maxCount = Integer.max(maxCount, loggedInUserCount.getTotalNodeCount());
159 dataToPlot.add(openCaseCount);
161 dataToPlot.add(loggedInUserCount);
178 protected void paintComponent(Graphics g) {
179 super.paintComponent(g);
180 Graphics2D g2 = (Graphics2D) g;
181 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
185 double maxValueOnXAxis = maxTimestamp + TimeUnit.HOURS.toMillis(2);
186 double minValueOnXAxis = minTimestamp - TimeUnit.HOURS.toMillis(1);
190 if((maxCount % 5) != 0) {
191 maxCount += (5 - (maxCount % 5));
193 int maxValueOnYAxis = Integer.max(maxCount, 5);
194 int minValueOnYAxis = minCount;
201 int leftGraphPadding = padding + labelPadding;
202 int rightGraphPadding = padding;
203 int topGraphPadding = padding + g2.getFontMetrics().getHeight();
204 int bottomGraphPadding = labelPadding;
214 int graphWidth = getWidth() - leftGraphPadding - rightGraphPadding;
215 int graphHeight = getHeight() - topGraphPadding - bottomGraphPadding;
216 double xScale = ((double) graphWidth) / (maxValueOnXAxis - minValueOnXAxis);
217 double yScale = ((double) graphHeight) / (maxValueOnYAxis - minValueOnYAxis);
220 g2.setColor(Color.WHITE);
221 g2.fillRect(leftGraphPadding, topGraphPadding, graphWidth, graphHeight);
225 int positionForMetricNameLabel = 0;
226 Map<Integer, Integer> countToGraphPosition =
new HashMap<>();
227 for (
int i = 0; i < numberYDivisions + 1; i++) {
228 int x0 = leftGraphPadding;
229 int x1 = pointWidth + leftGraphPadding;
230 int y0 = getHeight() - ((i * graphHeight) / numberYDivisions + bottomGraphPadding);
233 if ( ! dataToPlot.isEmpty()) {
235 g2.setColor(gridColor);
236 g2.drawLine(leftGraphPadding + 1 + pointWidth, y0, getWidth() - rightGraphPadding, y1);
239 g2.setColor(Color.BLACK);
240 double yValue = minValueOnYAxis + ((maxValueOnYAxis - minValueOnYAxis) * ((i * 1.0) / numberYDivisions));
241 int intermediateLabelVal = (int) (yValue * 100);
242 if ((i == numberYDivisions) || ((intermediateLabelVal % 100) == 0)) {
243 countToGraphPosition.put(intermediateLabelVal / 100, y0);
244 String yLabel = Integer.toString(intermediateLabelVal / 100);
245 FontMetrics fontMetrics = g2.getFontMetrics();
246 labelWidth = fontMetrics.stringWidth(yLabel);
247 g2.drawString(yLabel, x0 - labelWidth - 5, y0 + (fontMetrics.getHeight() / 2) - 3);
251 if (i == numberYDivisions) {
252 positionForMetricNameLabel = x0 - labelWidth - 5;
258 g2.setColor(Color.BLACK);
259 g2.drawLine(x0, y0, x1, y1);
263 Calendar maxDate =
new GregorianCalendar();
264 maxDate.setTimeInMillis(maxTimestamp);
265 maxDate.set(Calendar.HOUR_OF_DAY, 0);
266 maxDate.set(Calendar.MINUTE, 0);
267 maxDate.set(Calendar.SECOND, 0);
268 maxDate.set(Calendar.MILLISECOND, 0);
269 long maxMidnightInMillis = maxDate.getTimeInMillis();
273 long totalDays = (maxMidnightInMillis - (long)minValueOnXAxis) / MILLISECONDS_PER_DAY;
274 long daysPerDivision;
275 if(totalDays <= 20) {
278 daysPerDivision = (totalDays / 20);
279 if((totalDays % 20) != 0) {
287 for (
long currentDivision = maxMidnightInMillis; currentDivision >= minValueOnXAxis; currentDivision -= MILLISECONDS_PER_DAY * daysPerDivision) {
289 int x0 = (int) ((currentDivision - minValueOnXAxis) * xScale + leftGraphPadding);
291 int y0 = getHeight() - bottomGraphPadding;
292 int y1 = y0 - pointWidth;
295 g2.setColor(gridColor);
296 g2.drawLine(x0, getHeight() - bottomGraphPadding - 1 - pointWidth, x1, topGraphPadding);
299 g2.setColor(Color.BLACK);
300 g2.drawLine(x0, y0, x1, y1);
303 Calendar thisDate =
new GregorianCalendar();
304 thisDate.setTimeZone(TimeZone.getTimeZone(
"GMT"));
305 thisDate.setTimeInMillis(currentDivision);
306 int month = thisDate.get(Calendar.MONTH) + 1;
307 int day = thisDate.get(Calendar.DAY_OF_MONTH);
309 String xLabel = month +
"/" + day;
310 FontMetrics metrics = g2.getFontMetrics();
311 labelWidth = metrics.stringWidth(xLabel);
312 g2.drawString(xLabel, x0 - labelWidth / 2, y0 + metrics.getHeight() + 3);
316 g2.setColor(Color.BLACK);
317 g2.drawLine(leftGraphPadding, getHeight() - bottomGraphPadding, leftGraphPadding, topGraphPadding);
318 g2.drawLine(leftGraphPadding, getHeight() - bottomGraphPadding, getWidth() - rightGraphPadding, getHeight() - bottomGraphPadding);
321 Collections.sort(dataToPlot,
new Comparator<UserCount>(){
323 public int compare(UserCount o1, UserCount o2){
324 return Long.compare(o1.getTimestamp(), o2.getTimestamp());
329 for(
int i = 0;i < dataToPlot.size();i++) {
330 UserCount userCount = dataToPlot.get(i);
331 int x = (int) ((userCount.getTimestamp() - minValueOnXAxis) * xScale + leftGraphPadding);
332 int yTopOfExaminerBox;
333 if(countToGraphPosition.containsKey(userCount.getTotalNodeCount())) {
336 yTopOfExaminerBox = countToGraphPosition.get(userCount.getTotalNodeCount());
338 yTopOfExaminerBox = (int) ((maxValueOnYAxis - userCount.getTotalNodeCount()) * yScale + topGraphPadding);
344 if(i < dataToPlot.size() - 1) {
345 width = Integer.max((
int)((dataToPlot.get(i + 1).getTimestamp() - minValueOnXAxis) * xScale + leftGraphPadding) - x - 1,
348 width = Integer.max((
int)(dataInterval * xScale), 1);
354 int heightExaminerBox = (getHeight() - bottomGraphPadding) - yTopOfExaminerBox;
357 g2.setColor(examinerColor);
358 g2.fillRect(x, yTopOfExaminerBox, width, heightExaminerBox);
362 if (userCount.getAutoIngestNodeCount() > 0) {
363 int yTopOfAutoIngestBox;
364 if(countToGraphPosition.containsKey(userCount.getAutoIngestNodeCount())) {
367 yTopOfAutoIngestBox =countToGraphPosition.get(userCount.getAutoIngestNodeCount());
369 yTopOfAutoIngestBox = yTopOfExaminerBox + heightExaminerBox;
371 int heightAutoIngestBox = (getHeight() - bottomGraphPadding) - yTopOfAutoIngestBox;
374 g2.setColor(autoIngestColor);
375 g2.fillRect(x, yTopOfAutoIngestBox, width, heightAutoIngestBox);
381 g2.setColor(this.getBackground());
382 g2.fillRect(leftGraphPadding, 0, graphWidth, topGraphPadding);
385 g2.setColor(Color.BLACK);
386 String titleStr = graphLabel;
387 g2.drawString(titleStr, positionForMetricNameLabel, padding);
405 this.examinerCount = 0;
406 this.autoIngestCount = 0;
419 void addAutoIngestNode() {
427 int getExaminerNodeCount() {
435 int getAutoIngestNodeCount() {
443 int getTotalNodeCount() {
451 long getTimestamp() {