diff --git a/doc/index.rst b/doc/index.rst index 3a8e963..7e9b2f9 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -512,6 +512,87 @@ If you provide an ELF image via the `--image`_ option, then the output will contain the symbolic names of functions in the program. If not, then it will contain hex addresses. +tarmac-chromejson +----------------- + +``tarmac-chromejson`` writes out profiling data derived from one or more +trace files as `Chromium's Trace Event Format`_, suitable for timeline viewers that +understand this format. + +.. _Chromium's Trace Event Format: https://chromium.googlesource.com/catapult/+/HEAD/docs/trace-event-format.md + +The command-line syntax of ``tarmac-chromejson`` looks like this: + ``tarmac-chromejson`` [ *options* ] *trace-file-name*... + +All the options in `Common functionality`_ are supported. This tool +also recognizes the following additional options: + +``-o`` *filename* or ``--output=``\ *filename* + Tells the tool to write its output to the specified file, instead of + to standard output. + +``--pid`` *pid* + Tells the tool which Chrome trace process id to assign to generated + events. The default is 1. + +``--tid`` *tid* + Tells the tool which Chrome trace thread id to assign to generated + events. The default is 1. If multiple trace files are provided, they + are assigned consecutive thread ids starting from this value. + +When run over trace files, ``tarmac-chromejson`` produces a JSON object +whose ``traceEvents`` array contains one complete event for each +function activation identified in the traces. Each event's timestamp +and duration come from the Tarmac timestamps used by the index. + +An example of the event structure used for complete events is the following: + +.. code-block:: none + + { + "name": "quicksort", + "cat": "function", + "ph": "X", + "pid": 1, + "tid": 2, + "ts": 1234, + "dur": 56, + "args": { + "pc": "0x8038", + "line": 177 + } + } + +while metadata events look like: + +.. code-block:: none + + { + "ph": "M", + "name": "thread_name", + "pid": 1, + "tid": 2, + "args": { + "name": "CPU 1" + } + } + +where + +* ``name`` is the event name +* ``cat`` is the Category (e.g. ``"function"``) +* ``ph`` is the event type (e.g. ``"M"`` or ``"X"``) +* ``pid`` is the process ID +* ``tid`` is the Thread ID +* ``ts`` is the timestamp +* ``dur`` is the duration of the event +* ``args`` is arbitrary event-specific metadata + +If you provide an ELF image via the `--image`_ option, then the output +will contain the symbolic names of functions in the program. If more +than one trace file is provided, the same image is used for all of +them. If no image is provided, the output will contain hex addresses. + tarmac-vcd ---------- diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fc8f1de..4e5800e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -177,6 +177,27 @@ add_test(NAME flamegraph-symbols ${CMAKE_BINARY_DIR}/tarmac-flamegraph --index quicksort.tarmac.index --image ${CMAKE_CURRENT_SOURCE_DIR}/quicksort.elf ${CMAKE_CURRENT_SOURCE_DIR}/quicksort.tarmac ) +# Tests of Chrome trace JSON output on the same quicksort trace. +# Expected output, with and without symbol annotations from the ELF +# file, is in chrome-quicksort-*.ref. +add_test(NAME chrome-no-symbols + COMMAND ${test_driver_cmd} + --tempfile quicksort.tarmac.index + --compare reffile:${CMAKE_CURRENT_SOURCE_DIR}/chrome-quicksort-addr.ref stdout + ${CMAKE_BINARY_DIR}/tarmac-chromejson --index quicksort.tarmac.index ${CMAKE_CURRENT_SOURCE_DIR}/quicksort.tarmac + ) +add_test(NAME chrome-symbols + COMMAND ${test_driver_cmd} + --tempfile quicksort.tarmac.index + --compare reffile:${CMAKE_CURRENT_SOURCE_DIR}/chrome-quicksort-symbols.ref stdout + ${CMAKE_BINARY_DIR}/tarmac-chromejson --index quicksort.tarmac.index --image ${CMAKE_CURRENT_SOURCE_DIR}/quicksort.elf ${CMAKE_CURRENT_SOURCE_DIR}/quicksort.tarmac + ) +add_test(NAME chrome-multi-trace-symbols + COMMAND ${test_driver_cmd} + --compare reffile:${CMAKE_CURRENT_SOURCE_DIR}/chrome-quicksort-multi-symbols.ref stdout + ${CMAKE_BINARY_DIR}/tarmac-chrome --image ${CMAKE_CURRENT_SOURCE_DIR}/quicksort.elf ${CMAKE_CURRENT_SOURCE_DIR}/quicksort.tarmac ${CMAKE_CURRENT_SOURCE_DIR}/quicksort.tarmac + ) + # Tests of tarmac-profile, with and without ELF symbol annotations. # Same input trace file as the above tests; expected output is in # profile-quicksort-*.ref. diff --git a/tests/chrome-quicksort-addr.ref b/tests/chrome-quicksort-addr.ref new file mode 100644 index 0000000..433e02d --- /dev/null +++ b/tests/chrome-quicksort-addr.ref @@ -0,0 +1,35 @@ +{ + "traceEvents": [ + {"ph":"M","name":"process_name","pid":1,"tid":1,"args":{"name":"Tarmac trace"}}, + {"ph":"M","name":"thread_name","pid":1,"tid":1,"args":{"name":"CPU 0"}}, + {"ph":"X","cat":"function","name":"0x8000","pid":1,"tid":1,"ts":1,"dur":2043,"args":{"pc":"0x8000","line":157}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":10,"dur":2017,"args":{"pc":"0x8038","line":177}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":367,"dur":1401,"args":{"pc":"0x8038","line":860}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":581,"dur":296,"args":{"pc":"0x8038","line":1265}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":656,"dur":76,"args":{"pc":"0x8038","line":1411}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":686,"dur":6,"args":{"pc":"0x8038","line":1472}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":719,"dur":6,"args":{"pc":"0x8038","line":1546}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":781,"dur":89,"args":{"pc":"0x8038","line":1681}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":821,"dur":42,"args":{"pc":"0x8038","line":1762}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":850,"dur":6,"args":{"pc":"0x8038","line":1822}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1050,"dur":520,"args":{"pc":"0x8038","line":2229}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1178,"dur":385,"args":{"pc":"0x8038","line":2478}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1275,"dur":156,"args":{"pc":"0x8038","line":2666}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1337,"dur":87,"args":{"pc":"0x8038","line":2789}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1378,"dur":6,"args":{"pc":"0x8038","line":2871}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1411,"dur":6,"args":{"pc":"0x8038","line":2945}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1480,"dur":76,"args":{"pc":"0x8038","line":3099}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1510,"dur":6,"args":{"pc":"0x8038","line":3160}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1543,"dur":6,"args":{"pc":"0x8038","line":3234}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1616,"dur":6,"args":{"pc":"0x8038","line":3399}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1677,"dur":84,"args":{"pc":"0x8038","line":3526}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1717,"dur":37,"args":{"pc":"0x8038","line":3607}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1741,"dur":6,"args":{"pc":"0x8038","line":3657}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1825,"dur":6,"args":{"pc":"0x8038","line":3843}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1891,"dur":129,"args":{"pc":"0x8038","line":3980}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1927,"dur":6,"args":{"pc":"0x8038","line":4052}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":1971,"dur":42,"args":{"pc":"0x8038","line":4147}}, + {"ph":"X","cat":"function","name":"0x8038","pid":1,"tid":1,"ts":2000,"dur":6,"args":{"pc":"0x8038","line":4207}}, + {"ph":"X","cat":"function","name":"0x80ec","pid":1,"tid":1,"ts":2030,"dur":3,"args":{"pc":"0x80ec","line":4290}} + ] +} diff --git a/tests/chrome-quicksort-multi-symbols.ref b/tests/chrome-quicksort-multi-symbols.ref new file mode 100644 index 0000000..cfa36e0 --- /dev/null +++ b/tests/chrome-quicksort-multi-symbols.ref @@ -0,0 +1,65 @@ +{ + "traceEvents": [ + {"ph":"M","name":"process_name","pid":1,"tid":1,"args":{"name":"Tarmac trace"}}, + {"ph":"M","name":"thread_name","pid":1,"tid":1,"args":{"name":"quicksort.tarmac"}}, + {"ph":"M","name":"thread_name","pid":1,"tid":2,"args":{"name":"quicksort.tarmac"}}, + {"ph":"X","cat":"function","name":"_start","pid":1,"tid":1,"ts":1,"dur":2043,"args":{"pc":"0x8000","line":157}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":10,"dur":2017,"args":{"pc":"0x8038","line":177}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":367,"dur":1401,"args":{"pc":"0x8038","line":860}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":581,"dur":296,"args":{"pc":"0x8038","line":1265}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":656,"dur":76,"args":{"pc":"0x8038","line":1411}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":686,"dur":6,"args":{"pc":"0x8038","line":1472}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":719,"dur":6,"args":{"pc":"0x8038","line":1546}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":781,"dur":89,"args":{"pc":"0x8038","line":1681}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":821,"dur":42,"args":{"pc":"0x8038","line":1762}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":850,"dur":6,"args":{"pc":"0x8038","line":1822}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1050,"dur":520,"args":{"pc":"0x8038","line":2229}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1178,"dur":385,"args":{"pc":"0x8038","line":2478}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1275,"dur":156,"args":{"pc":"0x8038","line":2666}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1337,"dur":87,"args":{"pc":"0x8038","line":2789}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1378,"dur":6,"args":{"pc":"0x8038","line":2871}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1411,"dur":6,"args":{"pc":"0x8038","line":2945}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1480,"dur":76,"args":{"pc":"0x8038","line":3099}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1510,"dur":6,"args":{"pc":"0x8038","line":3160}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1543,"dur":6,"args":{"pc":"0x8038","line":3234}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1616,"dur":6,"args":{"pc":"0x8038","line":3399}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1677,"dur":84,"args":{"pc":"0x8038","line":3526}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1717,"dur":37,"args":{"pc":"0x8038","line":3607}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1741,"dur":6,"args":{"pc":"0x8038","line":3657}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1825,"dur":6,"args":{"pc":"0x8038","line":3843}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1891,"dur":129,"args":{"pc":"0x8038","line":3980}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1927,"dur":6,"args":{"pc":"0x8038","line":4052}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1971,"dur":42,"args":{"pc":"0x8038","line":4147}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":2000,"dur":6,"args":{"pc":"0x8038","line":4207}}, + {"ph":"X","cat":"function","name":"sys_write0","pid":1,"tid":1,"ts":2030,"dur":3,"args":{"pc":"0x80ec","line":4290}}, + {"ph":"X","cat":"function","name":"_start","pid":1,"tid":2,"ts":1,"dur":2043,"args":{"pc":"0x8000","line":157}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":10,"dur":2017,"args":{"pc":"0x8038","line":177}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":367,"dur":1401,"args":{"pc":"0x8038","line":860}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":581,"dur":296,"args":{"pc":"0x8038","line":1265}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":656,"dur":76,"args":{"pc":"0x8038","line":1411}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":686,"dur":6,"args":{"pc":"0x8038","line":1472}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":719,"dur":6,"args":{"pc":"0x8038","line":1546}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":781,"dur":89,"args":{"pc":"0x8038","line":1681}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":821,"dur":42,"args":{"pc":"0x8038","line":1762}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":850,"dur":6,"args":{"pc":"0x8038","line":1822}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1050,"dur":520,"args":{"pc":"0x8038","line":2229}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1178,"dur":385,"args":{"pc":"0x8038","line":2478}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1275,"dur":156,"args":{"pc":"0x8038","line":2666}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1337,"dur":87,"args":{"pc":"0x8038","line":2789}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1378,"dur":6,"args":{"pc":"0x8038","line":2871}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1411,"dur":6,"args":{"pc":"0x8038","line":2945}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1480,"dur":76,"args":{"pc":"0x8038","line":3099}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1510,"dur":6,"args":{"pc":"0x8038","line":3160}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1543,"dur":6,"args":{"pc":"0x8038","line":3234}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1616,"dur":6,"args":{"pc":"0x8038","line":3399}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1677,"dur":84,"args":{"pc":"0x8038","line":3526}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1717,"dur":37,"args":{"pc":"0x8038","line":3607}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1741,"dur":6,"args":{"pc":"0x8038","line":3657}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1825,"dur":6,"args":{"pc":"0x8038","line":3843}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1891,"dur":129,"args":{"pc":"0x8038","line":3980}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1927,"dur":6,"args":{"pc":"0x8038","line":4052}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":1971,"dur":42,"args":{"pc":"0x8038","line":4147}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":2,"ts":2000,"dur":6,"args":{"pc":"0x8038","line":4207}}, + {"ph":"X","cat":"function","name":"sys_write0","pid":1,"tid":2,"ts":2030,"dur":3,"args":{"pc":"0x80ec","line":4290}} + ] +} diff --git a/tests/chrome-quicksort-symbols.ref b/tests/chrome-quicksort-symbols.ref new file mode 100644 index 0000000..7a6e452 --- /dev/null +++ b/tests/chrome-quicksort-symbols.ref @@ -0,0 +1,35 @@ +{ + "traceEvents": [ + {"ph":"M","name":"process_name","pid":1,"tid":1,"args":{"name":"Tarmac trace"}}, + {"ph":"M","name":"thread_name","pid":1,"tid":1,"args":{"name":"CPU 0"}}, + {"ph":"X","cat":"function","name":"_start","pid":1,"tid":1,"ts":1,"dur":2043,"args":{"pc":"0x8000","line":157}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":10,"dur":2017,"args":{"pc":"0x8038","line":177}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":367,"dur":1401,"args":{"pc":"0x8038","line":860}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":581,"dur":296,"args":{"pc":"0x8038","line":1265}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":656,"dur":76,"args":{"pc":"0x8038","line":1411}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":686,"dur":6,"args":{"pc":"0x8038","line":1472}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":719,"dur":6,"args":{"pc":"0x8038","line":1546}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":781,"dur":89,"args":{"pc":"0x8038","line":1681}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":821,"dur":42,"args":{"pc":"0x8038","line":1762}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":850,"dur":6,"args":{"pc":"0x8038","line":1822}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1050,"dur":520,"args":{"pc":"0x8038","line":2229}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1178,"dur":385,"args":{"pc":"0x8038","line":2478}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1275,"dur":156,"args":{"pc":"0x8038","line":2666}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1337,"dur":87,"args":{"pc":"0x8038","line":2789}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1378,"dur":6,"args":{"pc":"0x8038","line":2871}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1411,"dur":6,"args":{"pc":"0x8038","line":2945}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1480,"dur":76,"args":{"pc":"0x8038","line":3099}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1510,"dur":6,"args":{"pc":"0x8038","line":3160}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1543,"dur":6,"args":{"pc":"0x8038","line":3234}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1616,"dur":6,"args":{"pc":"0x8038","line":3399}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1677,"dur":84,"args":{"pc":"0x8038","line":3526}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1717,"dur":37,"args":{"pc":"0x8038","line":3607}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1741,"dur":6,"args":{"pc":"0x8038","line":3657}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1825,"dur":6,"args":{"pc":"0x8038","line":3843}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1891,"dur":129,"args":{"pc":"0x8038","line":3980}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1927,"dur":6,"args":{"pc":"0x8038","line":4052}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":1971,"dur":42,"args":{"pc":"0x8038","line":4147}}, + {"ph":"X","cat":"function","name":"quicksort","pid":1,"tid":1,"ts":2000,"dur":6,"args":{"pc":"0x8038","line":4207}}, + {"ph":"X","cat":"function","name":"sys_write0","pid":1,"tid":1,"ts":2030,"dur":3,"args":{"pc":"0x80ec","line":4290}} + ] +} diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index f4bc590..23b1a6e 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -25,6 +25,9 @@ standard_target_configuration(tarmac-callinfo) add_executable(tarmac-calltree calltree.cpp ${EXTRA_FILES}) standard_target_configuration(tarmac-calltree) +add_executable(tarmac-chromejson chrome.cpp ${EXTRA_FILES}) +standard_target_configuration(tarmac-chromejson) + add_executable(tarmac-flamegraph flamegraph.cpp ${EXTRA_FILES}) standard_target_configuration(tarmac-flamegraph) @@ -38,7 +41,7 @@ add_executable(tarmac-vcd vcdwriter.cpp vcd.cpp ${EXTRA_FILES}) standard_target_configuration(tarmac-vcd) install(TARGETS - tarmac-callinfo tarmac-calltree tarmac-flamegraph tarmac-profile + tarmac-callinfo tarmac-calltree tarmac-chromejson tarmac-flamegraph tarmac-profile tarmac-vcd EXPORT ${TTU_targets_export_name} RUNTIME) diff --git a/tools/chrome.cpp b/tools/chrome.cpp new file mode 100644 index 0000000..509077b --- /dev/null +++ b/tools/chrome.cpp @@ -0,0 +1,289 @@ +/* + * Copyright 2026 Arm Limited. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is part of Tarmac Trace Utilities + */ + +#include "libtarmac/argparse.hh" +#include "libtarmac/calltree.hh" +#include "libtarmac/intl.hh" +#include "libtarmac/reporter.hh" +#include "libtarmac/tarmacutil.hh" + +#include +#include +#include +#include +#include +#include +#include + +using std::cout; +using std::make_shared; +using std::make_unique; +using std::ofstream; +using std::ostream; +using std::ostringstream; +using std::shared_ptr; +using std::string; +using std::unique_ptr; + +unique_ptr reporter = make_cli_reporter(); + +namespace { + +class ChromeTraceJson { + public: + ChromeTraceJson(ostream &os) : os(os), first_event(true) {} + + void start() { os << "{\n \"traceEvents\": [\n"; } + + void finish() { os << "\n ]\n}\n"; } + + void metadata_event(const string &name, const string &value, int pid, + int tid) + { + separator(); + os << " {\"ph\":\"M\",\"name\":\"" << json_escape(name) + << "\",\"pid\":" << pid << ",\"tid\":" << tid + << ",\"args\":{\"name\":\"" << json_escape(value) << "\"}}"; + } + + void function_event(const string &name, int pid, int tid, Time ts, Time dur, + Addr addr, unsigned long long line) + { + separator(); + + string func_name = name; + if (func_name.empty()) { + // Fall back to printing address when we have no valid name + func_name = format_as_address(addr); + } + + os << " {\"ph\":\"X\",\"cat\":\"function\",\"name\":\"" + << json_escape(func_name) << "\",\"pid\":" << pid + << ",\"tid\":" << tid << ",\"ts\":" << ts << ",\"dur\":" << dur + << ",\"args\":{\"pc\":\"" << format_as_address(addr) + << "\",\"line\":" << line << "}}"; + } + + private: + std::ostream &os; + bool first_event; + + void separator() + { + if (first_event) { + first_event = false; + } else { + os << ",\n"; + } + } + + string format_as_address(Addr addr) + { + std::ostringstream oss; + oss << "0x" << std::hex << addr; + return oss.str(); + } + + string json_escape(const string &input) + { + std::ostringstream oss; + constexpr unsigned char first_printable_ascii = ' '; + + for (unsigned char c : input) { + switch (c) { + case '"': + oss << "\\\""; + break; + case '\\': + oss << "\\\\"; + break; + case '\b': + oss << "\\b"; + break; + case '\f': + oss << "\\f"; + break; + case '\n': + oss << "\\n"; + break; + case '\r': + oss << "\\r"; + break; + case '\t': + oss << "\\t"; + break; + default: + // If we get other control bytes, encode as '\uXXXX', where + // 'XXXX' are hex digits encoding the character. Here we pad + // with zero to fill the 4 digits + if (c < first_printable_ascii) { + oss << "\\u" << std::hex << std::setw(4) + << std::setfill('0') << static_cast(c) + << std::dec << std::setfill(' '); + } else { + oss << c; + } + break; + } + } + + return oss.str(); + } +}; + +class ChromeTraceWriter : public CallTreeVisitor { + ChromeTraceJson &out_json; + int pid; + int tid; + + public: + ChromeTraceWriter(const CallTree &CT, ChromeTraceJson &json, int pid, + int tid) + : CallTreeVisitor(CT), out_json(json), pid(pid), tid(tid) + { + } + + void onFunctionEntry(const TarmacSite &function_entry, + const TarmacSite &function_exit) + { + if (function_exit.time < function_entry.time) { + return; + } + + out_json.function_event( + CT.getFunctionName(function_entry), pid, tid, function_entry.time, + function_exit.time - function_entry.time, function_entry.addr, + function_entry.tarmac_line); + } +}; + +string basename(const string &path) +{ + size_t pos = path.find_last_of("/\\"); + return pos == string::npos ? path : path.substr(pos + 1); +} + +struct ChromeTarmacUtility : public TarmacUtilityMT { + virtual void postProcessOptions() override + { + if (!index_on_disk) { + if (indexing == Troolean::No) + reporter->warnx(_("Ignoring --no-index since index is in " + "memory")); + indexing = Troolean::Yes; + } + + for (TracePair &trace : traces) { + trace.index_on_disk = index_on_disk; + if (trace.index_on_disk) { + if (trace.index_filename.empty()) + trace.index_filename = trace.tarmac_filename + ".index"; + } else { + if (!trace.index_filename.empty()) + reporter->warnx(_("Ignoring index file name since index is " + "in memory")); + trace.memory_index = make_shared(); + } + } + } +}; + +} // namespace + +int main(int argc, char **argv) +{ + gettext_setup(true); + + unique_ptr outfile = nullptr; + unique_ptr index_filename = nullptr; + + // Default PID and TID to 1 + int pid = 1; + int tid = 1; + + IndexerParams iparams; + iparams.record_memory = false; + + CallTreeOptions ctopts; + + Argparse ap("tarmac-chromejson", argc, argv); + ChromeTarmacUtility tu; + tu.set_indexer_params(iparams); + tu.add_options(ap); + ctopts.add_options(ap); + ap.optval( + {"--index"}, _("INDEXFILE"), + _("index file name, only valid with one trace file"), + [&](const string &s) { index_filename = make_unique(s); }); + ap.optval({"-o", "--output"}, _("OUTFILE"), + _("write output to OUTFILE (default: standard output)"), + [&](const string &s) { outfile = make_unique(s); }); + ap.optval({"--pid"}, _("PID"), _("process id to write into trace events"), + [&](const string &s) { pid = stoi(s); }); + ap.optval({"--tid"}, _("TID"), _("thread id to write into trace events"), + [&](const string &s) { tid = stoi(s); }); + ap.parse(); + + if (index_filename) { + if (tu.traces.size() != 1) + reporter->errx(EXIT_FAILURE, + _("--index is only valid with one trace file")); + tu.traces[0].index_filename = *index_filename; + } + + tu.setup(); + + shared_ptr image; + if (!tu.image_filename.empty()) + image = make_shared(tu.image_filename); + + unique_ptr ofs; + ostream *osp; + if (outfile) { + ofs = make_unique(outfile->c_str()); + osp = ofs.get(); + } else { + osp = &cout; + } + + ChromeTraceJson output_json(*osp); + output_json.start(); + output_json.metadata_event("process_name", "Tarmac trace", pid, tid); + + for (size_t i = 0; i < tu.traces.size(); i++) { + const TracePair &trace = tu.traces[i]; + int trace_tid = tid + static_cast(i); + string thread_name = + tu.traces.size() == 1 ? "CPU 0" : basename(trace.tarmac_filename); + output_json.metadata_event("thread_name", thread_name, pid, trace_tid); + } + + for (size_t i = 0; i < tu.traces.size(); i++) { + const TracePair &trace = tu.traces[i]; + int trace_tid = tid + static_cast(i); + + IndexNavigator IN(trace, image, tu.load_offset); + CallTree CT(IN); + CT.setOptions(ctopts); + + ChromeTraceWriter writer(CT, output_json, pid, trace_tid); + CT.visit(writer); + } + + output_json.finish(); +}