• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

xd009642 / llvm-profparser / #206

07 Aug 2025 02:14PM UTC coverage: 69.151% (-0.3%) from 69.494%
#206

push

web-flow
support mc/dc mapping regions in profdata files (#52)

2 of 27 new or added lines in 2 files covered. (7.41%)

13 existing lines in 6 files now uncovered.

1206 of 1744 relevant lines covered (69.15%)

3.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.82
/src/bin/profparser.rs
1
use anyhow::Result;
2
use llvm_profparser::instrumentation_profile::summary::*;
3
use llvm_profparser::instrumentation_profile::types::*;
4
use llvm_profparser::*;
5
use std::cmp::Ordering;
6
use std::collections::BinaryHeap;
7
use std::path::PathBuf;
8
use structopt::StructOpt;
9
use tracing_subscriber::filter::filter_fn;
10
use tracing_subscriber::{Layer, Registry};
11

12
#[derive(Clone, Debug, Eq, PartialEq, StructOpt)]
13
pub enum Command {
14
    Show {
15
        #[structopt(flatten)]
16
        show: ShowCommand,
17
    },
18
    Merge {
19
        #[structopt(flatten)]
20
        merge: MergeCommand,
21
    },
22
    Overlap {
23
        #[structopt(flatten)]
24
        overlap: OverlapCommand,
25
    },
26
}
27

28
#[derive(Clone, Debug, Eq, PartialEq, StructOpt)]
29
pub struct ShowCommand {
30
    /// Input profraw file to show some information about
31
    #[structopt(name = "<filename...>", long = "input", short = "i")]
32
    input: PathBuf,
33
    /// Show counter values for shown functions
34
    #[structopt(long = "counts")]
35
    show_counts: bool,
36
    /// Details for every function
37
    #[structopt(long = "all-functions")]
38
    all_functions: bool,
39
    /// Show instr profile data in text dump format
40
    #[structopt(long = "text")]
41
    text: bool,
42
    /// Show detailed profile summary
43
    #[structopt(long = "show_detailed_summary")]
44
    show_detailed_summary: bool,
45
    /// Cutoff percentages (times 10000) for generating detailed summary
46
    #[structopt(long = "detailed_summary_cutoffs")]
47
    detailed_summary_cutoffs: Vec<usize>,
48
    /// Show profile summary of a list of hot functions
49
    #[structopt(long = "show_hot_fn_list")]
50
    show_hot_fn_list: bool,
51
    /// Show context sensitive counts
52
    #[structopt(long = "showcs")]
53
    showcs: bool,
54
    /// Details for matching functions
55
    #[structopt(long = "function")]
56
    function: Option<String>,
57
    /// Output file
58
    #[structopt(long = "output", short = "o")]
59
    output: Option<String>,
60
    /// Show the list of functions with the largest internal counts
61
    #[structopt(long = "topn")]
62
    topn: Option<usize>,
63
    /// Set the count value cutoff. Functions with the maximum count less than
64
    /// this value will not be printed out. (Default is 0)
65
    #[structopt(long = "value_cutoff", default_value = "0")]
66
    value_cutoff: u64,
67
    /// Set the count value cutoff. Functions with the maximum count below the
68
    /// cutoff value
69
    #[structopt(long = "only_list_below")]
70
    only_list_below: bool,
71
    /// Show profile symbol list if it exists in the profile.
72
    #[structopt(long = "show_profile_sym_list")]
73
    show_profile_sym_list: bool,
74
    /// Show the information of each section in the sample profile. The flag is
75
    /// only usable when the sample profile is in extbinary format
76
    #[structopt(long = "show_section_info_only")]
77
    show_section_info_only: bool,
78
    /// Turn on debug logging
79
    #[structopt(long)]
80
    debug: bool,
81
}
82

83
#[derive(Clone, Debug, Eq, PartialEq, StructOpt)]
84
pub struct MergeCommand {
85
    /// Input files to merge
86
    #[structopt(name = "<filename...>", long = "input", short = "i")]
87
    input: Vec<PathBuf>,
88
    /// Output file
89
    #[structopt(long = "output", short = "o")]
90
    output: PathBuf,
91
    /// List of weights and filenames in `<weight>,<filename>` format
92
    #[structopt(long = "weighted-input", parse(try_from_str=try_parse_weighted))]
93
    weighted_input: Vec<(u64, String)>,
94
    /// Number of merge threads to use (will autodetect by default)
95
    #[structopt(long = "num-threads", short = "j")]
96
    jobs: Option<usize>,
97
    /// Turn on debug logging
98
    #[structopt(long)]
99
    debug: bool,
100
}
101

102
#[derive(Clone, Debug, Eq, PartialEq, StructOpt)]
103
pub struct OverlapCommand {
104
    #[structopt(name = "<base profile file>")]
105
    base_file: PathBuf,
106
    #[structopt(name = "<test profile file>")]
107
    test_file: PathBuf,
108
    #[structopt(long = "output", short = "o")]
109
    output: Option<PathBuf>,
110
    /// For context sensitive counts
111
    #[structopt(long = "cs")]
112
    context_sensitive_counts: bool,
113
    /// Function level overlap information for every function in test profile with max count value
114
    /// greater than the parameter value
115
    #[structopt(long = "value-cutoff")]
116
    value_cutoff: Option<usize>,
117
    /// Function level overlap information for matching functions
118
    #[structopt(long = "function")]
119
    function: Option<String>,
120
    /// Generate a sparse profile
121
    #[structopt(long = "sparse")]
122
    sparse: bool,
123
    /// Turn on debug logging
124
    #[structopt(long)]
125
    debug: bool,
126
}
127

128
#[derive(Clone, Debug, Eq, PartialEq, StructOpt)]
129
pub struct Opts {
130
    #[structopt(subcommand)]
131
    cmd: Command,
132
}
133

134
impl Opts {
135
    fn debug(&self) -> bool {
14✔
136
        match &self.cmd {
14✔
137
            &Command::Show { ref show } => show.debug,
14✔
138
            &Command::Merge { ref merge } => merge.debug,
×
139
            &Command::Overlap { ref overlap } => overlap.debug,
×
140
        }
141
    }
142
}
143

144
fn try_parse_weighted(input: &str) -> Result<(u64, String), String> {
1✔
145
    if !input.contains(',') {
2✔
146
        Ok((1, input.to_string()))
1✔
147
    } else {
148
        let parts = input.split(',').collect::<Vec<_>>();
1✔
149
        if parts.len() != 2 {
3✔
150
            Err("Unexpected weighting format, expected $weight,$name or just $name".to_string())
2✔
151
        } else {
152
            let weight = parts[0]
4✔
153
                .parse()
154
                .map_err(|e| format!("Invalid weight: {}", e))?;
4✔
155
            if weight < 1 {
1✔
156
                Err("Weight must be positive integer".to_string())
×
157
            } else {
158
                Ok((weight, parts[1].to_string()))
2✔
159
            }
160
        }
161
    }
162
}
163

164
fn check_function(name: Option<&String>, pattern: Option<&String>) -> bool {
×
165
    match pattern {
×
166
        Some(pat) => name.map(|x| x.contains(pat)).unwrap_or(false),
×
167
        None => false,
×
168
    }
169
}
170

171
#[derive(Clone, Debug, Eq)]
172
struct HotFn {
173
    name: String,
174
    count: u64,
175
}
176

177
impl PartialOrd for HotFn {
178
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
×
179
        Some(self.cmp(other))
×
180
    }
181
}
182

