// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// This script runs the benchmarks in this directory and uploads the
// results to cloud storage. These results are then ingested by our
// performance measurement system.
//
// The script only works when run on a LUCI builder in the dart-ci project,
// and uploads results to paths within gs://dart-test-results/benchmark-results.
//
// This script is needed to run benchmarks on platforms that we only have
// in our LUCI CI system, not in our performance lab, such as Windows.
// The script is currently only used and tested on Windows.

import 'dart:convert';
import 'dart:io';

void main(List<String> args) async {
  try {
    var results = <Map<String, dynamic>>[];
    for (int i = 0; i < countRuns; ++i) {
      results.addAll(await runBenchmarks(warm: true));
      results.addAll(await runBenchmarks(warm: false));
    }

    if (!Platform.isWindows) {
      print('Analyzer benchmark uploads only run on Windows');
      exit(1);
    }
    var targetResults = [
      for (final result in results)
        {
          'cpu': 'Windows VM',
          'machineType': 'windows-x64',
          'target': 'dart-analysis-server-external',
          ...result,
        },
    ];
    await uploadResults(targetResults);
  } catch (e, st) {
    print('$e\n$st');
  }
}

const countRuns = 2;

Future<List<Map<String, dynamic>>> runBenchmarks({required bool warm}) async {
  var temperature = warm ? 'warm' : 'cold';
  var benchmarkResults = await Process.run(Platform.resolvedExecutable, [
    'pkg/analysis_server/benchmark/benchmarks.dart',
    'run',
    if (warm) 'analysis-server' else 'analysis-server-cold',
  ]);

  print(benchmarkResults.stdout);
  print(benchmarkResults.stderr);
  if (benchmarkResults.exitCode != 0) {
    throw 'Failed to run $temperature benchmarks';
  }
  var result = jsonDecode(
    LineSplitter()
        .convert(benchmarkResults.stdout as String)
        .where((line) => line.startsWith('{"benchmark":'))
        .single,
  );

  return <Map<String, dynamic>>[
    {
      'benchmark': 'analysis-server-$temperature-memory',
      'metric': 'MemoryUse',
      'score': result['result']['analysis-server-$temperature-memory']['bytes'],
    },
    {
      'benchmark': 'analysis-server-$temperature-analysis',
      'metric': 'RunTimeRaw',
      'score':
          result['result']['analysis-server-$temperature-analysis']['micros'],
    },
    if (warm)
      {
        'benchmark': 'analysis-server-edit',
        'metric': 'RunTimeRaw',
        'score': result['result']['analysis-server-edit']['micros'],
      },
    if (warm)
      {
        'benchmark': 'analysis-server-completion',
        'metric': 'RunTimeRaw',
        'score': result['result']['analysis-server-completion']['micros'],
      },
  ];
}

Future<void> uploadResults(List<Map<String, dynamic>> results) async {
  // Create JSON results in the desired format
  // Write results file to cloud storage.
  var tempDir = await Directory.systemTemp.createTemp(
    'analysis-server-benchmarks',
  );
  try {
    var resultsJson = jsonEncode(results);
    var resultsFile = File.fromUri(tempDir.uri.resolve('results.json'));
    resultsFile.writeAsStringSync(resultsJson, flush: true);

    var taskId = Platform.environment['SWARMING_TASK_ID'] ?? 'test_task_id';
    if (taskId == 'test_task_id') {
      print('Benchmark_uploader requires SWARMING_TASK_ID in the environment.');
    }
    var cloudStoragePath =
        'gs://dart-test-results/benchmarks/$taskId/results.json';
    var args = [
      'third_party/gsutil/gsutil',
      'cp',
      resultsFile.path,
      cloudStoragePath,
    ];
    var python = 'python3.exe';
    print('Running $python ${args.join(' ')}');
    var commandResult = await Process.run(python, args);
    var exitCode = commandResult.exitCode;
    print(commandResult.stdout);
    print(commandResult.stderr);
    print('exit code: $exitCode');
    if (exitCode != 0) {
      throw 'Gsutil upload failed. Exit code $exitCode';
    }
  } finally {
    await tempDir.delete(recursive: true);
  }
}
