1use std::collections::{BTreeMap, BTreeSet};
23use std::error::Error;
24use std::fs::{self, File, OpenOptions};
25use std::io::{Read, Write};
26use std::num::NonZeroUsize;
27use std::panic::{AssertUnwindSafe, resume_unwind};
28use std::path::PathBuf;
29use std::pin::Pin;
30use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
31use std::sync::{Arc, Mutex};
32use std::task::{Context, Poll, Waker};
33use std::time::{Duration, Instant};
34
35use agents_kit::Agent;
36use app_kit::workflow::{
37 ActionBackend, ActionDeclaration, ActionEvent, ActionMode, ActionRequest, CheckpointKind,
38 ExecutionEvent, ExecutionOptions, RunId, RunSelector, RunSnapshot, RunStatus, StateChange,
39 WorkflowDefinition, WorkflowRun, WorkflowState,
40};
41use app_kit::{App, AppConfig, AppError, Shutdown, WorkspaceConfig, WorkspaceId};
42use futures_util::{FutureExt, Stream, StreamExt, stream};
43use serde_json::{Map, Value, json};
44use storage_kit::StateStore;
45use storage_kit::state::config::{LanceStateStoreConfig, StateStoreConfig};
46use tokio::sync::Notify;
47
48const WORKLOAD_BUDGET: Duration = Duration::from_secs(30 * 60);
49const CLEANUP_BUDGET: Duration = Duration::from_secs(2 * 60);
50const CLEANUP_OBSERVATION: Duration = Duration::from_secs(10);
51const NODE_DEADLINE_MS: u64 = 60_000;
52const PAGE_SIZE: usize = 128;
53const MANIFEST_SCHEMA: &str = "app-kit-performance/v2";
54
55struct Performance {
56 command: Command,
57 root: PathBuf,
58}
59
60enum Command {
61 Linear,
62 SeedHistory { rows: u64 },
63 ReopenHistory { open_only: bool },
64 Ready,
65 Map,
66 Repeated,
67 NestedCancel,
68 DelayedObserver,
69}
70
71impl Performance {
72 fn parse() -> Result<Self, Box<dyn Error + Send + Sync>> {
73 let mut args = std::env::args().skip(1);
74 let command = args
75 .next()
76 .ok_or("expected a documented performance command")?;
77 let mut root = None;
78 let mut rows = None;
79 let mut page_size = None;
80 let mut open_only = false;
81 while let Some(arg) = args.next() {
82 match arg.as_str() {
83 "--root" if root.is_none() => {
84 root = Some(PathBuf::from(args.next().ok_or("--root needs a path")?));
85 }
86 "--rows" if rows.is_none() => {
87 rows = Some(args.next().ok_or("--rows needs a count")?.parse::<u64>()?);
88 }
89 "--page-size" if page_size.is_none() => {
90 page_size = Some(
91 args.next()
92 .ok_or("--page-size needs a count")?
93 .parse::<usize>()?,
94 );
95 }
96 "--open-only" if !open_only => open_only = true,
97 _ => return Err(format!("unknown or repeated argument: {arg}").into()),
98 }
99 }
100 let command = match (command.as_str(), rows, page_size, open_only) {
101 ("linear", None, None, false) => Command::Linear,
102 ("ready32", None, None, false) => Command::Ready,
103 ("map4096", None, None, false) => Command::Map,
104 ("repeated100", None, None, false) => Command::Repeated,
105 ("nested-cancel", None, None, false) => Command::NestedCancel,
106 ("delayed-observer", None, None, false) => Command::DelayedObserver,
107 ("seed-history", Some(rows @ (1_000 | 10_000)), None, false) => {
108 Command::SeedHistory { rows }
109 }
110 ("reopen-history", None, Some(PAGE_SIZE), open_only) => {
111 Command::ReopenHistory { open_only }
112 }
113 _ => {
114 return Err(
115 "invalid command options; see the example's invocation documentation".into(),
116 );
117 }
118 };
119 let root = root.ok_or("--root is required")?;
120 if !root.is_absolute() {
121 return Err("--root must be absolute and its parent must already exist".into());
122 }
123 Ok(Self { command, root })
124 }
125
126 fn settings(actions: u64) -> Value {
127 json!({
128 "runtime":"tokio-current-thread", "backend":"LanceDB directory",
129 "storage_operation_timeout_ms":30_000,
130 "node_deadline_ms":NODE_DEADLINE_MS, "workload_timeout_seconds":WORKLOAD_BUDGET.as_secs(),
131 "cleanup_timeout_seconds":CLEANUP_BUDGET.as_secs(), "history_page_size":PAGE_SIZE,
132 "history_row_ceiling":actions * 16 + 16
133 })
134 }
135
136 fn definition(linear: bool, actions: u64) -> Result<(WorkflowDefinition, usize), AppError> {
137 let mut nodes = Vec::new();
138 let mut routes = Map::new();
139 for index in 0..if linear { actions } else { 1 } {
140 let node = if linear {
141 format!("n{index:04}")
142 } else {
143 "count".to_owned()
144 };
145 nodes.push(json!({"id":node,"deadline_ms":NODE_DEADLINE_MS,
146 "step":{"kind":"state","mutations":[
147 {"kind":"increment","target":"count","value":{"kind":"literal","value":1}}
148 ]}}));
149 if linear && index + 1 < actions {
150 routes.insert(
151 node,
152 json!({"mode":"first","branches":[
153 {"target":{"kind":"node","id":format!("n{:04}",index + 1)}}
154 ]}),
155 );
156 } else if !linear {
157 routes.insert(
158 node,
159 json!({"mode":"first","branches":[
160 {"when":{"kind":"less_than","left":{"kind":"state","path":"count"},
161 "right":{"kind":"literal","value":actions}},
162 "target":{"kind":"node","id":"count"}},
163 {"target":{"kind":"end"}}
164 ]}),
165 );
166 }
167 }
168 let source = json!({
169 "schema":"app-kit.workflow/v2","id":"performance","revision":"r1",
170 "state":{"count":0},"required":["count"],"reducers":{"count":"reject_concurrent"},
171 "entry":if linear {"n0000"} else {"count"}, "nodes":nodes,"routes":routes
172 })
173 .to_string();
174 assert!(
175 source.len() <= 1_048_576,
176 "generated definition must fit the real source bound"
177 );
178 Ok((WorkflowDefinition::parse(&source)?, source.len()))
179 }
180
181 fn read_manifest(&self) -> Result<Value, Box<dyn Error + Send + Sync>> {
182 for name in [".performance-root", "manifest.json"] {
185 if !fs::symlink_metadata(self.root.join(name))?
186 .file_type()
187 .is_file()
188 {
189 return Err(format!("performance root needs a regular {name} file").into());
190 }
191 }
192 let mut marker = String::new();
193 File::open(self.root.join(".performance-root"))?
194 .take(MANIFEST_SCHEMA.len() as u64 + 1)
195 .read_to_string(&mut marker)?;
196 if marker != MANIFEST_SCHEMA {
197 return Err("root is not owned by this performance consumer".into());
198 }
199 if !fs::symlink_metadata(self.root.join("state"))?
200 .file_type()
201 .is_dir()
202 || fs::read_dir(self.root.join("state"))?
203 .next()
204 .transpose()?
205 .is_none()
206 {
207 return Err("performance state directory is absent or empty".into());
208 }
209 let mut encoded = String::new();
210 File::open(self.root.join("manifest.json"))?
211 .take(16_385)
212 .read_to_string(&mut encoded)?;
213 if encoded.len() > 16_384 {
214 return Err("performance manifest exceeds its bounded size".into());
215 }
216 let manifest: Value = serde_json::from_str(&encoded)?;
217 if manifest["schema"] != MANIFEST_SCHEMA
218 || manifest["dataset"] != "history"
219 || manifest["cleanup_joined"] != true
220 {
221 return Err("unsupported performance manifest".into());
222 }
223 let actions = manifest["actions"]
224 .as_u64()
225 .ok_or("manifest has no action count")?;
226 let rows = manifest["retained_rows"]
227 .as_u64()
228 .ok_or("manifest has no retained row count")?;
229 if !matches!(rows, 1_000 | 10_000)
230 || actions != (rows - 4) / 3
231 || manifest["terminal_checkpoint"] != rows - 1
232 || manifest["settings"] != Self::settings(actions)
233 {
234 return Err("manifest workload or settings do not match this consumer".into());
235 }
236 Ok(manifest)
237 }
238
239 fn samples(mut samples: Vec<Duration>) -> Value {
240 samples.sort_unstable();
241 let length = samples.len();
242 if length == 0 {
243 return json!({"count":0,"p50_ns":null,"p95_ns":null,"max_ns":null});
244 }
245 json!({"count":length,
246 "p50_ns":samples[(length * 50).div_ceil(100) - 1].as_nanos(),
247 "p95_ns":samples[(length * 95).div_ceil(100) - 1].as_nanos(),
248 "max_ns":samples[length - 1].as_nanos(),
249 "percentile":"nearest rank: ceil(p * n), one-based"})
250 }
251
252 fn verify_snapshot(snapshot: &RunSnapshot, actions: u64) {
253 assert!(matches!(snapshot.status, RunStatus::Succeeded));
254 assert!(snapshot.interactions.is_empty());
255 assert_eq!(
256 snapshot.state.values(),
257 &Map::from_iter([("count".into(), json!(actions))])
258 );
259 assert_eq!(snapshot.last_action_ordinal, Some(actions - 1));
260 assert_eq!(snapshot.outcomes.len(), 1);
261 assert_eq!(snapshot.outcomes[0].ordinal, actions - 1);
262 assert!(snapshot.outcomes[0].result.is_empty());
263 }
264
265 async fn history(
266 &self,
267 run: &WorkflowRun<StateStore, Agent, WorkloadAction>,
268 actions: u64,
269 linear: bool,
270 terminal_sequence: u64,
271 ) -> Result<Value, AppError> {
272 let ceiling = actions * 16 + 16;
273 let mut cursor = None;
274 let mut prior_sequence = None;
275 let mut rows = 0_u64;
276 let mut observed_actions = 0_u64;
277 let mut initial = 0;
278 let mut terminal = 0;
279 let mut controls = 0;
280 let mut halts = 0;
281 let mut samples = Vec::new();
282 let started = Instant::now();
283 loop {
284 assert!(
285 samples.len() < ceiling.div_ceil(PAGE_SIZE as u64) as usize + 1,
286 "history page ceiling exhausted without cursor completion"
287 );
288 let page_started = Instant::now();
289 let page = run
290 .history(cursor, NonZeroUsize::new(PAGE_SIZE).unwrap())
291 .await?;
292 samples.push(page_started.elapsed());
293 assert!(page.checkpoints.len() <= PAGE_SIZE);
294 assert!(
295 !page.checkpoints.is_empty() || page.next.is_none(),
296 "history cursor made no progress"
297 );
298 for row in &page.checkpoints {
299 assert_eq!(&row.run, run.id());
300 assert!(prior_sequence.is_none_or(|prior| row.sequence > prior));
301 prior_sequence = Some(row.sequence);
302 rows += 1;
303 assert!(
304 rows <= ceiling,
305 "declared retained-row workload ceiling exceeded"
306 );
307 match row.kind {
308 CheckpointKind::Initial => {
309 initial += 1;
310 assert_eq!(observed_actions, 0);
311 assert_eq!(row.sequence, 0);
312 }
313 CheckpointKind::Action => {
314 assert_eq!(row.outcomes.len(), 1);
315 let outcome = &row.outcomes[0];
316 assert_eq!(outcome.ordinal, observed_actions);
317 assert_eq!(
318 outcome.node,
319 if linear {
320 format!("n{observed_actions:04}")
321 } else {
322 "count".into()
323 }
324 );
325 assert!(outcome.result.is_empty());
326 observed_actions += 1;
327 assert_eq!(row.action_ordinal, Some(observed_actions - 1));
328 }
329 CheckpointKind::Terminal => {
330 terminal += 1;
331 assert!(matches!(row.status, RunStatus::Succeeded));
332 }
333 CheckpointKind::Control => {
334 assert!(row.outcomes.is_empty());
335 controls += 1;
336 if let RunStatus::Halted { node, result } = &row.status {
337 assert!(!linear);
338 assert_eq!(row.sequence, 4);
339 assert_eq!(node, "count");
340 assert_eq!(result, &Some(Map::new()));
341 halts += 1;
342 }
343 }
344 CheckpointKind::Failed { .. } => {
345 panic!("workload history contains a failed action")
346 }
347 }
348 assert_eq!(
349 row.state.values(),
350 &Map::from_iter([("count".into(), json!(observed_actions))])
351 );
352 if !linear && matches!(row.sequence, 4 | 5) {
353 assert!(matches!(row.kind, CheckpointKind::Control));
354 assert_eq!(observed_actions, 1);
355 assert_eq!(row.action_ordinal, Some(0));
356 assert_eq!(row.position.as_deref(), Some("count"));
357 if row.sequence == 5 {
358 assert!(matches!(row.status, RunStatus::Paused));
359 }
360 }
361 }
362 cursor = page.next;
363 if cursor.is_none() {
365 break;
366 }
367 }
368 assert_eq!(initial, 1);
369 assert_eq!(terminal, 1);
370 assert_eq!(observed_actions, actions);
371 assert_eq!(prior_sequence, Some(terminal_sequence));
372 if !linear {
373 assert_eq!(rows, actions * 3 + 4);
374 assert_eq!(terminal_sequence, rows - 1);
375 assert_eq!(controls, actions * 2 + 2);
376 assert_eq!(halts, 1);
377 assert_eq!(samples.len(), (rows as usize).div_ceil(PAGE_SIZE));
378 }
379 let elapsed = started.elapsed();
380 let first = samples.first().copied().unwrap_or_default();
381 let subsequent: Duration = samples.iter().skip(1).copied().sum();
382 Ok(
383 json!({"rows":rows,"action_rows":observed_actions,"pages":samples.len(),
384 "elapsed_ns":elapsed.as_nanos(),"first_page_ns":first.as_nanos(),
385 "subsequent_pages_total_ns":subsequent.as_nanos(),
386 "rows_per_second":rows as f64 / elapsed.as_secs_f64(),"page_latency":Self::samples(samples)}),
387 )
388 }
389
390 async fn seed(
391 &self,
392 app: &App<StateStore, Agent, WorkloadAction>,
393 actions: u64,
394 linear: bool,
395 ) -> Result<Value, Box<dyn Error + Send + Sync>> {
396 let workspace = app
397 .create_workspace(WorkspaceConfig {
398 name: Some("performance consumer".into()),
399 root: self.root.join("workspace"),
400 })
401 .await?;
402 let workflow = workspace.workflow("performance".parse()?).await?;
403 let mut run = workflow.prepare(WorkflowState::default()).await?;
404 let mut execution = run
405 .execution(ExecutionOptions {
406 halt_after: if linear {
407 Vec::new()
408 } else {
409 vec!["count".into()]
410 },
411 ..ExecutionOptions::default()
412 })
413 .await?;
414 let mut samples = Vec::with_capacity(actions as usize);
415 let started = Instant::now();
416 for ordinal in 0..actions {
417 let step_started = Instant::now();
418 let event = execution.step().await?;
419 samples.push(step_started.elapsed());
420 let Some(ExecutionEvent::Committed { outcomes, .. }) = event else {
421 panic!("each serial workload step must commit one action");
422 };
423 assert_eq!(outcomes.len(), 1);
424 assert_eq!(outcomes[0].ordinal, ordinal);
425 assert_eq!(
426 outcomes[0].node,
427 if linear {
428 format!("n{ordinal:04}")
429 } else {
430 "count".into()
431 }
432 );
433 assert!(outcomes[0].result.is_empty());
434 if !linear && ordinal == 0 {
435 let halted = execution.finish().await?;
438 assert!(matches!(&halted.status, RunStatus::Halted { node, result }
439 if node == "count" && result == &Some(Map::new())));
440 assert_eq!(halted.checkpoint, 4);
441 assert_eq!(
442 halted.state.values(),
443 &Map::from_iter([("count".into(), json!(1))])
444 );
445 assert_eq!(halted.last_action_ordinal, Some(0));
446 assert!(halted.outcomes == outcomes);
447 assert!(halted.interactions.is_empty());
448 execution = run.execution(ExecutionOptions::default()).await?;
449 }
450 }
451 let snapshot = execution.finish().await?;
452 Self::verify_snapshot(&snapshot, actions);
453 let execution_elapsed = started.elapsed();
454 let history = self
455 .history(&run, actions, linear, snapshot.checkpoint)
456 .await?;
457 if let Command::SeedHistory { rows } = self.command {
458 assert_eq!(history["rows"], rows);
459 }
460 Ok(
461 json!({"dataset":if linear {"linear"} else {"history"},"actions":actions,
462 "workspace":workspace.id().to_string(),"run":run.id().to_string(),
463 "occurrence":snapshot.occurrence,
464 "expected_state":snapshot.state.values(),"last_action_ordinal":snapshot.last_action_ordinal,
465 "terminal_checkpoint":snapshot.checkpoint,"retained_rows":history["rows"],
466 "execution_ns":execution_elapsed.as_nanos(),
467 "actions_per_second":actions as f64 / execution_elapsed.as_secs_f64(),
468 "step_latency":Self::samples(samples),"history":history}),
469 )
470 }
471
472 async fn reopen(
473 &self,
474 app: &App<StateStore, Agent, WorkloadAction>,
475 manifest: &Value,
476 actions: u64,
477 open_only: bool,
478 ) -> Result<Value, Box<dyn Error + Send + Sync>> {
479 let started = Instant::now();
480 let workspace: WorkspaceId = manifest["workspace"]
481 .as_str()
482 .ok_or("missing workspace ID")?
483 .parse()?;
484 let run_id: RunId = manifest["run"].as_str().ok_or("missing run ID")?.parse()?;
485 let occurrence = manifest["occurrence"]
486 .as_str()
487 .ok_or("missing run occurrence")?
488 .to_owned();
489 let workspace = app.workspace(workspace).await?;
490 let workflow = workspace.workflow("performance".parse()?).await?;
491 let run = workflow
492 .open_run(RunSelector::Exact {
493 id: run_id,
494 occurrence: occurrence.clone(),
495 })
496 .await?;
497 let snapshot = run.snapshot().await?;
498 Self::verify_snapshot(&snapshot, actions);
499 assert_eq!(snapshot.occurrence, occurrence);
500 assert_eq!(json!(snapshot.checkpoint), manifest["terminal_checkpoint"]);
501 assert_eq!(json!(snapshot.state.values()), manifest["expected_state"]);
502 assert_eq!(
503 json!(snapshot.last_action_ordinal),
504 manifest["last_action_ordinal"]
505 );
506 let lookup = started.elapsed();
507 if open_only {
508 return Ok(
509 json!({"dataset":"history","actions":actions,"mode":"open-only",
510 "lookup_and_snapshot_ns":lookup.as_nanos(),
511 "seed_retained_rows":manifest["retained_rows"],"observed_history_rows":null}),
512 );
513 }
514 let history = self
515 .history(&run, actions, false, snapshot.checkpoint)
516 .await?;
517 assert_eq!(history["rows"], manifest["retained_rows"]);
518 Ok(
519 json!({"dataset":"history","actions":actions,"mode":"full-history",
520 "lookup_and_snapshot_ns":lookup.as_nanos(),"history":history}),
521 )
522 }
523
524 fn definitions(&self, actions: u64) -> Result<(Vec<WorkflowDefinition>, usize), AppError> {
525 if matches!(
526 self.command,
527 Command::Linear | Command::SeedHistory { .. } | Command::ReopenHistory { .. }
528 ) {
529 let (definition, bytes) =
530 Self::definition(matches!(self.command, Command::Linear), actions)?;
531 return Ok((vec![definition], bytes));
532 }
533 let leaf = json!({
534 "schema":"app-kit.workflow/v2","id":"performance-leaf","revision":"r1",
535 "state":{"value":0,"result":0},"required":["value","result"],
536 "reducers":{"value":"reject_concurrent","result":"reject_concurrent"},
537 "entry":"work","nodes":[{"id":"work","deadline_ms":NODE_DEADLINE_MS,
538 "step":{"kind":"action","action":"performance-action","revision":"r1"}}],"routes":{}
539 });
540 let documents = match self.command {
541 Command::Ready => {
542 let mut nodes = vec![json!({"id":"fork","step":{"kind":"state","mutations":[]}})];
543 let mut branches = Vec::new();
544 for index in 0..32 {
545 let node = format!("n{index:02}");
546 nodes.push(json!({"id":node,"deadline_ms":NODE_DEADLINE_MS,
547 "step":{"kind":"action","action":"performance-action","revision":"r1"}}));
548 branches.push(json!({"target":{"kind":"node","id":node}}));
549 }
550 vec![json!({
551 "schema":"app-kit.workflow/v2","id":"performance","revision":"r1",
552 "state":{"value":0,"result":0},"required":["value","result"],
553 "reducers":{"value":"reject_concurrent","result":"collect"},
554 "entry":"fork","nodes":nodes,
555 "routes":{"fork":{"mode":"broadcast","branches":branches}}
556 })]
557 }
558 Command::Map | Command::NestedCancel => {
559 let nested = matches!(self.command, Command::NestedCancel);
560 let items = if nested { 2 } else { 4_096 };
561 let mut documents = vec![
562 json!({
563 "schema":"app-kit.workflow/v2","id":"performance","revision":"r1",
564 "state":{"value":0,"result":0},"required":["value","result"],
565 "reducers":{"value":"reject_concurrent","result":"sum_integer"},
566 "entry":"map","nodes":[{"id":"map","deadline_ms":if nested {NODE_DEADLINE_MS} else {WORKLOAD_BUDGET.as_millis() as u64},
567 "step":{"kind":"map","workflow":if nested {"performance-mid"} else {"performance-leaf"},
568 "items":{"kind":"literal","value":(0..items).collect::<Vec<_>>()},
569 "input_key":"value","max_parallel":if nested {2} else {8},
570 "reductions":[{"source":"result","target":"result","reducer":"sum_integer"}]}}],"routes":{}
571 }),
572 leaf,
573 ];
574 if nested {
575 documents.push(json!({
576 "schema":"app-kit.workflow/v2","id":"performance-mid","revision":"r1",
577 "state":{"value":0,"result":0},"required":["value","result"],
578 "reducers":{"value":"reject_concurrent","result":"sum_integer"},
579 "entry":"map","nodes":[{"id":"map","deadline_ms":NODE_DEADLINE_MS,
580 "step":{"kind":"map","workflow":"performance-leaf",
581 "items":{"kind":"literal","value":(0..8).collect::<Vec<_>>()},
582 "input_key":"value","max_parallel":8,
583 "reductions":[{"source":"result","target":"result","reducer":"sum_integer"}]}}],"routes":{}
584 }));
585 }
586 documents
587 }
588 Command::Repeated | Command::DelayedObserver => vec![leaf],
589 Command::Linear | Command::SeedHistory { .. } | Command::ReopenHistory { .. } => {
590 unreachable!()
591 }
592 };
593 let mut bytes = 0;
594 let mut definitions = Vec::with_capacity(documents.len());
595 for document in documents {
596 let source = document.to_string();
597 assert!(source.len() <= 1_048_576);
598 bytes += source.len();
599 definitions.push(WorkflowDefinition::parse(&source)?);
600 }
601 Ok((definitions, bytes))
602 }
603
604 async fn ready(
605 &self,
606 app: &App<StateStore, Agent, WorkloadAction>,
607 probe: &WorkloadProbe,
608 ) -> Result<Value, Box<dyn Error + Send + Sync>> {
609 let workspace = app
610 .create_workspace(WorkspaceConfig {
611 name: None,
612 root: self.root.join("workspace"),
613 })
614 .await?;
615 let workflow = workspace.workflow("performance".parse()?).await?;
616 let mut waves = Vec::with_capacity(32);
617 let mut releases = Vec::with_capacity(32 * 31);
618 let mut barriers = Vec::with_capacity(32);
619 let started = Instant::now();
620 for permutation in 0..32 {
621 probe.assert_idle();
622 let mut run = workflow.prepare(WorkflowState::default()).await?;
623 let observer = workflow
624 .open_run(RunSelector::Latest(run.id().clone()))
625 .await?;
626 let mut execution = run.execution(ExecutionOptions::default()).await?;
627 let Some(ExecutionEvent::Committed { outcomes, .. }) = execution.step().await? else {
628 panic!("the native fork must commit before the ready wave");
629 };
630 assert_eq!(outcomes.len(), 1);
631 assert_eq!(
632 (outcomes[0].node.as_str(), outcomes[0].ordinal),
633 ("fork", 0)
634 );
635 let wave = Instant::now();
636 let mut finish = Box::pin(execution.finish());
637 tokio::select! {
638 _ = probe.wait_for(&probe.constructed, (permutation + 1) * 32) => {}
639 result = &mut finish => panic!("gated ready wave finished before admission: {}", result.is_ok()),
640 }
641 assert_eq!(probe.active.load(Ordering::SeqCst), 32);
642 let order = (0..32)
643 .map(|offset| (permutation + 31 - offset) % 32)
644 .collect::<Vec<_>>();
645 for (released, index) in order.iter().take(31).enumerate() {
646 let releasing = Instant::now();
647 probe.release(*index);
648 tokio::select! {
649 _ = probe.wait_for(&probe.completed, permutation * 32 + released + 1) => {}
650 result = &mut finish => panic!("a partial ready wave committed: {}", result.is_ok()),
651 }
652 releases.push(releasing.elapsed());
653 let visible = observer.snapshot().await?;
654 assert_eq!(
655 visible.state.values()["result"],
656 0,
657 "staged siblings cannot expose a partial barrier"
658 );
659 assert_eq!(visible.last_action_ordinal, Some(0));
660 }
661 let barrier = Instant::now();
662 probe.release(order[31]);
663 let snapshot = finish.await?;
664 barriers.push(barrier.elapsed());
665 waves.push(wave.elapsed());
666 assert!(matches!(snapshot.status, RunStatus::Succeeded));
667 assert!(snapshot.interactions.is_empty());
668 assert_eq!(
669 snapshot.state.values()["result"],
670 json!((1..=32).collect::<Vec<_>>())
671 );
672 assert_eq!(snapshot.last_action_ordinal, Some(32));
673 assert_eq!(snapshot.outcomes.len(), 32);
674 for (index, outcome) in snapshot.outcomes.iter().enumerate() {
675 assert_eq!(outcome.node, format!("n{index:02}"));
676 assert_eq!(outcome.ordinal, index as u64 + 1);
677 assert_eq!(
678 outcome.result,
679 Map::from_iter([("value".into(), json!(index + 1))])
680 );
681 }
682 assert_eq!(std::mem::take(&mut *probe.order.lock().unwrap()), order);
683 assert_eq!(
684 probe.completed.load(Ordering::SeqCst),
685 (permutation + 1) * 32
686 );
687 probe.assert_idle();
688 }
689 assert_eq!(probe.peak.load(Ordering::SeqCst), 32);
690 let elapsed = started.elapsed();
691 Ok(
692 json!({"dataset":"ready32","permutations":32,"ready_actions_per_wave":32,
693 "caller_action_commits":1024,"native_fork_commits":32,
694 "execution_ns":elapsed.as_nanos(),"caller_actions_per_second":1024.0 / elapsed.as_secs_f64(),
695 "wave_latency_including_snapshot_probes":Self::samples(waves),
696 "release_to_source_completion_latency":Self::samples(releases),
697 "last_release_to_committed_barrier_latency":Self::samples(barriers)}),
698 )
699 }
700
701 async fn mapped(
702 &self,
703 app: &App<StateStore, Agent, WorkloadAction>,
704 probe: &WorkloadProbe,
705 ) -> Result<Value, Box<dyn Error + Send + Sync>> {
706 let workspace = app
707 .create_workspace(WorkspaceConfig {
708 name: None,
709 root: self.root.join("workspace"),
710 })
711 .await?;
712 let workflow = workspace.workflow("performance".parse()?).await?;
713 let mut run = workflow.prepare(WorkflowState::default()).await?;
714 let observer = workflow
715 .open_run(RunSelector::Latest(run.id().clone()))
716 .await?;
717 let started = Instant::now();
718 let mut finish = Box::pin(run.execution(ExecutionOptions::default()).await?.finish());
719 tokio::select! {
720 _ = probe.wait_for(&probe.constructed, 8) => {}
721 result = &mut finish => panic!("map finished before its first eight blocked sources: {}", result.is_ok()),
722 }
723 assert_eq!(probe.active.load(Ordering::SeqCst), 8);
724 assert_eq!(probe.constructed.load(Ordering::SeqCst), 8);
725 let unchanged = observer.snapshot().await?;
726 assert_eq!(unchanged.state.values()["result"], 0);
727 assert!(unchanged.last_action_ordinal.is_none());
728 probe.release_all();
729 let snapshot = finish.await?;
730 let elapsed = started.elapsed();
731 assert!(matches!(snapshot.status, RunStatus::Succeeded));
732 assert!(snapshot.interactions.is_empty());
733 assert_eq!(snapshot.state.values()["result"], 4_096_u64 * 4_097 / 2);
734 assert_eq!(snapshot.last_action_ordinal, Some(0));
735 assert_eq!(snapshot.outcomes.len(), 1);
736 assert_eq!(snapshot.outcomes[0].node, "map");
737 assert_eq!(probe.constructed.load(Ordering::SeqCst), 4_096);
738 assert_eq!(probe.completed.load(Ordering::SeqCst), 4_096);
739 assert_eq!(probe.peak.load(Ordering::SeqCst), 8);
740 probe.assert_idle();
741 let child = workspace.workflow("performance-leaf".parse()?).await?;
742 let mut cursor = None;
743 let mut values = BTreeSet::new();
744 let verifying = Instant::now();
745 loop {
746 let page = child
747 .list_runs(cursor, NonZeroUsize::new(PAGE_SIZE).unwrap())
748 .await?;
749 assert!(page.len() <= PAGE_SIZE);
750 if page.is_empty() {
751 break;
752 }
753 for row in &page {
754 assert!(matches!(row.status, RunStatus::Succeeded));
755 let value = row.state.values()["value"].as_u64().unwrap();
756 assert!(
757 value < 4_096 && values.insert(value),
758 "every mapped input has exactly one retained child"
759 );
760 assert_eq!(row.state.values()["result"], value + 1);
761 assert_eq!(row.last_action_ordinal, Some(0));
762 assert_eq!(row.outcomes.len(), 1);
763 assert_eq!(row.outcomes[0].node, "work");
764 }
765 let last = page.last().unwrap();
766 cursor = Some(RunSelector::Exact {
767 id: last.id.clone(),
768 occurrence: last.occurrence.clone(),
769 });
770 assert!(values.len() <= 4_096);
771 }
772 assert_eq!(values.len(), 4_096);
773 Ok(json!({"dataset":"map4096","items":4096,"max_parallel":8,
774 "parent_node_deadline_ms":WORKLOAD_BUDGET.as_millis(),
775 "execution_ns":elapsed.as_nanos(),"child_actions_per_second":4096.0 / elapsed.as_secs_f64(),
776 "map_completion_latency":Self::samples(vec![elapsed]),
777 "child_inventory_verification_ns":verifying.elapsed().as_nanos(),"verified_children":values.len()}))
778 }
779
780 async fn repeated(
781 &self,
782 app: &App<StateStore, Agent, WorkloadAction>,
783 probe: &WorkloadProbe,
784 ) -> Result<Value, Box<dyn Error + Send + Sync>> {
785 let workspace = app
786 .create_workspace(WorkspaceConfig {
787 name: None,
788 root: self.root.join("workspace"),
789 })
790 .await?;
791 let workflow = workspace.workflow("performance-leaf".parse()?).await?;
792 let started = Instant::now();
793 let mut runs = std::pin::pin!(
796 stream::iter(0..100_u64)
797 .then(|value| {
798 let workflow = &workflow;
799 async move {
800 let started = Instant::now();
801 let run = workflow
802 .prepare(WorkflowState::new(Map::from_iter([(
803 "value".into(),
804 json!(value),
805 )]))?)
806 .await?;
807 Ok::<_, AppError>((value, run, started))
808 }
809 })
810 .map(|prepared| async move {
811 let (value, mut run, started) = prepared?;
812 let snapshot = run
813 .execution(ExecutionOptions::default())
814 .await?
815 .finish()
816 .await?;
817 assert!(matches!(snapshot.status, RunStatus::Succeeded));
818 assert!(snapshot.interactions.is_empty());
819 assert_eq!(snapshot.state.values()["value"], value);
820 assert_eq!(snapshot.state.values()["result"], value + 1);
821 assert_eq!(snapshot.last_action_ordinal, Some(0));
822 assert_eq!(snapshot.outcomes.len(), 1);
823 Ok::<_, AppError>((run.id().clone(), started.elapsed()))
824 })
825 .buffer_unordered(16)
826 );
827 tokio::select! {
828 _ = probe.wait_for(&probe.constructed, 16) => {}
829 result = runs.next() => panic!(
830 "run finished before the first 16 blocked sources: {:?}",
831 result.map(|result| result.map(|_| ())),
832 ),
833 }
834 assert_eq!(probe.active.load(Ordering::SeqCst), 16);
835 assert_eq!(probe.constructed.load(Ordering::SeqCst), 16);
836 probe.release_all();
837 let mut ids = BTreeSet::new();
838 let mut samples = Vec::with_capacity(100);
839 while let Some(result) = runs.next().await {
840 let (id, elapsed) = result?;
841 assert!(ids.insert(id));
842 samples.push(elapsed);
843 }
844 assert_eq!(ids.len(), 100);
845 assert_eq!(probe.constructed.load(Ordering::SeqCst), 100);
846 assert_eq!(probe.completed.load(Ordering::SeqCst), 100);
847 assert_eq!(probe.peak.load(Ordering::SeqCst), 16);
848 probe.assert_idle();
849 let rows = workflow
850 .list_runs(None, NonZeroUsize::new(128).unwrap())
851 .await?;
852 assert_eq!(rows.len(), 100);
853 assert_eq!(
854 rows.iter()
855 .map(|row| row.id.clone())
856 .collect::<BTreeSet<_>>(),
857 ids
858 );
859 assert!(
860 rows.iter()
861 .all(|row| matches!(row.status, RunStatus::Succeeded))
862 );
863 let elapsed = started.elapsed();
864 Ok(
865 json!({"dataset":"repeated100","runs":100,"caller_in_flight_limit":16,"admission":"sequential",
866 "production_active_limit":"unchanged; this measures only the caller's 16-journey envelope",
867 "execution_ns":elapsed.as_nanos(),"runs_per_second":100.0 / elapsed.as_secs_f64(),
868 "run_latency_including_admission":Self::samples(samples),"indefinite_stability_proof":false}),
869 )
870 }
871
872 async fn nested_cancel(
873 &self,
874 app: &App<StateStore, Agent, WorkloadAction>,
875 probe: &WorkloadProbe,
876 ) -> Result<Value, Box<dyn Error + Send + Sync>> {
877 let workspace = app
878 .create_workspace(WorkspaceConfig {
879 name: None,
880 root: self.root.join("workspace"),
881 })
882 .await?;
883 let workflow = workspace.workflow("performance".parse()?).await?;
884 let run = workflow.start(ExecutionOptions::default()).await?;
885 tokio::select! {
886 _ = probe.wait_for(&probe.constructed, 16) => {}
887 result = run.wait() => panic!("nested run ended before its held leaf sources: {}", result.is_ok()),
888 }
889 assert_eq!(probe.active.load(Ordering::SeqCst), 16);
890 let cancelling = Instant::now();
891 let snapshot = tokio::time::timeout(Duration::from_secs(30), async {
892 loop {
893 let before = run.snapshot().await?;
894 assert_eq!(before.state.values()["result"], 0);
895 assert!(before.outcomes.is_empty() && before.last_action_ordinal.is_none());
896 match run.cancel(before.checkpoint).await {
897 Ok(_) => break,
898 Err(AppError::Conflict(_)) => tokio::task::yield_now().await,
899 Err(error) => return Err(error),
900 }
901 }
902 run.wait().await
903 })
904 .await
905 .map_err(|_| {
906 AppError::Timeout("nested cancellation exceeded its declared 30-second grace".into())
907 })??;
908 let elapsed = cancelling.elapsed();
909 assert!(matches!(snapshot.status, RunStatus::Cancelled));
910 assert!(snapshot.interactions.is_empty() && snapshot.outcomes.is_empty());
911 assert!(snapshot.last_action_ordinal.is_none());
912 assert_eq!(snapshot.state.values()["result"], 0);
913 assert_eq!(probe.constructed.load(Ordering::SeqCst), 16);
914 assert_eq!(probe.completed.load(Ordering::SeqCst), 0);
915 probe.assert_idle();
916 for (id, count) in [
917 ("performance", 1),
918 ("performance-mid", 2),
919 ("performance-leaf", 16),
920 ] {
921 let child = workspace.workflow(id.parse()?).await?;
922 let rows = child
923 .list_runs(None, NonZeroUsize::new(128).unwrap())
924 .await?;
925 assert_eq!(rows.len(), count);
926 for row in rows {
927 assert!(matches!(row.status, RunStatus::Cancelled));
928 assert!(row.outcomes.is_empty() && row.last_action_ordinal.is_none());
929 assert_eq!(row.state.values()["result"], 0);
930 }
931 }
932 Ok(
933 json!({"dataset":"nested-cancel","depth":2,"intermediate_children":2,"leaf_children":16,
934 "cooperative_cancel_grace_ms":30_000,"cancellation_latency":Self::samples(vec![elapsed]),
935 "source_replays":0,"caller_sources_dropped":16,"agent_unknown_replay":"not applicable: no Agent attempt"}),
936 )
937 }
938
939 async fn delayed_observer(
940 &self,
941 app: &App<StateStore, Agent, WorkloadAction>,
942 probe: &WorkloadProbe,
943 ) -> Result<Value, Box<dyn Error + Send + Sync>> {
944 let workspace = app
945 .create_workspace(WorkspaceConfig {
946 name: None,
947 root: self.root.join("workspace"),
948 })
949 .await?;
950 let workflow = workspace.workflow("performance-leaf".parse()?).await?;
951 let run = workflow.start(ExecutionOptions::default()).await?;
952 let observer = workflow
953 .open_run(RunSelector::Latest(run.id().clone()))
954 .await?;
955 let delayed = observer.wait();
958 let produced = run.wait().await?;
959 assert!(matches!(produced.status, RunStatus::Succeeded));
960 assert_eq!(probe.completed.load(Ordering::SeqCst), 1);
961 probe.assert_idle();
962 tokio::time::sleep(Duration::from_secs(2)).await;
963 let observing = Instant::now();
964 let snapshot = delayed.await?;
965 let elapsed = observing.elapsed();
966 assert!(matches!(snapshot.status, RunStatus::Succeeded));
967 assert_eq!(snapshot.checkpoint, produced.checkpoint);
968 assert_eq!(snapshot.state.values()["result"], 1);
969 assert_eq!(snapshot.last_action_ordinal, Some(0));
970 assert_eq!(snapshot.outcomes.len(), 1);
971 assert_eq!(
972 snapshot.outcomes[0].result,
973 Map::from_iter([("value".into(), json!(1))])
974 );
975 assert_eq!(probe.constructed.load(Ordering::SeqCst), 1);
976 assert_eq!(probe.partials.load(Ordering::SeqCst), 256);
977 probe.assert_idle();
978 let terminal = run.snapshot().await?;
979 assert_eq!(terminal.checkpoint, snapshot.checkpoint);
980 assert_eq!(terminal.state, snapshot.state);
981 let mut cursor = None;
982 let mut actions = 0;
983 let mut rows = 0;
984 loop {
985 let page = run
986 .history(cursor, NonZeroUsize::new(PAGE_SIZE).unwrap())
987 .await?;
988 for row in &page.checkpoints {
989 rows += 1;
990 assert!(rows <= 32);
991 if matches!(row.kind, CheckpointKind::Action) {
992 actions += 1;
993 assert_eq!(row.action_ordinal, Some(0));
994 assert_eq!(row.outcomes.len(), 1);
995 }
996 }
997 cursor = page.next;
998 if cursor.is_none() {
999 break;
1000 }
1001 }
1002 assert_eq!(
1003 actions, 1,
1004 "partials cannot become extra durable action commits"
1005 );
1006 Ok(
1007 json!({"dataset":"delayed-observer","unobserved_partial_events":256,"delay_after_terminal_confirmation_ms":2_000,
1008 "observation_latency":Self::samples(vec![elapsed]),"retained_rows":rows,
1009 "memory_scope":"external process RSS; this consumer retains no partial payloads"}),
1010 )
1011 }
1012
1013 async fn close(
1014 app: &mut App<StateStore, Agent, WorkloadAction>,
1015 success: bool,
1016 ) -> Result<(), AppError> {
1017 let deadline = Instant::now() + CLEANUP_BUDGET;
1018 let mut first_failure = None;
1019 loop {
1020 let remaining = deadline.saturating_duration_since(Instant::now());
1021 if remaining.is_zero() {
1022 return Err(first_failure.unwrap_or_else(|| {
1023 AppError::Timeout("performance cleanup budget exhausted".into())
1024 }));
1025 }
1026 let policy = if success {
1027 Shutdown::Drain {
1028 timeout: CLEANUP_OBSERVATION,
1029 }
1030 } else {
1031 Shutdown::CancelAndDrain {
1032 timeout: CLEANUP_OBSERVATION,
1033 }
1034 };
1035 let result =
1036 tokio::time::timeout(remaining.min(CLEANUP_OBSERVATION), app.shutdown(policy))
1037 .await;
1038 match result {
1039 Ok(Ok(())) => return first_failure.map_or(Ok(()), Err),
1040 Ok(Err(error @ AppError::Timeout(_))) => {
1041 first_failure.get_or_insert(error);
1042 }
1043 Err(_) => {
1044 first_failure.get_or_insert_with(|| {
1045 AppError::Timeout("performance cleanup observation timed out".into())
1046 });
1047 }
1048 Ok(Err(error)) => return Err(first_failure.unwrap_or(error)),
1049 }
1050 }
1052 }
1053
1054 async fn run(mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
1055 let manifest = if matches!(self.command, Command::ReopenHistory { .. }) {
1056 self.root = self.root.canonicalize()?;
1057 Some(self.read_manifest()?)
1058 } else {
1059 fs::create_dir(&self.root)?;
1060 self.root = self.root.canonicalize()?;
1061 fs::create_dir(self.root.join("workspace"))?;
1062 let mut marker = OpenOptions::new()
1063 .write(true)
1064 .create_new(true)
1065 .open(self.root.join(".performance-root"))?;
1066 marker.write_all(MANIFEST_SCHEMA.as_bytes())?;
1067 marker.sync_all()?;
1068 None
1069 };
1070 let actions = match self.command {
1071 Command::Linear => 1_000,
1072 Command::SeedHistory { rows } => (rows - 4) / 3,
1073 Command::Ready => 32 * 32,
1074 Command::Map => 4_096,
1075 Command::Repeated => 100,
1076 Command::NestedCancel => 16,
1077 Command::DelayedObserver => 1,
1078 Command::ReopenHistory { .. } => {
1079 manifest.as_ref().unwrap()["actions"].as_u64().unwrap()
1080 }
1081 };
1082 let linear = matches!(self.command, Command::Linear);
1083 let (definitions, definition_bytes) = self.definitions(actions)?;
1084 let probe = match self.command {
1085 Command::Ready => Some(WorkloadProbe::new(SourceMode::Permuted, 1_024)),
1086 Command::Map | Command::Repeated => {
1087 Some(WorkloadProbe::new(SourceMode::Parallel, actions as usize))
1088 }
1089 Command::NestedCancel => Some(WorkloadProbe::new(SourceMode::Held, 16)),
1090 Command::DelayedObserver => Some(WorkloadProbe::new(SourceMode::Partials, 1)),
1091 Command::Linear | Command::SeedHistory { .. } | Command::ReopenHistory { .. } => None,
1092 };
1093 if let Some(manifest) = &manifest {
1094 assert_eq!(manifest["definition_bytes"], json!(definition_bytes));
1095 }
1096 let mut app = None;
1097 let started = Instant::now();
1098 let observed = AssertUnwindSafe(tokio::time::timeout(WORKLOAD_BUDGET, async {
1099 let connecting = Instant::now();
1100 let config = LanceStateStoreConfig::directory(self.root.join("state"))
1101 .with_operation_timeout(Duration::from_secs(30))?;
1102 let store = StateStore::connect(StateStoreConfig::Lance(config)).await?;
1103 let connect = connecting.elapsed();
1104 let opening = Instant::now();
1105 app = Some(
1106 App::open(
1107 AppConfig::<_, Agent>::new(store, Vec::new(), definitions).with_actions(
1108 probe
1109 .as_ref()
1110 .map(WorkloadAction::new)
1111 .into_iter()
1112 .collect(),
1113 ),
1114 )
1115 .await?,
1116 );
1117 let open = opening.elapsed();
1118 let mut result = match self.command {
1119 Command::Linear | Command::SeedHistory { .. } => {
1120 self.seed(app.as_ref().unwrap(), actions, linear).await?
1121 }
1122 Command::Ready => {
1123 self.ready(app.as_ref().unwrap(), probe.as_ref().unwrap())
1124 .await?
1125 }
1126 Command::Map => {
1127 self.mapped(app.as_ref().unwrap(), probe.as_ref().unwrap())
1128 .await?
1129 }
1130 Command::Repeated => {
1131 self.repeated(app.as_ref().unwrap(), probe.as_ref().unwrap())
1132 .await?
1133 }
1134 Command::NestedCancel => {
1135 self.nested_cancel(app.as_ref().unwrap(), probe.as_ref().unwrap())
1136 .await?
1137 }
1138 Command::DelayedObserver => {
1139 self.delayed_observer(app.as_ref().unwrap(), probe.as_ref().unwrap())
1140 .await?
1141 }
1142 Command::ReopenHistory { open_only } => {
1143 self.reopen(
1144 app.as_ref().unwrap(),
1145 manifest.as_ref().unwrap(),
1146 actions,
1147 open_only,
1148 )
1149 .await?
1150 }
1151 };
1152 result["store_connect_ns"] = json!(connect.as_nanos());
1153 result["app_open_ns"] = json!(open.as_nanos());
1154 result["workload_elapsed_ns"] = json!(started.elapsed().as_nanos());
1155 Ok::<_, Box<dyn Error + Send + Sync>>(result)
1156 }))
1157 .catch_unwind()
1158 .await;
1159 let success = matches!(&observed, Ok(Ok(Ok(_))));
1160 let closing = Instant::now();
1161 let cleanup = if let Some(mut app) = app.take() {
1162 let closed = AssertUnwindSafe(Self::close(&mut app, success))
1163 .catch_unwind()
1164 .await;
1165 drop(app);
1166 Some(closed)
1167 } else {
1168 None
1169 };
1170 if !success {
1171 eprintln!(
1172 "performance workload failed; cleanup completed={}",
1173 matches!(&cleanup, Some(Ok(Ok(()))))
1174 );
1175 }
1176 let mut result = match observed {
1177 Ok(Ok(result)) => result?,
1178 Ok(Err(_)) => {
1179 return Err(
1180 AppError::Timeout("performance workload budget exhausted".into()).into(),
1181 );
1182 }
1183 Err(panic) => resume_unwind(panic),
1184 };
1185 match cleanup {
1186 Some(Ok(result)) => result?,
1187 Some(Err(panic)) => resume_unwind(panic),
1188 None => return Err("successful workload did not retain its App cleanup owner".into()),
1189 }
1190 result["schema"] = json!(MANIFEST_SCHEMA);
1193 result["settings"] = Self::settings(actions);
1194 if let Some(probe) = &probe {
1195 probe.assert_idle();
1196 result["caller_sources"] = probe.report();
1197 result["settings"]
1198 .as_object_mut()
1199 .unwrap()
1200 .remove("history_row_ceiling");
1201 result["settings"]["private_task_counts"] =
1202 json!("not exposed; App shutdown proves joined ownership");
1203 result["settings"]["lower_agents_and_tool_handlers"] = json!("none constructed");
1204 }
1205 result["definition_bytes"] = json!(definition_bytes);
1206 result["process_id"] = json!(std::process::id());
1207 result["os"] = json!(std::env::consts::OS);
1208 result["architecture"] = json!(std::env::consts::ARCH);
1209 result["package_version"] = json!(env!("CARGO_PKG_VERSION"));
1210 result["cleanup_joined"] = json!(true);
1211 result["cleanup_ns"] = json!(closing.elapsed().as_nanos());
1212 let encoded = serde_json::to_vec(&result)?;
1213 assert!(
1214 encoded.len() <= 16_384,
1215 "result stays within the manifest bound"
1216 );
1217 if matches!(self.command, Command::SeedHistory { .. }) {
1218 let mut file = OpenOptions::new()
1219 .write(true)
1220 .create_new(true)
1221 .open(self.root.join("manifest.json"))?;
1222 file.write_all(&encoded)?;
1223 file.sync_all()?;
1224 }
1225 println!("{}", std::str::from_utf8(&encoded)?);
1226 Ok(())
1227 }
1228}
1229
1230#[derive(Clone, Copy)]
1233enum SourceMode {
1234 Permuted,
1235 Parallel,
1236 Held,
1237 Partials,
1238}
1239
1240struct WorkloadProbe {
1241 mode: SourceMode,
1242 expected: usize,
1243 open: AtomicBool,
1244 constructed: AtomicUsize,
1245 active: AtomicUsize,
1246 peak: AtomicUsize,
1247 completed: AtomicUsize,
1248 dropped: AtomicUsize,
1249 partials: AtomicUsize,
1250 permitted: Mutex<BTreeSet<usize>>,
1251 wakers: Mutex<BTreeMap<usize, Waker>>,
1252 order: Mutex<Vec<usize>>,
1253 lifetimes: Mutex<Vec<Duration>>,
1254 changed: Notify,
1255}
1256
1257impl WorkloadProbe {
1258 fn new(mode: SourceMode, expected: usize) -> Arc<Self> {
1259 Arc::new(Self {
1260 mode,
1261 expected,
1262 open: AtomicBool::new(false),
1263 constructed: AtomicUsize::new(0),
1264 active: AtomicUsize::new(0),
1265 peak: AtomicUsize::new(0),
1266 completed: AtomicUsize::new(0),
1267 dropped: AtomicUsize::new(0),
1268 partials: AtomicUsize::new(0),
1269 permitted: Mutex::new(BTreeSet::new()),
1270 wakers: Mutex::new(BTreeMap::new()),
1271 order: Mutex::new(Vec::new()),
1272 lifetimes: Mutex::new(Vec::new()),
1273 changed: Notify::new(),
1274 })
1275 }
1276
1277 async fn wait_for(&self, counter: &AtomicUsize, expected: usize) {
1278 loop {
1279 let changed = self.changed.notified();
1280 tokio::pin!(changed);
1281 changed.as_mut().enable();
1282 if counter.load(Ordering::SeqCst) >= expected {
1283 return;
1284 }
1285 changed.await;
1286 }
1287 }
1288
1289 fn release(&self, index: usize) {
1290 assert!(self.permitted.lock().unwrap().insert(index));
1291 let waker = self.wakers.lock().unwrap().remove(&index);
1292 if let Some(waker) = waker {
1293 waker.wake();
1294 }
1295 }
1296
1297 fn release_all(&self) {
1298 self.open.store(true, Ordering::SeqCst);
1299 let wakers = std::mem::take(&mut *self.wakers.lock().unwrap());
1300 for (_, waker) in wakers {
1301 waker.wake();
1302 }
1303 }
1304
1305 fn assert_idle(&self) {
1306 assert_eq!(
1307 self.active.load(Ordering::SeqCst),
1308 0,
1309 "original caller sources must be dropped"
1310 );
1311 assert_eq!(
1312 self.dropped.load(Ordering::SeqCst),
1313 self.constructed.load(Ordering::SeqCst)
1314 );
1315 assert!(self.wakers.lock().unwrap().is_empty());
1316 assert!(self.permitted.lock().unwrap().is_empty());
1317 }
1318
1319 fn report(&self) -> Value {
1320 json!({"constructed":self.constructed.load(Ordering::SeqCst),
1321 "completed":self.completed.load(Ordering::SeqCst),
1322 "dropped":self.dropped.load(Ordering::SeqCst),
1323 "active_after_cleanup":self.active.load(Ordering::SeqCst),
1324 "peak_live_sources":self.peak.load(Ordering::SeqCst),
1325 "partial_events":self.partials.load(Ordering::SeqCst),
1326 "construction_to_drop_latency":Performance::samples(std::mem::take(&mut *self.lifetimes.lock().unwrap()))})
1327 }
1328}
1329
1330struct WorkloadAction {
1331 declaration: ActionDeclaration,
1332 probe: Arc<WorkloadProbe>,
1333}
1334
1335impl WorkloadAction {
1336 fn new(probe: &Arc<WorkloadProbe>) -> Self {
1337 Self {
1338 declaration: ActionDeclaration {
1339 key: "performance-action".into(),
1340 revision: "r1".into(),
1341 reads: vec!["value".into()],
1342 writes: vec!["result".into()],
1343 required_inputs: Vec::new(),
1344 optional_inputs: Vec::new(),
1345 bound_inputs: Map::new(),
1346 default_inputs: Map::new(),
1347 tags: Vec::new(),
1348 source: None,
1349 mode: ActionMode::Asynchronous,
1350 },
1351 probe: Arc::clone(probe),
1352 }
1353 }
1354}
1355
1356impl ActionBackend for WorkloadAction {
1357 type Stream = WorkloadSource;
1358
1359 fn declaration(&self) -> &ActionDeclaration {
1360 &self.declaration
1361 }
1362
1363 fn start(&self, request: ActionRequest) -> Self::Stream {
1364 let value = match self.probe.mode {
1365 SourceMode::Permuted => request
1366 .context
1367 .node
1368 .strip_prefix('n')
1369 .unwrap()
1370 .parse()
1371 .unwrap(),
1372 SourceMode::Parallel | SourceMode::Held | SourceMode::Partials => {
1373 usize::try_from(request.state.values()["value"].as_u64().unwrap()).unwrap()
1374 }
1375 };
1376 assert!(
1377 self.probe.constructed.fetch_add(1, Ordering::SeqCst) < self.probe.expected,
1378 "a duplicate source construction exceeds this workload's exact bound"
1379 );
1380 let active = self.probe.active.fetch_add(1, Ordering::SeqCst) + 1;
1381 self.probe.peak.fetch_max(active, Ordering::SeqCst);
1382 self.probe.changed.notify_waiters();
1383 WorkloadSource {
1384 probe: Arc::clone(&self.probe),
1385 value,
1386 started: Instant::now(),
1387 state: SourceState::Polling(0),
1388 }
1389 }
1390}
1391
1392struct WorkloadSource {
1393 probe: Arc<WorkloadProbe>,
1394 value: usize,
1395 started: Instant,
1396 state: SourceState,
1397}
1398
1399enum SourceState {
1400 Polling(usize),
1401 Done,
1402}
1403
1404impl Stream for WorkloadSource {
1405 type Item = Result<ActionEvent, AppError>;
1406
1407 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1408 let source = self.get_mut();
1409 let SourceState::Polling(polls) = &mut source.state else {
1410 return Poll::Ready(None);
1411 };
1412 *polls += 1;
1413 let polls = *polls;
1414 match source.probe.mode {
1415 SourceMode::Held => return Poll::Pending,
1416 SourceMode::Permuted | SourceMode::Parallel => {
1417 source
1420 .probe
1421 .wakers
1422 .lock()
1423 .unwrap()
1424 .insert(source.value, cx.waker().clone());
1425 let ready = match source.probe.mode {
1426 SourceMode::Permuted => {
1427 source.probe.permitted.lock().unwrap().remove(&source.value)
1428 }
1429 SourceMode::Parallel => source.probe.open.load(Ordering::SeqCst),
1430 SourceMode::Held | SourceMode::Partials => unreachable!(),
1431 };
1432 if !ready {
1433 return Poll::Pending;
1434 }
1435 source.probe.wakers.lock().unwrap().remove(&source.value);
1436 if matches!(source.probe.mode, SourceMode::Parallel) && polls == 1 {
1437 cx.waker().wake_by_ref();
1438 return Poll::Pending;
1439 }
1440 }
1441 SourceMode::Partials if polls <= 256 => {
1442 source.probe.partials.fetch_add(1, Ordering::SeqCst);
1443 return Poll::Ready(Some(Ok(ActionEvent::Partial {
1444 result: Map::from_iter([("progress".into(), json!(polls))]),
1445 })));
1446 }
1447 SourceMode::Partials => {}
1448 }
1449 source.state = SourceState::Done;
1450 if matches!(source.probe.mode, SourceMode::Permuted) {
1451 source.probe.order.lock().unwrap().push(source.value);
1452 }
1453 source.probe.completed.fetch_add(1, Ordering::SeqCst);
1454 source.probe.changed.notify_waiters();
1455 Poll::Ready(Some(Ok(ActionEvent::Completed {
1456 result: Map::from_iter([("value".into(), json!(source.value + 1))]),
1457 writes: vec![StateChange::Assign {
1458 values: Map::from_iter([("result".into(), json!(source.value + 1))]),
1459 }],
1460 })))
1461 }
1462}
1463
1464impl Drop for WorkloadSource {
1465 fn drop(&mut self) {
1466 self.probe.wakers.lock().unwrap().remove(&self.value);
1467 self.probe.active.fetch_sub(1, Ordering::SeqCst);
1468 self.probe.dropped.fetch_add(1, Ordering::SeqCst);
1469 self.probe
1470 .lifetimes
1471 .lock()
1472 .unwrap()
1473 .push(self.started.elapsed());
1474 self.probe.changed.notify_waiters();
1475 }
1476}
1477
1478#[tokio::main(flavor = "current_thread")]
1479async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
1480 Performance::parse()?.run().await
1481}