Skip to main content

joule_profiler_cli/
lib.rs

1use std::collections::HashSet;
2
3use clap::{ArgAction, Parser, ValueEnum};
4
5use anyhow::Result;
6pub use commands::ProfilerCommand;
7use joule_profiler_core::config::{Command, Config, ProfileConfig};
8
9use crate::output::{
10    displayer::Displayer,
11    formats::{
12        OutputFormat, csv::CsvOutput, json::JsonOutput, output_format, terminal::TerminalOutput,
13    },
14};
15
16mod commands;
17mod logging;
18mod output;
19
20/// joule-profiler: measure program energy consumption
21#[allow(clippy::struct_excessive_bools)]
22#[derive(Parser, Debug)]
23#[command(name = "joule-profiler")]
24#[command(
25    version,
26    about = "Measure program metrics from various sources like RAPL"
27)]
28pub struct CliArgs {
29    /// Verbosity (-v, -vv, -vvv)
30    #[arg(short = 'v', long = "verbose", action = ArgAction::Count)]
31    pub verbose: u8,
32
33    /// Override the base path used to read Intel RAPL counters.
34    ///
35    /// By default, the profiler reads from:
36    ///   /sys/devices/virtual/powercap/intel-rapl
37    ///
38    /// If not provided, the profiler uses (by priority):
39    ///   1. $`JOULE_PROFILER_RAPL_PATH` (if set)
40    ///   2. /sys/devices/virtual/powercap/intel-rapl
41    #[arg(long = "rapl-path")]
42    pub rapl_path: Option<String>,
43
44    /// Sockets to measure (e.g. 0 or 0,1)
45    #[arg(short = 's', long = "sockets")]
46    pub sockets: Option<String>,
47
48    /// Export results as JSON instead of pretty terminal output
49    #[arg(long, conflicts_with = "csv")]
50    pub json: bool,
51
52    /// Export results as CSV (semicolon-separated values)
53    #[arg(long, conflicts_with = "json")]
54    pub csv: bool,
55
56    /// Output file for CSV/JSON (else `data<TIMESTAMP>`.csv/json)
57    #[arg(short = 'o', long = "output-file")]
58    pub output_file: Option<String>,
59
60    /// GPU support
61    #[arg(long)]
62    pub gpu: bool,
63
64    /// `perf_event` counters support
65    #[arg(long)]
66    pub perf: bool,
67
68    /// Choose RAPL backend between powercap or perf
69    #[arg(long = "rapl-backend", value_enum, default_value_t = RaplBackend::Perf)]
70    pub rapl_backend: RaplBackend,
71
72    /// The command to execute
73    #[command(subcommand)]
74    pub command: ProfilerCommand,
75}
76
77impl CliArgs {
78    pub fn from_args() -> Self {
79        Self::parse()
80    }
81}
82
83impl From<CliArgs> for Config {
84    fn from(cli_args: CliArgs) -> Self {
85        let command = match cli_args.command {
86            ProfilerCommand::Profile(profile_args) => Command::Profile(ProfileConfig {
87                stdout_file: profile_args.stdout_file,
88                cmd: profile_args.cmd,
89                token_pattern: profile_args.token_pattern,
90                use_root: profile_args.use_root,
91            }),
92
93            ProfilerCommand::ListSensors => Command::ListSensors,
94        };
95
96        Config {
97            command,
98            rapl_path: cli_args.rapl_path,
99        }
100    }
101}
102
103#[derive(Clone, Debug, ValueEnum)]
104pub enum RaplBackend {
105    Perf,
106    Powercap,
107}
108
109pub fn output_format_to_displayer(cli: &CliArgs) -> Result<Box<dyn Displayer>> {
110    let output_format = output_format(cli.json, cli.csv);
111    let output_file = cli.output_file.clone();
112
113    let displayer = match output_format {
114        OutputFormat::Terminal => TerminalOutput.into(),
115        OutputFormat::Json => JsonOutput::new(output_file)?.into(),
116        OutputFormat::Csv => CsvOutput::try_new(output_file)?.into(),
117    };
118
119    Ok(displayer)
120}
121
122pub fn init_logging(verbose: u8) {
123    logging::init_logging(verbose);
124}
125
126pub fn parse_sockets_spec(sockets_spec: Option<&str>) -> Option<HashSet<u32>> {
127    sockets_spec.map(|s| {
128        s.split(',')
129            .filter_map(|x| x.trim().parse::<u32>().ok())
130            .collect()
131    })
132}