# SPDX-FileCopyrightText: Copyright (c) 2024-present NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause

# "python benchmark_thunder.py -h" for help.

import argparse
import os
import subprocess
from typing import Iterable

# Switches to the given branch, syncs to head, and returns the short hash of
# the head.
def switch_to(branch: str, sync: bool) -> str:
    subprocess.check_call(f"git fetch origin {branch}", shell=True)

    # `advice.detachedHead=false` silences the detached HEAD warning.
    subprocess.check_call(
        f"git -c advice.detachedHead=false checkout {branch}", shell=True
    )

    if sync:
        subprocess.check_call(f"git reset --hard origin/{branch}", shell=True)

    return subprocess.check_output(
        "git rev-parse --short HEAD", shell=True, text=True
    ).strip()

# Sanitize the branch name so it can be used in a filename.
def sanitize_branch_name(branch: str) -> str:
    return branch.replace("/", "_")

class BenchmarkRunner:
    def __init__(self, storage: str, benchmark_filter: str, sync: bool):
        self._storage = storage
        self._benchmark_filter = benchmark_filter
        self._sync = sync

    def run_setting(self, setting: str) -> None:
        thunder_branch, nvfuser_branch = setting.split(":")
        print(
            f"Running Thunder benchmarks for nvFuser branch '{nvfuser_branch}' and Thunder branch '{thunder_branch}'..."
        )

        os.chdir("/opt/pytorch/nvfuser")
        nvfuser_commit = switch_to(nvfuser_branch, self._sync)
        subprocess.check_call("git submodule update --init --recursive", shell=True)
        subprocess.check_call("_bn", shell=True)

        os.chdir("/opt/pytorch/lightning-thunder")
        thunder_commit = switch_to(thunder_branch, self._sync)
        subprocess.check_call("pip install -r requirements/devel.txt", shell=True)

        out_stem = f"thunder_{sanitize_branch_name(thunder_branch)}_{thunder_commit}_nvfuser_{sanitize_branch_name(nvfuser_branch)}_{nvfuser_commit}"
        # Benchmarks fail occasionally for various reasons, e.g. OOM. Use `run`
        # instead of `check_call` to continue with other settings and report only
        # benchmarks that succeeded.
        command = f"pytest thunder/benchmarks/targets.py --color=no -k '{self._benchmark_filter}' --benchmark-save={out_stem}"
        if self._storage:
            command += f" --benchmark-storage={self._storage}"
        subprocess.run(command, shell=True)

    def run_settings(self, settings: Iterable[str]) -> None:
        for setting in settings:
            self.run_setting(setting)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Runs Thunder benchmarks with multiple settings. "
        "It stores benchmark results to the specified storage path, which "
        "can be compared by running `pytest-benchmark --storage <storage_path> "
        "compare <run IDs> --group-by name`."
    )
    parser.add_argument(
        "settings",
        type=str,
        nargs="+",
        help="a list of settings to benchmark. Each setting is in format of <thunder_branch>:<nvfuser_branch>",
    )
    parser.add_argument(
        "--storage",
        type=str,
        default="",
        help="the path to give to `pytest --benchmark-storage`. If not specified, `pytest` will use its default storage path. This flag is useful to save benchmark results out of a transient Docker container.",
    )
    parser.add_argument(
        "--filter",
        type=str,
        default="thunder]",
        help="a benchmark filter that will be passed to `pytest -k <filter>`. By default, the filter is 'thunder]'.",
    )
    parser.add_argument(
        "--sync", action="store_true", help="whether to `git reset origin`"
    )
    args = parser.parse_args()

    runner = BenchmarkRunner(args.storage, args.filter, args.sync)
    runner.run_settings(args.settings)


# SPDX-FileCopyrightText: Copyright (c) 2024-present NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause

# "python benchmark_thunder.py -h" for help.

import argparse
import os
import subprocess
from typing import Iterable

# Switches to the given branch, syncs to head, and returns the short hash of
# the head.
def switch_to(branch: str, sync: bool) -> str:
    subprocess.check_call(f"git fetch origin {branch}", shell=True)

    # `advice.detachedHead=false` silences the detached HEAD warning.
    subprocess.check_call(
        f"git -c advice.detachedHead=false checkout {branch}", shell=True
    )

    if sync:
        subprocess.check_call(f"git reset --hard origin/{branch}", shell=True)

    return subprocess.check_output(
        "git rev-parse --short HEAD", shell=True, text=True
    ).strip()

# Sanitize the branch name so it can be used in a filename.
def sanitize_branch_name(branch: str) -> str:
    return branch.replace("/", "_")

class BenchmarkRunner:
    def __init__(self, storage: str, benchmark_filter: str, sync: bool):
        self._storage = storage
        self._benchmark_filter = benchmark_filter
        self._sync = sync

    def run_setting(self, setting: str) -> None:
        thunder_branch, nvfuser_branch = setting.split(":")
        print(
            f"Running Thunder benchmarks for nvFuser branch '{nvfuser_branch}' and Thunder branch '{thunder_branch}'..."
        )

        os.chdir("/opt/pytorch/nvfuser")


# Switches to the given branch, syncs to head, and returns the short hash of
# the head.
def switch_to(branch: str, sync: bool) -> str:
    subprocess.check_call(f"git fetch origin {branch}", shell=True)

    # `advice.detachedHead=false` silences the detached HEAD warning.
    subprocess.check_call(
        f"git -c advice.detachedHead=false checkout {branch}", shell=True
    )

    if sync:
        subprocess.check_call(f"git reset --hard origin/{branch}", shell=True)

    return subprocess.check_output(
        "git rev-parse --short HEAD", shell=True, text=True
    ).strip()

# Sanitize the branch name so it can be used in a filename.
def sanitize_branch_name(branch: str) -> str:
    return branch.replace("/", "_")

class BenchmarkRunner:
    def __init__(self, storage: str, benchmark_filter: str, sync: bool):
        self._storage = storage
        self._benchmark_filter = benchmark_filter
        self._sync = sync

    def run_setting(self, setting: str) -> None:
        thunder_branch, nvfuser_branch = setting.split(":")
        print(
            f"Running Thunder benchmarks for nvFuser branch '{nvfuser_branch}' and Thunder branch '{thunder_branch}'..."
        )

        os.chdir("/opt/pytorch/nvfuser")
        nvfuser_commit = switch_to(nvfuser_branch, self._sync)
        subprocess.check_call("git submodule update --init --recursive", shell=True)
        subprocess.check_call("_bn", shell=True)

        os.chdir("/opt/pytorch/lightning-thunder")


BenchmarkRunner

    subprocess.check_call(f"git fetch origin {branch}", shell=True)

    # `advice.detachedHead=false` silences the detached HEAD warning.
    subprocess.check_call(
        f"git -c advice.detachedHead=false checkout {branch}", shell=True
    )

    if sync:
        subprocess.check_call(f"git reset --hard origin/{branch}", shell=True)

    return subprocess.check_output(
        "git rev-parse --short HEAD", shell=True, text=True
    ).strip()

# Sanitize the branch name so it can be used in a filename.
def sanitize_branch_name(branch: str) -> str:
    return branch.replace("/", "_")

