--- a/Cargo.lock +++ b/Cargo.lock @@ -158,6 +158,7 @@ dependencies = [ "clap", "jrsonnet-evaluator", + "jrsonnet-gc", "jrsonnet-parser", ] --- a/cmds/jrsonnet/src/main.rs +++ b/cmds/jrsonnet/src/main.rs @@ -1,5 +1,5 @@ use clap::{AppSettings, Clap, IntoApp}; -use jrsonnet_cli::{ConfigureState, GeneralOpts, InputOpts, ManifestOpts, OutputOpts}; +use jrsonnet_cli::{ConfigureState, GcOpts, GeneralOpts, InputOpts, ManifestOpts, OutputOpts}; use jrsonnet_evaluator::{error::LocError, EvaluationState, ManifestFormat}; use std::{ fs::{create_dir_all, File}, @@ -61,6 +61,8 @@ output: OutputOpts, #[clap(flatten)] debug: DebugOpts, + #[clap(flatten)] + gc: GcOpts, } fn main() { @@ -114,6 +116,7 @@ } fn main_catch(opts: Opts) -> bool { + let _printer = opts.gc.stats_printer(); let state = EvaluationState::default(); if let Err(e) = main_real(&state, opts) { if let Error::Evaluation(e) = e { @@ -127,6 +130,7 @@ } fn main_real(state: &EvaluationState, opts: Opts) -> Result<(), Error> { + opts.gc.configure_global(); opts.general.configure(&state)?; opts.manifest.configure(&state)?; --- a/crates/jrsonnet-cli/Cargo.toml +++ b/crates/jrsonnet-cli/Cargo.toml @@ -10,6 +10,7 @@ [dependencies] jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.3.6", features = ["explaining-traces"] } jrsonnet-parser = { path = "../../crates/jrsonnet-parser", version = "0.3.6" } +jrsonnet-gc = { version = "0.4.2", features = ["derive", "unstable-config", "unstable-stats"] } [dependencies.clap] git = "https://github.com/clap-rs/clap" --- a/crates/jrsonnet-cli/src/lib.rs +++ b/crates/jrsonnet-cli/src/lib.rs @@ -95,3 +95,55 @@ Ok(()) } } + +#[derive(Clap)] +#[clap(help_heading = "GARBAGE COLLECTION")] +pub struct GcOpts { + /// Min bytes allocated to start garbage collection + #[clap(long, default_value = "20000000")] + gc_initial_threshold: usize, + /// How much heap should grow after unsuccessful garbage collection + #[clap(long)] + gc_used_space_ratio: Option, + /// Do not skip gc on exit + #[clap(long)] + gc_collect_on_exit: bool, + /// Print gc stats before exit + #[clap(long)] + gc_print_stats: bool, + /// Force garbage collection before printing stats + /// Useful for checking for memory leaks + /// Does nothing useless --gc-print-stats is specified + #[clap(long)] + gc_collect_before_printing_stats: bool, +} +impl GcOpts { + pub fn stats_printer(&self) -> Option { + self.gc_print_stats + .then(|| GcStatsPrinter(self.gc_collect_before_printing_stats)) + } + pub fn configure_global(&self) { + jrsonnet_gc::configure(|config| { + config.leak_on_drop = !self.gc_collect_on_exit; + config.threshold = self.gc_initial_threshold; + if let Some(used_space_ratio) = self.gc_used_space_ratio { + config.used_space_ratio = used_space_ratio; + } + }); + } +} +pub struct GcStatsPrinter(bool); +impl Drop for GcStatsPrinter { + fn drop(&mut self) { + if self.0 { + jrsonnet_gc::force_collect() + } + eprintln!("=== GC STATS ==="); + jrsonnet_gc::configure(|c| { + eprintln!("Final threshold: {:?}", c.threshold); + }); + let stats = jrsonnet_gc::stats(); + eprintln!("Collections performed: {}", stats.collections_performed); + eprintln!("Bytes still allocated: {}", stats.bytes_allocated); + } +}