1use std::ffi::OsString;
4use std::fmt::Display;
5#[cfg(feature = "runner")]
6use std::fs::File;
7use std::net::SocketAddr;
8use std::path::{Path, PathBuf};
9#[cfg(feature = "runner")]
10use std::process::{Child, Command as StdCommand, Stdio as StdStdio};
11#[cfg(feature = "runner")]
12use std::str::FromStr;
13use std::time::Duration;
14
15#[cfg(feature = "runner")]
16use anyhow::anyhow;
17#[cfg(feature = "runner")]
18use indexmap::indexset;
19#[cfg(feature = "runner")]
20use indexmap::IndexSet;
21#[cfg(feature = "schema")]
22use schemars::JsonSchema;
23use serde::{Deserialize, Serialize};
24#[cfg(feature = "runner")]
25use strum::{EnumIter, IntoEnumIterator};
26
27#[cfg(feature = "runner")]
28use crate::runner;
29#[cfg(feature = "runner")]
30use crate::runner::metrics::Summarize;
31#[cfg(feature = "runner")]
32use crate::runner::metrics::TypeChecker;
33
34#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
40#[cfg_attr(feature = "schema", derive(JsonSchema))]
41#[cfg_attr(feature = "runner", derive(EnumIter))]
42pub enum CachegrindMetric {
43 Ir,
45 Dr,
47 Dw,
49 I1mr,
51 D1mr,
53 D1mw,
55 ILmr,
57 DLmr,
59 DLmw,
61 I1MissRate,
63 LLiMissRate,
65 D1MissRate,
67 LLdMissRate,
69 LLMissRate,
71 L1hits,
73 LLhits,
75 RamHits,
77 L1HitRate,
79 LLHitRate,
81 RamHitRate,
83 TotalRW,
85 EstimatedCycles,
87 Bc,
89 Bcm,
91 Bi,
93 Bim,
95}
96
97#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
101#[non_exhaustive]
102pub enum CachegrindMetrics {
103 #[default]
123 Default,
124
125 CacheMisses,
144
145 CacheMissRates,
163
164 CacheHits,
180
181 CacheHitRates,
196
197 CacheSim,
221
222 BranchSim,
238
239 All,
254
255 None,
257
258 SingleEvent(CachegrindMetric),
277}
278
279#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord)]
285#[non_exhaustive]
286pub enum CallgrindMetrics {
287 #[default]
311 Default,
312
313 CacheMisses,
332
333 CacheMissRates,
351
352 CacheHits,
368
369 CacheHitRates,
384
385 CacheSim,
407
408 CacheUse,
424
425 SystemCalls,
440
441 BranchSim,
457
458 WriteBackBehaviour,
473
474 All,
493
494 None,
496
497 SingleEvent(EventKind),
516}
517
518#[non_exhaustive]
520#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
521pub enum DelayKind {
522 DurationElapse(Duration),
524 TcpConnect(SocketAddr),
526 UdpResponse(SocketAddr, Vec<u8>),
528 PathExists(PathBuf),
530}
531
532#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
534#[cfg_attr(feature = "schema", derive(JsonSchema))]
535#[cfg_attr(feature = "runner", derive(EnumIter))]
536pub enum DhatMetric {
537 TotalUnits,
539 TotalEvents,
541 TotalBytes,
543 TotalBlocks,
545 AtTGmaxBytes,
547 AtTGmaxBlocks,
549 AtTEndBytes,
553 AtTEndBlocks,
557 ReadsBytes,
559 WritesBytes,
561 TotalLifetimes,
563 MaximumBytes,
565 MaximumBlocks,
567}
568
569#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
573pub enum DhatMetrics {
574 #[default]
596 Default,
597
598 All,
614
615 SingleMetric(DhatMetric),
629}
630
631#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
635pub enum Direction {
636 TopToBottom,
638 BottomToTop,
640}
641
642#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
644pub enum EntryPoint {
645 None,
647 #[default]
649 Default,
650 Custom(String),
655}
656
657#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
663#[cfg_attr(feature = "schema", derive(JsonSchema))]
664#[cfg_attr(feature = "runner", derive(EnumIter))]
665pub enum ErrorMetric {
666 Errors,
668 Contexts,
670 SuppressedErrors,
672 SuppressedContexts,
674}
675
676#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
682#[cfg_attr(feature = "schema", derive(JsonSchema))]
683#[cfg_attr(feature = "runner", derive(EnumIter))]
684pub enum EventKind {
685 Ir,
687 Dr,
689 Dw,
691 I1mr,
693 D1mr,
695 D1mw,
697 ILmr,
699 DLmr,
701 DLmw,
703 I1MissRate,
705 LLiMissRate,
707 D1MissRate,
709 LLdMissRate,
711 LLMissRate,
713 L1hits,
715 LLhits,
717 RamHits,
719 L1HitRate,
721 LLHitRate,
723 RamHitRate,
725 TotalRW,
727 EstimatedCycles,
729 SysCount,
731 SysTime,
733 SysCpuTime,
735 Ge,
737 Bc,
739 Bcm,
741 Bi,
743 Bim,
745 ILdmr,
747 DLdmr,
749 DLdmw,
751 AcCost1,
753 AcCost2,
755 SpLoss1,
757 SpLoss2,
759}
760
761#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
763pub enum ExitWith {
764 Success,
766 Failure,
769 Code(i32),
771}
772
773#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
775pub enum FlamegraphKind {
776 Regular,
778 Differential,
780 All,
783 None,
785}
786
787#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
796pub enum Limit {
797 Int(u64),
799 Float(f64),
801}
802
803#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
807pub enum Pipe {
808 #[default]
810 Stdout,
811 Stderr,
813}
814
815#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
819pub enum Stdin {
820 Setup(Pipe),
824 #[default]
825 Inherit,
827 Null,
829 File(PathBuf),
831 Pipe,
833}
834
835#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
840pub enum Stdio {
841 #[default]
843 Inherit,
844 Null,
846 File(PathBuf),
849 Pipe,
851}
852
853#[cfg(feature = "runner")]
855#[derive(Debug, Clone, Copy, PartialEq, Eq)]
856pub(crate) enum Stream {
857 Stdin,
858 Stderr,
859 Stdout,
860}
861
862#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
864pub enum ToolFlamegraphConfig {
865 Callgrind(FlamegraphConfig),
867 None,
869}
870
871#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
873pub enum ToolOutputFormat {
874 Callgrind(Vec<CallgrindMetrics>),
876 Cachegrind(Vec<CachegrindMetrics>),
878 DHAT(Vec<DhatMetric>),
880 Memcheck(Vec<ErrorMetric>),
882 Helgrind(Vec<ErrorMetric>),
884 DRD(Vec<ErrorMetric>),
886 None,
888}
889
890#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
892pub enum ToolRegressionConfig {
893 Cachegrind(CachegrindRegressionConfig),
895 Callgrind(CallgrindRegressionConfig),
897 Dhat(DhatRegressionConfig),
899 None,
901}
902
903#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
908#[cfg_attr(feature = "schema", derive(JsonSchema))]
909pub enum ValgrindTool {
910 Callgrind,
912 Cachegrind,
914 DHAT,
916 Memcheck,
918 Helgrind,
920 DRD,
922 Massif,
924 BBV,
926}
927
928#[derive(Debug, Clone, Default, Serialize, Deserialize)]
932pub struct BinaryBenchmark {
933 pub benches: Vec<BinaryBenchmarkBench>,
935 pub config: Option<BinaryBenchmarkConfig>,
937}
938
939#[derive(Debug, Clone, Default, Serialize, Deserialize)]
943pub struct BinaryBenchmarkBench {
944 pub args: Option<String>,
946 pub command: Command,
948 pub config: Option<BinaryBenchmarkConfig>,
950 pub function_name: String,
952 pub has_setup: bool,
954 pub has_teardown: bool,
956 pub id: Option<String>,
958}
959
960#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
965pub struct BinaryBenchmarkConfig {
966 pub current_dir: Option<PathBuf>,
968 pub default_tool: Option<ValgrindTool>,
970 pub env_clear: Option<bool>,
972 pub envs: Vec<(OsString, Option<OsString>)>,
974 pub exit_with: Option<ExitWith>,
976 pub output_format: Option<OutputFormat>,
978 pub sandbox: Option<Sandbox>,
980 pub setup_parallel: Option<bool>,
982 pub tools: Tools,
984 pub tools_override: Option<Tools>,
986 pub valgrind_args: RawArgs,
988}
989
990#[derive(Debug, Clone, Default, Serialize, Deserialize)]
992pub struct BinaryBenchmarkGroup {
993 pub binary_benchmarks: Vec<BinaryBenchmark>,
995 pub compare_by_id: Option<bool>,
997 pub config: Option<BinaryBenchmarkConfig>,
999 pub has_setup: bool,
1001 pub has_teardown: bool,
1003 pub id: String,
1005}
1006
1007#[derive(Debug, Clone, Serialize, Deserialize)]
1009pub struct BinaryBenchmarkGroups {
1010 pub command_line_args: Vec<String>,
1012 pub config: BinaryBenchmarkConfig,
1014 pub default_tool: ValgrindTool,
1016 pub groups: Vec<BinaryBenchmarkGroup>,
1018 pub has_setup: bool,
1020 pub has_teardown: bool,
1022}
1023
1024#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1026pub struct CachegrindRegressionConfig {
1027 pub fail_fast: Option<bool>,
1029 pub hard_limits: Vec<(CachegrindMetrics, Limit)>,
1031 pub soft_limits: Vec<(CachegrindMetrics, f64)>,
1033}
1034
1035#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1037pub struct CallgrindRegressionConfig {
1038 pub fail_fast: Option<bool>,
1040 pub hard_limits: Vec<(CallgrindMetrics, Limit)>,
1042 pub soft_limits: Vec<(CallgrindMetrics, f64)>,
1044}
1045
1046#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1048pub struct Command {
1049 pub args: Vec<OsString>,
1051 pub config: BinaryBenchmarkConfig,
1053 pub delay: Option<Delay>,
1055 pub path: PathBuf,
1057 pub stderr: Option<Stdio>,
1059 pub stdin: Option<Stdin>,
1061 pub stdout: Option<Stdio>,
1063}
1064
1065#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
1067pub struct Delay {
1068 pub kind: DelayKind,
1070 pub poll: Option<Duration>,
1072 pub timeout: Option<Duration>,
1074}
1075
1076#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1078pub struct DhatRegressionConfig {
1079 pub fail_fast: Option<bool>,
1081 pub hard_limits: Vec<(DhatMetrics, Limit)>,
1083 pub soft_limits: Vec<(DhatMetrics, f64)>,
1085}
1086
1087#[derive(Debug, Clone, Serialize, Deserialize)]
1089pub struct Fixtures {
1090 pub follow_symlinks: bool,
1092 pub path: PathBuf,
1094}
1095
1096#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1098pub struct FlamegraphConfig {
1099 pub direction: Option<Direction>,
1101 pub event_kinds: Option<Vec<EventKind>>,
1103 pub kind: Option<FlamegraphKind>,
1105 pub min_width: Option<f64>,
1107 pub negate_differential: Option<bool>,
1109 pub normalize_differential: Option<bool>,
1111 pub subtitle: Option<String>,
1113 pub title: Option<String>,
1115}
1116
1117#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1119pub struct LibraryBenchmark {
1120 pub benches: Vec<LibraryBenchmarkBench>,
1122 pub config: Option<LibraryBenchmarkConfig>,
1124}
1125
1126#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1128pub struct LibraryBenchmarkBench {
1129 pub args: Option<String>,
1131 pub config: Option<LibraryBenchmarkConfig>,
1133 pub function_name: String,
1135 pub id: Option<String>,
1137}
1138
1139#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1144pub struct LibraryBenchmarkConfig {
1145 pub default_tool: Option<ValgrindTool>,
1147 pub env_clear: Option<bool>,
1149 pub envs: Vec<(OsString, Option<OsString>)>,
1151 pub output_format: Option<OutputFormat>,
1153 pub tools: Tools,
1155 pub tools_override: Option<Tools>,
1157 pub valgrind_args: RawArgs,
1159}
1160
1161#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1163pub struct LibraryBenchmarkGroup {
1164 pub compare_by_id: Option<bool>,
1166 pub config: Option<LibraryBenchmarkConfig>,
1168 pub has_setup: bool,
1170 pub has_teardown: bool,
1172 pub id: String,
1174 pub library_benchmarks: Vec<LibraryBenchmark>,
1176}
1177
1178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1180pub struct LibraryBenchmarkGroups {
1181 pub command_line_args: Vec<String>,
1183 pub config: LibraryBenchmarkConfig,
1185 pub default_tool: ValgrindTool,
1187 pub groups: Vec<LibraryBenchmarkGroup>,
1189 pub has_setup: bool,
1191 pub has_teardown: bool,
1193}
1194
1195#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1197pub struct OutputFormat {
1198 pub show_grid: Option<bool>,
1200 pub show_intermediate: Option<bool>,
1202 pub tolerance: Option<f64>,
1204 pub truncate_description: Option<Option<usize>>,
1206}
1207
1208#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
1210pub struct RawArgs(pub Vec<String>);
1211
1212#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
1214pub struct Sandbox {
1215 pub enabled: Option<bool>,
1217 pub fixtures: Vec<PathBuf>,
1219 pub follow_symlinks: Option<bool>,
1221}
1222
1223#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1225pub struct Tool {
1226 pub enable: Option<bool>,
1228 pub entry_point: Option<EntryPoint>,
1230 pub flamegraph_config: Option<ToolFlamegraphConfig>,
1232 pub frames: Option<Vec<String>>,
1234 pub kind: ValgrindTool,
1236 pub output_format: Option<ToolOutputFormat>,
1238 pub raw_args: RawArgs,
1240 pub regression_config: Option<ToolRegressionConfig>,
1242 pub show_log: Option<bool>,
1244}
1245
1246#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1248pub struct Tools(pub Vec<Tool>);
1249
1250impl BinaryBenchmarkConfig {
1251 #[must_use]
1253 pub fn update_from_all<'a, T>(mut self, others: T) -> Self
1254 where
1255 T: IntoIterator<Item = Option<&'a Self>>,
1256 {
1257 for other in others.into_iter().flatten() {
1258 self.default_tool = update_option(&self.default_tool, &other.default_tool);
1259 self.env_clear = update_option(&self.env_clear, &other.env_clear);
1260 self.current_dir = update_option(&self.current_dir, &other.current_dir);
1261 self.exit_with = update_option(&self.exit_with, &other.exit_with);
1262
1263 self.valgrind_args
1264 .extend_ignore_flag(other.valgrind_args.0.iter());
1265
1266 self.envs.extend_from_slice(&other.envs);
1267
1268 if let Some(other_tools) = &other.tools_override {
1269 self.tools = other_tools.clone();
1270 } else if !other.tools.is_empty() {
1271 self.tools.update_from_other(&other.tools);
1272 } else {
1273 }
1275
1276 self.sandbox = update_option(&self.sandbox, &other.sandbox);
1277 self.setup_parallel = update_option(&self.setup_parallel, &other.setup_parallel);
1278 self.output_format = update_option(&self.output_format, &other.output_format);
1279 }
1280 self
1281 }
1282
1283 pub fn resolve_envs(&self) -> Vec<(OsString, OsString)> {
1288 self.envs
1289 .iter()
1290 .filter_map(|(key, value)| {
1291 value.as_ref().map_or_else(
1292 || std::env::var_os(key).map(|value| (key.clone(), value)),
1293 |value| Some((key.clone(), value.clone())),
1294 )
1295 })
1296 .collect()
1297 }
1298
1299 pub fn collect_envs(&self) -> Vec<(OsString, OsString)> {
1303 self.envs
1304 .iter()
1305 .filter_map(|(key, option)| option.as_ref().map(|value| (key.clone(), value.clone())))
1306 .collect()
1307 }
1308}
1309
1310impl CachegrindMetric {
1311 pub fn is_derived(&self) -> bool {
1330 matches!(
1331 self,
1332 Self::L1hits
1333 | Self::LLhits
1334 | Self::RamHits
1335 | Self::TotalRW
1336 | Self::EstimatedCycles
1337 | Self::I1MissRate
1338 | Self::D1MissRate
1339 | Self::LLiMissRate
1340 | Self::LLdMissRate
1341 | Self::LLMissRate
1342 | Self::L1HitRate
1343 | Self::LLHitRate
1344 | Self::RamHitRate
1345 )
1346 }
1347
1348 pub fn to_name(&self) -> String {
1350 format!("{:?}", *self)
1351 }
1352}
1353
1354impl Display for CachegrindMetric {
1355 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1356 match self {
1357 key @ (Self::Ir
1358 | Self::L1hits
1359 | Self::LLhits
1360 | Self::RamHits
1361 | Self::TotalRW
1362 | Self::EstimatedCycles
1363 | Self::I1MissRate
1364 | Self::D1MissRate
1365 | Self::LLiMissRate
1366 | Self::LLdMissRate
1367 | Self::LLMissRate
1368 | Self::L1HitRate
1369 | Self::LLHitRate
1370 | Self::RamHitRate) => write!(f, "{}", EventKind::from(*key)),
1371 _ => write!(f, "{self:?}"),
1372 }
1373 }
1374}
1375
1376#[cfg(feature = "runner")]
1377impl FromStr for CachegrindMetric {
1378 type Err = anyhow::Error;
1379
1380 fn from_str(string: &str) -> Result<Self, Self::Err> {
1381 let lower = string.to_lowercase();
1382 let metric = match lower.as_str() {
1383 "instructions" | "ir" => Self::Ir,
1384 "dr" => Self::Dr,
1385 "dw" => Self::Dw,
1386 "i1mr" => Self::I1mr,
1387 "ilmr" => Self::ILmr,
1388 "d1mr" => Self::D1mr,
1389 "dlmr" => Self::DLmr,
1390 "d1mw" => Self::D1mw,
1391 "dlmw" => Self::DLmw,
1392 "bc" => Self::Bc,
1393 "bcm" => Self::Bcm,
1394 "bi" => Self::Bi,
1395 "bim" => Self::Bim,
1396 "l1hits" => Self::L1hits,
1397 "llhits" => Self::LLhits,
1398 "ramhits" => Self::RamHits,
1399 "totalrw" => Self::TotalRW,
1400 "estimatedcycles" => Self::EstimatedCycles,
1401 "i1missrate" => Self::I1MissRate,
1402 "d1missrate" => Self::D1MissRate,
1403 "llimissrate" => Self::LLiMissRate,
1404 "lldmissrate" => Self::LLdMissRate,
1405 "llmissrate" => Self::LLMissRate,
1406 "l1hitrate" => Self::L1HitRate,
1407 "llhitrate" => Self::LLHitRate,
1408 "ramhitrate" => Self::RamHitRate,
1409 _ => return Err(anyhow!("Unknown cachegrind metric: '{string}'")),
1410 };
1411
1412 Ok(metric)
1413 }
1414}
1415
1416#[cfg(feature = "runner")]
1417impl TypeChecker for CachegrindMetric {
1418 fn is_int(&self) -> bool {
1419 match self {
1420 Self::Ir
1421 | Self::Dr
1422 | Self::Dw
1423 | Self::I1mr
1424 | Self::D1mr
1425 | Self::D1mw
1426 | Self::ILmr
1427 | Self::DLmr
1428 | Self::DLmw
1429 | Self::L1hits
1430 | Self::LLhits
1431 | Self::RamHits
1432 | Self::TotalRW
1433 | Self::EstimatedCycles
1434 | Self::Bc
1435 | Self::Bcm
1436 | Self::Bi
1437 | Self::Bim => true,
1438 Self::I1MissRate
1439 | Self::LLiMissRate
1440 | Self::D1MissRate
1441 | Self::LLdMissRate
1442 | Self::LLMissRate
1443 | Self::L1HitRate
1444 | Self::LLHitRate
1445 | Self::RamHitRate => false,
1446 }
1447 }
1448
1449 fn is_float(&self) -> bool {
1450 !self.is_int()
1451 }
1452}
1453
1454impl From<CachegrindMetric> for CachegrindMetrics {
1455 fn from(value: CachegrindMetric) -> Self {
1456 Self::SingleEvent(value)
1457 }
1458}
1459
1460#[cfg(feature = "runner")]
1461impl FromStr for CachegrindMetrics {
1462 type Err = anyhow::Error;
1463
1464 fn from_str(string: &str) -> Result<Self, Self::Err> {
1465 let lower = string.to_lowercase();
1466 match lower.as_str().strip_prefix('@') {
1467 Some(suffix) => match suffix {
1468 "default" | "def" => Ok(Self::Default),
1469 "all" => Ok(Self::All),
1470 "cachemisses" | "misses" | "ms" => Ok(Self::CacheMisses),
1471 "cachemissrates" | "missrates" | "mr" => Ok(Self::CacheMissRates),
1472 "cachehits" | "hits" | "hs" => Ok(Self::CacheHits),
1473 "cachehitrates" | "hitrates" | "hr" => Ok(Self::CacheHitRates),
1474 "cachesim" | "cs" => Ok(Self::CacheSim),
1475 "branchsim" | "bs" => Ok(Self::BranchSim),
1476 _ => Err(anyhow!("Invalid cachegrind metric group: '{string}")),
1477 },
1478 None => CachegrindMetric::from_str(string).map(Self::SingleEvent),
1480 }
1481 }
1482}
1483
1484impl From<EventKind> for CallgrindMetrics {
1485 fn from(value: EventKind) -> Self {
1486 Self::SingleEvent(value)
1487 }
1488}
1489
1490#[cfg(feature = "runner")]
1491impl FromStr for CallgrindMetrics {
1492 type Err = anyhow::Error;
1493
1494 fn from_str(string: &str) -> Result<Self, Self::Err> {
1495 let lower = string.to_lowercase();
1496 match lower.as_str().strip_prefix('@') {
1497 Some(suffix) => match suffix {
1498 "default" | "def" => Ok(Self::Default),
1499 "all" => Ok(Self::All),
1500 "cachemisses" | "misses" | "ms" => Ok(Self::CacheMisses),
1501 "cachemissrates" | "missrates" | "mr" => Ok(Self::CacheMissRates),
1502 "cachehits" | "hits" | "hs" => Ok(Self::CacheHits),
1503 "cachehitrates" | "hitrates" | "hr" => Ok(Self::CacheHitRates),
1504 "cachesim" | "cs" => Ok(Self::CacheSim),
1505 "cacheuse" | "cu" => Ok(Self::CacheUse),
1506 "systemcalls" | "syscalls" | "sc" => Ok(Self::SystemCalls),
1507 "branchsim" | "bs" => Ok(Self::BranchSim),
1508 "writebackbehaviour" | "writeback" | "wb" => Ok(Self::WriteBackBehaviour),
1509 _ => Err(anyhow!("Invalid event group: '{string}")),
1510 },
1511 None => EventKind::from_str(string).map(Self::SingleEvent),
1514 }
1515 }
1516}
1517
1518impl Default for DelayKind {
1519 fn default() -> Self {
1520 Self::DurationElapse(Duration::from_secs(60))
1521 }
1522}
1523
1524impl Display for DhatMetric {
1525 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1526 match self {
1527 Self::TotalUnits => f.write_str("Total units"),
1528 Self::TotalEvents => f.write_str("Total events"),
1529 Self::TotalBytes => f.write_str("Total bytes"),
1530 Self::TotalBlocks => f.write_str("Total blocks"),
1531 Self::AtTGmaxBytes => f.write_str("At t-gmax bytes"),
1532 Self::AtTGmaxBlocks => f.write_str("At t-gmax blocks"),
1533 Self::AtTEndBytes => f.write_str("At t-end bytes"),
1534 Self::AtTEndBlocks => f.write_str("At t-end blocks"),
1535 Self::ReadsBytes => f.write_str("Reads bytes"),
1536 Self::WritesBytes => f.write_str("Writes bytes"),
1537 Self::TotalLifetimes => f.write_str("Total lifetimes"),
1538 Self::MaximumBytes => f.write_str("Maximum bytes"),
1539 Self::MaximumBlocks => f.write_str("Maximum blocks"),
1540 }
1541 }
1542}
1543
1544#[cfg(feature = "runner")]
1545impl FromStr for DhatMetric {
1546 type Err = anyhow::Error;
1547
1548 fn from_str(string: &str) -> Result<Self, Self::Err> {
1549 let lower = string.to_lowercase();
1550 let metric = match lower.as_str() {
1551 "totalunits" | "tun" => Self::TotalUnits,
1552 "totalevents" | "tev" => Self::TotalEvents,
1553 "totalbytes" | "tb" => Self::TotalBytes,
1554 "totalblocks" | "tbk" => Self::TotalBlocks,
1555 "attgmaxbytes" | "gb" => Self::AtTGmaxBytes,
1556 "attgmaxblocks" | "gbk" => Self::AtTGmaxBlocks,
1557 "attendbytes" | "eb" => Self::AtTEndBytes,
1558 "attendblocks" | "ebk" => Self::AtTEndBlocks,
1559 "readsbytes" | "rb" => Self::ReadsBytes,
1560 "writesbytes" | "wb" => Self::WritesBytes,
1561 "totallifetimes" | "tl" => Self::TotalLifetimes,
1562 "maximumbytes" | "mb" => Self::MaximumBytes,
1563 "maximumblocks" | "mbk" => Self::MaximumBlocks,
1564 _ => return Err(anyhow!("Unknown dhat metric: '{string}'")),
1565 };
1566
1567 Ok(metric)
1568 }
1569}
1570
1571#[cfg(feature = "runner")]
1572impl Summarize for DhatMetric {}
1573
1574#[cfg(feature = "runner")]
1575impl TypeChecker for DhatMetric {
1576 fn is_int(&self) -> bool {
1577 true
1578 }
1579
1580 fn is_float(&self) -> bool {
1581 false
1582 }
1583}
1584
1585impl From<DhatMetric> for DhatMetrics {
1586 fn from(value: DhatMetric) -> Self {
1587 Self::SingleMetric(value)
1588 }
1589}
1590
1591#[cfg(feature = "runner")]
1592impl FromStr for DhatMetrics {
1593 type Err = anyhow::Error;
1594
1595 fn from_str(string: &str) -> Result<Self, Self::Err> {
1596 let lower = string.to_lowercase();
1597 match lower.as_str().strip_prefix('@') {
1598 Some(suffix) => match suffix {
1599 "default" | "def" => Ok(Self::Default),
1600 "all" => Ok(Self::All),
1601 _ => Err(anyhow!("Invalid dhat metrics group: '{string}")),
1602 },
1603 None => DhatMetric::from_str(string).map(Self::SingleMetric),
1605 }
1606 }
1607}
1608
1609impl Default for Direction {
1610 fn default() -> Self {
1611 Self::BottomToTop
1612 }
1613}
1614
1615impl<T> From<T> for EntryPoint
1616where
1617 T: Into<String>,
1618{
1619 fn from(value: T) -> Self {
1620 Self::Custom(value.into())
1621 }
1622}
1623
1624impl Display for ErrorMetric {
1625 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1626 match self {
1627 Self::Errors => f.write_str("Errors"),
1628 Self::Contexts => f.write_str("Contexts"),
1629 Self::SuppressedErrors => f.write_str("Suppressed Errors"),
1630 Self::SuppressedContexts => f.write_str("Suppressed Contexts"),
1631 }
1632 }
1633}
1634
1635#[cfg(feature = "runner")]
1636impl FromStr for ErrorMetric {
1637 type Err = anyhow::Error;
1638
1639 fn from_str(string: &str) -> Result<Self, Self::Err> {
1640 let lower = string.to_lowercase();
1641 let metric = match lower.as_str() {
1642 "errors" | "err" => Self::Errors,
1643 "contexts" | "ctx" => Self::Contexts,
1644 "suppressederrors" | "serr" => Self::SuppressedErrors,
1645 "suppressedcontexts" | "sctx" => Self::SuppressedContexts,
1646 _ => return Err(anyhow!("Unknown error metric: '{string}'")),
1647 };
1648
1649 Ok(metric)
1650 }
1651}
1652
1653#[cfg(feature = "runner")]
1654impl Summarize for ErrorMetric {}
1655
1656impl EventKind {
1657 pub fn is_derived(&self) -> bool {
1677 matches!(
1678 self,
1679 Self::L1hits
1680 | Self::LLhits
1681 | Self::RamHits
1682 | Self::TotalRW
1683 | Self::EstimatedCycles
1684 | Self::I1MissRate
1685 | Self::D1MissRate
1686 | Self::LLiMissRate
1687 | Self::LLdMissRate
1688 | Self::LLMissRate
1689 | Self::L1HitRate
1690 | Self::LLHitRate
1691 | Self::RamHitRate
1692 )
1693 }
1694
1695 pub fn to_name(&self) -> String {
1697 format!("{:?}", *self)
1698 }
1699}
1700
1701impl Display for EventKind {
1702 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1703 match self {
1704 Self::Ir => f.write_str("Instructions"),
1705 Self::L1hits => f.write_str("L1 Hits"),
1706 Self::LLhits => f.write_str("LL Hits"),
1707 Self::RamHits => f.write_str("RAM Hits"),
1708 Self::TotalRW => f.write_str("Total read+write"),
1709 Self::EstimatedCycles => f.write_str("Estimated Cycles"),
1710 Self::I1MissRate => f.write_str("I1 Miss Rate"),
1711 Self::D1MissRate => f.write_str("D1 Miss Rate"),
1712 Self::LLiMissRate => f.write_str("LLi Miss Rate"),
1713 Self::LLdMissRate => f.write_str("LLd Miss Rate"),
1714 Self::LLMissRate => f.write_str("LL Miss Rate"),
1715 Self::L1HitRate => f.write_str("L1 Hit Rate"),
1716 Self::LLHitRate => f.write_str("LL Hit Rate"),
1717 Self::RamHitRate => f.write_str("RAM Hit Rate"),
1718 _ => write!(f, "{self:?}"),
1719 }
1720 }
1721}
1722
1723impl From<CachegrindMetric> for EventKind {
1724 fn from(value: CachegrindMetric) -> Self {
1725 match value {
1726 CachegrindMetric::Ir => Self::Ir,
1727 CachegrindMetric::Dr => Self::Dr,
1728 CachegrindMetric::Dw => Self::Dw,
1729 CachegrindMetric::I1mr => Self::I1mr,
1730 CachegrindMetric::D1mr => Self::D1mr,
1731 CachegrindMetric::D1mw => Self::D1mw,
1732 CachegrindMetric::ILmr => Self::ILmr,
1733 CachegrindMetric::DLmr => Self::DLmr,
1734 CachegrindMetric::DLmw => Self::DLmw,
1735 CachegrindMetric::L1hits => Self::L1hits,
1736 CachegrindMetric::LLhits => Self::LLhits,
1737 CachegrindMetric::RamHits => Self::RamHits,
1738 CachegrindMetric::TotalRW => Self::TotalRW,
1739 CachegrindMetric::EstimatedCycles => Self::EstimatedCycles,
1740 CachegrindMetric::Bc => Self::Bc,
1741 CachegrindMetric::Bcm => Self::Bcm,
1742 CachegrindMetric::Bi => Self::Bi,
1743 CachegrindMetric::Bim => Self::Bim,
1744 CachegrindMetric::I1MissRate => Self::I1MissRate,
1745 CachegrindMetric::D1MissRate => Self::D1MissRate,
1746 CachegrindMetric::LLiMissRate => Self::LLiMissRate,
1747 CachegrindMetric::LLdMissRate => Self::LLdMissRate,
1748 CachegrindMetric::LLMissRate => Self::LLMissRate,
1749 CachegrindMetric::L1HitRate => Self::L1HitRate,
1750 CachegrindMetric::LLHitRate => Self::LLHitRate,
1751 CachegrindMetric::RamHitRate => Self::RamHitRate,
1752 }
1753 }
1754}
1755
1756#[cfg(feature = "runner")]
1757impl FromStr for EventKind {
1758 type Err = anyhow::Error;
1759
1760 fn from_str(string: &str) -> Result<Self, Self::Err> {
1761 let lower = string.to_lowercase();
1762 let event_kind = match lower.as_str() {
1763 "instructions" | "ir" => Self::Ir,
1764 "dr" => Self::Dr,
1765 "dw" => Self::Dw,
1766 "i1mr" => Self::I1mr,
1767 "d1mr" => Self::D1mr,
1768 "d1mw" => Self::D1mw,
1769 "ilmr" => Self::ILmr,
1770 "dlmr" => Self::DLmr,
1771 "dlmw" => Self::DLmw,
1772 "syscount" => Self::SysCount,
1773 "systime" => Self::SysTime,
1774 "syscputime" => Self::SysCpuTime,
1775 "ge" => Self::Ge,
1776 "bc" => Self::Bc,
1777 "bcm" => Self::Bcm,
1778 "bi" => Self::Bi,
1779 "bim" => Self::Bim,
1780 "ildmr" => Self::ILdmr,
1781 "dldmr" => Self::DLdmr,
1782 "dldmw" => Self::DLdmw,
1783 "accost1" => Self::AcCost1,
1784 "accost2" => Self::AcCost2,
1785 "sploss1" => Self::SpLoss1,
1786 "sploss2" => Self::SpLoss2,
1787 "l1hits" => Self::L1hits,
1788 "llhits" => Self::LLhits,
1789 "ramhits" => Self::RamHits,
1790 "totalrw" => Self::TotalRW,
1791 "estimatedcycles" => Self::EstimatedCycles,
1792 "i1missrate" => Self::I1MissRate,
1793 "d1missrate" => Self::D1MissRate,
1794 "llimissrate" => Self::LLiMissRate,
1795 "lldmissrate" => Self::LLdMissRate,
1796 "llmissrate" => Self::LLMissRate,
1797 "l1hitrate" => Self::L1HitRate,
1798 "llhitrate" => Self::LLHitRate,
1799 "ramhitrate" => Self::RamHitRate,
1800 _ => return Err(anyhow!("Unknown event kind: '{string}'")),
1801 };
1802
1803 Ok(event_kind)
1804 }
1805}
1806
1807#[cfg(feature = "runner")]
1808impl TypeChecker for EventKind {
1809 fn is_int(&self) -> bool {
1810 match self {
1811 Self::Ir
1812 | Self::Dr
1813 | Self::Dw
1814 | Self::I1mr
1815 | Self::D1mr
1816 | Self::D1mw
1817 | Self::ILmr
1818 | Self::DLmr
1819 | Self::DLmw
1820 | Self::L1hits
1821 | Self::LLhits
1822 | Self::RamHits
1823 | Self::TotalRW
1824 | Self::EstimatedCycles
1825 | Self::SysCount
1826 | Self::SysTime
1827 | Self::SysCpuTime
1828 | Self::Ge
1829 | Self::Bc
1830 | Self::Bcm
1831 | Self::Bi
1832 | Self::Bim
1833 | Self::ILdmr
1834 | Self::DLdmr
1835 | Self::DLdmw
1836 | Self::AcCost1
1837 | Self::AcCost2
1838 | Self::SpLoss1
1839 | Self::SpLoss2 => true,
1840 Self::I1MissRate
1841 | Self::LLiMissRate
1842 | Self::D1MissRate
1843 | Self::LLdMissRate
1844 | Self::LLMissRate
1845 | Self::L1HitRate
1846 | Self::LLHitRate
1847 | Self::RamHitRate => false,
1848 }
1849 }
1850
1851 fn is_float(&self) -> bool {
1852 !self.is_int()
1853 }
1854}
1855
1856#[cfg(feature = "runner")]
1857impl From<CachegrindMetrics> for IndexSet<CachegrindMetric> {
1858 fn from(value: CachegrindMetrics) -> Self {
1859 let mut metrics = Self::new();
1860 match value {
1861 CachegrindMetrics::None => {}
1862 CachegrindMetrics::All => metrics.extend(CachegrindMetric::iter()),
1863 CachegrindMetrics::Default => {
1864 metrics.insert(CachegrindMetric::Ir);
1865 metrics.extend(Self::from(CachegrindMetrics::CacheHits));
1866 metrics.extend([CachegrindMetric::TotalRW, CachegrindMetric::EstimatedCycles]);
1867 metrics.extend(Self::from(CachegrindMetrics::BranchSim));
1868 }
1869 CachegrindMetrics::CacheMisses => metrics.extend([
1870 CachegrindMetric::I1mr,
1871 CachegrindMetric::D1mr,
1872 CachegrindMetric::D1mw,
1873 CachegrindMetric::ILmr,
1874 CachegrindMetric::DLmr,
1875 CachegrindMetric::DLmw,
1876 ]),
1877 CachegrindMetrics::CacheMissRates => metrics.extend([
1878 CachegrindMetric::I1MissRate,
1879 CachegrindMetric::LLiMissRate,
1880 CachegrindMetric::D1MissRate,
1881 CachegrindMetric::LLdMissRate,
1882 CachegrindMetric::LLMissRate,
1883 ]),
1884 CachegrindMetrics::CacheHits => {
1885 metrics.extend([
1886 CachegrindMetric::L1hits,
1887 CachegrindMetric::LLhits,
1888 CachegrindMetric::RamHits,
1889 ]);
1890 }
1891 CachegrindMetrics::CacheHitRates => {
1892 metrics.extend([
1893 CachegrindMetric::L1HitRate,
1894 CachegrindMetric::LLHitRate,
1895 CachegrindMetric::RamHitRate,
1896 ]);
1897 }
1898 CachegrindMetrics::CacheSim => {
1899 metrics.extend([CachegrindMetric::Dr, CachegrindMetric::Dw]);
1900 metrics.extend(Self::from(CachegrindMetrics::CacheMisses));
1901 metrics.extend(Self::from(CachegrindMetrics::CacheMissRates));
1902 metrics.extend(Self::from(CachegrindMetrics::CacheHits));
1903 metrics.extend(Self::from(CachegrindMetrics::CacheHitRates));
1904 metrics.insert(CachegrindMetric::TotalRW);
1905 metrics.insert(CachegrindMetric::EstimatedCycles);
1906 }
1907 CachegrindMetrics::BranchSim => {
1908 metrics.extend([
1909 CachegrindMetric::Bc,
1910 CachegrindMetric::Bcm,
1911 CachegrindMetric::Bi,
1912 CachegrindMetric::Bim,
1913 ]);
1914 }
1915 CachegrindMetrics::SingleEvent(metric) => {
1916 metrics.insert(metric);
1917 }
1918 }
1919
1920 metrics
1921 }
1922}
1923
1924#[cfg(feature = "runner")]
1925impl From<DhatMetrics> for IndexSet<DhatMetric> {
1926 fn from(value: DhatMetrics) -> Self {
1927 use DhatMetric::*;
1928 match value {
1929 DhatMetrics::All => DhatMetric::iter().collect(),
1930 DhatMetrics::Default => indexset! {
1931 TotalUnits,
1932 TotalEvents,
1933 TotalBytes,
1934 TotalBlocks,
1935 AtTGmaxBytes,
1936 AtTGmaxBlocks,
1937 AtTEndBytes,
1938 AtTEndBlocks,
1939 ReadsBytes,
1940 WritesBytes },
1941 DhatMetrics::SingleMetric(dhat_metric) => indexset! { dhat_metric },
1942 }
1943 }
1944}
1945
1946#[cfg(feature = "runner")]
1947impl From<CallgrindMetrics> for IndexSet<EventKind> {
1948 fn from(value: CallgrindMetrics) -> Self {
1949 let mut event_kinds = Self::new();
1950 match value {
1951 CallgrindMetrics::None => {}
1952 CallgrindMetrics::All => event_kinds.extend(EventKind::iter()),
1953 CallgrindMetrics::Default => {
1954 event_kinds.insert(EventKind::Ir);
1955 event_kinds.extend(Self::from(CallgrindMetrics::CacheHits));
1956 event_kinds.extend([EventKind::TotalRW, EventKind::EstimatedCycles]);
1957 event_kinds.extend(Self::from(CallgrindMetrics::SystemCalls));
1958 event_kinds.insert(EventKind::Ge);
1959 event_kinds.extend(Self::from(CallgrindMetrics::BranchSim));
1960 event_kinds.extend(Self::from(CallgrindMetrics::WriteBackBehaviour));
1961 event_kinds.extend(Self::from(CallgrindMetrics::CacheUse));
1962 }
1963 CallgrindMetrics::CacheMisses => event_kinds.extend([
1964 EventKind::I1mr,
1965 EventKind::D1mr,
1966 EventKind::D1mw,
1967 EventKind::ILmr,
1968 EventKind::DLmr,
1969 EventKind::DLmw,
1970 ]),
1971 CallgrindMetrics::CacheMissRates => event_kinds.extend([
1972 EventKind::I1MissRate,
1973 EventKind::LLiMissRate,
1974 EventKind::D1MissRate,
1975 EventKind::LLdMissRate,
1976 EventKind::LLMissRate,
1977 ]),
1978 CallgrindMetrics::CacheHits => {
1979 event_kinds.extend([EventKind::L1hits, EventKind::LLhits, EventKind::RamHits]);
1980 }
1981 CallgrindMetrics::CacheHitRates => {
1982 event_kinds.extend([
1983 EventKind::L1HitRate,
1984 EventKind::LLHitRate,
1985 EventKind::RamHitRate,
1986 ]);
1987 }
1988 CallgrindMetrics::CacheSim => {
1989 event_kinds.extend([EventKind::Dr, EventKind::Dw]);
1990 event_kinds.extend(Self::from(CallgrindMetrics::CacheMisses));
1991 event_kinds.extend(Self::from(CallgrindMetrics::CacheMissRates));
1992 event_kinds.extend(Self::from(CallgrindMetrics::CacheHits));
1993 event_kinds.extend(Self::from(CallgrindMetrics::CacheHitRates));
1994 event_kinds.insert(EventKind::TotalRW);
1995 event_kinds.insert(EventKind::EstimatedCycles);
1996 }
1997 CallgrindMetrics::CacheUse => event_kinds.extend([
1998 EventKind::AcCost1,
1999 EventKind::AcCost2,
2000 EventKind::SpLoss1,
2001 EventKind::SpLoss2,
2002 ]),
2003 CallgrindMetrics::SystemCalls => {
2004 event_kinds.extend([
2005 EventKind::SysCount,
2006 EventKind::SysTime,
2007 EventKind::SysCpuTime,
2008 ]);
2009 }
2010 CallgrindMetrics::BranchSim => {
2011 event_kinds.extend([EventKind::Bc, EventKind::Bcm, EventKind::Bi, EventKind::Bim]);
2012 }
2013 CallgrindMetrics::WriteBackBehaviour => {
2014 event_kinds.extend([EventKind::ILdmr, EventKind::DLdmr, EventKind::DLdmw]);
2015 }
2016 CallgrindMetrics::SingleEvent(event_kind) => {
2017 event_kinds.insert(event_kind);
2018 }
2019 }
2020
2021 event_kinds
2022 }
2023}
2024
2025impl LibraryBenchmarkConfig {
2026 #[must_use]
2028 pub fn update_from_all<'a, T>(mut self, others: T) -> Self
2029 where
2030 T: IntoIterator<Item = Option<&'a Self>>,
2031 {
2032 for other in others.into_iter().flatten() {
2033 self.default_tool = update_option(&self.default_tool, &other.default_tool);
2034 self.env_clear = update_option(&self.env_clear, &other.env_clear);
2035
2036 self.valgrind_args
2037 .extend_ignore_flag(other.valgrind_args.0.iter());
2038
2039 self.envs.extend_from_slice(&other.envs);
2040 if let Some(other_tools) = &other.tools_override {
2041 self.tools = other_tools.clone();
2042 } else if !other.tools.is_empty() {
2043 self.tools.update_from_other(&other.tools);
2044 } else {
2045 }
2047
2048 self.output_format = update_option(&self.output_format, &other.output_format);
2049 }
2050 self
2051 }
2052
2053 pub fn resolve_envs(&self) -> Vec<(OsString, OsString)> {
2057 self.envs
2058 .iter()
2059 .filter_map(|(key, value)| match value {
2060 Some(value) => Some((key.clone(), value.clone())),
2061 None => std::env::var_os(key).map(|value| (key.clone(), value)),
2062 })
2063 .collect()
2064 }
2065
2066 pub fn collect_envs(&self) -> Vec<(OsString, OsString)> {
2070 self.envs
2071 .iter()
2072 .filter_map(|(key, option)| option.as_ref().map(|value| (key.clone(), value.clone())))
2073 .collect()
2074 }
2075}
2076
2077#[cfg(feature = "runner")]
2078impl From<runner::metrics::Metric> for Limit {
2079 fn from(value: runner::metrics::Metric) -> Self {
2080 match value {
2081 runner::metrics::Metric::Int(a) => Self::Int(a),
2082 runner::metrics::Metric::Float(b) => Self::Float(b),
2083 }
2084 }
2085}
2086
2087impl From<f64> for Limit {
2088 fn from(value: f64) -> Self {
2089 Self::Float(value)
2090 }
2091}
2092
2093impl From<u64> for Limit {
2094 fn from(value: u64) -> Self {
2095 Self::Int(value)
2096 }
2097}
2098
2099impl RawArgs {
2100 pub fn new<I, T>(args: T) -> Self
2102 where
2103 I: Into<String>,
2104 T: IntoIterator<Item = I>,
2105 {
2106 Self(args.into_iter().map(Into::into).collect())
2107 }
2108
2109 pub fn extend_ignore_flag<I, T>(&mut self, args: T)
2111 where
2112 I: AsRef<str>,
2113 T: IntoIterator<Item = I>,
2114 {
2115 self.0.extend(
2116 args.into_iter()
2117 .filter(|s| !s.as_ref().is_empty())
2118 .map(|s| {
2119 let string = s.as_ref();
2120 if string.starts_with('-') {
2121 string.to_owned()
2122 } else {
2123 format!("--{string}")
2124 }
2125 }),
2126 );
2127 }
2128
2129 pub fn is_empty(&self) -> bool {
2131 self.0.is_empty()
2132 }
2133
2134 pub fn update(&mut self, other: &Self) {
2136 self.extend_ignore_flag(other.0.iter());
2137 }
2138
2139 pub fn prepend(&mut self, other: &Self) {
2141 if !other.is_empty() {
2142 let mut other = other.clone();
2143 other.update(self);
2144 *self = other;
2145 }
2146 }
2147}
2148
2149impl<I> FromIterator<I> for RawArgs
2150where
2151 I: AsRef<str>,
2152{
2153 fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {
2154 let mut this = Self::default();
2155 this.extend_ignore_flag(iter);
2156 this
2157 }
2158}
2159
2160impl Stdin {
2161 #[cfg(feature = "runner")]
2162 pub(crate) fn apply(
2163 &self,
2164 command: &mut StdCommand,
2165 stream: Stream,
2166 child: Option<&mut Child>,
2167 ) -> Result<(), String> {
2168 match (self, child) {
2169 (Self::Setup(Pipe::Stdout), Some(child)) => {
2170 command.stdin(
2171 child
2172 .stdout
2173 .take()
2174 .ok_or_else(|| "Error piping setup stdout".to_owned())?,
2175 );
2176 Ok(())
2177 }
2178 (Self::Setup(Pipe::Stderr), Some(child)) => {
2179 command.stdin(
2180 child
2181 .stderr
2182 .take()
2183 .ok_or_else(|| "Error piping setup stderr".to_owned())?,
2184 );
2185 Ok(())
2186 }
2187 (Self::Setup(_) | Self::Pipe, _) => Stdio::Pipe.apply(command, stream),
2188 (Self::Inherit, _) => Stdio::Inherit.apply(command, stream),
2189 (Self::Null, _) => Stdio::Null.apply(command, stream),
2190 (Self::File(path), _) => Stdio::File(path.clone()).apply(command, stream),
2191 }
2192 }
2193}
2194
2195impl From<Stdio> for Stdin {
2196 fn from(value: Stdio) -> Self {
2197 match value {
2198 Stdio::Inherit => Self::Inherit,
2199 Stdio::Null => Self::Null,
2200 Stdio::File(file) => Self::File(file),
2201 Stdio::Pipe => Self::Pipe,
2202 }
2203 }
2204}
2205
2206impl From<PathBuf> for Stdin {
2207 fn from(value: PathBuf) -> Self {
2208 Self::File(value)
2209 }
2210}
2211
2212impl From<&PathBuf> for Stdin {
2213 fn from(value: &PathBuf) -> Self {
2214 Self::File(value.to_owned())
2215 }
2216}
2217
2218impl From<&Path> for Stdin {
2219 fn from(value: &Path) -> Self {
2220 Self::File(value.to_path_buf())
2221 }
2222}
2223
2224impl Stdio {
2225 #[cfg(feature = "runner")]
2226 pub(crate) fn apply(&self, command: &mut StdCommand, stream: Stream) -> Result<(), String> {
2227 let stdio = match self {
2228 Self::Pipe => StdStdio::piped(),
2229 Self::Inherit => StdStdio::inherit(),
2230 Self::Null => StdStdio::null(),
2231 Self::File(path) => match stream {
2232 Stream::Stdin => StdStdio::from(File::open(path).map_err(|error| {
2233 format!(
2234 "Failed to open file '{}' in read mode for {stream}: {error}",
2235 path.display()
2236 )
2237 })?),
2238 Stream::Stdout | Stream::Stderr => {
2239 StdStdio::from(File::create(path).map_err(|error| {
2240 format!(
2241 "Failed to create file '{}' for {stream}: {error}",
2242 path.display()
2243 )
2244 })?)
2245 }
2246 },
2247 };
2248
2249 match stream {
2250 Stream::Stdin => command.stdin(stdio),
2251 Stream::Stdout => command.stdout(stdio),
2252 Stream::Stderr => command.stderr(stdio),
2253 };
2254
2255 Ok(())
2256 }
2257
2258 #[cfg(feature = "runner")]
2259 pub(crate) fn is_pipe(&self) -> bool {
2260 match self {
2261 Self::Inherit => false,
2262 Self::Null | Self::File(_) | Self::Pipe => true,
2263 }
2264 }
2265}
2266
2267impl From<PathBuf> for Stdio {
2268 fn from(value: PathBuf) -> Self {
2269 Self::File(value)
2270 }
2271}
2272
2273impl From<&PathBuf> for Stdio {
2274 fn from(value: &PathBuf) -> Self {
2275 Self::File(value.to_owned())
2276 }
2277}
2278
2279impl From<&Path> for Stdio {
2280 fn from(value: &Path) -> Self {
2281 Self::File(value.to_path_buf())
2282 }
2283}
2284
2285#[cfg(feature = "runner")]
2286impl Display for Stream {
2287 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2288 f.write_str(&format!("{self:?}").to_lowercase())
2289 }
2290}
2291
2292impl Tool {
2293 pub fn new(kind: ValgrindTool) -> Self {
2295 Self {
2296 kind,
2297 enable: None,
2298 raw_args: RawArgs::default(),
2299 show_log: None,
2300 regression_config: None,
2301 flamegraph_config: None,
2302 output_format: None,
2303 entry_point: None,
2304 frames: None,
2305 }
2306 }
2307
2308 pub fn with_args<I, T>(kind: ValgrindTool, args: T) -> Self
2310 where
2311 I: AsRef<str>,
2312 T: IntoIterator<Item = I>,
2313 {
2314 let mut this = Self::new(kind);
2315 this.raw_args = RawArgs::from_iter(args);
2316 this
2317 }
2318
2319 pub fn update(&mut self, other: &Self) {
2321 if self.kind == other.kind {
2322 self.enable = update_option(&self.enable, &other.enable);
2323 self.show_log = update_option(&self.show_log, &other.show_log);
2324 self.regression_config =
2325 update_option(&self.regression_config, &other.regression_config);
2326 self.flamegraph_config =
2327 update_option(&self.flamegraph_config, &other.flamegraph_config);
2328 self.output_format = update_option(&self.output_format, &other.output_format);
2329 self.entry_point = update_option(&self.entry_point, &other.entry_point);
2330 self.frames = update_option(&self.frames, &other.frames);
2331
2332 self.raw_args.extend_ignore_flag(other.raw_args.0.iter());
2333 }
2334 }
2335}
2336
2337impl Tools {
2338 pub fn is_empty(&self) -> bool {
2340 self.0.is_empty()
2341 }
2342
2343 pub fn update(&mut self, other: Tool) {
2345 if let Some(tool) = self.0.iter_mut().find(|t| t.kind == other.kind) {
2346 tool.update(&other);
2347 } else {
2348 self.0.push(other);
2349 }
2350 }
2351
2352 pub fn update_all<T>(&mut self, tools: T)
2354 where
2355 T: IntoIterator<Item = Tool>,
2356 {
2357 for tool in tools {
2358 self.update(tool);
2359 }
2360 }
2361
2362 pub fn update_from_other(&mut self, tools: &Self) {
2364 self.update_all(tools.0.iter().cloned());
2365 }
2366
2367 pub fn consume(&mut self, kind: ValgrindTool) -> Option<Tool> {
2369 self.0
2370 .iter()
2371 .position(|p| p.kind == kind)
2372 .map(|position| self.0.remove(position))
2373 }
2374}
2375
2376impl ValgrindTool {
2377 pub fn id(&self) -> String {
2379 match self {
2380 Self::DHAT => "dhat".to_owned(),
2381 Self::Callgrind => "callgrind".to_owned(),
2382 Self::Memcheck => "memcheck".to_owned(),
2383 Self::Helgrind => "helgrind".to_owned(),
2384 Self::DRD => "drd".to_owned(),
2385 Self::Massif => "massif".to_owned(),
2386 Self::BBV => "exp-bbv".to_owned(),
2387 Self::Cachegrind => "cachegrind".to_owned(),
2388 }
2389 }
2390
2391 pub fn has_output_file(&self) -> bool {
2393 matches!(
2394 self,
2395 Self::Callgrind | Self::DHAT | Self::BBV | Self::Massif | Self::Cachegrind
2396 )
2397 }
2398
2399 pub fn has_xtree_file(&self) -> bool {
2401 matches!(self, Self::Memcheck | Self::Massif | Self::Helgrind)
2402 }
2403
2404 pub fn has_xleak_file(&self) -> bool {
2406 *self == Self::Memcheck
2407 }
2408}
2409
2410impl Display for ValgrindTool {
2411 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2412 f.write_str(&self.id())
2413 }
2414}
2415
2416#[cfg(feature = "runner")]
2417impl FromStr for ValgrindTool {
2418 type Err = anyhow::Error;
2419
2420 fn from_str(s: &str) -> Result<Self, Self::Err> {
2421 Self::try_from(s.to_lowercase().as_str())
2422 }
2423}
2424
2425#[cfg(feature = "runner")]
2426impl TryFrom<&str> for ValgrindTool {
2427 type Error = anyhow::Error;
2428
2429 fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
2430 match value {
2431 "callgrind" => Ok(Self::Callgrind),
2432 "cachegrind" => Ok(Self::Cachegrind),
2433 "dhat" => Ok(Self::DHAT),
2434 "memcheck" => Ok(Self::Memcheck),
2435 "helgrind" => Ok(Self::Helgrind),
2436 "drd" => Ok(Self::DRD),
2437 "massif" => Ok(Self::Massif),
2438 "exp-bbv" => Ok(Self::BBV),
2439 v => Err(anyhow!("Unknown tool '{}'", v)),
2440 }
2441 }
2442}
2443
2444pub fn update_option<T: Clone>(first: &Option<T>, other: &Option<T>) -> Option<T> {
2446 other.clone().or_else(|| first.clone())
2447}
2448
2449#[cfg(test)]
2450mod tests {
2451 use indexmap::indexset;
2452 use pretty_assertions::assert_eq;
2453 use rstest::rstest;
2454
2455 use super::EventKind::*;
2456 use super::{CachegrindMetric as Cm, *};
2457
2458 #[test]
2459 fn test_cachegrind_metric_from_str_ignore_case() {
2460 for metric in CachegrindMetric::iter() {
2461 let string = format!("{metric:?}");
2462 let actual = CachegrindMetric::from_str(&string);
2463 assert_eq!(actual.unwrap(), metric);
2464 }
2465 }
2466
2467 #[test]
2468 fn test_event_kind_from_str_ignore_case() {
2469 for event_kind in EventKind::iter() {
2470 let string = format!("{event_kind:?}");
2471 let actual = EventKind::from_str(&string);
2472 assert_eq!(actual.unwrap(), event_kind);
2473 }
2474 }
2475
2476 #[test]
2477 fn test_library_benchmark_config_update_from_all_when_default() {
2478 assert_eq!(
2479 LibraryBenchmarkConfig::default()
2480 .update_from_all([Some(&LibraryBenchmarkConfig::default())]),
2481 LibraryBenchmarkConfig::default()
2482 );
2483 }
2484
2485 #[test]
2486 fn test_library_benchmark_config_update_from_all_when_no_tools_override() {
2487 let base = LibraryBenchmarkConfig::default();
2488 let other = LibraryBenchmarkConfig {
2489 env_clear: Some(true),
2490 valgrind_args: RawArgs(vec!["--valgrind-arg=yes".to_owned()]),
2491 envs: vec![(OsString::from("MY_ENV"), Some(OsString::from("value")))],
2492 tools: Tools(vec![Tool {
2493 kind: ValgrindTool::DHAT,
2494 enable: None,
2495 raw_args: RawArgs(vec![]),
2496 show_log: None,
2497 regression_config: Some(ToolRegressionConfig::Callgrind(
2498 CallgrindRegressionConfig::default(),
2499 )),
2500 flamegraph_config: Some(ToolFlamegraphConfig::Callgrind(
2501 FlamegraphConfig::default(),
2502 )),
2503 entry_point: Some(EntryPoint::default()),
2504 output_format: Some(ToolOutputFormat::None),
2505 frames: Some(vec!["some::frame".to_owned()]),
2506 }]),
2507 tools_override: None,
2508 output_format: None,
2509 default_tool: Some(ValgrindTool::BBV),
2510 };
2511
2512 assert_eq!(base.update_from_all([Some(&other.clone())]), other);
2513 }
2514
2515 #[test]
2516 fn test_library_benchmark_config_update_from_all_when_tools_override() {
2517 let base = LibraryBenchmarkConfig::default();
2518 let other = LibraryBenchmarkConfig {
2519 env_clear: Some(true),
2520 valgrind_args: RawArgs(vec!["--valgrind-arg=yes".to_owned()]),
2521 envs: vec![(OsString::from("MY_ENV"), Some(OsString::from("value")))],
2522 tools: Tools(vec![Tool {
2523 kind: ValgrindTool::DHAT,
2524 enable: None,
2525 raw_args: RawArgs(vec![]),
2526 show_log: None,
2527 regression_config: Some(ToolRegressionConfig::Callgrind(
2528 CallgrindRegressionConfig::default(),
2529 )),
2530 flamegraph_config: Some(ToolFlamegraphConfig::Callgrind(
2531 FlamegraphConfig::default(),
2532 )),
2533 entry_point: Some(EntryPoint::default()),
2534 output_format: Some(ToolOutputFormat::None),
2535 frames: Some(vec!["some::frame".to_owned()]),
2536 }]),
2537 tools_override: Some(Tools(vec![])),
2538 output_format: Some(OutputFormat::default()),
2539 default_tool: Some(ValgrindTool::BBV),
2540 };
2541 let expected = LibraryBenchmarkConfig {
2542 tools: other.tools_override.as_ref().unwrap().clone(),
2543 tools_override: None,
2544 ..other.clone()
2545 };
2546
2547 assert_eq!(base.update_from_all([Some(&other)]), expected);
2548 }
2549
2550 #[rstest]
2551 #[case::env_clear(
2552 LibraryBenchmarkConfig {
2553 env_clear: Some(true),
2554 ..Default::default()
2555 }
2556 )]
2557 fn test_library_benchmark_config_update_from_all_truncate_description(
2558 #[case] config: LibraryBenchmarkConfig,
2559 ) {
2560 let actual = LibraryBenchmarkConfig::default().update_from_all([Some(&config)]);
2561 assert_eq!(actual, config);
2562 }
2563
2564 #[rstest]
2565 #[case::all_none(None, None, None)]
2566 #[case::some_and_none(Some(true), None, Some(true))]
2567 #[case::none_and_some(None, Some(true), Some(true))]
2568 #[case::some_and_some(Some(false), Some(true), Some(true))]
2569 #[case::some_and_some_value_does_not_matter(Some(true), Some(false), Some(false))]
2570 fn test_update_option(
2571 #[case] first: Option<bool>,
2572 #[case] other: Option<bool>,
2573 #[case] expected: Option<bool>,
2574 ) {
2575 assert_eq!(update_option(&first, &other), expected);
2576 }
2577
2578 #[rstest]
2579 #[case::empty(vec![], &[], vec![])]
2580 #[case::empty_base(vec![], &["--a=yes"], vec!["--a=yes"])]
2581 #[case::no_flags(vec![], &["a=yes"], vec!["--a=yes"])]
2582 #[case::already_exists_single(vec!["--a=yes"], &["--a=yes"], vec!["--a=yes","--a=yes"])]
2583 #[case::already_exists_when_multiple(vec!["--a=yes", "--b=yes"], &["--a=yes"], vec!["--a=yes", "--b=yes", "--a=yes"])]
2584 fn test_raw_args_extend_ignore_flags(
2585 #[case] base: Vec<&str>,
2586 #[case] data: &[&str],
2587 #[case] expected: Vec<&str>,
2588 ) {
2589 let mut base = RawArgs(base.iter().map(std::string::ToString::to_string).collect());
2590 base.extend_ignore_flag(data.iter().map(std::string::ToString::to_string));
2591
2592 assert_eq!(base.0.into_iter().collect::<Vec<String>>(), expected);
2593 }
2594
2595 #[rstest]
2596 #[case::none(CallgrindMetrics::None, indexset![])]
2597 #[case::all(CallgrindMetrics::All, indexset![Ir, Dr, Dw, I1mr, D1mr, D1mw, ILmr, DLmr,
2598 DLmw, I1MissRate, LLiMissRate, D1MissRate, LLdMissRate, LLMissRate, L1hits, LLhits, RamHits,
2599 TotalRW, L1HitRate, LLHitRate, RamHitRate, EstimatedCycles, SysCount, SysTime, SysCpuTime,
2600 Ge, Bc, Bcm, Bi, Bim, ILdmr, DLdmr, DLdmw, AcCost1, AcCost2, SpLoss1, SpLoss2]
2601 )]
2602 #[case::default(CallgrindMetrics::Default, indexset![Ir, L1hits, LLhits, RamHits, TotalRW,
2603 EstimatedCycles, SysCount, SysTime, SysCpuTime, Ge, Bc,
2604 Bcm, Bi, Bim, ILdmr, DLdmr, DLdmw, AcCost1, AcCost2, SpLoss1, SpLoss2]
2605 )]
2606 #[case::cache_misses(CallgrindMetrics::CacheMisses, indexset![I1mr, D1mr, D1mw, ILmr,
2607 DLmr, DLmw]
2608 )]
2609 #[case::cache_miss_rates(CallgrindMetrics::CacheMissRates, indexset![I1MissRate,
2610 D1MissRate, LLMissRate, LLiMissRate, LLdMissRate]
2611 )]
2612 #[case::cache_hits(CallgrindMetrics::CacheHits, indexset![L1hits, LLhits, RamHits])]
2613 #[case::cache_hit_rates(CallgrindMetrics::CacheHitRates, indexset![
2614 L1HitRate, LLHitRate, RamHitRate
2615 ])]
2616 #[case::cache_sim(CallgrindMetrics::CacheSim, indexset![Dr, Dw, I1mr, D1mr, D1mw, ILmr, DLmr,
2617 DLmw, I1MissRate, LLiMissRate, D1MissRate, LLdMissRate, LLMissRate, L1hits, LLhits, RamHits,
2618 TotalRW, L1HitRate, LLHitRate, RamHitRate, EstimatedCycles]
2619 )]
2620 #[case::cache_use(CallgrindMetrics::CacheUse, indexset![AcCost1, AcCost2, SpLoss1, SpLoss2])]
2621 #[case::system_calls(CallgrindMetrics::SystemCalls, indexset![SysCount, SysTime, SysCpuTime])]
2622 #[case::branch_sim(CallgrindMetrics::BranchSim, indexset![Bc, Bcm, Bi, Bim])]
2623 #[case::write_back(CallgrindMetrics::WriteBackBehaviour, indexset![ILdmr, DLdmr, DLdmw])]
2624 #[case::single_event(CallgrindMetrics::SingleEvent(Ir), indexset![Ir])]
2625 fn test_callgrind_metrics_into_index_set(
2626 #[case] callgrind_metrics: CallgrindMetrics,
2627 #[case] expected_metrics: IndexSet<EventKind>,
2628 ) {
2629 assert_eq!(IndexSet::from(callgrind_metrics), expected_metrics);
2630 }
2631
2632 #[rstest]
2633 #[case::none(CachegrindMetrics::None, indexset![])]
2634 #[case::all(CachegrindMetrics::All, indexset![Cm::Ir, Cm::Dr, Cm::Dw, Cm::I1mr, Cm::D1mr,
2635 Cm::D1mw, Cm::ILmr, Cm::DLmr, Cm::DLmw, Cm::I1MissRate, Cm::LLiMissRate, Cm::D1MissRate,
2636 Cm::LLdMissRate, Cm::LLMissRate, Cm::L1hits, Cm::LLhits, Cm::RamHits, Cm::TotalRW,
2637 Cm::L1HitRate, Cm::LLHitRate, Cm::RamHitRate, Cm::EstimatedCycles, Cm::Bc, Cm::Bcm, Cm::Bi,
2638 Cm::Bim,
2639 ])]
2640 #[case::default(CachegrindMetrics::Default, indexset![Cm::Ir, Cm::L1hits, Cm::LLhits,
2641 Cm::RamHits, Cm::TotalRW, Cm::EstimatedCycles, Cm::Bc, Cm::Bcm, Cm::Bi, Cm::Bim
2642 ])]
2643 #[case::cache_misses(CachegrindMetrics::CacheMisses, indexset![Cm::I1mr, Cm::D1mr, Cm::D1mw,
2644 Cm::ILmr, Cm::DLmr, Cm::DLmw
2645 ])]
2646 #[case::cache_miss_rates(CachegrindMetrics::CacheMissRates, indexset![Cm::I1MissRate,
2647 Cm::D1MissRate, Cm::LLMissRate, Cm::LLiMissRate, Cm::LLdMissRate
2648 ])]
2649 #[case::cache_hits(CachegrindMetrics::CacheHits, indexset![
2650 Cm::L1hits, Cm::LLhits, Cm::RamHits
2651 ])]
2652 #[case::cache_hit_rates(CachegrindMetrics::CacheHitRates, indexset![
2653 Cm::L1HitRate, Cm::LLHitRate, Cm::RamHitRate
2654 ])]
2655 #[case::cache_sim(CachegrindMetrics::CacheSim, indexset![Cm::Dr, Cm::Dw, Cm::I1mr, Cm::D1mr,
2656 Cm::D1mw, Cm::ILmr, Cm::DLmr, Cm::DLmw, Cm::I1MissRate, Cm::LLiMissRate, Cm::D1MissRate,
2657 Cm::LLdMissRate, Cm::LLMissRate, Cm::L1hits, Cm::LLhits, Cm::RamHits, Cm::TotalRW,
2658 Cm::L1HitRate, Cm::LLHitRate, Cm::RamHitRate, Cm::EstimatedCycles
2659 ])]
2660 #[case::branch_sim(CachegrindMetrics::BranchSim, indexset![
2661 Cm::Bc, Cm::Bcm, Cm::Bi, Cm::Bim
2662 ])]
2663 #[case::single_event(CachegrindMetrics::SingleEvent(Cm::Ir), indexset![Cm::Ir])]
2664 fn test_cachegrind_metrics_into_index_set(
2665 #[case] cachegrind_metrics: CachegrindMetrics,
2666 #[case] expected_metrics: IndexSet<CachegrindMetric>,
2667 ) {
2668 assert_eq!(IndexSet::from(cachegrind_metrics), expected_metrics);
2669 }
2670
2671 #[rstest]
2672 #[case::empty(&[], &[], &[])]
2673 #[case::prepend_empty(&["--some"], &[], &["--some"])]
2674 #[case::initial_empty(&[], &["--some"], &["--some"])]
2675 #[case::both_same_arg(&["--some"], &["--some"], &["--some", "--some"])]
2676 #[case::both_different_arg(&["--some"], &["--other"], &["--other", "--some"])]
2677 fn test_raw_args_prepend(
2678 #[case] raw_args: &[&str],
2679 #[case] other: &[&str],
2680 #[case] expected: &[&str],
2681 ) {
2682 let mut raw_args = RawArgs::new(raw_args.iter().map(ToOwned::to_owned));
2683 let other = RawArgs::new(other.iter().map(ToOwned::to_owned));
2684 let expected = RawArgs::new(expected.iter().map(ToOwned::to_owned));
2685
2686 raw_args.prepend(&other);
2687 assert_eq!(raw_args, expected);
2688 }
2689
2690 #[test]
2691 fn test_tool_update_when_tools_match() {
2692 let mut base = Tool::new(ValgrindTool::Callgrind);
2693 let other = Tool {
2694 kind: ValgrindTool::Callgrind,
2695 enable: Some(true),
2696 raw_args: RawArgs::new(["--some"]),
2697 show_log: Some(false),
2698 regression_config: Some(ToolRegressionConfig::None),
2699 flamegraph_config: Some(ToolFlamegraphConfig::None),
2700 output_format: Some(ToolOutputFormat::None),
2701 entry_point: Some(EntryPoint::Default),
2702 frames: Some(vec!["some::frame".to_owned()]),
2703 };
2704 let expected = other.clone();
2705 base.update(&other);
2706 assert_eq!(base, expected);
2707 }
2708
2709 #[test]
2710 fn test_tool_update_when_tools_not_match() {
2711 let mut base = Tool::new(ValgrindTool::Callgrind);
2712 let other = Tool {
2713 kind: ValgrindTool::DRD,
2714 enable: Some(true),
2715 raw_args: RawArgs::new(["--some"]),
2716 show_log: Some(false),
2717 regression_config: Some(ToolRegressionConfig::None),
2718 flamegraph_config: Some(ToolFlamegraphConfig::None),
2719 output_format: Some(ToolOutputFormat::None),
2720 entry_point: Some(EntryPoint::Default),
2721 frames: Some(vec!["some::frame".to_owned()]),
2722 };
2723
2724 let expected = base.clone();
2725 base.update(&other);
2726
2727 assert_eq!(base, expected);
2728 }
2729}