Skip to content

Component Diagrams

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

Component Diagrams (C4-style)

Kronikol can aggregate all tracked interactions across your entire test suite to auto-generate a C4-style component diagram. This diagram shows every discovered participant (services, event brokers, databases) and their relationships — giving you a high-level architecture overview derived directly from real test traffic.

This feature is enabled by default (GenerateComponentDiagram = true).


Table of Contents


Quick Start

Add GenerateComponentDiagram = true to your ReportConfigurationOptions (this is the default):

var options = new ReportConfigurationOptions
{
    GenerateComponentDiagram = true  // already the default
};

That's it. When your test run completes and reports are generated, a ComponentDiagram.puml and ComponentDiagram.html file will appear in the Reports/ directory alongside your existing report files.


How It Works

RequestResponseLogger.RequestAndResponseLogs  (all tests, already in memory)
        │
        ▼
ComponentDiagramGenerator.ExtractRelationships()
        │  Filters: ignores responses, override markers, action markers, TrackingIgnore
        │  Groups by: (CallerName, ServiceName, Protocol)
        │  Counts: total calls, distinct test IDs, distinct HTTP methods
        ▼
ComponentDiagramGenerator.GeneratePlantUml()
        │  Classifies participants as Person() or System()
        │  Generates C4 Component PlantUML syntax
        ▼
ComponentDiagramReportGenerator
        │  Writes ComponentDiagram.puml  (raw PlantUML)
        │  Writes ComponentDiagram.html  (HTML wrapper)
        ▼
Reports/ directory
  1. Extract — All RequestResponseLog entries accumulated during the test run are filtered to only include meaningful request logs (excluding responses, override markers, action start markers, and ignored logs). They are then grouped by the unique combination of (CallerName, ServiceName, Protocol) to discover relationships. When a log has a DependencyCategory set (e.g. "CosmosDB" from the CosmosDB extension), it is used as the Protocol instead of "HTTP".

  2. Aggregate — For each relationship, the generator counts total calls, distinct test IDs (how many tests exercised this path), and the distinct HTTP methods or event types used. The DependencyCategory from the first matching log is preserved on the relationship for shape and color determination.

  3. Generate — The aggregated relationships are converted into PlantUML C4 Component diagram syntax. Participants are classified as Person() (pure callers), System() (HTTP APIs), SystemDb() (databases/storage), or SystemQueue() (message queues) based on their DependencyCategory. Arrows are coloured by dependency type by default.

  4. Write — The PlantUML source is written to a .puml file, wrapped in an HTML page for standalone viewing, and optionally embedded in the TestRunReport (hidden by default, with a toolbar toggle button to show/hide).


Configuration

Enabling Component Diagrams

GenerateComponentDiagram defaults to true. To disable:

var options = new ReportConfigurationOptions
{
    GenerateComponentDiagram = false  // opt out
};

To customise the diagram:

var options = new ReportConfigurationOptions
{
    ComponentDiagramOptions = new ComponentDiagramOptions
    {
        Title = "My Service Architecture"
    }
};

When GenerateComponentDiagram is false, no component diagram files are generated and there is zero overhead.

ComponentDiagramOptions

Property Type Default Description
FileName string "ComponentDiagram" Base filename for the generated .puml and .html files
EmbedInTestRunReport bool true When true, the component diagram is embedded in the TestRunReport (hidden by default) with a "Component Diagram" toggle button in the toolbar. Clicking the button shows/hides the diagram. The standalone file is always generated.
Title string "Component Diagram" The title displayed in the PlantUML diagram and HTML page
PlantUmlTheme string? null Optional PlantUML theme name (e.g. "cerulean", "superhero")
ParticipantFilter Func<string, bool>? null Predicate to include/exclude participants by name
RelationshipLabelFormatter Func<ComponentRelationship, string>? null Custom function to format relationship labels
ShowRelationshipFlows bool true Adds a clickable relationship list below the component diagram. Each relationship opens a popup showing the aggregated internal flow (OTel spans) for that caller→service path. Requires InternalFlowTracking = true.
RelationshipFlowStyle InternalFlowDiagramStyle ActivityDiagram Visual style for relationship flow popups. ActivityDiagram shows a PlantUML diagram. CallTree shows an HTML nested list.
ShowSystemFlameChart bool true Adds a system-level flow section below the component diagram showing performance analytics (bar chart, status codes, payload sizes, outliers, etc.).
LowCoverageThreshold int 3 Relationships with fewer than this many calls are rendered with dashed arrows and labelled as low-coverage.
ArrowColorMode ArrowColorMode DependencyType Controls arrow coloring. DependencyType colors arrows by target service type (blue=HTTP, red=Database, etc.). Performance uses P95-based green/orange/red hotspot coloring.
DependencyColors Dictionary<string, string>? null Per-category color overrides (key = category string like "CosmosDB", value = hex color like "#FF0000"). Also inheritable from ReportConfigurationOptions.DependencyColors.
MaxFlameChartTests int 50 Legacy option retained for backward compatibility.

