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

xd009642 / llvm-profparser / #239

pending completion
#239

push

web-flow
More perf work (#79)

* move to using Arc<str>

More string allocation reuse means more speed less memory use etc etc.

* Reuse the hash read from hash_table

md5 hashing is a bottleneck so seek to remove it where we can.

* Bump version in prep

35 of 47 new or added lines in 6 files covered. (74.47%)

4 existing lines in 2 files now uncovered.

1119 of 1532 relevant lines covered (73.04%)

2165.99 hits per line

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

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

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

28
#[derive(Clone, Debug, Eq, PartialEq, Parser)]
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, Parser)]
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", value_parser=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, Parser)]
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, Parser)]
129
pub struct Opts {
130
    #[command(subcommand)]
131
    cmd: Command,
132
}
133

134
impl Opts {
135
    fn debug(&self) -> bool {
13✔
136
        match &self.cmd {
13✔
137
            &Command::Show { ref show } => show.debug,
26✔
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> {
5✔
145
    if !input.contains(',') {
5✔
146
        Ok((1, input.to_string()))
1✔
147
    } else {
148
        let parts = input.split(',').collect::<Vec<_>>();
16✔
149
        if parts.len() != 2 {
4✔
150
            Err("Unexpected weighting format, expected $weight,$name or just $name".to_string())
1✔
151
        } else {
152
            let weight = parts[0]
5✔
153
                .parse()
154
                .map_err(|e| format!("Invalid weight: {}", e))?;
5✔
155
            if weight < 1 {
2✔
156
                Err("Weight must be positive integer".to_string())
×
157
            } else {
158
                Ok((weight, parts[1].to_string()))
4✔
159
            }
160
        }
161
    }
162
}
163

NEW
164
fn check_function(name: Option<&str>, 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<()> {
13✔
198
        let profile = parse(&self.input)?;
39✔
199
        let mut summary = ProfileSummary::new();
26✔
200

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

219
            if show && self.text {
78✔
220
                // TODO text format dump
221
                continue;
×
222
            }
223
            summary.add_record(&func.record);
117✔
224

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

324
        if self.show_detailed_summary {
13✔
325
            println!("Total number of blocks: ?");
×
326
            println!("Total count: ?");
×
327
        }
328
        Ok(())
13✔
329
    }
330
}
331

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

345
fn enable_debug_logging() -> anyhow::Result<()> {
×
346
    let fmt = tracing_subscriber::fmt::Layer::default();
×
347
    let subscriber = fmt
×
348
        .with_filter(filter_fn(|metadata| {
×
349
            metadata.target().contains("llvm_profparser")
×
350
        }))
351
        .with_subscriber(Registry::default());
×
352
    tracing::subscriber::set_global_default(subscriber)?;
×
353
    Ok(())
×
354
}
355

356
fn main() -> Result<()> {
13✔
357
    let opts = Opts::parse();
26✔
358
    if opts.debug() {
26✔
359
        let _ = enable_debug_logging();
×
360
    }
361
    match opts.cmd {
13✔
362
        Command::Show { show } => show.run(),
39✔
363
        Command::Merge { merge } => merge.run(),
×
364
        _ => {
365
            panic!("Unsupported command");
×
366
        }
367
    }
368
}
369

370
#[cfg(test)]
371
mod tests {
372
    use super::*;
373

374
    #[test]
375
    fn weight_arg_parsing() {
376
        // Examples taken from LLVM docs
377
        let foo_10 = "10,foo.profdata";
378
        let bar_1 = "1,bar.profdata";
379

380
        assert_eq!(
381
            Ok((10, "foo.profdata".to_string())),
382
            try_parse_weighted(foo_10)
383
        );
384
        assert_eq!(
385
            Ok((1, "bar.profdata".to_string())),
386
            try_parse_weighted(bar_1)
387
        );
388
        assert_eq!(
389
            Ok((1, "foo.profdata".to_string())),
390
            try_parse_weighted("foo.profdata")
391
        );
392
        assert!(try_parse_weighted("foo.profdata,1").is_err());
393
        assert!(try_parse_weighted("1,1,foo.profdata").is_err());
394
    }
395
}
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