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}