Output Files

When enabled, two files are generated in the Reports/ directory:

File Description
ComponentDiagram.html A standalone HTML page containing the PlantUML source in a collapsible <details> element. Can be opened directly in a browser.

The filenames can be customised via ComponentDiagramOptions.FileName.


Participant Classification

Participants are automatically classified based on their role in the tracked interactions and their dependency type:

Classification C4 Element Plain PlantUML Shape Rule
Person Person(alias, "Name") rectangle <<person>> The participant only appears as a CallerName and never as a ServiceName in any tracked log. This is typically your test client / SUT caller.
HTTP API System(alias, "Name") rectangle <<system>> Default for services with no DependencyCategory or HTTP-based dependencies.
Database SystemDb(alias, "Name") database Services with DependencyCategory of "CosmosDB", "SQL", "BigQuery", "MongoDB", "DynamoDB", "Elasticsearch", "Spanner", "Bigtable", or "Database".
Cache System(alias, "Name") collections Services with DependencyCategory of "Redis".
Message Queue SystemQueue(alias, "Name") queue Services with DependencyCategory of "ServiceBus".
Storage SystemDb(alias, "Name") database Services with DependencyCategory of "BlobStorage".

The DependencyCategory is automatically set by each extension package. For example, CosmosTrackingMessageHandler sets DependencyCategory: "CosmosDB" on every tracked log, which causes the CosmosDB service to render as a database shape with a red color.


Relationship Labels

By default, each relationship label contains:

<Protocol>: <Methods> — <CallCount> calls across <TestCount> tests

Examples:

  • HTTP: GET, POST — 14 calls across 8 tests
  • Publish: Publish — 3 calls across 2 tests
  • CosmosDB: Query, Upsert — 5 calls across 3 tests

The protocol is determined automatically:

  • HTTP — For standard HTTP requests (MetaType == Default). The distinct HTTP method names (GET, POST, PUT, etc.) are listed.
  • Event/Custom — For non-HTTP interactions (MetaType == Event), the protocol is the string value of the Method property on RequestResponseLog (e.g. "Publish", "CosmosDB", "SQL").

Stats-Driven Labels

When request/response timestamp pairs are available, relationship labels automatically include latency percentiles:

HTTP: GET, POST
P50: 45ms | P95: 120ms | P99: 250ms
14 calls across 8 tests

If a relationship has a non-zero error rate, it is also shown:

P50: 45ms | P95: 120ms | P99: 250ms | 15% errors

