DFTracer Aggregation Pipeline

See also

For complete class and member documentation, see the API Reference.

Getting Started

Minimal example using the high-level AggregatorUtility:

#include <dftracer/utils/utilities/composites/dft/aggregators/aggregator_utility.h>

AggregatorUtility util;
AggregatorInput input;
input.directory = "./traces";
input.config.time_interval_us = 5000000;  // 5-second buckets
input.config.compute_percentiles = true;

auto gen = util.process(input);
while (auto batch = co_await gen.next()) {
    auto arrow = batch->to_arrow();  // 18-column Arrow batch
    // process arrow data...
}

Event aggregation pipeline for computing statistics over DFTracer trace files. All classes are in the dftracer::utils::utilities::composites::dft::aggregators namespace.

The aggregation pipeline processes trace files in parallel chunks, computes per-key metrics (duration, size, custom fields), and merges results into a unified output. It supports time bucketing, process hierarchy tracking, boundary event association, and Perfetto trace output.

        graph LR
    subgraph Input
        Files["Trace Files<br/>(.pfw.gz)"]
    end

    subgraph Mapping["Chunk Mapping"]
        CM["FileChunkMapperUtility"]
    end

    subgraph Parallel["Parallel Aggregation"]
        CA1["ChunkAggregatorUtility"]
        CA2["ChunkAggregatorUtility"]
        CAN["ChunkAggregatorUtility"]
    end

    subgraph Merge["Merge & Resolve"]
        EA["EventAggregator"]
        AR["AssociationResolverUtility"]
    end

    subgraph Output
        Perfetto["PerfettoTraceWriterUtility"]
    end

    Files --> CM
    CM --> CA1
    CM --> CA2
    CM --> CAN
    CA1 --> EA
    CA2 --> EA
    CAN --> EA
    EA --> AR
    AR --> Perfetto
    

Configuration

AggregationConfig

Main configuration for the aggregation pipeline.

Controls time bucketing, grouping and metric fields, statistical computation, boundary event tracking, and output format.

AggregationConfig config;
config.time_interval_us = 1000000;  // 1-second buckets
config.use_relative_time = true;
config.compute_statistics = true;
config.compute_percentiles = true;
config.percentiles = {0.25, 0.5, 0.75, 0.90, 0.99};

// Extra JSON fields to add to the grouping key
config.extra_group_keys = {"args.filename"};

// Extra numeric JSON fields to accumulate metrics for
config.custom_metric_fields = {"args.size"};

// Track boundary events (e.g., epoch boundaries)
config.boundary_events.push_back({
    .event_name = "epoch_start",
    .value_field = "epoch_id",
    .output_name = "epoch"
});

Grouping Keys

AggregationKey

Composite key for grouping events during aggregation.

Events are grouped by category, name, process/thread IDs, host/function hashes, time bucket, and any extra grouping dimensions specified in the config.

String fields (cat, name, hhash, fhash) are stored as interned uint32_t IDs via a global StringIntern table (see Core Infrastructure), reducing memory usage and enabling faster hashing. Accessor methods (.cat(), .name(), etc.) resolve IDs back to string_view.

Extra key-value pairs use a lazily-allocated unique_ptr<vector<pair<uint32_t, uint32_t>>> to avoid heap allocation for the common case of no extra keys.

AggregationMap

Type alias for the map from aggregation keys to metrics:

using AggregationMap =
    std::unordered_map<AggregationKey, AggregationMetrics,
                       AggregationKeyHash, AggregationKeyEqual>;

Metrics

AggregationMetrics

Per-key aggregated metrics using Welford’s online algorithm for numerically stable variance computation and DDSketch for percentile estimation.

Supports incremental updates and merging across chunks.

MetricStats

Single-metric statistics using Welford’s online algorithm.

Tracks count, min, max, mean, variance (M2), skewness (M3), kurtosis (M4), and a DDSketch for percentile estimation. All operations are O(1) per update.

The DDSketch uses a collapsing dense store with 128 fixed bins (uint16_t counters), giving ~256 bytes per sketch. When the bin range exceeds MAX_BINS, the oldest bins are collapsed into bin[0].

System Metrics

Separate metric types for system-level counters (CPU, memory, …) that are aggregated as per-bucket means rather than as event durations. Defined in aggregators/system_metrics.h.

FloatMetricStats

Single floating-point metric aggregated with Welford’s online mean/variance plus an optional DDSketch for percentiles.

