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}