001package com.streamconverter.benchmark; 002 003import java.io.PrintWriter; 004import java.io.StringWriter; 005import java.time.LocalDateTime; 006import java.time.format.DateTimeFormatter; 007import java.util.*; 008import java.util.stream.Collectors; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012/** 013 * StreamConverterのパフォーマンス分析とレポート生成 014 * 015 * <p>このクラスは、パフォーマンス計測値から詳細なパフォーマンス分析を行い、 実行時間、メモリ使用量、スループットなどの統計情報を提供します。 016 * 017 * <p>主な機能: 018 * 019 * <ul> 020 * <li>実行時間の統計分析(平均、中央値、分散など) 021 * <li>メモリ使用量の追跡と分析 022 * <li>スループット計算とボトルネック識別 023 * <li>詳細なレポート生成(テキスト、CSV形式) 024 * </ul> 025 */ 026public class PerformanceAnalyzer { 027 028 private static final Logger logger = LoggerFactory.getLogger(PerformanceAnalyzer.class); 029 030 private final List<PerformanceRecord> records = new ArrayList<>(); 031 032 /** Creates a new analyzer. */ 033 public PerformanceAnalyzer() {} 034 035 /** 036 * パフォーマンス記録を追加 037 * 038 * @param testName テスト名 039 * @param commandCount コマンド数 040 * @param totalExecutionTimeMillis 総実行時間(ミリ秒) 041 * @param totalDataSize 処理したデータの総サイズ 042 */ 043 public void addRecord( 044 String testName, int commandCount, long totalExecutionTimeMillis, long totalDataSize) { 045 if (commandCount <= 0) { 046 logger.warn("Invalid command count provided for test: {}", testName); 047 return; 048 } 049 050 PerformanceRecord record = 051 new PerformanceRecord(testName, commandCount, totalExecutionTimeMillis, totalDataSize); 052 records.add(record); 053 054 if (logger.isDebugEnabled()) { 055 logger.debug("Added performance record: {} with {} commands", testName, commandCount); 056 } 057 } 058 059 /** 全記録をクリア */ 060 public void clearRecords() { 061 records.clear(); 062 } 063 064 /** 065 * パフォーマンス統計を取得 066 * 067 * @return パフォーマンス統計情報 068 */ 069 public PerformanceStatistics getStatistics() { 070 if (records.isEmpty()) { 071 logger.warn("No performance records available for statistics"); 072 return new PerformanceStatistics(); 073 } 074 075 return new PerformanceStatistics(records); 076 } 077 078 /** 079 * 詳細なパフォーマンスレポートを生成 080 * 081 * @return パフォーマンスレポートの文字列 082 */ 083 public String generateDetailedReport() { 084 StringWriter writer = new StringWriter(); 085 try (PrintWriter pw = new PrintWriter(writer)) { 086 generateDetailedReport(pw); 087 } 088 return writer.toString(); 089 } 090 091 /** 092 * 詳細なパフォーマンスレポートを指定のPrintWriterに出力 093 * 094 * @param writer 出力先のPrintWriter 095 */ 096 public void generateDetailedReport(PrintWriter writer) { 097 writer.println("StreamConverter Performance Analysis Report"); 098 writer.println("=========================================="); 099 writer.println( 100 "Generated: " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); 101 writer.println(); 102 103 if (records.isEmpty()) { 104 writer.println("No performance data available."); 105 return; 106 } 107 108 PerformanceStatistics stats = getStatistics(); 109 110 // サマリー情報 111 writer.println("EXECUTIVE SUMMARY"); 112 writer.println("-----------------"); 113 writer.printf("Total test runs: %d%n", records.size()); 114 writer.printf("Average execution time: %.2f ms%n", stats.averageExecutionTime); 115 writer.printf("Average throughput: %.2f MB/s%n", stats.averageThroughput); 116 writer.printf("Peak memory usage: %.2f MB%n", stats.peakMemoryUsage / 1024.0 / 1024.0); 117 writer.printf("Total data processed: %.2f MB%n", stats.totalDataProcessed / 1024.0 / 1024.0); 118 writer.println(); 119 120 // 個別テスト結果 121 writer.println("DETAILED TEST RESULTS"); 122 writer.println("---------------------"); 123 124 for (PerformanceRecord record : records) { 125 writer.printf("Test: %s%n", record.testName); 126 writer.printf(" Data size: %.2f MB%n", record.totalDataSize / 1024.0 / 1024.0); 127 writer.printf(" Total execution time: %d ms%n", record.totalExecutionTime); 128 writer.printf(" Throughput: %.2f MB/s%n", record.throughput); 129 writer.printf(" Commands: %d%n", record.commandCount); 130 writer.printf(" Peak memory: %.2f MB%n", record.peakMemoryUsage / 1024.0 / 1024.0); 131 writer.println(); 132 } 133 134 // 統計分析 135 writer.println("STATISTICAL ANALYSIS"); 136 writer.println("--------------------"); 137 writer.printf("Execution time statistics:%n"); 138 writer.printf(" Min: %.2f ms%n", stats.minExecutionTime); 139 writer.printf(" Max: %.2f ms%n", stats.maxExecutionTime); 140 writer.printf(" Median: %.2f ms%n", stats.medianExecutionTime); 141 writer.printf(" Standard deviation: %.2f ms%n", stats.executionTimeStdDev); 142 writer.println(); 143 144 writer.printf("Throughput statistics:%n"); 145 writer.printf(" Min: %.2f MB/s%n", stats.minThroughput); 146 writer.printf(" Max: %.2f MB/s%n", stats.maxThroughput); 147 writer.printf(" Median: %.2f MB/s%n", stats.medianThroughput); 148 writer.printf(" Standard deviation: %.2f MB/s%n", stats.throughputStdDev); 149 writer.println(); 150 151 // パフォーマンス推奨事項 152 generateRecommendations(writer, stats); 153 } 154 155 /** 156 * CSV形式のレポートを生成 157 * 158 * @return CSV形式のパフォーマンスレポート 159 */ 160 public String generateCSVReport() { 161 StringWriter writer = new StringWriter(); 162 try (PrintWriter pw = new PrintWriter(writer)) { 163 generateCSVReport(pw); 164 } 165 return writer.toString(); 166 } 167 168 /** 169 * CSV形式のレポートを指定のPrintWriterに出力 170 * 171 * @param writer 出力先のPrintWriter 172 */ 173 public void generateCSVReport(PrintWriter writer) { 174 // ヘッダー 175 writer.println( 176 "Test Name,Data Size (MB),Total Time (ms),Throughput (MB/s),Commands,Peak Memory (MB)"); 177 178 // データ行 179 for (PerformanceRecord record : records) { 180 writer.printf( 181 "%s,%.2f,%d,%.2f,%d,%.2f%n", 182 record.testName, 183 record.totalDataSize / 1024.0 / 1024.0, 184 record.totalExecutionTime, 185 record.throughput, 186 record.commandCount, 187 record.peakMemoryUsage / 1024.0 / 1024.0); 188 } 189 } 190 191 /** パフォーマンス推奨事項を生成 */ 192 private void generateRecommendations(PrintWriter writer, PerformanceStatistics stats) { 193 writer.println("PERFORMANCE RECOMMENDATIONS"); 194 writer.println("---------------------------"); 195 196 List<String> recommendations = new ArrayList<>(); 197 198 // スループットに基づく推奨事項 199 if (stats.averageThroughput < 10.0) { 200 recommendations.add( 201 "Low throughput detected (< 10 MB/s). Consider optimizing I/O operations or reducing command overhead."); 202 } 203 204 // メモリ使用量に基づく推奨事項 205 double memoryToDataRatio = stats.peakMemoryUsage / (double) stats.totalDataProcessed; 206 if (memoryToDataRatio > 2.0) { 207 recommendations.add( 208 "High memory usage ratio detected (> 2x data size). Consider implementing streaming optimization."); 209 } 210 211 // 実行時間のばらつきに基づく推奨事項 212 double cvExecutionTime = stats.executionTimeStdDev / stats.averageExecutionTime; 213 if (cvExecutionTime > 0.3) { 214 recommendations.add( 215 "High execution time variability detected (CV > 30%). Consider investigating performance consistency."); 216 } 217 218 // コマンド数に基づく推奨事項 219 double avgCommandsPerTest = 220 records.stream().mapToInt(r -> r.commandCount).average().orElse(0.0); 221 if (avgCommandsPerTest > 5) { 222 recommendations.add( 223 "Complex pipelines detected (> 5 commands). Consider pipeline optimization or parallel processing."); 224 } 225 226 if (recommendations.isEmpty()) { 227 writer.println( 228 "No specific performance issues detected. Current performance is within acceptable ranges."); 229 } else { 230 for (int i = 0; i < recommendations.size(); i++) { 231 writer.printf("%d. %s%n", i + 1, recommendations.get(i)); 232 } 233 } 234 writer.println(); 235 } 236 237 /** パフォーマンス記録クラス */ 238 private static class PerformanceRecord { 239 final String testName; 240 final int commandCount; 241 final long totalDataSize; 242 final long totalExecutionTime; 243 final double throughput; 244 final long peakMemoryUsage; 245 246 PerformanceRecord( 247 String testName, int commandCount, long totalExecutionTimeMillis, long totalDataSize) { 248 this.testName = testName; 249 this.commandCount = commandCount; 250 this.totalDataSize = totalDataSize; 251 this.totalExecutionTime = totalExecutionTimeMillis; 252 253 this.throughput = 254 totalExecutionTime > 0 255 ? (totalDataSize / 1024.0 / 1024.0) / (totalExecutionTime / 1000.0) 256 : 0.0; 257 258 this.peakMemoryUsage = 0L; 259 } 260 } 261 262 /** パフォーマンス統計クラス */ 263 public static class PerformanceStatistics { 264 /** 平均実行時間(ミリ秒) */ 265 public final double averageExecutionTime; 266 267 /** 最小実行時間(ミリ秒) */ 268 public final double minExecutionTime; 269 270 /** 最大実行時間(ミリ秒) */ 271 public final double maxExecutionTime; 272 273 /** 中央値実行時間(ミリ秒) */ 274 public final double medianExecutionTime; 275 276 /** 実行時間の標準偏差 */ 277 public final double executionTimeStdDev; 278 279 /** 平均スループット(MB/s) */ 280 public final double averageThroughput; 281 282 /** 最小スループット(MB/s) */ 283 public final double minThroughput; 284 285 /** 最大スループット(MB/s) */ 286 public final double maxThroughput; 287 288 /** 中央値スループット(MB/s) */ 289 public final double medianThroughput; 290 291 /** スループットの標準偏差 */ 292 public final double throughputStdDev; 293 294 /** 処理した総データサイズ(バイト) */ 295 public final long totalDataProcessed; 296 297 /** ピークメモリ使用量(バイト) */ 298 public final long peakMemoryUsage; 299 300 /** 総テスト数 */ 301 public final int totalTests; 302 303 /** 総コマンド数 */ 304 public final int totalCommands; 305 306 PerformanceStatistics() { 307 // 空の統計(データなし) 308 this.averageExecutionTime = 0.0; 309 this.minExecutionTime = 0.0; 310 this.maxExecutionTime = 0.0; 311 this.medianExecutionTime = 0.0; 312 this.executionTimeStdDev = 0.0; 313 314 this.averageThroughput = 0.0; 315 this.minThroughput = 0.0; 316 this.maxThroughput = 0.0; 317 this.medianThroughput = 0.0; 318 this.throughputStdDev = 0.0; 319 320 this.totalDataProcessed = 0L; 321 this.peakMemoryUsage = 0L; 322 323 this.totalTests = 0; 324 this.totalCommands = 0; 325 } 326 327 PerformanceStatistics(List<PerformanceRecord> records) { 328 this.totalTests = records.size(); 329 this.totalCommands = records.stream().mapToInt(r -> r.commandCount).sum(); 330 331 // 実行時間統計 332 List<Double> executionTimes = 333 records.stream() 334 .map(r -> (double) r.totalExecutionTime) 335 .sorted() 336 .collect(Collectors.toList()); 337 338 this.averageExecutionTime = 339 executionTimes.stream().mapToDouble(Double::doubleValue).average().orElse(0.0); 340 341 this.minExecutionTime = executionTimes.isEmpty() ? 0.0 : executionTimes.get(0); 342 this.maxExecutionTime = 343 executionTimes.isEmpty() ? 0.0 : executionTimes.get(executionTimes.size() - 1); 344 this.medianExecutionTime = calculateMedian(executionTimes); 345 this.executionTimeStdDev = calculateStandardDeviation(executionTimes, averageExecutionTime); 346 347 // スループット統計 348 List<Double> throughputs = 349 records.stream().map(r -> r.throughput).sorted().collect(Collectors.toList()); 350 351 this.averageThroughput = 352 throughputs.stream().mapToDouble(Double::doubleValue).average().orElse(0.0); 353 354 this.minThroughput = throughputs.isEmpty() ? 0.0 : throughputs.get(0); 355 this.maxThroughput = throughputs.isEmpty() ? 0.0 : throughputs.get(throughputs.size() - 1); 356 this.medianThroughput = calculateMedian(throughputs); 357 this.throughputStdDev = calculateStandardDeviation(throughputs, averageThroughput); 358 359 // その他の統計 360 this.totalDataProcessed = records.stream().mapToLong(r -> r.totalDataSize).sum(); 361 362 this.peakMemoryUsage = records.stream().mapToLong(r -> r.peakMemoryUsage).max().orElse(0L); 363 } 364 365 private double calculateMedian(List<Double> sortedValues) { 366 if (sortedValues.isEmpty()) { 367 return 0.0; 368 } 369 370 int size = sortedValues.size(); 371 if (size % 2 == 0) { 372 return (sortedValues.get(size / 2 - 1) + sortedValues.get(size / 2)) / 2.0; 373 } else { 374 return sortedValues.get(size / 2); 375 } 376 } 377 378 private double calculateStandardDeviation(List<Double> values, double mean) { 379 if (values.size() <= 1) { 380 return 0.0; 381 } 382 383 double sumSquaredDifferences = 384 values.stream().mapToDouble(value -> Math.pow(value - mean, 2)).sum(); 385 386 return Math.sqrt(sumSquaredDifferences / (values.size() - 1)); 387 } 388 } 389}