// Copyright 2020 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 net.starlark.java.eval;

import com.google.common.io.ByteStreams;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.time.Duration;
import net.starlark.java.syntax.FileOptions;
import net.starlark.java.syntax.ParserInput;

/**
 * CpuProfilerTest is a simple integration test that the Starlark CPU profiler emits minimally
 * plausible pprof-compatible output.
 *
 * <p>It runs under Blaze only, because it requires a pprof executable.
 */
public final class CpuProfilerTest {

  private CpuProfilerTest() {} // uninstantiable

  public static void main(String[] args) throws Exception {
    String pprofCmd = args.length == 0 ? "/bin/pprof" : args[0];
    if (!new File(pprofCmd).exists()) {
      throw new AssertionError("no pprof command: " + pprofCmd);
    }

    // This test will fail during profiling of the Java tests
    // because a process (the JVM) can have only one profiler.
    // That's ok; just ignore it.

    // Start writing profile to temporary file.
    File profile = java.io.File.createTempFile("pprof", ".gz", null);
    OutputStream prof = new FileOutputStream(profile);
    boolean success = Starlark.startCpuProfile(prof, Duration.ofMillis(10));

    if (!success) {
      System.err.println("Failed to start cpu profiler");
      System.exit(1);
    }

    // This program consumes about 3s of CPU.
    ParserInput input =
        ParserInput.fromLines(
            "x = [0]", //
            "",
            "def f():",
            "    for i in range(10000):",
            "        g()",
            "",
            "def g():",
            "    list(range(10000))",
            "    int(3)",
            "    sorted(range(10000))",
            "",
            "f()");

    // Execute the workload.
    Module module = Module.create();
    try (Mutability mu = Mutability.create("test")) {
      StarlarkThread thread = new StarlarkThread(mu, StarlarkSemantics.DEFAULT);
      Starlark.execFile(input, FileOptions.DEFAULT, module, thread);
    }

    Starlark.stopCpuProfile();

    // Run pprof -top. Typical output (may vary by pprof release):
    //
    // Type: CPU
    // Time: Jan 21, 2020 at 11:08am (PST)
    // Duration: 3.26s, Total samples = 2640ms (80.97%)
    // Showing nodes accounting for 2640ms, 100% of 2640ms total
    //       flat  flat%   sum%        cum   cum%
    //     1390ms 52.65% 52.65%     1390ms 52.65%  sorted
    //      960ms 36.36% 89.02%      960ms 36.36%  list
    //      220ms  8.33% 97.35%      220ms  8.33%  range
    //       70ms  2.65%   100%       70ms  2.65%  int
    //          0     0%   100%     2640ms   100%  <unknown>
    //          0     0%   100%     2640ms   100%  f
    //          0     0%   100%     2640ms   100%  g

    // Runtime.exec is deprecated at Google but its open-source replacement is not yet available.
    @SuppressWarnings("RuntimeExec")
    Process pprof =
        Runtime.getRuntime()
            .exec(pprofCmd + " -top " + profile, /*envp=*/ new String[0], /*dir=*/ null);
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    ByteStreams.copy(pprof.getInputStream(), out);
    String got = out.toString(); // encoding undefined but unimportant---result is ASCII

    // We'll assert that a few key substrings are present.
    boolean ok = true;
    for (String want : new String[] {"flat%", "sorted", "range"}) {
      if (!got.contains(want)) {
        System.err.println("pprof output does not contain substring: " + got);
        ok = false;
      }
    }
    if (!ok) {
      System.err.println("pprof output:\n" + out);
      System.exit(1);
    }
  }
}