183
impl Ord for HotFn {
184
    fn cmp(&self, other: &Self) -> Ordering {
×
185
        // Do the reverse here
186
        other.count.cmp(&self.count)
×
187
    }
188
}
189

190
impl PartialEq for HotFn {
191
    fn eq(&self, other: &Self) -> bool {
×
192
        self.count == other.count
×
193
    }
194
}
195

196
impl ShowCommand {
197
    pub fn run(&self) -> Result<()> {
14✔
198
        let profile = parse(&self.input)?;
14✔
199
        let mut summary = ProfileSummary::new();
14✔
200

201
        let is_ir_instr = profile.is_ir_level_profile();
28✔
202
        let mut hotties =
14✔
203
            BinaryHeap::<HotFn>::with_capacity(self.topn.unwrap_or_default() as usize);
204
        let mut shown_funcs = 0;
14✔
205
        let mut below_cutoff_funcs = 0;
14✔
206
        let topn = self.topn.unwrap_or_default();
28✔
207
        for func in profile.records() {
14✔
208
            if func.name.is_none() || func.hash.is_none() {
28✔
209
                continue;
210
            }
211
            if is_ir_instr && func.has_cs_flag() != self.showcs {
14✔
212
                continue;
213
            }
214
            let show =
14✔
215
                self.all_functions || check_function(func.name.as_ref(), self.function.as_ref());
216

217
            if show && self.text {
28✔
218
                // TODO text format dump
219
                continue;
220
            }
221
            summary.add_record(&func.record);
14✔
222

223
            let (func_max, func_sum) = func.counts().iter().fold((0, 0u64), |acc, x| {
28✔
224
                (*x.max(&acc.0), acc.1.saturating_add(*x))
14✔
225
            });
226
            if func_max < self.value_cutoff {
14✔
227
                below_cutoff_funcs += 1;
×
228
                if self.only_list_below {
×
229
                    println!(
×
230
                        "  {}: (Max = {} Sum = {})",
231
                        func.name.as_ref().unwrap(),
×
232
                        func_max,
233
                        func_sum
234
                    );
235
                    continue;
236
                }
237
            } else if self.only_list_below {
14✔
238
                continue;
239
            }
240
            if topn > 0 {
14✔
241
                if hotties.len() == topn {
×
242
                    let top = hotties.peek().unwrap();
×
243
                    if top.count < func_max {
×
244
                        hotties.pop();
×
245
                        hotties.push(HotFn {
×
246
                            name: func.name.as_ref().unwrap().to_string(),
×
UNCOV
247
                            count: func_max,
×
248
                        });
249
                    }
250
                } else {
251
                    hotties.push(HotFn {
×
252
                        name: func.name.as_ref().unwrap().to_string(),
×
UNCOV
253
                        count: func_max,
×
254
                    });
255
                }
256
            }
257
            if show {
14✔
258
                if shown_funcs == 0 {
14✔
259
                    println!("Counters:");
28✔
260
                }
261
                shown_funcs += 1;
28✔
262
                println!("  {}:", func.name.as_ref().unwrap());
28✔
263
                println!("    Hash: {:#018x}", func.hash.unwrap());
14✔
264
                println!("    Counters: {}", func.counts().len());
14✔
265
                if !is_ir_instr {
14✔
266
                    let counts = if func.counts().is_empty() {
42✔
267
                        0
×
268
                    } else {
269
                        func.counts()[0]
28✔
270
                    };
271
                    println!("    Function count: {}", counts);
14✔
272
                }
273
                if self.show_counts {
14✔
274
                    let start = if is_ir_instr { 0 } else { 1 };
14✔
275
                    let counts = func
14✔
276
                        .counts()
277
                        .iter()
278
                        .skip(start)
14✔
279
                        .map(|x| x.to_string())
40✔
280
                        .collect::<Vec<String>>()
281
                        .join(", ");
282
                    println!("    Block counts: [{}]", counts);
14✔
283
                }
284
            }
285
        }
286
        if profile.get_level() == InstrumentationLevel::Ir {
14✔
287
            // This is just to enable same printout in older versions with llvm 11
288
            #[cfg(not(llvm_11))]
289
            println!(
×
290
                "Instrumentation level: {}  entry_first = {}",
291
                profile.get_level(),
×
292
                profile.is_entry_first() as usize
×
293
            );
294
            #[cfg(llvm_11)]
295
            println!("Instrumentation level: {}", profile.get_level());
296
        } else {
297
            println!("Instrumentation level: {}", profile.get_level());
28✔
298
        }
299
        if self.all_functions || self.function.is_some() {
14✔
300
            println!("Functions shown: {}", shown_funcs);
28✔
301
        }
302
        println!("Total functions: {}", summary.num_functions());
28✔
303
        if self.value_cutoff > 0 {
14✔
304
            println!(
×
305
                "Number of functions with maximum count (< {} ): {}",
306
                self.value_cutoff, below_cutoff_funcs
307
            );
308
            println!(
×
309
                "Number of functions with maximum count (>= {}): {}",
310
                self.value_cutoff,
311
                summary.num_functions() - below_cutoff_funcs
×
312
            );
313
        }
314
        println!("Maximum function count: {}", summary.max_function_count());
28✔
315
        println!(
14✔
316
            "Maximum internal block count: {}",
317
            summary.max_internal_block_count()
14✔
318
        );
319
        if let Some(topn) = self.topn {
14✔
320
            println!(
×
321
                "Top {} functions with the largest internal block counts: ",
322
                topn
323
            );
324
            let hotties = hotties.into_sorted_vec();
×
325
            for f in hotties.iter() {
×
326
                println!("  {}, max count = {}", f.name, f.count);
×
327
            }
328
        }
329

330
        if self.show_detailed_summary {
14✔
331
            println!("Total number of blocks: ?");
×
332
            println!("Total count: ?");
×
333
        }
334
        Ok(())
14✔
335
    }
336
}
337

