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:
Collect – The service copies profiling samples from the Kanzi Engine profilers into internal storage.
Store – Samples accumulate in storage until a write trigger occurs.
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
tracecommand 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 |
|---|---|
|
Write trace to file. |