From dd4324bad40656210efb56081faee4ce9565a237 Mon Sep 17 00:00:00 2001 From: VAN BOSSUYT Nicolas Date: Wed, 17 Jan 2024 10:40:06 +0100 Subject: [PATCH] Profiling is easy now :) --- cutekit/builder.py | 53 +++++++----------------------------- cutekit/const.py | 1 + cutekit/mixins.py | 9 +++--- cutekit/shell.py | 68 +++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 77 insertions(+), 54 deletions(-) diff --git a/cutekit/builder.py b/cutekit/builder.py index b08e48e..9ac0819 100644 --- a/cutekit/builder.py +++ b/cutekit/builder.py @@ -371,50 +371,17 @@ def runCmd(args: cli.Args): os.environ["CK_BUILDDIR"] = product.target.builddir os.environ["CK_COMPONENT"] = product.component.id - if debug: - if debugger == "lldb": - shell.exec( - "lldb", - *(("-o", "b main") if wait else ()), - *("-o", "run"), - str(product.path), - *args.extra, - ) - elif debugger == "gdb": - shell.exec( - "gdb", - *(("-ex", "b main") if wait else ()), - *("-ex", "run"), - str(product.path), - *args.extra, - ) + try: + command = [str(product.path), *args.extra] + + if debug: + shell.debug(command, debugger=debugger, wait=wait) + elif profile: + shell.profile(command) else: - raise RuntimeError(f"Unknown debugger {debugger}") - elif profile: - # Profile the executable using perf - shell.exec( - "perf", - "record", - "-g", - "-o", - "perf.data", - "--call-graph", - "dwarf", - str(object=product.path), - *args.extra, - ) - - # Run this command using subprocess - # perf script -i perf.data | speedscope - - import subprocess - - proc = subprocess.Popen( - ["perf", "script", "-i", "perf.data"], stdout=subprocess.PIPE - ) - subprocess.run(["speedscope", "-"], stdin=proc.stdout) - proc.wait() - else: - shell.exec(str(product.path), *args.extra) + shell.exec(*command) + except Exception as e: + cli.error(e) @cli.command("t", "builder/test", "Run all test targets") diff --git a/cutekit/const.py b/cutekit/const.py index b870651..1d5a457 100644 --- a/cutekit/const.py +++ b/cutekit/const.py @@ -24,6 +24,7 @@ GLOBAL_CK_DIR = os.path.join(os.path.expanduser("~"), ".cutekit") BUILD_DIR = os.path.join(PROJECT_CK_DIR, "build") CACHE_DIR = os.path.join(PROJECT_CK_DIR, "cache") EXTERN_DIR = os.path.join(PROJECT_CK_DIR, "extern") +TMP_DIR = os.path.join(PROJECT_CK_DIR, "tmp") SRC_DIR = "src" META_DIR = "meta" TARGETS_DIR = os.path.join(META_DIR, "targets") diff --git a/cutekit/mixins.py b/cutekit/mixins.py index b88cb9f..0ee7e72 100644 --- a/cutekit/mixins.py +++ b/cutekit/mixins.py @@ -69,14 +69,15 @@ def combineMixins(*mixins: Mixin) -> Mixin: mixins: dict[str, Mixin] = { "cache": mixinCache, "debug": mixinDebug, - "asan": makeMixinSan("address"), + "asan": combineMixins(makeMixinSan("address"), makeMixinSan("leak")), "msan": makeMixinSan("memory"), "tsan": makeMixinSan("thread"), "ubsan": makeMixinSan("undefined"), - "sanitize": combineMixins( + "lsan": makeMixinSan("leak"), + "san": combineMixins( makeMixinSan("address"), - makeMixinSan("memory"), - makeMixinSan("thread"), + makeMixinSan("undefined"), + makeMixinSan("leak"), ), "tune": makeMixinTune("native"), "release": combineMixins( diff --git a/cutekit/shell.py b/cutekit/shell.py index 9c93084..da47eaf 100644 --- a/cutekit/shell.py +++ b/cutekit/shell.py @@ -12,6 +12,7 @@ import logging import tempfile import dataclasses as dt +from pathlib import Path from typing import Optional from . import const @@ -134,6 +135,7 @@ def wget(url: str, path: Optional[str] = None) -> str: def exec(*args: str, quiet: bool = False, cwd: Optional[str] = None) -> bool: _logger.debug(f"Executing {args}") + cmdName = Path(args[0]).name try: proc = subprocess.run( @@ -156,34 +158,86 @@ def exec(*args: str, quiet: bool = False, cwd: Optional[str] = None) -> bool: raise RuntimeError(f"{args[0]}: Command not found") except KeyboardInterrupt: - raise RuntimeError(f"{args[0]}: Interrupted") + raise RuntimeError(f"{cmdName}: Interrupted") if proc.returncode == -signal.SIGSEGV: - raise RuntimeError(f"{args[0]}: Segmentation fault") + raise RuntimeError(f"{cmdName}: Segmentation fault") if proc.returncode != 0: - raise RuntimeError(f"{args[0]}: Process exited with code {proc.returncode}") + raise RuntimeError(f"{cmdName}: Process exited with code {proc.returncode}") return True def popen(*args: str) -> str: - _logger.debug(f"Executing {args}") + _logger.debug(f"Executing {args}...") + + cmdName = Path(args[0]).name try: proc = subprocess.run(args, stdout=subprocess.PIPE, stderr=sys.stderr) except FileNotFoundError: - raise RuntimeError(f"{args[0]}: Command not found") + raise RuntimeError(f"{cmdName}: Command not found") if proc.returncode == -signal.SIGSEGV: - raise RuntimeError(f"{args[0]}: Segmentation fault") + raise RuntimeError(f"{cmdName}: Segmentation fault") if proc.returncode != 0: - raise RuntimeError(f"{args[0]}: Process exited with code {proc.returncode}") + raise RuntimeError(f"{cmdName}: Process exited with code {proc.returncode}") return proc.stdout.decode("utf-8").strip() +def debug(cmd: list[str], debugger: str = "lldb", wait: bool = False): + if debugger == "lldb": + exec( + "lldb", + *(("-o", "b main") if wait else ()), + *("-o", "run"), + *cmd, + ) + elif debugger == "gdb": + exec( + "gdb", + *(("-ex", "b main") if wait else ()), + *("-ex", "run"), + *cmd, + ) + else: + raise RuntimeError(f"Unknown debugger {debugger}") + + +def profile(cmd: list[str]): + mkdir(const.TMP_DIR) + perfFile = f"{const.TMP_DIR}/perf.data" + try: + exec( + "perf", + "record", + "-g", + "-o", + perfFile, + "--call-graph", + "dwarf", + *cmd, + ) + except Exception as e: + if not os.path.exists(perfFile): + raise e + + try: + proc = subprocess.Popen( + ["perf", "script", "-i", perfFile], stdout=subprocess.PIPE + ) + subprocess.run(["speedscope", "-"], stdin=proc.stdout) + proc.wait() + except Exception as e: + rmrf(perfFile) + raise e + + rmrf(perfFile) + + def readdir(path: str) -> list[str]: _logger.debug(f"Reading directory {path}")