class BenchmarkRunner:
    def __init__(self, storage: str, benchmark_filter: str, sync: bool):
        self._storage = storage
        self._benchmark_filter = benchmark_filter
        self._sync = sync

    def run_setting(self, setting: str) -> None:
        thunder_branch, nvfuser_branch = setting.split(":")
        print(
            f"Running Thunder benchmarks for nvFuser branch '{nvfuser_branch}' and Thunder branch '{thunder_branch}'..."
        )

        os.chdir("/opt/pytorch/nvfuser")
        nvfuser_commit = switch_to(nvfuser_branch, self._sync)
        subprocess.check_call("git submodule update --init --recursive", shell=True)
        subprocess.check_call("_bn", shell=True)

        os.chdir("/opt/pytorch/lightning-thunder")
        thunder_commit = switch_to(thunder_branch, self._sync)
        subprocess.check_call("pip install -r requirements/devel.txt", shell=True)

        out_stem = f"thunder_{sanitize_branch_name(thunder_branch)}_{thunder_commit}_nvfuser_{sanitize_branch_name(nvfuser_branch)}_{nvfuser_commit}"
        # Benchmarks fail occasionally for various reasons, e.g. OOM. Use `run`
        # instead of `check_call` to continue with other settings and report only
        # benchmarks that succeeded.
        command = f"pytest thunder/benchmarks/targets.py --color=no -k '{self._benchmark_filter}' --benchmark-save={out_stem}"
        if self._storage:
            command += f" --benchmark-storage={self._storage}"
        subprocess.run(command, shell=True)

    def run_settings(self, settings: Iterable[str]) -> None:
        for setting in settings:
            self.run_setting(setting)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Runs Thunder benchmarks with multiple settings. "
        "It stores benchmark results to the specified storage path, which "
        "can be compared by running `pytest-benchmark --storage <storage_path> "
        "compare <run IDs> --group-by name`."
    )
    parser.add_argument(
        "settings",
        type=str,
        nargs="+",
        help="a list of settings to benchmark. Each setting is in format of <thunder_branch>:<nvfuser_branch>",
    )
    parser.add_argument(
        "--storage",
        type=str,
        default="",
        help="the path to give to `pytest --benchmark-storage`. If not specified, `pytest` will use its default storage path. This flag is useful to save benchmark results out of a transient Docker container.",


    # `advice.detachedHead=false` silences the detached HEAD warning.
    subprocess.check_call(
        f"git -c advice.detachedHead=false checkout {branch}", shell=True
    )

    if sync:
        subprocess.check_call(f"git reset --hard origin/{branch}", shell=True)

    return subprocess.check_output(
        "git rev-parse --short HEAD", shell=True, text=True
    ).strip()

# Sanitize the branch name so it can be used in a filename.
def sanitize_branch_name(branch: str) -> str:
    return branch.replace("/", "_")

class BenchmarkRunner:
    def __init__(self, storage: str, benchmark_filter: str, sync: bool):
        self._storage = storage
        self._benchmark_filter = benchmark_filter
        self._sync = sync

    def run_setting(self, setting: str) -> None:
        thunder_branch, nvfuser_branch = setting.split(":")
        print(
            f"Running Thunder benchmarks for nvFuser branch '{nvfuser_branch}' and Thunder branch '{thunder_branch}'..."
        )

        os.chdir("/opt/pytorch/nvfuser")
        nvfuser_commit = switch_to(nvfuser_branch, self._sync)
        subprocess.check_call("git submodule update --init --recursive", shell=True)
        subprocess.check_call("_bn", shell=True)

        os.chdir("/opt/pytorch/lightning-thunder")
        thunder_commit = switch_to(thunder_branch, self._sync)
        subprocess.check_call("pip install -r requirements/devel.txt", shell=True)

        out_stem = f"thunder_{sanitize_branch_name(thunder_branch)}_{thunder_commit}_nvfuser_{sanitize_branch_name(nvfuser_branch)}_{nvfuser_commit}"
        # Benchmarks fail occasionally for various reasons, e.g. OOM. Use `run`
        # instead of `check_call` to continue with other settings and report only
        # benchmarks that succeeded.


    if sync:
        subprocess.check_call(f"git reset --hard origin/{branch}", shell=True)

    return subprocess.check_output(
        "git rev-parse --short HEAD", shell=True, text=True
    ).strip()

# Sanitize the branch name so it can be used in a filename.
def sanitize_branch_name(branch: str) -> str:
    return branch.replace("/", "_")

class BenchmarkRunner:
    def __init__(self, storage: str, benchmark_filter: str, sync: bool):
        self._storage = storage
        self._benchmark_filter = benchmark_filter
        self._sync = sync

    def run_setting(self, setting: str) -> None:
        thunder_branch, nvfuser_branch = setting.split(":")
        print(
            f"Running Thunder benchmarks for nvFuser branch '{nvfuser_branch}' and Thunder branch '{thunder_branch}'..."
        )

        os.chdir("/opt/pytorch/nvfuser")
        nvfuser_commit = switch_to(nvfuser_branch, self._sync)
        subprocess.check_call("git submodule update --init --recursive", shell=True)
        subprocess.check_call("_bn", shell=True)

        os.chdir("/opt/pytorch/lightning-thunder")
        thunder_commit = switch_to(thunder_branch, self._sync)
        subprocess.check_call("pip install -r requirements/devel.txt", shell=True)

        out_stem = f"thunder_{sanitize_branch_name(thunder_branch)}_{thunder_commit}_nvfuser_{sanitize_branch_name(nvfuser_branch)}_{nvfuser_commit}"
        # Benchmarks fail occasionally for various reasons, e.g. OOM. Use `run`
        # instead of `check_call` to continue with other settings and report only
        # benchmarks that succeeded.
        command = f"pytest thunder/benchmarks/targets.py --color=no -k '{self._benchmark_filter}' --benchmark-save={out_stem}"
        if self._storage:
            command += f" --benchmark-storage={self._storage}"
        subprocess.run(command, shell=True)

    def run_settings(self, settings: Iterable[str]) -> None:
        for setting in settings:
            self.run_setting(setting)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Runs Thunder benchmarks with multiple settings. "
        "It stores benchmark results to the specified storage path, which "
        "can be compared by running `pytest-benchmark --storage <storage_path> "
        "compare <run IDs> --group-by name`."
    )
    parser.add_argument(
        "settings",
        type=str,
        nargs="+",
        help="a list of settings to benchmark. Each setting is in format of <thunder_branch>:<nvfuser_branch>",
    )
    parser.add_argument(


        )

        os.chdir("/opt/pytorch/nvfuser")
        nvfuser_commit = switch_to(nvfuser_branch, self._sync)
        subprocess.check_call("git submodule update --init --recursive", shell=True)
        subprocess.check_call("_bn", shell=True)

        os.chdir("/opt/pytorch/lightning-thunder")
        thunder_commit = switch_to(thunder_branch, self._sync)
        subprocess.check_call("pip install -r requirements/devel.txt", shell=True)

        out_stem = f"thunder_{sanitize_branch_name(thunder_branch)}_{thunder_commit}_nvfuser_{sanitize_branch_name(nvfuser_branch)}_{nvfuser_commit}"
        # Benchmarks fail occasionally for various reasons, e.g. OOM. Use `run`
        # instead of `check_call` to continue with other settings and report only
        # benchmarks that succeeded.
        command = f"pytest thunder/benchmarks/targets.py --color=no -k '{self._benchmark_filter}' --benchmark-save={out_stem}"
        if self._storage:
            command += f" --benchmark-storage={self._storage}"
        subprocess.run(command, shell=True)

    def run_settings(self, settings: Iterable[str]) -> None:
        for setting in settings:
            self.run_setting(setting)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Runs Thunder benchmarks with multiple settings. "
        "It stores benchmark results to the specified storage path, which "
        "can be compared by running `pytest-benchmark --storage <storage_path> "
        "compare <run IDs> --group-by name`."
    )
    parser.add_argument(
        "settings",
        type=str,
        nargs="+",
        help="a list of settings to benchmark. Each setting is in format of <thunder_branch>:<nvfuser_branch>",
    )
    parser.add_argument(
        "--storage",
        type=str,
        default="",
        help="the path to give to `pytest --benchmark-storage`. If not specified, `pytest` will use its default storage path. This flag is useful to save benchmark results out of a transient Docker container.",


# SPDX-FileCopyrightText: Copyright (c) 2023-present NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
#
# "compare_benchmark.py -h" for help.

import argparse
from dataclasses import dataclass
import json
import matplotlib.pyplot as plt
import numpy as np
import os
import subprocess
from typing import Iterable

# If `arg` uses a forbidden option, returns that forbidden option; otherwise,
# returns an empty string.
def uses_forbidden_option(arg: str) -> str:
    for forbidden_option in (
        "--benchmark_out",
        "--benchmark_out_format",
        "--benchmark_format",
    ):
        # Depending on which shell, the name of a long option and the value can
        # be split by a space or an =.
        if arg == forbidden_option or arg.startswith(forbidden_option + "="):
            return forbidden_option
    return ""

def sanitize_benchmark_args(args: list[str]) -> list[str]:
    # Skip the leading "--". It's sometimes written before the benchmark args
    # as a convention.
    if args and args[0] == "--":
        args = args[1:]

    for arg in args:
        if forbidden_option := uses_forbidden_option(arg):
            raise ValueError(
                f"{forbidden_option} should be specified by run_benchmark not the user."
            )

    return args

def check_out(branch_or_commit: str) -> None:
    # `advice.detachedHead=false` silences the detached HEAD warning.
    subprocess.check_call(
        f"git -c advice.detachedHead=false checkout {branch_or_commit}", shell=True
    )
    subprocess.check_call("git submodule update --init --recursive", shell=True)

# Runs nvfuser_bench with `benchmark_args` on the given branch or commit. Dumps
# outputs to `out_dir`. Returns `out_dir`/`branch_or_commit`.json that captures
# the benchmark result. If the output already exists, skips benchmarking and
# uses that output. This is useful, for example, when comparing multiple
# contenders to the same base.
def run_benchmark(
    branch_or_commit: str, benchmark_args: list[str], out_dir: str
) -> str:
    benchmark_out = os.path.join(out_dir, branch_or_commit + ".json")
    if os.path.exists(benchmark_out):
        print(f"{benchmark_out} already exists. Skip benchmarking {branch_or_commit}.")
        return benchmark_out

    check_out(branch_or_commit)

    subprocess.check_call("pip install -e .", shell=True)

    benchmark_command = " ".join(
        ["bin/nvfuser_bench"]
        + benchmark_args
        + [f"--benchmark_out={benchmark_out}", "--benchmark_format=json"]
    )
    stdout_path = os.path.join(out_dir, branch_or_commit + ".stdout")
    stderr_path = os.path.join(out_dir, branch_or_commit + ".stderr")
    print("Running benchmark command: " + benchmark_command)
    print(f"Stdout and stderr are redirected to {stdout_path} and {stderr_path}.")
    with open(stdout_path, "w") as stdout, open(stderr_path, "w") as stderr:
        subprocess.check_call(
            benchmark_command, stdout=stdout, stderr=stderr, shell=True
        )
    print(f"The benchmark output is stored in {benchmark_out}.")

    return benchmark_out

@dataclass
class Comparison:
    name: str
    baseline_time: float
    contender_time: float
    # contender_time divided by baseline_time. Smaller is better.
    change: float
    time_unit: str

    def __str__(self):
        return f"Benchmark {self.name} changed from {self.baseline_time}{self.time_unit} to {self.contender_time}{self.time_unit} ({self.change:.2f}x)"

# Compares the two given benchmark results and produces a .json file containing
# the comparison.
def compare(baseline_out: str, contender_out: str, out_dir: str) -> str:
    baseline, _ = os.path.splitext(os.path.basename(baseline_out))
    contender, _ = os.path.splitext(os.path.basename(contender_out))
    comparison_out = os.path.join(out_dir, f"{baseline}_vs_{contender}.json")
    subprocess.check_call(
        f"third_party/benchmark/tools/compare.py -d {comparison_out} benchmarks {baseline_out} {contender_out}",
        shell=True,
    )
    return comparison_out

def load_comparison(comparison_out: str) -> list[Comparison]:
    comparisons: list[Comparison] = []
    with open(comparison_out) as f:
        data = json.loads(f.read())

    for row in data:
        for measurement in row["measurements"]:
            if row["run_type"] != "iteration":
                continue
            comparisons.append(
                Comparison(
                    name=row["name"],
                    baseline_time=measurement["real_time"],
                    contender_time=measurement["real_time_other"],
                    # measurement["time"] means (contender-baseline)/baseline.
                    # That plus 1 is the ratio that we want.
                    change=measurement["time"] + 1,
                    time_unit=row["time_unit"],
                )
            )

    return comparisons

def summarize_comparison(comparisons: Iterable[Comparison], out_dir: str) -> None:
    comparisons = sorted(comparisons, key=lambda x: x.change)

    # Print top improvements and regressions.
    num_tops = 5
    print(f"Top {num_tops} improvements:")
    for i in range(min(num_tops, len(comparisons))):
        comparison = comparisons[i]
        if comparison.change >= 1:
            break
        print(f"  {comparison}")
    print()
    print(f"Top {num_tops} regressions:")
    for i in range(min(num_tops, len(comparisons))):
        comparison = comparisons[-(i + 1)]
        if comparison.change <= 1:
            break
        print(f"  {comparison}")

    # Generate and save the histogram of time changes.
    plt.xlabel("Time change (contender/baseline)")
    plt.ylabel("Number of benchmarks")
    n, bins, patches = plt.hist([comparison.change for comparison in comparisons])
    bin_centers = np.diff(bins) / 2 + bins[:-1]
    for bar, count, x in zip(patches, n, bin_centers):
        plt.text(x, count + 0.5, str(count), ha="center", va="bottom")

    histogram_out = os.path.join(out_dir, "histogram.png")
    plt.savefig(histogram_out)
    print()
    print(f"Saved the histogram of time changes to {histogram_out}.")

def get_head_branch_or_commit() -> str:
    # Return the branch name if possible.
    head_branch_or_commit = subprocess.check_output(
        "git rev-parse --abbrev-ref HEAD", text=True, shell=True
    ).strip()

    if head_branch_or_commit != "HEAD":
        return head_branch_or_commit
    # Head is detached. Return the commit instead.
    return subprocess.check_output("git rev-parse HEAD", text=True, shell=True).strip()

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Runs nvfuser_bench on two commits and compares their results. See https://github.com/NVIDIA/Fuser/wiki/Developer-guide#benchmark-nvfuser for usage."
    )

    parser.add_argument("baseline", type=str, help="The baseline branch or commit")
    parser.add_argument("contender", type=str, help="The contender branch or commit")
    parser.add_argument(
        "out_dir",
        type=str,
        help="The output folder that will contain benchmark results and comparison",
    )
    parser.add_argument(
        "benchmark_args",
        type=str,
        nargs=argparse.REMAINDER,
        help="Arguments passed to nvfuser_bench, e.g., --benchmark_filter=NvFuserScheduler",
    )
    args = parser.parse_args()

    benchmark_args = sanitize_benchmark_args(args.benchmark_args)

    if not os.path.exists(args.out_dir):
        os.makedirs(args.out_dir)

    original_branch_or_commit = get_head_branch_or_commit()
    try:
        baseline_out = run_benchmark(args.baseline, benchmark_args, args.out_dir)
        contender_out = run_benchmark(args.contender, benchmark_args, args.out_dir)
    finally:
        # Check out the original branch even when benchmarking failed.
        check_out(original_branch_or_commit)

    comparison_out = compare(baseline_out, contender_out, args.out_dir)
    comparisons = load_comparison(comparison_out)
    summarize_comparison(comparisons, args.out_dir)


