use anyhow::{bail, Context, Result};
use backbeat_cli::{convert, inspect, merge, model, trace};
use clap::{Parser, Subcommand, ValueEnum};
use std::path::{Path, PathBuf};
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
#[derive(Parser)]
#[command(
name = "backbeat",
version,
about = "Query self-describing backbeat trace dumps"
)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
enum Format {
Parquet,
Trace,
}
#[derive(Subcommand)]
enum Command {
Convert {
#[arg(required = true)]
dumps: Vec<PathBuf>,
#[arg(long, short = 'o')]
output: Option<PathBuf>,
#[arg(long, value_enum)]
format: Option<Format>,
#[arg(long)]
host: Option<String>,
#[arg(long, default_value_t = 3)]
compression_level: i32,
},
Merge {
#[arg(required = true, num_args = 2..)]
dumps: Vec<PathBuf>,
#[arg(long, short = 'o')]
output: PathBuf,
#[arg(long)]
no_dedup: bool,
},
Inspect {
dump: PathBuf,
},
Skill,
}
const SKILL: &str = include_str!("skill.md");
fn resolve_format(format: Option<Format>, output: Option<&Path>) -> Result<Format> {
if let Some(f) = format {
return Ok(f);
}
match output.and_then(|p| p.extension()).and_then(|e| e.to_str()) {
Some("parquet") => Ok(Format::Parquet),
Some("json") => Ok(Format::Trace),
_ => bail!("cannot infer output format; pass --format parquet|trace (or use a .parquet / .json output path)"),
}
}
fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Command::Convert {
dumps,
output,
format,
host,
compression_level,
} => {
let format = resolve_format(format, output.as_deref())?;
let output = output.unwrap_or_else(|| {
let ext = match format {
Format::Parquet => "parquet",
Format::Trace => "json",
};
dumps[0].with_extension(ext)
});
let loaded = model::load_many(&dumps)?;
let count = match format {
Format::Parquet => convert::to_parquet(
&loaded,
&output,
host.as_deref().unwrap_or(""),
compression_level,
)?,
Format::Trace => trace::to_trace(&loaded, &output)?,
};
let what = match format {
Format::Parquet => "rows",
Format::Trace => "events",
};
println!(
"wrote {count} {what} from {} dump(s) to {}",
dumps.len(),
output.display()
);
Ok(())
}
Command::Merge {
dumps,
output,
no_dedup,
} => {
let schemas = merge::merge(&dumps, &output, !no_dedup)?;
let how = if no_dedup { "spliced" } else { "merged" };
println!(
"{how} {} dump(s) into {} ({schemas} event schema(s))",
dumps.len(),
output.display()
);
Ok(())
}
Command::Inspect { dump } => {
let bytes =
std::fs::read(&dump).with_context(|| format!("reading dump {}", dump.display()))?;
inspect::inspect(bytes, &mut std::io::stdout())
.with_context(|| format!("inspecting {}", dump.display()))
}
Command::Skill => {
print!("{SKILL}");
Ok(())
}
}
}