1use anyhow::bail;
2use csv::Reader;
3use procfs::CpuInfo;
4use procfs::Current as _;
5use regex::Regex;
6use serde::Deserialize;
7use serde::Deserializer;
8use serde::Serialize;
9use std::env;
10use std::env::consts::ARCH;
11use std::ffi::OsString;
12use std::fs;
13use std::path::Path;
14
15use crate::resources::ResourceDir;
16use anyhow::Result;
17
18use std::collections::HashMap;
19
20fn hex_to_u64<'de, D>(de: D) -> Result<u64, D::Error>
21where
22 D: Deserializer<'de>,
23{
24 let s: &str = Deserialize::deserialize(de)?;
25 let s = s.to_lowercase();
26 let s = s.trim_start_matches("0x");
27 u64::from_str_radix(s, 16).map_err(serde::de::Error::custom)
28}
29
30fn hexlist<'de, D>(de: D) -> Result<Vec<u64>, D::Error>
33where
34 D: Deserializer<'de>,
35{
36 let mut result = vec![];
37 let s: &str = Deserialize::deserialize(de)?;
38 for token in s.split(',') {
39 let radix = if token.to_lowercase().starts_with("0x") {
40 16
41 } else {
42 10
43 };
44 let event = u64::from_str_radix(token.to_lowercase().trim_start_matches("0x"), radix)
45 .map_err(serde::de::Error::custom)?;
46
47 result.push(event);
48 }
49
50 Ok(result)
51}
52
53fn num_to_bool<'de, D>(de: D) -> Result<bool, D::Error>
54where
55 D: Deserializer<'de>,
56{
57 let s: &str = Deserialize::deserialize(de)?;
58 let num = u64::from_str_radix(s, 16).map_err(serde::de::Error::custom)?;
59 Ok(num != 0)
60}
61
62#[derive(Clone, Debug, Serialize, Deserialize, Default)]
63#[serde(default)]
64pub struct PMUSpec {
65 #[serde(alias = "BriefDescription", alias = "BriefDescript6ion")]
68 desc: Option<String>,
69
70 #[serde(alias = "PublicDescription")]
71 desc_public: Option<String>,
72
73 #[serde(alias = "EventCode")]
74 #[serde(deserialize_with = "hexlist")]
75 pub event: Vec<u64>,
76
77 #[serde(alias = "EventName")]
78 pub name: String,
79
80 #[serde(alias = "UMask")]
81 #[serde(deserialize_with = "hex_to_u64")]
82 pub umask: u64,
83
84 #[serde(alias = "Unit")]
85 pmu: Option<String>,
86
87 #[serde(alias = "ConfigCode")]
88 #[serde(deserialize_with = "hex_to_u64")]
89 config: u64,
90
91 #[serde(alias = "MetricExpr")]
93 metric_expr: Option<String>,
94
95 #[serde(alias = "MetricName")]
96 metric_name: Option<String>,
97
98 #[serde(alias = "MetricGroup")]
99 metric_group: Option<String>,
100
101 #[serde(alias = "MetricConstraint")]
102 metric_constraint: Option<String>,
103
104 #[serde(alias = "PerPkg")]
105 #[serde(deserialize_with = "num_to_bool")]
106 per_pkg: bool,
107
108 #[serde(alias = "Invert")]
109 #[serde(deserialize_with = "num_to_bool")]
110 invert: bool,
111
112 #[serde(alias = "MSRIndex")]
113 #[serde(deserialize_with = "hexlist", default)]
114 msr_index: Vec<u64>,
115
116 #[serde(alias = "MSRValue")]
117 #[serde(deserialize_with = "hex_to_u64")]
118 msr_value: u64,
119
120 #[serde(alias = "Counter")]
121 counter: Option<String>,
122
123 #[serde(alias = "CounterNumFixed")]
124 counters_num_fixed: Option<u64>,
125
126 #[serde(alias = "CounterNumGeneric")]
127 counters_num_generic: Option<u64>,
128}
129
130pub struct PMUManager {
131 pub dataroot: OsString,
132 pub arch: String,
133 pub tuple: String,
134 pub codename: String,
135 pub pmus: HashMap<String, PMUSpec>,
136}
137
138impl PMUManager {
139 fn identify_architecture() -> Result<String> {
142 let cpuinfo = CpuInfo::current()?;
143 Ok(format!(
144 "{}-{}-{:X}",
145 cpuinfo.fields["vendor_id"],
146 cpuinfo.fields["cpu family"],
147 cpuinfo.fields["model"].parse::<i32>().unwrap()
148 ))
149 }
150
151 pub fn list_counters(&self) -> Result<()> {
153 for pmu in self.pmus.iter() {
154 println!("{}", serde_json::to_string_pretty(&pmu)?);
155 }
156
157 Ok(())
158 }
159
160 pub fn list_metadata(&self) -> () {
161 println!("Dataroot {}", self.dataroot.to_string_lossy());
162 println!("Arch: {}", self.arch);
163 println!("Tuple: {}", self.tuple);
164 println!("Codename: {}", self.codename);
165 }
166
167 fn read_file_counters(json_bytes: &[u8]) -> Result<Vec<PMUSpec>> {
168 Ok(serde_json::from_slice(json_bytes)?)
169 }
170
171 fn arch_code() -> String {
172 String::from(match ARCH {
173 "x86" => "x86",
174 "x86_64" => "x86",
175 "aarch64" => "arm64",
176 "powerpc" => "powerpc",
177 "powerpc64" => "powerpc",
178 "riscv32" => "riscv",
179 "riscv64" => "riscv",
180 "s390x" => "s390",
181 "loongarch32" => "x86",
182 "loongarch64" => "x86",
183 _ => panic!("unsupported architecture"),
184 })
185 }
186
187 fn spec_dir(resource_dir: &ResourceDir, arch: &str, tuple: &str) -> Result<String> {
188 let arch_dir = resource_dir.get_dir(arch)?;
189 let mapfile = arch_dir.get_file("mapfile.csv")?;
190 let mapfile_contents = mapfile.read()?;
191
192 for record in Reader::from_reader(mapfile_contents.as_ref()).records() {
193 let record = record?;
194 let regex = record.get(0).expect("no regex found in csv");
195 let codename = record.get(2).expect("no codename found in csv");
196 let re = Regex::new(regex)?;
197
198 if re.is_match(tuple) {
199 return Ok(codename.to_string());
200 }
201 }
202
203 bail!("No matching config for tuple")
204 }
205
206 fn new_with_resource_dir(
207 resource_dir: ResourceDir,
208 dataroot_display: OsString,
209 ) -> Result<Self> {
210 let tuple = Self::identify_architecture()?;
211 let arch = Self::arch_code();
212 let codename = Self::spec_dir(&resource_dir, &arch, &tuple)?;
213
214 let arch_dir = resource_dir.get_dir(&arch)?;
215 let spec_dir = arch_dir.get_dir(&codename)?;
216
217 let mut pmus = HashMap::new();
218 for file in spec_dir.files()? {
219 if file.path().ends_with("metricgroups.json") {
221 continue;
222 }
223
224 let file_contents = file.read()?;
225 let counters = Self::read_file_counters(file_contents.as_ref())?;
226
227 for counter in counters.iter() {
228 pmus.insert(String::from(&counter.name), counter.clone());
229 }
230 }
231
232 Ok(Self {
233 dataroot: dataroot_display,
234 arch,
235 tuple,
236 codename,
237 pmus,
238 })
239 }
240
241 pub fn new() -> Result<Self> {
242 let mut dataroot: OsString = env::current_exe()?.into();
243 dataroot.push(":embedded");
244
245 let resource_dir = ResourceDir::default();
246 Self::new_with_resource_dir(resource_dir, dataroot)
247 }
248
249 pub fn new_with_dataroot(dataroot: Option<&Path>) -> Result<Self> {
250 let dataroot = match dataroot {
251 Some(path) => fs::canonicalize(path)?,
252 None => env::current_dir()?.to_path_buf(),
253 };
254
255 let resource_dir = ResourceDir::new_filesystem(dataroot.clone());
256 Self::new_with_resource_dir(resource_dir, dataroot.into())
257 }
258}