# SPDX-FileCopyrightText: Copyright (c) 2023-present NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
#
# "compare_benchmark.py -h" for help.

import argparse
from dataclasses import dataclass
import json
import matplotlib.pyplot as plt
import numpy as np
import os
import subprocess
from typing import Iterable

# If `arg` uses a forbidden option, returns that forbidden option; otherwise,
# returns an empty string.
def uses_forbidden_option(arg: str) -> str:
    for forbidden_option in (
        "--benchmark_out",
        "--benchmark_out_format",
        "--benchmark_format",
    ):
        # Depending on which shell, the name of a long option and the value can
        # be split by a space or an =.
        if arg == forbidden_option or arg.startswith(forbidden_option + "="):
            return forbidden_option
    return ""

def sanitize_benchmark_args(args: list[str]) -> list[str]:
    # Skip the leading "--". It's sometimes written before the benchmark args
    # as a convention.
    if args and args[0] == "--":
        args = args[1:]

    for arg in args:
        if forbidden_option := uses_forbidden_option(arg):
            raise ValueError(
                f"{forbidden_option} should be specified by run_benchmark not the user."
            )

    return args

def check_out(branch_or_commit: str) -> None:
    # `advice.detachedHead=false` silences the detached HEAD warning.
    subprocess.check_call(


import os
import subprocess
from typing import Iterable

# If `arg` uses a forbidden option, returns that forbidden option; otherwise,
# returns an empty string.
def uses_forbidden_option(arg: str) -> str:
    for forbidden_option in (
        "--benchmark_out",
        "--benchmark_out_format",
        "--benchmark_format",
    ):
        # Depending on which shell, the name of a long option and the value can
        # be split by a space or an =.
        if arg == forbidden_option or arg.startswith(forbidden_option + "="):
            return forbidden_option
    return ""

def sanitize_benchmark_args(args: list[str]) -> list[str]:
    # Skip the leading "--". It's sometimes written before the benchmark args
    # as a convention.
    if args and args[0] == "--":
        args = args[1:]

    for arg in args:
        if forbidden_option := uses_forbidden_option(arg):
            raise ValueError(
                f"{forbidden_option} should be specified by run_benchmark not the user."
            )

    return args

def check_out(branch_or_commit: str) -> None:
    # `advice.detachedHead=false` silences the detached HEAD warning.
    subprocess.check_call(
        f"git -c advice.detachedHead=false checkout {branch_or_commit}", shell=True
    )
    subprocess.check_call("git submodule update --init --recursive", shell=True)

# Runs nvfuser_bench with `benchmark_args` on the given branch or commit. Dumps
# outputs to `out_dir`. Returns `out_dir`/`branch_or_commit`.json that captures
# the benchmark result. If the output already exists, skips benchmarking and
# uses that output. This is useful, for example, when comparing multiple
# contenders to the same base.
def run_benchmark(
    branch_or_commit: str, benchmark_args: list[str], out_dir: str
) -> str:
    benchmark_out = os.path.join(out_dir, branch_or_commit + ".json")
    if os.path.exists(benchmark_out):


        if arg == forbidden_option or arg.startswith(forbidden_option + "="):
            return forbidden_option
    return ""

def sanitize_benchmark_args(args: list[str]) -> list[str]:
    # Skip the leading "--". It's sometimes written before the benchmark args
    # as a convention.
    if args and args[0] == "--":
        args = args[1:]

    for arg in args:
        if forbidden_option := uses_forbidden_option(arg):
            raise ValueError(
                f"{forbidden_option} should be specified by run_benchmark not the user."
            )

    return args

def check_out(branch_or_commit: str) -> None:
    # `advice.detachedHead=false` silences the detached HEAD warning.
    subprocess.check_call(
        f"git -c advice.detachedHead=false checkout {branch_or_commit}", shell=True
    )
    subprocess.check_call("git submodule update --init --recursive", shell=True)

# Runs nvfuser_bench with `benchmark_args` on the given branch or commit. Dumps
# outputs to `out_dir`. Returns `out_dir`/`branch_or_commit`.json that captures
# the benchmark result. If the output already exists, skips benchmarking and
# uses that output. This is useful, for example, when comparing multiple
# contenders to the same base.
def run_benchmark(
    branch_or_commit: str, benchmark_args: list[str], out_dir: str
) -> str:
    benchmark_out = os.path.join(out_dir, branch_or_commit + ".json")
    if os.path.exists(benchmark_out):
        print(f"{benchmark_out} already exists. Skip benchmarking {branch_or_commit}.")
        return benchmark_out

    check_out(branch_or_commit)

    subprocess.check_call("pip install -e .", shell=True)

    benchmark_command = " ".join(


            raise ValueError(
                f"{forbidden_option} should be specified by run_benchmark not the user."
            )

    return args

def check_out(branch_or_commit: str) -> None:
    # `advice.detachedHead=false` silences the detached HEAD warning.
    subprocess.check_call(
        f"git -c advice.detachedHead=false checkout {branch_or_commit}", shell=True
    )
    subprocess.check_call("git submodule update --init --recursive", shell=True)

# Runs nvfuser_bench with `benchmark_args` on the given branch or commit. Dumps
# outputs to `out_dir`. Returns `out_dir`/`branch_or_commit`.json that captures
# the benchmark result. If the output already exists, skips benchmarking and
# uses that output. This is useful, for example, when comparing multiple
# contenders to the same base.
def run_benchmark(
    branch_or_commit: str, benchmark_args: list[str], out_dir: str
) -> str:
    benchmark_out = os.path.join(out_dir, branch_or_commit + ".json")
    if os.path.exists(benchmark_out):
        print(f"{benchmark_out} already exists. Skip benchmarking {branch_or_commit}.")
        return benchmark_out

    check_out(branch_or_commit)

    subprocess.check_call("pip install -e .", shell=True)

    benchmark_command = " ".join(
        ["bin/nvfuser_bench"]
        + benchmark_args
        + [f"--benchmark_out={benchmark_out}", "--benchmark_format=json"]
    )
    stdout_path = os.path.join(out_dir, branch_or_commit + ".stdout")
    stderr_path = os.path.join(out_dir, branch_or_commit + ".stderr")
    print("Running benchmark command: " + benchmark_command)
    print(f"Stdout and stderr are redirected to {stdout_path} and {stderr_path}.")
    with open(stdout_path, "w") as stdout, open(stderr_path, "w") as stderr:
        subprocess.check_call(
            benchmark_command, stdout=stdout, stderr=stderr, shell=True
        )
    print(f"The benchmark output is stored in {benchmark_out}.")

    return benchmark_out

@dataclass
class Comparison:
    name: str
    baseline_time: float
    contender_time: float
    # contender_time divided by baseline_time. Smaller is better.
    change: float
    time_unit: str

    def __str__(self):
        return f"Benchmark {self.name} changed from {self.baseline_time}{self.time_unit} to {self.contender_time}{self.time_unit} ({self.change:.2f}x)"

# Compares the two given benchmark results and produces a .json file containing
# the comparison.
def compare(baseline_out: str, contender_out: str, out_dir: str) -> str:
    baseline, _ = os.path.splitext(os.path.basename(baseline_out))
    contender, _ = os.path.splitext(os.path.basename(contender_out))


Comparison

    benchmark_command = " ".join(
        ["bin/nvfuser_bench"]
        + benchmark_args
        + [f"--benchmark_out={benchmark_out}", "--benchmark_format=json"]
    )
    stdout_path = os.path.join(out_dir, branch_or_commit + ".stdout")
    stderr_path = os.path.join(out_dir, branch_or_commit + ".stderr")
    print("Running benchmark command: " + benchmark_command)
    print(f"Stdout and stderr are redirected to {stdout_path} and {stderr_path}.")
    with open(stdout_path, "w") as stdout, open(stderr_path, "w") as stderr:
        subprocess.check_call(
            benchmark_command, stdout=stdout, stderr=stderr, shell=True
        )
    print(f"The benchmark output is stored in {benchmark_out}.")

    return benchmark_out

@dataclass
class Comparison:
    name: str
    baseline_time: float
    contender_time: float
    # contender_time divided by baseline_time. Smaller is better.
    change: float
    time_unit: str

    def __str__(self):
        return f"Benchmark {self.name} changed from {self.baseline_time}{self.time_unit} to {self.contender_time}{self.time_unit} ({self.change:.2f}x)"

# Compares the two given benchmark results and produces a .json file containing
# the comparison.
def compare(baseline_out: str, contender_out: str, out_dir: str) -> str:
    baseline, _ = os.path.splitext(os.path.basename(baseline_out))
    contender, _ = os.path.splitext(os.path.basename(contender_out))
    comparison_out = os.path.join(out_dir, f"{baseline}_vs_{contender}.json")
    subprocess.check_call(
        f"third_party/benchmark/tools/compare.py -d {comparison_out} benchmarks {baseline_out} {contender_out}",
        shell=True,
    )
    return comparison_out

def load_comparison(comparison_out: str) -> list[Comparison]:
    comparisons: list[Comparison] = []
    with open(comparison_out) as f:
        data = json.loads(f.read())


compare

    print(f"The benchmark output is stored in {benchmark_out}.")

    return benchmark_out

@dataclass
class Comparison:
    name: str
    baseline_time: float
    contender_time: float
    # contender_time divided by baseline_time. Smaller is better.
    change: float
    time_unit: str

    def __str__(self):
        return f"Benchmark {self.name} changed from {self.baseline_time}{self.time_unit} to {self.contender_time}{self.time_unit} ({self.change:.2f}x)"

# Compares the two given benchmark results and produces a .json file containing
# the comparison.
def compare(baseline_out: str, contender_out: str, out_dir: str) -> str:
    baseline, _ = os.path.splitext(os.path.basename(baseline_out))
    contender, _ = os.path.splitext(os.path.basename(contender_out))
    comparison_out = os.path.join(out_dir, f"{baseline}_vs_{contender}.json")
    subprocess.check_call(
        f"third_party/benchmark/tools/compare.py -d {comparison_out} benchmarks {baseline_out} {contender_out}",
        shell=True,
    )
    return comparison_out

def load_comparison(comparison_out: str) -> list[Comparison]:
    comparisons: list[Comparison] = []
    with open(comparison_out) as f:
        data = json.loads(f.read())

    for row in data:
        for measurement in row["measurements"]:
            if row["run_type"] != "iteration":
                continue
            comparisons.append(
                Comparison(
                    name=row["name"],
                    baseline_time=measurement["real_time"],
                    contender_time=measurement["real_time_other"],
                    # measurement["time"] means (contender-baseline)/baseline.
                    # That plus 1 is the ratio that we want.
                    change=measurement["time"] + 1,
                    time_unit=row["time_unit"],


    change: float
    time_unit: str

    def __str__(self):
        return f"Benchmark {self.name} changed from {self.baseline_time}{self.time_unit} to {self.contender_time}{self.time_unit} ({self.change:.2f}x)"

# Compares the two given benchmark results and produces a .json file containing
# the comparison.
def compare(baseline_out: str, contender_out: str, out_dir: str) -> str:
    baseline, _ = os.path.splitext(os.path.basename(baseline_out))
    contender, _ = os.path.splitext(os.path.basename(contender_out))
    comparison_out = os.path.join(out_dir, f"{baseline}_vs_{contender}.json")
    subprocess.check_call(
        f"third_party/benchmark/tools/compare.py -d {comparison_out} benchmarks {baseline_out} {contender_out}",
        shell=True,
    )
    return comparison_out

def load_comparison(comparison_out: str) -> list[Comparison]:
    comparisons: list[Comparison] = []
    with open(comparison_out) as f:
        data = json.loads(f.read())

    for row in data:
        for measurement in row["measurements"]:
            if row["run_type"] != "iteration":
                continue
            comparisons.append(
                Comparison(
                    name=row["name"],
                    baseline_time=measurement["real_time"],
                    contender_time=measurement["real_time_other"],
                    # measurement["time"] means (contender-baseline)/baseline.
                    # That plus 1 is the ratio that we want.
                    change=measurement["time"] + 1,
                    time_unit=row["time_unit"],
                )
            )

    return comparisons

def summarize_comparison(comparisons: Iterable[Comparison], out_dir: str) -> None:
    comparisons = sorted(comparisons, key=lambda x: x.change)

    # Print top improvements and regressions.
    num_tops = 5
    print(f"Top {num_tops} improvements:")
    for i in range(min(num_tops, len(comparisons))):
        comparison = comparisons[i]
        if comparison.change >= 1:
            break
        print(f"  {comparison}")
    print()
    print(f"Top {num_tops} regressions:")
    for i in range(min(num_tops, len(comparisons))):
        comparison = comparisons[-(i + 1)]
        if comparison.change <= 1:
            break
        print(f"  {comparison}")


    for row in data:
        for measurement in row["measurements"]:
            if row["run_type"] != "iteration":
                continue
            comparisons.append(
                Comparison(
                    name=row["name"],
                    baseline_time=measurement["real_time"],
                    contender_time=measurement["real_time_other"],
                    # measurement["time"] means (contender-baseline)/baseline.
                    # That plus 1 is the ratio that we want.
                    change=measurement["time"] + 1,
                    time_unit=row["time_unit"],
                )
            )

    return comparisons

def summarize_comparison(comparisons: Iterable[Comparison], out_dir: str) -> None:
    comparisons = sorted(comparisons, key=lambda x: x.change)

    # Print top improvements and regressions.
    num_tops = 5
    print(f"Top {num_tops} improvements:")
    for i in range(min(num_tops, len(comparisons))):
        comparison = comparisons[i]
        if comparison.change >= 1:
            break
        print(f"  {comparison}")
    print()
    print(f"Top {num_tops} regressions:")
    for i in range(min(num_tops, len(comparisons))):
        comparison = comparisons[-(i + 1)]
        if comparison.change <= 1:
            break
        print(f"  {comparison}")

    # Generate and save the histogram of time changes.
    plt.xlabel("Time change (contender/baseline)")
    plt.ylabel("Number of benchmarks")
    n, bins, patches = plt.hist([comparison.change for comparison in comparisons])
    bin_centers = np.diff(bins) / 2 + bins[:-1]
    for bar, count, x in zip(patches, n, bin_centers):
        plt.text(x, count + 0.5, str(count), ha="center", va="bottom")

    histogram_out = os.path.join(out_dir, "histogram.png")
    plt.savefig(histogram_out)
    print()
    print(f"Saved the histogram of time changes to {histogram_out}.")

def get_head_branch_or_commit() -> str:
    # Return the branch name if possible.
    head_branch_or_commit = subprocess.check_output(
        "git rev-parse --abbrev-ref HEAD", text=True, shell=True
    ).strip()

    if head_branch_or_commit != "HEAD":
        return head_branch_or_commit
    # Head is detached. Return the commit instead.
    return subprocess.check_output("git rev-parse HEAD", text=True, shell=True).strip()

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Runs nvfuser_bench on two commits and compares their results. See https://github.com/NVIDIA/Fuser/wiki/Developer-guide#benchmark-nvfuser for usage."
    )

    parser.add_argument("baseline", type=str, help="The baseline branch or commit")


    for i in range(min(num_tops, len(comparisons))):
        comparison = comparisons[-(i + 1)]
        if comparison.change <= 1:
            break
        print(f"  {comparison}")

    # Generate and save the histogram of time changes.
    plt.xlabel("Time change (contender/baseline)")
    plt.ylabel("Number of benchmarks")
    n, bins, patches = plt.hist([comparison.change for comparison in comparisons])
    bin_centers = np.diff(bins) / 2 + bins[:-1]
    for bar, count, x in zip(patches, n, bin_centers):
        plt.text(x, count + 0.5, str(count), ha="center", va="bottom")

    histogram_out = os.path.join(out_dir, "histogram.png")
    plt.savefig(histogram_out)
    print()
    print(f"Saved the histogram of time changes to {histogram_out}.")

def get_head_branch_or_commit() -> str:
    # Return the branch name if possible.
    head_branch_or_commit = subprocess.check_output(
        "git rev-parse --abbrev-ref HEAD", text=True, shell=True
    ).strip()

    if head_branch_or_commit != "HEAD":
        return head_branch_or_commit
    # Head is detached. Return the commit instead.
    return subprocess.check_output("git rev-parse HEAD", text=True, shell=True).strip()

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Runs nvfuser_bench on two commits and compares their results. See https://github.com/NVIDIA/Fuser/wiki/Developer-guide#benchmark-nvfuser for usage."
    )

    parser.add_argument("baseline", type=str, help="The baseline branch or commit")
    parser.add_argument("contender", type=str, help="The contender branch or commit")
    parser.add_argument(
        "out_dir",
        type=str,
        help="The output folder that will contain benchmark results and comparison",
    )
    parser.add_argument(
        "benchmark_args",
        type=str,
        nargs=argparse.REMAINDER,
        help="Arguments passed to nvfuser_bench, e.g., --benchmark_filter=NvFuserScheduler",
    )


    print("Running benchmark command: " + benchmark_command)
    print(f"Stdout and stderr are redirected to {stdout_path} and {stderr_path}.")
    with open(stdout_path, "w") as stdout, open(stderr_path, "w") as stderr:
        subprocess.check_call(
            benchmark_command, stdout=stdout, stderr=stderr, shell=True
        )
    print(f"The benchmark output is stored in {benchmark_out}.")

    return benchmark_out

@dataclass
class Comparison:
    name: str
    baseline_time: float
    contender_time: float
    # contender_time divided by baseline_time. Smaller is better.
    change: float
    time_unit: str

    def __str__(self):
        return f"Benchmark {self.name} changed from {self.baseline_time}{self.time_unit} to {self.contender_time}{self.time_unit} ({self.change:.2f}x)"

# Compares the two given benchmark results and produces a .json file containing
# the comparison.
def compare(baseline_out: str, contender_out: str, out_dir: str) -> str:
    baseline, _ = os.path.splitext(os.path.basename(baseline_out))
    contender, _ = os.path.splitext(os.path.basename(contender_out))
    comparison_out = os.path.join(out_dir, f"{baseline}_vs_{contender}.json")
    subprocess.check_call(
        f"third_party/benchmark/tools/compare.py -d {comparison_out} benchmarks {baseline_out} {contender_out}",
        shell=True,
    )
    return comparison_out

def load_comparison(comparison_out: str) -> list[Comparison]:
    comparisons: list[Comparison] = []
    with open(comparison_out) as f:
        data = json.loads(f.read())


tools.memory

# SPDX-FileCopyrightText: Copyright (c) 2023-present NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause

def get_available_memory_gb():
    """Returns the available memory in GB."""
    try:
        import psutil

        return psutil.virtual_memory().available / 1024 / 1024 / 1024
    except:  # noqa: E722
        pass

    try:
        with open("/proc/meminfo", "r") as f:
            while True:
                line = f.readline()
                if line.startswith("MemAvailable:"):
                    mem = line.split()[1]
                    assert line.split()[2] == "kB"
                    return int(mem) / 1024 / 1024
                if not line:
                    break
    except:  # noqa: E722
        pass

    return 0


tools.cpp-repro-gen

# SPDX-FileCopyrightText: Copyright (c) 2023-present NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
import ast
import typing
import sys
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--symbolic_sizes", nargs="+", type=int)
cmd_args = parser.parse_args()
symbolic_sizes_index = 0

input = sys.stdin.read()

lines = input.strip().splitlines()

is_aten = False

def parse(l: str):
    ast_ = ast.parse(l)
    assert len(ast_.body) == 1
    return ast_.body[0]

def replace_name(name: str):
    ops_prefix = "fd.ops."
    if name.startswith("T"):
        if is_aten:
            return f"t{name[1:]}"
        else:
            return f"tv{name[1:]}"
    elif name.startswith("S"):
        return f"s{name[1:]}"
    elif name.startswith(ops_prefix):
        op = name[len(ops_prefix) :]
        if is_aten:
            return "at::" + op
        else:
            if op == "cast":
                return "castOp"
            if op == "var_mean":
                return "variance_mean"
            return op
    elif name == "fd.add_output":
        if is_aten:
            return "outputs.push_back"
        else:
            return "fusion->addOutput"
    elif name == "fd.define_scalar":
        if not is_aten:
            return "IrBuilder::create<Val>"
    return name

def tocppstr(x):
    if isinstance(x, bool):
        return "true" if x else "false"
    return str(x)

def list2vector(l: typing.Union[ast.List, list]):
    if isinstance(l, ast.List):
        l = eval(ast.unparse(l))
    l = [tocppstr(x) for x in l]
    l = "{" + ", ".join(l) + "}"
    return ast.Name(l)

def handle_call(l: ast.Call):
    func = ast.Name(replace_name(ast.unparse(l.func)))
    args = handle(l.args)
    keywords = l.keywords
    if func.id == "IrBuilder::create<Val>":
        assert len(args) == 1
        arg = args[0]
        assert isinstance(arg, ast.Constant)
        arg = arg.value
        if len(keywords) > 0:
            keyword = keywords[0]
            assert keyword.arg == "dtype"
            value = ast.unparse(keyword.value).replace(".", "::")
            value = ast.Name(value)
            args.append(value)
        keywords = []
    elif func.id == "fd.define_scalar":
        assert is_aten
        arg = args[0]
        assert isinstance(arg, ast.Constant)
        return arg
    elif func.id == "castOp":
        assert len(args) == 1
        assert len(keywords) == 1
        keyword = keywords[0]
        assert isinstance(keyword, ast.keyword)
        assert keyword.arg == "dtype"
        value = ast.unparse(keyword.value).replace(".", "::")
        value = ast.Name(value)
        args.insert(0, value)
        keywords = []
    elif func.id == "at::cast":
        assert is_aten
        assert len(args) == 1
        assert len(keywords) == 1
        keyword = keywords[0]
        assert isinstance(keyword, ast.keyword)
        assert keyword.arg == "dtype"
        value = ast.unparse(keyword.value).replace("DataType.", "ScalarType::")
        value = ast.Name(value)
        func = ast.Attribute(args[0], "to")
        args[0] = value
        keywords = []
    elif func.id == "view" or func.id == "at::view":
        assert len(args) == 1
        assert len(keywords) == 2

        original_shape = keywords[0]
        assert original_shape.arg == "original_shape"
        original_shape = list2vector(original_shape.value)

        new_shape = keywords[1]
        assert new_shape.arg == "new_shape"
        new_shape = list2vector(new_shape.value)

        if is_aten:
            func = ast.Attribute(args[0], "view")
            args = [new_shape]
        else:
            args.extend([original_shape, new_shape])

        keywords = []
    elif func.id == "fd.define_tensor":
        assert len(keywords) == 3
        assert len(args) == 0

        symbolic_sizes = keywords[0]
        assert symbolic_sizes.arg == "symbolic_sizes"
        symbolic_sizes_val = symbolic_sizes.value
        ndims = len(symbolic_sizes_val.elts)
        symbolic_sizes = list2vector(symbolic_sizes_val)

        contiguous = keywords[1]
        assert contiguous.arg == "contiguous"
        contiguous = contiguous.value
        assert ndims == len(contiguous.elts)
        contiguous = list2vector(contiguous)

        dtype = keywords[2]
        assert dtype.arg == "dtype"
        dtype = ast.unparse(dtype.value)
        if is_aten:
            sizes = symbolic_sizes
            if cmd_args.symbolic_sizes is not None:
                sizes = eval(ast.unparse(symbolic_sizes_val))
                for i, s in enumerate(sizes):
                    if s == -1:
                        global symbolic_sizes_index
                        sizes[i] = cmd_args.symbolic_sizes[symbolic_sizes_index]
                        symbolic_sizes_index += 1
                sizes = list2vector(sizes)
            result = ast.Call(ast.Name("at::randn"), [sizes, ast.Name("options")], [])
            if dtype != "DataType.Float":
                to = ast.Attribute(result, "to")
                result = ast.Call(
                    to, [ast.Name(dtype.replace("DataType.", "ScalarType::"))], []
                )
            if "false" in contiguous.id:
                contig = ast.Attribute(result, "set_contiguous")
                result = ast.Call(contig, [contiguous], [])
            return result
        else:
            builder = ast.Name("TensorViewBuilder()")
            ndims_call = ast.Call(
                ast.Attribute(builder, "ndims"), [ast.Constant(ndims)], []
            )
            shape_call = ast.Call(
                ast.Attribute(ndims_call, "shape"), [symbolic_sizes], []
            )
            contig_call = ast.Call(
                ast.Attribute(shape_call, "contiguity"), [contiguous], []
            )
            dtype_call = ast.Call(
                ast.Attribute(contig_call, "dtype"),
                [ast.Name(dtype.replace(".", "::"))],
                [],
            )
            build_call = ast.Call(ast.Attribute(dtype_call, "build"), [], [])
            return build_call
    elif func.id == "broadcast_in_dim" or func.id == "at::broadcast_in_dim":
        assert len(keywords) == 2
        assert len(args) == 1

        output_shape = keywords[0]
        assert output_shape.arg == "output_shape"
        output_shape = output_shape.value
        output_shape = eval(ast.unparse(output_shape))

        broadcast_dims = keywords[1]
        assert broadcast_dims.arg == "broadcast_dims"
        broadcast_dims = broadcast_dims.value
        broadcast_dims = eval(ast.unparse(broadcast_dims))

        n_out_dims = len(output_shape)

        is_broadcast = [True] * n_out_dims
        for orig_dim in broadcast_dims:
            is_broadcast[orig_dim] = False

        if is_aten:
            result = args[0]
            for i, b in enumerate(is_broadcast):
                if b:
                    result = ast.Call(
                        ast.Attribute(result, "unsqueeze"), [ast.Constant(i)], []
                    )
            result = ast.Call(
                ast.Attribute(result, "expand"), [list2vector(output_shape)], []
            )
        else:
            result = ast.Call(
                ast.Name("broadcast"), [args[0], list2vector(is_broadcast)], []
            )
            result = ast.Call(
                ast.Name("expand"),
                [
                    result,
                    list2vector([f"IrBuilder::create<Val>({x})" for x in output_shape]),
                ],
                [],
            )

        return result
    elif func.id == "at::var_mean" or func.id == "variance_mean":
        assert len(args) == 1
        assert len(keywords) == 3

        axes = keywords[0]
        assert isinstance(axes, ast.keyword)
        assert axes.arg == "axes"
        axes = list2vector(axes.value)

        correction = keywords[1]
        assert isinstance(correction, ast.keyword)
        assert correction.arg == "correction"
        correction = correction.value

        keepdim = keywords[2]
        assert isinstance(keepdim, ast.keyword)
        assert keepdim.arg == "keepdim"
        keepdim = keepdim.value
        assert isinstance(keepdim, ast.Constant)
        keepdim = ast.Name(tocppstr(keepdim.value))

        args.extend([axes, correction, keepdim])
        keywords = []

    return ast.Call(func, args, keywords)

def handle(l):
    if isinstance(l, list):
        result = []
        for item in l:
            result.append(handle(item))
        return result
    elif isinstance(l, ast.Assign):
        create = ast.Assign(handle(l.targets), handle(l.value), l.type_comment)
        if len(create.targets) == 1 and isinstance(create.targets[0], ast.Tuple):
            targets = create.targets[0].elts
            tuple_name = ast.Name("_".join([x.id for x in targets]))
            create.targets[0] = tuple_name
            result = [create]
            for i, n in enumerate(targets):
                value = ast.Call(ast.Name(f"std::get<{i}>"), [tuple_name], [])
                result.append(ast.Assign([n], value))
            return result
        is_define_tensor = (
            isinstance(l.value, ast.Call)
            and ast.unparse(l.value.func) == "fd.define_tensor"
        )
        if is_define_tensor:
            if is_aten:
                func = "inputs.push_back"
            else:
                func = "fusion->addInput"
            assert len(create.targets) == 1
            add = ast.Call(ast.Name(func), [create.targets[0]], [])
            return [create, add]
        else:
            return create
    elif isinstance(l, ast.Name):
        return ast.Name(replace_name(l.id), l.ctx)
    elif isinstance(l, ast.Call):
        return handle_call(l)
    elif isinstance(l, ast.Expr):
        return ast.Expr(handle(l.value))
    elif isinstance(l, ast.Tuple):
        return ast.Tuple([handle(x) for x in l.elts])
    return l

def ast2str(l):
    if isinstance(l, ast.Assign):
        assert len(l.targets) == 1
        return f"auto {ast2str(l.targets[0])} = {ast2str(l.value)}"
    l.lineno = 0
    return ast.unparse(l)

test_str = """TEST_F(NVFuserTest, FusionGeneratedTest_CUDA) {
  std::unique_ptr<Fusion> fusion_ptr = std::make_unique<Fusion>();
  auto fusion = fusion_ptr.get();
  FusionGuard fg(fusion);

  {
"""

for l in lines:
    l = parse(l)
    l = handle(l)
    if not isinstance(l, list):
        l = [l]
    for x in l:
        test_str += f"    {ast2str(x)};\n"

test_str += """  }

  auto options = at::TensorOptions().dtype(kFloat).device(at::kCUDA, 0);
  std::vector<IValue> inputs;
  std::vector<Tensor> outputs;

  {
"""

is_aten = True

for l in lines:
    l = parse(l)
    l = handle(l)
    if not isinstance(l, list):
        l = [l]
    for x in l:
        test_str += f"    {ast2str(x)};\n"

test_str += """  }

  FusionExecutorCache executor_cache(std::move(fusion_ptr));
  auto cg_outputs = executor_cache.runFusionWithInputs(inputs);
  testValidate(fusion, cg_outputs, inputs, outputs, __LINE__, __FILE__);
}"""

print(test_str)


parse

# SPDX-FileCopyrightText: Copyright (c) 2023-present NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
import ast
import typing
import sys
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--symbolic_sizes", nargs="+", type=int)
cmd_args = parser.parse_args()
symbolic_sizes_index = 0

input = sys.stdin.read()

lines = input.strip().splitlines()

is_aten = False

def parse(l: str):
    ast_ = ast.parse(l)
    assert len(ast_.body) == 1
    return ast_.body[0]

def replace_name(name: str):
    ops_prefix = "fd.ops."
    if name.startswith("T"):
        if is_aten:
            return f"t{name[1:]}"
        else:
            return f"tv{name[1:]}"
    elif name.startswith("S"):
        return f"s{name[1:]}"
    elif name.startswith(ops_prefix):
        op = name[len(ops_prefix) :]
        if is_aten:
            return "at::" + op
        else:
            if op == "cast":
                return "castOp"
            if op == "var_mean":
                return "variance_mean"


import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--symbolic_sizes", nargs="+", type=int)
cmd_args = parser.parse_args()
symbolic_sizes_index = 0

input = sys.stdin.read()

lines = input.strip().splitlines()

is_aten = False

def parse(l: str):
    ast_ = ast.parse(l)
    assert len(ast_.body) == 1
    return ast_.body[0]

def replace_name(name: str):
    ops_prefix = "fd.ops."
    if name.startswith("T"):
        if is_aten:
            return f"t{name[1:]}"
        else:
            return f"tv{name[1:]}"
    elif name.startswith("S"):
        return f"s{name[1:]}"
    elif name.startswith(ops_prefix):
        op = name[len(ops_prefix) :]
        if is_aten:
            return "at::" + op
        else:
            if op == "cast":
                return "castOp"
            if op == "var_mean":
                return "variance_mean"
            return op
    elif name == "fd.add_output":
        if is_aten:
            return "outputs.push_back"
        else:
            return "fusion->addOutput"
    elif name == "fd.define_scalar":
        if not is_aten:
            return "IrBuilder::create<Val>"
    return name

def tocppstr(x):
    if isinstance(x, bool):
        return "true" if x else "false"
    return str(x)

def list2vector(l: typing.Union[ast.List, list]):
    if isinstance(l, ast.List):
        l = eval(ast.unparse(l))
    l = [tocppstr(x) for x in l]
    l = "{" + ", ".join(l) + "}"
    return ast.Name(l)

def handle_call(l: ast.Call):
    func = ast.Name(replace_name(ast.unparse(l.func)))
    args = handle(l.args)
    keywords = l.keywords


tocppstr

        op = name[len(ops_prefix) :]
        if is_aten:
            return "at::" + op
        else:
            if op == "cast":
                return "castOp"
            if op == "var_mean":
                return "variance_mean"
            return op
    elif name == "fd.add_output":
        if is_aten:
            return "outputs.push_back"
        else:
            return "fusion->addOutput"
    elif name == "fd.define_scalar":
        if not is_aten:
            return "IrBuilder::create<Val>"
    return name

def tocppstr(x):
    if isinstance(x, bool):
        return "true" if x else "false"
    return str(x)

def list2vector(l: typing.Union[ast.List, list]):
    if isinstance(l, ast.List):
        l = eval(ast.unparse(l))
    l = [tocppstr(x) for x in l]
    l = "{" + ", ".join(l) + "}"
    return ast.Name(l)

def handle_call(l: ast.Call):
    func = ast.Name(replace_name(ast.unparse(l.func)))
    args = handle(l.args)
    keywords = l.keywords
    if func.id == "IrBuilder::create<Val>":
        assert len(args) == 1
        arg = args[0]
        assert isinstance(arg, ast.Constant)
        arg = arg.value
        if len(keywords) > 0:


            if op == "var_mean":
                return "variance_mean"
            return op
    elif name == "fd.add_output":
        if is_aten:
            return "outputs.push_back"
        else:
            return "fusion->addOutput"
    elif name == "fd.define_scalar":
        if not is_aten:
            return "IrBuilder::create<Val>"
    return name

def tocppstr(x):
    if isinstance(x, bool):
        return "true" if x else "false"
    return str(x)

def list2vector(l: typing.Union[ast.List, list]):
    if isinstance(l, ast.List):
        l = eval(ast.unparse(l))
    l = [tocppstr(x) for x in l]
    l = "{" + ", ".join(l) + "}"
    return ast.Name(l)

def handle_call(l: ast.Call):
    func = ast.Name(replace_name(ast.unparse(l.func)))
    args = handle(l.args)
    keywords = l.keywords
    if func.id == "IrBuilder::create<Val>":
        assert len(args) == 1
        arg = args[0]
        assert isinstance(arg, ast.Constant)
        arg = arg.value
        if len(keywords) > 0:
            keyword = keywords[0]
            assert keyword.arg == "dtype"
            value = ast.unparse(keyword.value).replace(".", "::")
            value = ast.Name(value)
            args.append(value)
        keywords = []
    elif func.id == "fd.define_scalar":
        assert is_aten


    elif name == "fd.define_scalar":
        if not is_aten:
            return "IrBuilder::create<Val>"
    return name

def tocppstr(x):
    if isinstance(x, bool):
        return "true" if x else "false"
    return str(x)

def list2vector(l: typing.Union[ast.List, list]):
    if isinstance(l, ast.List):
        l = eval(ast.unparse(l))
    l = [tocppstr(x) for x in l]
    l = "{" + ", ".join(l) + "}"
    return ast.Name(l)

def handle_call(l: ast.Call):
    func = ast.Name(replace_name(ast.unparse(l.func)))
    args = handle(l.args)
    keywords = l.keywords
    if func.id == "IrBuilder::create<Val>":
        assert len(args) == 1
        arg = args[0]
        assert isinstance(arg, ast.Constant)
        arg = arg.value
        if len(keywords) > 0:
            keyword = keywords[0]
            assert keyword.arg == "dtype"
            value = ast.unparse(keyword.value).replace(".", "::")
            value = ast.Name(value)
            args.append(value)
        keywords = []
    elif func.id == "fd.define_scalar":
        assert is_aten
        arg = args[0]
        assert isinstance(arg, ast.Constant)
        return arg
    elif func.id == "castOp":
        assert len(args) == 1
        assert len(keywords) == 1
        keyword = keywords[0]
        assert isinstance(keyword, ast.keyword)
        assert keyword.arg == "dtype"
        value = ast.unparse(keyword.value).replace(".", "::")
        value = ast.Name(value)
        args.insert(0, value)
        keywords = []
    elif func.id == "at::cast":
        assert is_aten
        assert len(args) == 1
        assert len(keywords) == 1
        keyword = keywords[0]
        assert isinstance(keyword, ast.keyword)
        assert keyword.arg == "dtype"
        value = ast.unparse(keyword.value).replace("DataType.", "ScalarType::")
        value = ast.Name(value)
        func = ast.Attribute(args[0], "to")
        args[0] = value
        keywords = []
    elif func.id == "view" or func.id == "at::view":
        assert len(args) == 1
        assert len(keywords) == 2

        original_shape = keywords[0]
        assert original_shape.arg == "original_shape"
        original_shape = list2vector(original_shape.value)

        new_shape = keywords[1]
        assert new_shape.arg == "new_shape"
        new_shape = list2vector(new_shape.value)

        if is_aten:
            func = ast.Attribute(args[0], "view")
            args = [new_shape]
        else:
            args.extend([original_shape, new_shape])

        keywords = []
    elif func.id == "fd.define_tensor":
        assert len(keywords) == 3
        assert len(args) == 0

        symbolic_sizes = keywords[0]
        assert symbolic_sizes.arg == "symbolic_sizes"
        symbolic_sizes_val = symbolic_sizes.value
        ndims = len(symbolic_sizes_val.elts)
        symbolic_sizes = list2vector(symbolic_sizes_val)

        contiguous = keywords[1]
        assert contiguous.arg == "contiguous"
        contiguous = contiguous.value
        assert ndims == len(contiguous.elts)
        contiguous = list2vector(contiguous)

        dtype = keywords[2]
        assert dtype.arg == "dtype"
        dtype = ast.unparse(dtype.value)
        if is_aten:
            sizes = symbolic_sizes
            if cmd_args.symbolic_sizes is not None:
                sizes = eval(ast.unparse(symbolic_sizes_val))
                for i, s in enumerate(sizes):
                    if s == -1:
                        global symbolic_sizes_index
                        sizes[i] = cmd_args.symbolic_sizes[symbolic_sizes_index]
                        symbolic_sizes_index += 1
                sizes = list2vector(sizes)
            result = ast.Call(ast.Name("at::randn"), [sizes, ast.Name("options")], [])
            if dtype != "DataType.Float":
                to = ast.Attribute(result, "to")
                result = ast.Call(
                    to, [ast.Name(dtype.replace("DataType.", "ScalarType::"))], []
                )
            if "false" in contiguous.id:
                contig = ast.Attribute(result, "set_contiguous")
                result = ast.Call(contig, [contiguous], [])
            return result
        else:
            builder = ast.Name("TensorViewBuilder()")
            ndims_call = ast.Call(
                ast.Attribute(builder, "ndims"), [ast.Constant(ndims)], []
            )
            shape_call = ast.Call(
                ast.Attribute(ndims_call, "shape"), [symbolic_sizes], []
            )
            contig_call = ast.Call(
                ast.Attribute(shape_call, "contiguity"), [contiguous], []
            )
            dtype_call = ast.Call(
                ast.Attribute(contig_call, "dtype"),
                [ast.Name(dtype.replace(".", "::"))],
                [],
            )
            build_call = ast.Call(ast.Attribute(dtype_call, "build"), [], [])
            return build_call
    elif func.id == "broadcast_in_dim" or func.id == "at::broadcast_in_dim":
        assert len(keywords) == 2
        assert len(args) == 1

        output_shape = keywords[0]
        assert output_shape.arg == "output_shape"
        output_shape = output_shape.value
        output_shape = eval(ast.unparse(output_shape))

        broadcast_dims = keywords[1]
        assert broadcast_dims.arg == "broadcast_dims"
        broadcast_dims = broadcast_dims.value
        broadcast_dims = eval(ast.unparse(broadcast_dims))

        n_out_dims = len(output_shape)

        is_broadcast = [True] * n_out_dims
        for orig_dim in broadcast_dims:
            is_broadcast[orig_dim] = False

        if is_aten:
            result = args[0]
            for i, b in enumerate(is_broadcast):
                if b:
                    result = ast.Call(
                        ast.Attribute(result, "unsqueeze"), [ast.Constant(i)], []
                    )
            result = ast.Call(
                ast.Attribute(result, "expand"), [list2vector(output_shape)], []
            )
        else:
            result = ast.Call(
                ast.Name("broadcast"), [args[0], list2vector(is_broadcast)], []
            )
            result = ast.Call(
                ast.Name("expand"),
                [
                    result,
                    list2vector([f"IrBuilder::create<Val>({x})" for x in output_shape]),
                ],
                [],
            )

        return result
    elif func.id == "at::var_mean" or func.id == "variance_mean":
        assert len(args) == 1
        assert len(keywords) == 3

        axes = keywords[0]
        assert isinstance(axes, ast.keyword)
        assert axes.arg == "axes"
        axes = list2vector(axes.value)

        correction = keywords[1]
        assert isinstance(correction, ast.keyword)
        assert correction.arg == "correction"
        correction = correction.value

        keepdim = keywords[2]
        assert isinstance(keepdim, ast.keyword)
        assert keepdim.arg == "keepdim"
        keepdim = keepdim.value
        assert isinstance(keepdim, ast.Constant)
        keepdim = ast.Name(tocppstr(keepdim.value))

        args.extend([axes, correction, keepdim])
        keywords = []

    return ast.Call(func, args, keywords)

def handle(l):
    if isinstance(l, list):
        result = []
        for item in l:
            result.append(handle(item))
        return result
    elif isinstance(l, ast.Assign):
        create = ast.Assign(handle(l.targets), handle(l.value), l.type_comment)
        if len(create.targets) == 1 and isinstance(create.targets[0], ast.Tuple):
            targets = create.targets[0].elts
            tuple_name = ast.Name("_".join([x.id for x in targets]))
            create.targets[0] = tuple_name
            result = [create]
            for i, n in enumerate(targets):
                value = ast.Call(ast.Name(f"std::get<{i}>"), [tuple_name], [])
                result.append(ast.Assign([n], value))
            return result
        is_define_tensor = (


handle

        axes = list2vector(axes.value)

        correction = keywords[1]
        assert isinstance(correction, ast.keyword)
        assert correction.arg == "correction"
        correction = correction.value

        keepdim = keywords[2]
        assert isinstance(keepdim, ast.keyword)
        assert keepdim.arg == "keepdim"
        keepdim = keepdim.value
        assert isinstance(keepdim, ast.Constant)
        keepdim = ast.Name(tocppstr(keepdim.value))

        args.extend([axes, correction, keepdim])
        keywords = []

    return ast.Call(func, args, keywords)

def handle(l):
    if isinstance(l, list):
        result = []
        for item in l:
            result.append(handle(item))
        return result
    elif isinstance(l, ast.Assign):
        create = ast.Assign(handle(l.targets), handle(l.value), l.type_comment)
        if len(create.targets) == 1 and isinstance(create.targets[0], ast.Tuple):
            targets = create.targets[0].elts
            tuple_name = ast.Name("_".join([x.id for x in targets]))
            create.targets[0] = tuple_name
            result = [create]
            for i, n in enumerate(targets):
                value = ast.Call(ast.Name(f"std::get<{i}>"), [tuple_name], [])
                result.append(ast.Assign([n], value))
            return result
        is_define_tensor = (
            isinstance(l.value, ast.Call)
            and ast.unparse(l.value.func) == "fd.define_tensor"
        )
        if is_define_tensor:
            if is_aten:
                func = "inputs.push_back"
            else:
                func = "fusion->addInput"
            assert len(create.targets) == 1
            add = ast.Call(ast.Name(func), [create.targets[0]], [])
            return [create, add]
        else:
            return create
    elif isinstance(l, ast.Name):
        return ast.Name(replace_name(l.id), l.ctx)
    elif isinstance(l, ast.Call):
        return handle_call(l)
    elif isinstance(l, ast.Expr):
        return ast.Expr(handle(l.value))
    elif isinstance(l, ast.Tuple):
        return ast.Tuple([handle(x) for x in l.elts])
    return l

def ast2str(l):
    if isinstance(l, ast.Assign):
        assert len(l.targets) == 1
        return f"auto {ast2str(l.targets[0])} = {ast2str(l.value)}"
    l.lineno = 0
    return ast.unparse(l)

test_str = """TEST_F(NVFuserTest, FusionGeneratedTest_CUDA) {
  std::unique_ptr<Fusion> fusion_ptr = std::make_unique<Fusion>();
  auto fusion = fusion_ptr.get();
  FusionGuard fg(fusion);

  {
"""

for l in lines:
    l = parse(l)


            if is_aten:
                func = "inputs.push_back"
            else:
                func = "fusion->addInput"
            assert len(create.targets) == 1
            add = ast.Call(ast.Name(func), [create.targets[0]], [])
            return [create, add]
        else:
            return create
    elif isinstance(l, ast.Name):
        return ast.Name(replace_name(l.id), l.ctx)
    elif isinstance(l, ast.Call):
        return handle_call(l)
    elif isinstance(l, ast.Expr):
        return ast.Expr(handle(l.value))
    elif isinstance(l, ast.Tuple):
        return ast.Tuple([handle(x) for x in l.elts])
    return l

def ast2str(l):
    if isinstance(l, ast.Assign):
        assert len(l.targets) == 1
        return f"auto {ast2str(l.targets[0])} = {ast2str(l.value)}"
    l.lineno = 0
    return ast.unparse(l)

test_str = """TEST_F(NVFuserTest, FusionGeneratedTest_CUDA) {
  std::unique_ptr<Fusion> fusion_ptr = std::make_unique<Fusion>();
  auto fusion = fusion_ptr.get();
  FusionGuard fg(fusion);

  {
"""

for l in lines:
    l = parse(l)
    l = handle(l)
    if not isinstance(l, list):
        l = [l]
    for x in l:
        test_str += f"    {ast2str(x)};\n"

test_str += """  }


import os

ext_map = {
    "cpp": ("c", "cpp", "cu", "cc", "cuh", "h"),
    "py": ("py",),
    "txt": ("txt",),
}

licence_style_0 = """\
// clang-format off
/*
 * SPDX-FileCopyrightText: Copyright (c) 2023-present NVIDIA CORPORATION & AFFILIATES.
 * All rights reserved.
 * SPDX-License-Identifier: BSD-3-Clause
 */
// clang-format on
"""

licence_style_1 = """\
# SPDX-FileCopyrightText: Copyright (c) 2023-present NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
"""

header_license = {
    "cpp": licence_style_0,
    "py": licence_style_1,
    "txt": licence_style_1,
}

exclude_list = (
    "./tools/update_copyright.py",
    "./examples/sinh_libtorch/main.cpp",
    "./version.txt",
    "./test/main.cpp",
    "Dependencies.cmake",
    "FlatBuffers.cmake",
    # lint adapters are taken from pytorch
    "tools/linter/adapters/black_linter.py",
    "tools/linter/adapters/clangformat_linter.py",
    "tools/linter/adapters/clangtidy_linter.py",
    "tools/linter/adapters/exec_linter.py",
    "tools/linter/adapters/flake8_linter.py",
    "tools/linter/adapters/grep_linter.py",
    "tools/linter/adapters/mypy_linter.py",
    "tools/linter/adapters/newlines_linter.py",
    "tools/linter/adapters/pip_init.py",
    "tools/linter/adapters/README.md",
    "tools/linter/adapters/s3_init_config.json",
    "tools/linter/adapters/s3_init.py",
)

def get_exclusions():
    parent_path = os.path.abspath(os.path.dirname(os.path.abspath(__file__)) + "/..")
    print(parent_path)
    return [os.path.abspath(os.path.join(parent_path, f)) for f in exclude_list]

def has_licence(file_handle, licence_str):
    header_content = file_handle.read(len(licence_str))
    file_handle.seek(0, 0)
    return header_content.startswith(licence_str)

def update_licence(file_handle, licence_str):
    if not has_licence(file_handle, licence_str):
        content = file_handle.read()
        file_handle.seek(0, 0)
        file_handle.write(licence_str + content)
        return True
    return False

def update_files(root_path):
    exclusions = get_exclusions()
    print(exclusions)
    for root, dirs, files in os.walk(root_path):
        for file_name in files:
            abs_file = os.path.abspath(os.path.join(root, file_name))
            print(abs_file)
            if file_name[0] == "." or abs_file in exclusions:
                continue
            file_ext = file_name.split(".")[-1]
            for k, v in ext_map.items():
                if file_ext in v:
                    licence_str = header_license[k]
                    with open(abs_file, "r+") as file_handle:
                        if update_licence(file_handle, licence_str):
                            print("attached licence header to ", abs_file)

if __name__ == "__main__":
    update_files(".")


    "./version.txt",
    "./test/main.cpp",
    "Dependencies.cmake",
    "FlatBuffers.cmake",
    # lint adapters are taken from pytorch
    "tools/linter/adapters/black_linter.py",
    "tools/linter/adapters/clangformat_linter.py",
    "tools/linter/adapters/clangtidy_linter.py",
    "tools/linter/adapters/exec_linter.py",
    "tools/linter/adapters/flake8_linter.py",
    "tools/linter/adapters/grep_linter.py",
    "tools/linter/adapters/mypy_linter.py",
    "tools/linter/adapters/newlines_linter.py",
    "tools/linter/adapters/pip_init.py",
    "tools/linter/adapters/README.md",
    "tools/linter/adapters/s3_init_config.json",
    "tools/linter/adapters/s3_init.py",
)

def get_exclusions():
    parent_path = os.path.abspath(os.path.dirname(os.path.abspath(__file__)) + "/..")
    print(parent_path)
    return [os.path.abspath(os.path.join(parent_path, f)) for f in exclude_list]

def has_licence(file_handle, licence_str):
    header_content = file_handle.read(len(licence_str))
    file_handle.seek(0, 0)
    return header_content.startswith(licence_str)

def update_licence(file_handle, licence_str):
    if not has_licence(file_handle, licence_str):
        content = file_handle.read()
        file_handle.seek(0, 0)
        file_handle.write(licence_str + content)
        return True
    return False

def update_files(root_path):
    exclusions = get_exclusions()
    print(exclusions)


    "tools/linter/adapters/clangformat_linter.py",
    "tools/linter/adapters/clangtidy_linter.py",
    "tools/linter/adapters/exec_linter.py",
    "tools/linter/adapters/flake8_linter.py",
    "tools/linter/adapters/grep_linter.py",
    "tools/linter/adapters/mypy_linter.py",
    "tools/linter/adapters/newlines_linter.py",
    "tools/linter/adapters/pip_init.py",
    "tools/linter/adapters/README.md",
    "tools/linter/adapters/s3_init_config.json",
    "tools/linter/adapters/s3_init.py",
)

def get_exclusions():
    parent_path = os.path.abspath(os.path.dirname(os.path.abspath(__file__)) + "/..")
    print(parent_path)
    return [os.path.abspath(os.path.join(parent_path, f)) for f in exclude_list]

def has_licence(file_handle, licence_str):
    header_content = file_handle.read(len(licence_str))
    file_handle.seek(0, 0)
    return header_content.startswith(licence_str)

def update_licence(file_handle, licence_str):
    if not has_licence(file_handle, licence_str):
        content = file_handle.read()
        file_handle.seek(0, 0)
        file_handle.write(licence_str + content)
        return True
    return False

def update_files(root_path):
    exclusions = get_exclusions()
    print(exclusions)
    for root, dirs, files in os.walk(root_path):
        for file_name in files:
            abs_file = os.path.abspath(os.path.join(root, file_name))
            print(abs_file)
            if file_name[0] == "." or abs_file in exclusions:
                continue


    "tools/linter/adapters/newlines_linter.py",
    "tools/linter/adapters/pip_init.py",
    "tools/linter/adapters/README.md",
    "tools/linter/adapters/s3_init_config.json",
    "tools/linter/adapters/s3_init.py",
)

def get_exclusions():
    parent_path = os.path.abspath(os.path.dirname(os.path.abspath(__file__)) + "/..")
    print(parent_path)
    return [os.path.abspath(os.path.join(parent_path, f)) for f in exclude_list]

def has_licence(file_handle, licence_str):
    header_content = file_handle.read(len(licence_str))
    file_handle.seek(0, 0)
    return header_content.startswith(licence_str)

def update_licence(file_handle, licence_str):
    if not has_licence(file_handle, licence_str):
        content = file_handle.read()
        file_handle.seek(0, 0)
        file_handle.write(licence_str + content)
        return True
    return False

def update_files(root_path):
    exclusions = get_exclusions()
    print(exclusions)
    for root, dirs, files in os.walk(root_path):
        for file_name in files:
            abs_file = os.path.abspath(os.path.join(root, file_name))
            print(abs_file)
            if file_name[0] == "." or abs_file in exclusions:
                continue
            file_ext = file_name.split(".")[-1]
            for k, v in ext_map.items():
                if file_ext in v:
                    licence_str = header_license[k]
                    with open(abs_file, "r+") as file_handle:
                        if update_licence(file_handle, licence_str):
                            print("attached licence header to ", abs_file)


    parent_path = os.path.abspath(os.path.dirname(os.path.abspath(__file__)) + "/..")
    print(parent_path)
    return [os.path.abspath(os.path.join(parent_path, f)) for f in exclude_list]

def has_licence(file_handle, licence_str):
    header_content = file_handle.read(len(licence_str))
    file_handle.seek(0, 0)
    return header_content.startswith(licence_str)

def update_licence(file_handle, licence_str):
    if not has_licence(file_handle, licence_str):
        content = file_handle.read()
        file_handle.seek(0, 0)
        file_handle.write(licence_str + content)
        return True
    return False

def update_files(root_path):
    exclusions = get_exclusions()
    print(exclusions)
    for root, dirs, files in os.walk(root_path):
        for file_name in files:
            abs_file = os.path.abspath(os.path.join(root, file_name))
            print(abs_file)
            if file_name[0] == "." or abs_file in exclusions:
                continue
            file_ext = file_name.split(".")[-1]
            for k, v in ext_map.items():
                if file_ext in v:
                    licence_str = header_license[k]
                    with open(abs_file, "r+") as file_handle:
                        if update_licence(file_handle, licence_str):
                            print("attached licence header to ", abs_file)

if __name__ == "__main__":
    update_files(".")


# SPDX-FileCopyrightText: Copyright (c) 2023-present NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause

# Generates a C++ header files embedding the original input as a string literal

import argparse
import pathlib
from datetime import datetime

arg_parser = argparse.ArgumentParser(
    description="Converts source files to C++ string literals", allow_abbrev=False
)

arg_parser.add_argument("-i", "--input", required=True, help="Input source file")

arg_parser.add_argument(
    "-o", "--output", required=True, help="Name of the generated header file"
)

args = arg_parser.parse_args()

# msvc string literal maximum length 16380
# https://docs.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/compiler-error-c2026?view=msvc-170
MAX_STRING_LITERAL = 1600000000000
# https://docs.microsoft.com/en-us/cpp/c-language/maximum-string-length?view=msvc-170
MAX_STRING_CONCATENATED = 6553500000000

with open(args.input, "r") as fin:
    with open(args.output, "w") as fout:
        literal_name = f"{pathlib.Path(args.input).stem}_cu"
        fout.write(f'// Generated from "{args.input}"\n')
        fout.write(f'// {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}\n\n')
        fout.write("namespace nvfuser_resources {\n\n")
        fout.write(f'constexpr const char* {literal_name} = R"(\n')
        accumulated_chars = 0
        accumulated_chars_per_literal = 0
        for line in fin:
            accumulated_chars = accumulated_chars + len(line) + 1
            accumulated_chars_per_literal = (
                accumulated_chars_per_literal + len(line) + 1
            )
            if accumulated_chars_per_literal >= MAX_STRING_LITERAL:
                fout.write(')"\n')
                fout.write('R"(\n')
                fout.write(line)
                accumulated_chars_per_literal = len(line) + 1
            else:
                fout.write(line)
        fout.write(')";\n')
        fout.write("\n} // namespace nvfuser_resources\n")
        if accumulated_chars >= MAX_STRING_CONCATENATED:
            raise Exception("runtime header file exceeds size limit of 65535 for MSVC")