Using the Profiling Trace service

The Profiling Trace service collects runtime profiling data from the Kanzi Engine and writes it to a JSON trace file compatible with the Perfetto trace viewer.

Platform support

The Profiling Trace service builds from sources on all Kanzi target platforms supported by this release. The target platform must support writing files to the filesystem and allow developers to obtain those files from the filesystem.

How the Trace service works

The Trace service operates as a three-stage pipeline:

  1. Collect – The service copies profiling samples from the Kanzi Engine profilers into internal storage.

  2. Store – Samples accumulate in storage until a write trigger occurs.

  3. Write – The service writes all stored samples to a JSON trace file.

You can trigger collection and writing independently using timers, commands, or events.

Understanding trace data

The amount of profiling data available in the trace depends on the build configuration of the Kanzi Engine. The built-in instrumentation of the Kanzi Engine uses compile-time profiling category flags that control whether the profilers produce data.

Profiling build

In a Profiling build, the compiler enables the built-in profiling categories and the trace contains the full set of data that the Trace service collects:

  • Startup profiling – Application startup timing (available once the application is registered).

  • Main loop profiling – Frame timing and main-loop stage durations, such as input, update, layout, and rendering.

  • Resource profiling – Resource loading timing.

  • Custom collection tasks – Any additional profiling data registered through the API, such as Performance service counters and Log service entries.

Note

Unlike Kanzi 3.9, Kanzi 3.6 has no built-in domain profiling (node, layout, and render operations), so per-component timing does not appear in the trace automatically. You can still add it: using the Kanzi metaclass-override technique (ObjectFactory::overrideMetaclass), an application can inject a profiling variant of a node component that measures the overridden operations and records them in the trace, grouped by component type and component name. The bundled monitor_example application (CustomPerformanceTools) includes a reference implementation — see profilingUtil_component_overrides.hpp. It profiles all node types except Kanzi.Screen, which the Monitor core already overrides for node-tree auto-registration: a metaclass override replaces rather than chains, so the same class cannot be overridden for both purposes at once.

Debug and Release builds

In Debug and Release builds, the compiler disables the built-in profiling categories of the Kanzi Engine. The built-in startup, main-loop, and resource profilers then produce no samples, so the trace contains only:

  • Custom collection tasks – Any additional profiling data registered through the API, such as Performance service counters and Log service entries.

To get the most detailed trace data, use a Profiling build of your application.

Triggering a trace write

There are several ways to trigger a trace write:

  • On-demand command – Use the trace command from the local or remote console. This collects and writes immediately.

  • One-shot timer (WritingTimerOnceInterval) – Writes the trace once after a specified delay in milliseconds. Use this to capture startup behavior.

  • Repeating timer (WritingTimerRepeatInterval) – Writes the trace at regular intervals. Use this for continuous monitoring.

  • On exit (WritingOnExitEnabled) – Writes the trace when the application shuts down.

  • Frame-duration threshold (WritingOnFrameDurationThreshold) – Writes the trace when a frame’s duration exceeds the threshold in milliseconds. Use this to capture what the application was doing when it hitched.

Note

On Kanzi 3.6 the frame-duration threshold trigger runs through the per-frame service contract, so the application must drive TraceService::onFrameUpdate() (the same forwarding that feeds the Performance service — see Getting started with Kanzi Monitor). Two differences from Kanzi 3.9: it is compared against the whole-frame time rather than the input-to-render active-stage span, and it is edge-triggered — it writes once when a frame first crosses the threshold and re-arms only after a frame recovers below it, so a sustained slow spell does not write a trace every frame.

Controlling sample collection and storage

By default, each collection replaces the previously stored samples. This means the trace file contains only the most recent data from each profiler buffer.

To accumulate samples over time, enable CollectWithAppendingEnabled. When appending is enabled, each collection adds new samples to the existing storage instead of replacing it. This builds up a longer trace at the cost of increased memory usage.

When CollectWithAppendingEnabled is enabled, you can also use CollectingOnFullSampleBufferEnabled (enabled by default). This automatically collects samples from a profiler when its sample buffer becomes full, preventing data loss when profilers produce data faster than the collection timer runs.

The CollectingTimerRepeatInterval setting controls how often samples are collected independently of writing. This is useful when you want to collect frequently to avoid losing samples, but write less often.

Understanding output files

The trace output file is named using the pattern:

<SessionLabel>_<timestamp>_<sequence>.json

Where <SessionLabel> is the configured session label (default: tracing_output), <timestamp> is the session start time, and <sequence> is incremented for each write within the session.

  • On Windows and platforms other than Android, the output file is written to the current working directory.

  • On Android, the output file is written to /sdcard/.

To view the output file, open it in the Perfetto trace viewer.

If you capture or fetch a trace through the Monitor Web UI, the Trace tab’s View in Perfetto action opens the loaded trace directly in the Perfetto trace viewer, so you do not have to save the JSON file and load it manually.

Note

Event timestamps (ts) and durations (dur) are written in fractional microseconds, so scopes shorter than a microsecond keep their real duration rather than being rounded to zero. This preserves sub-microsecond "duration" events as thin spans in the trace viewer instead of collapsing them to zero-width, instant-looking events.

Adding custom collection tasks

You can register custom collection tasks to include additional profiling data in the trace output. A collection task is a callback that the Trace service invokes when collecting samples. The callback writes profiler data into the sample storage, which is then exported to the trace file.

Each collection task has an event type that determines how the data appears in the trace viewer:

  • "duration" – Time intervals with a start and end. Use for profiling scoped operations such as function durations and stage timings. Appears as spans in the trace timeline.

  • "instant" – Single points in time with no duration. Use for events such as log messages. Appears as markers in the trace timeline.

  • "counter" – Numeric values over time. Use for metrics such as FPS and memory usage. Appears as counter tracks in the trace viewer.

To register a custom collection task:

#include "profilinghelper_collector.hpp"

void onCollectMyData(SampleDataStorage& storage)
{
    // Write profiler registry data into the storage.
    storage.writeRegistry<MyProfilerType>(myProfilerRegistry);
}

ThreadIndex mainThreadIndex = SampleCollector::getKanziMainThreadIndex();
SampleCollector::registerGeneralCollectionTask(
    "my_custom_profiling",    // Name for the data storage
    onCollectMyData,          // Callback invoked during collection
    mainThreadIndex,          // Thread index for trace visualization
    "duration");              // Event type: "duration", "instant", or "counter"

Available commands

Command

Description

trace

Write trace to file.

See also

Using the Performance service

Using the Log service

Configuring Kanzi Monitor