1use std::sync::atomic::Ordering;
2
3use super::server::{CepComputedStats, CrpMode, LeanCtxServer, ToolCallRecord};
4use super::startup::auto_consolidate_knowledge;
5use super::{ctx_compress, ctx_share};
6
7fn ocla_metric_points(
13 context: &crate::core::ocla::OclaRequestContext,
14 tool: &str,
15 original: usize,
16 saved: usize,
17 duration_ms: u64,
18) -> Vec<crate::core::ocla::MetricPoint> {
19 let mut dimensions = std::collections::BTreeMap::new();
20 dimensions.insert("tool".to_string(), tool.to_string());
21 let original = metric_milli(original as u64);
22 let saved = metric_milli(saved as u64);
23 let duration = metric_milli(duration_ms);
24 vec![
25 crate::core::ocla::MetricPoint {
26 context: context.clone(),
27 name: "mcp.tool.original_tokens".to_string(),
28 value_milli: original,
29 dimensions: dimensions.clone(),
30 },
31 crate::core::ocla::MetricPoint {
32 context: context.clone(),
33 name: "mcp.tool.saved_tokens".to_string(),
34 value_milli: saved,
35 dimensions: dimensions.clone(),
36 },
37 crate::core::ocla::MetricPoint {
38 context: context.clone(),
39 name: "mcp.tool.duration_ms".to_string(),
40 value_milli: duration,
41 dimensions,
42 },
43 ]
44}
45
46fn metric_milli(value: u64) -> i64 {
47 i64::try_from(value.saturating_mul(1_000)).unwrap_or(i64::MAX)
48}
49
50impl LeanCtxServer {
51 pub async fn record_call(
53 &self,
54 tool: &str,
55 original: usize,
56 saved: usize,
57 mode: Option<String>,
58 ) {
59 self.record_call_with_timing(tool, original, saved, mode, 0)
60 .await;
61 }
62
63 pub async fn record_call_with_path(
67 &self,
68 tool: &str,
69 original: usize,
70 saved: usize,
71 mode: Option<String>,
72 path: Option<&str>,
73 duration_ms: u64,
74 ) {
75 self.record_call_with_timing_inner(tool, original, saved, mode, duration_ms, path)
76 .await;
77 }
78
79 pub async fn record_call_with_timing(
81 &self,
82 tool: &str,
83 original: usize,
84 saved: usize,
85 mode: Option<String>,
86 duration_ms: u64,
87 ) {
88 self.record_call_with_timing_inner(tool, original, saved, mode, duration_ms, None)
89 .await;
90 }
91
92 async fn record_call_with_timing_inner(
93 &self,
94 tool: &str,
95 original: usize,
96 saved: usize,
97 mode: Option<String>,
98 duration_ms: u64,
99 path: Option<&str>,
100 ) {
101 if let Some(agent_id) = self.presence_agent_id.read().await.clone()
102 && let Err(error) = crate::core::agents::AgentRegistry::heartbeat_persistent(&agent_id)
103 {
104 tracing::warn!("lean-ctx: failed to update MCP agent heartbeat: {error}");
105 }
106 let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
107 let mut calls = self.tool_calls.write().await;
108 calls.push(ToolCallRecord {
109 tool: tool.to_string(),
110 original_tokens: original,
111 saved_tokens: saved,
112 mode: mode.clone(),
113 duration_ms,
114 timestamp: ts.clone(),
115 });
116
117 const MAX_TOOL_CALL_RECORDS: usize = 500;
118 if calls.len() > MAX_TOOL_CALL_RECORDS {
119 let excess = calls.len() - MAX_TOOL_CALL_RECORDS;
120 calls.drain(..excess);
121 }
122
123 if duration_ms > 0 || original > 0 {
128 Self::append_tool_call_log(tool, duration_ms, original, saved, mode.as_deref(), &ts);
129 }
130
131 crate::core::events::emit_tool_call(
132 tool,
133 original as u64,
134 saved as u64,
135 mode.clone(),
136 duration_ms,
137 path.map(ToString::to_string),
138 );
139
140 let output_tokens = original.saturating_sub(saved);
141 crate::core::stats::record_at_turn(
142 tool,
143 original,
144 output_tokens,
145 crate::core::context_overhead::observed_turns(),
146 );
147 if tool == "ctx_shell" {
154 crate::core::savings_ledger::record_tool_event(
155 tool,
156 original,
157 output_tokens,
158 None,
159 None,
160 );
161 }
162
163 {
166 let session_r = self.session.read().await;
167 let agent_id = self
168 .presence_agent_id
169 .read()
170 .await
171 .clone()
172 .unwrap_or_default();
173 let ctx = crate::core::ocla::OclaRequestContext {
174 request_id: format!("mcp-{}", self.call_count.load(Ordering::Relaxed)),
175 session_id: session_r.id.clone(),
176 agent_id,
177 content_ref: path.map_or_else(|| format!("tool:{tool}"), |p| format!("file:{p}")),
178 tenant_id: None,
179 trace_id: "tr-unit".into(),
180 };
181 drop(session_r);
182 let observation = crate::core::ocla::Observation {
183 context: ctx.clone(),
184 name: format!("tool_call:{tool}"),
185 attributes: std::collections::BTreeMap::from([
186 ("original_tokens".into(), original.to_string()),
187 ("saved_tokens".into(), saved.to_string()),
188 ("duration_ms".into(), duration_ms.to_string()),
189 ]),
190 };
191 let hook = crate::core::ocla::OclaRegistry::global()
192 .observation_hook
193 .as_ref();
194 let _ = hook.observe(observation);
195
196 let outcome = crate::core::ocla::Outcome {
197 context: ctx.clone(),
198 accepted: Some(saved > 0),
199 quality_score_milli: if original > 0 {
200 Some(((saved as u64 * 1000) / original as u64).min(1000) as u16)
201 } else {
202 None
203 },
204 outcome_ref: None,
205 };
206 let _ = crate::core::ocla::OclaRegistry::global()
207 .outcome_tracker
208 .record_outcome(outcome);
209
210 let metrics = ocla_metric_points(&ctx, tool, original, saved, duration_ms);
214 let _ = crate::core::ocla::OclaRegistry::global()
215 .metrics_exporter
216 .export_metrics(metrics);
217 }
218 let mut session = self.session.write().await;
219 session.record_tool_call(saved as u64, original as u64);
220 if tool == "ctx_shell" {
221 session.record_command();
222 }
223 let pending_save = if session.should_save() {
224 session.prepare_save().ok()
225 } else {
226 None
227 };
228 drop(calls);
229 drop(session);
230
231 if let Some(prepared) = pending_save {
232 tokio::task::spawn_blocking(move || {
233 let _ = prepared.write_to_disk();
234 });
235 }
236
237 self.write_mcp_live_stats().await;
238 }
239
240 pub fn increment_and_check(&self) -> bool {
242 let count = self.call_count.fetch_add(1, Ordering::Relaxed) + 1;
243 let interval = Self::checkpoint_interval_effective();
244 interval > 0 && count.is_multiple_of(interval)
245 }
246
247 pub async fn auto_checkpoint(&self) -> Option<String> {
249 let cache = self.cache.read().await;
250 if cache.get_all_entries().is_empty() {
251 return None;
252 }
253 let complexity = crate::core::adaptive::classify_from_context(&cache);
254 let checkpoint = ctx_compress::handle(&cache, false, CrpMode::effective());
255 drop(cache);
256
257 let mut session = self.session.write().await;
258 let _ = session.save();
259 let session_summary = session.format_compact();
260 let has_insights = !session.findings.is_empty() || !session.decisions.is_empty();
261 let project_root = session.project_root.clone();
262 let summary_candidate = crate::core::session_summary::build_candidate(&session);
264 drop(session);
265
266 if has_insights && let Some(ref root) = project_root {
267 let root = root.clone();
268 std::thread::spawn(move || {
269 auto_consolidate_knowledge(&root);
270 });
271 }
272
273 if let Some(ref root) = project_root {
275 let root = root.clone();
276 std::thread::spawn(move || {
277 let _ =
278 crate::core::session_summary::maybe_record_periodic(&root, summary_candidate);
279 });
280 }
281
282 let multi_agent_block = self
283 .auto_multi_agent_checkpoint(project_root.as_ref())
284 .await;
285
286 self.record_call("ctx_compress", 0, 0, Some("auto".to_string()))
287 .await;
288
289 self.record_cep_snapshot().await;
290
291 if !crate::core::protocol::meta_visible() {
292 return None;
293 }
294
295 let doc_reminder = {
296 let session = self.session.read().await;
297 let calls = self.tool_calls.read().await;
298 Self::activity_nudge(&session, &calls)
299 };
300
301 Some(format!(
302 "{checkpoint}\n\n--- SESSION STATE ---\n{session_summary}\n\n{}{multi_agent_block}{doc_reminder}",
303 complexity.instruction_suffix()
304 ))
305 }
306
307 async fn auto_multi_agent_checkpoint(&self, project_root: Option<&String>) -> String {
308 let Some(root) = project_root else {
309 return String::new();
310 };
311
312 let registry = crate::core::agents::AgentRegistry::load_or_create();
313 let active = registry.list_active(Some(root));
314 if active.len() <= 1 {
315 return String::new();
316 }
317
318 let agent_id = self.agent_id.read().await;
319 let my_id = match agent_id.as_deref() {
320 Some(id) => id.to_string(),
321 None => return String::new(),
322 };
323 drop(agent_id);
324
325 let cache = self.cache.read().await;
326 let entries = cache.get_all_entries();
327 if !entries.is_empty() {
328 let mut by_access: Vec<_> = entries.iter().collect();
329 by_access.sort_by_key(|x| std::cmp::Reverse(x.1.read_count()));
330 let top_paths: Vec<&str> = by_access
331 .iter()
332 .take(5)
333 .map(|(key, _)| key.as_str())
334 .collect();
335 let paths_csv = top_paths.join(",");
336
337 let _ = ctx_share::handle(
338 "push",
339 Some(&my_id),
340 None,
341 Some(&paths_csv),
342 None,
343 &cache,
344 root,
345 );
346 }
347 drop(cache);
348
349 let pending_count = registry
350 .scratchpad
351 .iter()
352 .filter(|e| !e.read_by.contains(&my_id) && e.from_agent != my_id)
353 .count();
354
355 let shared_dir = crate::core::data_dir::lean_ctx_data_dir()
356 .unwrap_or_default()
357 .join("agents")
358 .join("shared");
359 let shared_count = if shared_dir.exists() {
360 std::fs::read_dir(&shared_dir).map_or(0, std::iter::Iterator::count)
361 } else {
362 0
363 };
364
365 let agent_names: Vec<String> = active
366 .iter()
367 .map(|a| {
368 let role = a.role.as_deref().unwrap_or(&a.agent_type);
369 format!("{role}({})", &a.agent_id[..8.min(a.agent_id.len())])
370 })
371 .collect();
372
373 format!(
374 "\n\n--- MULTI-AGENT SYNC ---\nAgents: {} | Pending msgs: {} | Shared contexts: {}\nAuto-shared top-5 cached files.\n--- END SYNC ---",
375 agent_names.join(", "),
376 pending_count,
377 shared_count,
378 )
379 }
380
381 pub fn append_tool_call_log(
383 tool: &str,
384 duration_ms: u64,
385 original: usize,
386 saved: usize,
387 mode: Option<&str>,
388 timestamp: &str,
389 ) {
390 const MAX_LOG_LINES: usize = 50;
391 if let Ok(dir) = crate::core::paths::state_dir() {
392 let log_path = dir.join("tool-calls.log");
393 let mode_str = mode.unwrap_or("-");
394 let slow = if duration_ms > 5000 { " **SLOW**" } else { "" };
395 let line = format!(
396 "{timestamp}\t{tool}\t{duration_ms}ms\torig={original}\tsaved={saved}\tmode={mode_str}{slow}\n"
397 );
398
399 let mut lines: Vec<String> = std::fs::read_to_string(&log_path)
400 .unwrap_or_default()
401 .lines()
402 .map(std::string::ToString::to_string)
403 .collect();
404
405 lines.push(line.trim_end().to_string());
406 if lines.len() > MAX_LOG_LINES {
407 lines.drain(0..lines.len() - MAX_LOG_LINES);
408 }
409
410 let _ = std::fs::write(&log_path, lines.join("\n") + "\n");
411 }
412 }
413
414 fn compute_cep_stats(
415 calls: &[ToolCallRecord],
416 stats: &crate::core::cache::CacheStats,
417 complexity: &crate::core::adaptive::TaskComplexity,
418 ) -> CepComputedStats {
419 let total_original: u64 = calls.iter().map(|c| c.original_tokens as u64).sum();
420 let total_saved: u64 = calls.iter().map(|c| c.saved_tokens as u64).sum();
421 let total_compressed = total_original.saturating_sub(total_saved);
422 let compression_rate = if total_original > 0 {
423 total_saved as f64 / total_original as f64
424 } else {
425 0.0
426 };
427
428 let modes_used: std::collections::HashSet<&str> =
429 calls.iter().filter_map(|c| c.mode.as_deref()).collect();
430 let mode_diversity = (modes_used.len() as f64 / 10.0).min(1.0);
431 let cache_util = stats.hit_rate() / 100.0;
432 let output_efficiency = 1.0 - crate::core::output_echo::current_avg_ratio();
435 let cep_score = cache_util * 0.25
436 + mode_diversity * 0.15
437 + compression_rate * 0.45
438 + output_efficiency * 0.15;
439
440 let mut mode_counts: std::collections::HashMap<String, u64> =
441 std::collections::HashMap::new();
442 for call in calls {
443 if let Some(ref mode) = call.mode {
444 *mode_counts.entry(mode.clone()).or_insert(0) += 1;
445 }
446 }
447
448 CepComputedStats {
449 cep_score: (cep_score * 100.0).round() as u32,
450 cache_util: (cache_util * 100.0).round() as u32,
451 mode_diversity: (mode_diversity * 100.0).round() as u32,
452 compression_rate: (compression_rate * 100.0).round() as u32,
453 total_original,
454 total_compressed,
455 total_saved,
456 mode_counts,
457 complexity: format!("{complexity:?}"),
458 cache_hits: stats.cache_hits(),
459 total_reads: stats.total_reads(),
460 tool_call_count: calls.len() as u64,
461 }
462 }
463
464 async fn write_mcp_live_stats(&self) {
465 let count = self.call_count.load(Ordering::Relaxed);
466 if count > 1 && !count.is_multiple_of(5) {
467 return;
468 }
469
470 let cache = self.cache.read().await;
471 let calls = self.tool_calls.read().await;
472 let stats = cache.get_stats();
473 let complexity = crate::core::adaptive::classify_from_context(&cache);
474 let dedup = crate::tools::ctx_read::dedup_hook::summary();
475
476 let cs = Self::compute_cep_stats(&calls, stats, &complexity);
477 let started_at = calls
478 .first()
479 .map(|c| c.timestamp.clone())
480 .unwrap_or_default();
481
482 drop(cache);
483 drop(calls);
484
485 crate::core::stats::record_cep_session(
491 cs.cep_score,
492 cs.cache_hits,
493 cs.total_reads,
494 cs.total_original,
495 cs.total_compressed,
496 &cs.mode_counts,
497 cs.tool_call_count,
498 &cs.complexity,
499 );
500
501 let live = serde_json::json!({
502 "cep_score": cs.cep_score,
503 "cache_utilization": cs.cache_util,
504 "mode_diversity": cs.mode_diversity,
505 "compression_rate": cs.compression_rate,
506 "task_complexity": cs.complexity,
507 "files_cached": cs.total_reads,
508 "total_reads": cs.total_reads,
509 "cache_hits": cs.cache_hits,
510 "dedup_reads": dedup.total_reads,
511 "dedup_hits": dedup.dedup_hits,
512 "dedup_tokens_saved": dedup.tokens_saved,
513 "tokens_saved": cs.total_saved,
514 "tokens_original": cs.total_original,
515 "tool_calls": cs.tool_call_count,
516 "started_at": started_at,
517 "updated_at": chrono::Local::now().to_rfc3339(),
518 });
519
520 if let Ok(dir) = crate::core::paths::state_dir() {
521 let _ = std::fs::write(dir.join("mcp-live.json"), live.to_string());
522 }
523 }
524
525 pub async fn record_cep_snapshot(&self) {
527 let cache = self.cache.read().await;
528 let calls = self.tool_calls.read().await;
529 let stats = cache.get_stats();
530 let complexity = crate::core::adaptive::classify_from_context(&cache);
531
532 let cs = Self::compute_cep_stats(&calls, stats, &complexity);
533
534 drop(cache);
535 drop(calls);
536
537 crate::core::stats::record_cep_session(
538 cs.cep_score,
539 cs.cache_hits,
540 cs.total_reads,
541 cs.total_original,
542 cs.total_compressed,
543 &cs.mode_counts,
544 cs.tool_call_count,
545 &cs.complexity,
546 );
547 }
548
549 fn activity_nudge(
550 session: &crate::core::session::SessionState,
551 calls: &[ToolCallRecord],
552 ) -> &'static str {
553 let last_doc_ts = session
554 .progress
555 .last()
556 .map(|p| p.timestamp)
557 .or_else(|| session.decisions.last().map(|d| d.timestamp))
558 .or_else(|| session.findings.last().map(|f| f.timestamp));
559
560 if let Some(ts) = last_doc_ts {
561 let age = chrono::Utc::now() - ts;
562 if age.num_minutes() < 8 {
563 return "";
564 }
565 }
566
567 let (weighted_score, significant_tools, shell_heavy, edit_heavy) =
568 Self::compute_activity_score(calls, last_doc_ts);
569
570 if weighted_score < 20 || significant_tools < 5 {
571 if session.stats.total_tool_calls >= 30
572 && session.decisions.is_empty()
573 && session.progress.is_empty()
574 {
575 return "\n[CHECKPOINT: please document current progress via ctx_session(action=\"task\") or ctx_knowledge(action=\"remember\")]";
576 }
577 return "";
578 }
579
580 if shell_heavy {
581 "\n[CHECKPOINT: multiple shell commands executed — any test results or findings worth persisting via ctx_knowledge(action=\"remember\")?]"
582 } else if edit_heavy {
583 "\n[CHECKPOINT: several files modified — document the architecture decision or pattern via ctx_knowledge(action=\"remember\")?]"
584 } else {
585 "\n[CHECKPOINT: significant work detected — consider persisting decisions via ctx_knowledge(action=\"remember\")]"
586 }
587 }
588
589 fn compute_activity_score(
590 calls: &[ToolCallRecord],
591 last_doc_ts: Option<chrono::DateTime<chrono::Utc>>,
592 ) -> (u32, u32, bool, bool) {
593 let mut weighted_score: u32 = 0;
594 let mut significant_tools: u32 = 0;
595 let mut shell_count: u32 = 0;
596 let mut edit_count: u32 = 0;
597
598 let since_doc: Vec<&ToolCallRecord> = if let Some(ts) = last_doc_ts {
599 let ts_str = ts.format("%Y-%m-%d %H:%M:%S").to_string();
600 calls.iter().filter(|c| c.timestamp > ts_str).collect()
601 } else {
602 calls.iter().collect()
603 };
604
605 for call in &since_doc {
606 let tool = call.tool.as_str();
607 let is_knowledge = tool == "ctx_knowledge" || tool == "ctx_session";
608 if is_knowledge {
609 weighted_score = 0;
610 significant_tools = 0;
611 shell_count = 0;
612 edit_count = 0;
613 continue;
614 }
615
616 let (weight, significant) = match tool {
617 "edit" | "write" | "str_replace" => {
618 edit_count += 1;
619 (4u32, true)
620 }
621 "ctx_shell" => {
622 shell_count += 1;
623 let is_test_or_build = call
624 .mode
625 .as_deref()
626 .is_some_and(|m| m.contains("test") || m.contains("build"));
627 if is_test_or_build {
628 (3, true)
629 } else {
630 (2, true)
631 }
632 }
633 "ctx_read" => {
634 let is_cache_hit = call.saved_tokens > 0
635 && call.original_tokens > 0
636 && call.saved_tokens == call.original_tokens;
637 if is_cache_hit { (0, false) } else { (1, false) }
638 }
639 _ => (1, false),
640 };
641
642 weighted_score = weighted_score.saturating_add(weight);
643 if significant {
644 significant_tools += 1;
645 }
646 }
647
648 let shell_heavy = shell_count >= 3 && shell_count > edit_count;
649 let edit_heavy = edit_count >= 3 && edit_count >= shell_count;
650
651 (weighted_score, significant_tools, shell_heavy, edit_heavy)
652 }
653}
654
655#[cfg(test)]
656mod activity_score_tests {
657 use super::*;
658
659 fn make_call(tool: &str, mode: Option<&str>) -> ToolCallRecord {
660 ToolCallRecord {
661 tool: tool.to_string(),
662 original_tokens: 100,
663 saved_tokens: 50,
664 mode: mode.map(String::from),
665 duration_ms: 10,
666 timestamp: "2026-01-01 12:00:00".to_string(),
667 }
668 }
669
670 fn make_cache_hit() -> ToolCallRecord {
671 ToolCallRecord {
672 tool: "ctx_read".to_string(),
673 original_tokens: 100,
674 saved_tokens: 100,
675 mode: Some("full".to_string()),
676 duration_ms: 1,
677 timestamp: "2026-01-01 12:00:00".to_string(),
678 }
679 }
680
681 #[test]
682 fn empty_calls_zero_score() {
683 let (score, sig, _, _) = LeanCtxServer::compute_activity_score(&[], None);
684 assert_eq!(score, 0);
685 assert_eq!(sig, 0);
686 }
687
688 #[test]
689 fn edits_have_highest_weight() {
690 let calls = vec![
691 make_call("edit", None),
692 make_call("edit", None),
693 make_call("edit", None),
694 ];
695 let (score, sig, _, edit_heavy) = LeanCtxServer::compute_activity_score(&calls, None);
696 assert_eq!(score, 12);
697 assert_eq!(sig, 3);
698 assert!(edit_heavy);
699 }
700
701 #[test]
702 fn shell_test_build_weight_three() {
703 let calls = vec![
704 make_call("ctx_shell", Some("test")),
705 make_call("ctx_shell", Some("build")),
706 make_call("ctx_shell", Some("test")),
707 ];
708 let (score, sig, shell_heavy, _) = LeanCtxServer::compute_activity_score(&calls, None);
709 assert_eq!(score, 9);
710 assert_eq!(sig, 3);
711 assert!(shell_heavy);
712 }
713
714 #[test]
715 fn cache_hits_zero_weight() {
716 let calls = vec![make_cache_hit(), make_cache_hit(), make_cache_hit()];
717 let (score, sig, _, _) = LeanCtxServer::compute_activity_score(&calls, None);
718 assert_eq!(score, 0);
719 assert_eq!(sig, 0);
720 }
721
722 #[test]
723 fn knowledge_call_resets_score() {
724 let calls = vec![
725 make_call("edit", None),
726 make_call("edit", None),
727 make_call("ctx_knowledge", None),
728 make_call("ctx_read", None),
729 ];
730 let (score, sig, _, _) = LeanCtxServer::compute_activity_score(&calls, None);
731 assert_eq!(score, 1);
732 assert_eq!(sig, 0);
733 }
734
735 #[test]
736 fn mixed_workflow_scoring() {
737 let calls = vec![
738 make_call("ctx_read", None),
739 make_call("ctx_read", None),
740 make_call("edit", None),
741 make_call("edit", None),
742 make_call("ctx_shell", Some("test output")),
743 make_call("ctx_shell", None),
744 ];
745 let (score, sig, _, _) = LeanCtxServer::compute_activity_score(&calls, None);
746 assert_eq!(score, 2 + 4 + 4 + 3 + 2);
747 assert_eq!(sig, 4);
748 }
749
750 #[test]
751 fn ocla_metrics_are_payload_free_and_saturating() {
752 let context = crate::core::ocla::OclaRequestContext {
753 request_id: "mcp-1".into(),
754 session_id: "session-1".into(),
755 agent_id: "agent-1".into(),
756 content_ref: "tool:ctx_read".into(),
757 tenant_id: None,
758 trace_id: "tr-unit".into(),
759 };
760 let points = super::ocla_metric_points(&context, "ctx_read", 750, 125, 42);
761 assert_eq!(points.len(), 3);
762 assert_eq!(points[0].name, "mcp.tool.original_tokens");
763 assert_eq!(points[0].value_milli, 750_000);
764 assert_eq!(points[1].name, "mcp.tool.saved_tokens");
765 assert_eq!(points[1].value_milli, 125_000);
766 assert_eq!(points[2].name, "mcp.tool.duration_ms");
767 assert_eq!(points[2].value_milli, 42_000);
768 assert_eq!(points[0].dimensions["tool"], "ctx_read");
769 assert_eq!(points[0].context, context);
770 assert!(!points[0].dimensions.contains_key("path"));
771
772 let saturated = super::ocla_metric_points(
773 &points[0].context,
774 "ctx_shell",
775 usize::MAX,
776 usize::MAX,
777 u64::MAX,
778 );
779 assert!(saturated.iter().all(|point| point.value_milli == i64::MAX));
780 }
781}