This is an automated email from the ASF dual-hosted git repository.

agrove pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-comet.git


The following commit(s) were added to refs/heads/main by this push:
     new 0cc1e8c47 feat(benchmarks): add async-profiler support to TPC 
benchmark scripts (#3613)
0cc1e8c47 is described below

commit 0cc1e8c47c5c8672ee2b0ab73f002e042945dddc
Author: Andy Grove <[email protected]>
AuthorDate: Tue Mar 3 08:37:51 2026 -0700

    feat(benchmarks): add async-profiler support to TPC benchmark scripts 
(#3613)
---
 benchmarks/tpc/README.md                           | 111 +++++++++++++++++++--
 benchmarks/tpc/infra/docker/Dockerfile             |  15 ++-
 .../tpc/infra/docker/docker-compose-laptop.yml     |   2 +
 benchmarks/tpc/infra/docker/docker-compose.yml     |   2 +
 benchmarks/tpc/run.py                              |  62 +++++++++++-
 5 files changed, 177 insertions(+), 15 deletions(-)

diff --git a/benchmarks/tpc/README.md b/benchmarks/tpc/README.md
index 319288df0..fac54a789 100644
--- a/benchmarks/tpc/README.md
+++ b/benchmarks/tpc/README.md
@@ -38,17 +38,21 @@ All benchmarks are run via `run.py`:
 python3 run.py --engine <engine> --benchmark <tpch|tpcds> [options]
 ```
 
-| Option         | Description                                              |
-| -------------- | -------------------------------------------------------- |
-| `--engine`     | Engine name (matches a TOML file in `engines/`)          |
-| `--benchmark`  | `tpch` or `tpcds`                                        |
-| `--iterations` | Number of iterations (default: 1)                        |
-| `--output`     | Output directory (default: `.`)                          |
-| `--query`      | Run a single query number                                |
-| `--no-restart` | Skip Spark master/worker restart                         |
-| `--dry-run`    | Print the spark-submit command without executing         |
-| `--jfr`        | Enable Java Flight Recorder profiling                    |
-| `--jfr-dir`    | Directory for JFR output files (default: `/results/jfr`) |
+| Option                    | Description                                      
                               |
+| ------------------------- | 
------------------------------------------------------------------------------- 
|
+| `--engine`                | Engine name (matches a TOML file in `engines/`)  
                               |
+| `--benchmark`             | `tpch` or `tpcds`                                
                               |
+| `--iterations`            | Number of iterations (default: 1)                
                               |
+| `--output`                | Output directory (default: `.`)                  
                               |
+| `--query`                 | Run a single query number                        
                               |
+| `--no-restart`            | Skip Spark master/worker restart                 
                               |
+| `--dry-run`               | Print the spark-submit command without executing 
                               |
+| `--jfr`                   | Enable Java Flight Recorder profiling            
                               |
+| `--jfr-dir`               | Directory for JFR output files (default: 
`/results/jfr`)                        |
+| `--async-profiler`        | Enable async-profiler (profiles Java + native 
code)                             |
+| `--async-profiler-dir`    | Directory for async-profiler output (default: 
`/results/async-profiler`)        |
+| `--async-profiler-event`  | Event type: `cpu`, `wall`, `alloc`, `lock`, etc. 
(default: `cpu`)               |
+| `--async-profiler-format` | Output format: `flamegraph`, `jfr`, `collapsed`, 
`text` (default: `flamegraph`) |
 
 Available engines: `spark`, `comet`, `comet-iceberg`, `gluten`
 
@@ -392,3 +396,88 @@ docker compose -f 
benchmarks/tpc/infra/docker/docker-compose.yml \
 
 Open the `.jfr` files with [JDK Mission Control](https://jdk.java.net/jmc/),
 IntelliJ IDEA's profiler, or `jfr` CLI tool (`jfr summary driver.jfr`).
+
+## async-profiler Profiling
+
+Use the `--async-profiler` flag to capture profiles with
+[async-profiler](https://github.com/async-profiler/async-profiler). Unlike JFR,
+async-profiler can profile **both Java and native (Rust/C++) code** in the same
+flame graph, making it especially useful for profiling Comet workloads.
+
+### Prerequisites
+
+async-profiler must be installed on every node where the driver or executors 
run.
+Set `ASYNC_PROFILER_HOME` to the installation directory:
+
+```shell
+# Download and extract (Linux x64 example)
+wget 
https://github.com/async-profiler/async-profiler/releases/download/v3.0/async-profiler-3.0-linux-x64.tar.gz
+tar xzf async-profiler-3.0-linux-x64.tar.gz -C /opt/async-profiler 
--strip-components=1
+export ASYNC_PROFILER_HOME=/opt/async-profiler
+```
+
+On Linux, `perf_event_paranoid` must be set to allow profiling:
+
+```shell
+sudo sysctl kernel.perf_event_paranoid=1   # or 0 / -1 for full access
+sudo sysctl kernel.kptr_restrict=0          # optional: enable kernel symbols
+```
+
+### Basic usage
+
+```shell
+python3 run.py --engine comet --benchmark tpch --async-profiler
+```
+
+This produces HTML flame graphs in `/results/async-profiler/` by default
+(`driver.html` and `executor.html`).
+
+### Choosing events and output format
+
+```shell
+# Wall-clock profiling (includes time spent waiting/sleeping)
+python3 run.py --engine comet --benchmark tpch \
+    --async-profiler --async-profiler-event wall
+
+# Allocation profiling with JFR output
+python3 run.py --engine comet --benchmark tpch \
+    --async-profiler --async-profiler-event alloc --async-profiler-format jfr
+
+# Lock contention profiling
+python3 run.py --engine comet --benchmark tpch \
+    --async-profiler --async-profiler-event lock
+```
+
+| Event   | Description                                         |
+| ------- | --------------------------------------------------- |
+| `cpu`   | On-CPU time (default). Shows where CPU cycles go.   |
+| `wall`  | Wall-clock time. Includes threads that are blocked. |
+| `alloc` | Heap allocation profiling.                          |
+| `lock`  | Lock contention profiling.                          |
+
+| Format       | Extension | Description                              |
+| ------------ | --------- | ---------------------------------------- |
+| `flamegraph` | `.html`   | Interactive HTML flame graph (default).  |
+| `jfr`        | `.jfr`    | JFR format, viewable in JMC or IntelliJ. |
+| `collapsed`  | `.txt`    | Collapsed stacks for FlameGraph scripts. |
+| `text`       | `.txt`    | Flat text summary of hot methods.        |
+
+### Docker usage
+
+The Docker image includes async-profiler pre-installed at
+`/opt/async-profiler`. The `ASYNC_PROFILER_HOME` environment variable is
+already set in the compose files, so no extra configuration is needed:
+
+```shell
+docker compose -f benchmarks/tpc/infra/docker/docker-compose.yml \
+    run --rm bench \
+    python3 /opt/benchmarks/run.py \
+    --engine comet --benchmark tpch --output /results --no-restart 
--async-profiler
+```
+
+Output files are collected in `$RESULTS_DIR/async-profiler/` on the host.
+
+**Note:** On Linux, the Docker container needs `--privileged` or
+`SYS_PTRACE` capability and `perf_event_paranoid <= 1` on the host for
+`cpu`/`wall` events. Allocation (`alloc`) and lock (`lock`) events work
+without special privileges.
diff --git a/benchmarks/tpc/infra/docker/Dockerfile 
b/benchmarks/tpc/infra/docker/Dockerfile
index 60567536a..9bf5ae393 100644
--- a/benchmarks/tpc/infra/docker/Dockerfile
+++ b/benchmarks/tpc/infra/docker/Dockerfile
@@ -29,10 +29,23 @@ RUN apt-get update \
     && apt-get install -y --no-install-recommends \
        openjdk-8-jdk-headless \
        openjdk-17-jdk-headless \
-       python3 python3-pip procps \
+       python3 python3-pip procps wget \
     && apt-get clean \
     && rm -rf /var/lib/apt/lists/*
 
+# Install async-profiler for profiling Java + native (Rust/C++) code.
+ARG ASYNC_PROFILER_VERSION=3.0
+RUN ARCH=$(uname -m) && \
+    if [ "$ARCH" = "x86_64" ]; then AP_ARCH="linux-x64"; \
+    elif [ "$ARCH" = "aarch64" ]; then AP_ARCH="linux-aarch64"; \
+    else echo "Unsupported architecture: $ARCH" && exit 1; fi && \
+    wget -q 
"https://github.com/async-profiler/async-profiler/releases/download/v${ASYNC_PROFILER_VERSION}/async-profiler-${ASYNC_PROFILER_VERSION}-${AP_ARCH}.tar.gz";
 \
+         -O /tmp/async-profiler.tar.gz && \
+    mkdir -p /opt/async-profiler && \
+    tar xzf /tmp/async-profiler.tar.gz -C /opt/async-profiler 
--strip-components=1 && \
+    rm /tmp/async-profiler.tar.gz
+ENV ASYNC_PROFILER_HOME=/opt/async-profiler
+
 # Default to Java 17 (override with JAVA_HOME at runtime for Gluten).
 # Detect architecture (amd64 or arm64) so the image works on both Linux and 
macOS.
 ARG TARGETARCH
diff --git a/benchmarks/tpc/infra/docker/docker-compose-laptop.yml 
b/benchmarks/tpc/infra/docker/docker-compose-laptop.yml
index 727268406..02a520894 100644
--- a/benchmarks/tpc/infra/docker/docker-compose-laptop.yml
+++ b/benchmarks/tpc/infra/docker/docker-compose-laptop.yml
@@ -30,6 +30,7 @@
 #   ICEBERG_JAR        - Host path to Iceberg Spark runtime JAR
 #   BENCH_JAVA_HOME    - Java home inside container (default: 
/usr/lib/jvm/java-17-openjdk)
 #                        Set to /usr/lib/jvm/java-8-openjdk for Gluten
+#   ASYNC_PROFILER_HOME - async-profiler install path (default: 
/opt/async-profiler)
 
 x-volumes: &volumes
   - ${DATA_DIR:-/tmp/tpc-data}:/data:ro
@@ -95,5 +96,6 @@ services:
       - TPCH_DATA=/data
       - TPCDS_DATA=/data
       - SPARK_EVENT_LOG_DIR=/results/spark-events
+      - ASYNC_PROFILER_HOME=/opt/async-profiler
     mem_limit: 4g
     memswap_limit: 4g
diff --git a/benchmarks/tpc/infra/docker/docker-compose.yml 
b/benchmarks/tpc/infra/docker/docker-compose.yml
index f5c9f0ebe..4e9f45d03 100644
--- a/benchmarks/tpc/infra/docker/docker-compose.yml
+++ b/benchmarks/tpc/infra/docker/docker-compose.yml
@@ -33,6 +33,7 @@
 #   BENCH_MEM_LIMIT    - Hard memory limit for the bench runner (default: 10g)
 #   BENCH_JAVA_HOME    - Java home inside container (default: 
/usr/lib/jvm/java-17-openjdk)
 #                        Set to /usr/lib/jvm/java-8-openjdk for Gluten
+#   ASYNC_PROFILER_HOME - async-profiler install path (default: 
/opt/async-profiler)
 
 x-volumes: &volumes
   - ${DATA_DIR:-/tmp/tpc-data}:/data:ro
@@ -109,6 +110,7 @@ services:
       - TPCH_DATA=/data
       - TPCDS_DATA=/data
       - SPARK_EVENT_LOG_DIR=/results/spark-events
+      - ASYNC_PROFILER_HOME=/opt/async-profiler
     mem_limit: ${BENCH_MEM_LIMIT:-10g}
     memswap_limit: ${BENCH_MEM_LIMIT:-10g}
 
diff --git a/benchmarks/tpc/run.py b/benchmarks/tpc/run.py
index 58afc0bbe..5a89166cd 100755
--- a/benchmarks/tpc/run.py
+++ b/benchmarks/tpc/run.py
@@ -279,6 +279,38 @@ def build_spark_submit_cmd(config, benchmark, args):
             existing = conf.get(spark_key, "")
             conf[spark_key] = f"{existing} {jfr_opts}".strip()
 
+    # async-profiler: attach as a Java agent via -agentpath
+    if args.async_profiler:
+        ap_home = os.environ.get("ASYNC_PROFILER_HOME", "")
+        if not ap_home:
+            print(
+                "Error: ASYNC_PROFILER_HOME is not set. "
+                "Set it to the async-profiler installation directory.",
+                file=sys.stderr,
+            )
+            sys.exit(1)
+        lib_ext = "dylib" if sys.platform == "darwin" else "so"
+        ap_lib = os.path.join(ap_home, "lib", f"libasyncProfiler.{lib_ext}")
+        ap_dir = args.async_profiler_dir
+        ap_event = args.async_profiler_event
+        ap_fmt = args.async_profiler_format
+        ext = {"flamegraph": "html", "jfr": "jfr", "collapsed": "txt", "text": 
"txt"}[ap_fmt]
+
+        driver_ap = (
+            f"-agentpath:{ap_lib}=start,event={ap_event},"
+            f"{ap_fmt},file={ap_dir}/driver.{ext}"
+        )
+        executor_ap = (
+            f"-agentpath:{ap_lib}=start,event={ap_event},"
+            f"{ap_fmt},file={ap_dir}/executor.{ext}"
+        )
+        for spark_key, ap_opts in [
+            ("spark.driver.extraJavaOptions", driver_ap),
+            ("spark.executor.extraJavaOptions", executor_ap),
+        ]:
+            existing = conf.get(spark_key, "")
+            conf[spark_key] = f"{existing} {ap_opts}".strip()
+
     for key, val in sorted(conf.items()):
         cmd += ["--conf", f"{key}={val}"]
 
@@ -385,6 +417,27 @@ def main():
         default="/results/jfr",
         help="Directory for JFR output files (default: /results/jfr)",
     )
+    parser.add_argument(
+        "--async-profiler",
+        action="store_true",
+        help="Enable async-profiler for driver and executors (profiles Java + 
native code)",
+    )
+    parser.add_argument(
+        "--async-profiler-dir",
+        default="/results/async-profiler",
+        help="Directory for async-profiler output files (default: 
/results/async-profiler)",
+    )
+    parser.add_argument(
+        "--async-profiler-event",
+        default="cpu",
+        help="async-profiler event type: cpu, wall, alloc, lock, etc. 
(default: cpu)",
+    )
+    parser.add_argument(
+        "--async-profiler-format",
+        default="flamegraph",
+        choices=["flamegraph", "jfr", "collapsed", "text"],
+        help="async-profiler output format (default: flamegraph)",
+    )
     args = parser.parse_args()
 
     config = load_engine_config(args.engine)
@@ -401,9 +454,12 @@ def main():
     if not args.no_restart and not args.dry_run:
         restart_spark()
 
-    # Create JFR output directory if profiling is enabled
-    if args.jfr:
-        os.makedirs(args.jfr_dir, exist_ok=True)
+    # Create profiling output directories (skip for dry-run)
+    if not args.dry_run:
+        if args.jfr:
+            os.makedirs(args.jfr_dir, exist_ok=True)
+        if args.async_profiler:
+            os.makedirs(args.async_profiler_dir, exist_ok=True)
 
     cmd = build_spark_submit_cmd(config, args.benchmark, args)
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to