Skip to content

Internal Flow Tracking

aryehcitron@gmail.com edited this page May 24, 2026 · 13 revisions

Internal Flow Tracking

Internal Flow Tracking adds interactive internal flow visualisation to your sequence diagrams. When enabled, clicking an arrow in a PlantUML sequence diagram opens a popup showing the internal class/method activity that occurred inside the SUT between that HTTP boundary and the next — captured via OpenTelemetry spans.

This lets you see not just what your API called, but what happened inside the API while processing each request and response.

Setup order: This page covers the report configuration for Internal Flow Tracking. For the OpenTelemetry exporter setup that captures the spans, see Integration OpenTelemetry Extension first. Complete that setup, then return here to configure how internal flow data is displayed in your reports.


How It Works

  1. OpenTelemetry spans are captured during test execution using the Kronikol.Extensions.OpenTelemetry package. A custom exporter stores all Activity spans in memory.

  2. HTTP boundaries are timestampedTestTrackingMessageHandler and MessageTracker record Activity.Current?.TraceId, Activity.Current?.SpanId, and DateTimeOffset.UtcNow on every request and response log entry.

  3. Segments are built at report generation time. InternalFlowSegmentBuilder correlates the captured OTel spans with the HTTP boundary timestamps, grouping spans into segments — one segment per arrow in the sequence diagram. Each segment contains the spans that started between two consecutive HTTP boundaries.

  4. Clickable links are injected into the PlantUML sequence diagram arrows using PlantUML's [[link]] syntax. Each arrow becomes a clickable hyperlink with an #iflow-{id} anchor.

  5. A popup script intercepts clicks on these SVG links. When you click an arrow, it looks up the segment data from window.__iflowSegments and displays the internal flow as either a PlantUML activity diagram (rendered in-browser) or an HTML call tree.

Note: Internal Flow Tracking requires DiagramFormat = DiagramFormat.PlantUml.


Prerequisites

Internal flow tracking is enabled by default — no extra configuration is needed for the standard case.

Span capture is auto-started by the TestTrackingMessageHandler constructor. All well-known auto-instrumentation sources (ASP.NET Core, HttpClient, EF Core, Redis, Cosmos, etc.) are captured automatically.

To also capture your SUT's custom ActivitySources, add their names to the handler options:

new XUnitTestTrackingMessageHandlerOptions
{
    CallerName = "Tests",
    PortsToServiceNames = { ... },
    InternalFlowActivitySources = ["MyApi.Services", "MyApi.Database"]  // optional
}

Alternatively, you can explicitly start a listener via DI with AddActivityListenerForInternalFlowTracking(), or use the manual OTel SDK AddTestTrackingExporter() approach. See Integration OpenTelemetry Extension for details.


Configuration

InternalFlowTracking defaults to true on ReportConfigurationOptions. To disable it, set it to false:

new ReportConfigurationOptions
{
    InternalFlowTracking = false, // opt out
    // ... other options
}

When InternalFlowTracking is enabled, the following are automatically forced:

  • InlineSvgRendering = true — SVG diagrams are embedded inline in the HTML (required for clickable arrows)
  • PlantUmlImageFormat = PlantUmlImageFormat.Svg — when using PlantUmlRendering.Server or PlantUmlRendering.Local (required for SVG-based interaction)

Note: PlantUmlRendering.BrowserJs already renders inline SVG, so no override is needed for that mode.


Diagram Styles

The popup content can be rendered in two styles, controlled by InternalFlowDiagramStyle:

ActivityDiagram (default)

Renders a PlantUML activity diagram with swimlanes grouped by ActivitySource name. Each span appears as an action in its source's swimlane, with duration shown in milliseconds. The activity diagram is rendered client-side using the PlantUML TeaVM JS engine.

InternalFlowDiagramStyle = InternalFlowDiagramStyle.ActivityDiagram

CallTree

Renders an HTML nested list showing the span parent-child hierarchy as a call tree. Each node shows the source name, span display name, and duration. This is lightweight and doesn't require the PlantUML JS engine.