#include <dftracer/utils/utilities/composites/dft/aggregators/system_metrics.h>

FloatMetricStats stats(0.01);            // relative sketch accuracy
stats.update(3.5, /*compute_percentiles=*/true);
stats.update(4.0, true);
double sd = stats.get_stddev();          // sample stddev (count-1 divisor)

FloatMetricStats other(0.01);
other.update(5.0);
stats.merge_from(other);                 // combine across chunks

SystemAggregationMetrics

A bucket of named FloatMetricStats plus a timestamp span (ts/te). Backs the SYSTEM aggregation map. update_metric lazily creates the per-name entry; merge_from combines buckets.

SystemAggregationMetrics bucket(0.01);
bucket.update_metric("cpu_percent", 42.0);
bucket.update_metric("mem_rss_mb", 1024.0);
bucket.update_timestamp(event_ts);

Pipeline Stages

FileChunkMapperUtility

Maps a trace file to parallel chunk work items.

Takes file metadata (from MetadataCollectorUtility) and splits the file into chunks based on checkpoint boundaries. Each chunk becomes a ChunkAggregatorInput for parallel processing.

// FileChunkMapperOutput is std::vector<ChunkAggregatorInput>
coro::CoroTask<FileChunkMapperOutput> process(const FileChunkMapperInput&);

FileChunkMapperUtility mapper;
auto input = FileChunkMapperInput::from_metadata(meta)
                 .with_config(config)
                 .with_checkpoint_size(checkpoint_size)
                 .with_target_chunk_size(4);  // MB per chunk
FileChunkMapperOutput chunks = co_await mapper.process(input);

ChunkAggregatorUtility

Per-chunk event aggregation.

Reads events from a byte range within a trace file, applies filters, computes aggregation keys, and accumulates metrics. Uses bloom filter predicates for early chunk skipping when available. Multiple instances run concurrently across chunks.

coro::CoroTask<ChunkAggregationOutput> process(const ChunkAggregatorInput&);

ChunkAggregatorUtility agg;
auto input = ChunkAggregatorInput()
                 .with_file_path("trace.pfw.gz")
                 .with_index_path("./traces/.dftindex")
                 .with_byte_range(start, end)
                 .with_config(config)
                 .with_chunk_index(0);
ChunkAggregationOutput out = co_await agg.process(input);
// out.aggregations, out.profile_aggregations, out.system_aggregations
// are AggregationMap instances keyed by AggregationKey.

EventAggregator

Unified event aggregator (formerly EventAggregatorUtility and the internal RocksDbAggregator, now merged into one class). Holds a RocksDatabase handle and merges per-chunk aggregation results into a unified output, deduplicating file counts and collecting association trackers for downstream resolution.

auto db = EventAggregator::open_with_merge_operator(index_path);
EventAggregator agg(db, config.compute_hash());
agg.merge_chunk(std::move(chunk_output));   // repeat per chunk
EventAggregatorOutput result = agg.finalize();
auto tracker = agg.build_global_tracker();  // merged AssociationTracker

AggregationVisitor

DftEventVisitor subclass that accumulates AggregationMetrics per AggregationKey directly from parsed events during a scan, so the aggregation pass can share a single parse with bloom and manifest visitors via DftEventDispatcher. Defined in dftracer/utils/utilities/composites/dft/aggregators/aggregation_visitor.h.

DftEventDispatcher

Fan-out adapter that implements the IndexVisitor interface, parses each line once, and dispatches the parsed DftEvent to a list of registered DftEventVisitor instances (BloomVisitor, ManifestVisitor, AggregationVisitor, …). This collapses multiple visitor passes into a single read of the input. Defined in dftracer/utils/utilities/composites/dft/dft_event_dispatcher.h.

Association Tracking

AssociationTracker

Tracks process hierarchy (parent-child PIDs) and boundary event intervals during chunk processing. Each chunk gets its own tracker, and trackers are merged during the resolution phase.

Process hierarchy: Extracts parent PID from metadata events to build a process tree. Used to annotate aggregated events with their root process.

Boundary events: Tracks named intervals (e.g., training epochs) by matching start/end events. Aggregated events are associated with the boundary interval that contains their timestamp.

AssociationResolverUtility

Resolves process hierarchy and boundary associations across all chunks.

Merges all per-chunk AssociationTracker instances, resolves parent PIDs to root processes, computes trace-wide metadata (duration, boundary ranges), and annotates aggregated events with their associations.

