From 0ecb0746a602d445f7b67793773cedbe738ab84f Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Tue, 18 Jul 2023 22:11:41 +0200 Subject: flame_graph.sh -> flamegraph.sh Inspired by flamegraph.pl. --- scripts/flame_graph.sh | 113 ------------------------------------------------- scripts/flamegraph.sh | 113 +++++++++++++++++++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 2 +- test/py/conftest.py | 12 +++--- 4 files changed, 120 insertions(+), 120 deletions(-) delete mode 100755 scripts/flame_graph.sh create mode 100755 scripts/flamegraph.sh diff --git a/scripts/flame_graph.sh b/scripts/flame_graph.sh deleted file mode 100755 index ae9129c..0000000 --- a/scripts/flame_graph.sh +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2023 Egor Tensin -# This file is part of the "cimple" project. -# For details, see https://github.com/egor-tensin/cimple. -# Distributed under the MIT License. - -# This script attaches to a set of processes and makes a flame graph using -# flamegraph.pl. - -set -o errexit -o nounset -o pipefail -shopt -s inherit_errexit lastpipe - -script_name="$( basename -- "${BASH_SOURCE[0]}" )" -readonly script_name - -script_usage() { - local msg - for msg; do - echo "$script_name: $msg" - done - - echo "usage: $script_name OUTPUT_PATH PID [PID...]" -} - -join_pids() { - local result='' - while [ "$#" -gt 0 ]; do - if [ -n "$result" ]; then - result="$result," - fi - result="$result$1" - shift - done - echo "$result" -} - -output_dir='' - -cleanup() { - if [ -n "$output_dir" ]; then - echo "Removing temporary directory: $output_dir" - rm -rf -- "$output_dir" - fi -} - -make_graph() { - wait "$record_pid" || true - perf script -i "$output_dir/perf.data" > "$output_dir/perf.out" - stackcollapse-perf.pl "$output_dir/perf.out" > "$output_dir/perf.folded" - flamegraph.pl --width 2400 "$output_dir/perf.folded" > "$output_path" -} - -record_pid='' - -stop_record() { - echo "Stopping 'perf record' process $record_pid" - kill -SIGINT "$record_pid" - make_graph -} - -check_tools() { - local tool - for tool in perf stackcollapse-perf.pl flamegraph.pl; do - if ! command -v "$tool" &> /dev/null; then - echo "$script_name: $tool is missing" >&2 - exit 1 - fi - done -} - -main() { - trap cleanup EXIT - check_tools - - if [ "$#" -lt 1 ]; then - script_usage "output path is required" >&2 - exit 1 - fi - local output_path="$1" - output_path="$( realpath -- "$output_path" )" - shift - - if [ "$#" -lt 1 ]; then - script_usage "at least one process ID is required" >&2 - exit 1 - fi - local pids - pids="$( join_pids "$@" )" - shift - - echo "Output path: $output_path" - echo "PIDs: $pids" - - output_dir="$( dirname -- "$output_path" )" - output_dir="$( mktemp -d --tmpdir="$output_dir" )" - readonly output_dir - - perf record \ - -o "$output_dir/perf.data" \ - --freq=99 \ - --call-graph dwarf,65528 \ - --pid="$pids" \ - --no-inherit & - - record_pid="$!" - echo "Started 'perf record' process $record_pid" - trap stop_record SIGINT SIGTERM - - make_graph -} - -main "$@" diff --git a/scripts/flamegraph.sh b/scripts/flamegraph.sh new file mode 100755 index 0000000..ae9129c --- /dev/null +++ b/scripts/flamegraph.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash + +# Copyright (c) 2023 Egor Tensin +# This file is part of the "cimple" project. +# For details, see https://github.com/egor-tensin/cimple. +# Distributed under the MIT License. + +# This script attaches to a set of processes and makes a flame graph using +# flamegraph.pl. + +set -o errexit -o nounset -o pipefail +shopt -s inherit_errexit lastpipe + +script_name="$( basename -- "${BASH_SOURCE[0]}" )" +readonly script_name + +script_usage() { + local msg + for msg; do + echo "$script_name: $msg" + done + + echo "usage: $script_name OUTPUT_PATH PID [PID...]" +} + +join_pids() { + local result='' + while [ "$#" -gt 0 ]; do + if [ -n "$result" ]; then + result="$result," + fi + result="$result$1" + shift + done + echo "$result" +} + +output_dir='' + +cleanup() { + if [ -n "$output_dir" ]; then + echo "Removing temporary directory: $output_dir" + rm -rf -- "$output_dir" + fi +} + +make_graph() { + wait "$record_pid" || true + perf script -i "$output_dir/perf.data" > "$output_dir/perf.out" + stackcollapse-perf.pl "$output_dir/perf.out" > "$output_dir/perf.folded" + flamegraph.pl --width 2400 "$output_dir/perf.folded" > "$output_path" +} + +record_pid='' + +stop_record() { + echo "Stopping 'perf record' process $record_pid" + kill -SIGINT "$record_pid" + make_graph +} + +check_tools() { + local tool + for tool in perf stackcollapse-perf.pl flamegraph.pl; do + if ! command -v "$tool" &> /dev/null; then + echo "$script_name: $tool is missing" >&2 + exit 1 + fi + done +} + +main() { + trap cleanup EXIT + check_tools + + if [ "$#" -lt 1 ]; then + script_usage "output path is required" >&2 + exit 1 + fi + local output_path="$1" + output_path="$( realpath -- "$output_path" )" + shift + + if [ "$#" -lt 1 ]; then + script_usage "at least one process ID is required" >&2 + exit 1 + fi + local pids + pids="$( join_pids "$@" )" + shift + + echo "Output path: $output_path" + echo "PIDs: $pids" + + output_dir="$( dirname -- "$output_path" )" + output_dir="$( mktemp -d --tmpdir="$output_dir" )" + readonly output_dir + + perf record \ + -o "$output_dir/perf.data" \ + --freq=99 \ + --call-graph dwarf,65528 \ + --pid="$pids" \ + --no-inherit & + + record_pid="$!" + echo "Started 'perf record' process $record_pid" + trap stop_record SIGINT SIGTERM + + make_graph +} + +main "$@" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 74ade1a..f9f53e6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -36,5 +36,5 @@ endif() add_python_tests(python_tests_perf Python3::Interpreter -m pytest ${python_test_args} -m "flame_graph" - --flame-graph-binary "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/flame_graph.sh" + --flamegraph-binary "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/flamegraph.sh" --flame-graphs-dir "${FLAME_GRAPHS_DIR}") diff --git a/test/py/conftest.py b/test/py/conftest.py index 274c419..748b5e5 100644 --- a/test/py/conftest.py +++ b/test/py/conftest.py @@ -53,13 +53,13 @@ PARAM_VERSION = Param('project_version', 'project version') PARAM_VALGRIND = ParamBinary('valgrind', required=False) -PARAM_FLAME_GRAPH = ParamBinary('flame_graph', required=False) +PARAM_FLAMEGRAPH = ParamBinary('flamegraph', required=False) PARAM_FLAME_GRAPHS_DIR = Param('flame_graphs_dir', 'directory to store flame graphs', required=False) PARAMS = list(BINARY_PARAMS) PARAMS += [ PARAM_VALGRIND, - PARAM_FLAME_GRAPH, + PARAM_FLAMEGRAPH, PARAM_FLAME_GRAPHS_DIR, PARAM_VERSION, ] @@ -199,7 +199,7 @@ def repo_path(tmp_path): @fixture -def flame_graph_path(pytestconfig, tmp_path): +def flame_graph_svg(pytestconfig, tmp_path): dir = pytestconfig.getoption(PARAM_FLAME_GRAPHS_DIR.codename) if dir is None: return os.path.join(tmp_path, 'flame_graph.svg') @@ -208,14 +208,14 @@ def flame_graph_path(pytestconfig, tmp_path): @fixture -def profiler(pytestconfig, server, workers, flame_graph_path): - script = pytestconfig.getoption(PARAM_FLAME_GRAPH.codename) +def profiler(pytestconfig, server, workers, flame_graph_svg): + script = pytestconfig.getoption(PARAM_FLAMEGRAPH.codename) if script is None: yield return pids = [server.pid] + [worker.pid for worker in workers] pids = map(str, pids) - cmd_line = CmdLine(script, flame_graph_path, *pids) + cmd_line = CmdLine(script, flame_graph_svg, *pids) with cmd_line.run_async() as proc: yield assert proc.returncode == 0 -- cgit v1.2.3