InternalFlowDiagramStyle = InternalFlowDiagramStyle.CallTree

SequenceDiagram

Reserved for future use.

Context Menu on Internal Flow Content

All internal flow content types support the right-click context menu:

  • Activity diagrams (SVG) — Full menu with PNG, PNG (no transparency), SVG, and source options
  • Flame charts (data-diagram-type="flamechart") — PNG copy/save only
  • Call trees (data-diagram-type="calltree") — PNG copy/save only

The context menu renders at z-index: 20001 to appear above popup overlays. See Inline SVG Rendering#context-menu for the full menu reference.


Span Granularity

Control which spans are included using InternalFlowSpanGranularity:

Value Behaviour Best for
AutoInstrumentation (default) Only includes spans from well-known auto-instrumentation sources (ASP.NET Core, HttpClient, EF Core, Redis, Cosmos, etc.) Most projects — shows framework-level flow without noise
Manual Only includes spans from sources listed in InternalFlowActivitySources When you want to show only your own custom activity sources
Full Includes all captured spans regardless of source Debugging or when you need complete visibility

Well-Known Auto-Instrumentation Sources

The AutoInstrumentation filter recognises these ActivitySource names:

  • Microsoft.AspNetCore
  • System.Net.Http
  • Microsoft.EntityFrameworkCore
  • Npgsql
  • StackExchange.Redis
  • Azure.Cosmos
  • Azure.Storage
  • Microsoft.Azure.Cosmos
  • OpenTelemetry.Instrumentation.Http
  • OpenTelemetry.Instrumentation.AspNetCore
  • OpenTelemetry.Instrumentation.SqlClient
  • OpenTelemetry.Instrumentation.EntityFrameworkCore
  • Kronikol.Grpc
  • Grpc.Net.Client

Manual Source Filtering

To show only specific activity sources, use Manual granularity with InternalFlowActivitySources:

new ReportConfigurationOptions
{
    InternalFlowTracking = true,
    InternalFlowSpanGranularity = InternalFlowSpanGranularity.Manual,
    InternalFlowActivitySources = ["MyApi.Services", "MyApi.Repositories"]
}

Configuration Reference

Property Type Default Description
InternalFlowTracking bool true Master switch. When true, enables internal flow popups on PlantUML sequence diagram arrows. Forces InlineSvgRendering = true and PlantUmlImageFormat.Svg for Server/Local.
InternalFlowDisplay InternalFlowDisplay Popup How the internal flow content is shown. Popup shows it in a modal overlay. Inline embeds it directly below the diagram.
InternalFlowTrigger InternalFlowTrigger Click User interaction that opens the popup. Click requires clicking the arrow. Hover shows on mouse hover.
InternalFlowDiagramStyle InternalFlowDiagramStyle ActivityDiagram Visual style for the internal flow content. ActivityDiagram renders a PlantUML activity diagram with swimlanes. CallTree renders an HTML nested list.
InternalFlowSpanGranularity InternalFlowSpanGranularity AutoInstrumentation Controls which spans are included. AutoInstrumentation filters to well-known sources. Manual uses InternalFlowActivitySources. Full includes everything.
InternalFlowActivitySources string[]? null Activity source names to include when InternalFlowSpanGranularity is Manual. Ignored for other granularity settings.
InternalFlowNoDataBehavior InternalFlowNoDataBehavior HideLink What happens when an arrow has no captured spans. HideLink (default) removes the clickable link. ShowMessage shows "No internal activity captured". VisualDistinction visually marks the arrow differently.
InternalFlowHasDataBehavior InternalFlowHasDataBehavior ShowLinkOnHover What happens when an arrow has captured spans. ShowLinkOnHover (default) shows the clickable link only on hover. ShowLink always shows the link.
InternalFlowShowFlameChart bool true When true, adds a flame chart visualisation alongside the main content.
InternalFlowFlameChartPosition InternalFlowFlameChartPosition BehindWithToggle Where the flame chart appears. Underneath places it below the main content. BehindWithToggle shows a toggle button to switch between the main view and the flame chart.
InternalFlowContentStrategy InternalFlowContentStrategy Embedded How segment data is stored. Embedded includes all data inline in the HTML. SeparateFragments writes each segment to a separate file in InternalFlowFragmentsFolderName.
InternalFlowFragmentsFolderName string "spans" Folder name for separate fragment files when InternalFlowContentStrategy is SeparateFragments. Relative to the reports folder.
InternalFlowPopupCustomStyleSheet string? null Custom CSS injected into the popup. When set, allows overriding or extending the default popup styles.