High-Level Aggregator

AggregatorUtility

High-level StreamingUtility that orchestrates the full aggregation pipeline: directory scan, index building, metadata collection, chunk mapping, parallel aggregation, merge, and association resolution.

Yields AggregationBatch objects that can be converted to Arrow via to_arrow().

AggregatorUtility util;
AggregatorInput input;
input.directory = "./traces";
input.config.time_interval_us = 1000000;

auto gen = util.process(input);
while (auto batch = co_await gen.next()) {
    auto arrow = batch->to_arrow();  // 18-column Arrow batch
    // write to IPC file, send to Python, etc.
}

Output Utilities

PerfettoTraceWriterUtility

Writes aggregated results in Perfetto trace format for visualization in the Perfetto UI (https://ui.perfetto.dev).

Supports three event formats (PerfettoEventFormat):

  • COUNTER - Counter track events (default, best for time-series metrics)

  • ASYNC - Async slice events (shows duration spans)

  • REGULAR - Regular slice events

// process() returns coro::CoroTask<bool>; needs a CoroScope context.
PerfettoTraceWriterInput input;
input.output_path = "out.pftrace";
input.aggregator = &aggregator;      // populated EventAggregator
input.agg_config = &config;
input.format = PerfettoEventFormat::COUNTER;
input.compress = true;

PerfettoTraceWriterUtility writer;
bool ok = co_await writer.process(input);

Serialization

RocksDB key/value codecs for the AGGREGATION column family. Aggregation keys are packed as a 2-byte shard prefix, a 1-byte AggMapType, and LEB128 varint intern IDs; values are varint-packed metrics with three format tiers (METRIC_FMT_COMPACT, METRIC_FMT_FULL, METRIC_FMT_FULL_WITH_SKETCH). Defined in aggregators/aggregation_serialization.h.

String fields are interned; the intern dictionary is persisted under the 0xFFFD key prefix, global config under 0xFFFE, and per-file “aggregated” markers under 0xFFFF.

#include <dftracer/utils/utilities/composites/dft/aggregators/aggregation_serialization.h>

std::string key = serialize_agg_key(config_hash, AggMapType::EVENT, agg_key);
std::string val = serialize_agg_value(metrics);

DeserializedAggKey dk = deserialize_agg_key(key);
AggregationMetrics m = deserialize_agg_value(val);

// Zero-copy views for Arrow export (skip mean/m2/sketch):
AggKeyView kv;
AggMetricsView mv;
if (parse_agg_key_view(key, kv) && parse_agg_value_view(val, mv)) {
    // kv.cat, kv.name, kv.time_bucket; mv.count, mv.dur_total, ...
}

// Intern dictionary lifecycle against a live DB:
load_intern_dictionary(db);
// ... encode keys ...
flush_intern_dictionary(db, batch);

Merge Operators

RocksDB merge operators that combine partial aggregation values written concurrently by parallel chunk workers, so no read-modify-write is needed on the hot path.

AggregationMergeOperator

Merges event/profile AggregationMetrics values (defined in aggregators/aggregation_merge_operator.h). Installed via EventAggregator::open_with_merge_operator.

SystemMetricsMergeOperator

Merges SystemAggregationMetrics values in the SYSTEM_METRICS column family (defined in aggregators/system_metrics_merge_operator.h).

// Both derive from rocksdb::MergeOperator and are set on the CF options:
cf_options.merge_operator =
    std::make_shared<AggregationMergeOperator>();

Running the Full Pipeline

run_aggregation (in aggregators/aggregation_runner.h) is the one-call entry point used by the CLI binaries: it scans the log directory, indexes any files that need it, runs the aggregation visitor pipeline, and optionally emits a Perfetto JSON / Arrow IPC file. When output_file is unset it only populates the AGGREGATION column family for downstream consumers.

#include <dftracer/utils/utilities/composites/dft/aggregators/aggregation_runner.h>

AggregationRunInput input;
input.log_dir = "./traces";
input.index_dir = "./traces/.dftindex";
input.agg_config.time_interval_us = 1000000;
input.output_file = "out.pftrace";              // omit to only fill the CF
input.event_format = PerfettoEventFormat::COUNTER;

Result<AggregationRunResult> result = co_await run_aggregation(input);
if (result) {
    const auto& r = *result;
    // r.index_path, r.total_keys, r.processed_file_count, r.elapsed_ms
}