Packages
A DSL for simulating actor systems in virtual time: send patterns, declarative handlers, statistics, sequence diagrams and flowchart reports, driven by gen_server_virtual_time's clock so a simulated hour takes milliseconds.
Current section
Files
Jump to
Current section
Files
docs/flowchart_reports.md
# Mermaid Flowchart Reports
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](https://mermaid.js.org/syntax/flowchart.html).
## Quick Start
```elixir
# 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`:
```elixir
# ✅ 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 `targets` when possible
- Enabling tracing only for report generation
- Running shorter simulations for documentation
## Options
```elixir
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 behaviour:
| Shape | Behaviour | Visual |
| ------------------- | ------------------------------ | ------------------- |
| Stadium `([...])` | Source (sends, never receives) | Rounded pill shape |
| Rounded `(...)` | Processor (sends and receives) | Rounded rectangle |
| Rectangle `[...]` | Sink or passive (receives only, or no send pattern, or a real process) | Square corners |
## Color Coding
With `style_by_activity: true` (the default), nodes are coloured by their total
message activity — `sent_count + received_count` — and real processes are
coloured the same way as simulated actors:
- **Gray** - Inactive (0 messages)
- **Light Blue** - Low activity (fewer than 10 messages)
- **Green** - Medium activity (10 to 49 messages)
- **Orange** - High activity (50 or more messages, drawn with a thicker border)
## Examples
### Pipeline System
```elixir
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
```elixir
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
```elixir
# 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
```elixir
# 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
```elixir
# 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:#388e3c
```
## Report 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:
- [Interactive Flowchart Reports](https://d-led.github.io/gen_server_virtual_time/examples/reports/)
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
```elixir
# 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
end
```
## Customization
For advanced customization, use the `MermaidReportGenerator` module directly:
```elixir
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/2`
- `ActorSimulation.write_flowchart_report/3`
- `ActorSimulation.MermaidReportGenerator`
## Learn More
- [Mermaid Flowchart Syntax](https://mermaid.js.org/syntax/flowchart.html)
- [Project README](README.md)
- [Sequence diagrams](README.md#visualize-with-sequence-diagrams)
- [Live Examples](https://d-led.github.io/gen_server_virtual_time/examples/)