GenServerVirtualTime can generate beautiful flowchart reports that visualize your actor system topology with embedded statistics. This feature complements the existing sequence diagrams by showing the overall system structure and message flow patterns.
Overview
Flowchart reports show:
- Actor topology - Visual representation of your system structure
- Message flows - Arrows showing which actors communicate
- Embedded statistics - Message counts and rates directly on nodes
- Activity-based styling - Color coding based on message traffic
- Performance metrics - Virtual time speedup and simulation stats
Based on Mermaid Flowchart Syntax.
Quick Start
# Create and run a simulation
simulation = ActorSimulation.new()
|> ActorSimulation.add_actor(:producer,
send_pattern: {:rate, 100, :data},
targets: [:consumer])
|> ActorSimulation.add_actor(:consumer)
|> ActorSimulation.run(duration: 5000)
# Generate flowchart report
html = ActorSimulation.generate_flowchart_report(simulation,
title: "Producer-Consumer System")
File.write!("report.html", html)Open report.html in your browser to see:
- Interactive Mermaid flowchart
- Statistics table with message counts and rates
- Simulation summary with timing metrics
- Activity-based color coding
When to Enable Tracing
Important: Enable tracing when actors send messages dynamically via
on_match or on_receive callbacks without explicit targets:
# ✅ Tracing required - dynamic sends without targets
simulation = ActorSimulation.new(trace: true)
|> ActorSimulation.add_actor(:worker,
on_match: [
{:work, fn s -> {:send, [{:collector, :result}], s} end}
])
# ✅ No tracing needed - explicit targets defined
simulation = ActorSimulation.new()
|> ActorSimulation.add_actor(:worker,
targets: [:collector], # Explicit target
on_receive: fn msg, s -> {:send, [{:collector, msg}], s} end)
# ✅ No tracing needed - static send_pattern
simulation = ActorSimulation.new()
|> ActorSimulation.add_actor(:producer,
send_pattern: {:periodic, 100, :data},
targets: [:consumer])Without tracing, dynamic message sends won't appear as edges in the flowchart. Tracing captures the actual message flows during simulation, which is essential for accurately representing runtime message patterns.
Trade-off: Tracing has minimal overhead but collects message events. For large simulations (thousands of messages), consider:
- Using explicit
targetswhen possible - Enabling tracing only for report generation
- Running shorter simulations for documentation
Options
ActorSimulation.generate_flowchart_report(simulation,
title: "My System", # Report title
layout: "TB", # Direction: TB, LR, RL, BT
show_stats_on_nodes: true, # Show stats on nodes
show_message_labels: true, # Show message types on edges
style_by_activity: true # Color code by activity
)Layout Directions
"TB"(Top to Bottom) - Default, vertical layout"LR"(Left to Right) - Horizontal layout"RL"(Right to Left) - Horizontal, reversed"BT"(Bottom to Top) - Vertical, reversed
Node Shapes
Actors are automatically assigned shapes based on their behavior:
| Shape | Behavior | Visual |
|---|---|---|
Stadium ([...]) | Source (sends only) | Rounded pill shape |
Asymmetric >...] | Sink (receives only) | Trapezoid pointing right |
Rounded (...) | Processor (send & receive) | Rounded rectangle |
Rectangle [...] | Passive (no send pattern) | Square corners |
Subroutine [[...]] | Real process | Double brackets |
Color Coding
When style_by_activity: true (default), nodes are colored by message activity:
- Gray - Inactive (0 messages)
- Light Blue - Low activity (< 10 messages)
- Green - Medium activity (10-49 messages)
- Orange - High activity (50+ messages)
- Blue - Real processes (special indicator)
Examples
Pipeline System
forward = fn msg, state ->
{:send, [{state.next, msg}], state}
end
simulation = ActorSimulation.new()
|> ActorSimulation.add_actor(:source,
send_pattern: {:periodic, 100, :request},
targets: [:stage1])
|> ActorSimulation.add_actor(:stage1,
targets: [:stage2], # Explicit targets enable edge detection
on_receive: forward,
initial_state: %{next: :stage2})
|> ActorSimulation.add_actor(:stage2,
targets: [:sink],
on_receive: forward,
initial_state: %{next: :sink})
|> ActorSimulation.add_actor(:sink)
|> ActorSimulation.run(duration: 1000)
html = ActorSimulation.generate_flowchart_report(simulation,
title: "Pipeline Processing",
layout: "LR") # Horizontal layout
File.write!("pipeline.html", html)Pub-Sub System
simulation = ActorSimulation.new()
|> ActorSimulation.add_actor(:publisher,
send_pattern: {:rate, 10, :event},
targets: [:sub1, :sub2, :sub3])
|> ActorSimulation.add_actor(:sub1)
|> ActorSimulation.add_actor(:sub2)
|> ActorSimulation.add_actor(:sub3)
|> ActorSimulation.run(duration: 1000)
html = ActorSimulation.generate_flowchart_report(simulation,
title: "Pub-Sub System",
layout: "TB") # Vertical layout
File.write!("pubsub.html", html)Load Balanced Workers
# Enable tracing to capture dynamic sends from workers to collector
simulation = ActorSimulation.new(trace: true)
|> ActorSimulation.add_actor(:load_balancer,
send_pattern: {:burst, 3, 200, :work},
targets: [:worker1, :worker2, :worker3])
|> ActorSimulation.add_actor(:worker1,
on_match: [
{:work, fn s -> {:send, [{:collector, :result}], s} end}
])
|> ActorSimulation.add_actor(:worker2,
on_match: [
{:work, fn s -> {:send, [{:collector, :result}], s} end}
])
|> ActorSimulation.add_actor(:worker3,
on_match: [
{:work, fn s -> {:send, [{:collector, :result}], s} end}
])
|> ActorSimulation.add_actor(:collector)
|> ActorSimulation.run(duration: 1000)
html = ActorSimulation.generate_flowchart_report(simulation,
title: "Load-Balanced System")
File.write!("loadbalanced.html", html)Writing Directly to File
# Shortcut to write directly to file
{:ok, path} = ActorSimulation.write_flowchart_report(
simulation,
"my_report.html",
title: "My System"
)
IO.puts("Report written to: #{path}")Generating Just the Mermaid Code
# Get just the Mermaid flowchart code (no HTML wrapper)
mermaid = ActorSimulation.MermaidReportGenerator.generate_flowchart(simulation, %{
layout: "LR",
show_stats: true,
show_labels: true,
style_by_activity: true
})
IO.puts(mermaid)
# Output:
# flowchart LR
# producer(["producer<br/>📤 Sent: 100<br/>📥 Recv: 0"])
# consumer(["consumer<br/>📤 Sent: 0<br/>📥 Recv: 100"])
# producer -->|:data<br/>every 10ms| consumer
# style producer fill:#e8f5e9,stroke:#388e3c
# style consumer fill:#e8f5e9,stroke:#388e3cReport Components
Each HTML report includes:
1. Simulation Summary
- Virtual time duration
- Real time elapsed
- Speedup calculation (virtual time / real time)
- Termination status (normal or early)
2. Actor Topology Flowchart
- Interactive Mermaid diagram
- Nodes with embedded statistics
- Edges showing message flows
- Activity-based color coding
- Shape legend
3. Detailed Statistics Table
- Per-actor message counts
- Send and receive rates (msg/s)
- Activity level badges
- Sortable columns
4. Summary Box
- Total message count
- Simulation duration
- Number of actors
Live Examples
View live examples at:
Examples include:
- Pipeline processing
- Pub-sub systems
- Load-balanced workers
- Custom termination scenarios
- Multiple layout directions
Comparison with Sequence Diagrams
| Feature | Flowchart Reports | Sequence Diagrams |
|---|---|---|
| Shows | System topology | Message timeline |
| Best for | Architecture overview | Debugging interactions |
| Statistics | Embedded in nodes | Optional timestamps |
| Time axis | No | Yes |
| Activity view | Color coding | Message frequency |
| Use case | System design, documentation | Debugging, protocol analysis |
Recommendation: Use both! Flowcharts for architecture and statistics, sequence diagrams for detailed message flows.
Integration with CI/CD
# In your test suite
defmodule MySystemTest do
use ExUnit.Case
test "generates system report", %{tmp_dir: tmp_dir} do
simulation = build_simulation()
|> ActorSimulation.run(duration: 5000)
report_path = Path.join(tmp_dir, "system_report.html")
{:ok, _} = ActorSimulation.write_flowchart_report(
simulation,
report_path,
title: "System Test Report"
)
# Optionally upload to artifact storage
upload_test_artifact(report_path)
end
endCustomization
For advanced customization, use the MermaidReportGenerator module directly:
alias ActorSimulation.MermaidReportGenerator
# Custom options
html = MermaidReportGenerator.generate_report(simulation,
title: "Custom Report",
show_stats_on_nodes: false, # Hide stats from nodes
show_message_labels: false, # Hide message details
layout: "RL",
style_by_activity: false # No color coding
)API Reference
See full API documentation:
ActorSimulation.generate_flowchart_report/2ActorSimulation.write_flowchart_report/3ActorSimulation.MermaidReportGenerator