Introduction
The USE Method (Utilization, Saturation, Errors) is a systematic approach for analyzing system performance and identifying bottlenecks. It focuses on three key metrics for every resource: Utilization (percentage time busy), Saturation (queue length or wait time), and Errors (error counts).
Architecture Overview
USE Method Framework
public class USEMethodFramework {
/**
* USE Method Components:
* 1. Resource Discovery - Identify system resources
* 2. Metric Collection - Gather Utilization, Saturation, Errors
* 3. Analysis Engine - Identify bottlenecks and saturation points
* 4. Reporting - Generate actionable insights
*/
private ResourceDiscoverer resourceDiscoverer;
private MetricCollector metricCollector;
private AnalysisEngine analysisEngine;
private ReportGenerator reportGenerator;
public USEAnalysis analyzeSystem() {
List<SystemResource> resources = resourceDiscoverer.discoverResources();
Map<SystemResource, USEMetrics> metrics = metricCollector.collectMetrics(resources);
List<Bottleneck> bottlenecks = analysisEngine.analyze(metrics);
return reportGenerator.generateReport(metrics, bottlenecks);
}
}
Core Implementation
Resource Model
public enum ResourceType {
CPU,
MEMORY,
DISK_IO,
NETWORK_IO,
DATABASE_CONNECTION,
THREAD_POOL,
GARBAGE_COLLECTOR,
EXTERNAL_SERVICE,
MESSAGE_QUEUE,
CACHE
}
public class SystemResource {
private final String id;
private final String name;
private final ResourceType type;
private final String description;
private final Map<String, String> attributes;
public SystemResource(String id, String name, ResourceType type,
String description, Map<String, String> attributes) {
this.id = id;
this.name = name;
this.type = type;
this.description = description;
this.attributes = attributes != null ? attributes : new HashMap<>();
}
// Builder pattern
public static class Builder {
private String id;
private String name;
private ResourceType type;
private String description;
private Map<String, String> attributes = new HashMap<>();
public Builder id(String id) { this.id = id; return this; }
public Builder name(String name) { this.name = name; return this; }
public Builder type(ResourceType type) { this.type = type; return this; }
public Builder description(String description) { this.description = description; return this; }
public Builder attribute(String key, String value) { this.attributes.put(key, value); return this; }
public SystemResource build() {
return new SystemResource(id, name, type, description, attributes);
}
}
// Getters
public String getId() { return id; }
public ResourceType getType() { return type; }
}
public class USEMetrics {
private final SystemResource resource;
private final double utilization; // 0.0 to 1.0 (percentage)
private final double saturation; // queue length or wait time
private final long errors; // error count
private final long timestamp;
private final Map<String, Object> additionalMetrics;
public USEMetrics(SystemResource resource, double utilization,
double saturation, long errors, long timestamp,
Map<String, Object> additionalMetrics) {
this.resource = resource;
this.utilization = utilization;
this.saturation = saturation;
this.errors = errors;
this.timestamp = timestamp;
this.additionalMetrics = additionalMetrics != null ? additionalMetrics : new HashMap<>();
}
// Getters
public double getUtilization() { return utilization; }
public double getSaturation() { return saturation; }
public long getErrors() { return errors; }
public boolean isUtilizationCritical() {
return utilization > 0.8; // 80% threshold
}
public boolean isSaturationCritical() {
return saturation > 10; // Queue length > 10
}
public boolean hasErrors() {
return errors > 0;
}
}
Resource Discovery
@Component
public class ResourceDiscoverer {
private static final Logger logger = LoggerFactory.getLogger(ResourceDiscoverer.class);
public List<SystemResource> discoverResources() {
List<SystemResource> resources = new ArrayList<>();
// CPU Resources
resources.addAll(discoverCPUResources());
// Memory Resources
resources.addAll(discoverMemoryResources());
// Disk I/O Resources
resources.addAll(discoverDiskResources());
// Network Resources
resources.addAll(discoverNetworkResources());
// Application Resources
resources.addAll(discoverApplicationResources());
logger.info("Discovered {} system resources", resources.size());
return resources;
}
private List<SystemResource> discoverCPUResources() {
List<SystemResource> cpuResources = new ArrayList<>();
int processorCount = Runtime.getRuntime().availableProcessors();
for (int i = 0; i < processorCount; i++) {
cpuResources.add(new SystemResource.Builder()
.id("cpu-" + i)
.name("CPU Core " + i)
.type(ResourceType.CPU)
.description("CPU processor core")
.attribute("core_id", String.valueOf(i))
.build());
}
// Overall CPU
cpuResources.add(new SystemResource.Builder()
.id("cpu-total")
.name("Total CPU")
.type(ResourceType.CPU)
.description("Total system CPU")
.build());
return cpuResources;
}
private List<SystemResource> discoverMemoryResources() {
return Arrays.asList(
new SystemResource.Builder()
.id("memory-heap")
.name("Heap Memory")
.type(ResourceType.MEMORY)
.description("JVM Heap Memory")
.build(),
new SystemResource.Builder()
.id("memory-nonheap")
.name("Non-Heap Memory")
.type(ResourceType.MEMORY)
.description("JVM Non-Heap Memory")
.build(),
new SystemResource.Builder()
.id("memory-system")
.name("System Memory")
.type(ResourceType.MEMORY)
.description("Total System Memory")
.build()
);
}
private List<SystemResource> discoverDiskResources() {
List<SystemResource> diskResources = new ArrayList<>();
try {
File[] roots = File.listRoots();
for (File root : roots) {
diskResources.add(new SystemResource.Builder()
.id("disk-" + root.getAbsolutePath().replace(File.separator, ""))
.name("Disk " + root.getAbsolutePath())
.type(ResourceType.DISK_IO)
.description("Disk storage")
.attribute("path", root.getAbsolutePath())
.build());
}
} catch (Exception e) {
logger.warn("Failed to discover disk resources", e);
}
return diskResources;
}
private List<SystemResource> discoverNetworkResources() {
List<SystemResource> networkResources = new ArrayList<>();
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface ni = interfaces.nextElement();
if (ni.isUp() && !ni.isLoopback()) {
networkResources.add(new SystemResource.Builder()
.id("network-" + ni.getName())
.name("Network " + ni.getDisplayName())
.type(ResourceType.NETWORK_IO)
.description("Network interface")
.attribute("interface_name", ni.getName())
.build());
}
}
} catch (Exception e) {
logger.warn("Failed to discover network resources", e);
}
return networkResources;
}
private List<SystemResource> discoverApplicationResources() {
List<SystemResource> appResources = new ArrayList<>();
// Database connections
appResources.add(new SystemResource.Builder()
.id("db-connections")
.name("Database Connections")
.type(ResourceType.DATABASE_CONNECTION)
.description("Database connection pool")
.build());
// Thread pools
appResources.add(new SystemResource.Builder()
.id("thread-pool-main")
.name("Main Thread Pool")
.type(ResourceType.THREAD_POOL)
.description("Main application thread pool")
.build());
// Garbage collector
appResources.add(new SystemResource.Builder()
.id("gc-heap")
.name("Garbage Collector")
.type(ResourceType.GARBAGE_COLLECTOR)
.description("JVM Garbage Collector")
.build());
// External services
appResources.add(new SystemResource.Builder()
.id("external-api")
.name("External API")
.type(ResourceType.EXTERNAL_SERVICE)
.description("External REST API service")
.build());
return appResources;
}
}
Metric Collection
Comprehensive Metric Collector
@Component
public class MetricCollector {
private static final Logger logger = LoggerFactory.getLogger(MetricCollector.class);
private final OperatingSystemMXBean osBean;
private final RuntimeMXBean runtimeBean;
private final ThreadMXBean threadBean;
private final MemoryMXBean memoryBean;
private final List<GarbageCollectorMXBean> gcBeans;
private final MeterRegistry meterRegistry;
public MetricCollector(MeterRegistry meterRegistry) {
this.osBean = ManagementFactory.getOperatingSystemMXBean();
this.runtimeBean = ManagementFactory.getRuntimeMXBean();
this.threadBean = ManagementFactory.getThreadMXBean();
this.memoryBean = ManagementFactory.getMemoryMXBean();
this.gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
this.meterRegistry = meterRegistry;
}
public Map<SystemResource, USEMetrics> collectMetrics(List<SystemResource> resources) {
Map<SystemResource, USEMetrics> metrics = new ConcurrentHashMap<>();
resources.parallelStream().forEach(resource -> {
try {
USEMetrics resourceMetrics = collectResourceMetrics(resource);
metrics.put(resource, resourceMetrics);
} catch (Exception e) {
logger.warn("Failed to collect metrics for resource: {}", resource.getId(), e);
}
});
return metrics;
}
private USEMetrics collectResourceMetrics(SystemResource resource) {
long timestamp = System.currentTimeMillis();
Map<String, Object> additionalMetrics = new HashMap<>();
switch (resource.getType()) {
case CPU:
return collectCPUMetrics(resource, timestamp, additionalMetrics);
case MEMORY:
return collectMemoryMetrics(resource, timestamp, additionalMetrics);
case DISK_IO:
return collectDiskMetrics(resource, timestamp, additionalMetrics);
case NETWORK_IO:
return collectNetworkMetrics(resource, timestamp, additionalMetrics);
case THREAD_POOL:
return collectThreadPoolMetrics(resource, timestamp, additionalMetrics);
case GARBAGE_COLLECTOR:
return collectGCMetrics(resource, timestamp, additionalMetrics);
case DATABASE_CONNECTION:
return collectDatabaseMetrics(resource, timestamp, additionalMetrics);
case EXTERNAL_SERVICE:
return collectExternalServiceMetrics(resource, timestamp, additionalMetrics);
default:
return new USEMetrics(resource, 0.0, 0.0, 0, timestamp, additionalMetrics);
}
}
private USEMetrics collectCPUMetrics(SystemResource resource, long timestamp,
Map<String, Object> additionalMetrics) {
double utilization = 0.0;
double saturation = 0.0;
long errors = 0;
if (resource.getId().equals("cpu-total")) {
// System CPU utilization
if (osBean instanceof com.sun.management.OperatingSystemMXBean) {
com.sun.management.OperatingSystemMXBean sunOsBean =
(com.sun.management.OperatingSystemMXBean) osBean;
utilization = sunOsBean.getSystemCpuLoad();
if (utilization < 0) utilization = 0.0;
}
// System load average as saturation metric
double loadAverage = osBean.getSystemLoadAverage();
if (loadAverage > 0) {
saturation = loadAverage;
}
additionalMetrics.put("available_processors", osBean.getAvailableProcessors());
additionalMetrics.put("system_load_average", osBean.getSystemLoadAverage());
} else {
// Individual core metrics (simplified - Java doesn't provide per-core stats directly)
utilization = 0.5; // Placeholder
saturation = 0.0;
}
return new USEMetrics(resource, utilization, saturation, errors, timestamp, additionalMetrics);
}
private USEMetrics collectMemoryMetrics(SystemResource resource, long timestamp,
Map<String, Object> additionalMetrics) {
double utilization = 0.0;
double saturation = 0.0;
long errors = 0;
MemoryUsage usage;
if (resource.getId().equals("memory-heap")) {
usage = memoryBean.getHeapMemoryUsage();
long max = usage.getMax();
long used = usage.getUsed();
utilization = max > 0 ? (double) used / max : 0.0;
// Saturation: GC pressure
saturation = calculateGCPressure();
} else if (resource.getId().equals("memory-nonheap")) {
usage = memoryBean.getNonHeapMemoryUsage();
long max = usage.getMax();
long used = usage.getUsed();
utilization = max > 0 ? (double) used / max : 0.0;
} else if (resource.getId().equals("memory-system")) {
if (osBean instanceof com.sun.management.OperatingSystemMXBean) {
com.sun.management.OperatingSystemMXBean sunOsBean =
(com.sun.management.OperatingSystemMXBean) osBean;
long totalMemory = sunOsBean.getTotalPhysicalMemorySize();
long freeMemory = sunOsBean.getFreePhysicalMemorySize();
long usedMemory = totalMemory - freeMemory;
utilization = totalMemory > 0 ? (double) usedMemory / totalMemory : 0.0;
additionalMetrics.put("total_memory_bytes", totalMemory);
additionalMetrics.put("free_memory_bytes", freeMemory);
additionalMetrics.put("used_memory_bytes", usedMemory);
}
}
// Memory errors (OOMEs)
errors = getMemoryErrorCount();
return new USEMetrics(resource, utilization, saturation, errors, timestamp, additionalMetrics);
}
private USEMetrics collectDiskMetrics(SystemResource resource, long timestamp,
Map<String, Object> additionalMetrics) {
double utilization = 0.0;
double saturation = 0.0;
long errors = 0;
try {
String path = resource.getAttributes().get("path");
if (path != null) {
File file = new File(path);
long totalSpace = file.getTotalSpace();
long freeSpace = file.getFreeSpace();
long usedSpace = totalSpace - freeSpace;
utilization = totalSpace > 0 ? (double) usedSpace / totalSpace : 0.0;
additionalMetrics.put("total_space_bytes", totalSpace);
additionalMetrics.put("free_space_bytes", freeSpace);
additionalMetrics.put("used_space_bytes", usedSpace);
}
} catch (Exception e) {
logger.warn("Failed to collect disk metrics for {}", resource.getId(), e);
errors++;
}
return new USEMetrics(resource, utilization, saturation, errors, timestamp, additionalMetrics);
}
private USEMetrics collectNetworkMetrics(SystemResource resource, long timestamp,
Map<String, Object> additionalMetrics) {
// Network metrics collection would require OS-specific implementation
// or using libraries like OSHI or Sigar
double utilization = 0.0;
double saturation = 0.0;
long errors = 0;
return new USEMetrics(resource, utilization, saturation, errors, timestamp, additionalMetrics);
}
private USEMetrics collectThreadPoolMetrics(SystemResource resource, long timestamp,
Map<String, Object> additionalMetrics) {
double utilization = 0.0;
double saturation = 0.0;
long errors = 0;
// Thread pool metrics
int threadCount = threadBean.getThreadCount();
int peakThreadCount = threadBean.getPeakThreadCount();
int daemonThreadCount = threadBean.getDaemonThreadCount();
// Utilization: active threads vs peak
utilization = peakThreadCount > 0 ? (double) threadCount / peakThreadCount : 0.0;
// Saturation: thread wait time or queue length (simplified)
saturation = calculateThreadSaturation();
additionalMetrics.put("thread_count", threadCount);
additionalMetrics.put("peak_thread_count", peakThreadCount);
additionalMetrics.put("daemon_thread_count", daemonThreadCount);
return new USEMetrics(resource, utilization, saturation, errors, timestamp, additionalMetrics);
}
private USEMetrics collectGCMetrics(SystemResource resource, long timestamp,
Map<String, Object> additionalMetrics) {
double utilization = 0.0;
double saturation = 0.0;
long errors = 0;
long totalGcTime = 0;
long totalGcCount = 0;
for (GarbageCollectorMXBean gcBean : gcBeans) {
totalGcTime += gcBean.getCollectionTime();
totalGcCount += gcBean.getCollectionCount();
}
// Utilization: GC time as percentage of uptime
long uptime = runtimeBean.getUptime();
utilization = uptime > 0 ? (double) totalGcTime / uptime : 0.0;
// Saturation: GC frequency
saturation = totalGcCount;
additionalMetrics.put("total_gc_time_ms", totalGcTime);
additionalMetrics.put("total_gc_count", totalGcCount);
return new USEMetrics(resource, utilization, saturation, errors, timestamp, additionalMetrics);
}
private USEMetrics collectDatabaseMetrics(SystemResource resource, long timestamp,
Map<String, Object> additionalMetrics) {
// Database connection pool metrics
// This would integrate with HikariCP, Tomcat JDBC, etc.
double utilization = 0.0;
double saturation = 0.0;
long errors = 0;
try {
// Example for HikariCP
Object pool = getConnectionPool();
if (pool != null) {
utilization = getPoolUtilization(pool);
saturation = getPoolWaitCount(pool);
errors = getPoolErrorCount(pool);
}
} catch (Exception e) {
logger.warn("Failed to collect database metrics", e);
}
return new USEMetrics(resource, utilization, saturation, errors, timestamp, additionalMetrics);
}
private USEMetrics collectExternalServiceMetrics(SystemResource resource, long timestamp,
Map<String, Object> additionalMetrics) {
// External service metrics from Micrometer
double utilization = 0.0;
double saturation = 0.0;
long errors = 0;
try {
Counter errorCounter = meterRegistry.find("http.server.requests").counter();
Timer requestTimer = meterRegistry.find("http.server.requests").timer();
if (errorCounter != null) {
errors = (long) errorCounter.count();
}
if (requestTimer != null) {
// Use mean response time as saturation indicator
saturation = requestTimer.mean(TimeUnit.MILLISECONDS);
// Utilization based on request rate (simplified)
utilization = Math.min(requestTimer.count() / 1000.0, 1.0); // Normalized
}
} catch (Exception e) {
logger.warn("Failed to collect external service metrics", e);
}
return new USEMetrics(resource, utilization, saturation, errors, timestamp, additionalMetrics);
}
// Helper methods
private double calculateGCPressure() {
// Simplified GC pressure calculation
long totalGcTime = gcBeans.stream()
.mapToLong(GarbageCollectorMXBean::getCollectionTime)
.sum();
long uptime = runtimeBean.getUptime();
return uptime > 0 ? (double) totalGcTime / uptime : 0.0;
}
private double calculateThreadSaturation() {
// Thread saturation based on blocked threads
long[] threadIds = threadBean.getAllThreadIds();
long blockedCount = Arrays.stream(threadIds)
.filter(id -> threadBean.getThreadInfo(id) != null)
.filter(id -> threadBean.getThreadInfo(id).getBlockedCount() > 0)
.count();
return (double) blockedCount / threadIds.length;
}
private long getMemoryErrorCount() {
// Count memory-related errors from logs or MBeans
return 0; // Implementation would depend on error tracking system
}
// Placeholder methods for database pool metrics
private Object getConnectionPool() { return null; }
private double getPoolUtilization(Object pool) { return 0.0; }
private double getPoolWaitCount(Object pool) { return 0.0; }
private long getPoolErrorCount(Object pool) { return 0; }
}
Analysis Engine
Bottleneck Detection
@Component
public class AnalysisEngine {
private static final Logger logger = LoggerFactory.getLogger(AnalysisEngine.class);
private final double utilizationThreshold;
private final double saturationThreshold;
public AnalysisEngine() {
this.utilizationThreshold = 0.8; // 80%
this.saturationThreshold = 10.0; // Queue length 10
}
public List<Bottleneck> analyze(Map<SystemResource, USEMetrics> metrics) {
List<Bottleneck> bottlenecks = new ArrayList<>();
for (Map.Entry<SystemResource, USEMetrics> entry : metrics.entrySet()) {
SystemResource resource = entry.getKey();
USEMetrics resourceMetrics = entry.getValue();
List<Bottleneck> resourceBottlenecks = analyzeResource(resource, resourceMetrics);
bottlenecks.addAll(resourceBottlenecks);
}
// Sort by severity
bottlenecks.sort(Comparator.comparing(Bottleneck::getSeverity).reversed());
logger.info("Found {} potential bottlenecks", bottlenecks.size());
return bottlenecks;
}
private List<Bottleneck> analyzeResource(SystemResource resource, USEMetrics metrics) {
List<Bottleneck> bottlenecks = new ArrayList<>();
// Utilization analysis
if (metrics.getUtilization() > utilizationThreshold) {
bottlenecks.add(createUtilizationBottleneck(resource, metrics));
}
// Saturation analysis
if (metrics.getSaturation() > saturationThreshold) {
bottlenecks.add(createSaturationBottleneck(resource, metrics));
}
// Error analysis
if (metrics.hasErrors()) {
bottlenecks.add(createErrorBottleneck(resource, metrics));
}
return bottlenecks;
}
private Bottleneck createUtilizationBottleneck(SystemResource resource, USEMetrics metrics) {
double utilization = metrics.getUtilization();
Severity severity = calculateUtilizationSeverity(utilization);
String description = String.format(
"High utilization detected: %.1f%% (threshold: %.1f%%)",
utilization * 100, utilizationThreshold * 100);
String recommendation = generateUtilizationRecommendation(resource, utilization);
return new Bottleneck(
resource,
BottleneckType.UTILIZATION,
severity,
description,
recommendation,
metrics.getTimestamp()
);
}
private Bottleneck createSaturationBottleneck(SystemResource resource, USEMetrics metrics) {
double saturation = metrics.getSaturation();
Severity severity = calculateSaturationSeverity(saturation);
String description = String.format(
"High saturation detected: %.1f (threshold: %.1f)",
saturation, saturationThreshold);
String recommendation = generateSaturationRecommendation(resource, saturation);
return new Bottleneck(
resource,
BottleneckType.SATURATION,
severity,
description,
recommendation,
metrics.getTimestamp()
);
}
private Bottleneck createErrorBottleneck(SystemResource resource, USEMetrics metrics) {
long errors = metrics.getErrors();
Severity severity = calculateErrorSeverity(errors);
String description = String.format("Errors detected: %d", errors);
String recommendation = generateErrorRecommendation(resource, errors);
return new Bottleneck(
resource,
BottleneckType.ERRORS,
severity,
description,
recommendation,
metrics.getTimestamp()
);
}
private Severity calculateUtilizationSeverity(double utilization) {
if (utilization > 0.95) return Severity.CRITICAL;
if (utilization > 0.90) return Severity.HIGH;
if (utilization > 0.80) return Severity.MEDIUM;
return Severity.LOW;
}
private Severity calculateSaturationSeverity(double saturation) {
if (saturation > 100) return Severity.CRITICAL;
if (saturation > 50) return Severity.HIGH;
if (saturation > 10) return Severity.MEDIUM;
return Severity.LOW;
}
private Severity calculateErrorSeverity(long errors) {
if (errors > 100) return Severity.CRITICAL;
if (errors > 10) return Severity.HIGH;
if (errors > 0) return Severity.MEDIUM;
return Severity.LOW;
}
private String generateUtilizationRecommendation(SystemResource resource, double utilization) {
switch (resource.getType()) {
case CPU:
return "Consider optimizing CPU-intensive operations, scaling horizontally, or upgrading hardware";
case MEMORY:
return "Investigate memory leaks, increase heap size, or optimize memory usage";
case DISK_IO:
return "Optimize disk I/O operations, use faster storage, or implement caching";
case NETWORK_IO:
return "Optimize network usage, increase bandwidth, or use compression";
case DATABASE_CONNECTION:
return "Optimize database queries, increase connection pool size, or add connection pooling";
case THREAD_POOL:
return "Increase thread pool size, optimize task execution, or use async processing";
default:
return "Investigate resource usage and consider optimization or scaling";
}
}
private String generateSaturationRecommendation(SystemResource resource, double saturation) {
switch (resource.getType()) {
case CPU:
return "High load average indicates processes waiting for CPU. Consider scaling or optimization";
case MEMORY:
return "High GC pressure indicates memory contention. Investigate memory usage patterns";
case THREAD_POOL:
return "Thread pool queue is growing. Increase pool size or optimize task processing";
case DATABASE_CONNECTION:
return "Connection wait queue is growing. Increase pool size or optimize query performance";
default:
return "Resource is experiencing high contention. Consider scaling or optimization";
}
}
private String generateErrorRecommendation(SystemResource resource, long errors) {
switch (resource.getType()) {
case MEMORY:
return "Memory errors detected. Check for OutOfMemoryErrors and memory leaks";
case DATABASE_CONNECTION:
return "Database connection errors. Check database health and connection settings";
case EXTERNAL_SERVICE:
return "External service errors. Check service availability and implement retry logic";
default:
return "Resource errors detected. Investigate error logs and implement error handling";
}
}
}
public class Bottleneck {
private final SystemResource resource;
private final BottleneckType type;
private final Severity severity;
private final String description;
private final String recommendation;
private final long detectedAt;
public Bottleneck(SystemResource resource, BottleneckType type, Severity severity,
String description, String recommendation, long detectedAt) {
this.resource = resource;
this.type = type;
this.severity = severity;
this.description = description;
this.recommendation = recommendation;
this.detectedAt = detectedAt;
}
// Getters
public Severity getSeverity() { return severity; }
public BottleneckType getType() { return type; }
public SystemResource getResource() { return resource; }
}
public enum BottleneckType {
UTILIZATION,
SATURATION,
ERRORS
}
public enum Severity {
CRITICAL,
HIGH,
MEDIUM,
LOW
}
Real-time Monitoring
Continuous USE Monitoring
@Component
public class USEMonitor {
private static final Logger logger = LoggerFactory.getLogger(USEMonitor.class);
private final ResourceDiscoverer resourceDiscoverer;
private final MetricCollector metricCollector;
private final AnalysisEngine analysisEngine;
private final ScheduledExecutorService scheduler;
private final List<USEMetricsListener> listeners;
private volatile boolean monitoring = false;
private ScheduledFuture<?> monitoringTask;
public USEMonitor(ResourceDiscoverer resourceDiscoverer, MetricCollector metricCollector,
AnalysisEngine analysisEngine) {
this.resourceDiscoverer = resourceDiscoverer;
this.metricCollector = metricCollector;
this.analysisEngine = analysisEngine;
this.scheduler = Executors.newSingleThreadScheduledExecutor();
this.listeners = new CopyOnWriteArrayList<>();
}
public void startMonitoring(long intervalSeconds) {
if (monitoring) {
logger.warn("USE monitoring is already running");
return;
}
monitoring = true;
monitoringTask = scheduler.scheduleAtFixedRate(
this::collectAndAnalyze,
0, intervalSeconds, TimeUnit.SECONDS
);
logger.info("Started USE monitoring with {} second interval", intervalSeconds);
}
public void stopMonitoring() {
if (!monitoring) {
return;
}
monitoring = false;
if (monitoringTask != null) {
monitoringTask.cancel(false);
}
logger.info("Stopped USE monitoring");
}
public void addListener(USEMetricsListener listener) {
listeners.add(listener);
}
public void removeListener(USEMetricsListener listener) {
listeners.remove(listener);
}
private void collectAndAnalyze() {
try {
// Discover resources
List<SystemResource> resources = resourceDiscoverer.discoverResources();
// Collect metrics
Map<SystemResource, USEMetrics> metrics = metricCollector.collectMetrics(resources);
// Analyze for bottlenecks
List<Bottleneck> bottlenecks = analysisEngine.analyze(metrics);
// Notify listeners
notifyListeners(metrics, bottlenecks);
// Log critical issues
logCriticalBottlenecks(bottlenecks);
} catch (Exception e) {
logger.error("Error during USE monitoring cycle", e);
}
}
private void notifyListeners(Map<SystemResource, USEMetrics> metrics,
List<Bottleneck> bottlenecks) {
for (USEMetricsListener listener : listeners) {
try {
listener.onMetricsCollected(metrics, bottlenecks);
} catch (Exception e) {
logger.warn("Error notifying metrics listener", e);
}
}
}
private void logCriticalBottlenecks(List<Bottleneck> bottlenecks) {
List<Bottleneck> criticalBottlenecks = bottlenecks.stream()
.filter(b -> b.getSeverity() == Severity.CRITICAL)
.collect(Collectors.toList());
if (!criticalBottlenecks.isEmpty()) {
logger.warn("Found {} critical bottlenecks:", criticalBottlenecks.size());
for (Bottleneck bottleneck : criticalBottlenecks) {
logger.warn("CRITICAL: {} - {}", bottleneck.getResource().getName(),
bottleneck.getDescription());
}
}
}
@PreDestroy
public void shutdown() {
stopMonitoring();
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
public interface USEMetricsListener {
void onMetricsCollected(Map<SystemResource, USEMetrics> metrics,
List<Bottleneck> bottlenecks);
}
@Component
public class AlertingListener implements USEMetricsListener {
private static final Logger logger = LoggerFactory.getLogger(AlertingListener.class);
private final AlertService alertService;
private final Set<String> notifiedBottlenecks = ConcurrentHashMap.newKeySet();
public AlertingListener(AlertService alertService) {
this.alertService = alertService;
}
@Override
public void onMetricsCollected(Map<SystemResource, USEMetrics> metrics,
List<Bottleneck> bottlenecks) {
for (Bottleneck bottleneck : bottlenecks) {
if (bottleneck.getSeverity() == Severity.CRITICAL ||
bottleneck.getSeverity() == Severity.HIGH) {
String bottleneckKey = createBottleneckKey(bottleneck);
if (!notifiedBottlenecks.contains(bottleneckKey)) {
sendAlert(bottleneck);
notifiedBottlenecks.add(bottleneckKey);
}
}
}
// Clean up resolved bottlenecks
Set<String> currentBottleneckKeys = bottlenecks.stream()
.filter(b -> b.getSeverity() == Severity.CRITICAL || b.getSeverity() == Severity.HIGH)
.map(this::createBottleneckKey)
.collect(Collectors.toSet());
notifiedBottlenecks.retainAll(currentBottleneckKeys);
}
private String createBottleneckKey(Bottleneck bottleneck) {
return bottleneck.getResource().getId() + ":" + bottleneck.getType();
}
private void sendAlert(Bottleneck bottleneck) {
Alert alert = new Alert(
"USE Method Bottleneck Detected",
String.format("Critical bottleneck detected in %s: %s",
bottleneck.getResource().getName(), bottleneck.getDescription()),
bottleneck.getSeverity() == Severity.CRITICAL ? AlertLevel.CRITICAL : AlertLevel.HIGH,
bottleneck.getRecommendation()
);
alertService.sendAlert(alert);
logger.warn("Sent alert for bottleneck: {}", bottleneck.getDescription());
}
}
Integration with Application Performance Monitoring
Micrometer Integration
@Component
public class MicrometerUSEIntegration {
private final MeterRegistry meterRegistry;
private final USEMonitor useMonitor;
public MicrometerUSEIntegration(MeterRegistry meterRegistry, USEMonitor useMonitor) {
this.meterRegistry = meterRegistry;
this.useMonitor = useMonitor;
setupMetricsPublishing();
}
private void setupMetricsPublishing() {
useMonitor.addListener((metrics, bottlenecks) -> {
publishUSEMetrics(metrics);
publishBottleneckMetrics(bottlenecks);
});
}
private void publishUSEMetrics(Map<SystemResource, USEMetrics> metrics) {
for (Map.Entry<SystemResource, USEMetrics> entry : metrics.entrySet()) {
SystemResource resource = entry.getKey();
USEMetrics useMetrics = entry.getValue();
// Publish utilization gauge
Gauge.builder("use.utilization", useMetrics, USEMetrics::getUtilization)
.tag("resource", resource.getId())
.tag("resource_type", resource.getType().name())
.register(meterRegistry);
// Publish saturation gauge
Gauge.builder("use.saturation", useMetrics, USEMetrics::getSaturation)
.tag("resource", resource.getId())
.tag("resource_type", resource.getType().name())
.register(meterRegistry);
// Publish errors counter
Counter.builder("use.errors")
.tag("resource", resource.getId())
.tag("resource_type", resource.getType().name())
.register(meterRegistry)
.increment(useMetrics.getErrors());
}
}
private void publishBottleneckMetrics(List<Bottleneck> bottlenecks) {
// Count bottlenecks by severity and type
Map<Severity, Long> bottlenecksBySeverity = bottlenecks.stream()
.collect(Collectors.groupingBy(Bottleneck::getSeverity, Collectors.counting()));
Map<BottleneckType, Long> bottlenecksByType = bottlenecks.stream()
.collect(Collectors.groupingBy(Bottleneck::getType, Collectors.counting()));
// Publish bottleneck counts
bottlenecksBySeverity.forEach((severity, count) -> {
Gauge.builder("use.bottlenecks.count")
.tag("severity", severity.name())
.register(meterRegistry)
.set(count);
});
bottlenecksByType.forEach((type, count) -> {
Gauge.builder("use.bottlenecks.by_type")
.tag("type", type.name())
.register(meterRegistry)
.set(count);
});
}
}
Web Dashboard
REST API for USE Metrics
@RestController
@RequestMapping("/api/use")
public class USEMetricsController {
private final USEMonitor useMonitor;
private final ResourceDiscoverer resourceDiscoverer;
private final MetricCollector metricCollector;
private final AnalysisEngine analysisEngine;
public USEMetricsController(USEMonitor useMonitor, ResourceDiscoverer resourceDiscoverer,
MetricCollector metricCollector, AnalysisEngine analysisEngine) {
this.useMonitor = useMonitor;
this.resourceDiscoverer = resourceDiscoverer;
this.metricCollector = metricCollector;
this.analysisEngine = analysisEngine;
}
@GetMapping("/resources")
public List<SystemResource> getResources() {
return resourceDiscoverer.discoverResources();
}
@GetMapping("/metrics")
public Map<String, Object> getCurrentMetrics() {
List<SystemResource> resources = resourceDiscoverer.discoverResources();
Map<SystemResource, USEMetrics> metrics = metricCollector.collectMetrics(resources);
List<Bottleneck> bottlenecks = analysisEngine.analyze(metrics);
Map<String, Object> response = new HashMap<>();
response.put("timestamp", System.currentTimeMillis());
response.put("metrics", convertMetricsToMap(metrics));
response.put("bottlenecks", bottlenecks);
response.put("summary", createSummary(metrics, bottlenecks));
return response;
}
@GetMapping("/bottlenecks")
public List<Bottleneck> getCurrentBottlenecks() {
List<SystemResource> resources = resourceDiscoverer.discoverResources();
Map<SystemResource, USEMetrics> metrics = metricCollector.collectMetrics(resources);
return analysisEngine.analyze(metrics);
}
@PostMapping("/monitoring/start")
public ResponseEntity<String> startMonitoring(@RequestParam(defaultValue = "30") long interval) {
useMonitor.startMonitoring(interval);
return ResponseEntity.ok("USE monitoring started with " + interval + " second interval");
}
@PostMapping("/monitoring/stop")
public ResponseEntity<String> stopMonitoring() {
useMonitor.stopMonitoring();
return ResponseEntity.ok("USE monitoring stopped");
}
@GetMapping("/health")
public Map<String, Object> getHealthStatus() {
List<SystemResource> resources = resourceDiscoverer.discoverResources();
Map<SystemResource, USEMetrics> metrics = metricCollector.collectMetrics(resources);
List<Bottleneck> bottlenecks = analysisEngine.analyze(metrics);
long criticalBottlenecks = bottlenecks.stream()
.filter(b -> b.getSeverity() == Severity.CRITICAL)
.count();
Map<String, Object> health = new HashMap<>();
health.put("status", criticalBottlenecks > 0 ? "UNHEALTHY" : "HEALTHY");
health.put("critical_bottlenecks", criticalBottlenecks);
health.put("total_resources", resources.size());
health.put("timestamp", System.currentTimeMillis());
return health;
}
private Map<String, Object> convertMetricsToMap(Map<SystemResource, USEMetrics> metrics) {
return metrics.entrySet().stream()
.collect(Collectors.toMap(
entry -> entry.getKey().getId(),
entry -> {
USEMetrics useMetrics = entry.getValue();
Map<String, Object> metricMap = new HashMap<>();
metricMap.put("utilization", useMetrics.getUtilization());
metricMap.put("saturation", useMetrics.getSaturation());
metricMap.put("errors", useMetrics.getErrors());
metricMap.put("timestamp", useMetrics.getTimestamp());
metricMap.put("additional_metrics", useMetrics.getAdditionalMetrics());
return metricMap;
}
));
}
private Map<String, Object> createSummary(Map<SystemResource, USEMetrics> metrics,
List<Bottleneck> bottlenecks) {
Map<String, Object> summary = new HashMap<>();
// Resource counts by type
Map<ResourceType, Long> resourcesByType = metrics.keySet().stream()
.collect(Collectors.groupingBy(SystemResource::getType, Collectors.counting()));
summary.put("resources_by_type", resourcesByType);
// Bottleneck counts by severity
Map<Severity, Long> bottlenecksBySeverity = bottlenecks.stream()
.collect(Collectors.groupingBy(Bottleneck::getSeverity, Collectors.counting()));
summary.put("bottlenecks_by_severity", bottlenecksBySeverity);
// Average utilization
double avgUtilization = metrics.values().stream()
.mapToDouble(USEMetrics::getUtilization)
.average()
.orElse(0.0);
summary.put("average_utilization", avgUtilization);
// Max utilization
double maxUtilization = metrics.values().stream()
.mapToDouble(USEMetrics::getUtilization)
.max()
.orElse(0.0);
summary.put("max_utilization", maxUtilization);
return summary;
}
}
Testing Framework
USE Method Test Suite
@SpringBootTest
@TestPropertySource(properties = {
"use.monitoring.enabled=true",
"use.monitoring.interval=30"
})
public class USEMethodTest {
@Autowired
private ResourceDiscoverer resourceDiscoverer;
@Autowired
private MetricCollector metricCollector;
@Autowired
private AnalysisEngine analysisEngine;
@Autowired
private USEMonitor useMonitor;
@Test
void testResourceDiscovery() {
List<SystemResource> resources = resourceDiscoverer.discoverResources();
assertFalse(resources.isEmpty(), "Should discover at least one resource");
// Verify CPU resources are discovered
boolean hasCPU = resources.stream()
.anyMatch(r -> r.getType() == ResourceType.CPU);
assertTrue(hasCPU, "Should discover CPU resources");
// Verify memory resources are discovered
boolean hasMemory = resources.stream()
.anyMatch(r -> r.getType() == ResourceType.MEMORY);
assertTrue(hasMemory, "Should discover memory resources");
}
@Test
void testMetricCollection() {
List<SystemResource> resources = resourceDiscoverer.discoverResources();
Map<SystemResource, USEMetrics> metrics = metricCollector.collectMetrics(resources);
assertEquals(resources.size(), metrics.size(),
"Should collect metrics for all discovered resources");
// Verify metrics have valid values
for (USEMetrics metric : metrics.values()) {
assertTrue(metric.getUtilization() >= 0.0 && metric.getUtilization() <= 1.0,
"Utilization should be between 0 and 1");
assertTrue(metric.getSaturation() >= 0.0,
"Saturation should be non-negative");
assertTrue(metric.getErrors() >= 0,
"Errors should be non-negative");
}
}
@Test
void testBottleneckDetection() {
// Create test resources with high utilization
SystemResource highCpuResource = new SystemResource.Builder()
.id("test-cpu-high")
.name("Test CPU High")
.type(ResourceType.CPU)
.build();
USEMetrics highCpuMetrics = new USEMetrics(
highCpuResource, 0.95, 5.0, 0, System.currentTimeMillis(), null);
Map<SystemResource, USEMetrics> testMetrics = new HashMap<>();
testMetrics.put(highCpuResource, highCpuMetrics);
List<Bottleneck> bottlenecks = analysisEngine.analyze(testMetrics);
assertFalse(bottlenecks.isEmpty(), "Should detect bottlenecks for high utilization");
Bottleneck cpuBottleneck = bottlenecks.get(0);
assertEquals(BottleneckType.UTILIZATION, cpuBottleneck.getType());
assertEquals(Severity.CRITICAL, cpuBottleneck.getSeverity());
}
@Test
void testMonitoringLifecycle() throws InterruptedException {
// Test start/stop monitoring
useMonitor.startMonitoring(1); // 1 second interval
Thread.sleep(1500); // Wait for one collection cycle
useMonitor.stopMonitoring();
// Verify no exceptions occurred during monitoring
assertTrue(true, "Monitoring should run without exceptions");
}
@Test
void testErrorConditions() {
// Test with invalid resource
SystemResource invalidResource = new SystemResource.Builder()
.id("invalid")
.name("Invalid Resource")
.type(ResourceType.CPU)
.build();
// This should not throw an exception
USEMetrics metrics = metricCollector.collectResourceMetrics(invalidResource);
assertNotNull(metrics, "Should handle invalid resources gracefully");
}
}
Performance Test with Saturation
@SpringBootTest
public class SaturationTest {
@Autowired
private USEMonitor useMonitor;
@Test
void testUnderLoad() throws InterruptedException {
// Start monitoring
useMonitor.startMonitoring(1);
// Create CPU load
createCPULoad(5000); // 5 seconds of load
// Wait for monitoring to detect
Thread.sleep(3000);
// Stop monitoring
useMonitor.stopMonitoring();
// Verify high CPU utilization was detected
// (This would typically verify through listeners or stored metrics)
}
private void createCPULoad(long durationMs) {
ExecutorService executor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
long endTime = System.currentTimeMillis() + durationMs;
for (int i = 0; i < Runtime.getRuntime().availableProcessors(); i++) {
executor.submit(() -> {
while (System.currentTimeMillis() < endTime) {
// Busy wait to consume CPU
Math.sqrt(Math.random() * 1000000);
}
});
}
executor.shutdown();
try {
executor.awaitTermination(durationMs + 1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
Configuration
Spring Boot Configuration
@Configuration
@EnableConfigurationProperties(USEMonitoringProperties.class)
public class USEMonitoringConfig {
@Bean
@ConditionalOnProperty(name = "use.monitoring.enabled", havingValue = "true")
public ResourceDiscoverer resourceDiscoverer() {
return new ResourceDiscoverer();
}
@Bean
@ConditionalOnProperty(name = "use.monitoring.enabled", havingValue = "true")
public MetricCollector metricCollector(MeterRegistry meterRegistry) {
return new MetricCollector(meterRegistry);
}
@Bean
@ConditionalOnProperty(name = "use.monitoring.enabled", havingValue = "true")
public AnalysisEngine analysisEngine(USEMonitoringProperties properties) {
return new AnalysisEngine(
properties.getUtilizationThreshold(),
properties.getSaturationThreshold()
);
}
@Bean
@ConditionalOnProperty(name = "use.monitoring.enabled", havingValue = "true")
public USEMonitor useMonitor(ResourceDiscoverer resourceDiscoverer,
MetricCollector metricCollector,
AnalysisEngine analysisEngine) {
return new USEMonitor(resourceDiscoverer, metricCollector, analysisEngine);
}
@Bean
@ConditionalOnProperty(name = "use.monitoring.enabled", havingValue = "true")
public MicrometerUSEIntegration micrometerUSEIntegration(MeterRegistry meterRegistry,
USEMonitor useMonitor) {
return new MicrometerUSEIntegration(meterRegistry, useMonitor);
}
}
@ConfigurationProperties(prefix = "use.monitoring")
@Data
public class USEMonitoringProperties {
private boolean enabled = false;
private long interval = 30; // seconds
private double utilizationThreshold = 0.8;
private double saturationThreshold = 10.0;
private Alerting alerting = new Alerting();
@Data
public static class Alerting {
private boolean enabled = true;
private Severity minimumSeverity = Severity.HIGH;
private List<String> notificationChannels = Arrays.asList("LOG");
}
}
Conclusion
This comprehensive USE Method implementation provides:
- Resource Discovery - Automatic identification of system resources
- Metric Collection - Utilization, saturation, and error metrics for all resources
- Bottleneck Detection - Intelligent analysis of performance bottlenecks
- Real-time Monitoring - Continuous monitoring with configurable intervals
- Alerting - Proactive notification of critical issues
- Integration - Micrometer integration for existing monitoring systems
- REST API - Web interface for metrics and analysis
- Testing - Comprehensive test suite for validation
The USE Method helps systematically identify performance bottlenecks by focusing on the three key metrics for every resource, enabling targeted optimization and capacity planning.