// Copyright 2018 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.metrics;

import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.google.devtools.build.lib.actions.ActionCompletionEvent;
import com.google.devtools.build.lib.analysis.AnalysisPhaseCompleteEvent;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.ActionSummary;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.MemoryMetrics;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.PackageMetrics;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.TargetMetrics;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.TimingMetrics;
import com.google.devtools.build.lib.buildtool.BuildPrecompleteEvent;
import com.google.devtools.build.lib.metrics.MetricsModule.Options;
import com.google.devtools.build.lib.profiler.Profiler;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;

class MetricsCollector {

  private final CommandEnvironment env;
  private final boolean bepPublishUsedHeapSizePostBuild;
  private final AtomicLong executedActionCount = new AtomicLong();

  private int actionsConstructed;
  private int targetsLoaded;
  private int targetsConfigured;
  private int packagesLoaded;

  MetricsCollector(CommandEnvironment env) {
    this.env = env;
    Options options = env.getOptions().getOptions(Options.class);
    this.bepPublishUsedHeapSizePostBuild =
        options != null && options.bepPublishUsedHeapSizePostBuild;
    env.getEventBus().register(this);
  }

  static void installInEnv(CommandEnvironment env) {
    new MetricsCollector(env);
  }

  @Subscribe
  public void onAnalysisPhaseComplete(AnalysisPhaseCompleteEvent event) {
    actionsConstructed = event.getActionsConstructed();
    targetsLoaded = event.getTargetsLoaded();
    targetsConfigured = event.getTargetsConfigured();
    packagesLoaded = event.getPkgManagerStats().getPackagesLoaded();
  }

  @Subscribe
  @AllowConcurrentEvents
  public void onActionComplete(ActionCompletionEvent event) {
    executedActionCount.incrementAndGet();
  }

  @Subscribe
  public void onBuildComplete(BuildPrecompleteEvent event) {
    env.getEventBus().post(new BuildMetricsEvent(createBuildMetrics()));
  }

  private BuildMetrics createBuildMetrics() {
    BuildMetrics.Builder metrics = BuildMetrics.newBuilder();
    metrics.setActionSummary(createActionSummary());
    metrics.setMemoryMetrics(createMemoryMetrics());
    metrics.setTargetMetrics(createTargetMetrics());
    metrics.setPackageMetrics(createPackageMetrics());
    metrics.setTimingMetrics(createTimingMetrics());
    return metrics.build();
  }

  private ActionSummary createActionSummary() {
    return ActionSummary.newBuilder()
        .setActionsCreated(actionsConstructed)
        .setActionsExecuted(executedActionCount.get())
        .build();
  }

  private MemoryMetrics createMemoryMetrics() {
    MemoryMetrics.Builder memoryMetrics = MemoryMetrics.newBuilder();
    if (bepPublishUsedHeapSizePostBuild) {
      System.gc();
      MemoryMXBean memBean = ManagementFactory.getMemoryMXBean();
      memoryMetrics.setUsedHeapSizePostBuild(memBean.getHeapMemoryUsage().getUsed());
      Optional<Long> peakPostGcHeapSize =
          PostGCMemoryUseRecorder.get().getPeakPostGCHeapMemoryUsed();
      if (peakPostGcHeapSize.isPresent()) {
        memoryMetrics.setPeakPostGcHeapSize(peakPostGcHeapSize.get());
      }
    }
    return memoryMetrics.build();
  }

  private TargetMetrics createTargetMetrics() {
    return TargetMetrics.newBuilder()
        .setTargetsLoaded(targetsLoaded)
        .setTargetsConfigured(targetsConfigured)
        .build();
  }

  private PackageMetrics createPackageMetrics() {
    return PackageMetrics.newBuilder().setPackagesLoaded(packagesLoaded).build();
  }

  private static TimingMetrics createTimingMetrics() {
    TimingMetrics.Builder timingMetricsBuilder = TimingMetrics.newBuilder();
    Duration elapsedWallTime = Profiler.elapsedTimeMaybe();
    if (elapsedWallTime != null) {
      timingMetricsBuilder.setWallTimeInMs(elapsedWallTime.toMillis());
    }
    Duration cpuTime = Profiler.getProcessCpuTimeMaybe();
    if (cpuTime != null) {
      timingMetricsBuilder.setCpuTimeInMs(cpuTime.toMillis());
    }
    return timingMetricsBuilder.build();
  }
}
