#!/usr/bin/env vpython3
# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Runs benchmarks and generates an orderfile, similar to generate_profile.py.

Example:
Build trichrome_chrome_64_32_bundle and install it on device.

Run this script with:
$ tools/cygprofile/generate_orderfile.py -C out/orderfile-arm64 \
    --android-browser android-trichrome-chrome-64-32-bundle \
    --target-arch arm64

The orderfiles should be located in out/orderfile-arm64/orderfiles.
"""

import argparse
import logging
import pathlib
import sys

import android_profile_tool
import orderfile_shared

_SRC_PATH = pathlib.Path(__file__).resolve().parents[2]
sys.path.append(str(_SRC_PATH / 'third_party/catapult/devil'))
from devil.android import device_utils


def _GetOrderfilesDir(options) -> pathlib.Path:
  if options.isolated_script_test_output:
    orderfiles_dir = options.isolated_script_test_output.parent / 'orderfiles'
  else:
    orderfiles_dir = options.out_dir / 'orderfiles'
  orderfiles_dir.mkdir(exist_ok=True)
  return orderfiles_dir


def _GetOrderfileFilename(options):
  """Gets the path to the architecture-specific orderfile."""
  arch = options.arch
  orderfiles_dir = _GetOrderfilesDir(options)
  return str(orderfiles_dir / f'orderfile.{arch}.out')


def _GetUnpatchedOrderfileFilename(options):
  """Gets the path to the architecture-specific unpatched orderfile."""
  arch = options.arch
  orderfiles_dir = _GetOrderfilesDir(options)
  return str(orderfiles_dir / f'unpatched_orderfile.{arch}')


def CreateArgumentParser():
  """Creates and returns the argument parser."""
  parser = argparse.ArgumentParser()
  orderfile_shared.AddCommonArguments(parser)

  # Essential arguments for profiling and processing:
  parser.add_argument(
    '--android-browser',
    required=True,
    help='Browser string to pass to run_benchmark.',
  )
  parser.add_argument(
    '-C',
    '--out-dir',
    type=pathlib.Path,
    required=True,
    help='Path to the output directory (e.g. out/Release).',
  )
  # The following two are bot-specific args.
  parser.add_argument(
    '--isolated-script-test-output',
    type=pathlib.Path,
    help='Output.json file that the script can write to.',
  )
  parser.add_argument(
    '--isolated-script-test-perf-output',
    help='Deprecated and ignored, but bots pass it.',
  )

  return parser


def GenerateOrderfile(options, device):
  """Generates an orderfile for a given device."""
  host_profile_root = options.out_dir / 'profile_data'
  profiler = android_profile_tool.AndroidProfileTool(
    str(host_profile_root),
    device,
    debug=options.streamline_for_debugging,
    verbosity=options.verbosity,
  )

  lib_chrome_so = orderfile_shared.GetLibchromeSoPath(
    options.out_dir, options.arch, options.profile_webview
  )
  try:
    if options.profile_webview:
      if options.arch == 'arm64':
        webview_target = 'system_webview_64_32_apk'
        apk_name = 'SystemWebView6432.apk'
      else:
        webview_target = 'system_webview_apk'
        apk_name = 'SystemWebView.apk'
      webview_installer_path = str(options.out_dir / 'bin' / webview_target)
      apk_or_browser = str(options.out_dir / 'apks' / apk_name)
    else:
      apk_or_browser = options.android_browser
      webview_installer_path = None
    files = orderfile_shared.CollectProfiles(
      profiler,
      options.profile_webview,
      options.arch,
      apk_or_browser,
      str(options.out_dir),
      webview_installer_path,
    )
    ordered_symbols, _ = orderfile_shared.ProcessProfiles(files, lib_chrome_so)
    with open(_GetUnpatchedOrderfileFilename(options), 'w') as orderfile:
      orderfile.write('\n'.join(ordered_symbols))
  finally:
    if not options.save_profile_data:
      profiler.Cleanup()
    logging.getLogger().setLevel(logging.INFO)

  orderfile_shared.AddDummyFunctions(
    _GetUnpatchedOrderfileFilename(options), _GetOrderfileFilename(options)
  )


def main():
  parser = CreateArgumentParser()
  options = parser.parse_args()
  if options.verbosity >= 2:
    level = logging.DEBUG
  elif options.verbosity == 1:
    level = logging.INFO
  else:
    level = logging.WARNING
  logging.basicConfig(
    level=level, format='%(levelname).1s %(relativeCreated)6d %(message)s'
  )

  logging.info('Generate Profile Data')

  # Ensure that the output directory is an absolute path.
  options.out_dir = options.out_dir.resolve(strict=True)
  logging.info('Using options.out_dir=%s', options.out_dir)

  devices = device_utils.DeviceUtils.HealthyDevices()
  assert devices, 'Expected at least one connected device'
  device = devices[0]

  logging.getLogger().setLevel(logging.DEBUG)
  GenerateOrderfile(options, device)


if __name__ == '__main__':
  main()
