Introduction
Java Mission Control (JMC) Dashboard is a powerful monitoring and management tool for Java applications. This implementation provides a custom dashboard for tracking JVM metrics, performance statistics, and application health in real-time.
Architecture Overview
Core Components
public class JMCDashboardArchitecture {
// JVM Metrics Collector
// Real-time Data Processor
// Visualization Engine
// Alert Management System
// Data Storage and Retrieval
// Web Dashboard Interface
}
Core Data Models
JVM Metrics Model
public class JVMMetrics {
private final String jvmId;
private final Instant timestamp;
private final MemoryMetrics memory;
private final GCMetrics garbageCollection;
private final ThreadMetrics threads;
private final CPUMetrics cpu;
private final ClassLoadingMetrics classLoading;
private final RuntimeMetrics runtime;
private final Map<String, Object> customMetrics;
public JVMMetrics(String jvmId) {
this.jvmId = jvmId;
this.timestamp = Instant.now();
this.memory = new MemoryMetrics();
this.garbageCollection = new GCMetrics();
this.threads = new ThreadMetrics();
this.cpu = new CPUMetrics();
this.classLoading = new ClassLoadingMetrics();
this.runtime = new RuntimeMetrics();
this.customMetrics = new HashMap<>();
}
// Getters and utility methods
public String getJvmId() { return jvmId; }
public Instant getTimestamp() { return timestamp; }
public MemoryMetrics getMemory() { return memory; }
public GCMetrics getGarbageCollection() { return garbageCollection; }
public ThreadMetrics getThreads() { return threads; }
public CPUMetrics getCpu() { return cpu; }
public ClassLoadingMetrics getClassLoading() { return classLoading; }
public RuntimeMetrics getRuntime() { return runtime; }
public Map<String, Object> getCustomMetrics() { return customMetrics; }
public void addCustomMetric(String key, Object value) {
customMetrics.put(key, value);
}
}
public class MemoryMetrics {
private long heapUsed;
private long heapCommitted;
private long heapMax;
private long nonHeapUsed;
private long nonHeapCommitted;
private long nonHeapMax;
private Map<String, MemoryPoolMetrics> memoryPools;
public MemoryMetrics() {
this.memoryPools = new HashMap<>();
}
// Getters and setters
public long getHeapUsed() { return heapUsed; }
public void setHeapUsed(long heapUsed) { this.heapUsed = heapUsed; }
public long getHeapCommitted() { return heapCommitted; }
public void setHeapCommitted(long heapCommitted) { this.heapCommitted = heapCommitted; }
public long getHeapMax() { return heapMax; }
public void setHeapMax(long heapMax) { this.heapMax = heapMax; }
public long getNonHeapUsed() { return nonHeapUsed; }
public void setNonHeapUsed(long nonHeapUsed) { this.nonHeapUsed = nonHeapUsed; }
public long getNonHeapCommitted() { return nonHeapCommitted; }
public void setNonHeapCommitted(long nonHeapCommitted) { this.nonHeapCommitted = nonHeapCommitted; }
public long getNonHeapMax() { return nonHeapMax; }
public void setNonHeapMax(long nonHeapMax) { this.nonHeapMax = nonHeapMax; }
public Map<String, MemoryPoolMetrics> getMemoryPools() { return memoryPools; }
public void addMemoryPool(String name, MemoryPoolMetrics pool) {
memoryPools.put(name, pool);
}
public double getHeapUsagePercentage() {
return heapMax > 0 ? (double) heapUsed / heapMax * 100 : 0;
}
public double getNonHeapUsagePercentage() {
return nonHeapMax > 0 ? (double) nonHeapUsed / nonHeapMax * 100 : 0;
}
}
public class MemoryPoolMetrics {
private final String poolName;
private long used;
private long committed;
private long max;
private final MemoryType type;
public MemoryPoolMetrics(String poolName, MemoryType type) {
this.poolName = poolName;
this.type = type;
}
// Getters and setters
public String getPoolName() { return poolName; }
public long getUsed() { return used; }
public void setUsed(long used) { this.used = used; }
public long getCommitted() { return committed; }
public void setCommitted(long committed) { this.committed = committed; }
public long getMax() { return max; }
public void setMax(long max) { this.max = max; }
public MemoryType getType() { return type; }
public double getUsagePercentage() {
return max > 0 ? (double) used / max * 100 : 0;
}
}
public enum MemoryType {
HEAP("Heap"),
NON_HEAP("Non-Heap"),
CODE_CACHE("Code Cache"),
METASPACE("Metaspace");
private final String description;
MemoryType(String description) {
this.description = description;
}
public String getDescription() { return description; }
}
public class GCMetrics {
private long gcCount;
private long gcTime;
private Map<String, GarbageCollectorMetrics> collectors;
public GCMetrics() {
this.collectors = new HashMap<>();
}
// Getters and setters
public long getGcCount() { return gcCount; }
public void setGcCount(long gcCount) { this.gcCount = gcCount; }
public long getGcTime() { return gcTime; }
public void setGcTime(long gcTime) { this.gcTime = gcTime; }
public Map<String, GarbageCollectorMetrics> getCollectors() { return collectors; }
public void addCollector(String name, GarbageCollectorMetrics collector) {
collectors.put(name, collector);
}
}
public class GarbageCollectorMetrics {
private final String collectorName;
private long collectionCount;
private long collectionTime;
public GarbageCollectorMetrics(String collectorName) {
this.collectorName = collectorName;
}
// Getters and setters
public String getCollectorName() { return collectorName; }
public long getCollectionCount() { return collectionCount; }
public void setCollectionCount(long collectionCount) { this.collectionCount = collectionCount; }
public long getCollectionTime() { return collectionTime; }
public void setCollectionTime(long collectionTime) { this.collectionTime = collectionTime; }
}
public class ThreadMetrics {
private int liveThreads;
private int peakThreads;
private int daemonThreads;
private long totalStartedThreads;
private Map<Thread.State, Integer> threadStateCounts;
public ThreadMetrics() {
this.threadStateCounts = new HashMap<>();
}
// Getters and setters
public int getLiveThreads() { return liveThreads; }
public void setLiveThreads(int liveThreads) { this.liveThreads = liveThreads; }
public int getPeakThreads() { return peakThreads; }
public void setPeakThreads(int peakThreads) { this.peakThreads = peakThreads; }
public int getDaemonThreads() { return daemonThreads; }
public void setDaemonThreads(int daemonThreads) { this.daemonThreads = daemonThreads; }
public long getTotalStartedThreads() { return totalStartedThreads; }
public void setTotalStartedThreads(long totalStartedThreads) { this.totalStartedThreads = totalStartedThreads; }
public Map<Thread.State, Integer> getThreadStateCounts() { return threadStateCounts; }
public void setThreadStateCount(Thread.State state, int count) {
threadStateCounts.put(state, count);
}
}
Metrics Collection System
JVM Metrics Collector
public class JVMMetricsCollector {
private final String jvmId;
private final RuntimeMXBean runtimeMXBean;
private final MemoryMXBean memoryMXBean;
private final ThreadMXBean threadMXBean;
private final OperatingSystemMXBean osMXBean;
private final List<GarbageCollectorMXBean> gcMXBeans;
private final List<MemoryPoolMXBean> memoryPoolMXBeans;
private final ClassLoadingMXBean classLoadingMXBean;
private final CompilationMXBean compilationMXBean;
public JVMMetricsCollector(String jvmId) {
this.jvmId = jvmId;
this.runtimeMXBean = ManagementFactory.getRuntimeMXBean();
this.memoryMXBean = ManagementFactory.getMemoryMXBean();
this.threadMXBean = ManagementFactory.getThreadMXBean();
this.osMXBean = ManagementFactory.getOperatingSystemMXBean();
this.gcMXBeans = ManagementFactory.getGarbageCollectorMXBeans();
this.memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();
this.classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();
this.compilationMXBean = ManagementFactory.getCompilationMXBean();
}
public JVMMetrics collectMetrics() {
JVMMetrics metrics = new JVMMetrics(jvmId);
collectMemoryMetrics(metrics);
collectGCMetrics(metrics);
collectThreadMetrics(metrics);
collectCPUMetrics(metrics);
collectClassLoadingMetrics(metrics);
collectRuntimeMetrics(metrics);
return metrics;
}
private void collectMemoryMetrics(JVMMetrics metrics) {
MemoryMetrics memoryMetrics = metrics.getMemory();
// Heap memory
MemoryUsage heapUsage = memoryMXBean.getHeapMemoryUsage();
memoryMetrics.setHeapUsed(heapUsage.getUsed());
memoryMetrics.setHeapCommitted(heapUsage.getCommitted());
memoryMetrics.setHeapMax(heapUsage.getMax());
// Non-heap memory
MemoryUsage nonHeapUsage = memoryMXBean.getNonHeapMemoryUsage();
memoryMetrics.setNonHeapUsed(nonHeapUsage.getUsed());
memoryMetrics.setNonHeapCommitted(nonHeapUsage.getCommitted());
memoryMetrics.setNonHeapMax(nonHeapUsage.getMax());
// Memory pools
for (MemoryPoolMXBean pool : memoryPoolMXBeans) {
MemoryUsage usage = pool.getUsage();
MemoryType type = determineMemoryType(pool.getType());
MemoryPoolMetrics poolMetrics = new MemoryPoolMetrics(pool.getName(), type);
poolMetrics.setUsed(usage.getUsed());
poolMetrics.setCommitted(usage.getCommitted());
poolMetrics.setMax(usage.getMax());
memoryMetrics.addMemoryPool(pool.getName(), poolMetrics);
}
}
private MemoryType determineMemoryType(MemoryType mxBeanType) {
return switch (mxBeanType.name()) {
case "HEAP" -> MemoryType.HEAP;
case "NON_HEAP" -> MemoryType.NON_HEAP;
case "CODE_CACHE" -> MemoryType.CODE_CACHE;
case "METASPACE" -> MemoryType.METASPACE;
default -> MemoryType.NON_HEAP;
};
}
private void collectGCMetrics(JVMMetrics metrics) {
GCMetrics gcMetrics = metrics.getGarbageCollection();
long totalGcCount = 0;
long totalGcTime = 0;
for (GarbageCollectorMXBean gcBean : gcMXBeans) {
GarbageCollectorMetrics collectorMetrics = new GarbageCollectorMetrics(gcBean.getName());
collectorMetrics.setCollectionCount(gcBean.getCollectionCount());
collectorMetrics.setCollectionTime(gcBean.getCollectionTime());
gcMetrics.addCollector(gcBean.getName(), collectorMetrics);
totalGcCount += gcBean.getCollectionCount();
totalGcTime += gcBean.getCollectionTime();
}
gcMetrics.setGcCount(totalGcCount);
gcMetrics.setGcTime(totalGcTime);
}
private void collectThreadMetrics(JVMMetrics metrics) {
ThreadMetrics threadMetrics = metrics.getThreads();
threadMetrics.setLiveThreads(threadMXBean.getThreadCount());
threadMetrics.setPeakThreads(threadMXBean.getPeakThreadCount());
threadMetrics.setDaemonThreads(threadMXBean.getDaemonThreadCount());
threadMetrics.setTotalStartedThreads(threadMXBean.getTotalStartedThreadCount());
// Thread states
long[] threadIds = threadMXBean.getAllThreadIds();
for (long threadId : threadIds) {
ThreadInfo threadInfo = threadMXBean.getThreadInfo(threadId);
if (threadInfo != null) {
Thread.State state = threadInfo.getThreadState();
threadMetrics.setThreadStateCount(state,
threadMetrics.getThreadStateCounts().getOrDefault(state, 0) + 1);
}
}
}
private void collectCPUMetrics(JVMMetrics metrics) {
CPUMetrics cpuMetrics = metrics.getCpu();
if (osMXBean instanceof com.sun.management.OperatingSystemMXBean sunOsMXBean) {
cpuMetrics.setProcessCpuLoad(sunOsMXBean.getProcessCpuLoad());
cpuMetrics.setSystemCpuLoad(sunOsMXBean.getSystemCpuLoad());
cpuMetrics.setProcessCpuTime(sunOsMXBean.getProcessCpuTime());
}
cpuMetrics.setAvailableProcessors(osMXBean.getAvailableProcessors());
// Thread CPU time if supported
if (threadMXBean.isThreadCpuTimeSupported()) {
threadMXBean.setThreadCpuTimeEnabled(true);
}
}
private void collectClassLoadingMetrics(JVMMetrics metrics) {
ClassLoadingMetrics classMetrics = metrics.getClassLoading();
classMetrics.setLoadedClassCount(classLoadingMXBean.getLoadedClassCount());
classMetrics.setTotalLoadedClassCount(classLoadingMXBean.getTotalLoadedClassCount());
classMetrics.setUnloadedClassCount(classLoadingMXBean.getUnloadedClassCount());
}
private void collectRuntimeMetrics(JVMMetrics metrics) {
RuntimeMetrics runtimeMetrics = metrics.getRuntime();
runtimeMetrics.setStartTime(runtimeMXBean.getStartTime());
runtimeMetrics.setUptime(runtimeMXBean.getUptime());
runtimeMetrics.setVmName(runtimeMXBean.getVmName());
runtimeMetrics.setVmVendor(runtimeMXBean.getVmVendor());
runtimeMetrics.setVmVersion(runtimeMXBean.getVmVersion());
// System properties
runtimeMetrics.setSystemProperties(runtimeMXBean.getSystemProperties());
// Input arguments
runtimeMetrics.setInputArguments(runtimeMXBean.getInputArguments());
}
}
Real-time Monitoring Service
Metrics Storage and Retrieval
public class MetricsStorageService {
private final Map<String, Deque<JVMMetrics>> metricsHistory;
private final int maxHistorySize;
private final ScheduledExecutorService scheduler;
private final JVMMetricsCollector collector;
public MetricsStorageService(String jvmId, int maxHistorySize) {
this.metricsHistory = new ConcurrentHashMap<>();
this.maxHistorySize = maxHistorySize;
this.scheduler = Executors.newScheduledThreadPool(1);
this.collector = new JVMMetricsCollector(jvmId);
startMetricsCollection();
}
public void startMetricsCollection() {
scheduler.scheduleAtFixedRate(() -> {
try {
JVMMetrics metrics = collector.collectMetrics();
storeMetrics(metrics);
} catch (Exception e) {
System.err.println("Error collecting metrics: " + e.getMessage());
}
}, 0, 1, TimeUnit.SECONDS); // Collect every second
}
private void storeMetrics(JVMMetrics metrics) {
String jvmId = metrics.getJvmId();
metricsHistory.computeIfAbsent(jvmId, k -> new ConcurrentLinkedDeque<>());
Deque<JVMMetrics> history = metricsHistory.get(jvmId);
history.addLast(metrics);
// Maintain history size
while (history.size() > maxHistorySize) {
history.removeFirst();
}
}
public List<JVMMetrics> getRecentMetrics(String jvmId, Duration duration) {
Deque<JVMMetrics> history = metricsHistory.get(jvmId);
if (history == null) {
return Collections.emptyList();
}
Instant cutoff = Instant.now().minus(duration);
return history.stream()
.filter(metric -> metric.getTimestamp().isAfter(cutoff))
.collect(Collectors.toList());
}
public Optional<JVMMetrics> getLatestMetrics(String jvmId) {
Deque<JVMMetrics> history = metricsHistory.get(jvmId);
return history != null && !history.isEmpty() ?
Optional.of(history.getLast()) : Optional.empty();
}
public Map<String, List<JVMMetrics>> getAllMetrics() {
return metricsHistory.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> new ArrayList<>(entry.getValue())
));
}
public void shutdown() {
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
Web Dashboard with Spring Boot
REST API Controllers
@RestController
@RequestMapping("/api/jmc")
@CrossOrigin(origins = "*")
public class JMCDashboardController {
private final MetricsStorageService storageService;
private final AlertService alertService;
public JMCDashboardController(MetricsStorageService storageService,
AlertService alertService) {
this.storageService = storageService;
this.alertService = alertService;
}
@GetMapping("/metrics/current")
public ResponseEntity<JVMMetrics> getCurrentMetrics() {
Optional<JVMMetrics> metrics = storageService.getLatestMetrics("default");
return metrics.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@GetMapping("/metrics/history")
public ResponseEntity<List<JVMMetrics>> getMetricsHistory(
@RequestParam(defaultValue = "PT5M") String duration) {
Duration durationParam = Duration.parse(duration);
List<JVMMetrics> metrics = storageService.getRecentMetrics("default", durationParam);
return ResponseEntity.ok(metrics);
}
@GetMapping("/metrics/summary")
public ResponseEntity<MetricsSummary> getMetricsSummary() {
List<JVMMetrics> recentMetrics = storageService.getRecentMetrics("default", Duration.ofMinutes(5));
MetricsSummary summary = calculateSummary(recentMetrics);
return ResponseEntity.ok(summary);
}
@GetMapping("/alerts")
public ResponseEntity<List<Alert>> getActiveAlerts() {
List<Alert> alerts = alertService.getActiveAlerts();
return ResponseEntity.ok(alerts);
}
@GetMapping("/memory/details")
public ResponseEntity<MemoryDetails> getMemoryDetails() {
Optional<JVMMetrics> current = storageService.getLatestMetrics("default");
return current.map(metrics -> {
MemoryDetails details = extractMemoryDetails(metrics);
return ResponseEntity.ok(details);
}).orElse(ResponseEntity.notFound().build());
}
@GetMapping("/threads/details")
public ResponseEntity<ThreadDetails> getThreadDetails() {
Optional<JVMMetrics> current = storageService.getLatestMetrics("default");
return current.map(metrics -> {
ThreadDetails details = extractThreadDetails(metrics);
return ResponseEntity.ok(details);
}).orElse(ResponseEntity.notFound().build());
}
@GetMapping("/gc/details")
public ResponseEntity<GCDetails> getGCDetails() {
Optional<JVMMetrics> current = storageService.getLatestMetrics("default");
return current.map(metrics -> {
GCDetails details = extractGCDetails(metrics);
return ResponseEntity.ok(details);
}).orElse(ResponseEntity.notFound().build());
}
private MetricsSummary calculateSummary(List<JVMMetrics> metrics) {
if (metrics.isEmpty()) {
return new MetricsSummary();
}
MetricsSummary summary = new MetricsSummary();
// Calculate averages, min, max, trends
return summary;
}
private MemoryDetails extractMemoryDetails(JVMMetrics metrics) {
MemoryDetails details = new MemoryDetails();
// Extract detailed memory information
return details;
}
private ThreadDetails extractThreadDetails(JVMMetrics metrics) {
ThreadDetails details = new ThreadDetails();
// Extract detailed thread information
return details;
}
private GCDetails extractGCDetails(JVMMetrics metrics) {
GCDetails details = new GCDetails();
// Extract detailed GC information
return details;
}
}
// DTO classes for API responses
public class MetricsSummary {
private double avgCpuUsage;
private double maxCpuUsage;
private double avgMemoryUsage;
private double maxMemoryUsage;
private long totalGcCount;
private long totalGcTime;
private int peakThreadCount;
private double systemLoad;
// Getters and setters
public double getAvgCpuUsage() { return avgCpuUsage; }
public void setAvgCpuUsage(double avgCpuUsage) { this.avgCpuUsage = avgCpuUsage; }
public double getMaxCpuUsage() { return maxCpuUsage; }
public void setMaxCpuUsage(double maxCpuUsage) { this.maxCpuUsage = maxCpuUsage; }
public double getAvgMemoryUsage() { return avgMemoryUsage; }
public void setAvgMemoryUsage(double avgMemoryUsage) { this.avgMemoryUsage = avgMemoryUsage; }
public double getMaxMemoryUsage() { return maxMemoryUsage; }
public void setMaxMemoryUsage(double maxMemoryUsage) { this.maxMemoryUsage = maxMemoryUsage; }
public long getTotalGcCount() { return totalGcCount; }
public void setTotalGcCount(long totalGcCount) { this.totalGcCount = totalGcCount; }
public long getTotalGcTime() { return totalGcTime; }
public void setTotalGcTime(long totalGcTime) { this.totalGcTime = totalGcTime; }
public int getPeakThreadCount() { return peakThreadCount; }
public void setPeakThreadCount(int peakThreadCount) { this.peakThreadCount = peakThreadCount; }
public double getSystemLoad() { return systemLoad; }
public void setSystemLoad(double systemLoad) { this.systemLoad = systemLoad; }
}
public class MemoryDetails {
private Map<String, MemoryPoolDetails> memoryPools;
private HeapDetails heapDetails;
private NonHeapDetails nonHeapDetails;
// Getters and setters
public Map<String, MemoryPoolDetails> getMemoryPools() { return memoryPools; }
public void setMemoryPools(Map<String, MemoryPoolDetails> memoryPools) { this.memoryPools = memoryPools; }
public HeapDetails getHeapDetails() { return heapDetails; }
public void setHeapDetails(HeapDetails heapDetails) { this.heapDetails = heapDetails; }
public NonHeapDetails getNonHeapDetails() { return nonHeapDetails; }
public void setNonHeapDetails(NonHeapDetails nonHeapDetails) { this.nonHeapDetails = nonHeapDetails; }
}
public class ThreadDetails {
private Map<Thread.State, Integer> threadStates;
private List<ThreadInfo> threadInfos;
private ThreadSummary summary;
// Getters and setters
public Map<Thread.State, Integer> getThreadStates() { return threadStates; }
public void setThreadStates(Map<Thread.State, Integer> threadStates) { this.threadStates = threadStates; }
public List<ThreadInfo> getThreadInfos() { return threadInfos; }
public void setThreadInfos(List<ThreadInfo> threadInfos) { this.threadInfos = threadInfos; }
public ThreadSummary getSummary() { return summary; }
public void setSummary(ThreadSummary summary) { this.summary = summary; }
}
Real-time WebSocket Updates
WebSocket Configuration
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MetricsWebSocketHandler(), "/ws/metrics")
.setAllowedOrigins("*");
}
}
@Component
public class MetricsWebSocketHandler extends TextWebSocketHandler {
private final Set<WebSocketSession> sessions = Collections.synchronizedSet(new HashSet<>());
private final MetricsStorageService storageService;
private final ScheduledExecutorService scheduler;
public MetricsWebSocketHandler(MetricsStorageService storageService) {
this.storageService = storageService;
this.scheduler = Executors.newScheduledThreadPool(1);
startMetricsBroadcast();
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
// Send initial metrics
sendCurrentMetrics(session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
}
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// Handle client messages if needed
String payload = message.getPayload();
// Process client requests
}
private void startMetricsBroadcast() {
scheduler.scheduleAtFixedRate(() -> {
if (!sessions.isEmpty()) {
broadcastMetrics();
}
}, 1, 1, TimeUnit.SECONDS);
}
private void broadcastMetrics() {
Optional<JVMMetrics> currentMetrics = storageService.getLatestMetrics("default");
currentMetrics.ifPresent(metrics -> {
String json = convertToJson(metrics);
synchronized (sessions) {
sessions.forEach(session -> {
try {
if (session.isOpen()) {
session.sendMessage(new TextMessage(json));
}
} catch (IOException e) {
System.err.println("Error sending WebSocket message: " + e.getMessage());
}
});
}
});
}
private void sendCurrentMetrics(WebSocketSession session) {
Optional<JVMMetrics> currentMetrics = storageService.getLatestMetrics("default");
currentMetrics.ifPresent(metrics -> {
try {
String json = convertToJson(metrics);
session.sendMessage(new TextMessage(json));
} catch (IOException e) {
System.err.println("Error sending initial metrics: " + e.getMessage());
}
});
}
private String convertToJson(JVMMetrics metrics) {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writeValueAsString(metrics);
} catch (JsonProcessingException e) {
throw new RuntimeException("Error converting metrics to JSON", e);
}
}
@PreDestroy
public void cleanup() {
scheduler.shutdown();
}
}
Alert Management System
Alert Engine
@Component
public class AlertService {
private final List<AlertRule> alertRules;
private final List<Alert> activeAlerts;
private final MetricsStorageService storageService;
private final ScheduledExecutorService alertChecker;
public AlertService(MetricsStorageService storageService) {
this.storageService = storageService;
this.alertRules = new ArrayList<>();
this.activeAlerts = Collections.synchronizedList(new ArrayList<>());
this.alertChecker = Executors.newScheduledThreadPool(1);
initializeDefaultRules();
startAlertChecking();
}
private void initializeDefaultRules() {
// Memory usage alerts
alertRules.add(new MemoryUsageAlertRule(80, 90, 95));
// CPU usage alerts
alertRules.add(new CPUUsageAlertRule(70, 85, 95));
// GC alerts
alertRules.add(new GCAlertRule(1000, 5000)); // GC time in ms
// Thread alerts
alertRules.add(new ThreadAlertRule(100, 200, 500));
// Deadlock detection
alertRules.add(new DeadlockAlertRule());
}
private void startAlertChecking() {
alertChecker.scheduleAtFixedRate(() -> {
try {
checkAlerts();
} catch (Exception e) {
System.err.println("Error checking alerts: " + e.getMessage());
}
}, 0, 5, TimeUnit.SECONDS);
}
private void checkAlerts() {
Optional<JVMMetrics> currentMetrics = storageService.getLatestMetrics("default");
if (currentMetrics.isEmpty()) {
return;
}
JVMMetrics metrics = currentMetrics.get();
for (AlertRule rule : alertRules) {
Optional<Alert> alert = rule.check(metrics);
alert.ifPresent(this::processAlert);
}
// Clean up resolved alerts
cleanResolvedAlerts();
}
private void processAlert(Alert alert) {
// Check if similar alert already exists
boolean exists = activeAlerts.stream()
.anyMatch(a -> a.getType().equals(alert.getType()) &&
a.getSeverity() == alert.getSeverity());
if (!exists) {
activeAlerts.add(alert);
notifyAlert(alert);
}
}
private void notifyAlert(Alert alert) {
// Log alert
System.out.printf("ALERT: [%s] %s - %s%n",
alert.getSeverity(), alert.getType(), alert.getMessage());
// Could send email, Slack notification, etc.
// implementNotificationSystem(alert);
}
private void cleanResolvedAlerts() {
Instant cutoff = Instant.now().minus(Duration.ofMinutes(5));
activeAlerts.removeIf(alert ->
alert.getTimestamp().isBefore(cutoff) && alert.isAutoResolve());
}
public List<Alert> getActiveAlerts() {
return new ArrayList<>(activeAlerts);
}
public void addAlertRule(AlertRule rule) {
alertRules.add(rule);
}
public void removeAlertRule(AlertRule rule) {
alertRules.remove(rule);
}
@PreDestroy
public void cleanup() {
alertChecker.shutdown();
}
}
public abstract class AlertRule {
protected final String name;
protected final AlertType type;
public AlertRule(String name, AlertType type) {
this.name = name;
this.type = type;
}
public abstract Optional<Alert> check(JVMMetrics metrics);
public String getName() { return name; }
public AlertType getType() { return type; }
}
public class MemoryUsageAlertRule extends AlertRule {
private final double warningThreshold;
private final double errorThreshold;
private final double criticalThreshold;
public MemoryUsageAlertRule(double warning, double error, double critical) {
super("Memory Usage", AlertType.MEMORY);
this.warningThreshold = warning;
this.errorThreshold = error;
this.criticalThreshold = critical;
}
@Override
public Optional<Alert> check(JVMMetrics metrics) {
double heapUsage = metrics.getMemory().getHeapUsagePercentage();
if (heapUsage >= criticalThreshold) {
return Optional.of(new Alert(
AlertType.MEMORY,
AlertSeverity.CRITICAL,
String.format("Critical memory usage: %.1f%%", heapUsage),
Instant.now()
));
} else if (heapUsage >= errorThreshold) {
return Optional.of(new Alert(
AlertType.MEMORY,
AlertSeverity.ERROR,
String.format("High memory usage: %.1f%%", heapUsage),
Instant.now()
));
} else if (heapUsage >= warningThreshold) {
return Optional.of(new Alert(
AlertType.MEMORY,
AlertSeverity.WARNING,
String.format("Elevated memory usage: %.1f%%", heapUsage),
Instant.now()
));
}
return Optional.empty();
}
}
public class CPUUsageAlertRule extends AlertRule {
private final double warningThreshold;
private final double errorThreshold;
private final double criticalThreshold;
public CPUUsageAlertRule(double warning, double error, double critical) {
super("CPU Usage", AlertType.CPU);
this.warningThreshold = warning;
this.errorThreshold = error;
this.criticalThreshold = critical;
}
@Override
public Optional<Alert> check(JVMMetrics metrics) {
double cpuUsage = metrics.getCpu().getProcessCpuLoad() * 100;
if (cpuUsage >= criticalThreshold) {
return Optional.of(new Alert(
AlertType.CPU,
AlertSeverity.CRITICAL,
String.format("Critical CPU usage: %.1f%%", cpuUsage),
Instant.now()
));
} else if (cpuUsage >= errorThreshold) {
return Optional.of(new Alert(
AlertType.CPU,
AlertSeverity.ERROR,
String.format("High CPU usage: %.1f%%", cpuUsage),
Instant.now()
));
} else if (cpuUsage >= warningThreshold) {
return Optional.of(new Alert(
AlertType.CPU,
AlertSeverity.WARNING,
String.format("Elevated CPU usage: %.1f%%", cpuUsage),
Instant.now()
));
}
return Optional.empty();
}
}
public class Alert {
private final AlertType type;
private final AlertSeverity severity;
private final String message;
private final Instant timestamp;
private boolean autoResolve = true;
public Alert(AlertType type, AlertSeverity severity, String message, Instant timestamp) {
this.type = type;
this.severity = severity;
this.message = message;
this.timestamp = timestamp;
}
// Getters
public AlertType getType() { return type; }
public AlertSeverity getSeverity() { return severity; }
public String getMessage() { return message; }
public Instant getTimestamp() { return timestamp; }
public boolean isAutoResolve() { return autoResolve; }
public void setAutoResolve(boolean autoResolve) { this.autoResolve = autoResolve; }
}
public enum AlertType {
MEMORY("Memory"),
CPU("CPU"),
GC("Garbage Collection"),
THREAD("Thread"),
DEADLOCK("Deadlock"),
CLASS_LOADING("Class Loading");
private final String description;
AlertType(String description) {
this.description = description;
}
public String getDescription() { return description; }
}
public enum AlertSeverity {
INFO("Info", "blue"),
WARNING("Warning", "yellow"),
ERROR("Error", "orange"),
CRITICAL("Critical", "red");
private final String description;
private final String color;
AlertSeverity(String description, String color) {
this.description = description;
this.color = color;
}
public String getDescription() { return description; }
public String getColor() { return color; }
}
Dashboard Visualization Components
Chart Data Generation
@Component
public class ChartDataService {
private final MetricsStorageService storageService;
public ChartDataService(MetricsStorageService storageService) {
this.storageService = storageService;
}
public ChartData generateMemoryUsageChart(Duration duration) {
List<JVMMetrics> metrics = storageService.getRecentMetrics("default", duration);
List<ChartDataPoint> dataPoints = metrics.stream()
.map(metric -> new ChartDataPoint(
metric.getTimestamp(),
metric.getMemory().getHeapUsagePercentage(),
metric.getMemory().getNonHeapUsagePercentage()
))
.collect(Collectors.toList());
return new ChartData("Memory Usage", dataPoints, "line");
}
public ChartData generateCPUUsageChart(Duration duration) {
List<JVMMetrics> metrics = storageService.getRecentMetrics("default", duration);
List<ChartDataPoint> dataPoints = metrics.stream()
.map(metric -> new ChartDataPoint(
metric.getTimestamp(),
metric.getCpu().getProcessCpuLoad() * 100,
metric.getCpu().getSystemCpuLoad() * 100
))
.collect(Collectors.toList());
return new ChartData("CPU Usage", dataPoints, "line");
}
public ChartData generateThreadCountChart(Duration duration) {
List<JVMMetrics> metrics = storageService.getRecentMetrics("default", duration);
List<ChartDataPoint> dataPoints = metrics.stream()
.map(metric -> new ChartDataPoint(
metric.getTimestamp(),
(double) metric.getThreads().getLiveThreads(),
(double) metric.getThreads().getPeakThreads()
))
.collect(Collectors.toList());
return new ChartData("Thread Count", dataPoints, "line");
}
public ChartData generateGCTimeChart(Duration duration) {
List<JVMMetrics> metrics = storageService.getRecentMetrics("default", duration);
List<ChartDataPoint> dataPoints = metrics.stream()
.map(metric -> new ChartDataPoint(
metric.getTimestamp(),
(double) metric.getGarbageCollection().getGcTime()
))
.collect(Collectors.toList());
return new ChartData("GC Time", dataPoints, "bar");
}
public ChartData generateMemoryPoolChart() {
Optional<JVMMetrics> current = storageService.getLatestMetrics("default");
if (current.isEmpty()) {
return new ChartData("Memory Pools", Collections.emptyList(), "doughnut");
}
JVMMetrics metrics = current.get();
List<ChartDataPoint> dataPoints = metrics.getMemory().getMemoryPools().values().stream()
.map(pool -> new ChartDataPoint(
pool.getPoolName(),
pool.getUsagePercentage()
))
.collect(Collectors.toList());
return new ChartData("Memory Pools", dataPoints, "doughnut");
}
}
public class ChartData {
private final String title;
private final List<ChartDataPoint> dataPoints;
private final String chartType;
private final Map<String, Object> options;
public ChartData(String title, List<ChartDataPoint> dataPoints, String chartType) {
this.title = title;
this.dataPoints = dataPoints;
this.chartType = chartType;
this.options = createDefaultOptions();
}
private Map<String, Object> createDefaultOptions() {
Map<String, Object> options = new HashMap<>();
options.put("responsive", true);
options.put("maintainAspectRatio", false);
options.put("animation", true);
return options;
}
// Getters
public String getTitle() { return title; }
public List<ChartDataPoint> getDataPoints() { return dataPoints; }
public String getChartType() { return chartType; }
public Map<String, Object> getOptions() { return options; }
}
public class ChartDataPoint {
private final Instant timestamp;
private final String label;
private final double value;
private final double secondaryValue;
public ChartDataPoint(Instant timestamp, double value) {
this(timestamp, value, 0);
}
public ChartDataPoint(Instant timestamp, double value, double secondaryValue) {
this.timestamp = timestamp;
this.label = null;
this.value = value;
this.secondaryValue = secondaryValue;
}
public ChartDataPoint(String label, double value) {
this.timestamp = null;
this.label = label;
this.value = value;
this.secondaryValue = 0;
}
// Getters
public Instant getTimestamp() { return timestamp; }
public String getLabel() { return label; }
public double getValue() { return value; }
public double getSecondaryValue() { return secondaryValue; }
}
Main Application Class
Spring Boot Application
@SpringBootApplication
public class JMCDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(JMCDashboardApplication.class, args);
}
@Bean
public MetricsStorageService metricsStorageService() {
return new MetricsStorageService("main-app", 1000); // Keep 1000 data points
}
@Bean
public AlertService alertService(MetricsStorageService storageService) {
return new AlertService(storageService);
}
@Bean
public ChartDataService chartDataService(MetricsStorageService storageService) {
return new ChartDataService(storageService);
}
@Bean
public CommandLineRunner initDashboard(MetricsStorageService storageService) {
return args -> {
System.out.println("Java Mission Control Dashboard Started");
System.out.println("Monitoring JVM metrics...");
System.out.println("Dashboard available at: http://localhost:8080");
};
}
}
// Configuration properties
@ConfigurationProperties(prefix = "jmc.dashboard")
@Data
public class JMCDashboardProperties {
private int historySize = 1000;
private int collectionIntervalSeconds = 1;
private int alertCheckIntervalSeconds = 5;
private boolean enableAlerts = true;
private boolean enableWebSocket = true;
private String defaultJvmId = "main-app";
// Chart configuration
private Charts charts = new Charts();
@Data
public static class Charts {
private int defaultDataPoints = 100;
private boolean enableAnimations = true;
private String defaultTheme = "dark";
}
}
This Java Mission Control Dashboard provides a comprehensive monitoring solution with real-time metrics collection, alerting, and visualization capabilities. It can be extended with additional features like historical data analysis, custom metrics, and integration with external monitoring systems.