Labels are clickable (via [[#iflow-rel-...]] PlantUML links) and open the relationship flow popup.

Dependency-Type Arrow Colouring (Default)

By default, arrows are coloured by the target service's dependency type using the following palette:

Dependency Type Colour Hex
HTTP API Blue #438DD5
Database (CosmosDB, SQL, BigQuery) Red #E74C3C
Cache (Redis) Orange #F39C12
Message Queue (ServiceBus) Purple #9B59B6
Storage (BlobStorage) Green #2ECC71
Unknown Grey #95A5A6

This provides an instant visual indicator of what type of dependency each arrow represents. The same colors are applied to both sequence diagrams and component diagrams.

To override specific colors:

var options = new ReportConfigurationOptions
{
    DependencyColors = new Dictionary<string, string>
    {
        ["CosmosDB"] = "#FF0000",   // custom red for CosmosDB
        ["Redis"] = "#00FF00"       // custom green for Redis
    }
};

Performance-Based Arrow Colouring (Opt-in)

To switch to P95 latency-based arrow colouring (the previous default), set ArrowColorMode.Performance:

var options = new ReportConfigurationOptions
{
    ComponentDiagramOptions = new ComponentDiagramOptions
    {
        ArrowColorMode = ArrowColorMode.Performance
    }
};
P95 Latency Colour Meaning
< 50ms Green Fast
50ms – 200ms Orange Moderate
> 200ms Red Slow (hotspot)

This provides an instant visual indicator of which service dependencies are performance bottlenecks.

Low-Coverage Warnings

Relationships with fewer calls than LowCoverageThreshold (default: 3) are rendered with dashed arrows (..>) instead of solid arrows. This highlights service paths that may not have sufficient test coverage to produce reliable statistics.


Customisation

Custom Title

var options = new ComponentDiagramOptions
{
    Title = "Order Service Architecture"
};

PlantUML Theme

Apply any built-in PlantUML theme:

var options = new ComponentDiagramOptions
{
    PlantUmlTheme = "cerulean"
};

This inserts !theme cerulean into the generated PlantUML.

Participant Filter

Exclude specific participants from the component diagram:

var options = new ComponentDiagramOptions
{
    // Exclude internal helper services and health check endpoints
    ParticipantFilter = name => name != "InternalHelper" && name != "HealthCheck"
};

The filter is a Func<string, bool> that receives the participant name. Return true to include, false to exclude. When a participant is excluded, all relationships involving that participant (as either caller or service) are removed.

Custom Relationship Labels

Override the default label format with a custom formatter:

var options = new ComponentDiagramOptions
{
    RelationshipLabelFormatter = rel =>
        $"{rel.Protocol} ({rel.CallCount} calls)"
};

The ComponentRelationship record passed to the formatter contains:

Property Type Description
Caller string The calling participant name
Service string The receiving participant name
Protocol string "HTTP" or the event/custom protocol name
Methods HashSet<string> Distinct method names (e.g. GET, POST, Publish)
CallCount int Total number of calls across all tests
TestCount int Number of distinct tests that exercised this relationship

Example Output

Given a test suite that exercises an Order Service with downstream dependencies:

Generated PlantUML (ComponentDiagram.puml)

@startuml
!include <C4/C4_Component>

title Order Service Architecture

Person(webApp, "WebApp")
System(orderService, "OrderService")
System(paymentService, "PaymentService")
System(kafka, "Kafka")
System(cosmosDb, "CosmosDB")

Rel(webApp, orderService, "[[#iflow-rel-WebApp-OrderService HTTP: GET, POST]]\nP50: 42ms | P95: 120ms | P99: 250ms\n14 calls across 8 tests", $tags="#Orange")
Rel(orderService, paymentService, "[[#iflow-rel-OrderService-PaymentService HTTP: POST]]\nP50: 150ms | P95: 400ms | P99: 500ms | 2% errors\n6 calls across 4 tests", $tags="#Red")
Rel(orderService, kafka, "[[#iflow-rel-OrderService-Kafka Publish: Publish]]\nP50: 5ms | P95: 15ms | P99: 25ms\n3 calls across 2 tests", $tags="#Green")
orderService ..> cosmosDb : "[[#iflow-rel-OrderService-CosmosDB CosmosDB: Query, Upsert]]\nP50: 8ms | P95: 20ms | P99: 40ms\n2 calls across 1 tests"

@enduml

In this example:

  • The Kafka arrow is green (P95 < 50ms)
  • The OrderService → WebApp arrow is orange (P95 50–200ms)
  • The PaymentService arrow is red (P95 > 200ms) with error rate shown
  • The CosmosDB arrow is dashed (only 2 calls, below the low-coverage threshold of 3)
  • All labels are clickable and open relationship flow popups

Performance Summary Table

The component diagram HTML page includes a System Flow section with a sortable performance summary table showing aggregated statistics for every relationship:

Column Description
Relationship Caller → Service. Click to expand per-endpoint breakdown rows. Includes ⚠ badge for low-coverage relationships and 🔺 badge for outlier detection.
Calls Total number of calls across all tests
Mean Mean latency in milliseconds
P50 50th percentile (median) latency
P95 95th percentile latency
P99 99th percentile latency
CV Coefficient of Variation (stdDev ÷ mean). Colour-coded: green (< 0.3, consistent), orange (0.3–0.7, moderate), red (> 0.7, highly variable)
Errors Error rate percentage (status codes ≥ 400)

Click any column header to sort ascending/descending. Sorting preserves the parent-child relationship between main rows and their endpoint breakdown sub-rows.

Click a relationship row to expand per-endpoint breakdown rows showing individual stats for each HTTP method + path combination (e.g. GET /api/orders, POST /api/orders).


Latency Distribution Bar Chart

Below the performance table, a horizontal bar chart visualises latency across all relationships. Toggle buttons let you switch between Mean, P50, P95 (default), and P99 views. Bar width is proportional to the maximum value across all relationships.


Latency Variance (CV)

The Coefficient of Variation (CV = stdDev ÷ mean) measures how consistent a relationship's latency is across calls:

CV Range Colour Interpretation
< 0.3 Green Consistent — latency is predictable
0.3 – 0.7 Orange Moderate variability
> 0.7 Red Highly variable — investigate potential issues

A high CV often indicates intermittent performance problems such as cold starts, resource contention, or retry storms. The CV column appears in the performance summary table.


Outlier Detection

For relationships with ≥ 5 calls, the system automatically detects outlier tests where the latency exceeds mean + 2σ (two standard deviations above the mean).

When outliers are detected:

  • A 🔺 N badge appears in the Relationship column of the performance table
  • A dedicated Outlier Detection section appears below the main analytics, showing:
    • The threshold used (mean + 2σ in milliseconds)
    • Number of outlier tests
    • Top 5 outlier tests with their duration and deviation from mean (e.g. "3.2σ")

Relationships with fewer than 5 calls skip outlier detection (insufficient data for meaningful statistics).


Latency Contribution

When the test suite exercises multiple dependencies, the Latency Contribution section shows a stacked bar chart visualising what percentage of total test time each dependency consumes, averaged across all tests.

For example, if OrderService consistently takes 200ms and PaymentService takes 50ms, the chart shows OrderService at ~80% and PaymentService at ~20%. This helps identify which dependencies dominate your test execution time.

Only shown when there are 2+ relationships with latency data.


Request Method Distribution

When a relationship uses multiple HTTP methods (e.g. GET and POST), the Request Methods section shows a colour-coded stacked bar per relationship:

Method Colour
GET Blue
POST Green
PUT Orange
DELETE Red
PATCH Purple

Each segment shows the method name and is sized proportionally to its call count. This section is hidden when all calls to a relationship use the same method (no useful information to display).


Status Codes & Payload Sizes

The report includes additional insight sections when data is available:

  • Status Codes — Per-relationship breakdown showing the distribution of HTTP status codes (e.g. 200: 45, 500: 3), colour-coded by status class (2xx green, 4xx orange, 5xx red)
  • Payload Sizes — Request and response mean/P95 payload sizes per relationship, formatted as B/KB/MB

Concurrent Call Detection

When the system detects overlapping in-flight calls from the same caller within the same test (e.g. parallel HTTP requests), a Concurrent Calls section appears showing:

Column Description
Relationship The relationship where concurrency was detected
Concurrent Tests Number of tests with overlapping calls
Percentage % of tests that exhibited concurrency
With Which other services had overlapping calls

Error Correlation

The Error Correlations section performs pairwise co-occurrence analysis across tests. When relationship A errors, how often does relationship B also error in the same test?

Column Description
When this errors... The relationship that errors
This also errors The co-occurring erroring relationship
Co-occurrence Percentage of the time both error together
Count co-occurrences / total error tests

Only shown when co-occurrence is ≥ 50% with ≥ 2 error tests. This helps identify cascading failure patterns (e.g. "when the auth service fails, the order service always fails too").


Call Ordering Patterns

The Call Ordering section aggregates the sequence of dependency calls across all tests to identify dominant patterns. For each pair of services that appear in the same test, it reports what percentage of tests call one before the other.

For example: "100% of tests call OrderService before PaymentService" or "70% of tests call AuthService before OrderService".

Only patterns with ≥ 3 samples and ≥ 60% dominance are shown, filtering out noise from low-sample or evenly-split orderings.


Interactive Focus Mode

When using PlantUmlRendering.BrowserJs (the default), clicking a service node in the component diagram dims all unrelated nodes and relationships, letting you focus on a single service's connections. Click the same node again (or click the background) to restore the full view.


Diff Mode

Compare component diagrams between test runs to detect architectural changes:

var diff = ComponentDiagramDiffer.Compare(
    baselineRelationships, currentRelationships,
    baselineStats, currentStats);

var diffPuml = ComponentDiagramDiffer.GenerateDiffPlantUml(diff);

The DiffResult includes added/removed relationships, new/removed services, and performance regressions/improvements based on P95 latency changes.

Visual Rendering

The diff PlantUML diagram uses colour coding to highlight changes:

Element Rendering
New relationships Green (#LimeGreen) solid arrow labelled NEW
Removed relationships Red (#Red) dashed arrow labelled REMOVED
Unchanged relationships Default arrow labelled unchanged
New services Green background participant
Removed services Red background participant

Pass useC4: false to GenerateDiffPlantUml to use custom skinparam styling instead of C4 includes:

var diffPuml = ComponentDiagramDiffer.GenerateDiffPlantUml(diff, title: "My Diff", useC4: false);

Performance Regression/Improvement Detection

When you pass RelationshipStats dictionaries for both baseline and current runs, the differ detects performance changes on unchanged relationships:

  • Regression: P95 latency increased by more than 20% compared to baseline
  • Improvement: P95 latency decreased by more than 20% compared to baseline

The threshold is currently hardcoded at 20%. Each StatsChange entry includes the relationship, both stats snapshots, and the absolute P95 delta in milliseconds.

Integration with PlantUML IKVM

The component diagram feature works with the standard PlantUML server rendering. If you're using PlantUML IKVM for local rendering, the .puml file can still be rendered locally using the IKVM renderer, but the component diagram report itself currently outputs raw PlantUML source in the HTML file rather than a rendered image.

Future versions may add support for rendering the component diagram as an image via the local renderer.


Performance

The component diagram feature has negligible performance impact:

Aspect Impact
Data source Reads the same RequestResponseLogger.RequestAndResponseLogs that are already in memory — no additional data collection
Processing Single O(n) pass over log entries with GroupBy — microseconds for typical test suites (100–10,000 logs)
Memory Aggregated relationships are O(unique participant pairs), typically < 50 entries
File I/O Two small files (.puml + .html) written in parallel with existing report generation
When disabled Zero cost — a single if guard at the entry point

Limitations & Known Behaviours

  1. Request-only aggregation — Only request logs are counted. Response logs are excluded to avoid double-counting (a request and its response represent a single interaction).

  2. Protocol detection — HTTP interactions (MetaType == Default) are grouped under the "HTTP" protocol. Event/message interactions (MetaType == Event) use the Method value as the protocol name. If multiple event types share the same method name but different service names, they are tracked as separate relationships.

  3. Override and action markersIsOverrideStart, IsOverrideEnd, and IsActionStart log entries are excluded from aggregation, consistent with how they're handled in sequence diagrams.

  4. Static data — The component diagram is generated from RequestResponseLogger.RequestAndResponseLogs, which is a static concurrent queue. In test runs where the queue is shared across frameworks or assemblies, all interactions are included.

  5. Browser SVG rendering — When PlantUmlRendering.BrowserJs is used (the default), the component diagram is rendered as an interactive SVG with clickable links and focus mode. When using PlantUmlRendering.Server, the diagram is rendered as a static <img> tag.

  6. Deprecated GanttInternalFlowRenderer.RenderGantt() and ComponentFlowSegmentBuilder.BuildSystemSegment() are marked [Obsolete]. They have been replaced by the latency bar chart and performance analytics sections.


API Reference

ComponentDiagramGenerator (static class)

// Extract unique relationships from tracked logs
public static ComponentRelationship[] ExtractRelationships(
    IEnumerable<RequestResponseLog> logs,
    Func<string, bool>? participantFilter = null);

// Generate C4 PlantUML from relationships (with optional stats)
public static string GeneratePlantUml(
    ComponentRelationship[] relationships,
    ComponentDiagramOptions? options = null,
    Dictionary<string, RelationshipStats>? stats = null,
    bool useC4 = true);

ComponentDiagramReportGenerator (static class)

// Generate the .puml and .html report files
public static ComponentDiagramResult GenerateComponentDiagramReport(
    IEnumerable<RequestResponseLog> logs,
    ReportConfigurationOptions reportOptions,
    Dictionary<string, InternalFlowSegment>? perBoundarySegments = null,
    Dictionary<string, InternalFlowSegment>? wholeTestSegments = null);

ComponentDiagramDiffer (static class)

// Compare two sets of relationships to detect changes
public static DiffResult Compare(
    ComponentRelationship[] baseline,
    ComponentRelationship[] current,
    Dictionary<string, RelationshipStats>? baselineStats = null,
    Dictionary<string, RelationshipStats>? currentStats = null);

// Generate a PlantUML diagram highlighting the diff
public static string GenerateDiffPlantUml(DiffResult diff, string title = "Component Diagram Diff", bool useC4 = true);

DiffResult (record)

public record DiffResult(
    ComponentRelationship[] Added,
    ComponentRelationship[] Removed,
    ComponentRelationship[] Unchanged,
    string[] NewServices,
    string[] RemovedServices,
    StatsChange[] Regressions,
    StatsChange[] Improvements);

StatsChange (record)

public record StatsChange(
    ComponentRelationship Relationship,  // The affected relationship
    RelationshipStats Baseline,          // Stats from the baseline run
    RelationshipStats Current,           // Stats from the current run
    double P95DeltaMs);                  // Absolute P95 change in milliseconds

ComponentDiagramResult (record)

public record ComponentDiagramResult(
    string HtmlFilePath,    // Absolute path to the generated .html file
    string PlantUml);       // The raw PlantUML string

ComponentRelationship (record)

public record ComponentRelationship(
    string Caller,              // Calling participant name
    string Service,             // Receiving participant name
    string Protocol,            // "HTTP" or custom protocol name
    HashSet<string> Methods,    // Distinct method names
    int CallCount,              // Total calls across all tests
    int TestCount,              // Distinct test count
    string? DependencyCategory = null);

ComponentDiagramOptions (record)

public record ComponentDiagramOptions
{
    public string FileName { get; set; } = "ComponentDiagram";
    public bool EmbedInTestRunReport { get; set; } = true;
    public string Title { get; set; } = "Component Diagram";
    public string? PlantUmlTheme { get; set; }
    public Func<string, bool>? ParticipantFilter { get; set; }
    public Func<ComponentRelationship, string>? RelationshipLabelFormatter { get; set; }
    public bool ShowRelationshipFlows { get; set; } = true;
    public InternalFlowDiagramStyle RelationshipFlowStyle { get; set; } = InternalFlowDiagramStyle.ActivityDiagram;
    public bool ShowSystemFlameChart { get; set; } = true;
    public int LowCoverageThreshold { get; set; } = 3;
    public int MaxFlameChartTests { get; set; } = 50;
    public ArrowColorMode ArrowColorMode { get; set; } = ArrowColorMode.DependencyType;
    public Dictionary<string, string>? DependencyColors { get; set; }
}

ReportConfigurationOptions (new properties)

public bool GenerateComponentDiagram { get; set; } = true;           // Default: true
public ComponentDiagramOptions? ComponentDiagramOptions { get; set; }  // Default: null

Home


Demo


Getting Started

Common Tasks

Integration Guides

Extensions

Configuration

Features

Reference

Clone this wiki locally