Skip to content

Commit 7291e51

Browse files
authored
Merge pull request #69 from mdabir1203/codex/develop-supply-chain-integrity-framework2025-11-01-9k42bo
[examples] fix: restore hotpath benchmark example
2 parents 9cb435a + f0ea253 commit 7291e51

2 files changed

Lines changed: 169 additions & 0 deletions

File tree

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,28 @@ cargo fmt --all
4040
cargo clippy --workspace --all-targets -- -D warnings
4141
```
4242

43+
### Performance Profiling & Hotpath CI
44+
45+
**Issue tree (why the `hotpath-profile` workflow failed):**
46+
47+
- GitHub Action error: `no example target named 'benchmark'`
48+
- Workflow command: `cargo run --example benchmark --features='hotpath,hotpath-ci'`
49+
- Repository state: no `examples/benchmark.rs` instrumented for Hotpath
50+
- Outcome: profiling metrics were never emitted, so the workflow aborted before collecting JSON baselines
51+
52+
With the new `examples/benchmark.rs` workload in place the workflow can build deterministic profiling metrics again. You can replay the job locally with the same steps the Action runs:
53+
54+
1. **Timing profile**
55+
```bash
56+
cargo run --example benchmark --features='hotpath,hotpath-ci' | grep '^{"hotpath_profiling_mode"'
57+
```
58+
2. **Allocation profile** (mirrors the CI's second check)
59+
```bash
60+
cargo run --example benchmark --features='hotpath,hotpath-ci,hotpath-alloc-count-total' \
61+
| grep '^{"hotpath_profiling_mode"'
62+
```
63+
3. **Interpret the JSON output** – Each run prints a DS-like payload keyed by `hotpath_profiling_mode`, listing function metrics captured from the synthetic ShadowMap pipeline (enumeration, TLS parsing, fingerprint normalization, etc.). The workflow artifacts stash both the head and base JSON blobs so regressions are easy to spot.
64+
4365
### Landing page, billing checkout & Vercel
4466

4567
ShadowMap now includes a minimalist, luxury-inspired landing page that mirrors the in-app experience. The Rust server renders it dynamically with localized pricing and optional Stripe checkout, while a static export in `landing-page/index.html` is ready for Vercel hosting.

examples/benchmark.rs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
use std::hash::{Hash, Hasher};
2+
use std::hint::black_box;
3+
use std::thread;
4+
use std::time::Duration;
5+
6+
#[cfg(feature = "hotpath")]
7+
use hotpath::{measure_block, Format, GuardBuilder};
8+
9+
#[cfg(not(feature = "hotpath"))]
10+
macro_rules! measure_block {
11+
($label:expr, $expr:expr) => {{
12+
let _ = $label;
13+
$expr
14+
}};
15+
}
16+
17+
#[cfg(feature = "hotpath-alloc-count-total")]
18+
use std::collections::HashMap;
19+
20+
fn main() {
21+
run_benchmark();
22+
}
23+
24+
#[cfg(feature = "hotpath")]
25+
fn run_benchmark() {
26+
let _guard = GuardBuilder::new("shadowmap::benchmark")
27+
.percentiles(&[75, 90, 95, 99])
28+
.format(Format::Json)
29+
.limit(32)
30+
.build();
31+
32+
orchestrate_shadowmap_pipeline();
33+
}
34+
35+
#[cfg(not(feature = "hotpath"))]
36+
fn run_benchmark() {
37+
eprintln!(
38+
"Hotpath profiling is disabled; rerun with `--features hotpath,hotpath-ci` to record metrics."
39+
);
40+
orchestrate_shadowmap_pipeline();
41+
}
42+
43+
#[cfg_attr(feature = "hotpath", hotpath::measure)]
44+
fn orchestrate_shadowmap_pipeline() {
45+
let scenarios = [
46+
("shadowmap.io", 18usize),
47+
("api.shadowmap.io", 22),
48+
("assets.shadowmap.io", 16),
49+
("billing.shadowmap.io", 20),
50+
];
51+
52+
for (seed, batch) in scenarios {
53+
let inventory = enumerate_domains(seed, batch);
54+
correlate_risk(seed, inventory);
55+
}
56+
}
57+
58+
#[cfg_attr(feature = "hotpath", hotpath::measure)]
59+
fn enumerate_domains(seed: &str, batch: usize) -> usize {
60+
let mut discovered = 0usize;
61+
62+
for i in 0..batch {
63+
let candidate = format!("{seed}-{i:02}");
64+
65+
measure_block!("dns_resolution", {
66+
discovered ^= hash_candidate(&candidate);
67+
if i % 4 == 0 {
68+
thread::sleep(Duration::from_micros(110));
69+
}
70+
});
71+
72+
if i % 3 == 0 {
73+
let artifacts = synthesize_fingerprint(i, candidate.len());
74+
discovered ^= artifacts;
75+
}
76+
}
77+
78+
discovered
79+
}
80+
81+
#[cfg_attr(feature = "hotpath", hotpath::measure)]
82+
fn synthesize_fingerprint(iteration: usize, width: usize) -> usize {
83+
let size = 128 + (iteration % 8) * 16 + width;
84+
let mut buffer = Vec::with_capacity(size);
85+
86+
for offset in 0..size {
87+
buffer.push(((offset * 37 + iteration) % 255) as u8);
88+
}
89+
90+
measure_block!("normalize_headers", {
91+
buffer.sort_unstable();
92+
let chunk = (buffer.len() / 8).max(1);
93+
let mut rolling = 0u32;
94+
95+
for piece in buffer.chunks(chunk) {
96+
rolling ^= piece.iter().fold(0u32, |acc, byte| acc ^ (*byte as u32));
97+
}
98+
99+
black_box(rolling);
100+
thread::sleep(Duration::from_micros(80));
101+
});
102+
103+
buffer.len()
104+
}
105+
106+
#[cfg_attr(feature = "hotpath", hotpath::measure)]
107+
fn correlate_risk(seed: &str, coverage: usize) {
108+
let iterations = (coverage % 6) + 5;
109+
let mut total = 0usize;
110+
111+
for offset in 0..iterations {
112+
total += evaluate_certificate(seed, offset, coverage);
113+
}
114+
115+
black_box(total);
116+
}
117+
118+
#[cfg_attr(feature = "hotpath", hotpath::measure)]
119+
fn evaluate_certificate(seed: &str, offset: usize, coverage: usize) -> usize {
120+
let mut bytes = seed.as_bytes().to_vec();
121+
bytes.resize(bytes.len() + (offset % 5) + 4, b'*');
122+
123+
measure_block!("tls_parse", {
124+
let len = bytes.len().max(1);
125+
bytes.rotate_left(offset % len);
126+
if coverage % 2 == 0 {
127+
thread::sleep(Duration::from_micros(60));
128+
}
129+
});
130+
131+
#[cfg(feature = "hotpath-alloc-count-total")]
132+
{
133+
let mut map = HashMap::with_capacity(offset + 4);
134+
for (idx, byte) in bytes.iter().enumerate() {
135+
map.insert(idx, *byte);
136+
}
137+
black_box(map.len());
138+
}
139+
140+
bytes.iter().filter(|ch| **ch == b'*').count() + offset
141+
}
142+
143+
fn hash_candidate(input: &str) -> usize {
144+
let mut hasher = std::collections::hash_map::DefaultHasher::new();
145+
input.hash(&mut hasher);
146+
hasher.finish() as usize
147+
}

0 commit comments

Comments
 (0)