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 known Kanzi target platforms. 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 profilers produce data.

Profiling build

In a Profiling build, the compiler enables all profiling categories and the trace contains the full set of data:

  • Startup profiling – Application startup timing (requires Application registration)

  • Main loop profiling – Frame timing, stage durations (Input, User, Render)

  • Domain node profiling – Node operations

  • Domain layout profiling – Layout operations

  • Domain render profiling – Render operations

  • Resource profiling – Resource loading timing

  • Default registry profiling – All interval profilers registered in the default profiling registry

  • Named interval profiling – Named interval profilers from the default profiling registry

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

Debug and Release builds

In Debug and Release builds, the compiler disables the built-in profiling categories of the Kanzi Engine. The Kanzi Engine profilers exist but produce no samples. The trace contains only:

  • Default registry profiling – All interval profilers registered in the default profiling registry

  • Named interval profiling – Named interval profilers from the default profiling registry

  • Custom collection tasks – Any additional profiling data registered through the API

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 exceeds the specified duration in milliseconds. Use this to catch performance spikes. Kanzi Monitor disables this trigger when running in Kanzi Studio Preview.

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 the host application’s internal files directory (Context.getFilesDir()), resolved at runtime via JNI. The exact path depends on the package, typically /data/user/0/<package>/files/ on modern devices. To copy a trace off the device, use adb shell run-as <package> cat files/<trace>.json > <trace>.json.

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

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"

Trace writers

The Trace Service drives output through a registry of named writers. Each writer registers itself under a name, has a human-readable description, and can be independently activated or deactivated — at startup via configuration or at runtime via a command.

Built-in writers:

Name

Description

chrometrace

Chrome Trace Format JSON (.json). Opens in Perfetto UI, chrome://tracing, and Android Performance Analyzer. Carries the full Monitor sample set, including resource samples.

perfetto

Native Perfetto protobuf (.perfetto-trace). Smaller on disk than chrometrace, supports typed counters and debug annotations, and is the canonical input format for Perfetto UI and Android Performance Analyzer. Implemented with a hand-rolled, header-only protobuf encoder — no external dependency. Carries the full Monitor sample set, including resource profiling samples.

perfetto-producer

Stream events through the in-process Perfetto producer SDK to the platform tracing daemon — Android traced or Linux perfetto. No file on disk; events appear in system traces captured by Android Performance Analyzer or the perfetto CLI alongside ATrace, SurfaceFlinger, binder, and other system producers. Requires the plugin to be built with -DKZMONITOR_PERFETTO_PRODUCER=ON (pre-built Android binaries already are). See the Streaming traces with the Perfetto producer SDK section below for the full workflow.

The ActiveWriters configuration key (see Configuring Kanzi Monitor) lists writers to activate at startup, as a comma-separated string. For example, to enable two writers at once:

ActiveWriters = chrometrace,otherwriter

If the key is absent or empty, chrometrace is activated by default.

Note

ActiveWriters is applied once during TraceService::initialize() and can only refer to writers that are already registered at that point — the built-in writers and any writer whose module initialises before the Trace Service. Writers registered by a downstream plugin whose initialize() runs later must call TraceWriterRegistry::activate() from their own init path (see the example plugin’s customperformancetools_module.cpp for the pattern). At runtime, overwatch.activatewriter <name> works for any registered writer regardless of registration time.

Unknown names in ActiveWriters log a warning (ActiveWriters: writer '<name>' is not registered; skipping) and are skipped. If the resulting active set is empty — for example, ActiveWriters = typo — the Trace Service additionally logs ActiveWriters resolved to zero active writers no trace output will be produced. Selection is intentionally explicit: a typo silently disabling all tracing is preferable to a typo silently activating an unintended writer.

At runtime, the overwatch.writers command returns the registry contents as JSON, and overwatch.activatewriter / overwatch.deactivatewriter toggle individual writers. A trace write fires through every currently-active writer; writers added by future versions of Kanzi Monitor or by your own integration are picked up the same way without any change to the Trace Service itself.

Streaming traces with the Perfetto producer SDK

The perfetto-producer writer streams events through the in-process Perfetto producer SDK to the platform tracing daemon. Events appear in system traces captured by Android Performance Analyzer or the perfetto CLI alongside ATrace, SurfaceFlinger, binder, and other system producers — giving you a single timeline that correlates Kanzi work with the rest of the device.

Compared to the file-output writers (chrometrace and perfetto), the perfetto-producer writer:

  • Has no end-of-session “write the file” step — events flow as the application runs.

  • Co-captures with the rest of the system, making it easy to align Kanzi frame stages with Choreographer#doFrame, surface composition, and Binder traffic.

  • Requires the producer SDK to be built into Kanzi Monitor and a tracing daemon to be available on the platform.

Build prerequisites

The Perfetto producer SDK is gated behind a CMake option that defaults to off when building from source:

cmake -DKZMONITOR_PERFETTO_PRODUCER=ON ...

When the option is off, the perfetto-producer writer’s registered callback is a no-op and the writer never produces output. Activating it via ActiveWriters = perfetto-producer logs an informational message but otherwise does nothing.

The pre-built Android binaries shipped with Kanzi Monitor are built with -DKZMONITOR_PERFETTO_PRODUCER=ON. If you use those directly, the producer SDK is already linked and no extra flag is required. Verify in logcat: Perfetto producer SDK initialized (system + in-process backends) appears at startup.

Platform support

Platform

Daemon

Notes

Android

traced (system-wide)

Built into the platform on Android 9 (API 28) and later. Apps must be declared profileable: add <profileable android:shell="true" /> to AndroidManifest.xml. See Setting permissions on Android.

Linux desktop

perfetto (user-started)

The producer connects through /tmp/perfetto-producer; start a host perfetto daemon first (traced --background).

