Skip to content

Commit 122bf2b

Browse files
authored
feat: Support throughput throttling (#5)
Adds an option to throttle the throughput of spans per second. This option only makes sense for larger volumes. We will not throttle if the requested throughput is higher than what spangen can produce, but there's also no warning.
1 parent 0c14cf0 commit 122bf2b

File tree

4 files changed

+65
-6
lines changed

4 files changed

+65
-6
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
uses: actions/checkout@v4
2121

2222
- name: Install Rust Toolchain
23-
run: rustup toolchain install stable --profile minimal --component rustfmt clippy --no-self-update
23+
run: rustup toolchain install stable --profile minimal --component rustfmt,clippy --no-self-update
2424

2525
- name: Run rustfmt
2626
run: cargo fmt --check

README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ A load generator for spans in traces that adheres to the `snuba-spans` schema.
66

77
```
88
Options:
9-
--count the number of spans to generate in total.
9+
--count the number of spans to generate in total. spangen will stop
10+
generating new traces after this number has been reached,
11+
but it will finish started traces and segments. The actual
12+
number of spans generated may therefore be higher than this
13+
option.
14+
--throughput the throughput of spans per second (defaults to no
15+
throttling).
1016
--spans-per-segment
1117
the average number of spans per segment (randomized).
1218
--spans-per-segment-stddev
@@ -37,9 +43,8 @@ Options:
3743
--segments-without-root
3844
the percentage of segments without an explicit root span
3945
(0..100)
40-
--number-of-orgs the number of organizations.
41-
--number-of-projects
42-
the number of projects per organization.
46+
--orgs the number of organizations.
47+
--projects the number of projects per organization.
4348
--help, help display usage information
4449
```
4550

src/cli.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,17 @@ pub const MAX_PROJECTS: u64 = 1000;
77
#[derive(Debug, FromArgs)]
88
pub struct Config {
99
/// the number of spans to generate in total.
10+
///
11+
/// spangen will stop generating new traces after this number has been reached, but it will
12+
/// finish started traces and segments. The actual number of spans generated may therefore be
13+
/// higher than this option.
1014
#[argh(option)]
1115
pub count: usize,
1216

17+
/// the throughput of spans per second (defaults to no throttling).
18+
#[argh(option)]
19+
pub throughput: Option<u32>,
20+
1321
/// the average number of spans per segment (randomized).
1422
#[argh(option, default = "17")]
1523
pub spans_per_segment: usize,

src/main.rs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::io::{StdoutLock, Write};
2-
use std::time::Instant;
2+
use std::thread;
3+
use std::time::{Duration, Instant};
34

45
use anyhow::Result;
56
use rand::Rng;
@@ -13,6 +14,47 @@ mod cli;
1314
mod data;
1415
mod types;
1516

17+
const MIN_SLEEP: Duration = Duration::from_millis(1);
18+
19+
struct Throttle {
20+
throughput: Option<u32>,
21+
accepted: u32,
22+
last_sleep: Instant,
23+
}
24+
25+
impl Throttle {
26+
pub fn new(throughput: Option<u32>) -> Self {
27+
Self {
28+
throughput,
29+
accepted: 0,
30+
last_sleep: Instant::now(),
31+
}
32+
}
33+
34+
pub fn accept(&mut self) {
35+
if self.throughput.is_some() {
36+
self.accepted += 1;
37+
}
38+
}
39+
40+
pub fn wait(&mut self) {
41+
let Some(throughput) = self.throughput else {
42+
return;
43+
};
44+
45+
let now = Instant::now();
46+
let elapsed = now - self.last_sleep;
47+
48+
let expected_duration = (Duration::from_secs(1) * self.accepted) / throughput;
49+
let sleep_duration = expected_duration.saturating_sub(elapsed);
50+
if sleep_duration >= MIN_SLEEP {
51+
thread::sleep(sleep_duration);
52+
self.last_sleep = now + sleep_duration;
53+
self.accepted = 0;
54+
}
55+
}
56+
}
57+
1658
struct StdoutProducer {
1759
stdout: StdoutLock<'static>,
1860
}
@@ -35,6 +77,7 @@ fn produce(config: &Config) -> Result<()> {
3577
let start = Instant::now();
3678
let mut generator = RandomGenerator::new(config);
3779
let mut producer = StdoutProducer::new();
80+
let mut throttle = Throttle::new(config.throughput);
3881

3982
while generator.stats().spans < config.count {
4083
let trace = generator.trace();
@@ -44,6 +87,8 @@ fn produce(config: &Config) -> Result<()> {
4487
let segment = generator.segment(&trace);
4588
let span_refs = generator.span_refs();
4689

90+
throttle.wait();
91+
4792
for span_ref in &span_refs {
4893
let mut span = generator.span(&segment, *span_ref);
4994
if span_ref.parent_id.is_none() {
@@ -52,6 +97,7 @@ fn produce(config: &Config) -> Result<()> {
5297
}
5398

5499
producer.produce_json(&span)?;
100+
throttle.accept();
55101
}
56102

57103
// in 50% of the cases, pick a random span as the remote parent for the next segment

0 commit comments

Comments
 (0)