No-Data Behaviour

When the OpenTelemetry extension is not installed, or spans are not captured for a particular segment, the InternalFlowNoDataBehavior setting controls how empty segments are handled:

Value Effect
HideLink (default) The arrow is not clickable — no [[link]] is injected for segments without span data.
ShowMessage The arrow is still clickable but the popup shows a diagnostic message including the current granularity setting, configured sources, total span count, and actionable suggestions. See Diagnostics and Debugging#Empty Diagram Diagnostics.
VisualDistinction The arrow is clickable but visually styled differently (e.g. dashed or lighter colour) to indicate no data is available.

Has-Data Behaviour

When spans are captured for a segment, the InternalFlowHasDataBehavior setting controls how the clickable link is displayed:

Value Effect
ShowLinkOnHover (default) The clickable link is only visible when hovering over the diagram arrow. This keeps the diagram clean while maintaining interactivity.
ShowLink The clickable link is always visible on arrows that have internal flow data.

Content Strategy

By default, all segment data is embedded inline in the HTML report as a <script> block containing window.__iflowSegments. For large test suites with many spans, this can increase file size significantly.

Use InternalFlowContentStrategy.SeparateFragments to write each segment's data to a separate file:

new ReportConfigurationOptions
{
    InternalFlowTracking = true,
    InternalFlowContentStrategy = InternalFlowContentStrategy.SeparateFragments,
    InternalFlowFragmentsFolderName = "spans"  // default
}

This produces files like Reports/spans/iflow-{guid}.html that are loaded on demand when the user clicks an arrow.


Full Example

// Span capture is automatic — the TestTrackingMessageHandler auto-starts
// an ActivityListener for well-known instrumentation sources.
// To capture custom ActivitySources, add them to handler options:
new XUnitTestTrackingMessageHandlerOptions
{
    CallerName = "Tests",
    PortsToServiceNames = { [5001] = "MyApi" },
    InternalFlowActivitySources = ["MyApi.Services", "Microsoft.EntityFrameworkCore"]
};

// In your report generation (InternalFlowTracking defaults to true)
var options = new ReportConfigurationOptions
{
    // InternalFlowTracking = true,  // already the default
    InternalFlowDiagramStyle = InternalFlowDiagramStyle.ActivityDiagram,
    InternalFlowSpanGranularity = InternalFlowSpanGranularity.AutoInstrumentation,
    InternalFlowNoDataBehavior = InternalFlowNoDataBehavior.HideLink,
    PlantUmlRendering = PlantUmlRendering.BrowserJs  // Recommended for internal flow
};

When you open the HTML report and click a sequence diagram arrow, a popup will show the internal flow activity that occurred inside your SUT during that segment of the request processing.


Whole Test Flow

In addition to per-arrow flow popups, you can visualise the entire test's internal activity as a single flame chart or activity diagram. This shows all OTel spans captured during the test, across all HTTP boundaries, with optional boundary markers showing where each HTTP request occurred.

Configuration

The WholeTestFlowVisualization property on ReportConfigurationOptions controls this feature:

Value Behaviour
None No whole-test flow section is rendered.
FlameChart Shows a flame chart with vertical dashed boundary markers at HTTP request timestamps.
ActivityDiagram Shows a PlantUML activity diagram with all spans.
Both (default) Shows a toggle between Activity and Flame Chart views.
var options = new ReportConfigurationOptions
{
    WholeTestFlowVisualization = WholeTestFlowVisualization.Both // default
};

