001package com.streamconverter.benchmark; 002 003import com.streamconverter.CommandResult; 004import java.io.PrintWriter; 005import java.io.StringWriter; 006import java.time.Instant; 007import java.time.LocalDateTime; 008import java.time.format.DateTimeFormatter; 009import java.util.*; 010import java.util.stream.Collectors; 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014/** 015 * StreamConverterのパフォーマンス分析とレポート生成 016 * 017 * <p>このクラスは、CommandResultから詳細なパフォーマンス分析を行い、 実行時間、メモリ使用量、スループットなどの統計情報を提供します。 018 * 019 * <p>主な機能: 020 * 021 * <ul> 022 * <li>実行時間の統計分析(平均、中央値、分散など) 023 * <li>メモリ使用量の追跡と分析 024 * <li>スループット計算とボトルネック識別 025 * <li>コマンド別パフォーマンス比較 026 * <li>詳細なレポート生成(テキスト、CSV形式) 027 * </ul> 028 */ 029public class PerformanceAnalyzer { 030 031 private static final Logger logger = LoggerFactory.getLogger(PerformanceAnalyzer.class); 032 033 private final List<PerformanceRecord> records = new ArrayList<>(); 034 035 /** Creates a new analyzer. */ 036 public PerformanceAnalyzer() {} 037 038 /** 039 * CommandResult配列からパフォーマンス記録を追加 040 * 041 * @param testName テスト名 042 * @param results コマンド実行結果のリスト 043 * @param totalDataSize 処理したデータの総サイズ 044 */ 045 public void addRecord(String testName, List<CommandResult> results, long totalDataSize) { 046 if (results == null || results.isEmpty()) { 047 logger.warn("Empty results provided for test: {}", testName); 048 return; 049 } 050 051 PerformanceRecord record = new PerformanceRecord(testName, results, totalDataSize); 052 records.add(record); 053 054 if (logger.isDebugEnabled()) { 055 logger.debug("Added performance record: {} with {} commands", testName, results.size()); 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.results.size()); 130 writer.printf(" Peak memory: %.2f MB%n", record.peakMemoryUsage / 1024.0 / 1024.0); 131 132 // コマンド別詳細 133 writer.println(" Command breakdown:"); 134 for (int i = 0; i < record.results.size(); i++) { 135 CommandResult result = record.results.get(i); 136 writer.printf( 137 " [%d] %s: %d ms (%.2f MB/s, %.2f MB memory)%n", 138 i + 1, 139 result.getCommandName(), 140 result.getExecutionTimeMillis(), 141 calculateThroughput(result, record.totalDataSize), 142 result.getInputBytes() / 1024.0 / 1024.0); 143 } 144 writer.println(); 145 } 146 147 // 統計分析 148 writer.println("STATISTICAL ANALYSIS"); 149 writer.println("--------------------"); 150 writer.printf("Execution time statistics:%n"); 151 writer.printf(" Min: %.2f ms%n", stats.minExecutionTime); 152 writer.printf(" Max: %.2f ms%n", stats.maxExecutionTime); 153 writer.printf(" Median: %.2f ms%n", stats.medianExecutionTime); 154 writer.printf(" Standard deviation: %.2f ms%n", stats.executionTimeStdDev); 155 writer.println(); 156 157 writer.printf("Throughput statistics:%n"); 158 writer.printf(" Min: %.2f MB/s%n", stats.minThroughput); 159 writer.printf(" Max: %.2f MB/s%n", stats.maxThroughput); 160 writer.printf(" Median: %.2f MB/s%n", stats.medianThroughput); 161 writer.printf(" Standard deviation: %.2f MB/s%n", stats.throughputStdDev); 162 writer.println(); 163 164 // パフォーマンス推奨事項 165 generateRecommendations(writer, stats); 166 } 167 168 /** 169 * CSV形式のレポートを生成 170 * 171 * @return CSV形式のパフォーマンスレポート 172 */ 173 public String generateCSVReport() { 174 StringWriter writer = new StringWriter(); 175 try (PrintWriter pw = new PrintWriter(writer)) { 176 generateCSVReport(pw); 177 } 178 return writer.toString(); 179 } 180 181 /** 182 * CSV形式のレポートを指定のPrintWriterに出力 183 * 184 * @param writer 出力先のPrintWriter 185 */ 186 public void generateCSVReport(PrintWriter writer) { 187 // ヘッダー 188 writer.println( 189 "Test Name,Data Size (MB),Total Time (ms),Throughput (MB/s),Commands,Peak Memory (MB),Success Rate"); 190 191 // データ行 192 for (PerformanceRecord record : records) { 193 writer.printf( 194 "%s,%.2f,%d,%.2f,%d,%.2f,%.2f%n", 195 record.testName, 196 record.totalDataSize / 1024.0 / 1024.0, 197 record.totalExecutionTime, 198 record.throughput, 199 record.results.size(), 200 record.peakMemoryUsage / 1024.0 / 1024.0, 201 record.successRate * 100.0); 202 } 203 } 204 205 /** パフォーマンス推奨事項を生成 */ 206 private void generateRecommendations(PrintWriter writer, PerformanceStatistics stats) { 207 writer.println("PERFORMANCE RECOMMENDATIONS"); 208 writer.println("---------------------------"); 209 210 List<String> recommendations = new ArrayList<>(); 211 212 // スループットに基づく推奨事項 213 if (stats.averageThroughput < 10.0) { 214 recommendations.add( 215 "Low throughput detected (< 10 MB/s). Consider optimizing I/O operations or reducing command overhead."); 216 } 217 218 // メモリ使用量に基づく推奨事項 219 double memoryToDataRatio = stats.peakMemoryUsage / (double) stats.totalDataProcessed; 220 if (memoryToDataRatio > 2.0) { 221 recommendations.add( 222 "High memory usage ratio detected (> 2x data size). Consider implementing streaming optimization."); 223 } 224 225 // 実行時間のばらつきに基づく推奨事項 226 double cvExecutionTime = stats.executionTimeStdDev / stats.averageExecutionTime; 227 if (cvExecutionTime > 0.3) { 228 recommendations.add( 229 "High execution time variability detected (CV > 30%). Consider investigating performance consistency."); 230 } 231 232 // コマンド数に基づく推奨事項 233 double avgCommandsPerTest = 234 records.stream().mapToInt(r -> r.results.size()).average().orElse(0.0); 235 if (avgCommandsPerTest > 5) { 236 recommendations.add( 237 "Complex pipelines detected (> 5 commands). Consider pipeline optimization or parallel processing."); 238 } 239 240 if (recommendations.isEmpty()) { 241 writer.println( 242 "No specific performance issues detected. Current performance is within acceptable ranges."); 243 } else { 244 for (int i = 0; i < recommendations.size(); i++) { 245 writer.printf("%d. %s%n", i + 1, recommendations.get(i)); 246 } 247 } 248 writer.println(); 249 } 250 251 /** スループットを計算 */ 252 private double calculateThroughput(CommandResult result, long totalDataSize) { 253 if (result.getExecutionTimeMillis() <= 0) { 254 return 0.0; 255 } 256 return (totalDataSize / 1024.0 / 1024.0) / (result.getExecutionTimeMillis() / 1000.0); 257 } 258 259 /** パフォーマンス記録クラス */ 260 private static class PerformanceRecord { 261 final String testName; 262 final List<CommandResult> results; 263 final long totalDataSize; 264 final long totalExecutionTime; 265 final double throughput; 266 final long peakMemoryUsage; 267 final double successRate; 268 269 PerformanceRecord(String testName, List<CommandResult> results, long totalDataSize) { 270 this.testName = testName; 271 this.results = new ArrayList<>(results); 272 this.totalDataSize = totalDataSize; 273 Instant.now(); 274 275 // 統計計算 276 this.totalExecutionTime = 277 results.stream().mapToLong(CommandResult::getExecutionTimeMillis).sum(); 278 279 this.throughput = 280 totalExecutionTime > 0 281 ? (totalDataSize / 1024.0 / 1024.0) / (totalExecutionTime / 1000.0) 282 : 0.0; 283 284 this.peakMemoryUsage = 285 results.stream().mapToLong(r -> r.getInputBytes() + r.getOutputBytes()).max().orElse(0L); 286 287 long successCount = results.stream().mapToLong(r -> r.isSuccess() ? 1 : 0).sum(); 288 this.successRate = results.size() > 0 ? (double) successCount / results.size() : 0.0; 289 } 290 } 291 292 /** パフォーマンス統計クラス */ 293 public static class PerformanceStatistics { 294 /** 平均実行時間(ミリ秒) */ 295 public final double averageExecutionTime; 296 297 /** 最小実行時間(ミリ秒) */ 298 public final double minExecutionTime; 299 300 /** 最大実行時間(ミリ秒) */ 301 public final double maxExecutionTime; 302 303 /** 中央値実行時間(ミリ秒) */ 304 public final double medianExecutionTime; 305 306 /** 実行時間の標準偏差 */ 307 public final double executionTimeStdDev; 308 309 /** 平均スループット(MB/s) */ 310 public final double averageThroughput; 311 312 /** 最小スループット(MB/s) */ 313 public final double minThroughput; 314 315 /** 最大スループット(MB/s) */ 316 public final double maxThroughput; 317 318 /** 中央値スループット(MB/s) */ 319 public final double medianThroughput; 320 321 /** スループットの標準偏差 */ 322 public final double throughputStdDev; 323 324 /** 処理した総データサイズ(バイト) */ 325 public final long totalDataProcessed; 326 327 /** ピークメモリ使用量(バイト) */ 328 public final long peakMemoryUsage; 329 330 /** 全体の成功率 */ 331 public final double overallSuccessRate; 332 333 /** 総テスト数 */ 334 public final int totalTests; 335 336 /** 総コマンド数 */ 337 public final int totalCommands; 338 339 PerformanceStatistics() { 340 // 空の統計(データなし) 341 this.averageExecutionTime = 0.0; 342 this.minExecutionTime = 0.0; 343 this.maxExecutionTime = 0.0; 344 this.medianExecutionTime = 0.0; 345 this.executionTimeStdDev = 0.0; 346 347 this.averageThroughput = 0.0; 348 this.minThroughput = 0.0; 349 this.maxThroughput = 0.0; 350 this.medianThroughput = 0.0; 351 this.throughputStdDev = 0.0; 352 353 this.totalDataProcessed = 0L; 354 this.peakMemoryUsage = 0L; 355 this.overallSuccessRate = 0.0; 356 357 this.totalTests = 0; 358 this.totalCommands = 0; 359 } 360 361 PerformanceStatistics(List<PerformanceRecord> records) { 362 this.totalTests = records.size(); 363 this.totalCommands = records.stream().mapToInt(r -> r.results.size()).sum(); 364 365 // 実行時間統計 366 List<Double> executionTimes = 367 records.stream() 368 .map(r -> (double) r.totalExecutionTime) 369 .sorted() 370 .collect(Collectors.toList()); 371 372 this.averageExecutionTime = 373 executionTimes.stream().mapToDouble(Double::doubleValue).average().orElse(0.0); 374 375 this.minExecutionTime = executionTimes.isEmpty() ? 0.0 : executionTimes.get(0); 376 this.maxExecutionTime = 377 executionTimes.isEmpty() ? 0.0 : executionTimes.get(executionTimes.size() - 1); 378 this.medianExecutionTime = calculateMedian(executionTimes); 379 this.executionTimeStdDev = calculateStandardDeviation(executionTimes, averageExecutionTime); 380 381 // スループット統計 382 List<Double> throughputs = 383 records.stream().map(r -> r.throughput).sorted().collect(Collectors.toList()); 384 385 this.averageThroughput = 386 throughputs.stream().mapToDouble(Double::doubleValue).average().orElse(0.0); 387 388 this.minThroughput = throughputs.isEmpty() ? 0.0 : throughputs.get(0); 389 this.maxThroughput = throughputs.isEmpty() ? 0.0 : throughputs.get(throughputs.size() - 1); 390 this.medianThroughput = calculateMedian(throughputs); 391 this.throughputStdDev = calculateStandardDeviation(throughputs, averageThroughput); 392 393 // その他の統計 394 this.totalDataProcessed = records.stream().mapToLong(r -> r.totalDataSize).sum(); 395 396 this.peakMemoryUsage = records.stream().mapToLong(r -> r.peakMemoryUsage).max().orElse(0L); 397 398 this.overallSuccessRate = 399 records.stream().mapToDouble(r -> r.successRate).average().orElse(0.0); 400 } 401 402 private double calculateMedian(List<Double> sortedValues) { 403 if (sortedValues.isEmpty()) { 404 return 0.0; 405 } 406 407 int size = sortedValues.size(); 408 if (size % 2 == 0) { 409 return (sortedValues.get(size / 2 - 1) + sortedValues.get(size / 2)) / 2.0; 410 } else { 411 return sortedValues.get(size / 2); 412 } 413 } 414 415 private double calculateStandardDeviation(List<Double> values, double mean) { 416 if (values.size() <= 1) { 417 return 0.0; 418 } 419 420 double sumSquaredDifferences = 421 values.stream().mapToDouble(value -> Math.pow(value - mean, 2)).sum(); 422 423 return Math.sqrt(sumSquaredDifferences / (values.size() - 1)); 424 } 425 } 426}