338
impl MergeCommand {
339
    fn run(&self) -> Result<()> {
×
340
        assert!(
×
341
            !self.input.is_empty(),
×
342
            "No input files selected. See merge --help"
343
        );
344
        let profile = merge_profiles(&self.input)?;
×
345
        // Now to write it out?
346
        println!("{:#?}", profile);
×
347
        Ok(())
×
348
    }
349
}
350

351
fn enable_debug_logging() -> anyhow::Result<()> {
×
352
    let fmt = tracing_subscriber::fmt::Layer::default();
×
353
    let subscriber = fmt
354
        .with_filter(filter_fn(|metadata| {
×
355
            metadata.target().contains("llvm_profparser")
×
356
        }))
357
        .with_subscriber(Registry::default());
×
358
    tracing::subscriber::set_global_default(subscriber)?;
×
359
    Ok(())
×
360
}
361

362
fn main() -> Result<()> {
14✔
363
    let opts = Opts::from_args();
14✔
364
    if opts.debug() {
28✔
365
        let _ = enable_debug_logging();
×
366
    }
367
    match opts.cmd {
14✔
368
        Command::Show { show } => show.run(),
14✔
369
        Command::Merge { merge } => merge.run(),
×
370
        _ => {
371
            panic!("Unsupported command");
×
372
        }
373
    }
374
}
375

376
#[cfg(test)]
377
mod tests {
378
    use super::*;
379

380
    #[test]
381
    fn weight_arg_parsing() {
3✔
382
        // Examples taken from LLVM docs
383
        let foo_10 = "10,foo.profdata";
1✔
384
        let bar_1 = "1,bar.profdata";
1✔
385

386
        assert_eq!(
1✔
387
            Ok((10, "foo.profdata".to_string())),
1✔
388
            try_parse_weighted(foo_10)
1✔
389
        );
390
        assert_eq!(
1✔
391
            Ok((1, "bar.profdata".to_string())),
1✔
392
            try_parse_weighted(bar_1)
1✔
393
        );
394
        assert_eq!(
1✔
395
            Ok((1, "foo.profdata".to_string())),
1✔
396
            try_parse_weighted("foo.profdata")
1✔
397
        );
398
        assert!(try_parse_weighted("foo.profdata,1").is_err());
1✔
399
        assert!(try_parse_weighted("1,1,foo.profdata").is_err());
1✔
400
    }
401
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc