Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ ARG AP_BUILDER_ALPINE=@sha256:69704ef328d05a9f806b6b8502915e6a0a4faa4d72018dc423
ARG BURN_BUILDER_GOLANG=@sha256:f7d3519759ba6988a2b73b5874b17c5958ac7d0aa48a8b1d84d66ef25fa345f1
# gprofiler - ubuntu 20.04
ARG GPROFILER_BUILDER_UBUNTU=@sha256:cf31af331f38d1d7158470e095b132acd126a7180a54f263d386da88eb681d93
# node-package-builder-musl alpine
ARG NODE_PACKAGE_BUILDER_MUSL=@sha256:69704ef328d05a9f806b6b8502915e6a0a4faa4d72018dc42343f511490daf8a
# node-package-builder-glibc - ubuntu:20.04
ARG NODE_PACKAGE_BUILDER_GLIBC=@sha256:cf31af331f38d1d7158470e095b132acd126a7180a54f263d386da88eb681d93

# pyspy & rbspy builder base
FROM rust${RUST_BUILDER_VERSION} AS pyspy-rbspy-builder-common
Expand Down Expand Up @@ -148,6 +152,22 @@ COPY scripts/async_profiler_build_shared.sh .
COPY scripts/async_profiler_build_musl.sh .
RUN ./async_profiler_build_shared.sh /tmp/async_profiler_build_musl.sh

# node-package-builder-musl
FROM alpine${NODE_PACKAGE_BUILDER_MUSL} AS node-package-builder-musl
WORKDIR /tmp
COPY scripts/node_builder_musl_env.sh .
RUN ./node_builder_musl_env.sh
COPY scripts/build_node_package.sh .
RUN ./build_node_package.sh

# node-package-builder-glibc
FROM ubuntu${NODE_PACKAGE_BUILDER_GLIBC} AS node-package-builder-glibc
WORKDIR /tmp
COPY scripts/node_builder_glibc_env.sh .
RUN ./node_builder_glibc_env.sh
COPY scripts/build_node_package.sh .
RUN ./build_node_package.sh

# burn
FROM golang${BURN_BUILDER_GOLANG} AS burn-builder
WORKDIR /tmp
Expand Down Expand Up @@ -191,6 +211,8 @@ COPY --from=async-profiler-builder-glibc /tmp/async-profiler/build/async-profile
COPY --from=async-profiler-builder-glibc /tmp/async-profiler/build/libasyncProfiler.so gprofiler/resources/java/glibc/libasyncProfiler.so
COPY --from=async-profiler-builder-musl /tmp/async-profiler/build/libasyncProfiler.so gprofiler/resources/java/musl/libasyncProfiler.so
COPY --from=async-profiler-builder-glibc /tmp/async-profiler/build/fdtransfer gprofiler/resources/java/fdtransfer
COPY --from=node-package-builder-musl /tmp/module_build gprofiler/resources/node/module/musl
COPY --from=node-package-builder-glibc /tmp/module_build gprofiler/resources/node/module/glibc