Output

When enabled, a collapsed <details> block titled "Whole Test Flow (N spans)" appears after the sequence diagrams for each scenario. Expanding it shows:

  • Activity view — A full PlantUML activity diagram with swimlanes grouped by ActivitySource name.
  • Flame Chart view — An HTML flame chart where each span is a horizontal bar proportional to its duration. Vertical dashed markers show when HTTP requests were made. Supports click-to-zoom — click any bar to zoom into that time range, double-click to reset. Tooltips show the source name, span name, duration, and percentage of total.

The toggle buttons let you switch between views without reloading.

LightBDD Integration

Whole-test flow is automatically integrated into LightBDD HTML reports when InternalFlowTracking is enabled. The WholeTestFlowHtmlProvider is wired up in both Kronikol.LightBDD.xUnit2 and Kronikol.LightBDD.xUnit3 packages.


Architecture

Test Execution                                    Report Generation
                                                  
┌──────────────────────────────┐                  ┌──────────────────────────────┐
│                              │                  │ InternalFlowSpanCollector    │
│ Auto-started (default):      │                  │  └─ Reads spans directly     │
│ ActivityListener (BCL)       │                  │     from InternalFlowSpanStore│
│  └─ Started by                │                  │     (no reflection)          │
│     TestTrackingMessageHandler│                  │                              │
│  └─ AllData sampling ────────┼──┐               │ InternalFlowSegmentBuilder   │
│     (non-invasive)           │  │               │  └─ Correlates spans with    │
│                              │  │               │     HTTP boundary timestamps │
│ Optional (manual):           │  │               │                              │
│ OTel TracerProvider          │  │               │ InternalFlowRenderer         │
│  └─ AddTestTrackingExporter()│  │               │  └─ ActivityDiagram or       │
│  └─ TestTrackingSpanExporter ┼──┤               │     CallTree output          │
│                              │  │               │                              │
│ Both paths write to:         │  ▼               │ InternalFlowHtmlGenerator    │
│  InternalFlowSpanStore ◄─────┼──┘               │  └─ window.__iflowSegments   │
│  (core package, static)      │                  │     JSON data block          │
│  (dedup by reference)        │                  │                              │
│                              │                  │ DiagramContextMenu           │
│ TestTrackingMessageHandler ──┼──────────────►   │  └─ Popup JS/CSS for         │
│  └─ Timestamps + TraceId     │                  │     interactive arrows       │
│     on RequestResponseLog    │                  │                              │
└──────────────────────────────┘                  └──────────────────────────────┘

Requirements and Limitations

  • PlantUML only — Internal Flow Tracking requires DiagramFormat.PlantUml.
  • Inline SVG required — The feature relies on injecting [[link]] anchors into PlantUML arrows and intercepting them in the rendered SVG. This requires inline SVG rendering (automatically enabled).
  • Zero-config by default — The TestTrackingMessageHandler auto-starts an InternalFlowActivityListener for well-known sources. InternalFlowTracking defaults to true. No extra packages, no extension methods needed for the standard case. Use InternalFlowActivitySources on TestTrackingMessageHandlerOptions for custom sources, or AddActivityListenerForInternalFlowTracking() for DI-based registration. The Kronikol.Extensions.OpenTelemetry package is only needed for manual OTel SDK exporter control via AddTestTrackingExporter(). The store deduplicates by reference, so both paths can be active without duplicates.
  • Span correlation by timestamp — Spans are correlated with HTTP boundaries using timestamps, not parent-child span relationships. This means spans are grouped into the segment whose HTTP boundary immediately precedes them.

See Also

  • Step Tracking — BDD-style step attributes for structuring tests (complementary to internal flow tracking)

Home


Demo


Getting Started

Common Tasks

Integration Guides

Extensions

Configuration

Features

Reference

Clone this wiki locally