Skip to content

Commit 2c87253

Browse files
committed
Add examples directory to samply-markers
This commit adds an examples directory with instructions on how to build, run, and profile the examples shown in the main README file.
1 parent 6f15b39 commit 2c87253

File tree

9 files changed

+673
-3
lines changed

9 files changed

+673
-3
lines changed

Cargo.lock

Lines changed: 251 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,8 @@ exclude = ["etw-reader"] # Should not be compiled on non-Windows
2323
[profile.dist]
2424
inherits = "release"
2525
lto = "thin"
26+
27+
# Profile for profiling with samply
28+
[profile.profiling]
29+
inherits = "release"
30+
debug = true

samply-markers/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,9 @@ regex = "1.12.2"
3434
serial_test = "3.2.0"
3535
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "time"] }
3636
futures = "0.3"
37+
38+
[dev-dependencies]
39+
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
40+
reqwest = "0.12"
41+
futures = "0.3"
42+
rayon = "1.10"

samply-markers/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,9 @@ async fn load_config(path: &str) -> (u32, u32) {
273273

274274
Here's a complete example demonstrating everything in context:
275275

276+
> [!NOTE]
277+
> See the [examples directory] to build, run, and profile the examples in this README yourself.
278+
276279

277280
**[macOS] | [Ubuntu] | Windows (not yet supported)**
278281

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use samply_markers::prelude::*;
2+
3+
fn fib(n: u64) -> u64 {
4+
samply_measure!({
5+
match n {
6+
0 | 1 => n,
7+
_ => fib(n - 1) + fib(n - 2),
8+
}
9+
}, marker: {
10+
name: format!("fib({n})"),
11+
})
12+
}
13+
14+
fn main() {
15+
let n = 10;
16+
let value = fib(n);
17+
println!("fib({n}) = {}", value);
18+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use samply_markers::prelude::*;
2+
3+
async fn fetch_url(url: &str) -> (String, Option<String>) {
4+
// Emit an interval marker for the time it takes to fetch.
5+
let result = samply_measure!(async {
6+
let response = reqwest::get(url).await?;
7+
response.text().await
8+
}, marker: {
9+
name: format!("fetch {url}"),
10+
})
11+
.await;
12+
13+
match result {
14+
Ok(content) => {
15+
println!(" ✓ Fetched {url} ({} bytes)", content.len());
16+
(String::from(url), Some(content))
17+
}
18+
Err(error) => {
19+
// Emit an instant marker any time a fetch fails.
20+
samply_marker!({ name: format!("fetch failed: {url}") });
21+
println!(" ✗ Failed to fetch {url}: {error}");
22+
(String::from(url), None)
23+
}
24+
}
25+
}
26+
27+
#[tokio::main]
28+
async fn main() {
29+
// Create a timer that will span the entirety of main().
30+
// The timer will emit an interval marker when it is dropped at the end of the scope.
31+
let _main_timer = samply_timer!({ name: "main()" });
32+
33+
println!("\nStarting samply-markers demo...\n");
34+
35+
std::thread::sleep(std::time::Duration::from_millis(200));
36+
37+
let urls = &[
38+
"https://example.com",
39+
"https://fail.invalid",
40+
"https://fail.invalid",
41+
"https://en.wikipedia.org/wiki/Firefox",
42+
"https://fail.invalid",
43+
"https://github.com/nordzilla",
44+
];
45+
46+
println!("\n=== Sequential Fetching ===\n");
47+
48+
// Emit an interval marker for the total time to fetch all URLs sequentially.
49+
samply_measure!({
50+
for url in urls {
51+
fetch_url(url).await;
52+
}
53+
}, marker: {
54+
name: "fetch all URLs sequentially",
55+
});
56+
57+
std::thread::sleep(std::time::Duration::from_millis(200));
58+
59+
println!("\n=== Concurrent Fetching ===\n");
60+
61+
// Emit an interval marker for the total time to fetch all URLs concurrently.
62+
samply_measure!({
63+
futures::future::join_all(
64+
urls.iter().map(|url| fetch_url(url))
65+
).await;
66+
}, marker: {
67+
name: "fetch all URLs concurrently",
68+
});
69+
70+
std::thread::sleep(std::time::Duration::from_millis(200));
71+
72+
println!("\nDemo completed!\n");
73+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use samply_markers::prelude::*;
2+
use tokio::time::{Duration, sleep};
3+
4+
async fn migrating_task(task_id: usize, iterations: usize) {
5+
for i in 0..iterations {
6+
samply_measure!(async {
7+
samply_marker!({ name: format!("task({task_id}): iteration {i} start") });
8+
9+
sleep(Duration::from_micros(100)).await;
10+
11+
let mut sum = 0u64;
12+
for j in 0..10_000 {
13+
sum = sum.wrapping_add(j);
14+
}
15+
16+
sleep(Duration::from_micros(100)).await;
17+
}, marker: {
18+
name: format!("task {task_id} iter {i}"),
19+
})
20+
.await;
21+
22+
// Yield to encourage task migration.
23+
tokio::task::yield_now().await;
24+
}
25+
}
26+
27+
#[tokio::main(flavor = "multi_thread", worker_threads = 10)]
28+
async fn main() {
29+
let _main_timer = samply_timer!({ name: "main()" });
30+
31+
println!("\n=== Tokio Task Migration Demo ===\n");
32+
println!("This example spawns async tasks on a multi-threaded runtime.");
33+
println!("Tasks will migrate between worker threads during execution.");
34+
35+
let num_tasks = 8;
36+
let iterations_per_task = 20;
37+
38+
samply_marker!({ name: "spawning tasks" });
39+
40+
let handles = (0..num_tasks)
41+
.map(|task_id| {
42+
tokio::spawn(async move { migrating_task(task_id, iterations_per_task).await })
43+
})
44+
.collect::<Vec<_>>();
45+
46+
samply_marker!({ name: "all tasks spawned" });
47+
48+
samply_measure!({
49+
futures::future::join_all(handles).await
50+
}, marker: {
51+
name: "waiting for all tasks",
52+
});
53+
54+
println!("\nAll tasks completed!");
55+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
use rayon::{ThreadPoolBuilder, prelude::*};
2+
use samply_markers::prelude::*;
3+
use std::{collections::HashSet, time::Duration};
4+
5+
fn contains_digit(mut value: u32, digit: u32) -> bool {
6+
if value == 0 {
7+
return digit == 0;
8+
}
9+
10+
while value > 0 {
11+
if value % 10 == digit {
12+
return true;
13+
}
14+
value /= 10;
15+
}
16+
17+
false
18+
}
19+
20+
fn filter_numbers_with_digit(
21+
chunk_id: usize,
22+
numbers: &[u32],
23+
digit: u32,
24+
parallel_filters: bool,
25+
) -> Vec<u32> {
26+
let mode = if parallel_filters {
27+
"parallel"
28+
} else {
29+
"sequential"
30+
};
31+
32+
samply_measure!({
33+
if parallel_filters {
34+
numbers
35+
.par_iter()
36+
.filter(|&&n| contains_digit(n, digit))
37+
.cloned()
38+
.collect()
39+
} else {
40+
numbers
41+
.iter()
42+
.filter(|&&n| contains_digit(n, digit))
43+
.cloned()
44+
.collect()
45+
}
46+
}, marker: {
47+
name: format!("chunk {chunk_id} filter digit {digit} ({mode})"),
48+
})
49+
}
50+
51+
fn symmetric_difference_sum(chunk_id: usize, twos: &[u32], fives: &[u32]) -> u64 {
52+
samply_measure!({
53+
let two_set: HashSet<u32> = twos.iter().copied().collect();
54+
let five_set: HashSet<u32> = fives.iter().copied().collect();
55+
56+
let only_twos: u64 = two_set
57+
.difference(&five_set)
58+
.map(|&value| value as u64)
59+
.sum();
60+
let only_fives: u64 = five_set
61+
.difference(&two_set)
62+
.map(|&value| value as u64)
63+
.sum();
64+
65+
only_twos + only_fives
66+
}, marker: {
67+
name: format!("chunk {chunk_id} symmetric diff"),
68+
})
69+
}
70+
71+
fn process_chunk_pipeline(chunk_id: usize, numbers: &[u32], parallel_filters: bool) -> u64 {
72+
let _chunk_timer = samply_timer!({ name: format!("chunk {chunk_id} pipeline") });
73+
74+
let twos = filter_numbers_with_digit(chunk_id, numbers, 2, parallel_filters);
75+
let fives = filter_numbers_with_digit(chunk_id, numbers, 5, parallel_filters);
76+
77+
symmetric_difference_sum(chunk_id, &twos, &fives)
78+
}
79+
80+
fn generate_chunks(chunk_count: usize, chunk_len: usize) -> Vec<Vec<u32>> {
81+
let mut seed = 0u64;
82+
83+
(0..chunk_count)
84+
.map(|_| {
85+
let mut values = Vec::with_capacity(chunk_len);
86+
for _ in 0..chunk_len {
87+
seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
88+
values.push((seed % 1_000_000) as u32);
89+
}
90+
values
91+
})
92+
.collect()
93+
}
94+
95+
fn main() {
96+
let _main_timer = samply_timer!({ name: "main()" });
97+
98+
ThreadPoolBuilder::new()
99+
.num_threads(8)
100+
.build_global()
101+
.expect("Failed to configure Rayon thread pool");
102+
103+
println!("\n=== Digit Symmetric Difference Demo ===\n");
104+
105+
let chunk_count = 32;
106+
let chunk_len = 131072;
107+
let chunks = generate_chunks(chunk_count, chunk_len);
108+
109+
println!(
110+
"Generated {} chunks with {} numbers each ({} total)\n",
111+
chunk_count,
112+
chunk_len,
113+
chunk_count * chunk_len
114+
);
115+
116+
println!("=== Sequential Processing ===");
117+
let sequential_results: Vec<_> = samply_measure!({
118+
chunks
119+
.iter()
120+
.enumerate()
121+
.map(|(i, chunk)| process_chunk_pipeline(i, chunk, false))
122+
.collect()
123+
}, marker: {
124+
name: "sequential pass",
125+
});
126+
let sequential_total: u64 = sequential_results.iter().sum();
127+
println!(
128+
"Sequential: {} chunks processed | total symmetric diff sum {}\n",
129+
sequential_results.len(),
130+
sequential_total
131+
);
132+
133+
std::thread::sleep(Duration::from_millis(20));
134+
135+
println!("=== Parallel Processing (Rayon) ===");
136+
let parallel_results: Vec<_> = samply_measure!({
137+
chunks
138+
.par_iter()
139+
.enumerate()
140+
.map(|(i, chunk)| process_chunk_pipeline(i + chunk_count, chunk, true))
141+
.collect()
142+
}, marker: {
143+
name: "parallel pass",
144+
});
145+
let parallel_total: u64 = parallel_results.iter().sum();
146+
println!(
147+
"Parallel: {} chunks processed | total symmetric diff sum {}\n",
148+
parallel_results.len(),
149+
parallel_total
150+
);
151+
}

0 commit comments

Comments
 (0)