Windows, QNX, embedded

No platform tracing daemon. Use the in-process capture mode described below, or fall back to ActiveWriters = perfetto to write a .perfetto-trace file.

Configuration

Activate the perfetto-producer writer via ActiveWriters in kanzimonitor.cfg:

ServiceTraceEnabled = 1
ActiveWriters = perfetto-producer
CollectingTimerRepeatInterval = 1000
WritingTimerRepeatInterval = 1000
CollectWithAppendingEnabled = 1

The collect/write timers still drive when Kanzi Monitor drains its profiler ring buffers into the producer — the perfetto-producer writer is the existing collect/write pipeline with a different sink, not a separate path.

You can combine perfetto-producer with file-output writers, e.g. ActiveWriters = perfetto-producer,chrometrace to stream to APA and keep a JSON file for offline analysis.

Capturing a trace

There are two ways to capture the live stream.

External capture (recommended). Let the platform’s tracing daemon collect Kanzi events into a system trace.

On Android, the perfetto CLI on the device drives the capture. Save the following config to kanzi.pbtx and push it to the device:

buffers {
  size_kb: 32768
}
data_sources {
  config {
    name: "track_event"
    track_event_config {
      enabled_categories: "kanzi.mainloop"
      enabled_categories: "kanzi.resource"
      enabled_categories: "kanzi.render"
      enabled_categories: "kanzi.layout"
      enabled_categories: "kanzi.domain"
      enabled_categories: "kanzi.startup"
      enabled_categories: "kanzi.interop"
      enabled_categories: "kanzi.generic"
    }
  }
}
data_sources { config { name: "linux.ftrace" } }
duration_ms: 10000

Then capture and pull the trace:

adb push kanzi.pbtx /data/local/tmp/
adb shell perfetto --txt -c /data/local/tmp/kanzi.pbtx -o /data/local/tmp/kanzi.pftrace
adb pull /data/local/tmp/kanzi.pftrace

Android Performance Analyzer drives the same workflow from a GUI — select the application and add the eight kanzi.* track-event categories to the recording config.

On Linux desktop, point the host perfetto CLI at the same config.

In-process capture. When no external daemon is available (Windows, embedded), the plugin can drive its own session at startup. Set:

ActiveWriters = perfetto-producer
PerfettoProducerCaptureSeconds = 10

Kanzi Monitor starts an in-process Perfetto session at plugin load, runs it for PerfettoProducerCaptureSeconds seconds, and writes the resulting binary to <SessionLabel>_<timestamp>_producer.pftrace in the same output directory as the other writers. PerfettoProducerCaptureSeconds is independent of ActiveWriters — it drives the in-process Perfetto session rather than the writer registry, so it works even if you only have chrometrace and perfetto writers active.

Track-event categories

Kanzi Monitor registers a fixed set of track-event categories that map onto Kanzi’s existing profiling categories. Enable any subset in your perfetto config to filter what gets recorded:

Category

Maps to

kanzi.mainloop

Main-loop phases (frame, input / user / render stage wrappers, present).

kanzi.resource

Resource acquire / load / deploy operations.

kanzi.render

Scene-graph render-time operations (Node2DRender, applyMaterial, …).

kanzi.layout

Scene-graph layout operations (measure, arrange, recursive walks).

kanzi.domain

Scene-graph node operations (attach, detach, property changed).

kanzi.startup

Startup phases (kzb load, prefab instantiate, …).

kanzi.interop

Interop bridges (JNI, scripting, native callbacks).

kanzi.generic

Other Kanzi profiling samples (logging, internal collector ops, fallback for unmapped categories).

Timestamps are re-based onto Perfetto’s BOOTTIME clock so Kanzi events line up with ATrace and SurfaceFlinger in the same timeline.

Limitations

  • The producer registers on the same thread that loaded the plugin. Slices are emitted from the Kanzi main thread, so events land on that thread’s track in Perfetto UI.

Available commands

Command

Description

trace

Write trace to file.

kzTrace: engine-native tracing

In addition to the Monitor profiling trace, Kanzi Monitor supports Kanzi engine-native tracing through the overwatch.kztrace command. This uses the kanzi::logTraceToFile() API from the Kanzi Engine tracing subsystem (kanzi/core/tracing/tracing.hpp).

kzTrace captures data from the Kanzi Engine kzTrace scope macros, covering frame stages, rendering, resource loading, and node operations. Like the Monitor profiling trace, kzTrace includes engine-internal instrumentation, but the two systems collect data independently using different mechanisms. It produces Chrome Trace JSON that you can view in Perfetto alongside Monitor profiling traces.

See the Tracing API reference and Tracing application configuration in the Kanzi Engine documentation.

Note

kzTrace is only available in Debug and Profiling builds (KANZI_TRACING_BUILD). In Release builds, overwatch.kztrace returns an error message.

Note

On Android, the Kanzi engine writes kzTrace data to /sdcard/ regardless of the path Kanzi Monitor passes, so the host application must declare WRITE_EXTERNAL_STORAGE (SDK below 30) or MANAGE_EXTERNAL_STORAGE (SDK 30 and later) in AndroidManifest.xml and request the runtime permission, otherwise the kzTrace file is silently skipped. The Monitor plugin returns a JSON error with a hint field when this happens. The example application under examples/monitor_example/ does not declare these permissions because the Monitor profiling trace no longer needs them. See Known issues for details.

kzTrace output files are named kztrace_<timestamp>_<sequence>.json and are written to the same output directory as Monitor profiling traces on platforms other than Android. On Android, kzTrace writes to /sdcard/ (see the note above).

See Using the Overwatch service for the overwatch.kztrace and overwatch.fetchkztrace command reference.

See also

Using the Performance service

Using the Log service

Configuring Kanzi Monitor