001package com.streamconverter.benchmark;
002
003import java.lang.management.ManagementFactory;
004import java.lang.management.MemoryMXBean;
005import java.lang.management.MemoryUsage;
006import java.time.Instant;
007import java.util.concurrent.Executors;
008import java.util.concurrent.ScheduledExecutorService;
009import java.util.concurrent.TimeUnit;
010import java.util.concurrent.atomic.AtomicLong;
011
012/**
013 * リソース使用量を監視するユーティリティクラス
014 *
015 * <p>メモリ使用量とパフォーマンス指標を測定し、大容量データ処理の効率性を検証します。 5GBデータ/50MBメモリ目標の達成を評価するために使用されます。
016 */
017public class ResourceMonitor {
018  private final MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
019  private final AtomicLong peakMemory = new AtomicLong(0);
020  private final AtomicLong currentMemory = new AtomicLong(0);
021
022  private long startTime;
023  private long startMemory;
024  private long dataSize;
025  private ScheduledExecutorService executor;
026  private volatile boolean monitoring = false;
027
028  /** Creates a new {@code ResourceMonitor}. */
029  public ResourceMonitor() {}
030
031  /**
032   * モニタリングを開始します
033   *
034   * @param expectedDataSize 処理予定のデータサイズ(バイト)
035   */
036  public void start(long expectedDataSize) {
037    this.dataSize = expectedDataSize;
038    this.startTime = System.currentTimeMillis();
039
040    // 初期メモリ状態を記録
041    forceGC();
042    this.startMemory = getCurrentMemoryUsage();
043    this.peakMemory.set(startMemory);
044    this.currentMemory.set(startMemory);
045
046    // メモリ監視スレッドを開始
047    this.executor =
048        Executors.newSingleThreadScheduledExecutor(
049            r -> {
050              Thread t = new Thread(r, "ResourceMonitor-Thread");
051              t.setDaemon(true);
052              return t;
053            });
054
055    this.monitoring = true;
056    this.executor.scheduleAtFixedRate(this::updateMemoryUsage, 50, 50, TimeUnit.MILLISECONDS);
057  }
058
059  /**
060   * モニタリングを停止し、結果を返します
061   *
062   * @return リソース使用量の測定結果
063   */
064  public ResourceUsage stop() {
065    if (!monitoring) {
066      throw new IllegalStateException("Monitor not started");
067    }
068
069    monitoring = false;
070    if (executor != null) {
071      executor.shutdown();
072      try {
073        executor.awaitTermination(1, TimeUnit.SECONDS);
074      } catch (InterruptedException e) {
075        Thread.currentThread().interrupt();
076      }
077    }
078
079    // 最終測定
080    forceGC();
081    long endTime = System.currentTimeMillis();
082    long endMemory = getCurrentMemoryUsage();
083
084    long duration = endTime - startTime;
085    long memoryUsed = peakMemory.get() - startMemory;
086    double throughputMBps =
087        dataSize > 0 && duration > 0 ? (dataSize / 1024.0 / 1024.0) / (duration / 1000.0) : 0.0;
088
089    return new ResourceUsage(
090        duration,
091        memoryUsed,
092        peakMemory.get(),
093        startMemory,
094        endMemory,
095        dataSize,
096        throughputMBps,
097        Instant.now());
098  }
099
100  /** 現在のメモリ使用量を更新 */
101  private void updateMemoryUsage() {
102    if (!monitoring) return;
103
104    long current = getCurrentMemoryUsage();
105    currentMemory.set(current);
106
107    // ピーク値を更新
108    long peak = peakMemory.get();
109    while (current > peak && !peakMemory.compareAndSet(peak, current)) {
110      peak = peakMemory.get();
111    }
112  }
113
114  /**
115   * 現在のヒープメモリ使用量を取得
116   *
117   * @return ヒープメモリ使用量(バイト)
118   */
119  private long getCurrentMemoryUsage() {
120    MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
121    return heapUsage.getUsed();
122  }
123
124  /** ガベージコレクションを強制実行 */
125  private void forceGC() {
126    System.gc();
127    System.gc(); // 2回実行してより確実にクリーンアップ
128    try {
129      Thread.sleep(100); // GC完了待機
130    } catch (InterruptedException e) {
131      Thread.currentThread().interrupt();
132    }
133  }
134
135  /**
136   * 現在のメモリ使用量をMB単位で取得(デバッグ用)
137   *
138   * @return メモリ使用量(MB)
139   */
140  public double getCurrentMemoryMB() {
141    return currentMemory.get() / 1024.0 / 1024.0;
142  }
143
144  /**
145   * ピークメモリ使用量をMB単位で取得(デバッグ用)
146   *
147   * @return ピークメモリ使用量(MB)
148   */
149  public double getPeakMemoryMB() {
150    return peakMemory.get() / 1024.0 / 1024.0;
151  }
152}