COPY --from=rbspy-builder /tmp/rbspy/rbspy gprofiler/resources/ruby/rbspy

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Profiling using eBPF incurs lower overhead & provides kernel & native stacks.
* `--nodejs-mode`: Controls which profiler is used for NodeJS.
* `none` - (default) no profiler is used.
* `perf` - augment the system profiler (`perf`) results with jitdump files generated by NodeJS. This requires running your `node` processes with `--perf-prof` (and for Node >= 10, with `--interpreted-frames-native-stack`). See this [NodeJS page](https://nodejs.org/en/docs/guides/diagnostics-flamegraph/) for more information.
* `attach-maps` - Generates perf map using [node-linux-perf module](https://github.com/mmarchini-oss/node-linux-perf). This module is injected at runtime. Requires entrypoint of application to be CommonJS script. (Doesn't work for ES modules)

### System profiling options

Expand Down Expand Up @@ -367,6 +368,7 @@ Alongside `perf`, gProfiler invokes runtime-specific profilers for processes bas
* Uses [Granulate's fork](https://github.com/Granulate/rbspy) of the [rbspy](https://github.com/rbspy/rbspy) profiler.
* NodeJS (version >= 10 for functioning `--perf-prof`):
* Uses `perf inject --jit` and NodeJS's ability to generate jitdump files. See [NodeJS profiling options](#nodejs-profiling-options).
* Can also generate perf maps at runtime.
* .NET runtime
* Uses dotnet-trace.

Expand Down
1 change: 1 addition & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ types-PyYAML==6.0.3
types-pkg-resources==0.1.3
types-protobuf==3.19.22
types-toml==0.10.8
types-retry==0.9.9
12 changes: 7 additions & 5 deletions gprofiler/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,9 +433,10 @@ def parse_cmd_args() -> configargparse.Namespace:
"--nodejs-mode",
dest="nodejs_mode",
default="disabled",
choices=["perf", "disabled", "none"],
help="Select the NodeJS profiling mode: perf (run 'perf inject --jit' on perf results, to augment them"
" with jitdump files of NodeJS processes, if present) or disabled (no runtime-specific profilers for NodeJS)",
choices=["attach-maps", "perf", "disabled", "none"],
help="Select the NodeJS profiling mode: attach-maps (generates perf-maps at runtime),"
" perf (run 'perf inject --jit' on perf results, to augment them with jitdump files"
" of NodeJS processes, if present) or disabled (no runtime-specific profilers for NodeJS)",
)

nodejs_options.add_argument(
Expand Down Expand Up @@ -601,6 +602,7 @@ def parse_cmd_args() -> configargparse.Namespace:
args = parser.parse_args()

args.perf_inject = args.nodejs_mode == "perf"
args.perf_node_attach = args.nodejs_mode == "attach-maps"

if args.upload_results:
if not args.server_token:
Expand All @@ -617,8 +619,8 @@ def parse_cmd_args() -> configargparse.Namespace:
if args.perf_mode in ("dwarf", "smart") and args.frequency > 100:
parser.error("--profiling-frequency|-f can't be larger than 100 when using --perf-mode 'smart' or 'dwarf'")

if args.nodejs_mode == "perf" and args.perf_mode not in ("fp", "smart"):
parser.error("--nodejs-mode perf requires --perf-mode 'fp' or 'smart'")
if args.nodejs_mode in ("perf", "attach-maps") and args.perf_mode not in ("fp", "smart"):
parser.error("--nodejs-mode perf or attach-maps requires --perf-mode 'fp' or 'smart'")

return args

Expand Down
20 changes: 2 additions & 18 deletions gprofiler/metadata/application_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@
#

import functools
from subprocess import CompletedProcess
from threading import Event, Lock
from typing import Any, Dict, Optional

from granulate_utils.linux.ns import get_process_nspid, run_in_ns
from granulate_utils.linux.process import is_process_running, read_process_execfn
from psutil import NoSuchProcess, Process

from gprofiler.log import get_logger_adapter
from gprofiler.utils import run_process
from gprofiler.metadata.versions import get_exe_version

logger = get_logger_adapter(__name__)

Expand All @@ -38,21 +36,7 @@ def _clear_cache(self) -> None:
del self._cache[process]

def get_exe_version(self, process: Process, version_arg: str = "--version", try_stderr: bool = False) -> str:
"""
Runs {process.exe()} --version in the appropriate namespace
"""
exe_path = f"/proc/{get_process_nspid(process.pid)}/exe"

def _run_get_version() -> "CompletedProcess[bytes]":
return run_process([exe_path, version_arg], stop_event=self._stop_event, timeout=self._GET_VERSION_TIMEOUT)

cp = run_in_ns(["pid", "mnt"], _run_get_version, process.pid)
stdout = cp.stdout.decode().strip()
# return stderr if stdout is empty, some apps print their version to stderr.
if try_stderr and not stdout:
return cp.stderr.decode().strip()

return stdout
return get_exe_version(process, self._stop_event, self._GET_VERSION_TIMEOUT, version_arg, try_stderr)

@functools.lru_cache(4096)
def get_exe_version_cached(self, process: Process, version_arg: str = "--version", try_stderr: bool = False) -> str:
Expand Down
35 changes: 35 additions & 0 deletions gprofiler/metadata/versions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#
# Copyright (c) Granulate. All rights reserved.
# Licensed under the AGPL3 License. See LICENSE.md in the project root for license information.
#
from subprocess import CompletedProcess
from threading import Event

from granulate_utils.linux.ns import get_process_nspid, run_in_ns
from psutil import Process

from gprofiler.utils import run_process


def get_exe_version(
process: Process,
stop_event: Event,
get_version_timeout: int,
version_arg: str = "--version",
try_stderr: bool = False,
) -> str:
"""
Runs {process.exe()} --version in the appropriate namespace
"""
exe_path = f"/proc/{get_process_nspid(process.pid)}/exe"

def _run_get_version() -> "CompletedProcess[bytes]":
return run_process([exe_path, version_arg], stop_event=stop_event, timeout=get_version_timeout)

cp = run_in_ns(["pid", "mnt"], _run_get_version, process.pid)
stdout = cp.stdout.decode().strip()
# return stderr if stdout is empty, some apps print their version to stderr.
if try_stderr and not stdout:
return cp.stderr.decode().strip()

return stdout
Loading