diff --git a/oxen-python/Cargo.lock b/oxen-python/Cargo.lock index d05aceea5..7f3019a7f 100644 --- a/oxen-python/Cargo.lock +++ b/oxen-python/Cargo.lock @@ -3092,6 +3092,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "httparse", + "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -3727,6 +3728,8 @@ dependencies = [ "lofty", "log", "lru 0.14.0", + "metrics", + "metrics-exporter-prometheus", "minus", "mp4", "num_cpus", @@ -3762,6 +3765,10 @@ dependencies = [ "tokio-stream", "tokio-util", "toml", + "tracing", + "tracing-appender", + "tracing-log", + "tracing-subscriber", "url", "urlencoding", "utoipa", @@ -3941,6 +3948,15 @@ dependencies = [ "libc", ] +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "maybe-rayon" version = "0.1.1" @@ -3985,6 +4001,53 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metrics" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +dependencies = [ + "ahash 0.8.12", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" +dependencies = [ + "base64", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-util", + "indexmap 2.12.1", + "ipnet", + "metrics", + "metrics-util", + "quanta", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-util" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.15.5", + "metrics", + "quanta", + "rand 0.9.2", + "rand_xoshiro", + "sketches-ddsketch", +] + [[package]] name = "mime" version = "0.3.17" @@ -4154,6 +4217,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num" version = "0.4.3" @@ -5422,6 +5494,21 @@ dependencies = [ "tabwriter", ] +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "2.0.1" @@ -5594,6 +5681,15 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core 0.9.3", +] + [[package]] name = "rav1e" version = "0.8.1" @@ -6342,6 +6438,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -6432,6 +6537,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "sketches-ddsketch" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" + [[package]] name = "skiplist" version = "0.5.1" @@ -6792,6 +6903,15 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "tiff" version = "0.10.3" @@ -7082,6 +7202,18 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +dependencies = [ + "crossbeam-channel", + "thiserror 2.0.17", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.30" @@ -7100,6 +7232,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", ] [[package]] @@ -7250,6 +7425,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "value-bag" version = "1.12.0" diff --git a/oxen-rust/Cargo.lock b/oxen-rust/Cargo.lock index 108c47179..159cd3007 100644 --- a/oxen-rust/Cargo.lock +++ b/oxen-rust/Cargo.lock @@ -4450,6 +4450,8 @@ dependencies = [ "lofty", "log", "lru 0.14.0", + "metrics", + "metrics-exporter-prometheus", "minus", "mockito", "mp4", @@ -4488,6 +4490,10 @@ dependencies = [ "tokio-stream", "tokio-util", "toml", + "tracing", + "tracing-appender", + "tracing-log", + "tracing-subscriber", "url", "urlencoding", "utoipa", @@ -4696,6 +4702,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "matrixmultiply" version = "0.3.10" @@ -4747,6 +4762,53 @@ dependencies = [ "libc", ] +[[package]] +name = "metrics" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +dependencies = [ + "ahash 0.8.12", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" +dependencies = [ + "base64", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-util", + "indexmap 2.13.0", + "ipnet", + "metrics", + "metrics-util", + "quanta", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-util" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.15.5", + "metrics", + "quanta", + "rand 0.9.2", + "rand_xoshiro", + "sketches-ddsketch", +] + [[package]] name = "mime" version = "0.3.17" @@ -4957,6 +5019,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num" version = "0.4.3" @@ -5234,6 +5305,10 @@ dependencies = [ "tempfile", "time", "tokio", + "tracing", + "tracing-appender", + "tracing-log", + "tracing-subscriber", "url", "uuid", ] @@ -5262,6 +5337,8 @@ dependencies = [ "liboxen", "log", "lru 0.14.0", + "metrics", + "metrics-exporter-prometheus", "mime", "os_path", "percent-encoding", @@ -5274,6 +5351,10 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tokio-util", + "tracing", + "tracing-appender", + "tracing-log", + "tracing-subscriber", "url", "utoipa", "utoipa-swagger-ui", @@ -6269,6 +6350,21 @@ dependencies = [ "tabwriter", ] +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "2.0.1" @@ -6441,6 +6537,15 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core 0.9.5", +] + [[package]] name = "rav1e" version = "0.8.1" @@ -7307,6 +7412,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shell-words" version = "1.1.1" @@ -7422,6 +7536,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +[[package]] +name = "sketches-ddsketch" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" + [[package]] name = "skiplist" version = "0.5.1" @@ -7776,6 +7896,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "thumbnails" version = "0.2.1" @@ -8109,6 +8238,18 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +dependencies = [ + "crossbeam-channel", + "thiserror 2.0.18", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.31" @@ -8127,6 +8268,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", ] [[package]] @@ -8315,6 +8499,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "value-bag" version = "1.12.0" diff --git a/oxen-rust/Cargo.toml b/oxen-rust/Cargo.toml index 4699de6ff..950452976 100644 --- a/oxen-rust/Cargo.toml +++ b/oxen-rust/Cargo.toml @@ -84,6 +84,8 @@ libduckdb-sys = { version = "=1.1.1" } lofty = "0.22.2" log = "0.4.20" lru = "0.14.0" +metrics = "0.24" +metrics-exporter-prometheus = "0.16" mime = "0.3.17" minus = { version = "5.4.0", features = ["static_output", "search"] } mockito = "1.1.0" @@ -127,6 +129,10 @@ tokio-console = "0.1.13" tokio-stream = "0.1.17" tokio-util = { version = "0.7.8", features = ["compat"] } toml = "0.8.19" +tracing = "0.1" +tracing-appender = "0.2" +tracing-log = "0.2" +tracing-subscriber = { version = "0.3", features = ["json", "env-filter", "registry"] } url = "2.4.1" urlencoding = "2.1.3" utoipa = { version = "5", features = ["time"] } diff --git a/oxen-rust/config_for_tracing_metrics.md b/oxen-rust/config_for_tracing_metrics.md new file mode 100644 index 000000000..e69de29bb diff --git a/oxen-rust/crates/cli/Cargo.toml b/oxen-rust/crates/cli/Cargo.toml index a1482da39..68b420260 100644 --- a/oxen-rust/crates/cli/Cargo.toml +++ b/oxen-rust/crates/cli/Cargo.toml @@ -34,5 +34,9 @@ serde_json = { workspace = true } tempfile = { workspace = true } time = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } +tracing-appender = { workspace = true } +tracing-log = { workspace = true } +tracing-subscriber = { workspace = true } url = { workspace = true } uuid = { workspace = true } diff --git a/oxen-rust/crates/cli/src/cmd/fsck.rs b/oxen-rust/crates/cli/src/cmd/fsck.rs index 11fa649f5..0353b7bb3 100644 --- a/oxen-rust/crates/cli/src/cmd/fsck.rs +++ b/oxen-rust/crates/cli/src/cmd/fsck.rs @@ -54,7 +54,7 @@ impl RunCmd for FsckCmd { println!(" Cleaned: {}", result.cleaned); } println!(" Errors: {}", result.errors); - println!(" Elapsed: {:.2}s", result.elapsed.as_secs_f64()); + println!(" Elapsed: {:.2}s", result.elapsed.as_millis() as f64); if dry_run && result.corrupted > 0 { println!("\nRun with --clean to remove corrupted version files."); diff --git a/oxen-rust/crates/cli/src/main.rs b/oxen-rust/crates/cli/src/main.rs index cf6adadb8..58ad3f119 100644 --- a/oxen-rust/crates/cli/src/main.rs +++ b/oxen-rust/crates/cli/src/main.rs @@ -43,7 +43,7 @@ fn main() -> ExitCode { } async fn async_main() -> ExitCode { - util::logging::init_logging(); + let _tracing_guard = util::telemetry::init_tracing("oxen"); let cmds: Vec> = vec![ Box::new(cmd::AddCmd), diff --git a/oxen-rust/crates/lib/Cargo.toml b/oxen-rust/crates/lib/Cargo.toml index 85aeafb32..6a0fab665 100644 --- a/oxen-rust/crates/lib/Cargo.toml +++ b/oxen-rust/crates/lib/Cargo.toml @@ -75,6 +75,8 @@ libduckdb-sys = { workspace = true, optional = true } lofty = { workspace = true } log = { workspace = true } lru = { workspace = true } +metrics = { workspace = true } +metrics-exporter-prometheus = { workspace = true } minus = { workspace = true } mp4 = { workspace = true } num_cpus = { workspace = true } @@ -111,6 +113,10 @@ tokio = { workspace = true } tokio-stream = { workspace = true } tokio-util = { workspace = true } toml = { workspace = true } +tracing = { workspace = true } +tracing-appender = { workspace = true } +tracing-log = { workspace = true } +tracing-subscriber = { workspace = true } url = { workspace = true } urlencoding = { workspace = true } utoipa = { workspace = true } diff --git a/oxen-rust/crates/lib/src/api/client/branches.rs b/oxen-rust/crates/lib/src/api/client/branches.rs index 81a979be4..80fd520dc 100644 --- a/oxen-rust/crates/lib/src/api/client/branches.rs +++ b/oxen-rust/crates/lib/src/api/client/branches.rs @@ -11,10 +11,12 @@ use crate::view::{ use serde_json::json; use std::path::Path; +#[tracing::instrument(skip(repository, branch_name))] pub async fn get_by_name( repository: &RemoteRepository, branch_name: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_client_branches_get_by_name_total").increment(1); let branch_name = branch_name.as_ref(); let uri = format!("/branches/{branch_name}"); let url = api::endpoint::url_from_repo(repository, &uri)?; @@ -32,11 +34,13 @@ pub async fn get_by_name( } /// Create a new branch from an existing branch +#[tracing::instrument(skip(repository, new_name, from_name))] pub async fn create_from_branch( repository: &RemoteRepository, new_name: impl AsRef, from_name: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_branches_create_from_branch_total").increment(1); let new_name = new_name.as_ref(); let from_name = from_name.as_ref(); @@ -57,11 +61,13 @@ pub async fn create_from_branch( /// Create a new remote branch from a commit /// The commit must already exist on the remote +#[tracing::instrument(skip(repository, new_name, commit))] pub async fn create_from_commit( repository: &RemoteRepository, new_name: impl AsRef, commit: &Commit, ) -> Result { + metrics::counter!("oxen_client_branches_create_from_commit_total").increment(1); let new_name = new_name.as_ref(); let url = api::endpoint::url_from_repo(repository, "/branches")?; @@ -79,11 +85,13 @@ pub async fn create_from_commit( Ok(response.branch) } +#[tracing::instrument(skip(repository, new_name, commit_id))] pub async fn create_from_commit_id( repository: &RemoteRepository, new_name: impl AsRef, commit_id: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_branches_create_from_commit_id_total").increment(1); let new_name = new_name.as_ref(); let commit_id = commit_id.as_ref(); @@ -103,7 +111,9 @@ pub async fn create_from_commit_id( } /// List all branches on the remote +#[tracing::instrument(skip(repository))] pub async fn list(repository: &RemoteRepository) -> Result, OxenError> { + metrics::counter!("oxen_client_branches_list_total").increment(1); let url = api::endpoint::url_from_repo(repository, "/branches")?; let client = client::new_for_url(&url)?; @@ -114,11 +124,13 @@ pub async fn list(repository: &RemoteRepository) -> Result, OxenErro } /// Update a remote branch to point to a new commit +#[tracing::instrument(skip(repository, branch_name, commit))] pub async fn update( repository: &RemoteRepository, branch_name: impl AsRef, commit: &Commit, ) -> Result { + metrics::counter!("oxen_client_branches_update_total").increment(1); let branch_name = branch_name.as_ref(); let uri = format!("/branches/{branch_name}"); let url = api::endpoint::url_from_repo(repository, &uri)?; @@ -133,12 +145,14 @@ pub async fn update( } // Creates a merge commit between two commits on the server if possible, returning the commit +#[tracing::instrument(skip(repository))] pub async fn maybe_create_merge( repository: &RemoteRepository, branch_name: &str, local_head_id: &str, remote_head_id: &str, // Remote head pre-push - merge target ) -> Result { + metrics::counter!("oxen_client_branches_maybe_create_merge_total").increment(1); let uri = format!("/branches/{branch_name}/merge"); let url = api::endpoint::url_from_repo(repository, &uri)?; log::debug!("api::client::branches::maybe_create_merge url: {url}"); @@ -157,11 +171,13 @@ pub async fn maybe_create_merge( } /// # Delete a remote branch +#[tracing::instrument(skip(repo, remote, branch_name))] pub async fn delete_remote( repo: &LocalRepository, remote: impl AsRef, branch_name: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_branches_delete_remote_total").increment(1); if let Some(remote) = repo.get_remote(&remote) { if let Some(remote_repo) = api::client::repositories::get_by_remote(&remote).await? { if let Some(branch) = @@ -180,10 +196,12 @@ pub async fn delete_remote( } } +#[tracing::instrument(skip(repository))] pub async fn delete( repository: &RemoteRepository, branch_name: &str, ) -> Result { + metrics::counter!("oxen_client_branches_delete_total").increment(1); let uri = format!("/branches/{branch_name}"); let url = api::endpoint::url_from_repo(repository, &uri)?; log::debug!("Deleting branch: {url}"); @@ -194,10 +212,12 @@ pub async fn delete( Ok(response) } +#[tracing::instrument(skip(repository))] pub async fn lock( repository: &RemoteRepository, branch_name: &str, ) -> Result { + metrics::counter!("oxen_client_branches_lock_total").increment(1); let uri = format!("/branches/{branch_name}/lock"); let url = api::endpoint::url_from_repo(repository, &uri)?; log::debug!("Locking branch: {url}"); @@ -208,10 +228,12 @@ pub async fn lock( Ok(response) } +#[tracing::instrument(skip(repository))] pub async fn unlock( repository: &RemoteRepository, branch_name: &str, ) -> Result { + metrics::counter!("oxen_client_branches_unlock_total").increment(1); let uri = format!("/branches/{branch_name}/unlock"); let url = api::endpoint::url_from_repo(repository, &uri)?; log::debug!("Unlocking branch: {url}"); @@ -222,12 +244,14 @@ pub async fn unlock( Ok(response) } +#[tracing::instrument(skip(repository, page_opts))] pub async fn list_entry_versions( repository: &RemoteRepository, branch_name: &str, path: &Path, page_opts: &PaginateOpts, ) -> Result { + metrics::counter!("oxen_client_branches_list_entry_versions_total").increment(1); let path_str = path.to_string_lossy(); let uri = format!( "/branches/{branch_name}/versions/{path_str}?page={}&page_size={}", diff --git a/oxen-rust/crates/lib/src/api/client/commits.rs b/oxen-rust/crates/lib/src/api/client/commits.rs index 53734d91d..58dcefdb4 100644 --- a/oxen-rust/crates/lib/src/api/client/commits.rs +++ b/oxen-rust/crates/lib/src/api/client/commits.rs @@ -38,10 +38,12 @@ pub struct ChunkParams { pub total_size: usize, } +#[tracing::instrument(skip(repository, commit_id))] pub async fn get_by_id( repository: &RemoteRepository, commit_id: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_client_commits_get_by_id_total").increment(1); let commit_id = commit_id.as_ref(); let uri = format!("/commits/{commit_id}"); let url = api::endpoint::url_from_repo(repository, &uri)?; @@ -65,12 +67,14 @@ pub async fn get_by_id( } /// List commits for a file +#[tracing::instrument(skip(remote_repo, revision, path, page_opts))] pub async fn list_commits_for_path( remote_repo: &RemoteRepository, revision: impl AsRef, path: impl AsRef, page_opts: &PaginateOpts, ) -> Result { + metrics::counter!("oxen_client_commits_list_commits_for_path_total").increment(1); let revision = revision.as_ref(); let path = path.as_ref(); let path_str = path.to_string_lossy(); @@ -91,7 +95,9 @@ pub async fn list_commits_for_path( } } +#[tracing::instrument(skip(remote_repo))] pub async fn list_all(remote_repo: &RemoteRepository) -> Result, OxenError> { + metrics::counter!("oxen_client_commits_list_all_total").increment(1); let mut all_commits: Vec = Vec::new(); let mut page_num = DEFAULT_PAGE_NUM; let page_size = 100; @@ -129,10 +135,12 @@ pub async fn list_all(remote_repo: &RemoteRepository) -> Result, Oxe Ok(all_commits) } +#[tracing::instrument(skip(remote_repo, commits))] pub async fn list_missing_hashes( remote_repo: &RemoteRepository, commits: Vec, ) -> Result, OxenError> { + metrics::counter!("oxen_client_commits_list_missing_hashes_total").increment(1); let uri = "/commits/missing".to_string(); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; let client = client::new_for_url(&url)?; @@ -164,11 +172,13 @@ pub async fn list_missing_hashes( } } +#[tracing::instrument(skip(remote_repo, base_commit), fields(head_commit_id))] pub async fn list_missing_files( remote_repo: &RemoteRepository, base_commit: Option, head_commit_id: &str, ) -> Result, OxenError> { + metrics::counter!("oxen_client_commits_list_missing_files_total").increment(1); let url = match base_commit { Some(base_commit) => { let base_commit_id = base_commit.id; @@ -193,10 +203,12 @@ pub async fn list_missing_files( } } +#[tracing::instrument(skip(remote_repo, commit_hashes))] pub async fn mark_commits_as_synced( remote_repo: &RemoteRepository, commit_hashes: HashSet, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_commits_mark_commits_as_synced_total").increment(1); let uri = "/commits/mark_commits_as_synced".to_string(); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; let client = client::new_for_url(&url)?; @@ -217,10 +229,12 @@ pub async fn mark_commits_as_synced( } } +#[tracing::instrument(skip(remote_repo), fields(revision))] pub async fn list_commit_history( remote_repo: &RemoteRepository, revision: &str, ) -> Result, OxenError> { + metrics::counter!("oxen_client_commits_list_commit_history_total").increment(1); let mut all_commits: Vec = Vec::new(); let mut page_num = DEFAULT_PAGE_NUM; let page_size = 100; @@ -258,11 +272,13 @@ pub async fn list_commit_history( Ok(all_commits) } +#[tracing::instrument(skip(remote_repo, page_opts), fields(revision))] pub async fn list_commit_history_paginated( remote_repo: &RemoteRepository, revision: &str, page_opts: &PaginateOpts, ) -> Result { + metrics::counter!("oxen_client_commits_list_commit_history_paginated_total").increment(1); let page_num = page_opts.page_num; let page_size = page_opts.page_size; let uri = format!("/commits/history/{revision}?page={page_num}&page_size={page_size}"); @@ -313,9 +329,11 @@ async fn list_all_commits_paginated( } } +#[tracing::instrument(skip(remote_repo))] pub async fn root_commit_maybe( remote_repo: &RemoteRepository, ) -> Result, OxenError> { + metrics::counter!("oxen_client_commits_root_commit_maybe_total").increment(1); let uri = "/commits/root".to_string(); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; log::debug!("remote::commits::root_commit {url}"); @@ -336,23 +354,27 @@ pub async fn root_commit_maybe( } } +#[tracing::instrument(skip(remote_repo, path), fields(commit_id))] pub async fn download_dir_hashes_from_commit( remote_repo: &RemoteRepository, commit_id: &str, path: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_commits_download_dir_hashes_from_commit_total").increment(1); let uri = format!("/commits/{commit_id}/download_dir_hashes_db"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; log::debug!("calling download_dir_hashes_from_commit for commit {commit_id}"); download_dir_hashes_from_url(url, path).await } +#[tracing::instrument(skip(remote_repo, path), fields(base_commit_id, head_commit_id))] pub async fn download_base_head_dir_hashes( remote_repo: &RemoteRepository, base_commit_id: &str, head_commit_id: &str, path: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_commits_download_base_head_dir_hashes_total").increment(1); let uri = format!("/commits/{base_commit_id}..{head_commit_id}/download_dir_hashes_db"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; log::debug!( @@ -361,10 +383,12 @@ pub async fn download_base_head_dir_hashes( download_dir_hashes_from_url(url, path).await } +#[tracing::instrument(skip(url, path))] pub async fn download_dir_hashes_from_url( url: impl AsRef, path: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_commits_download_dir_hashes_from_url_total").increment(1); let url = url.as_ref(); log::debug!("{} downloading from {}", current_function!(), url); let client = client::new_for_url(url)?; @@ -438,11 +462,13 @@ pub async fn download_dir_hashes_from_url( } } +#[tracing::instrument(skip(remote_repo, path), fields(commit_id))] pub async fn download_dir_hashes_db_to_path( remote_repo: &RemoteRepository, commit_id: &str, path: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_commits_download_dir_hashes_db_to_path_total").increment(1); let uri = format!("/commits/{commit_id}/commit_db"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; log::debug!("calling download_dir_hashes_db_to_path for commit {commit_id}"); @@ -512,10 +538,12 @@ pub async fn download_dir_hashes_db_to_path( } } +#[tracing::instrument(skip(remote_repo), fields(commit_id))] pub async fn get_remote_parent( remote_repo: &RemoteRepository, commit_id: &str, ) -> Result, OxenError> { + metrics::counter!("oxen_client_commits_get_remote_parent_total").increment(1); let uri = format!("/commits/{commit_id}/parents"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; @@ -534,12 +562,14 @@ pub async fn get_remote_parent( } } +#[tracing::instrument(skip(remote_repo, branch, commit_id))] pub async fn post_push_complete( remote_repo: &RemoteRepository, branch: &Branch, // we need to pass in the commit id because we might be pushing multiple commits from the same branch commit_id: impl AsRef, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_commits_post_push_complete_total").increment(1); use serde_json::json; let commit_id = commit_id.as_ref(); let uri = format!("/commits/{commit_id}/complete"); @@ -569,10 +599,12 @@ pub async fn post_push_complete( } // Commits must be in oldest-to-newest-order +#[tracing::instrument(skip(remote_repo, commits))] pub async fn bulk_post_push_complete( remote_repo: &RemoteRepository, commits: &Vec, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_commits_bulk_post_push_complete_total").increment(1); use serde_json::json; let uri = "/commits/complete".to_string(); @@ -597,10 +629,12 @@ pub async fn bulk_post_push_complete( } } +#[tracing::instrument(skip(remote_repo, branch))] pub async fn get_commits_with_unsynced_dbs( remote_repo: &RemoteRepository, branch: &Branch, ) -> Result, OxenError> { + metrics::counter!("oxen_client_commits_get_commits_with_unsynced_dbs_total").increment(1); let revision = branch.commit_id.clone(); let uri = format!("/commits/{revision}/db_status"); @@ -623,10 +657,12 @@ pub async fn get_commits_with_unsynced_dbs( } } +#[tracing::instrument(skip(remote_repo, branch))] pub async fn get_commits_with_unsynced_entries( remote_repo: &RemoteRepository, branch: &Branch, ) -> Result, OxenError> { + metrics::counter!("oxen_client_commits_get_commits_with_unsynced_entries_total").increment(1); let commit_id = branch.commit_id.clone(); let uri = format!("/commits/{commit_id}/entries_status"); @@ -649,12 +685,14 @@ pub async fn get_commits_with_unsynced_entries( } } +#[tracing::instrument(skip(local_repo, remote_repo, commits))] pub async fn post_commits_to_server( local_repo: &LocalRepository, remote_repo: &RemoteRepository, commits: &Vec, branch_name: String, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_commits_post_commits_to_server_total").increment(1); let mut commits_with_size: Vec = Vec::new(); for commit_with_entries in commits { let commit_history_dir = util::fs::oxen_hidden_dir(&local_repo.path) @@ -684,11 +722,13 @@ pub async fn post_commits_to_server( Ok(()) } +#[tracing::instrument(skip(local_repo, remote_repo, commit))] pub async fn post_commit_dir_hashes_to_server( local_repo: &LocalRepository, remote_repo: &RemoteRepository, commit: &Commit, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_commits_post_commit_dir_hashes_to_server_total").increment(1); let commit_dir = util::fs::oxen_hidden_dir(&local_repo.path) .join(HISTORY_DIR) .join(commit.id.clone()); @@ -731,11 +771,13 @@ pub async fn post_commit_dir_hashes_to_server( .await } +#[tracing::instrument(skip(local_repo, remote_repo, commits))] pub async fn post_commits_dir_hashes_to_server( local_repo: &LocalRepository, remote_repo: &RemoteRepository, commits: &Vec, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_commits_post_commits_dir_hashes_to_server_total").increment(1); let enc = GzEncoder::new(Vec::new(), Compression::default()); let mut tar = tar::Builder::new(enc); @@ -777,10 +819,12 @@ pub async fn post_commits_dir_hashes_to_server( .await } +#[tracing::instrument(skip(remote_repo, commits))] pub async fn bulk_create_commit_obj_on_server( remote_repo: &RemoteRepository, commits: &Vec, ) -> Result { + metrics::counter!("oxen_client_commits_bulk_create_commit_obj_on_server_total").increment(1); let url = api::endpoint::url_from_repo(remote_repo, "/commits/bulk")?; log::debug!("bulk_create_commit_obj_on_server {url}\n{commits:?}"); @@ -802,6 +846,7 @@ pub async fn bulk_create_commit_obj_on_server( } } +#[tracing::instrument(skip(client, remote_repo, buffer, bar), fields(is_compressed))] pub async fn post_data_to_server_with_client( client: &reqwest::Client, remote_repo: &RemoteRepository, @@ -810,6 +855,7 @@ pub async fn post_data_to_server_with_client( filename: &Option, bar: Arc, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_commits_post_data_to_server_with_client_total").increment(1); let chunk_size: usize = constants::AVG_CHUNK_SIZE as usize; if buffer.len() > chunk_size { @@ -829,12 +875,17 @@ pub async fn post_data_to_server_with_client( Ok(()) } +#[tracing::instrument(skip(client, remote_repo, buffer, bar))] pub async fn upload_single_tarball_to_server_with_client_with_retry( client: &reqwest::Client, remote_repo: &RemoteRepository, buffer: &[u8], bar: Arc, ) -> Result<(), OxenError> { + metrics::counter!( + "oxen_client_commits_upload_single_tarball_to_server_with_client_with_retry_total" + ) + .increment(1); let config = crate::api::client::retry::RetryConfig::default(); crate::api::client::retry::with_retry(&config, |_attempt| { let bar = bar.clone(); @@ -922,6 +973,7 @@ async fn upload_data_to_server_in_chunks_with_client( Ok(()) } +#[tracing::instrument(skip(client, remote_repo, chunk, params), fields(hash, is_compressed))] pub async fn upload_data_chunk_to_server_with_retry( client: &reqwest::Client, remote_repo: &RemoteRepository, @@ -931,6 +983,8 @@ pub async fn upload_data_chunk_to_server_with_retry( is_compressed: bool, filename: &Option, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_commits_upload_data_chunk_to_server_with_retry_total") + .increment(1); let config = crate::api::client::retry::RetryConfig::default(); crate::api::client::retry::with_retry(&config, |_attempt| async move { upload_data_chunk_to_server( diff --git a/oxen-rust/crates/lib/src/api/client/compare.rs b/oxen-rust/crates/lib/src/api/client/compare.rs index 2520f9914..f52ded1db 100644 --- a/oxen-rust/crates/lib/src/api/client/compare.rs +++ b/oxen-rust/crates/lib/src/api/client/compare.rs @@ -14,6 +14,7 @@ use crate::view::{JsonDataFrameView, compare::CompareTabular}; use serde_json::json; // TODO this should probably be cpath +#[tracing::instrument(skip(remote_repo, keys, compare, display))] #[allow(clippy::too_many_arguments)] pub async fn create_compare( remote_repo: &RemoteRepository, @@ -26,6 +27,7 @@ pub async fn create_compare( compare: Vec, display: Vec, ) -> Result { + metrics::counter!("oxen_client_compare_create_compare_total").increment(1); let req_body = TabularCompareBody { compare_id: compare_id.to_string(), left: TabularCompareResourceBody { @@ -58,6 +60,7 @@ pub async fn create_compare( } } +#[tracing::instrument(skip(remote_repo, keys, compare, display))] #[allow(clippy::too_many_arguments)] pub async fn update_compare( remote_repo: &RemoteRepository, @@ -70,6 +73,7 @@ pub async fn update_compare( compare: Vec, display: Vec, ) -> Result { + metrics::counter!("oxen_client_compare_update_compare_total").increment(1); let req_body = TabularCompareBody { compare_id: compare_id.to_string(), left: TabularCompareResourceBody { @@ -106,10 +110,12 @@ pub async fn update_compare( } } +#[tracing::instrument(skip(remote_repo))] pub async fn get_derived_compare_df( remote_repo: &RemoteRepository, compare_id: &str, ) -> Result { + metrics::counter!("oxen_client_compare_get_derived_compare_df_total").increment(1); // TODO: Factor out this basehead - not actually using it but needs to sync w/ routes on server let uri = format!("/compare/data_frames/{compare_id}/diff/main..main"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; @@ -128,11 +134,13 @@ pub async fn get_derived_compare_df( } } +#[tracing::instrument(skip(remote_repo))] pub async fn commits( remote_repo: &RemoteRepository, base_commit_id: &MerkleHash, head_commit_id: &MerkleHash, ) -> Result, OxenError> { + metrics::counter!("oxen_client_compare_commits_total").increment(1); let base_commit_id = base_commit_id.to_string(); let head_commit_id = head_commit_id.to_string(); let uri = format!("/compare/commits/{base_commit_id}..{head_commit_id}"); @@ -150,11 +158,13 @@ pub async fn commits( } } +#[tracing::instrument(skip(remote_repo))] pub async fn dir_tree( remote_repo: &RemoteRepository, base_commit_id: &MerkleHash, head_commit_id: &MerkleHash, ) -> Result, OxenError> { + metrics::counter!("oxen_client_compare_dir_tree_total").increment(1); let base_commit_id = base_commit_id.to_string(); let head_commit_id = head_commit_id.to_string(); let uri = format!("/compare/dir_tree/{base_commit_id}..{head_commit_id}"); @@ -172,11 +182,13 @@ pub async fn dir_tree( } } +#[tracing::instrument(skip(remote_repo))] pub async fn entries( remote_repo: &RemoteRepository, base_commit_id: &MerkleHash, head_commit_id: &MerkleHash, ) -> Result { + metrics::counter!("oxen_client_compare_entries_total").increment(1); let base_commit_id = base_commit_id.to_string(); let head_commit_id = head_commit_id.to_string(); let uri = format!("/compare/entries/{base_commit_id}..{head_commit_id}"); diff --git a/oxen-rust/crates/lib/src/api/client/data_frames.rs b/oxen-rust/crates/lib/src/api/client/data_frames.rs index ccdc10b64..b295b33fc 100644 --- a/oxen-rust/crates/lib/src/api/client/data_frames.rs +++ b/oxen-rust/crates/lib/src/api/client/data_frames.rs @@ -10,12 +10,14 @@ use crate::view::commit::CommitResponse; use crate::view::data_frames::FromDirectoryRequest; use crate::view::{JsonDataFrameViewResponse, StatusMessage}; +#[tracing::instrument(skip(remote_repo, path, opts))] pub async fn get( remote_repo: &RemoteRepository, commit_or_branch: &str, path: impl AsRef, opts: DFOpts, ) -> Result { + metrics::counter!("oxen_client_data_frames_get_total").increment(1); let path_str = util::fs::to_unix_str(path); let query_str = opts.to_http_query_params(); let uri = format!("/data_frames/{commit_or_branch}/{path_str}?{query_str}"); @@ -38,11 +40,13 @@ pub async fn get( } } +#[tracing::instrument(skip(remote_repo, path))] pub async fn index( remote_repo: &RemoteRepository, commit_or_branch: &str, path: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_data_frames_index_total").increment(1); let path_str = path.as_ref().to_str().unwrap(); let uri = format!("/data_frames/index/{commit_or_branch}/{path_str}"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; @@ -64,12 +68,14 @@ pub async fn index( } } +#[tracing::instrument(skip(remote_repo, path, request))] pub async fn from_directory( remote_repo: &RemoteRepository, commit_or_branch: &str, path: impl AsRef, request: FromDirectoryRequest, ) -> Result { + metrics::counter!("oxen_client_data_frames_from_directory_total").increment(1); let path_str = util::fs::to_unix_str(path); let uri = format!("/data_frames/from_directory/{commit_or_branch}/{path_str}"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; diff --git a/oxen-rust/crates/lib/src/api/client/diff.rs b/oxen-rust/crates/lib/src/api/client/diff.rs index ab4eb2cc1..bfe680097 100644 --- a/oxen-rust/crates/lib/src/api/client/diff.rs +++ b/oxen-rust/crates/lib/src/api/client/diff.rs @@ -7,6 +7,7 @@ use crate::model::{DiffEntry, RemoteRepository}; use crate::view::CompareEntriesResponse; use crate::view::compare::{CompareEntries, CompareEntryResponse}; +#[tracing::instrument(skip(remote_repo, base, head), fields(page, page_size))] pub async fn list_diff_entries( remote_repo: &RemoteRepository, base: impl AsRef, @@ -14,6 +15,7 @@ pub async fn list_diff_entries( page: usize, page_size: usize, ) -> Result { + metrics::counter!("oxen_client_diff_list_diff_entries_total").increment(1); let base = base.as_ref(); let head = head.as_ref(); let uri = format!("/compare/entries/{base}..{head}?page={page}&page_size={page_size}"); @@ -31,12 +33,14 @@ pub async fn list_diff_entries( } } +#[tracing::instrument(skip(remote_repo, base, head, path))] pub async fn diff_entries( remote_repo: &RemoteRepository, base: impl AsRef, head: impl AsRef, path: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_diff_diff_entries_total").increment(1); let base = base.as_ref(); let head = head.as_ref(); let path = path.as_ref(); diff --git a/oxen-rust/crates/lib/src/api/client/dir.rs b/oxen-rust/crates/lib/src/api/client/dir.rs index da5a1e7f3..24fe0fa81 100644 --- a/oxen-rust/crates/lib/src/api/client/dir.rs +++ b/oxen-rust/crates/lib/src/api/client/dir.rs @@ -9,7 +9,9 @@ use crate::view::entries::EMetadataEntry; use crate::view::{PaginatedDirEntries, PaginatedDirEntriesResponse}; use crate::{api, constants}; +#[tracing::instrument(skip(remote_repo))] pub async fn list_root(remote_repo: &RemoteRepository) -> Result { + metrics::counter!("oxen_client_dir_list_root_total").increment(1); list( remote_repo, constants::DEFAULT_BRANCH_NAME, @@ -20,6 +22,7 @@ pub async fn list_root(remote_repo: &RemoteRepository) -> Result, @@ -27,6 +30,7 @@ pub async fn list( page: usize, page_size: usize, ) -> Result { + metrics::counter!("oxen_client_dir_list_total").increment(1); let revision = revision.as_ref(); let path = path.as_ref().to_string_lossy(); let uri = format!("/dir/{revision}/{path}?page={page}&page_size={page_size}"); @@ -44,11 +48,13 @@ pub async fn list( } } +#[tracing::instrument(skip(remote_repo, revision, path))] pub async fn file_counts( remote_repo: &RemoteRepository, revision: impl AsRef, path: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_dir_file_counts_total").increment(1); let path_str = path.as_ref().to_string_lossy(); let response = list(remote_repo, revision, &path, 1, 1).await?; match response.dir { @@ -69,11 +75,13 @@ pub async fn file_counts( } } +#[tracing::instrument(skip(remote_repo, revision, path))] pub async fn get_dir( remote_repo: &RemoteRepository, revision: impl AsRef, path: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_dir_get_dir_total").increment(1); let path_str = path.as_ref().to_string_lossy(); let revision = revision.as_ref(); let uri = format!("/dir/{revision}/{path_str}"); diff --git a/oxen-rust/crates/lib/src/api/client/entries.rs b/oxen-rust/crates/lib/src/api/client/entries.rs index 44f0b0185..b5c31b720 100644 --- a/oxen-rust/crates/lib/src/api/client/entries.rs +++ b/oxen-rust/crates/lib/src/api/client/entries.rs @@ -28,11 +28,13 @@ use tokio::fs::File; use tokio::io::AsyncWriteExt; /// Returns the metadata given a file path +#[tracing::instrument(skip(remote_repo, remote_path, revision))] pub async fn get_entry( remote_repo: &RemoteRepository, remote_path: impl AsRef, revision: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_client_entries_get_entry_total").increment(1); let remote_path = remote_path.as_ref(); let Some(response) = @@ -43,12 +45,14 @@ pub async fn get_entry( Ok(Some(response.entry)) } +#[tracing::instrument(skip(remote_repo, path, revision))] pub async fn list_entries_with_type( remote_repo: &RemoteRepository, path: impl AsRef, revision: impl AsRef, data_type: &EntryDataType, ) -> Result, OxenError> { + metrics::counter!("oxen_client_entries_list_entries_with_type_total").increment(1); let path = path.as_ref().to_string_lossy(); let revision = revision.as_ref(); let uri = if path.is_empty() || path == "/" { @@ -65,10 +69,12 @@ pub async fn list_entries_with_type( Ok(paginated_response.entries.entries) } +#[tracing::instrument(skip(remote_repo, opts))] pub async fn upload_entries( remote_repo: &RemoteRepository, opts: &UploadOpts, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_entries_upload_entries_total").increment(1); if opts.paths.is_empty() { return Err(OxenError::basic_str("No files to upload")); } @@ -127,12 +133,14 @@ pub async fn upload_entries( /// Pings the remote server first to see if the entry exists /// and get the size before downloading +#[tracing::instrument(skip(remote_repo, remote_path, local_path, revision))] pub async fn download_entry( remote_repo: &RemoteRepository, remote_path: impl AsRef, local_path: impl AsRef, revision: impl AsRef, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_entries_download_entry_total").increment(1); let remote_path = remote_path.as_ref(); let download_path = util::fs::remove_leading_slash(remote_path); @@ -189,12 +197,14 @@ pub async fn download_entry( } /// Get entry status in batch and download them to a specific local repo +#[tracing::instrument(skip(local_repo, remote_repo, paths_to_download, revision))] pub async fn download_entries_to_repo( local_repo: &LocalRepository, remote_repo: &RemoteRepository, paths_to_download: &[(PathBuf, PathBuf)], revision: impl AsRef, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_entries_download_entries_to_repo_total").increment(1); let revision = revision.as_ref(); for (local_path, remote_path) in paths_to_download.iter() { // TODO: Refactor to get the entries for all paths in one API call @@ -252,6 +262,7 @@ pub async fn download_entries_to_repo( Ok(()) } +#[tracing::instrument(skip(remote_repo, entry, remote_path, local_path, revision))] pub async fn download_file( remote_repo: &RemoteRepository, entry: &MetadataEntry, @@ -259,6 +270,7 @@ pub async fn download_file( local_path: impl AsRef, revision: impl AsRef, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_entries_download_file_total").increment(1); if entry.size > AVG_CHUNK_SIZE { download_large_entry( remote_repo, @@ -273,12 +285,14 @@ pub async fn download_file( } } +#[tracing::instrument(skip(remote_repo, remote_path, dest, revision))] pub async fn download_small_entry( remote_repo: &RemoteRepository, remote_path: impl AsRef, dest: impl AsRef, revision: impl AsRef, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_entries_download_small_entry_total").increment(1); let path = remote_path.as_ref().to_string_lossy(); let revision = revision.as_ref(); let uri = format!("/file/{revision}/{path}"); @@ -323,12 +337,14 @@ pub async fn download_small_entry( } /// Download a file from the remote repository in parallel chunks +#[tracing::instrument(skip(repo, remote_repo, remote_path, entry))] pub async fn pull_large_entry( repo: &LocalRepository, remote_repo: &RemoteRepository, remote_path: impl AsRef, entry: &Entry, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_entries_pull_large_entry_total").increment(1); // Read chunks let chunk_size = AVG_CHUNK_SIZE; let total_size = entry.num_bytes(); @@ -536,6 +552,10 @@ async fn pull_entry_chunk( } /// Download a file from the remote repository in parallel chunks +#[tracing::instrument( + skip(remote_repo, remote_path, local_path, revision), + fields(num_bytes) +)] pub async fn download_large_entry( remote_repo: &RemoteRepository, remote_path: impl AsRef, @@ -543,6 +563,7 @@ pub async fn download_large_entry( revision: impl AsRef, num_bytes: u64, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_entries_download_large_entry_total").increment(1); // Read chunks let chunk_size = AVG_CHUNK_SIZE; let total_size = num_bytes; @@ -806,11 +827,13 @@ async fn download_entry_chunk( } } +#[tracing::instrument(skip(remote_repo, content_ids, dst))] pub async fn download_data_from_version_paths( remote_repo: &RemoteRepository, content_ids: &[(String, PathBuf)], // tuple of content id and entry path dst: &Path, ) -> Result { + metrics::counter!("oxen_client_entries_download_data_from_version_paths_total").increment(1); let total_retries = constants::NUM_HTTP_RETRIES; let mut num_retries = 0; @@ -836,11 +859,14 @@ pub async fn download_data_from_version_paths( Err(OxenError::basic_str(err)) } +#[tracing::instrument(skip(remote_repo, content_ids, dst))] pub async fn try_download_data_from_version_paths( remote_repo: &RemoteRepository, content_ids: &[(String, PathBuf)], // tuple of content id and entry path dst: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_entries_try_download_data_from_version_paths_total") + .increment(1); let dst = dst.as_ref(); let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); for (content_id, _path) in content_ids.iter() { diff --git a/oxen-rust/crates/lib/src/api/client/export.rs b/oxen-rust/crates/lib/src/api/client/export.rs index cb839c0e9..3aef3090a 100644 --- a/oxen-rust/crates/lib/src/api/client/export.rs +++ b/oxen-rust/crates/lib/src/api/client/export.rs @@ -8,12 +8,15 @@ use crate::api::client; use crate::error::OxenError; use crate::model::RemoteRepository; +#[tracing::instrument(skip(remote_repo, revision, directory, local_path))] pub async fn download_dir_as_zip( remote_repo: &RemoteRepository, revision: impl AsRef, directory: impl AsRef, local_path: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_export_download_dir_as_zip_total").increment(1); + let timer = std::time::Instant::now(); let revision = revision.as_ref().to_string(); let directory = directory.as_ref().to_string_lossy().to_string(); let local_path = local_path.as_ref(); @@ -54,8 +57,12 @@ pub async fn download_dir_as_zip( log::debug!("Successfully downloaded {size} bytes to: {local_path:?}"); + metrics::histogram!("oxen_client_export_download_dir_as_zip_duration_ms") + .record(timer.elapsed().as_millis() as f64); Ok(size) } else { + metrics::histogram!("oxen_client_export_download_dir_as_zip_duration_ms") + .record(timer.elapsed().as_millis() as f64); let err = format!("try_download_dir_as_zip failed to send request {url}"); Err(OxenError::basic_str(err)) } diff --git a/oxen-rust/crates/lib/src/api/client/file.rs b/oxen-rust/crates/lib/src/api/client/file.rs index fb271550f..bb5ee3395 100644 --- a/oxen-rust/crates/lib/src/api/client/file.rs +++ b/oxen-rust/crates/lib/src/api/client/file.rs @@ -11,6 +11,7 @@ use futures_util::StreamExt; use reqwest::multipart::{Form, Part}; use std::path::Path; +#[tracing::instrument(skip(remote_repo, branch, directory, file_path, file_name, commit_body))] pub async fn put_file( remote_repo: &RemoteRepository, branch: impl AsRef, @@ -19,6 +20,7 @@ pub async fn put_file( file_name: Option>, commit_body: Option, ) -> Result { + metrics::counter!("oxen_client_file_put_file_total").increment(1); let branch = branch.as_ref(); let directory = directory.as_ref(); put_multipart_file( @@ -117,15 +119,18 @@ fn apply_commit_body(form: Form, commit_body: Option<&NewCommitBody>) -> Form { } } +#[tracing::instrument(skip(remote_repo, branch, file_path))] pub async fn get_file( remote_repo: &RemoteRepository, branch: impl AsRef, file_path: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_file_get_file_total").increment(1); get_file_with_params(remote_repo, branch, file_path, None, None, None, None).await } /// Get a file with optional query parameters (for thumbnails, image resizing, etc.) +#[tracing::instrument(skip(remote_repo, branch, file_path))] pub async fn get_file_with_params( remote_repo: &RemoteRepository, branch: impl AsRef, @@ -135,6 +140,7 @@ pub async fn get_file_with_params( height: Option, timestamp: Option, ) -> Result { + metrics::counter!("oxen_client_file_get_file_with_params_total").increment(1); let branch = branch.as_ref(); let path_ref = file_path.as_ref(); let file_path = path_ref @@ -177,6 +183,7 @@ pub async fn get_file_with_params( } /// Get a video thumbnail +#[tracing::instrument(skip(remote_repo, branch, file_path))] pub async fn get_file_thumbnail( remote_repo: &RemoteRepository, branch: impl AsRef, @@ -185,6 +192,7 @@ pub async fn get_file_thumbnail( height: Option, timestamp: Option, ) -> Result { + metrics::counter!("oxen_client_file_get_file_thumbnail_total").increment(1); get_file_with_params( remote_repo, branch, @@ -198,6 +206,7 @@ pub async fn get_file_thumbnail( } /// Move/rename a file in place (mv in a temp workspace and commit) +#[tracing::instrument(skip(remote_repo, branch, source_path, new_path, commit_body))] pub async fn mv_file( remote_repo: &RemoteRepository, branch: impl AsRef, @@ -205,6 +214,7 @@ pub async fn mv_file( new_path: impl AsRef, commit_body: Option, ) -> Result { + metrics::counter!("oxen_client_file_mv_file_total").increment(1); let branch = branch.as_ref(); let source_path = source_path.as_ref(); let new_path = new_path.as_ref(); @@ -241,12 +251,14 @@ pub async fn mv_file( } /// Delete a file in place (rm from a temp workspace and commit) +#[tracing::instrument(skip(remote_repo, branch, file_path, commit_body))] pub async fn delete_file( remote_repo: &RemoteRepository, branch: impl AsRef, file_path: impl AsRef, commit_body: Option, ) -> Result { + metrics::counter!("oxen_client_file_delete_file_total").increment(1); let branch = branch.as_ref(); let file_path = file_path.as_ref(); diff --git a/oxen-rust/crates/lib/src/api/client/import.rs b/oxen-rust/crates/lib/src/api/client/import.rs index 6f59e329e..f36e9babf 100644 --- a/oxen-rust/crates/lib/src/api/client/import.rs +++ b/oxen-rust/crates/lib/src/api/client/import.rs @@ -7,6 +7,15 @@ use crate::model::RemoteRepository; use std::path::Path; /// Upload a ZIP file that gets extracted into the workspace directory +#[tracing::instrument(skip( + remote_repo, + branch_name, + directory, + zip_path, + name, + email, + commit_message +))] pub async fn upload_zip( remote_repo: &RemoteRepository, branch_name: impl AsRef, @@ -16,6 +25,8 @@ pub async fn upload_zip( email: impl AsRef, commit_message: Option>, ) -> Result { + metrics::counter!("oxen_client_import_upload_zip_total").increment(1); + let timer = std::time::Instant::now(); let branch_name = branch_name.as_ref(); let directory = directory.as_ref(); let zip_path = zip_path.as_ref(); @@ -65,6 +76,8 @@ pub async fn upload_zip( let body = client::parse_json_body(&url, response).await?; let response: crate::view::CommitResponse = serde_json::from_str(&body) .map_err(|e| OxenError::basic_str(format!("Failed to parse response: {e}")))?; + metrics::histogram!("oxen_client_import_upload_zip_duration_ms") + .record(timer.elapsed().as_millis() as f64); Ok(response.commit) } }) diff --git a/oxen-rust/crates/lib/src/api/client/merger.rs b/oxen-rust/crates/lib/src/api/client/merger.rs index 05d2ea109..58671205f 100644 --- a/oxen-rust/crates/lib/src/api/client/merger.rs +++ b/oxen-rust/crates/lib/src/api/client/merger.rs @@ -9,11 +9,13 @@ use crate::view::merge::{MergeResult, MergeSuccessResponse, Mergeable, Mergeable /// Can check the mergeability of head into base /// base or head are strings that can be branch names or commit ids +#[tracing::instrument(skip(remote_repo))] pub async fn mergeable( remote_repo: &RemoteRepository, base: &str, head: &str, ) -> Result { + metrics::counter!("oxen_client_merger_mergeable_total").increment(1); let uri = format!("/merge/{base}..{head}"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; log::debug!("api::client::merger::mergeability url: {url}"); @@ -26,11 +28,13 @@ pub async fn mergeable( } /// Merge the head branch into the base branch +#[tracing::instrument(skip(remote_repo))] pub async fn merge( remote_repo: &RemoteRepository, base: &str, head: &str, ) -> Result { + metrics::counter!("oxen_client_merger_merge_total").increment(1); let uri = format!("/merge/{base}..{head}"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; log::debug!("api::client::merger::merge url: {url}"); diff --git a/oxen-rust/crates/lib/src/api/client/metadata.rs b/oxen-rust/crates/lib/src/api/client/metadata.rs index 8255b9d88..ac1da7aec 100644 --- a/oxen-rust/crates/lib/src/api/client/metadata.rs +++ b/oxen-rust/crates/lib/src/api/client/metadata.rs @@ -12,11 +12,13 @@ use crate::view::entry_metadata::EMetadataEntryResponseView; use std::path::Path; /// Get the metadata about a resource from the remote. +#[tracing::instrument(skip(remote_repo, revision, path))] pub async fn get_file( remote_repo: &RemoteRepository, revision: impl AsRef, path: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_client_metadata_get_file_total").increment(1); let path = path.as_ref().to_string_lossy(); let revision = revision.as_ref(); let uri = format!("/meta/{revision}/{path}"); diff --git a/oxen-rust/crates/lib/src/api/client/repositories.rs b/oxen-rust/crates/lib/src/api/client/repositories.rs index 839592573..c2dfa54e8 100644 --- a/oxen-rust/crates/lib/src/api/client/repositories.rs +++ b/oxen-rust/crates/lib/src/api/client/repositories.rs @@ -36,7 +36,9 @@ impl fmt::Display for ActionEventState { } /// Gets remote "origin" that is set on the local repo +#[tracing::instrument(skip(repo))] pub async fn get_default_remote(repo: &LocalRepository) -> Result { + metrics::counter!("oxen_client_repositories_get_default_remote_total").increment(1); let remote = repo .get_remote(DEFAULT_REMOTE_NAME) .ok_or(OxenError::remote_not_set(DEFAULT_REMOTE_NAME))?; @@ -48,44 +50,54 @@ pub async fn get_default_remote(repo: &LocalRepository) -> Result Result, OxenError> { + metrics::counter!("oxen_client_repositories_get_by_remote_repo_total").increment(1); get_by_remote(&repo.remote).await } /// Attempts to find a repo by name on the remote "origin". For example ox/CatDog +#[tracing::instrument(skip(name))] pub async fn get_by_name_default( name: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_client_repositories_get_by_name_default_total").increment(1); get_by_name_host_and_remote(name, DEFAULT_HOST, DEFAULT_SCHEME, DEFAULT_REMOTE_NAME).await } +#[tracing::instrument(skip(name, host, scheme))] pub async fn get_by_name_and_host( name: impl AsRef, host: impl AsRef, scheme: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_client_repositories_get_by_name_and_host_total").increment(1); get_by_name_host_and_remote(name, host, scheme, DEFAULT_REMOTE_NAME).await } +#[tracing::instrument(skip(name, host, scheme))] pub async fn get_by_name_host_and_scheme( name: impl AsRef, host: impl AsRef, scheme: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_client_repositories_get_by_name_host_and_scheme_total").increment(1); let name = name.as_ref(); let url = api::endpoint::remote_url_from_name_and_scheme(host.as_ref(), name, scheme.as_ref()); log::debug!("get_by_name_host_and_scheme({name}) remote url: {url}"); get_by_url(&url).await } +#[tracing::instrument(skip(name, host, scheme, remote))] pub async fn get_by_name_host_and_remote( name: impl AsRef, host: impl AsRef, scheme: impl AsRef, remote: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_client_repositories_get_by_name_host_and_remote_total").increment(1); let name = name.as_ref(); let scheme = scheme.as_ref(); let url = api::endpoint::remote_url_from_name_and_scheme(host.as_ref(), name, scheme); @@ -97,12 +109,16 @@ pub async fn get_by_name_host_and_remote( get_by_remote(&remote).await } +#[tracing::instrument(skip(repo))] pub async fn exists(repo: &RemoteRepository) -> Result { + metrics::counter!("oxen_client_repositories_exists_total").increment(1); let repo = get_by_remote_repo(repo).await?; Ok(repo.is_some()) } +#[tracing::instrument] pub async fn get_by_url(url: &str) -> Result, OxenError> { + metrics::counter!("oxen_client_repositories_get_by_url_total").increment(1); let remote = Remote { name: String::from(DEFAULT_REMOTE_NAME), url: url.to_string(), @@ -110,7 +126,9 @@ pub async fn get_by_url(url: &str) -> Result, OxenError get_by_remote(&remote).await } +#[tracing::instrument(skip(remote))] pub async fn get_by_remote(remote: &Remote) -> Result, OxenError> { + metrics::counter!("oxen_client_repositories_get_by_remote_total").increment(1); let url = api::endpoint::url_from_remote(remote, "")?; log::debug!("get_by_remote url: {url}"); @@ -136,9 +154,11 @@ pub async fn get_by_remote(remote: &Remote) -> Result, } } +#[tracing::instrument(skip(remote))] pub async fn get_repo_data_by_remote( remote: &Remote, ) -> Result, OxenError> { + metrics::counter!("oxen_client_repositories_get_repo_data_by_remote_total").increment(1); log::debug!("api::client::repositories::get_repo_data_by_remote({remote:?})"); let url = api::endpoint::url_from_remote(remote, "")?; log::debug!("api::client::repositories::get_repo_data_by_remote url: {url}"); @@ -174,7 +194,9 @@ pub async fn get_repo_data_by_remote( } } +#[tracing::instrument(skip(repo))] pub async fn create_empty(repo: RepoNew) -> Result { + metrics::counter!("oxen_client_repositories_create_empty_total").increment(1); let namespace = repo.namespace.as_ref(); let repo_name = repo.name.as_ref(); let host = repo.host(); @@ -218,7 +240,9 @@ pub async fn create_empty(repo: RepoNew) -> Result } } +#[tracing::instrument(skip(repo_new))] pub async fn create(repo_new: RepoNew) -> Result { + metrics::counter!("oxen_client_repositories_create_total").increment(1); let host = repo_new.host(); let scheme = repo_new.scheme(); let url = api::endpoint::url_from_host_and_scheme(&host, "", scheme); @@ -263,12 +287,14 @@ pub async fn create(repo_new: RepoNew) -> Result { } } +#[tracing::instrument(skip(repo_new, files))] pub async fn create_repo_with_files( repo_new: RepoNew, user_email: &str, user_name: &str, files: Vec, ) -> Result { + metrics::counter!("oxen_client_repositories_create_repo_with_files_total").increment(1); let host = repo_new.host(); let scheme = repo_new.scheme(); let url = api::endpoint::url_from_host_and_scheme(&host, "", scheme); @@ -324,10 +350,12 @@ pub async fn create_repo_with_files( } } +#[tracing::instrument(skip(repository, repo_new))] pub async fn create_from_local( repository: &LocalRepository, mut repo_new: RepoNew, ) -> Result { + metrics::counter!("oxen_client_repositories_create_from_local_total").increment(1); let host = repo_new.host(); let url = api::endpoint::url_from_host(&host, ""); repo_new.root_commit = repositories::commits::root_commit_maybe(repository)?; @@ -365,7 +393,9 @@ pub async fn create_from_local( } } +#[tracing::instrument(skip(repository))] pub async fn delete(repository: &RemoteRepository) -> Result { + metrics::counter!("oxen_client_repositories_delete_total").increment(1); let url = repository.api_url()?; let client = client::new_for_url(&url)?; @@ -385,7 +415,9 @@ pub async fn delete(repository: &RemoteRepository) -> Result Result { + metrics::counter!("oxen_client_repositories_delete_from_url_total").increment(1); let client = client::new_for_url(&url)?; if let Ok(res) = client.delete(&url).send().await { let body = client::parse_json_body(&url, res).await?; @@ -403,10 +435,12 @@ pub async fn delete_from_url(url: String) -> Result { } } +#[tracing::instrument(skip(repository), fields(to_namespace))] pub async fn transfer_namespace( repository: &RemoteRepository, to_namespace: &str, ) -> Result { + metrics::counter!("oxen_client_repositories_transfer_namespace_total").increment(1); let url = api::endpoint::url_from_repo(repository, "/transfer")?; let params = serde_json::to_string(&NamespaceView { namespace: to_namespace.to_string(), @@ -451,61 +485,83 @@ pub async fn transfer_namespace( } } +#[tracing::instrument(skip(repository))] pub async fn pre_clone(repository: &RemoteRepository) -> Result<(), OxenError> { + metrics::counter!("oxen_client_repositories_pre_clone_total").increment(1); let action_name = CLONE; action_hook(repository, action_name, ActionEventState::Started, None).await } +#[tracing::instrument(skip(repository))] pub async fn post_clone(repository: &RemoteRepository) -> Result<(), OxenError> { + metrics::counter!("oxen_client_repositories_post_clone_total").increment(1); let action_name = CLONE; action_hook(repository, action_name, ActionEventState::Completed, None).await } +#[tracing::instrument(skip(repository))] pub async fn pre_upload(repository: &RemoteRepository) -> Result<(), OxenError> { + metrics::counter!("oxen_client_repositories_pre_upload_total").increment(1); let action_name = UPLOAD; action_hook(repository, action_name, ActionEventState::Started, None).await } +#[tracing::instrument(skip(repository))] pub async fn post_upload(repository: &RemoteRepository) -> Result<(), OxenError> { + metrics::counter!("oxen_client_repositories_post_upload_total").increment(1); let action_name = UPLOAD; action_hook(repository, action_name, ActionEventState::Completed, None).await } +#[tracing::instrument(skip(repository))] pub async fn pre_download(repository: &RemoteRepository) -> Result<(), OxenError> { + metrics::counter!("oxen_client_repositories_pre_download_total").increment(1); let action_name = DOWNLOAD; action_hook(repository, action_name, ActionEventState::Started, None).await } +#[tracing::instrument(skip(repository))] pub async fn post_download(repository: &RemoteRepository) -> Result<(), OxenError> { + metrics::counter!("oxen_client_repositories_post_download_total").increment(1); let action_name = DOWNLOAD; action_hook(repository, action_name, ActionEventState::Completed, None).await } +#[tracing::instrument(skip(repository))] pub async fn pre_pull(repository: &RemoteRepository) -> Result<(), OxenError> { + metrics::counter!("oxen_client_repositories_pre_pull_total").increment(1); let action_name = PULL; action_hook(repository, action_name, ActionEventState::Started, None).await } +#[tracing::instrument(skip(repository))] pub async fn post_pull(repository: &RemoteRepository) -> Result<(), OxenError> { + metrics::counter!("oxen_client_repositories_post_pull_total").increment(1); let action_name = PULL; action_hook(repository, action_name, ActionEventState::Completed, None).await } +#[tracing::instrument(skip(repository))] pub async fn pre_fetch(repository: &RemoteRepository) -> Result<(), OxenError> { + metrics::counter!("oxen_client_repositories_pre_fetch_total").increment(1); let action_name = FETCH; action_hook(repository, action_name, ActionEventState::Started, None).await } +#[tracing::instrument(skip(repository))] pub async fn post_fetch(repository: &RemoteRepository) -> Result<(), OxenError> { + metrics::counter!("oxen_client_repositories_post_fetch_total").increment(1); let action_name = FETCH; action_hook(repository, action_name, ActionEventState::Completed, None).await } +#[tracing::instrument(skip(repository, branch))] pub async fn pre_push( repository: &RemoteRepository, branch: &Branch, commit_id: &str, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_repositories_pre_push_total").increment(1); let action_name = PUSH; let body = json!({ "branch": { @@ -522,11 +578,13 @@ pub async fn pre_push( .await } +#[tracing::instrument(skip(repository, branch))] pub async fn post_push( repository: &RemoteRepository, branch: &Branch, commit_id: &str, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_repositories_post_push_total").increment(1); let action_name = PUSH; let body = json!({ "branch": { diff --git a/oxen-rust/crates/lib/src/api/client/schemas.rs b/oxen-rust/crates/lib/src/api/client/schemas.rs index 3256a7d63..732ae768f 100644 --- a/oxen-rust/crates/lib/src/api/client/schemas.rs +++ b/oxen-rust/crates/lib/src/api/client/schemas.rs @@ -12,10 +12,12 @@ use crate::model::RemoteRepository; use crate::view::ListSchemaResponse; use crate::view::schema::SchemaWithPath; +#[tracing::instrument(skip(remote_repo, revision))] pub async fn list( remote_repo: &RemoteRepository, revision: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_client_schemas_list_total").increment(1); let revision = revision.as_ref(); let uri = format!("/schemas/{revision}"); @@ -45,11 +47,13 @@ pub async fn list( } } +#[tracing::instrument(skip(remote_repo, revision, path))] pub async fn get( remote_repo: &RemoteRepository, revision: impl AsRef, path: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_client_schemas_get_total").increment(1); let revision = revision.as_ref(); let path = path.as_ref(); diff --git a/oxen-rust/crates/lib/src/api/client/stats.rs b/oxen-rust/crates/lib/src/api/client/stats.rs index a1ef19783..4e3ed1d7b 100644 --- a/oxen-rust/crates/lib/src/api/client/stats.rs +++ b/oxen-rust/crates/lib/src/api/client/stats.rs @@ -9,7 +9,9 @@ use crate::error::OxenError; use crate::model::RemoteRepository; use crate::view::repository::{RepositoryStatsResponse, RepositoryStatsView}; +#[tracing::instrument(skip(remote_repo))] pub async fn get(remote_repo: &RemoteRepository) -> Result { + metrics::counter!("oxen_client_stats_get_total").increment(1); let uri = "/stats".to_string(); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; diff --git a/oxen-rust/crates/lib/src/api/client/tree.rs b/oxen-rust/crates/lib/src/api/client/tree.rs index 88b9cdd28..fa4b8bd2e 100644 --- a/oxen-rust/crates/lib/src/api/client/tree.rs +++ b/oxen-rust/crates/lib/src/api/client/tree.rs @@ -24,10 +24,12 @@ use crate::view::{MerkleHashesResponse, StatusMessage}; use crate::{api, util}; /// Check if a node exists in the remote repository merkle tree by hash +#[tracing::instrument(skip(repository))] pub async fn has_node( repository: &RemoteRepository, node_id: MerkleHash, ) -> Result { + metrics::counter!("oxen_client_tree_has_node_total").increment(1); let uri = format!("/tree/nodes/hash/{node_id}"); let url = api::endpoint::url_from_repo(repository, &uri)?; log::debug!("api::client::tree::has_node {url}"); @@ -50,12 +52,14 @@ pub async fn has_node( } /// Upload a node to the remote repository merkle tree +#[tracing::instrument(skip(local_repo, remote_repo, nodes, progress))] pub async fn create_nodes( local_repo: &LocalRepository, remote_repo: &RemoteRepository, nodes: HashSet, progress: &Arc, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_tree_create_nodes_total").increment(1); // Compress the node log::debug!("create_nodes starting compression"); // OPT: Try Compression::fast(); @@ -106,11 +110,13 @@ pub async fn create_nodes( } /// Download a node from the remote repository merkle tree by hash +#[tracing::instrument(skip(local_repo, remote_repo))] pub async fn download_node( local_repo: &LocalRepository, remote_repo: &RemoteRepository, node_id: &MerkleHash, ) -> Result { + metrics::counter!("oxen_client_tree_download_node_total").increment(1); let node_hash_str = node_id.to_string(); let uri = format!("/tree/nodes/hash/{node_hash_str}/download"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; @@ -131,11 +137,13 @@ pub async fn download_node( } /// Download a node and all its children from the remote repository merkle tree by hash +#[tracing::instrument(skip(local_repo, remote_repo))] pub async fn download_node_with_children( local_repo: &LocalRepository, remote_repo: &RemoteRepository, node_id: &MerkleHash, ) -> Result { + metrics::counter!("oxen_client_tree_download_node_with_children_total").increment(1); let node_hash_str = node_id.to_string(); let uri = format!("/tree/nodes/hash/{node_hash_str}/download"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; @@ -156,10 +164,12 @@ pub async fn download_node_with_children( } /// Downloads the full merkle tree from the remote repository +#[tracing::instrument(skip(local_repo, remote_repo))] pub async fn download_tree( local_repo: &LocalRepository, remote_repo: &RemoteRepository, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_tree_download_tree_total").increment(1); let uri = "/tree/download".to_string(); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; @@ -173,11 +183,13 @@ pub async fn download_tree( } /// Downloads a tree from the remote repository merkle tree by hash +#[tracing::instrument(skip(local_repo, remote_repo))] pub async fn download_tree_from( local_repo: &LocalRepository, remote_repo: &RemoteRepository, hash: &MerkleHash, ) -> Result { + metrics::counter!("oxen_client_tree_download_tree_from_total").increment(1); let hash_str = hash.to_string(); let uri = format!("/tree/download/{hash_str}"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; @@ -196,11 +208,13 @@ pub async fn download_tree_from( Ok(node) } +#[tracing::instrument(skip(remote_repo, path, commit_id))] pub async fn get_node_hash_by_path( remote_repo: &RemoteRepository, commit_id: impl AsRef, path: PathBuf, ) -> Result { + metrics::counter!("oxen_client_tree_get_node_hash_by_path_total").increment(1); let commit_id = commit_id.as_ref(); let path_str = path.to_string_lossy(); let uri = format!("/tree/nodes/resource/{commit_id}/{path_str}"); @@ -213,6 +227,7 @@ pub async fn get_node_hash_by_path( Ok(hash_response.hash) } +#[tracing::instrument(skip(local_repo, remote_repo, commit_id, path), fields(is_dir))] pub async fn download_tree_from_path( local_repo: &LocalRepository, remote_repo: &RemoteRepository, @@ -220,6 +235,7 @@ pub async fn download_tree_from_path( path: impl AsRef, is_dir: bool, ) -> Result { + metrics::counter!("oxen_client_tree_download_tree_from_path_total").increment(1); let download_tree_opts = DownloadTreeOpts { subtree_paths: path.as_ref().into(), depth: if is_dir { -1 } else { 0 }, @@ -253,12 +269,14 @@ pub async fn download_tree_from_path( } /// Download ALL the trees starting from the given commit id +#[tracing::instrument(skip(local_repo, remote_repo, commit_id, fetch_opts))] pub async fn download_trees_from( local_repo: &LocalRepository, remote_repo: &RemoteRepository, commit_id: impl AsRef, fetch_opts: &FetchOpts, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_tree_download_trees_from_total").increment(1); let commit_id = commit_id.as_ref(); let uri = append_fetch_opts_to_uri(format!("/tree/download/{commit_id}"), fetch_opts); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; @@ -326,6 +344,7 @@ fn append_subtree_paths_and_depth_to_uri( uri } +#[tracing::instrument(skip(local_repo, remote_repo, base_id, head_id, fetch_opts))] pub async fn download_trees_between( local_repo: &LocalRepository, remote_repo: &RemoteRepository, @@ -333,6 +352,7 @@ pub async fn download_trees_between( head_id: impl AsRef, fetch_opts: &FetchOpts, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_tree_download_trees_between_total").increment(1); let base_id = base_id.as_ref(); let head_id = head_id.as_ref(); let base_head = format!("{base_id}..{head_id}"); @@ -394,10 +414,12 @@ async fn node_download_request( Ok(()) } +#[tracing::instrument(skip(remote_repo, node_ids))] pub async fn list_missing_node_hashes( remote_repo: &RemoteRepository, node_ids: HashSet, ) -> Result, OxenError> { + metrics::counter!("oxen_client_tree_list_missing_node_hashes_total").increment(1); let uri = "/tree/nodes/missing_node_hashes".to_string(); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; let client = client::new_for_url(&url)?; @@ -413,10 +435,12 @@ pub async fn list_missing_node_hashes( } } +#[tracing::instrument(skip(remote_repo))] pub async fn list_missing_file_hashes( remote_repo: &RemoteRepository, node_id: &MerkleHash, ) -> Result, OxenError> { + metrics::counter!("oxen_client_tree_list_missing_file_hashes_total").increment(1); let uri = format!("/tree/nodes/hash/{node_id}/missing_file_hashes"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; let client = client::new_for_url(&url)?; @@ -431,11 +455,13 @@ pub async fn list_missing_file_hashes( } } +#[tracing::instrument(skip(local_repo, remote_repo, commit_ids))] pub async fn list_missing_file_hashes_from_commits( local_repo: &LocalRepository, remote_repo: &RemoteRepository, commit_ids: HashSet, ) -> Result, OxenError> { + metrics::counter!("oxen_client_tree_list_missing_file_hashes_from_commits_total").increment(1); let uri = "/tree/nodes/missing_file_hashes_from_commits".to_string(); let uri = append_subtree_paths_and_depth_to_uri( uri, @@ -458,10 +484,12 @@ pub async fn list_missing_file_hashes_from_commits( } } +#[tracing::instrument(skip(remote_repo, commit_hashes))] pub async fn mark_nodes_as_synced( remote_repo: &RemoteRepository, commit_hashes: HashSet, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_tree_mark_nodes_as_synced_total").increment(1); let uri = "/tree/nodes/mark_nodes_as_synced".to_string(); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; let client = client::new_for_url(&url)?; diff --git a/oxen-rust/crates/lib/src/api/client/version.rs b/oxen-rust/crates/lib/src/api/client/version.rs index bb37947e4..4e32aa875 100644 --- a/oxen-rust/crates/lib/src/api/client/version.rs +++ b/oxen-rust/crates/lib/src/api/client/version.rs @@ -4,7 +4,9 @@ use crate::error::OxenError; use crate::view::version::VersionResponse; use crate::view::StatusMessage; +#[tracing::instrument] pub async fn get_remote_version(scheme: &str, host: &str) -> Result { + metrics::counter!("oxen_client_version_get_remote_version_total").increment(1); let url = format!("{scheme}://{host}/api/version"); log::debug!("Checking version at url {}", url); @@ -26,7 +28,9 @@ pub async fn get_remote_version(scheme: &str, host: &str) -> Result Result { + metrics::counter!("oxen_client_version_get_min_oxen_version_total").increment(1); let url = format!("{scheme}://{host}/api/min_version"); log::debug!("Checking min cli version at url {}", url); diff --git a/oxen-rust/crates/lib/src/api/client/versions.rs b/oxen-rust/crates/lib/src/api/client/versions.rs index 7a6e6e79d..18ef54311 100644 --- a/oxen-rust/crates/lib/src/api/client/versions.rs +++ b/oxen-rust/crates/lib/src/api/client/versions.rs @@ -51,18 +51,22 @@ pub struct UploadResult { } /// Check if a file exists in the remote repository by version id +#[tracing::instrument(skip(repository))] pub async fn has_version( repository: &RemoteRepository, version_id: MerkleHash, ) -> Result { + metrics::counter!("oxen_client_versions_has_version_total").increment(1); Ok(get(repository, version_id).await?.is_some()) } /// Get the size of a version +#[tracing::instrument(skip(repository))] pub async fn get( repository: &RemoteRepository, version_id: MerkleHash, ) -> Result, OxenError> { + metrics::counter!("oxen_client_versions_get_total").increment(1); let uri = format!("/versions/{version_id}/metadata"); let url = api::endpoint::url_from_repo(repository, &uri)?; log::debug!("api::client::versions::get {url}"); @@ -83,9 +87,11 @@ pub async fn get( } } +#[tracing::instrument(skip(remote_repo))] pub async fn clean( remote_repo: &RemoteRepository, ) -> Result { + metrics::counter!("oxen_client_versions_clean_total").increment(1); let uri = "/versions"; let url = api::endpoint::url_from_repo(remote_repo, uri)?; log::debug!("api::client::versions::clean {url}"); @@ -105,6 +111,10 @@ pub async fn clean( /// Uploads a large file to the server in parallel and unpacks it in the versions directory /// Returns the `MultipartLargeFileUpload` struct for the created upload +#[tracing::instrument( + skip(remote_repo, file_path, dst_dir, progress), + fields(file_size, hash) +)] pub async fn parallel_large_file_upload( remote_repo: &RemoteRepository, file_path: impl AsRef, @@ -113,6 +123,8 @@ pub async fn parallel_large_file_upload( entry: Option, // entry is provided for push workflow progress: Option<&Arc>, // for push workflow ) -> Result { + metrics::counter!("oxen_client_versions_parallel_large_file_upload_total").increment(1); + let timer = std::time::Instant::now(); log::debug!("multipart_large_file_upload path: {:?}", file_path.as_ref()); let mut upload = @@ -132,7 +144,11 @@ pub async fn parallel_large_file_upload( .await?; let num_chunks = results.len(); log::debug!("multipart_large_file_upload num_chunks: {num_chunks:?}"); - complete_multipart_large_file_upload(remote_repo, upload, num_chunks, workspace_id).await + let result = + complete_multipart_large_file_upload(remote_repo, upload, num_chunks, workspace_id).await; + metrics::histogram!("oxen_client_versions_parallel_large_file_upload_duration_ms") + .record(timer.elapsed().as_millis() as f64); + result } /// Creates a new multipart large file upload @@ -192,27 +208,45 @@ async fn create_multipart_large_file_upload( } /// Batch download +#[tracing::instrument(skip(remote_repo, hashes, local_repo))] pub async fn download_data_from_version_paths( remote_repo: &RemoteRepository, hashes: &[String], local_repo: &LocalRepository, ) -> Result { + metrics::counter!("oxen_client_versions_download_data_from_version_paths_total").increment(1); + let timer = std::time::Instant::now(); let config = retry::RetryConfig::default(); retry::with_retry(&config, |_attempt| async { match try_download_data_from_version_paths(remote_repo, hashes, local_repo).await { - Ok(val) => Ok(val), - Err(OxenError::Authentication(val)) => Err(OxenError::Authentication(val)), + Ok(val) => { + metrics::histogram!( + "oxen_client_versions_download_data_from_version_paths_duration_ms" + ) + .record(timer.elapsed().as_millis() as f64); + Ok(val) + } + Err(OxenError::Authentication(val)) => { + metrics::histogram!( + "oxen_client_versions_download_data_from_version_paths_duration_ms" + ) + .record(timer.elapsed().as_millis() as f64); + Err(OxenError::Authentication(val)) + } Err(err) => Err(err), } }) .await } +#[tracing::instrument(skip(remote_repo, hashes, local_repo))] pub async fn try_download_data_from_version_paths( remote_repo: &RemoteRepository, hashes: &[String], local_repo: &LocalRepository, ) -> Result { + metrics::counter!("oxen_client_versions_try_download_data_from_version_paths_total") + .increment(1); let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); for hash in hashes.iter() { let line = format!("{hash}\n"); @@ -465,12 +499,15 @@ async fn complete_multipart_large_file_upload( /// Multipart batch upload with retry /// Uploads a batch of small files to the server in parallel and retries on failure /// Returns a list of files that failed to upload +#[tracing::instrument(skip(local_repo, remote_repo, chunk, client))] pub async fn multipart_batch_upload_with_retry( local_repo: &LocalRepository, remote_repo: &RemoteRepository, chunk: &Vec, client: &reqwest::Client, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_versions_multipart_batch_upload_with_retry_total").increment(1); + let timer = std::time::Instant::now(); let max_retries = max_retries(); let mut files_to_retry: Vec = vec![]; @@ -489,12 +526,15 @@ pub async fn multipart_batch_upload_with_retry( sleep(Duration::from_millis(wait_time)).await; } } + metrics::histogram!("oxen_client_versions_multipart_batch_upload_with_retry_duration_ms") + .record(timer.elapsed().as_millis() as f64); Err(OxenError::basic_str(format!( "Failed to upload files: {files_to_retry:#?}" ))) } +#[tracing::instrument(skip(local_repo, remote_repo, chunk, client, files_to_retry))] pub async fn multipart_batch_upload( local_repo: &LocalRepository, remote_repo: &RemoteRepository, @@ -502,6 +542,7 @@ pub async fn multipart_batch_upload( client: &reqwest::Client, files_to_retry: Vec, ) -> Result, OxenError> { + metrics::counter!("oxen_client_versions_multipart_batch_upload_total").increment(1); let version_store = local_repo.version_store()?; let mut form = reqwest::multipart::Form::new(); let mut err_files: Vec = vec![]; @@ -559,6 +600,7 @@ pub async fn multipart_batch_upload( Ok(err_files) } +#[tracing::instrument(skip(remote_repo, local_or_base, client, paths, result))] pub(crate) async fn workspace_multipart_batch_upload_versions( remote_repo: &RemoteRepository, local_or_base: Option<&LocalOrBase>, @@ -566,6 +608,8 @@ pub(crate) async fn workspace_multipart_batch_upload_versions( paths: Vec, result: UploadResult, ) -> Result { + metrics::counter!("oxen_client_versions_workspace_multipart_batch_upload_versions_total") + .increment(1); // save the errorred files info for retry let mut err_files: Vec = vec![]; // keep track of the files hash @@ -686,6 +730,7 @@ pub(crate) async fn workspace_multipart_batch_upload_versions( }) } +#[tracing::instrument(skip(remote_repo, client, form, files_to_retry, local_or_base))] pub(crate) async fn workspace_multipart_batch_upload_parts_with_retry( remote_repo: &RemoteRepository, client: Arc, @@ -693,6 +738,10 @@ pub(crate) async fn workspace_multipart_batch_upload_parts_with_retry( files_to_retry: &mut Vec, local_or_base: Option<&LocalOrBase>, ) -> Result, OxenError> { + metrics::counter!( + "oxen_client_versions_workspace_multipart_batch_upload_parts_with_retry_total" + ) + .increment(1); log::debug!("Beginning workspace multipart batch upload"); let uri = ("/versions").to_string(); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; diff --git a/oxen-rust/crates/lib/src/api/client/workspaces.rs b/oxen-rust/crates/lib/src/api/client/workspaces.rs index af4ad92d0..3fc70f567 100644 --- a/oxen-rust/crates/lib/src/api/client/workspaces.rs +++ b/oxen-rust/crates/lib/src/api/client/workspaces.rs @@ -15,7 +15,9 @@ use crate::view::workspaces::{ListWorkspaceResponseView, WorkspaceResponseWithSt use crate::view::workspaces::{NewWorkspace, WorkspaceResponse}; use crate::view::{StatusMessage, WorkspaceResponseView}; +#[tracing::instrument(skip(remote_repo))] pub async fn list(remote_repo: &RemoteRepository) -> Result, OxenError> { + metrics::counter!("oxen_client_workspaces_list_total").increment(1); let url = api::endpoint::url_from_repo(remote_repo, "/workspaces")?; let client = client::new_for_url(&url)?; let res = client.get(&url).send().await?; @@ -30,10 +32,12 @@ pub async fn list(remote_repo: &RemoteRepository) -> Result, ) -> Result, OxenError> { + metrics::counter!("oxen_client_workspaces_get_total").increment(1); let workspace_id = workspace_id.as_ref(); let url = api::endpoint::url_from_repo(remote_repo, &format!("/workspaces/{workspace_id}"))?; let client = client::new_for_url(&url)?; @@ -48,10 +52,12 @@ pub async fn get( Ok(workspace) } +#[tracing::instrument(skip(remote_repo, name))] pub async fn get_by_name( remote_repo: &RemoteRepository, name: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_client_workspaces_get_by_name_total").increment(1); let name = name.as_ref(); let url = api::endpoint::url_from_repo(remote_repo, &format!("/workspaces?name={name}"))?; let client = client::new_for_url(&url)?; @@ -78,20 +84,24 @@ pub async fn get_by_name( } } +#[tracing::instrument(skip(remote_repo, branch_name, workspace_id))] pub async fn create( remote_repo: &RemoteRepository, branch_name: impl AsRef, workspace_id: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_workspaces_create_total").increment(1); create_with_path(remote_repo, branch_name, workspace_id, Path::new("/"), None).await } +#[tracing::instrument(skip(remote_repo, branch_name, workspace_id, workspace_name))] pub async fn create_with_name( remote_repo: &RemoteRepository, branch_name: impl AsRef, workspace_id: impl AsRef, workspace_name: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_workspaces_create_with_name_total").increment(1); let workspace_name = workspace_name.as_ref().to_string(); create_with_path( remote_repo, @@ -103,6 +113,7 @@ pub async fn create_with_name( .await } +#[tracing::instrument(skip(remote_repo, branch_name, workspace_id, path))] pub async fn create_with_path( remote_repo: &RemoteRepository, branch_name: impl AsRef, @@ -110,6 +121,7 @@ pub async fn create_with_path( path: impl AsRef, workspace_name: Option, ) -> Result { + metrics::counter!("oxen_client_workspaces_create_with_path_total").increment(1); let branch_name = branch_name.as_ref(); let workspace_id = workspace_id.as_ref(); let path = path.as_ref(); @@ -145,10 +157,12 @@ pub async fn create_with_path( } } +#[tracing::instrument(skip(remote_repo, workspace_id))] pub async fn delete( remote_repo: &RemoteRepository, workspace_id: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_workspaces_delete_total").increment(1); let workspace_id = workspace_id.as_ref(); let url = api::endpoint::url_from_repo(remote_repo, &format!("/workspaces/{workspace_id}"))?; log::debug!("delete workspace {url}\n"); @@ -167,7 +181,9 @@ pub async fn delete( } } +#[tracing::instrument(skip(remote_repo))] pub async fn clear(remote_repo: &RemoteRepository) -> Result<(), OxenError> { + metrics::counter!("oxen_client_workspaces_clear_total").increment(1); let url = api::endpoint::url_from_repo(remote_repo, "/workspaces")?; log::debug!("clear workspaces {url}\n"); diff --git a/oxen-rust/crates/lib/src/api/client/workspaces/changes.rs b/oxen-rust/crates/lib/src/api/client/workspaces/changes.rs index d741f537e..2eed0bd5a 100644 --- a/oxen-rust/crates/lib/src/api/client/workspaces/changes.rs +++ b/oxen-rust/crates/lib/src/api/client/workspaces/changes.rs @@ -7,6 +7,7 @@ use crate::view::{RemoteStagedStatus, RemoteStagedStatusResponse}; use std::path::Path; +#[tracing::instrument(skip(remote_repo, workspace_id, path))] pub async fn list( remote_repo: &RemoteRepository, workspace_id: impl AsRef, @@ -14,6 +15,7 @@ pub async fn list( page: usize, page_size: usize, ) -> Result { + metrics::counter!("oxen_client_workspaces_changes_list_total").increment(1); let workspace_id = workspace_id.as_ref(); let path = path.as_ref(); let path_str = path.to_str().unwrap(); @@ -44,11 +46,13 @@ pub async fn list( } // TODO: Consolidate this with api::client::workspaces::files to have only one workspace rm API +#[tracing::instrument(skip(remote_repo, path))] pub async fn rm( remote_repo: &RemoteRepository, workspace_id: &str, path: impl AsRef, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_workspaces_changes_rm_total").increment(1); let file_name = path.as_ref().to_string_lossy(); let uri = format!("/workspaces/{workspace_id}/changes/{file_name}"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; diff --git a/oxen-rust/crates/lib/src/api/client/workspaces/commits.rs b/oxen-rust/crates/lib/src/api/client/workspaces/commits.rs index 943c9bdfc..3cd48488b 100644 --- a/oxen-rust/crates/lib/src/api/client/workspaces/commits.rs +++ b/oxen-rust/crates/lib/src/api/client/workspaces/commits.rs @@ -5,11 +5,13 @@ use crate::model::{Branch, Commit, NewCommitBody, RemoteRepository}; use crate::view::CommitResponse; use crate::view::merge::{Mergeable, MergeableResponse}; +#[tracing::instrument(skip(remote_repo))] pub async fn mergeability( remote_repo: &RemoteRepository, branch_name: &str, workspace_id: &str, ) -> Result { + metrics::counter!("oxen_client_workspaces_commits_mergeability_total").increment(1); let uri = format!("/workspaces/{workspace_id}/merge/{branch_name}"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; let client = client::new_for_url(&url)?; @@ -24,12 +26,14 @@ pub async fn mergeability( } } +#[tracing::instrument(skip(remote_repo, commit))] pub async fn commit( remote_repo: &RemoteRepository, branch_name: &str, workspace_id: &str, commit: &NewCommitBody, ) -> Result { + metrics::counter!("oxen_client_workspaces_commits_commit_total").increment(1); let uri = format!("/workspaces/{workspace_id}/commit/{branch_name}"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; log::debug!("commit_staged {url}\n{commit:?}"); diff --git a/oxen-rust/crates/lib/src/api/client/workspaces/data_frames.rs b/oxen-rust/crates/lib/src/api/client/workspaces/data_frames.rs index ad117679a..2e00420e9 100644 --- a/oxen-rust/crates/lib/src/api/client/workspaces/data_frames.rs +++ b/oxen-rust/crates/lib/src/api/client/workspaces/data_frames.rs @@ -16,12 +16,14 @@ pub mod columns; pub mod embeddings; pub mod rows; +#[tracing::instrument(skip(remote_repo, workspace_id, path, opts))] pub async fn get( remote_repo: &RemoteRepository, workspace_id: impl AsRef, path: impl AsRef, opts: &DFOpts, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_get_total").increment(1); let workspace_id = workspace_id.as_ref(); let path = path.as_ref(); let file_path_str = path.to_string_lossy(); @@ -46,12 +48,15 @@ pub async fn get( } } +#[tracing::instrument(skip(remote_repo, workspace_id, path, opts))] pub async fn download( remote_repo: &RemoteRepository, workspace_id: impl AsRef, path: impl AsRef, opts: &DFOpts, // opts holds output path ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_workspaces_data_frames_download_total").increment(1); + let timer = std::time::Instant::now(); let workspace_id = workspace_id.as_ref(); let path = path.as_ref(); let file_path_str = path.to_string_lossy(); @@ -93,23 +98,29 @@ pub async fn download( file.write_all(&chunk)?; } + metrics::histogram!("oxen_client_workspaces_data_frames_download_duration_ms") + .record(timer.elapsed().as_millis() as f64); Ok(()) } +#[tracing::instrument(skip(remote_repo))] pub async fn is_indexed( remote_repo: &RemoteRepository, workspace_id: &str, path: &Path, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_is_indexed_total").increment(1); let res = get(remote_repo, workspace_id, path, &DFOpts::empty()).await?; Ok(res.is_indexed) } +#[tracing::instrument(skip(remote_repo))] pub async fn list( remote_repo: &RemoteRepository, branch_name: &str, workspace_id: &str, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_list_total").increment(1); let uri = format!("/workspaces/{workspace_id}/data_frames/branch/{branch_name}"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; @@ -129,11 +140,13 @@ pub async fn list( } } +#[tracing::instrument(skip(remote_repo, path))] pub async fn index( remote_repo: &RemoteRepository, workspace_id: &str, path: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_index_total").increment(1); let path = util::fs::linux_path(path.as_ref()); put( remote_repo, @@ -144,11 +157,13 @@ pub async fn index( .await } +#[tracing::instrument(skip(remote_repo))] pub async fn unindex( remote_repo: &RemoteRepository, workspace_id: &str, path: &Path, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_unindex_total").increment(1); put( remote_repo, workspace_id, @@ -158,12 +173,14 @@ pub async fn unindex( .await } +#[tracing::instrument(skip(remote_repo, workspace_id, path, data))] pub async fn put( remote_repo: &RemoteRepository, workspace_id: impl AsRef, path: impl AsRef, data: &serde_json::Value, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_put_total").increment(1); let workspace_id = workspace_id.as_ref(); let path = path.as_ref(); let file_path_str = path.to_string_lossy(); @@ -186,11 +203,13 @@ pub async fn put( } } +#[tracing::instrument(skip(remote_repo, path))] pub async fn restore( remote_repo: &RemoteRepository, workspace_id: &str, path: impl AsRef, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_workspaces_data_frames_restore_total").increment(1); let file_name = path.as_ref().to_string_lossy(); let uri = format!("/workspaces/{workspace_id}/data_frames/resource/{file_name}"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; @@ -202,11 +221,13 @@ pub async fn restore( Ok(()) } +#[tracing::instrument(skip(remote_repo, path))] pub async fn restore_files( remote_repo: &RemoteRepository, workspace_id: &str, path: impl AsRef, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_workspaces_data_frames_restore_files_total").increment(1); let file_name = path.as_ref().to_string_lossy(); let uri = format!("/workspaces/{workspace_id}/data_frames/resource/{file_name}"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; @@ -218,6 +239,7 @@ pub async fn restore_files( Ok(()) } +#[tracing::instrument(skip(remote_repo))] pub async fn diff( remote_repo: &RemoteRepository, workspace_id: &str, @@ -225,6 +247,7 @@ pub async fn diff( page_num: usize, page_size: usize, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_diff_total").increment(1); let file_path_str = path.to_str().unwrap(); let uri = format!( @@ -254,12 +277,14 @@ pub async fn diff( } } +#[tracing::instrument(skip(remote_repo, workspace_id, path, new_path))] pub async fn rename_data_frame( remote_repo: &RemoteRepository, workspace_id: impl AsRef, path: impl AsRef, new_path: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_rename_data_frame_total").increment(1); let workspace_id = workspace_id.as_ref(); let path = path.as_ref(); let file_path_str = path.to_string_lossy(); diff --git a/oxen-rust/crates/lib/src/api/client/workspaces/data_frames/columns.rs b/oxen-rust/crates/lib/src/api/client/workspaces/data_frames/columns.rs index 2419bd21b..25f4f44b9 100644 --- a/oxen-rust/crates/lib/src/api/client/workspaces/data_frames/columns.rs +++ b/oxen-rust/crates/lib/src/api/client/workspaces/data_frames/columns.rs @@ -10,12 +10,14 @@ use crate::view::json_data_frame_view::JsonDataFrameColumnResponse; use crate::model::RemoteRepository; +#[tracing::instrument(skip(remote_repo))] pub async fn create( remote_repo: &RemoteRepository, workspace_id: &str, path: &Path, data: String, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_columns_create_total").increment(1); let Some(file_path_str) = path.to_str() else { return Err(OxenError::basic_str(format!( "Path must be a string: {path:?}" @@ -55,12 +57,14 @@ pub async fn create( } } +#[tracing::instrument(skip(remote_repo))] pub async fn delete( remote_repo: &RemoteRepository, workspace_id: &str, path: &Path, column_name: &str, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_columns_delete_total").increment(1); let Some(file_path_str) = path.to_str() else { return Err(OxenError::basic_str(format!( "Path must be a string: {path:?}" @@ -97,6 +101,7 @@ pub async fn delete( } } +#[tracing::instrument(skip(remote_repo))] pub async fn update( remote_repo: &RemoteRepository, workspace_id: &str, @@ -104,6 +109,7 @@ pub async fn update( column_name: &str, data: String, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_columns_update_total").increment(1); let Some(file_path_str) = path.to_str() else { return Err(OxenError::basic_str(format!( "Path must be a string: {path:?}" @@ -145,6 +151,7 @@ pub async fn update( } } +#[tracing::instrument(skip(remote_repo, metadata))] pub async fn add_column_metadata( remote_repo: &RemoteRepository, workspace_id: &str, @@ -152,6 +159,8 @@ pub async fn add_column_metadata( column_name: &str, metadata: serde_json::Value, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_workspaces_data_frames_columns_add_column_metadata_total") + .increment(1); let Some(file_path_str) = path.to_str() else { return Err(OxenError::basic_str(format!( "Path must be a string: {path:?}" diff --git a/oxen-rust/crates/lib/src/api/client/workspaces/data_frames/embeddings.rs b/oxen-rust/crates/lib/src/api/client/workspaces/data_frames/embeddings.rs index f4b612a84..f5c88f619 100644 --- a/oxen-rust/crates/lib/src/api/client/workspaces/data_frames/embeddings.rs +++ b/oxen-rust/crates/lib/src/api/client/workspaces/data_frames/embeddings.rs @@ -10,11 +10,13 @@ use std::path::Path; use crate::model::RemoteRepository; +#[tracing::instrument(skip(remote_repo))] pub async fn get( remote_repo: &RemoteRepository, workspace_id: &str, path: &Path, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_embeddings_get_total").increment(1); let Some(file_path_str) = path.to_str() else { return Err(OxenError::basic_str(format!( "Path must be a string: {path:?}" @@ -31,6 +33,7 @@ pub async fn get( Ok(response?) } +#[tracing::instrument(skip(remote_repo, column, embedding, paginate_opts))] pub async fn neighbors( remote_repo: &RemoteRepository, workspace_id: &str, @@ -39,6 +42,7 @@ pub async fn neighbors( embedding: &Vec, paginate_opts: &PaginateOpts, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_embeddings_neighbors_total").increment(1); let Some(file_path_str) = path.to_str() else { return Err(OxenError::basic_str(format!( "Path must be a string: {path:?}" @@ -65,6 +69,7 @@ pub async fn neighbors( Ok(response?) } +#[tracing::instrument(skip(remote_repo))] pub async fn index( remote_repo: &RemoteRepository, workspace_id: &str, @@ -72,6 +77,7 @@ pub async fn index( column: &str, use_background_thread: bool, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_embeddings_index_total").increment(1); let Some(file_path_str) = path.to_str() else { return Err(OxenError::basic_str(format!( "Path must be a string: {path:?}" diff --git a/oxen-rust/crates/lib/src/api/client/workspaces/data_frames/rows.rs b/oxen-rust/crates/lib/src/api/client/workspaces/data_frames/rows.rs index 676e861f0..e23a9be8a 100644 --- a/oxen-rust/crates/lib/src/api/client/workspaces/data_frames/rows.rs +++ b/oxen-rust/crates/lib/src/api/client/workspaces/data_frames/rows.rs @@ -9,12 +9,14 @@ use crate::view::json_data_frame_view::{JsonDataFrameRowResponse, VecBatchUpdate use crate::model::RemoteRepository; +#[tracing::instrument(skip(remote_repo))] pub async fn get( remote_repo: &RemoteRepository, workspace_id: &str, path: &Path, row_id: &str, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_rows_get_total").increment(1); let Some(file_path_str) = path.to_str() else { return Err(OxenError::basic_str(format!( "Path must be a string: {path:?}" @@ -40,6 +42,7 @@ pub async fn get( } } +#[tracing::instrument(skip(remote_repo))] pub async fn update( remote_repo: &RemoteRepository, workspace_id: &str, @@ -47,6 +50,7 @@ pub async fn update( row_id: &str, data: String, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_rows_update_total").increment(1); let Some(file_path_str) = path.to_str() else { return Err(OxenError::basic_str(format!( "Path must be a string: {path:?}" @@ -78,12 +82,14 @@ pub async fn update( } } +#[tracing::instrument(skip(remote_repo))] pub async fn delete( remote_repo: &RemoteRepository, workspace_id: &str, path: &Path, row_id: &str, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_rows_delete_total").increment(1); let Some(file_path_str) = path.to_str() else { return Err(OxenError::basic_str(format!( "Path must be a string: {path:?}" @@ -111,12 +117,14 @@ pub async fn delete( } } +#[tracing::instrument(skip(remote_repo))] pub async fn add( remote_repo: &RemoteRepository, workspace_id: &str, path: &Path, data: String, ) -> Result<(DataFrame, Option), OxenError> { + metrics::counter!("oxen_client_workspaces_data_frames_rows_add_total").increment(1); let Some(file_path_str) = path.to_str() else { return Err(OxenError::basic_str(format!( "Path must be a string: {path:?}" @@ -156,12 +164,14 @@ pub async fn add( } } +#[tracing::instrument(skip(remote_repo))] pub async fn restore_row( remote_repo: &RemoteRepository, workspace_id: &str, path: &Path, row_id: &str, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_rows_restore_row_total").increment(1); let Some(file_path_str) = path.to_str() else { return Err(OxenError::basic_str(format!( "Path must be a string: {path:?}" @@ -200,12 +210,14 @@ pub async fn restore_row( } } +#[tracing::instrument(skip(remote_repo))] pub async fn batch_update( remote_repo: &RemoteRepository, workspace_id: &str, path: &Path, data: String, ) -> Result { + metrics::counter!("oxen_client_workspaces_data_frames_rows_batch_update_total").increment(1); let Some(file_path_str) = path.to_str() else { return Err(OxenError::basic_str(format!( "Path must be a string: {path:?}" diff --git a/oxen-rust/crates/lib/src/api/client/workspaces/files.rs b/oxen-rust/crates/lib/src/api/client/workspaces/files.rs index 8620f5ca6..bbdfacc49 100644 --- a/oxen-rust/crates/lib/src/api/client/workspaces/files.rs +++ b/oxen-rust/crates/lib/src/api/client/workspaces/files.rs @@ -46,6 +46,7 @@ pub struct UploadResult { pub type UploadFails = Vec; // TODO: Test adding removed files +#[tracing::instrument(skip(remote_repo, workspace_id, directory, paths, local_repo))] pub async fn add( remote_repo: &RemoteRepository, workspace_id: impl AsRef, @@ -53,6 +54,8 @@ pub async fn add( paths: Vec, local_repo: &Option, ) -> Result { + metrics::counter!("oxen_client_workspaces_files_add_total").increment(1); + let timer = std::time::Instant::now(); let workspace_id = workspace_id.as_ref(); let directory = directory.as_ref(); @@ -88,6 +91,9 @@ pub async fn add( ) .await; + metrics::histogram!("oxen_client_workspaces_files_add_duration_ms") + .record(timer.elapsed().as_millis() as f64); + match upload_result { Ok(failed_to_upload) => { print_add_result(workspace_id, n_expected_uploads, &failed_to_upload); @@ -154,12 +160,15 @@ fn resolve_paths_in_place(base_dir: &Path, paths: &mut [PathBuf]) -> Result<(), /// /// The intended use case is to import a large pre-existing file-directory structure into a /// repository. +#[tracing::instrument(skip(remote_repo, workspace_id, base_dir, paths))] pub async fn add_files( remote_repo: &RemoteRepository, workspace_id: impl AsRef, base_dir: impl AsRef, paths: Vec, ) -> Result, OxenError> { + metrics::counter!("oxen_client_workspaces_files_add_files_total").increment(1); + let timer = std::time::Instant::now(); let base_dir = std::path::absolute(base_dir)?; if !base_dir.is_dir() { @@ -184,7 +193,7 @@ pub async fn add_files( let base_dir_enum = LocalOrBase::Base(base_dir); let n_expected_uploads = paths.len(); - match upload_multiple_files( + let result = upload_multiple_files( remote_repo, workspace_id, "", // Each path has the right relative directory components, so it's crucial that they're @@ -193,8 +202,10 @@ pub async fn add_files( paths, Some(&base_dir_enum), ) - .await - { + .await; + metrics::histogram!("oxen_client_workspaces_files_add_files_duration_ms") + .record(timer.elapsed().as_millis() as f64); + match result { Ok(failed_to_upload) => { print_add_result(workspace_id, n_expected_uploads, &failed_to_upload); Ok(failed_to_upload) @@ -203,6 +214,7 @@ pub async fn add_files( } } +#[tracing::instrument(skip(remote_repo, workspace_id, directory, path, buf))] pub async fn add_bytes( remote_repo: &RemoteRepository, workspace_id: impl AsRef, @@ -210,6 +222,7 @@ pub async fn add_bytes( path: PathBuf, buf: &[u8], ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_workspaces_files_add_bytes_total").increment(1); let workspace_id = workspace_id.as_ref(); let directory = directory.as_ref(); @@ -225,12 +238,15 @@ pub async fn add_bytes( Ok(()) } +#[tracing::instrument(skip(remote_repo, workspace_id, directory, path))] pub async fn upload_single_file( remote_repo: &RemoteRepository, workspace_id: impl AsRef, directory: impl AsRef, path: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_workspaces_files_upload_single_file_total").increment(1); + let timer = std::time::Instant::now(); let path = path.as_ref(); let Ok(metadata) = path.metadata() else { @@ -239,7 +255,7 @@ pub async fn upload_single_file( log::debug!("Uploading file with size: {}", metadata.len()); // If the file is larger than AVG_CHUNK_SIZE, use the parallel upload strategy - if metadata.len() > chunk_size() { + let result = if metadata.len() > chunk_size() { let directory = directory.as_ref(); match api::client::versions::parallel_large_file_upload( remote_repo, @@ -257,9 +273,13 @@ pub async fn upload_single_file( } else { // Single multipart request p_upload_single_file(remote_repo, workspace_id, directory, path).await - } + }; + metrics::histogram!("oxen_client_workspaces_files_upload_single_file_duration_ms") + .record(timer.elapsed().as_millis() as f64); + result } +#[tracing::instrument(skip(remote_repo, workspace_id, directory, path, buf))] pub async fn upload_bytes_as_file( remote_repo: &RemoteRepository, workspace_id: impl AsRef, @@ -267,6 +287,7 @@ pub async fn upload_bytes_as_file( path: impl AsRef, buf: &[u8], ) -> Result { + metrics::counter!("oxen_client_workspaces_files_upload_bytes_as_file_total").increment(1); p_upload_bytes_as_file(remote_repo, workspace_id, directory, path, buf).await } @@ -385,6 +406,7 @@ async fn upload_multiple_files( Ok(failed_to_upload) } +#[tracing::instrument(skip(remote_repo, workspace_id, directory, small_files, local_or_base))] pub(crate) async fn parallel_batched_small_file_upload( remote_repo: &RemoteRepository, workspace_id: impl AsRef, @@ -393,6 +415,9 @@ pub(crate) async fn parallel_batched_small_file_upload( small_files_size: u64, local_or_base: Option<&LocalOrBase>, ) -> Result, OxenError> { + metrics::counter!("oxen_client_workspaces_files_parallel_batched_small_file_upload_total") + .increment(1); + let timer = std::time::Instant::now(); if small_files.is_empty() { return Ok(vec![]); } @@ -762,6 +787,10 @@ pub(crate) async fn parallel_batched_small_file_upload( log::debug!("All upload tasks completed"); progress.finish(); + metrics::histogram!( + "oxen_client_workspaces_files_parallel_batched_small_file_upload_duration_ms" + ) + .record(timer.elapsed().as_millis() as f64); if !operational_errors.is_empty() { log::error!( "Encountered {} fatal error(s) during upload", @@ -782,6 +811,14 @@ pub(crate) async fn parallel_batched_small_file_upload( // Retry stage_files_to_workspace until successful or retry limit breached // If individual files fail, return them to be re-tried at the end +#[tracing::instrument(skip( + remote_repo, + client, + workspace_id, + files_to_add, + directory_str, + err_files +))] pub async fn stage_files_to_workspace_with_retry( remote_repo: &RemoteRepository, client: Arc, @@ -790,6 +827,9 @@ pub async fn stage_files_to_workspace_with_retry( directory_str: impl AsRef, err_files: Vec, ) -> Result, OxenError> { + metrics::counter!("oxen_client_workspaces_files_stage_files_to_workspace_with_retry_total") + .increment(1); + let timer = std::time::Instant::now(); let mut retry_count: usize = 0; let directory_str = directory_str.as_ref(); let workspace_id = workspace_id.as_ref().to_string(); @@ -810,11 +850,16 @@ pub async fn stage_files_to_workspace_with_retry( { // If successful, return individual files that failed to stage Ok(stage_err_files) => { + metrics::histogram!( + "oxen_client_workspaces_files_stage_files_to_workspace_with_retry_duration_ms" + ) + .record(timer.elapsed().as_millis() as f64); return Ok(stage_err_files); } Err(e) => { log::error!("Error staging files to workspace: {e:?}"); if retry_count == max_retries { + metrics::histogram!("oxen_client_workspaces_files_stage_files_to_workspace_with_retry_duration_ms").record(timer.elapsed().as_millis() as f64); return Err(OxenError::basic_str(format!( "failed to stage files to workspace after retries: {e:?}" ))); @@ -830,12 +875,24 @@ pub async fn stage_files_to_workspace_with_retry( "Error: Failed to stage files_to_add: {:?}", files_to_add.len() ); + metrics::histogram!( + "oxen_client_workspaces_files_stage_files_to_workspace_with_retry_duration_ms" + ) + .record(timer.elapsed().as_millis() as f64); Err(OxenError::basic_str( "failed to stage files to workspace after retries", )) } // Stage files to the workspace, filtering out files that previously failed to upload to version store +#[tracing::instrument(skip( + remote_repo, + client, + workspace_id, + files_to_add, + directory_str, + err_files +))] pub async fn stage_files_to_workspace( remote_repo: &RemoteRepository, client: Arc, @@ -844,6 +901,7 @@ pub async fn stage_files_to_workspace( directory_str: impl AsRef, err_files: Vec, ) -> Result, OxenError> { + metrics::counter!("oxen_client_workspaces_files_stage_files_to_workspace_total").increment(1); let workspace_id = workspace_id.as_ref(); let directory_str = directory_str.as_ref(); let uri = format!("/workspaces/{workspace_id}/versions/{directory_str}"); @@ -989,11 +1047,13 @@ async fn p_upload_bytes_as_file( // TODO: Merge this with 'rm_files' // Splitting them is a temporary solution to preserve compatibility with the python repo +#[tracing::instrument(skip(remote_repo, path))] pub async fn rm( remote_repo: &RemoteRepository, workspace_id: &str, path: impl AsRef, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_workspaces_files_rm_total").increment(1); let file_name = path.as_ref().to_string_lossy(); let uri = format!("/workspaces/{workspace_id}/files/{file_name}"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; @@ -1005,12 +1065,14 @@ pub async fn rm( Ok(()) } +#[tracing::instrument(skip(local_repo, remote_repo, workspace_id, paths))] pub async fn rm_files( local_repo: &LocalRepository, remote_repo: &RemoteRepository, workspace_id: impl AsRef, paths: Vec, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_workspaces_files_rm_files_total").increment(1); let workspace_id = workspace_id.as_ref(); // Parse glob paths @@ -1067,12 +1129,14 @@ pub async fn rm_files( Ok(()) } +#[tracing::instrument(skip(local_repo, remote_repo, workspace_id, paths))] pub async fn rm_files_from_staged( local_repo: &LocalRepository, remote_repo: &RemoteRepository, workspace_id: impl AsRef, paths: Vec, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_workspaces_files_rm_files_from_staged_total").increment(1); let workspace_id = workspace_id.as_ref(); // Parse glob paths @@ -1137,12 +1201,14 @@ pub async fn rm_files_from_staged( /// Move or rename a file within a workspace. /// Sends a PATCH request to update the file's path. +#[tracing::instrument(skip(remote_repo, workspace_id, path, new_path))] pub async fn mv( remote_repo: &RemoteRepository, workspace_id: impl AsRef, path: impl AsRef, new_path: impl AsRef, ) -> Result { + metrics::counter!("oxen_client_workspaces_files_mv_total").increment(1); let workspace_id = workspace_id.as_ref(); let path = path.as_ref(); let file_path_str = path.to_string_lossy(); @@ -1168,12 +1234,15 @@ pub async fn mv( } } +#[tracing::instrument(skip(remote_repo))] pub async fn download( remote_repo: &RemoteRepository, workspace_id: &str, path: &str, output_path: Option<&Path>, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_workspaces_files_download_total").increment(1); + let timer = std::time::Instant::now(); let uri = if util::fs::has_tabular_extension(path) { format!("/workspaces/{workspace_id}/data_frames/download/{path}") } else { @@ -1217,14 +1286,19 @@ pub async fn download( ))); } + metrics::histogram!("oxen_client_workspaces_files_download_duration_ms") + .record(timer.elapsed().as_millis() as f64); Ok(()) } +#[tracing::instrument(skip(remote_repo))] pub async fn validate_upload_feasibility( remote_repo: &RemoteRepository, workspace_id: &str, total_size: u64, ) -> Result<(), OxenError> { + metrics::counter!("oxen_client_workspaces_files_validate_upload_feasibility_total") + .increment(1); let uri = format!("/workspaces/{workspace_id}/validate"); let url = api::endpoint::url_from_repo(remote_repo, &uri)?; let client = client::new_for_url(&url)?; diff --git a/oxen-rust/crates/lib/src/core/v_latest/index/commit_merkle_tree.rs b/oxen-rust/crates/lib/src/core/v_latest/index/commit_merkle_tree.rs index 5d299004c..19e1e2c9a 100644 --- a/oxen-rust/crates/lib/src/core/v_latest/index/commit_merkle_tree.rs +++ b/oxen-rust/crates/lib/src/core/v_latest/index/commit_merkle_tree.rs @@ -25,10 +25,12 @@ pub struct CommitMerkleTree { impl CommitMerkleTree { /// The dir hashes allow you to skip to a directory in the tree + #[tracing::instrument(skip(repo), fields(commit_id = %commit.id))] pub fn dir_hashes( repo: &LocalRepository, commit: &Commit, ) -> Result, OxenError> { + metrics::counter!("oxen_merkle_dir_hashes_total").increment(1); let dir_hashes = with_dir_hash_db_manager(repo, &commit.id, |dir_hashes_db| { let mut dir_hashes = HashMap::new(); let iterator = dir_hashes_db.iterator(IteratorMode::Start); @@ -62,7 +64,10 @@ impl CommitMerkleTree { dir_hash_db_path(repo, commit) } + #[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display(), commit_id = %commit.id))] pub fn from_commit(repo: &LocalRepository, commit: &Commit) -> Result { + metrics::counter!("oxen_merkle_from_commit_total").increment(1); + let timer = std::time::Instant::now(); // This debug log is to help make sure we don't load the tree too many times // if you see it in the logs being called too much, it could be why the code is slow. log::debug!("Load tree from commit: {} in repo: {:?}", commit, repo.path); @@ -72,6 +77,8 @@ impl CommitMerkleTree { ))?; let dir_hashes = CommitMerkleTree::dir_hashes(repo, commit)?; + metrics::histogram!("oxen_merkle_from_commit_duration_ms") + .record(timer.elapsed().as_millis() as f64); Ok(Self { root, dir_hashes }) } @@ -99,23 +106,28 @@ impl CommitMerkleTree { }) } + #[tracing::instrument(skip(repo), fields(commit_id = %commit.id))] pub fn root_with_children( repo: &LocalRepository, commit: &Commit, ) -> Result, OxenError> { + metrics::counter!("oxen_merkle_root_with_children_total").increment(1); let node_hash = commit.id.parse()?; CommitMerkleTree::read_node(repo, &node_hash, true) } + #[tracing::instrument(skip(repo), fields(commit_id = %commit.id))] pub fn root_without_children( repo: &LocalRepository, commit: &Commit, ) -> Result, OxenError> { + metrics::counter!("oxen_merkle_root_without_children_total").increment(1); let node_hash = commit.id.parse()?; // Read the root node at depth 1 to get the directory node as well CommitMerkleTree::read_node(repo, &node_hash, false) } + #[tracing::instrument(skip(repo, base_hashes, unique_hashes, shared_hashes), fields(commit_id = %commit.id))] pub fn root_with_children_and_node_hashes( repo: &LocalRepository, commit: &Commit, @@ -123,6 +135,7 @@ impl CommitMerkleTree { unique_hashes: Option<&mut HashSet>, shared_hashes: Option<&mut HashSet>, ) -> Result, OxenError> { + metrics::counter!("oxen_merkle_root_with_children_and_node_hashes_total").increment(1); let node_hash = commit.id.parse()?; CommitMerkleTree::read_node_and_collect_hashes( @@ -134,6 +147,7 @@ impl CommitMerkleTree { ) } + #[tracing::instrument(skip(repo, base_hashes, unique_hashes, shared_hashes, partial_nodes), fields(commit_id = %commit.id))] pub fn root_with_children_and_partial_nodes( repo: &LocalRepository, commit: &Commit, @@ -142,6 +156,7 @@ impl CommitMerkleTree { shared_hashes: Option<&mut HashSet>, partial_nodes: &mut HashMap, ) -> Result, OxenError> { + metrics::counter!("oxen_merkle_root_with_children_and_partial_nodes_total").increment(1); let node_hash = commit.id.parse()?; CommitMerkleTree::read_node_and_collect_partial_nodes( @@ -273,11 +288,13 @@ impl CommitMerkleTree { ) } + #[tracing::instrument(skip(repo), fields(hash = %hash, depth))] pub fn read_depth( repo: &LocalRepository, hash: &MerkleHash, depth: i32, ) -> Result, OxenError> { + metrics::counter!("oxen_merkle_read_depth_total").increment(1); // log::debug!("Read depth {} node hash [{}]", depth, hash); if !MerkleNodeDB::exists(repo, hash) { log::debug!("read_depth merkle node db does not exist for hash: {hash}"); @@ -741,11 +758,13 @@ impl CommitMerkleTree { } /// This uses the dir_hashes db to skip right to a file in the tree + #[tracing::instrument(skip(repo, dir_hashes, path))] pub fn read_file( repo: &LocalRepository, dir_hashes: &HashMap, path: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_merkle_read_file_total").increment(1); // Get the directory from the path let path = path.as_ref(); let Some(parent_path) = path.parent() else { @@ -812,6 +831,7 @@ impl CommitMerkleTree { // TODO: We might want to simplify to one tree-loading method // The advantage of multiple is that it saves us tree traversals, when we want to collect something as we load in the tree // However, I'm not sure that's worth the cost of extending the code base. We could probably cut this file in half if we're willing to do extra tree traversals + #[tracing::instrument(skip(repo, node, base_hashes, unique_hashes, shared_hashes), fields(hash = %node.hash, requested_depth, traversed_depth))] fn load_children( repo: &LocalRepository, node: &mut MerkleTreeNode, @@ -821,6 +841,7 @@ impl CommitMerkleTree { requested_depth: i32, traversed_depth: i32, ) -> Result<(), OxenError> { + metrics::counter!("oxen_merkle_load_children_total").increment(1); let dtype = node.node.node_type(); // log::debug!( // "load_children requested_depth {} traversed_depth {} node {}", diff --git a/oxen-rust/crates/lib/src/repositories/add.rs b/oxen-rust/crates/lib/src/repositories/add.rs index 18e5314ea..d3647571e 100644 --- a/oxen-rust/crates/lib/src/repositories/add.rs +++ b/oxen-rust/crates/lib/src/repositories/add.rs @@ -36,22 +36,35 @@ use std::path::Path; /// # Ok(()) /// # } /// ``` +#[tracing::instrument(skip(repo, path))] pub async fn add(repo: &LocalRepository, path: impl AsRef) -> Result<(), OxenError> { - add_all_with_version(repo, vec![path], repo.min_version()).await + metrics::counter!("oxen_repo_add_add_total").increment(1); + let timer = std::time::Instant::now(); + let result = add_all_with_version(repo, vec![path], repo.min_version()).await; + metrics::histogram!("oxen_repo_add_add_duration_ms").record(timer.elapsed().as_millis() as f64); + result } +#[tracing::instrument(skip(repo, paths))] pub async fn add_all>( repo: &LocalRepository, paths: impl IntoIterator, ) -> Result<(), OxenError> { - add_all_with_version(repo, paths, repo.min_version()).await + metrics::counter!("oxen_repo_add_add_all_total").increment(1); + let timer = std::time::Instant::now(); + let result = add_all_with_version(repo, paths, repo.min_version()).await; + metrics::histogram!("oxen_repo_add_add_all_duration_ms") + .record(timer.elapsed().as_millis() as f64); + result } +#[tracing::instrument(skip(repo, paths), fields(version = %version))] pub async fn add_all_with_version>( repo: &LocalRepository, paths: impl IntoIterator, version: MinOxenVersion, ) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_add_add_all_with_version_total").increment(1); match version { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::add::add(repo, paths).await, diff --git a/oxen-rust/crates/lib/src/repositories/branches.rs b/oxen-rust/crates/lib/src/repositories/branches.rs index f05228f4f..85109fb75 100644 --- a/oxen-rust/crates/lib/src/repositories/branches.rs +++ b/oxen-rust/crates/lib/src/repositories/branches.rs @@ -13,25 +13,33 @@ use crate::repositories; use crate::{core, util}; /// List all the local branches within a repo +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn list(repo: &LocalRepository) -> Result, OxenError> { + metrics::counter!("oxen_repo_branch_list_total").increment(1); with_ref_manager(repo, |manager| manager.list_branches()) } /// List all the local branches within a repo along with their head commits +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn list_with_commits(repo: &LocalRepository) -> Result, OxenError> { + metrics::counter!("oxen_repo_branch_list_with_commits_total").increment(1); with_ref_manager(repo, |manager| manager.list_branches_with_commits()) } /// Get a branch by name +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn get_by_name(repo: &LocalRepository, name: &str) -> Result, OxenError> { + metrics::counter!("oxen_repo_branch_get_by_name_total").increment(1); with_ref_manager(repo, |manager| manager.get_branch_by_name(name)) } /// Get branch by name or fall back the current +#[tracing::instrument(skip(repo, branch_name), fields(repo_path = %repo.path.display()))] pub fn get_by_name_or_current( repo: &LocalRepository, branch_name: Option>, ) -> Result { + metrics::counter!("oxen_repo_branch_get_by_name_or_current_total").increment(1); if let Some(branch_name) = branch_name { let branch_name = branch_name.as_ref(); match repositories::branches::get_by_name(repo, branch_name)? { @@ -50,12 +58,16 @@ pub fn get_by_name_or_current( } /// Get commit id from a branch by name +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn get_commit_id(repo: &LocalRepository, name: &str) -> Result, OxenError> { + metrics::counter!("oxen_repo_branch_get_commit_id_total").increment(1); with_ref_manager(repo, |manager| manager.get_commit_id_for_branch(name)) } /// Check if a branch exists +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn exists(repo: &LocalRepository, name: &str) -> Result { + metrics::counter!("oxen_repo_branch_exists_total").increment(1); match get_by_name(repo, name)? { Some(_) => Ok(true), None => Ok(false), @@ -63,28 +75,34 @@ pub fn exists(repo: &LocalRepository, name: &str) -> Result { } /// Get the current branch +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn current_branch(repo: &LocalRepository) -> Result, OxenError> { + metrics::counter!("oxen_repo_branch_current_branch_total").increment(1); with_ref_manager(repo, |manager| manager.get_current_branch()) } /// # Create a new branch from the head commit /// This creates a new pointer to the current commit with a name, /// it does not switch you to this branch, you still must call `checkout_branch` +#[tracing::instrument(skip(repo, name), fields(repo_path = %repo.path.display()))] pub fn create_from_head( repo: &LocalRepository, name: impl AsRef, ) -> Result { + metrics::counter!("oxen_repo_branch_create_from_head_total").increment(1); let name = name.as_ref(); let head_commit = repositories::commits::head_commit(repo)?; with_ref_manager(repo, |manager| manager.create_branch(name, &head_commit.id)) } /// # Create a local branch from a specific commit id +#[tracing::instrument(skip(repo, name, commit_id), fields(repo_path = %repo.path.display()))] pub fn create( repo: &LocalRepository, name: impl AsRef, commit_id: impl AsRef, ) -> Result { + metrics::counter!("oxen_repo_branch_create_total").increment(1); let name = name.as_ref(); let commit_id = commit_id.as_ref(); @@ -98,7 +116,9 @@ pub fn create( /// # Create a branch and check it out in one go /// This creates a branch with name, /// then switches HEAD to point to the branch +#[tracing::instrument(skip(repo, name), fields(repo_path = %repo.path.display()))] pub fn create_checkout(repo: &LocalRepository, name: impl AsRef) -> Result { + metrics::counter!("oxen_repo_branch_create_checkout_total").increment(1); let name = name.as_ref(); let name = util::fs::linux_path_str(name); println!("Create and checkout branch: {name}"); @@ -112,11 +132,13 @@ pub fn create_checkout(repo: &LocalRepository, name: impl AsRef) -> Result< } /// Update the branch name to point to a commit id +#[tracing::instrument(skip(repo, name, commit_id), fields(repo_path = %repo.path.display()))] pub fn update( repo: &LocalRepository, name: impl AsRef, commit_id: impl AsRef, ) -> Result { + metrics::counter!("oxen_repo_branch_update_total").increment(1); let name = name.as_ref(); let commit_id = commit_id.as_ref(); with_ref_manager(repo, |manager| { @@ -131,7 +153,9 @@ pub fn update( } /// Delete a local branch +#[tracing::instrument(skip(repo, name), fields(repo_path = %repo.path.display()))] pub fn delete(repo: &LocalRepository, name: impl AsRef) -> Result { + metrics::counter!("oxen_repo_branch_delete_total").increment(1); let name = name.as_ref(); // Make sure they don't delete the current checked out branch if let Ok(Some(branch)) = current_branch(repo) @@ -153,7 +177,9 @@ pub fn delete(repo: &LocalRepository, name: impl AsRef) -> Result) -> Result { + metrics::counter!("oxen_repo_branch_force_delete_total").increment(1); let name = name.as_ref(); if let Ok(Some(branch)) = current_branch(repo) && branch.name == name @@ -166,7 +192,9 @@ pub fn force_delete(repo: &LocalRepository, name: impl AsRef) -> Result bool { + metrics::counter!("oxen_repo_branch_is_checked_out_total").increment(1); if let Ok(Some(current_branch)) = with_ref_manager(repo, |manager| manager.get_current_branch()) { // If we are already on the branch, do nothing @@ -178,11 +206,13 @@ pub fn is_checked_out(repo: &LocalRepository, name: &str) -> bool { } /// Checkout a branch +#[tracing::instrument(skip(repo, name, from_commit), fields(repo_path = %repo.path.display()))] pub async fn checkout_branch_from_commit( repo: &LocalRepository, name: impl AsRef, from_commit: &Option, ) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_branch_checkout_branch_from_commit_total").increment(1); let name = name.as_ref(); log::debug!("checkout_branch {name}"); match repo.min_version() { @@ -192,12 +222,14 @@ pub async fn checkout_branch_from_commit( } /// Checkout a subtree from a commit +#[tracing::instrument(skip(repo, subtree_paths), fields(repo_path = %repo.path.display(), commit_id = %to_commit.id, depth))] pub async fn checkout_subtrees_to_commit( repo: &LocalRepository, to_commit: &Commit, subtree_paths: &[PathBuf], depth: i32, ) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_branch_checkout_subtrees_to_commit_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => { panic!("checkout_subtree_from_commit not implemented for oxen v0.10.0") @@ -209,18 +241,22 @@ pub async fn checkout_subtrees_to_commit( } /// Checkout a commit +#[tracing::instrument(skip(repo, from_commit), fields(repo_path = %repo.path.display(), commit_id = %commit.id))] pub async fn checkout_commit_from_commit( repo: &LocalRepository, commit: &Commit, from_commit: &Option, ) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_branch_checkout_commit_from_commit_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::branches::checkout_commit(repo, commit, from_commit).await, } } +#[tracing::instrument(skip(repo, value), fields(repo_path = %repo.path.display()))] pub fn set_head(repo: &LocalRepository, value: impl AsRef) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_branch_set_head_total").increment(1); log::debug!("set_head {}", value.as_ref()); with_ref_manager(repo, |manager| { manager.set_head(value); @@ -251,7 +287,9 @@ fn branch_has_been_merged(repo: &LocalRepository, name: &str) -> Result Result<(), OxenError> { + metrics::counter!("oxen_repo_branch_rename_current_branch_total").increment(1); if let Ok(Some(branch)) = current_branch(repo) { with_ref_manager(repo, |manager| { manager.rename_branch(&branch.name, new_name)?; @@ -265,11 +303,13 @@ pub fn rename_current_branch(repo: &LocalRepository, new_name: &str) -> Result<( } // Traces through a branches history to list all unique versions of a file +#[tracing::instrument(skip(local_repo), fields(repo_path = %local_repo.path.display()))] pub fn list_entry_versions_on_branch( local_repo: &LocalRepository, branch_name: &str, path: &Path, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_branch_list_entry_versions_on_branch_total").increment(1); let branch = repositories::branches::get_by_name(local_repo, branch_name)? .ok_or(OxenError::local_branch_not_found(branch_name))?; log::debug!( @@ -287,11 +327,13 @@ pub fn list_entry_versions_on_branch( } } +#[tracing::instrument(skip(repo, from_commit), fields(repo_path = %repo.path.display(), commit_id = %commit.id))] pub async fn set_working_repo_to_commit( repo: &LocalRepository, commit: &Commit, from_commit: &Option, ) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_branch_set_working_repo_to_commit_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => { panic!("set_working_repo_to_commit not implemented for oxen v0.10.0") diff --git a/oxen-rust/crates/lib/src/repositories/checkout.rs b/oxen-rust/crates/lib/src/repositories/checkout.rs index e23bb2aac..8a7f04699 100644 --- a/oxen-rust/crates/lib/src/repositories/checkout.rs +++ b/oxen-rust/crates/lib/src/repositories/checkout.rs @@ -14,63 +14,80 @@ use crate::{repositories, util}; /// # Checkout a branch or commit id /// This switches HEAD to point to the branch name or commit id, /// it also updates all the local files to be from the commit that this branch references +#[tracing::instrument(skip(repo, value), fields(repo_path = %repo.path.display()))] pub async fn checkout( repo: &LocalRepository, value: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_checkout_checkout_total").increment(1); + let timer = std::time::Instant::now(); let value = value.as_ref(); log::debug!("--- CHECKOUT START {value} ----"); - if repositories::branches::exists(repo, value)? { + let result = if repositories::branches::exists(repo, value)? { if repositories::branches::is_checked_out(repo, value) { println!("Already on branch {value}"); - return repositories::branches::get_by_name(repo, value); - } - - println!("Checkout branch: {value}"); - let commit = repositories::revisions::get(repo, value)? - .ok_or(OxenError::revision_not_found(value.into()))?; - let subtree_paths = match repo.subtree_paths() { - Some(paths_vec) => paths_vec, // If Some(vec), take the inner vector - None => vec![Path::new("").to_path_buf()], - }; - let depth = repo.depth().unwrap_or(i32::MAX); //TODO: make repo depth not an option so that we use depth from the repo consistently. - repositories::branches::checkout_subtrees_to_commit(repo, &commit, &subtree_paths, depth) + repositories::branches::get_by_name(repo, value) + } else { + println!("Checkout branch: {value}"); + let commit = repositories::revisions::get(repo, value)? + .ok_or(OxenError::revision_not_found(value.into()))?; + let subtree_paths = match repo.subtree_paths() { + Some(paths_vec) => paths_vec, // If Some(vec), take the inner vector + None => vec![Path::new("").to_path_buf()], + }; + let depth = repo.depth().unwrap_or(i32::MAX); //TODO: make repo depth not an option so that we use depth from the repo consistently. + repositories::branches::checkout_subtrees_to_commit( + repo, + &commit, + &subtree_paths, + depth, + ) .await?; - repositories::branches::set_head(repo, value)?; - repositories::branches::get_by_name(repo, value) + repositories::branches::set_head(repo, value)?; + repositories::branches::get_by_name(repo, value) + } } else { // If we are already on the commit, do nothing if repositories::branches::is_checked_out(repo, value) { eprintln!("Commit already checked out {value}"); - return Ok(None); - } - - let commit = repositories::revisions::get(repo, value)? - .ok_or(OxenError::revision_not_found(value.into()))?; + Ok(None) + } else { + let commit = repositories::revisions::get(repo, value)? + .ok_or(OxenError::revision_not_found(value.into()))?; - let previous_head_commit = repositories::commits::head_commit_maybe(repo)?; - repositories::branches::checkout_commit_from_commit(repo, &commit, &previous_head_commit) + let previous_head_commit = repositories::commits::head_commit_maybe(repo)?; + repositories::branches::checkout_commit_from_commit( + repo, + &commit, + &previous_head_commit, + ) .await?; - repositories::branches::update(repo, value, &commit.id)?; - repositories::branches::set_head(repo, value)?; - - if repo.is_remote_mode() { - // Set workspace_name to new branch name - let mut mut_repo = repo.clone(); - mut_repo.set_workspace(value)?; - mut_repo.save()?; - } + repositories::branches::update(repo, value, &commit.id)?; + repositories::branches::set_head(repo, value)?; + + if repo.is_remote_mode() { + // Set workspace_name to new branch name + let mut mut_repo = repo.clone(); + mut_repo.set_workspace(value)?; + mut_repo.save()?; + } - Ok(None) - } + Ok(None) + } + }; + metrics::histogram!("oxen_repo_checkout_checkout_duration_ms") + .record(timer.elapsed().as_millis() as f64); + result } /// # Checkout a file and take their changes /// This overwrites the current file with the changes in the branch we are merging in +#[tracing::instrument(skip(repo, path), fields(repo_path = %repo.path.display()))] pub async fn checkout_theirs( repo: &LocalRepository, path: impl AsRef, ) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_checkout_checkout_theirs_total").increment(1); let conflicts = repositories::merge::list_conflicts(repo)?; log::debug!( "checkout_theirs {:?} conflicts.len() {}", @@ -96,10 +113,12 @@ pub async fn checkout_theirs( /// # Checkout a file and take our changes /// This overwrites the current file with the changes we had in our current branch +#[tracing::instrument(skip(repo, path), fields(repo_path = %repo.path.display()))] pub async fn checkout_ours( repo: &LocalRepository, path: impl AsRef, ) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_checkout_checkout_ours_total").increment(1); let conflicts = repositories::merge::list_conflicts(repo)?; log::debug!( "checkout_ours {:?} conflicts.len() {}", @@ -125,10 +144,12 @@ pub async fn checkout_ours( /// # Combine Conflicting Tabular Data Files /// This overwrites the current file with the changes in their file +#[tracing::instrument(skip(repo, path), fields(repo_path = %repo.path.display()))] pub async fn checkout_combine>( repo: &LocalRepository, path: P, ) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_checkout_checkout_combine_total").increment(1); let conflicts = repositories::merge::list_conflicts(repo)?; log::debug!( diff --git a/oxen-rust/crates/lib/src/repositories/clone.rs b/oxen-rust/crates/lib/src/repositories/clone.rs index ebdd9de7f..d64004874 100644 --- a/oxen-rust/crates/lib/src/repositories/clone.rs +++ b/oxen-rust/crates/lib/src/repositories/clone.rs @@ -14,26 +14,36 @@ use crate::opts::CloneOpts; use crate::opts::{FetchOpts, StorageOpts}; use crate::{api, util}; +#[tracing::instrument(skip(opts))] pub async fn clone(opts: &CloneOpts) -> Result { - match clone_remote(opts).await { + metrics::counter!("oxen_repo_clone_clone_total").increment(1); + let timer = std::time::Instant::now(); + let result = match clone_remote(opts).await { Ok(Some(repo)) => Ok(repo), Ok(None) => Err(OxenError::remote_repo_not_found(&opts.url)), Err(err) => Err(err), - } + }; + metrics::histogram!("oxen_repo_clone_clone_duration_ms") + .record(timer.elapsed().as_millis() as f64); + result } +#[tracing::instrument(skip(url, dst))] pub async fn clone_url( url: impl AsRef, dst: impl AsRef, ) -> Result { + metrics::counter!("oxen_repo_clone_clone_url_total").increment(1); let fetch_opts = FetchOpts::new(); _clone(url, dst, fetch_opts).await } +#[tracing::instrument(skip(url, dst))] pub async fn deep_clone_url( url: impl AsRef, dst: impl AsRef, ) -> Result { + metrics::counter!("oxen_repo_clone_deep_clone_url_total").increment(1); let fetch_opts = FetchOpts { all: true, ..FetchOpts::new() diff --git a/oxen-rust/crates/lib/src/repositories/commits.rs b/oxen-rust/crates/lib/src/repositories/commits.rs index 43976112f..42892b267 100644 --- a/oxen-rust/crates/lib/src/repositories/commits.rs +++ b/oxen-rust/crates/lib/src/repositories/commits.rs @@ -46,18 +46,26 @@ pub mod commit_writer; /// # Ok(()) /// # } /// ``` +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn commit(repo: &LocalRepository, message: &str) -> Result { - match repo.min_version() { + metrics::counter!("oxen_repo_commit_commit_total").increment(1); + let timer = std::time::Instant::now(); + let result = match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::commits::commit(repo, message), - } + }; + metrics::histogram!("oxen_repo_commit_commit_duration_ms") + .record(timer.elapsed().as_millis() as f64); + result } +#[tracing::instrument(skip(repo, user), fields(repo_path = %repo.path.display()))] pub fn commit_with_user( repo: &LocalRepository, message: &str, user: &User, ) -> Result { + metrics::counter!("oxen_repo_commit_commit_with_user_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::commits::commit_with_user(repo, message, user), @@ -68,7 +76,9 @@ pub fn commit_with_user( /// /// Allows creating a commit even when there are no staged changes. /// This reuses the existing create_empty_commit infrastructure. +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn commit_allow_empty(repo: &LocalRepository, message: &str) -> Result { + metrics::counter!("oxen_repo_commit_commit_allow_empty_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::commits::commit_allow_empty(repo, message), @@ -76,7 +86,9 @@ pub fn commit_allow_empty(repo: &LocalRepository, message: &str) -> Result Result { + metrics::counter!("oxen_repo_commit_latest_commit_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::commits::latest_commit(repo), @@ -84,7 +96,9 @@ pub fn latest_commit(repo: &LocalRepository) -> Result { } /// The current HEAD commit of the branch you currently have checked out +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn head_commit(repo: &LocalRepository) -> Result { + metrics::counter!("oxen_repo_commit_head_commit_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::commits::head_commit(repo), @@ -93,7 +107,9 @@ pub fn head_commit(repo: &LocalRepository) -> Result { /// Maybe get the head commit if it exists /// Returns None if the head commit does not exist (empty repo) +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn head_commit_maybe(repo: &LocalRepository) -> Result, OxenError> { + metrics::counter!("oxen_repo_commit_head_commit_maybe_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::commits::head_commit_maybe(repo), @@ -101,7 +117,9 @@ pub fn head_commit_maybe(repo: &LocalRepository) -> Result, OxenE } /// Get the root commit of a repository +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn root_commit_maybe(repo: &LocalRepository) -> Result, OxenError> { + metrics::counter!("oxen_repo_commit_root_commit_maybe_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::commits::root_commit_maybe(repo), @@ -109,7 +127,9 @@ pub fn root_commit_maybe(repo: &LocalRepository) -> Result, OxenE } /// Get a commit by it's MerkleHash +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn get_by_hash(repo: &LocalRepository, hash: &MerkleHash) -> Result, OxenError> { + metrics::counter!("oxen_repo_commit_get_by_hash_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::commits::get_by_hash(repo, hash), @@ -117,10 +137,12 @@ pub fn get_by_hash(repo: &LocalRepository, hash: &MerkleHash) -> Result, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_commit_get_by_id_total").increment(1); let commit_id = commit_id.as_ref(); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), @@ -129,19 +151,23 @@ pub fn get_by_id( } /// Commit id exists +#[tracing::instrument(skip(repo, commit_id), fields(repo_path = %repo.path.display()))] pub fn commit_id_exists( repo: &LocalRepository, commit_id: impl AsRef, ) -> Result { + metrics::counter!("oxen_repo_commit_commit_id_exists_total").increment(1); get_by_id(repo, commit_id.as_ref()).map(|commit| commit.is_some()) } /// Create an empty commit off of the head commit of a branch +#[tracing::instrument(skip(repo, branch_name), fields(repo_path = %repo.path.display(), commit_id = %commit.id))] pub fn create_empty_commit( repo: &LocalRepository, branch_name: impl AsRef, commit: &Commit, ) -> Result { + metrics::counter!("oxen_repo_commit_create_empty_commit_total").increment(1); let branch_name = branch_name.as_ref(); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("create_empty_commit not supported in v0.10.0"), @@ -152,12 +178,14 @@ pub fn create_empty_commit( /// Create an initial empty commit for an empty repository. /// This creates the first commit with an empty tree and sets up the branch. /// Returns an error if the repository already has commits. +#[tracing::instrument(skip(repo, branch_name, user, message), fields(repo_path = %repo.path.display()))] pub fn create_initial_commit( repo: &LocalRepository, branch_name: impl AsRef, user: &User, message: impl AsRef, ) -> Result { + metrics::counter!("oxen_repo_commit_create_initial_commit_total").increment(1); let branch_name = branch_name.as_ref(); let message = message.as_ref(); match repo.min_version() { @@ -167,7 +195,9 @@ pub fn create_initial_commit( } /// List commits on the current branch from HEAD +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn list(repo: &LocalRepository) -> Result, OxenError> { + metrics::counter!("oxen_repo_commit_list_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::commits::list(repo), @@ -175,7 +205,9 @@ pub fn list(repo: &LocalRepository) -> Result, OxenError> { } /// List commits for the repository in no particular order +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn list_all(repo: &LocalRepository) -> Result, OxenError> { + metrics::counter!("oxen_repo_commit_list_all_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::commits::list_all(repo), @@ -183,7 +215,9 @@ pub fn list_all(repo: &LocalRepository) -> Result, OxenError> { } /// List unsynced commits for the repository (ie they are missing their .version/ files) +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn list_unsynced(repo: &LocalRepository) -> Result, OxenError> { + metrics::counter!("oxen_repo_commit_list_unsynced_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("list_unsynced not supported in v0.10.0"), _ => core::v_latest::commits::list_unsynced(repo), @@ -191,30 +225,36 @@ pub fn list_unsynced(repo: &LocalRepository) -> Result, OxenErro } /// List unsynced commits from a specific revision +#[tracing::instrument(skip(repo, revision), fields(repo_path = %repo.path.display()))] pub fn list_unsynced_from( repo: &LocalRepository, revision: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_commit_list_unsynced_from_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("list_unsynced_from not supported in v0.10.0"), _ => core::v_latest::commits::list_unsynced_from(repo, revision), } } // Source +#[tracing::instrument(skip(repo, commit_id_or_branch_name), fields(repo_path = %repo.path.display()))] pub fn get_commit_or_head + Clone>( repo: &LocalRepository, commit_id_or_branch_name: Option, ) -> Result { + metrics::counter!("oxen_repo_commit_get_commit_or_head_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => resource::get_commit_or_head(repo, commit_id_or_branch_name), _ => core::v_latest::commits::get_commit_or_head(repo, commit_id_or_branch_name), } } +#[tracing::instrument(skip(repo, pagination), fields(repo_path = %repo.path.display()))] pub fn list_all_paginated( repo: &LocalRepository, pagination: PaginateOpts, ) -> Result { + metrics::counter!("oxen_repo_commit_list_all_paginated_total").increment(1); log::info!("list_all_paginated: {:?} {:?}", repo.path, pagination); let commits = list_all(repo)?; let commits: Vec = commits.into_iter().collect(); @@ -227,17 +267,21 @@ pub fn list_all_paginated( } /// List the history for a specific branch or commit (revision) +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn list_from(repo: &LocalRepository, revision: &str) -> Result, OxenError> { + metrics::counter!("oxen_repo_commit_list_from_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::commits::list_from(repo, revision), } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn list_from_with_depth( repo: &LocalRepository, revision: &str, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_commit_list_from_with_depth_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => Err(OxenError::basic_str( "list_from_with_depth not supported in v0.10.0", @@ -247,11 +291,13 @@ pub fn list_from_with_depth( } /// List the history between two commits +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display(), base_id = %base.id, head_id = %head.id))] pub fn list_between( repo: &LocalRepository, base: &Commit, head: &Commit, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_commit_list_between_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::commits::list_between(repo, base, head), @@ -259,10 +305,12 @@ pub fn list_between( } /// Get a list of commits by the commit message +#[tracing::instrument(skip(repo, msg), fields(repo_path = %repo.path.display()))] pub fn get_by_message( repo: &LocalRepository, msg: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_commit_get_by_message_total").increment(1); let commits = list_all(repo)?; let filtered: Vec = commits .into_iter() @@ -272,10 +320,12 @@ pub fn get_by_message( } /// Get the most recent commit by the commit message, starting at the HEAD commit +#[tracing::instrument(skip(repo, msg), fields(repo_path = %repo.path.display()))] pub fn first_by_message( repo: &LocalRepository, msg: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_commit_first_by_message_total").increment(1); let commits = list(repo)?; Ok(commits .into_iter() @@ -283,11 +333,13 @@ pub fn first_by_message( } /// Retrieve entries with filepaths matching a provided glob pattern +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display(), commit_id = %commit.id))] pub fn search_entries( repo: &LocalRepository, commit: &Commit, pattern: &str, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_commit_search_entries_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::commits::search_entries(repo, commit, pattern), @@ -295,11 +347,13 @@ pub fn search_entries( } /// List paginated commits starting from the given revision +#[tracing::instrument(skip(repo, pagination), fields(repo_path = %repo.path.display()))] pub fn list_from_paginated( repo: &LocalRepository, revision: &str, pagination: PaginateOpts, ) -> Result { + metrics::counter!("oxen_repo_commit_list_from_paginated_total").increment(1); let _perf = crate::perf_guard!("commits::list_from_paginated"); match repo.min_version() { @@ -349,12 +403,14 @@ pub fn list_from_paginated( } /// List paginated commits by resource +#[tracing::instrument(skip(repo, pagination), fields(repo_path = %repo.path.display(), commit_id = %commit.id))] pub fn list_by_path_from_paginated( repo: &LocalRepository, commit: &Commit, path: &Path, pagination: PaginateOpts, ) -> Result { + metrics::counter!("oxen_repo_commit_list_by_path_from_paginated_total").increment(1); let _perf = crate::perf_guard!("commits::list_by_path_from_paginated"); log::info!("list_by_path_from_paginated: {commit:?} {path:?}"); @@ -364,20 +420,24 @@ pub fn list_by_path_from_paginated( } } +#[tracing::instrument(skip(repo, revision), fields(repo_path = %repo.path.display()))] pub fn count_from( repo: &LocalRepository, revision: impl AsRef, ) -> Result<(usize, bool), OxenError> { + metrics::counter!("oxen_repo_commit_count_from_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => Err(OxenError::basic_str("count_from not supported in v0.10.0")), _ => core::v_latest::commits::count_from(repo, revision), } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display(), commit_id = %commit.id))] pub fn commit_history_is_complete( repo: &LocalRepository, commit: &Commit, ) -> Result { + metrics::counter!("oxen_repo_commit_commit_history_is_complete_total").increment(1); // Get full commit history from this head backwards let history = list_from(repo, &commit.id)?; diff --git a/oxen-rust/crates/lib/src/repositories/data_frames.rs b/oxen-rust/crates/lib/src/repositories/data_frames.rs index ce62c33d5..0445bd4cc 100644 --- a/oxen-rust/crates/lib/src/repositories/data_frames.rs +++ b/oxen-rust/crates/lib/src/repositories/data_frames.rs @@ -9,12 +9,14 @@ use std::path::Path; pub mod schemas; +#[tracing::instrument(skip(repo, resource, path, opts), fields(repo_path = %repo.path.display()))] pub async fn get_slice( repo: &LocalRepository, resource: &ParsedResource, path: impl AsRef, opts: &DFOpts, ) -> Result { + metrics::counter!("oxen_repo_data_frames_get_slice_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::data_frames::get_slice(repo, resource, path, opts).await, diff --git a/oxen-rust/crates/lib/src/repositories/diffs.rs b/oxen-rust/crates/lib/src/repositories/diffs.rs index fcc813823..e0d7d3085 100644 --- a/oxen-rust/crates/lib/src/repositories/diffs.rs +++ b/oxen-rust/crates/lib/src/repositories/diffs.rs @@ -61,7 +61,9 @@ fn is_files_utf8(file_1: impl AsRef, file_2: impl AsRef) -> bool { util::fs::is_utf8(file_1.as_ref()) && util::fs::is_utf8(file_2.as_ref()) } +#[tracing::instrument(skip(opts))] pub async fn diff(opts: DiffOpts) -> Result, OxenError> { + metrics::counter!("oxen_repo_diff_diff_total").increment(1); log::debug!( "Starting diff function with keys: {:?} and targets: {:?}", opts.keys, @@ -163,12 +165,14 @@ pub async fn diff(opts: DiffOpts) -> Result, OxenError> { } } +#[tracing::instrument(skip(repo, opts), fields(repo_path = %repo.path.display()))] pub async fn diff_uncommitted( repo: &LocalRepository, rev_1: &str, path_1: &Path, opts: &DiffOpts, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_diff_diff_uncommitted_total").increment(1); let status_opts = StagedDataOpts::from_paths(&[path_1.to_path_buf()]); let status = repositories::status::status_from_opts(repo, &status_opts)?; let unstaged_files = status.unstaged_files(); @@ -208,6 +212,7 @@ pub async fn diff_uncommitted( Ok(diff_result) } +#[tracing::instrument(skip(repo, opts), fields(repo_path = %repo.path.display()))] pub async fn diff_revs( repo: &LocalRepository, rev_1: &str, @@ -216,6 +221,7 @@ pub async fn diff_revs( path_2: &Path, opts: &DiffOpts, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_diff_diff_revs_total").increment(1); log::debug!( "Comparing revisions: {}:{} with {}:{}", rev_1, @@ -346,6 +352,7 @@ pub async fn diff_revs( Ok(content_diffs) } +#[tracing::instrument(skip(repo, cpath_1, cpath_2, keys, targets, display), fields(repo_path = %repo.path.display()))] pub async fn diff_commits( repo: &LocalRepository, cpath_1: CommitPath, @@ -354,6 +361,7 @@ pub async fn diff_commits( targets: Vec, display: Vec, ) -> Result { + metrics::counter!("oxen_repo_diff_diff_commits_total").increment(1); log::debug!("Compare command called with: {cpath_1:?} and {cpath_2:?}"); let (node_1, node_2) = match (cpath_1.commit, cpath_2.commit) { @@ -400,6 +408,7 @@ pub async fn diff_commits( } /// Diffs a directory between two commits, returning a summary of changes. +#[tracing::instrument(skip(repo, base_path, head_path, opts), fields(repo_path = %repo.path.display(), base_commit_id = %base_commit.id, head_commit_id = %head_commit.id))] pub async fn diff_path( repo: &LocalRepository, base_commit: &Commit, @@ -408,6 +417,7 @@ pub async fn diff_path( head_path: impl AsRef, opts: &DiffOpts, ) -> Result { + metrics::counter!("oxen_repo_diff_diff_path_total").increment(1); match (base_path.as_ref().is_file(), head_path.as_ref().is_file()) { (true, true) => { let diff_entry = DiffEntry { @@ -457,6 +467,7 @@ pub async fn diff_path( } } +#[tracing::instrument(skip(path_1, path_2, keys, targets, display))] pub async fn diff_files( path_1: impl AsRef, path_2: impl AsRef, @@ -464,6 +475,7 @@ pub async fn diff_files( targets: Vec, display: Vec, ) -> Result { + metrics::counter!("oxen_repo_diff_diff_files_total").increment(1); log::debug!( "Compare command called with: {:?} and {:?}", path_1.as_ref(), @@ -492,6 +504,7 @@ pub async fn diff_files( } // TODO: merge this and diff_file_and_node +#[tracing::instrument(skip(repo, file_node, file_path, keys, targets, display), fields(repo_path = %repo.path.display()))] pub async fn diff_file_and_node( repo: &LocalRepository, file_node: Option, @@ -500,6 +513,7 @@ pub async fn diff_file_and_node( targets: Vec, display: Vec, ) -> Result { + metrics::counter!("oxen_repo_diff_diff_file_and_node_total").increment(1); match file_node { Some(file_node) => match file_node.data_type() { EntryDataType::Tabular => { @@ -565,6 +579,7 @@ pub async fn diff_file_and_node( } } +#[tracing::instrument(skip(repo, file_1, file_2, keys, targets, display), fields(repo_path = %repo.path.display()))] pub async fn diff_file_nodes( repo: &LocalRepository, file_1: Option, @@ -573,6 +588,7 @@ pub async fn diff_file_nodes( targets: Vec, display: Vec, ) -> Result { + metrics::counter!("oxen_repo_diff_diff_file_nodes_total").increment(1); match (file_1, file_2) { (Some(file_1), Some(file_2)) => { log::debug!( @@ -819,6 +835,7 @@ pub async fn diff_text_file_nodes( } } +#[tracing::instrument(skip(file_1, file_2, keys, targets, display))] pub async fn tabular( file_1: impl AsRef, file_2: impl AsRef, @@ -826,6 +843,7 @@ pub async fn tabular( targets: Vec, display: Vec, ) -> Result { + metrics::counter!("oxen_repo_diff_tabular_total").increment(1); let df_1 = tabular::read_df(file_1, DFOpts::empty()).await?; let df_2 = tabular::read_df(file_2, DFOpts::empty()).await?; @@ -1267,6 +1285,7 @@ pub async fn compute_new_columns_from_dfs( }) } +#[tracing::instrument(skip(repo, file_path, base_entry, head_entry, df_opts), fields(repo_path = %repo.path.display(), base_commit_id = %base_commit.id, head_commit_id = %head_commit.id))] pub async fn diff_entries( repo: &LocalRepository, file_path: impl AsRef, @@ -1276,6 +1295,7 @@ pub async fn diff_entries( head_commit: &Commit, df_opts: DFOpts, ) -> Result { + metrics::counter!("oxen_repo_diff_diff_entries_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => { @@ -1442,6 +1462,7 @@ fn read_dupes(repo: &LocalRepository, compare_id: &str) -> Result Result { + metrics::counter!("oxen_repo_diff_list_diff_entries_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => { diff --git a/oxen-rust/crates/lib/src/repositories/download.rs b/oxen-rust/crates/lib/src/repositories/download.rs index 154b05a79..069546e07 100644 --- a/oxen-rust/crates/lib/src/repositories/download.rs +++ b/oxen-rust/crates/lib/src/repositories/download.rs @@ -13,12 +13,14 @@ use crate::model::MetadataEntry; use crate::model::RemoteRepository; use crate::repositories::LocalRepository; +#[tracing::instrument(skip(repo, remote_path, local_path, revision))] pub async fn download( repo: &RemoteRepository, remote_path: impl AsRef, local_path: impl AsRef, revision: impl AsRef, ) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_download_download_total").increment(1); // Ping server telling it we are about to download api::client::repositories::pre_download(repo).await?; api::client::entries::download_entry( @@ -33,12 +35,14 @@ pub async fn download( Ok(()) } +#[tracing::instrument(skip(remote_repo, entry, remote_path, local_path))] pub async fn download_dir( remote_repo: &RemoteRepository, entry: &MetadataEntry, remote_path: impl AsRef, local_path: impl AsRef, ) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_download_download_dir_total").increment(1); match remote_repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => { @@ -50,6 +54,7 @@ pub async fn download_dir( Ok(()) } +#[tracing::instrument(skip(local_repo, remote_repo, entry, remote_path, local_path), fields(repo_path = %local_repo.path.display()))] pub async fn download_dir_to_repo( local_repo: &LocalRepository, remote_repo: &RemoteRepository, @@ -57,6 +62,7 @@ pub async fn download_dir_to_repo( remote_path: impl AsRef, local_path: impl AsRef, ) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_download_download_dir_to_repo_total").increment(1); match remote_repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => { diff --git a/oxen-rust/crates/lib/src/repositories/entries.rs b/oxen-rust/crates/lib/src/repositories/entries.rs index c484bd9bd..51bd18d2b 100644 --- a/oxen-rust/crates/lib/src/repositories/entries.rs +++ b/oxen-rust/crates/lib/src/repositories/entries.rs @@ -21,11 +21,13 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; /// Get a directory object for a commit +#[tracing::instrument(skip(repo, path), fields(repo_path = %repo.path.display(), commit_id = %commit.id))] pub fn get_directory( repo: &LocalRepository, commit: &Commit, path: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_entries_get_directory_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 is no longer supported"), _ => core::v_latest::entries::get_directory(repo, commit, path), @@ -33,11 +35,13 @@ pub fn get_directory( } /// Get a file node for a commit +#[tracing::instrument(skip(repo, path), fields(repo_path = %repo.path.display(), commit_id = %commit.id))] pub fn get_file( repo: &LocalRepository, commit: &Commit, path: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_entries_get_file_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 is no longer supported"), MinOxenVersion::V0_19_0 => core::v_old::v0_19_0::entries::get_file(repo, commit, path), @@ -46,25 +50,30 @@ pub fn get_file( } /// List all the entries within a commit +#[tracing::instrument(skip(repo, revision, paginate_opts), fields(repo_path = %repo.path.display()))] pub fn list_commit_entries( repo: &LocalRepository, revision: impl AsRef, paginate_opts: &PaginateOpts, ) -> Result { + metrics::counter!("oxen_repo_entries_list_commit_entries_total").increment(1); list_directory_w_version(repo, ROOT_PATH, revision, paginate_opts, repo.min_version()) } /// List all the entries within a directory given a specific commit +#[tracing::instrument(skip(repo, directory, revision, paginate_opts), fields(repo_path = %repo.path.display()))] pub fn list_directory( repo: &LocalRepository, directory: impl AsRef, revision: impl AsRef, paginate_opts: &PaginateOpts, ) -> Result { + metrics::counter!("oxen_repo_entries_list_directory_total").increment(1); list_directory_w_version(repo, directory, revision, paginate_opts, repo.min_version()) } /// Force a version when listing a repo +#[tracing::instrument(skip(repo, directory, revision, paginate_opts), fields(repo_path = %repo.path.display()))] pub fn list_directory_w_version( repo: &LocalRepository, directory: impl AsRef, @@ -72,6 +81,7 @@ pub fn list_directory_w_version( paginate_opts: &PaginateOpts, version: MinOxenVersion, ) -> Result { + metrics::counter!("oxen_repo_entries_list_directory_w_version_total").increment(1); match version { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => { @@ -96,6 +106,7 @@ pub fn list_directory_w_version( } } +#[tracing::instrument(skip(repo, directory, revision, workspace, paginate_opts), fields(repo_path = %repo.path.display()))] pub fn list_directory_w_workspace( repo: &LocalRepository, directory: impl AsRef, @@ -104,6 +115,7 @@ pub fn list_directory_w_workspace( paginate_opts: &PaginateOpts, version: MinOxenVersion, ) -> Result { + metrics::counter!("oxen_repo_entries_list_directory_w_workspace_total").increment(1); list_directory_w_workspace_depth( repo, directory, @@ -115,6 +127,7 @@ pub fn list_directory_w_workspace( ) } +#[tracing::instrument(skip(repo, directory, revision, workspace, paginate_opts), fields(repo_path = %repo.path.display()))] pub fn list_directory_w_workspace_depth( repo: &LocalRepository, directory: impl AsRef, @@ -124,6 +137,7 @@ pub fn list_directory_w_workspace_depth( version: MinOxenVersion, depth: usize, ) -> Result { + metrics::counter!("oxen_repo_entries_list_directory_w_workspace_depth_total").increment(1); let _perf = crate::perf_guard!("entries::list_directory_w_workspace"); match version { @@ -160,7 +174,9 @@ pub fn list_directory_w_workspace_depth( } } +#[tracing::instrument(skip(repo, revision), fields(repo_path = %repo.path.display()))] pub fn update_metadata(repo: &LocalRepository, revision: impl AsRef) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_entries_update_metadata_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => { panic!("update_metadata not implemented for oxen v0.10.0") @@ -172,11 +188,13 @@ pub fn update_metadata(repo: &LocalRepository, revision: impl AsRef) -> Res /// Get the entry for a given path in a commit. /// Could be a file or a directory. +#[tracing::instrument(skip(repo, path), fields(repo_path = %repo.path.display(), commit_id = %commit.id))] pub fn get_meta_entry( repo: &LocalRepository, commit: &Commit, path: impl AsRef, ) -> Result { + metrics::counter!("oxen_repo_entries_get_meta_entry_total").increment(1); let path = path.as_ref(); let parsed_resource = ParsedResource { path: path.to_path_buf(), @@ -196,7 +214,9 @@ pub fn get_meta_entry( } /// List the paths of all the directories in a given commit +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display(), commit_id = %commit.id))] pub fn list_dir_paths(repo: &LocalRepository, commit: &Commit) -> Result, OxenError> { + metrics::counter!("oxen_repo_entries_list_dir_paths_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => { @@ -207,11 +227,13 @@ pub fn list_dir_paths(repo: &LocalRepository, commit: &Commit) -> Result Result, OxenError> { + metrics::counter!("oxen_repo_entries_get_commit_entry_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => match core::v_latest::entries::get_file(repo, commit, path)? { @@ -231,17 +253,21 @@ pub fn get_commit_entry( } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display(), commit_id = %commit.id))] pub fn list_for_commit( repo: &LocalRepository, commit: &Commit, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_entries_list_for_commit_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::entries::list_for_commit(repo, commit), } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display(), commit_id = %commit.id))] pub fn count_for_commit(repo: &LocalRepository, commit: &Commit) -> Result { + metrics::counter!("oxen_repo_entries_count_for_commit_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::entries::count_for_commit(repo, commit), @@ -314,11 +340,13 @@ pub fn group_schemas_to_parent_dirs( results } +#[tracing::instrument(skip(repo, base_commit), fields(repo_path = %repo.path.display(), head_commit_id = %head_commit.id))] pub async fn list_missing_files_in_commit_range( repo: &LocalRepository, base_commit: &Option, head_commit: &Commit, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_entries_list_missing_files_in_commit_range_total").increment(1); let version_store = repo.version_store()?; match base_commit { @@ -379,10 +407,12 @@ pub async fn list_missing_files_in_commit_range( } } +#[tracing::instrument(skip(local_repo), fields(repo_path = %local_repo.path.display(), commit_id = %commit.id))] pub fn list_tabular_files_in_repo( local_repo: &LocalRepository, commit: &Commit, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_entries_list_tabular_files_in_repo_total").increment(1); match local_repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::entries::list_tabular_files_in_repo(local_repo, commit), diff --git a/oxen-rust/crates/lib/src/repositories/fetch.rs b/oxen-rust/crates/lib/src/repositories/fetch.rs index e207e0822..ecbd97950 100644 --- a/oxen-rust/crates/lib/src/repositories/fetch.rs +++ b/oxen-rust/crates/lib/src/repositories/fetch.rs @@ -13,10 +13,12 @@ use crate::repositories; use futures::{StreamExt, stream}; /// # Fetch the remote branches and objects +#[tracing::instrument(skip(repo, fetch_opts), fields(repo_path = %repo.path.display()))] pub async fn fetch_all( repo: &LocalRepository, fetch_opts: &FetchOpts, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_fetch_fetch_all_total").increment(1); let remote = repo .get_remote(&fetch_opts.remote) .ok_or(OxenError::remote_not_set(fetch_opts.remote.clone()))?; @@ -83,10 +85,12 @@ pub async fn fetch_all( branches } +#[tracing::instrument(skip(repo, fetch_opts), fields(repo_path = %repo.path.display()))] pub async fn fetch_branch( repo: &LocalRepository, fetch_opts: &FetchOpts, ) -> Result { + metrics::counter!("oxen_repo_fetch_fetch_branch_total").increment(1); let remote = repo .get_remote(&fetch_opts.remote) .ok_or(OxenError::remote_not_set(fetch_opts.remote.clone()))?; @@ -100,11 +104,13 @@ pub async fn fetch_branch( Ok(branch) } +#[tracing::instrument(skip(repo, remote_repo, fetch_opts), fields(repo_path = %repo.path.display()))] pub async fn fetch_remote_branch( repo: &LocalRepository, remote_repo: &RemoteRepository, fetch_opts: &FetchOpts, ) -> Result { + metrics::counter!("oxen_repo_fetch_fetch_remote_branch_total").increment(1); println!( "Fetch remote branch: {}/{}", remote_repo.name, fetch_opts.branch diff --git a/oxen-rust/crates/lib/src/repositories/fork.rs b/oxen-rust/crates/lib/src/repositories/fork.rs index 51c82673d..6fbf15388 100644 --- a/oxen-rust/crates/lib/src/repositories/fork.rs +++ b/oxen-rust/crates/lib/src/repositories/fork.rs @@ -46,10 +46,12 @@ fn read_status(repo_path: &Path) -> Result, OxenError> { })) } +#[tracing::instrument] pub fn start_fork( original_path: PathBuf, new_path: PathBuf, ) -> Result { + metrics::counter!("oxen_repo_fork_start_fork_total").increment(1); if new_path.exists() { return Err(OxenError::basic_str(format!( "A file already exists at the destination path: {}", @@ -101,7 +103,9 @@ pub fn start_fork( }) } +#[tracing::instrument] pub fn get_fork_status(repo_path: &Path) -> Result { + metrics::counter!("oxen_repo_fork_get_fork_status_total").increment(1); let status = read_status(repo_path)?.ok_or_else(OxenError::fork_status_not_found)?; Ok(ForkStatusResponse { diff --git a/oxen-rust/crates/lib/src/repositories/init.rs b/oxen-rust/crates/lib/src/repositories/init.rs index e171139d8..8fd1dccf2 100644 --- a/oxen-rust/crates/lib/src/repositories/init.rs +++ b/oxen-rust/crates/lib/src/repositories/init.rs @@ -27,14 +27,18 @@ use crate::opts::StorageOpts; /// # Ok(()) /// # } /// ``` +#[tracing::instrument(skip(path))] pub fn init(path: impl AsRef) -> Result { + metrics::counter!("oxen_repo_init_init_total").increment(1); init_with_version(path, MIN_OXEN_VERSION) } +#[tracing::instrument(skip(path), fields(version = %version))] pub fn init_with_version( path: impl AsRef, version: MinOxenVersion, ) -> Result { + metrics::counter!("oxen_repo_init_init_with_version_total").increment(1); let path = path.as_ref(); match version { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), @@ -42,18 +46,22 @@ pub fn init_with_version( } } +#[tracing::instrument(skip(path, storage_opts))] pub async fn init_with_storage_opts( path: impl AsRef, storage_opts: Option, ) -> Result { + metrics::counter!("oxen_repo_init_init_with_storage_opts_total").increment(1); init_with_version_and_storage_opts(path, MIN_OXEN_VERSION, storage_opts).await } +#[tracing::instrument(skip(path, storage_opts), fields(version = %version))] pub async fn init_with_version_and_storage_opts( path: impl AsRef, version: MinOxenVersion, storage_opts: Option, ) -> Result { + metrics::counter!("oxen_repo_init_init_with_version_and_storage_opts_total").increment(1); let path = path.as_ref(); match version { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), diff --git a/oxen-rust/crates/lib/src/repositories/load.rs b/oxen-rust/crates/lib/src/repositories/load.rs index 23c3ce683..fc040be8c 100644 --- a/oxen-rust/crates/lib/src/repositories/load.rs +++ b/oxen-rust/crates/lib/src/repositories/load.rs @@ -6,11 +6,13 @@ use crate::constants::DEFAULT_BRANCH_NAME; use crate::repositories; use crate::util; use crate::{error::OxenError, model::LocalRepository}; +#[tracing::instrument(fields(no_working_dir))] pub async fn load( src_path: &Path, dest_path: &Path, no_working_dir: bool, ) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_load_load_total").increment(1); let done_msg: String = format!("✅ Loaded {src_path:?} to an oxen repo at {dest_path:?}"); let dest_path = if dest_path.exists() { diff --git a/oxen-rust/crates/lib/src/repositories/merge.rs b/oxen-rust/crates/lib/src/repositories/merge.rs index 1e8af75fd..f06fc3851 100644 --- a/oxen-rust/crates/lib/src/repositories/merge.rs +++ b/oxen-rust/crates/lib/src/repositories/merge.rs @@ -20,7 +20,9 @@ impl MergeCommits { } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn list_conflicts(repo: &LocalRepository) -> Result, OxenError> { + metrics::counter!("oxen_repo_merge_list_conflicts_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => { @@ -33,40 +35,48 @@ pub fn list_conflicts(repo: &LocalRepository) -> Result, Oxen } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub async fn has_conflicts( repo: &LocalRepository, base_branch: &Branch, merge_branch: &Branch, ) -> Result { + metrics::counter!("oxen_repo_merge_has_conflicts_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::merge::has_conflicts(repo, base_branch, merge_branch).await, } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn mark_conflict_as_resolved(repo: &LocalRepository, path: &Path) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_merge_mark_conflict_as_resolved_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("mark_conflict_as_resolved not supported for oxen v0.10"), _ => core::v_latest::merge::mark_conflict_as_resolved(repo, path), } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display(), base_commit_id = %base_commit.id, merge_commit_id = %merge_commit.id))] pub async fn can_merge_commits( repo: &LocalRepository, base_commit: &Commit, merge_commit: &Commit, ) -> Result { + metrics::counter!("oxen_repo_merge_can_merge_commits_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::merge::can_merge_commits(repo, base_commit, merge_commit).await, } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub async fn list_conflicts_between_branches( repo: &LocalRepository, base_branch: &Branch, merge_branch: &Branch, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_merge_list_conflicts_between_branches_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => { @@ -76,33 +86,39 @@ pub async fn list_conflicts_between_branches( } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn list_commits_between_branches( repo: &LocalRepository, base_branch: &Branch, head_branch: &Branch, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_merge_list_commits_between_branches_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::merge::list_commits_between_branches(repo, base_branch, head_branch), } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display(), base_commit_id = %base_commit.id, head_commit_id = %head_commit.id))] pub fn list_commits_between_commits( repo: &LocalRepository, base_commit: &Commit, head_commit: &Commit, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_merge_list_commits_between_commits_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::merge::list_commits_between_commits(repo, base_commit, head_commit), } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display(), base_commit_id = %base_commit.id, merge_commit_id = %merge_commit.id))] pub async fn list_conflicts_between_commits( repo: &LocalRepository, base_commit: &Commit, merge_commit: &Commit, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_merge_list_conflicts_between_commits_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => { @@ -112,44 +128,56 @@ pub async fn list_conflicts_between_commits( } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub async fn merge_into_base( repo: &LocalRepository, merge_branch: &Branch, base_branch: &Branch, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_merge_merge_into_base_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::merge::merge_into_base(repo, merge_branch, base_branch).await, } } +#[tracing::instrument(skip(repo, branch_name), fields(repo_path = %repo.path.display()))] pub async fn merge( repo: &LocalRepository, branch_name: impl AsRef, ) -> Result, OxenError> { - match repo.min_version() { + metrics::counter!("oxen_repo_merge_merge_total").increment(1); + let timer = std::time::Instant::now(); + let result = match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::merge::merge(repo, branch_name).await, - } + }; + metrics::histogram!("oxen_repo_merge_merge_duration_ms") + .record(timer.elapsed().as_millis() as f64); + result } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display(), merge_commit_id = %merge_commit.id, base_commit_id = %base_commit.id))] pub async fn merge_commit_into_base( repo: &LocalRepository, merge_commit: &Commit, base_commit: &Commit, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_merge_merge_commit_into_base_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::merge::merge_commit_into_base(repo, merge_commit, base_commit).await, } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display(), merge_commit_id = %merge_commit.id, base_commit_id = %base_commit.id))] pub async fn merge_commit_into_base_on_branch( repo: &LocalRepository, merge_commit: &Commit, base_commit: &Commit, branch: &Branch, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_merge_merge_commit_into_base_on_branch_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => { @@ -164,35 +192,43 @@ pub async fn merge_commit_into_base_on_branch( } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn has_file(repo: &LocalRepository, path: &Path) -> Result { + metrics::counter!("oxen_repo_merge_has_file_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::merge::has_file(repo, path), } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn remove_conflict_path(repo: &LocalRepository, path: &Path) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_merge_remove_conflict_path_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::merge::remove_conflict_path(repo, path), } } +#[tracing::instrument(skip(repo, branch_name), fields(repo_path = %repo.path.display()))] pub fn find_merge_commits>( repo: &LocalRepository, branch_name: S, ) -> Result { + metrics::counter!("oxen_repo_merge_find_merge_commits_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::merge::find_merge_commits(repo, branch_name), } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display(), base_commit_id = %base_commit.id, merge_commit_id = %merge_commit.id))] pub fn lowest_common_ancestor_from_commits( repo: &LocalRepository, base_commit: &Commit, merge_commit: &Commit, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_merge_lowest_common_ancestor_from_commits_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::merge::lowest_common_ancestor_from_commits( diff --git a/oxen-rust/crates/lib/src/repositories/metadata.rs b/oxen-rust/crates/lib/src/repositories/metadata.rs index 23b681b56..cef2e0794 100644 --- a/oxen-rust/crates/lib/src/repositories/metadata.rs +++ b/oxen-rust/crates/lib/src/repositories/metadata.rs @@ -21,7 +21,9 @@ pub mod text; pub mod video; /// Returns the metadata given a file path +#[tracing::instrument(skip(path))] pub fn get(path: impl AsRef) -> Result { + metrics::counter!("oxen_repo_metadata_get_total").increment(1); let path = path.as_ref(); let base_name = path.file_name().ok_or(OxenError::file_has_no_name(path))?; let size = get_file_size(path)?; @@ -47,7 +49,9 @@ pub fn get(path: impl AsRef) -> Result { } /// Returns the metadata given a file path +#[tracing::instrument(skip(path))] pub fn from_path(path: impl AsRef) -> Result { + metrics::counter!("oxen_repo_metadata_from_path_total").increment(1); let path = path.as_ref(); let base_name = path.file_name().ok_or(OxenError::file_has_no_name(path))?; let size = get_file_size(path)?; @@ -74,11 +78,13 @@ pub fn from_path(path: impl AsRef) -> Result { }) } +#[tracing::instrument(skip(_repo, node), fields(repo_path = %_repo.path.display(), commit_id = %commit.id))] pub fn from_file_node( _repo: &LocalRepository, node: &FileNode, commit: &Commit, ) -> Result { + metrics::counter!("oxen_repo_metadata_from_file_node_total").increment(1); Ok(MetadataEntry { filename: node.name().to_string(), hash: node.hash().to_string(), @@ -102,11 +108,13 @@ pub fn from_file_node( }) } +#[tracing::instrument(skip(_repo, node), fields(repo_path = %_repo.path.display(), commit_id = %commit.id))] pub fn from_dir_node( _repo: &LocalRepository, node: &DirNode, commit: &Commit, ) -> Result { + metrics::counter!("oxen_repo_metadata_from_dir_node_total").increment(1); Ok(MetadataEntry { filename: node.name().to_string(), hash: node.hash().to_string(), @@ -124,11 +132,13 @@ pub fn from_dir_node( } /// Returns metadata with latest commit information. Less efficient than get(). +#[tracing::instrument(skip(repo, entry_path, data_path), fields(repo_path = %repo.path.display()))] pub fn get_cli( repo: &LocalRepository, entry_path: impl AsRef, data_path: impl AsRef, ) -> Result { + metrics::counter!("oxen_repo_metadata_get_cli_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::metadata::get_cli(repo, entry_path, data_path), @@ -136,16 +146,20 @@ pub fn get_cli( } /// Returns the file size in bytes. +#[tracing::instrument(skip(path))] pub fn get_file_size(path: impl AsRef) -> Result { + metrics::counter!("oxen_repo_metadata_get_file_size_total").increment(1); let metadata = util::fs::metadata(path.as_ref())?; Ok(metadata.len()) } +#[tracing::instrument(skip(path))] pub fn get_file_metadata_with_extension( path: impl AsRef, data_type: &EntryDataType, extension: &str, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_metadata_get_file_metadata_with_extension_total").increment(1); match data_type { // dir should not be passed in here EntryDataType::Dir => Ok(Some(GenericMetadata::MetadataDir(MetadataDir::new(vec![])))), @@ -189,10 +203,12 @@ pub fn get_file_metadata_with_extension( } /// Returns metadata based on data_type +#[tracing::instrument(skip(path))] pub fn get_file_metadata( path: impl AsRef, data_type: &EntryDataType, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_metadata_get_file_metadata_total").increment(1); let path = path.as_ref(); get_file_metadata_with_extension(path, data_type, &util::fs::file_extension(path)) } diff --git a/oxen-rust/crates/lib/src/repositories/metadata/audio.rs b/oxen-rust/crates/lib/src/repositories/metadata/audio.rs index e3a1cea25..828ca63c4 100644 --- a/oxen-rust/crates/lib/src/repositories/metadata/audio.rs +++ b/oxen-rust/crates/lib/src/repositories/metadata/audio.rs @@ -15,7 +15,7 @@ pub fn get_metadata(path: impl AsRef) -> Result Ok(tagged_file) => { let properties = tagged_file.properties(); let duration = properties.duration(); - let seconds = duration.as_secs_f64(); + let seconds = duration.as_millis() as f64; let rate = properties.sample_rate().unwrap_or(0); let channels = properties.channels().unwrap_or(0); diff --git a/oxen-rust/crates/lib/src/repositories/metadata/video.rs b/oxen-rust/crates/lib/src/repositories/metadata/video.rs index 97608b524..adc869490 100644 --- a/oxen-rust/crates/lib/src/repositories/metadata/video.rs +++ b/oxen-rust/crates/lib/src/repositories/metadata/video.rs @@ -21,7 +21,7 @@ pub fn get_metadata(path: impl AsRef) -> Result match mp4::Mp4Reader::read_header(reader, size) { Ok(video) => { - let duration = video.duration().as_secs_f64(); + let duration = video.duration().as_millis() as f64; let video_tracks: Vec<&Mp4Track> = video .tracks() diff --git a/oxen-rust/crates/lib/src/repositories/prune.rs b/oxen-rust/crates/lib/src/repositories/prune.rs index 8f3e9d119..ac699a93a 100644 --- a/oxen-rust/crates/lib/src/repositories/prune.rs +++ b/oxen-rust/crates/lib/src/repositories/prune.rs @@ -17,7 +17,9 @@ use crate::model::{LocalRepository, RemoteRepository}; /// /// # Returns /// Statistics about the prune operation +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display(), dry_run))] pub async fn prune(repo: &LocalRepository, dry_run: bool) -> Result { + metrics::counter!("oxen_repo_prune_prune_total").increment(1); prune_impl(repo, dry_run).await } @@ -31,9 +33,11 @@ pub async fn prune(repo: &LocalRepository, dry_run: bool) -> Result Result { + metrics::counter!("oxen_repo_prune_prune_remote_total").increment(1); prune_remote_impl(remote_repo, dry_run).await } diff --git a/oxen-rust/crates/lib/src/repositories/pull.rs b/oxen-rust/crates/lib/src/repositories/pull.rs index 20d5ee4f9..86cc822e9 100644 --- a/oxen-rust/crates/lib/src/repositories/pull.rs +++ b/oxen-rust/crates/lib/src/repositories/pull.rs @@ -12,14 +12,22 @@ use crate::opts::fetch_opts::FetchOpts; /// Pull a repository's data from default branches origin/main /// Defaults defined in /// `constants::DEFAULT_REMOTE_NAME` and `constants::DEFAULT_BRANCH_NAME` +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub async fn pull(repo: &LocalRepository) -> Result<(), OxenError> { - match repo.min_version() { + metrics::counter!("oxen_repo_pull_pull_total").increment(1); + let timer = std::time::Instant::now(); + let result = match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::pull::pull(repo).await, - } + }; + metrics::histogram!("oxen_repo_pull_pull_duration_ms") + .record(timer.elapsed().as_millis() as f64); + result } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub async fn pull_all(repo: &LocalRepository) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_pull_pull_all_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::pull::pull_all(repo).await, @@ -27,10 +35,12 @@ pub async fn pull_all(repo: &LocalRepository) -> Result<(), OxenError> { } /// Pull a specific remote and branch +#[tracing::instrument(skip(repo, fetch_opts), fields(repo_path = %repo.path.display()))] pub async fn pull_remote_branch( repo: &LocalRepository, fetch_opts: &FetchOpts, ) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_pull_pull_remote_branch_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::pull::pull_remote_branch(repo, fetch_opts).await, diff --git a/oxen-rust/crates/lib/src/repositories/push.rs b/oxen-rust/crates/lib/src/repositories/push.rs index 43d8c3e88..6b2bf47d9 100644 --- a/oxen-rust/crates/lib/src/repositories/push.rs +++ b/oxen-rust/crates/lib/src/repositories/push.rs @@ -48,18 +48,26 @@ use crate::opts::PushOpts; /// # Ok(()) /// # } /// ``` +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub async fn push(repo: &LocalRepository) -> Result { - match repo.min_version() { + metrics::counter!("oxen_repo_push_push_total").increment(1); + let timer = std::time::Instant::now(); + let result = match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 is deprecated"), _ => core::v_latest::push::push(repo).await, - } + }; + metrics::histogram!("oxen_repo_push_push_duration_ms") + .record(timer.elapsed().as_millis() as f64); + result } /// Push to a specific remote branch on the default remote repository +#[tracing::instrument(skip(repo, opts), fields(repo_path = %repo.path.display()))] pub async fn push_remote_branch( repo: &LocalRepository, opts: &PushOpts, ) -> Result { + metrics::counter!("oxen_repo_push_push_remote_branch_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 is deprecated"), _ => core::v_latest::push::push_remote_branch(repo, opts).await, diff --git a/oxen-rust/crates/lib/src/repositories/restore.rs b/oxen-rust/crates/lib/src/repositories/restore.rs index db6dbe2ca..da9d91b9a 100644 --- a/oxen-rust/crates/lib/src/repositories/restore.rs +++ b/oxen-rust/crates/lib/src/repositories/restore.rs @@ -46,7 +46,9 @@ use crate::opts::RestoreOpts; /// # Ok(()) /// # } /// ``` +#[tracing::instrument(skip(repo, opts), fields(repo_path = %repo.path.display()))] pub async fn restore(repo: &LocalRepository, opts: RestoreOpts) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_restore_restore_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::restore::restore(repo, opts).await, diff --git a/oxen-rust/crates/lib/src/repositories/rm.rs b/oxen-rust/crates/lib/src/repositories/rm.rs index ce3964104..e90077892 100644 --- a/oxen-rust/crates/lib/src/repositories/rm.rs +++ b/oxen-rust/crates/lib/src/repositories/rm.rs @@ -13,7 +13,9 @@ use crate::{core, util}; use std::path::PathBuf; /// Removes the path from the index +#[tracing::instrument(skip(repo, opts), fields(repo_path = %repo.path.display()))] pub fn rm(repo: &LocalRepository, opts: &RmOpts) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_rm_rm_total").increment(1); log::debug!("Rm with opts: {opts:?}"); let path = &opts.path; diff --git a/oxen-rust/crates/lib/src/repositories/save.rs b/oxen-rust/crates/lib/src/repositories/save.rs index dc2096b62..0595aaaff 100644 --- a/oxen-rust/crates/lib/src/repositories/save.rs +++ b/oxen-rust/crates/lib/src/repositories/save.rs @@ -8,7 +8,9 @@ use std::io::Write; use crate::core; use crate::{constants::OXEN_HIDDEN_DIR, error::OxenError, model::LocalRepository, util}; +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn save(repo: &LocalRepository, dst_path: &Path) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_save_save_total").increment(1); let output_path = if !dst_path.exists() { dst_path.to_path_buf() } else { diff --git a/oxen-rust/crates/lib/src/repositories/size.rs b/oxen-rust/crates/lib/src/repositories/size.rs index 676d27300..349a0e066 100644 --- a/oxen-rust/crates/lib/src/repositories/size.rs +++ b/oxen-rust/crates/lib/src/repositories/size.rs @@ -37,7 +37,9 @@ impl fmt::Display for RepoSizeFile { } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn update_size(repo: &LocalRepository) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_size_update_size_total").increment(1); let path = repo_size_path(repo); let size = match util::fs::read_from_path(&path) { Ok(content) => match serde_json::from_str::(&content) { @@ -93,7 +95,9 @@ pub fn update_size(repo: &LocalRepository) -> Result<(), OxenError> { Ok(()) } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn get_size(repo: &LocalRepository) -> Result { + metrics::counter!("oxen_repo_size_get_size_total").increment(1); let path = repo_size_path(repo); let size = util::fs::read_from_path(&path); match size { @@ -109,6 +113,8 @@ pub fn get_size(repo: &LocalRepository) -> Result { } } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn repo_size_path(repo: &LocalRepository) -> PathBuf { + metrics::counter!("oxen_repo_size_repo_size_path_total").increment(1); util::fs::oxen_hidden_dir(&repo.path).join("repo_size.toml") } diff --git a/oxen-rust/crates/lib/src/repositories/stats.rs b/oxen-rust/crates/lib/src/repositories/stats.rs index 0bc9f6e4c..5dc3145ed 100644 --- a/oxen-rust/crates/lib/src/repositories/stats.rs +++ b/oxen-rust/crates/lib/src/repositories/stats.rs @@ -4,7 +4,9 @@ use crate::error::OxenError; use crate::model::LocalRepository; use crate::model::RepoStats; +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn get_stats(repo: &LocalRepository) -> Result { + metrics::counter!("oxen_repo_stats_get_stats_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::stats::get_stats(repo), diff --git a/oxen-rust/crates/lib/src/repositories/status.rs b/oxen-rust/crates/lib/src/repositories/status.rs index 54a4f211c..b15e8720f 100644 --- a/oxen-rust/crates/lib/src/repositories/status.rs +++ b/oxen-rust/crates/lib/src/repositories/status.rs @@ -67,27 +67,33 @@ use crate::model::{LocalRepository, StagedData}; /// # Ok(()) /// # } /// ``` +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn status(repo: &LocalRepository) -> Result { + metrics::counter!("oxen_repo_status_status_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::status::status(repo), } } +#[tracing::instrument(skip(repo, opts), fields(repo_path = %repo.path.display()))] pub fn status_from_opts( repo: &LocalRepository, opts: &StagedDataOpts, ) -> Result { + metrics::counter!("oxen_repo_status_status_from_opts_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v10 not supported"), _ => core::v_latest::status::status_from_opts(repo, opts), } } +#[tracing::instrument(skip(repo, dir), fields(repo_path = %repo.path.display()))] pub fn status_from_dir( repo: &LocalRepository, dir: impl AsRef, ) -> Result { + metrics::counter!("oxen_repo_status_status_from_dir_total").increment(1); match repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::status::status_from_dir(repo, dir), diff --git a/oxen-rust/crates/lib/src/repositories/tree.rs b/oxen-rust/crates/lib/src/repositories/tree.rs index d62a0ce01..2dfd71678 100644 --- a/oxen-rust/crates/lib/src/repositories/tree.rs +++ b/oxen-rust/crates/lib/src/repositories/tree.rs @@ -30,27 +30,39 @@ use crate::{repositories, util}; /// Otherwise it will return None /// The node will not have any children, so is fast to look up /// if you want the root with children, use `get_root_with_children`` +#[tracing::instrument(skip(repo), fields(commit_id = %commit.id))] pub fn get_root( repo: &LocalRepository, commit: &Commit, ) -> Result, OxenError> { - match repo.min_version() { + metrics::counter!("oxen_repo_tree_get_root_total").increment(1); + let timer = std::time::Instant::now(); + let result = match repo.min_version() { MinOxenVersion::V0_19_0 => CommitMerkleTreeV0_19_0::root_without_children(repo, commit), _ => CommitMerkleTreeLatest::root_without_children(repo, commit), - } + }; + metrics::histogram!("oxen_repo_tree_get_root_duration_ms") + .record(timer.elapsed().as_millis() as f64); + result } /// This will return the MerkleTreeNode with type CommitNode if the Commit exists /// Otherwise it will return None /// The node will load all children from disk, so is slower than `get_root` +#[tracing::instrument(skip(repo), fields(commit_id = %commit.id))] pub fn get_root_with_children( repo: &LocalRepository, commit: &Commit, ) -> Result, OxenError> { - match repo.min_version() { + metrics::counter!("oxen_repo_tree_get_root_with_children_total").increment(1); + let timer = std::time::Instant::now(); + let result = match repo.min_version() { MinOxenVersion::V0_19_0 => CommitMerkleTreeV0_19_0::root_with_children(repo, commit), _ => CommitMerkleTreeLatest::root_with_children(repo, commit), - } + }; + metrics::histogram!("oxen_repo_tree_get_root_with_children_duration_ms") + .record(timer.elapsed().as_millis() as f64); + result } /// This will return the MerkleTreeNode with type CommitNode if the Commit exists @@ -129,10 +141,12 @@ pub fn get_root_dir(node: &MerkleTreeNode) -> Result<&MerkleTreeNode, OxenError> Ok(root_dir) } +#[tracing::instrument(level = "debug", skip(repo), fields(hash = %hash))] pub fn get_node_by_id( repo: &LocalRepository, hash: &MerkleHash, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_tree_get_node_by_id_total").increment(1); let load_recursive = false; match repo.min_version() { MinOxenVersion::V0_19_0 => CommitMerkleTreeV0_19_0::read_node(repo, hash, load_recursive), @@ -201,11 +215,13 @@ pub fn has_path( } } +#[tracing::instrument(level = "debug", skip(repo, path), fields(commit_id = %commit.id))] pub fn get_node_by_path( repo: &LocalRepository, commit: &Commit, path: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_tree_get_node_by_path_total").increment(1); let load_recursive = false; match repo.min_version() { MinOxenVersion::V0_19_0 => { @@ -243,11 +259,13 @@ pub fn get_node_by_path_with_children( Ok(node) } +#[tracing::instrument(level = "debug", skip(repo, path), fields(commit_id = %commit.id))] pub fn get_file_by_path( repo: &LocalRepository, commit: &Commit, path: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_tree_get_file_by_path_total").increment(1); let Some(root) = get_node_by_path(repo, commit, &path)? else { return Ok(None); }; @@ -257,12 +275,14 @@ pub fn get_file_by_path( } } +#[tracing::instrument(level = "debug", skip(repo, path, dir_hashes), fields(commit_id = %commit.id))] pub fn get_dir_with_children( repo: &LocalRepository, commit: &Commit, path: impl AsRef, dir_hashes: Option<&HashMap>, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_tree_get_dir_with_children_total").increment(1); let _perf = crate::perf_guard!("tree::get_dir_with_children"); match repo.min_version() { @@ -403,7 +423,9 @@ pub fn list_nodes_from_paths( } /// List the files and folders given a directory node +#[tracing::instrument(level = "debug", skip(node))] pub fn list_files_and_folders(node: &MerkleTreeNode) -> Result, OxenError> { + metrics::counter!("oxen_repo_tree_list_files_and_folders_total").increment(1); if MerkleTreeNodeType::Dir != node.node.node_type() { return Err(OxenError::basic_str(format!( "list_files_and_folders Merkle tree node is not a directory: '{:?}'", @@ -500,10 +522,13 @@ pub fn collect_nodes_along_path( Ok(()) } +#[tracing::instrument(skip(repo, hashes), fields(num_hashes = hashes.len()))] pub fn list_missing_node_hashes( repo: &LocalRepository, hashes: &HashSet, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_tree_list_missing_node_hashes_total").increment(1); + let timer = std::time::Instant::now(); let mut results = HashSet::new(); for hash in hashes { if !node_sync_status::node_is_synced(repo, hash) { @@ -511,15 +536,20 @@ pub fn list_missing_node_hashes( } } + metrics::histogram!("oxen_repo_tree_list_missing_node_hashes_duration_ms") + .record(timer.elapsed().as_millis() as f64); Ok(results) } // TODO: Deduplicate functionality with model::MerkleTreeNode +#[tracing::instrument(skip(repo), fields(hash = %hash))] pub async fn list_missing_file_hashes( repo: &LocalRepository, hash: &MerkleHash, ) -> Result, OxenError> { - if repo.min_version() == MinOxenVersion::V0_19_0 { + metrics::counter!("oxen_repo_tree_list_missing_file_hashes_total").increment(1); + let timer = std::time::Instant::now(); + let result = if repo.min_version() == MinOxenVersion::V0_19_0 { let Some(node) = CommitMerkleTreeV0_19_0::read_depth(repo, hash, 1)? else { return Err(OxenError::basic_str(format!("Node {hash} not found"))); }; @@ -529,7 +559,10 @@ pub async fn list_missing_file_hashes( return Err(OxenError::basic_str(format!("Node {hash} not found"))); }; node.list_missing_file_hashes(repo).await - } + }; + metrics::histogram!("oxen_repo_tree_list_missing_file_hashes_duration_ms") + .record(timer.elapsed().as_millis() as f64); + result } /// Given a set of commit ids, return the hashes that are missing from the tree @@ -890,7 +923,10 @@ pub fn cp_dir_hashes_to( Ok(()) } +#[tracing::instrument(skip(repository))] pub fn compress_tree(repository: &LocalRepository) -> Result, OxenError> { + metrics::counter!("oxen_repo_tree_compress_tree_total").increment(1); + let timer = std::time::Instant::now(); let enc = GzEncoder::new(Vec::new(), Compression::fast()); let mut tar = tar::Builder::new(enc); compress_full_tree(repository, &mut tar)?; @@ -901,13 +937,18 @@ pub fn compress_tree(repository: &LocalRepository) -> Result, OxenError> log::debug!("Compressed entire tree size is {}", ByteSize::b(total_size)); + metrics::histogram!("oxen_repo_tree_compress_tree_duration_ms") + .record(timer.elapsed().as_millis() as f64); Ok(buffer) } +#[tracing::instrument(skip(repository, tar))] pub fn compress_full_tree( repository: &LocalRepository, tar: &mut tar::Builder>>, ) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_tree_compress_full_tree_total").increment(1); + let timer = std::time::Instant::now(); // This will be the subdir within the tarball, // so when we untar it, all the subdirs will be extracted to // tree/nodes/... @@ -924,6 +965,8 @@ pub fn compress_full_tree( tar.append_dir_all(&tar_subdir, nodes_dir)?; } + metrics::histogram!("oxen_repo_tree_compress_full_tree_duration_ms") + .record(timer.elapsed().as_millis() as f64); Ok(()) } @@ -1015,10 +1058,13 @@ pub fn compress_commits( Ok(buffer) } +#[tracing::instrument(skip(repository, buffer), fields(buffer_len = buffer.len()))] pub fn unpack_nodes( repository: &LocalRepository, buffer: &[u8], ) -> Result, OxenError> { + metrics::counter!("oxen_repo_tree_unpack_nodes_total").increment(1); + let timer = std::time::Instant::now(); let mut hashes: HashSet = HashSet::new(); log::debug!("Unpacking nodes from buffer..."); let decoder = GzDecoder::new(buffer); @@ -1064,16 +1110,23 @@ pub fn unpack_nodes( hashes.insert(id.parse()?); } } + metrics::histogram!("oxen_repo_tree_unpack_nodes_duration_ms") + .record(timer.elapsed().as_millis() as f64); Ok(hashes) } /// Write a node to disk +#[tracing::instrument(skip(repo, node))] pub fn write_tree(repo: &LocalRepository, node: &MerkleTreeNode) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_tree_write_tree_total").increment(1); + let timer = std::time::Instant::now(); let EMerkleTreeNode::Commit(commit_node) = &node.node else { return Err(OxenError::basic_str("Expected commit node")); }; let commit_node = CommitNode::new(repo, commit_node.get_opts())?; p_write_tree(repo, node, &commit_node)?; + metrics::histogram!("oxen_repo_tree_write_tree_duration_ms") + .record(timer.elapsed().as_millis() as f64); Ok(()) } @@ -1368,7 +1421,9 @@ pub fn get_ancestor_nodes( } // TODO: Deduplicate these +#[tracing::instrument(level = "debug", skip(repo), fields(commit_id = %commit.id))] pub fn print_tree(repo: &LocalRepository, commit: &Commit) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_tree_print_tree_total").increment(1); let tree = get_root_with_children(repo, commit)?.unwrap(); match repo.min_version() { MinOxenVersion::V0_19_0 => { diff --git a/oxen-rust/crates/lib/src/repositories/workspaces.rs b/oxen-rust/crates/lib/src/repositories/workspaces.rs index b43af979a..f0f925746 100644 --- a/oxen-rust/crates/lib/src/repositories/workspaces.rs +++ b/oxen-rust/crates/lib/src/repositories/workspaces.rs @@ -33,10 +33,12 @@ use uuid::Uuid; /// Loads a workspace from the filesystem. Must call create() first to create the workspace. /// /// Returns an None if the workspace does not exist +#[tracing::instrument(skip(repo, workspace_id), fields(repo_path = %repo.path.display()))] pub fn get( repo: &LocalRepository, workspace_id: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_workspace_get_total").increment(1); let workspace_id = workspace_id.as_ref(); let workspace_id_hash = util::hasher::hash_str_sha256(workspace_id); log::debug!("workspace::get workspace_id: {workspace_id:?} hash: {workspace_id_hash:?}"); @@ -56,10 +58,12 @@ pub fn get( } } +#[tracing::instrument(skip(repo, workspace_dir), fields(repo_path = %repo.path.display()))] pub fn get_by_dir( repo: &LocalRepository, workspace_dir: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_workspace_get_by_dir_total").increment(1); let workspace_dir = workspace_dir.as_ref(); let workspace_id = workspace_dir.file_name().unwrap().to_str().unwrap(); let config_path = Workspace::config_path_from_dir(workspace_dir); @@ -98,10 +102,12 @@ pub fn get_by_dir( })) } +#[tracing::instrument(skip(repo, workspace_name), fields(repo_path = %repo.path.display()))] pub fn get_by_name( repo: &LocalRepository, workspace_name: impl AsRef, ) -> Result, OxenError> { + metrics::counter!("oxen_repo_workspace_get_by_name_total").increment(1); let workspace_name = workspace_name.as_ref(); let workspaces = list(repo)?; for workspace in workspaces { @@ -113,15 +119,18 @@ pub fn get_by_name( } /// Creates a new workspace and saves it to the filesystem +#[tracing::instrument(skip(base_repo, workspace_id), fields(repo_path = %base_repo.path.display(), commit_id = %commit.id))] pub fn create( base_repo: &LocalRepository, commit: &Commit, workspace_id: impl AsRef, is_editable: bool, ) -> Result { + metrics::counter!("oxen_repo_workspace_create_total").increment(1); create_with_name(base_repo, commit, workspace_id, None, is_editable) } +#[tracing::instrument(skip(base_repo, workspace_id, workspace_name), fields(repo_path = %base_repo.path.display(), commit_id = %commit.id))] pub fn create_with_name( base_repo: &LocalRepository, commit: &Commit, @@ -129,6 +138,7 @@ pub fn create_with_name( workspace_name: Option, is_editable: bool, ) -> Result { + metrics::counter!("oxen_repo_workspace_create_with_name_total").increment(1); let workspace_id = workspace_id.as_ref(); let workspace_id_hash = util::hasher::hash_str_sha256(workspace_id); let workspace_dir = Workspace::workspace_dir(base_repo, &workspace_id_hash); @@ -219,10 +229,12 @@ impl Drop for TemporaryWorkspace { } /// Creates a new temporary workspace that will be deleted when the reference is dropped +#[tracing::instrument(skip(base_repo), fields(repo_path = %base_repo.path.display(), commit_id = %commit.id))] pub fn create_temporary( base_repo: &LocalRepository, commit: &Commit, ) -> Result { + metrics::counter!("oxen_repo_workspace_create_temporary_total").increment(1); let workspace_id = Uuid::new_v4().to_string(); let workspace_name = format!("temporary-{workspace_id}"); let workspace = create_with_name(base_repo, commit, workspace_id, Some(workspace_name), true)?; @@ -251,7 +263,9 @@ fn check_existing_workspace_name( Ok(()) } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn list(repo: &LocalRepository) -> Result, OxenError> { + metrics::counter!("oxen_repo_workspace_list_total").increment(1); let workspaces_dir = Workspace::workspaces_dir(repo); log::debug!("workspace::list got workspaces_dir: {workspaces_dir:?}"); if !workspaces_dir.exists() { @@ -283,10 +297,12 @@ pub fn list(repo: &LocalRepository) -> Result, OxenError> { Ok(workspaces) } +#[tracing::instrument(skip(repo, commit_id), fields(repo_path = %repo.path.display()))] pub fn get_non_editable_by_commit_id( repo: &LocalRepository, commit_id: impl AsRef, ) -> Result { + metrics::counter!("oxen_repo_workspace_get_non_editable_by_commit_id_total").increment(1); let workspaces = list(repo)?; for workspace in workspaces { if workspace.commit.id == commit_id.as_ref() && !workspace.is_editable { @@ -298,7 +314,9 @@ pub fn get_non_editable_by_commit_id( )) } +#[tracing::instrument(skip(workspace), fields(workspace_id = %workspace.id))] pub fn delete(workspace: &Workspace) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_workspace_delete_total").increment(1); let workspace_id = workspace.id.to_string(); let workspace_dir = workspace.dir(); if !workspace_dir.exists() { @@ -318,7 +336,9 @@ pub fn delete(workspace: &Workspace) -> Result<(), OxenError> { Ok(()) } +#[tracing::instrument(skip(repo), fields(repo_path = %repo.path.display()))] pub fn clear(repo: &LocalRepository) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_workspace_clear_total").increment(1); let workspaces_dir = Workspace::workspaces_dir(repo); if !workspaces_dir.exists() { return Ok(()); @@ -328,7 +348,9 @@ pub fn clear(repo: &LocalRepository) -> Result<(), OxenError> { Ok(()) } +#[tracing::instrument(skip(workspace), fields(workspace_id = %workspace.id))] pub fn update_commit(workspace: &Workspace, new_commit_id: &str) -> Result<(), OxenError> { + metrics::counter!("oxen_repo_workspace_update_commit_total").increment(1); let config_path = workspace.config_path(); if !config_path.exists() { @@ -360,21 +382,25 @@ pub fn update_commit(workspace: &Workspace, new_commit_id: &str) -> Result<(), O Ok(()) } +#[tracing::instrument(skip(workspace, new_commit, branch_name), fields(workspace_id = %workspace.id))] pub async fn commit( workspace: &Workspace, new_commit: &NewCommitBody, branch_name: impl AsRef, ) -> Result { + metrics::counter!("oxen_repo_workspace_commit_total").increment(1); match workspace.workspace_repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::workspaces::commit::commit(workspace, new_commit, branch_name).await, } } +#[tracing::instrument(skip(workspace, branch_name), fields(workspace_id = %workspace.id))] pub fn mergeability( workspace: &Workspace, branch_name: impl AsRef, ) -> Result { + metrics::counter!("oxen_repo_workspace_mergeability_total").increment(1); match workspace.workspace_repo.min_version() { MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"), _ => core::v_latest::workspaces::commit::mergeability(workspace, branch_name), @@ -392,12 +418,15 @@ fn init_workspace_repo( } } +#[tracing::instrument(skip(repo, workspace, entries), fields(repo_path = %repo.path.display(), workspace_id = %workspace.id))] pub fn populate_entries_with_workspace_data( repo: &LocalRepository, directory: &Path, workspace: &Workspace, entries: &[MetadataEntry], ) -> Result, OxenError> { + metrics::counter!("oxen_repo_workspace_populate_entries_with_workspace_data_total") + .increment(1); let workspace_changes = repositories::workspaces::status::status_from_dir(workspace, directory)?; let mut dir_entries: Vec = Vec::new(); @@ -455,11 +484,13 @@ pub fn populate_entries_with_workspace_data( Ok(dir_entries) } +#[tracing::instrument(skip(entry, workspace), fields(workspace_id = %workspace.id))] pub fn populate_entry_with_workspace_data( file_path: &Path, entry: MetadataEntry, workspace: &Workspace, ) -> Result { + metrics::counter!("oxen_repo_workspace_populate_entry_with_workspace_data_total").increment(1); let workspace_changes = repositories::workspaces::status::status_from_dir(workspace, file_path)?; let (_additions_map, other_changes_map) = build_file_status_maps_for_file(&workspace_changes); @@ -473,12 +504,14 @@ pub fn populate_entry_with_workspace_data( Ok(EMetadataEntry::WorkspaceMetadataEntry(entry)) } +#[tracing::instrument(skip(repo, workspace, resource), fields(repo_path = %repo.path.display(), workspace_id = %workspace.id))] pub fn get_added_entry( repo: &LocalRepository, file_path: &Path, workspace: &Workspace, resource: &ParsedResource, ) -> Result { + metrics::counter!("oxen_repo_workspace_get_added_entry_total").increment(1); let workspace_changes = repositories::workspaces::status::status_from_dir(workspace, file_path)?; let (additions_map, _other_changes_map) = build_file_status_maps_for_file(&workspace_changes); diff --git a/oxen-rust/crates/lib/src/test.rs b/oxen-rust/crates/lib/src/test.rs index 09881c4e5..4be8bb06d 100644 --- a/oxen-rust/crates/lib/src/test.rs +++ b/oxen-rust/crates/lib/src/test.rs @@ -74,13 +74,15 @@ pub fn repo_remote_url_from(name: &str) -> String { static ENV_LOCK: LazyLock>> = LazyLock::new(|| Arc::new(Mutex::new(false))); pub fn init_test_env() { - // check if logger is already initialized - util::logging::init_logging(); + let _tracing_guard = util::telemetry::init_tracing("oxen-test"); match ENV_LOCK.lock() { Ok(mut logging_setup) => { if !*logging_setup { - util::logging::init_logging(); + // TODO: Cleanup. This was broken in the branch pushed to #331 already when I went + // to merge in the crates rename change from main to be helpful. Forgot to + // do 'pre-commit install', maybe? PR was in draft mode. + //util::logging::init_logging(); *logging_setup = true; } diff --git a/oxen-rust/crates/lib/src/util.rs b/oxen-rust/crates/lib/src/util.rs index 34037de58..05862182e 100644 --- a/oxen-rust/crates/lib/src/util.rs +++ b/oxen-rust/crates/lib/src/util.rs @@ -14,6 +14,7 @@ pub mod perf; pub mod progress_bar; pub mod read_progress; pub mod str; +pub mod telemetry; pub use crate::util::read_progress::ReadProgress; pub use paginate::{paginate, paginate_with_total}; diff --git a/oxen-rust/crates/lib/src/util/logging.rs b/oxen-rust/crates/lib/src/util/logging.rs index 905177d07..0e9b5e9a1 100644 --- a/oxen-rust/crates/lib/src/util/logging.rs +++ b/oxen-rust/crates/lib/src/util/logging.rs @@ -1,6 +1,3 @@ -use env_logger::Env; -use std::io::Write; - #[macro_export] macro_rules! current_function { () => {{ @@ -14,55 +11,3 @@ macro_rules! current_function { .expect("Failed to find function name") }}; } - -pub fn init_logging() { - match env_logger::Builder::from_env(Env::default()) - .format(|buf, record| { - use crate::request_context::get_request_id; - - // Split string on a character and take the last part - fn take_last(s: &str, c: char) -> &str { - s.split(c).next_back().unwrap_or("") - } - - // Format the target to remove "liboxen::" prefix and replace "::" with "/" - fn format_target(target: &str) -> String { - target - .strip_prefix("liboxen::") - .unwrap_or(target) - .rsplit_once("::") - .map(|(path, _)| path.replace("::", "/")) - .unwrap_or_else(|| target.replace("::", "/")) - } - - let formatted_target = format_target(record.target()); - let file_name = take_last(record.file().unwrap_or("unknown"), '/'); - let line_number = record.line().unwrap_or(0); - - // Add request ID if available - let request_id_str = if let Some(request_id) = get_request_id() { - format!(" [request_id={request_id}]") - } else { - String::new() - }; - - writeln!( - buf, - "[{}] {} - {}/{}:{}{} {}", - record.level(), - chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%.3f"), - formatted_target, - file_name, - line_number, - request_id_str, - record.args() - ) - }) - .try_init() - { - Ok(_) => (), - Err(_) => { - // We already initialized the logger in tests - } - } -} diff --git a/oxen-rust/crates/lib/src/util/telemetry.rs b/oxen-rust/crates/lib/src/util/telemetry.rs new file mode 100644 index 000000000..9e3a9551f --- /dev/null +++ b/oxen-rust/crates/lib/src/util/telemetry.rs @@ -0,0 +1,113 @@ +use std::path::PathBuf; + +use tracing_appender::non_blocking::WorkerGuard; +use tracing_subscriber::EnvFilter; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; + +/// Initialize tracing with the `tracing-log` bridge. +/// +/// **Stderr** (always): human-readable formatted output, like `env_logger`. +/// +/// **File** (opt-in via `OXEN_LOG_FILE`): JSON-per-line output to a rolling +/// daily log file. The env var accepts: +/// - A directory path — rolling files are written there (e.g. `/var/log/oxen`) +/// - `"1"` or `"true"` — uses the default directory `~/.oxen/logs/` +/// +/// Filter level is controlled by `RUST_LOG` (default: `info`). The filter +/// applies to both stderr and file output. +/// +/// Returns `Some(WorkerGuard)` when file logging is active. The caller +/// **must** hold the guard in a named binding for the lifetime of the +/// application — dropping it flushes the non-blocking writer. When file +/// logging is not enabled, returns `None`. +pub fn init_tracing(app_name: &str) -> Option { + let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); + + // Optionally set up JSON file logging + let log_file_setting = std::env::var("OXEN_LOG_FILE").ok(); + let (json_layer, guard) = if let Some(ref value) = log_file_setting { + let log_dir = if value == "1" || value.eq_ignore_ascii_case("true") { + log_directory() + } else { + PathBuf::from(value) + }; + + std::fs::create_dir_all(&log_dir).ok(); + let file_appender = tracing_appender::rolling::daily(&log_dir, app_name); + let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); + + let layer = tracing_subscriber::fmt::layer() + .json() + .with_writer(non_blocking) + .with_target(true) + .with_thread_ids(true) + .with_file(true) + .with_line_number(true); + + (Some(layer), Some(guard)) + } else { + (None, None) + }; + + // Always: human-readable stderr output + let stderr_layer = tracing_subscriber::fmt::layer() + .with_writer(std::io::stderr) + .with_target(true); + + // .try_init() also installs the tracing-log bridge (forwarding log::* + // calls into tracing) when the `tracing-log` feature is enabled. + if let Err(e) = tracing_subscriber::registry() + .with(env_filter) + .with(json_layer) + .with(stderr_layer) + .try_init() + { + eprintln!("warning: failed to initialize tracing: {e}"); + } + + guard +} + +/// Install the Prometheus metrics exporter, listening on the given port. +/// +/// Metrics are available at `http://0.0.0.0:{port}/metrics`. +pub fn init_metrics_prometheus(port: u16) { + let builder = metrics_exporter_prometheus::PrometheusBuilder::new(); + if let Err(e) = builder.with_http_listener(([0, 0, 0, 0], port)).install() { + eprintln!("warning: failed to install Prometheus metrics exporter on port {port}: {e}"); + } +} + +/// Install a no-op metrics recorder (for CLI or when metrics are disabled). +pub fn init_metrics_noop() { + // metrics crate uses a global recorder; if none is installed counters/histograms + // are silently discarded. We don't need to install anything — the default + // behaviour when no recorder is set is to no-op. +} + +fn log_directory() -> PathBuf { + dirs::home_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join(".oxen") + .join("logs") +} + +/// Record an error metric. Use at every `return Err(...)` site. +#[macro_export] +macro_rules! oxen_err_metric { + ($module:expr, $error:expr) => { + metrics::counter!("oxen_errors_total", "module" => $module, "error" => $error).increment(1); + }; +} + +/// Record an operation counter. +#[macro_export] +macro_rules! oxen_counter { + ($name:expr) => { + metrics::counter!($name).increment(1); + }; + ($name:expr, $($key:expr => $value:expr),+) => { + metrics::counter!($name, $($key => $value),+).increment(1); + }; +} diff --git a/oxen-rust/crates/server/Cargo.toml b/oxen-rust/crates/server/Cargo.toml index 4c30e8b12..a9ff1ae11 100644 --- a/oxen-rust/crates/server/Cargo.toml +++ b/oxen-rust/crates/server/Cargo.toml @@ -37,6 +37,8 @@ jsonwebtoken = { workspace = true } liboxen = { path = "../lib" } log = { workspace = true } lru = { workspace = true } +metrics = { workspace = true } +metrics-exporter-prometheus = { workspace = true } mime = { workspace = true } os_path = { workspace = true } percent-encoding = { workspace = true } @@ -49,6 +51,10 @@ tar = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } tokio-util = { workspace = true } +tracing = { workspace = true } +tracing-appender = { workspace = true } +tracing-log = { workspace = true } +tracing-subscriber = { workspace = true } url = { workspace = true } utoipa = { workspace = true } utoipa-swagger-ui = { workspace = true } diff --git a/oxen-rust/crates/server/src/controllers/action.rs b/oxen-rust/crates/server/src/controllers/action.rs index 67b3e6398..2531608dc 100644 --- a/oxen-rust/crates/server/src/controllers/action.rs +++ b/oxen-rust/crates/server/src/controllers/action.rs @@ -10,7 +10,9 @@ struct ActionResponse { state: String, } +#[tracing::instrument(skip_all)] pub async fn completed(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_action_completed_total").increment(1); let action = path_param(&req, "action")?; log::debug!("{action} action completed"); let resp = ActionResponse { @@ -21,7 +23,9 @@ pub async fn completed(req: HttpRequest) -> actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_action_started_total").increment(1); let action = path_param(&req, "action")?; let resp = ActionResponse { action: action.to_string(), diff --git a/oxen-rust/crates/server/src/controllers/branches.rs b/oxen-rust/crates/server/src/controllers/branches.rs index 9f13d5c7a..44effb5b7 100644 --- a/oxen-rust/crates/server/src/controllers/branches.rs +++ b/oxen-rust/crates/server/src/controllers/branches.rs @@ -18,6 +18,7 @@ use liboxen::view::{ use liboxen::{constants, repositories}; /// List all branches +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/branches", @@ -66,6 +67,7 @@ use liboxen::{constants, repositories}; ) )] pub async fn index(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_branches_index_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -81,6 +83,7 @@ pub async fn index(req: HttpRequest) -> actix_web::Result actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_branches_show_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -117,6 +121,7 @@ pub async fn show(req: HttpRequest) -> actix_web::Result actix_web::Result Result { + metrics::counter!("oxen_server_branches_create_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -204,6 +210,7 @@ fn create_from_commit( } /// Delete a branch +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( delete, path = "/api/repos/{namespace}/{repo_name}/branches/{branch_name}", @@ -220,6 +227,7 @@ fn create_from_commit( ) )] pub async fn delete(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_branches_delete_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -237,6 +245,7 @@ pub async fn delete(req: HttpRequest) -> actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_branches_update_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -282,6 +292,7 @@ pub async fn update( } /// Merge a commit into a branch +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( post, path = "/api/repos/{namespace}/{repo_name}/branches/{branch_name}/merge", @@ -310,6 +321,8 @@ pub async fn maybe_create_merge( req: HttpRequest, body: String, ) -> actix_web::Result { + metrics::counter!("oxen_server_branches_maybe_create_merge_total").increment(1); + let timer = std::time::Instant::now(); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -339,6 +352,8 @@ pub async fn maybe_create_merge( .await?; // Return what will become the new head of the repo after push is complete. + metrics::histogram!("oxen_server_branches_maybe_create_merge_duration_ms") + .record(timer.elapsed().as_millis() as f64); if let Some(merge_commit) = maybe_merge_commit { log::debug!("returning merge commit {merge_commit:?}"); // Update branch head @@ -358,6 +373,7 @@ pub async fn maybe_create_merge( } /// Get all versions of a file on a branch +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/branches/{branch_name}/versions/{path}", @@ -379,6 +395,7 @@ pub async fn list_entry_versions( req: HttpRequest, query: web::Query, ) -> Result { + metrics::counter!("oxen_server_branches_list_entry_versions_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; diff --git a/oxen-rust/crates/server/src/controllers/commits.rs b/oxen-rust/crates/server/src/controllers/commits.rs index 3222302dc..9e9ddd8ac 100644 --- a/oxen-rust/crates/server/src/controllers/commits.rs +++ b/oxen-rust/crates/server/src/controllers/commits.rs @@ -71,6 +71,7 @@ pub struct ListMissingFilesQuery { } /// List commits +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/commits", @@ -86,6 +87,7 @@ pub struct ListMissingFilesQuery { ) )] pub async fn index(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_commits_index_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -96,6 +98,7 @@ pub async fn index(req: HttpRequest) -> actix_web::Result, ) -> Result { + metrics::counter!("oxen_server_commits_history_total").increment(1); let _perf = perf_guard!("commits::history_endpoint"); let _perf_parse = perf_guard!("commits::history_parse_params"); @@ -186,6 +190,7 @@ pub async fn history( } /// List all commits +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/commits/all", @@ -205,6 +210,7 @@ pub async fn list_all( req: HttpRequest, query: web::Query, ) -> actix_web::Result { + metrics::counter!("oxen_server_commits_list_all_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -220,6 +226,7 @@ pub async fn list_all( } /// List missing commits +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/commits/missing", @@ -246,6 +253,7 @@ pub async fn list_missing( req: HttpRequest, body: String, ) -> actix_web::Result { + metrics::counter!("oxen_server_commits_list_missing_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -276,6 +284,7 @@ pub async fn list_missing( } /// List missing files from commits +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/commits/missing_files", @@ -295,6 +304,7 @@ pub async fn list_missing_files( req: HttpRequest, query: web::Query, ) -> actix_web::Result { + metrics::counter!("oxen_server_commits_list_missing_files_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -323,6 +333,7 @@ pub async fn list_missing_files( } /// Mark commits as synced +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( post, path = "/api/repos/{namespace}/{repo_name}/commits/synced", @@ -348,6 +359,7 @@ pub async fn mark_commits_as_synced( req: HttpRequest, mut body: web::Payload, ) -> actix_web::Result { + metrics::counter!("oxen_server_commits_mark_commits_as_synced_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -377,6 +389,7 @@ pub async fn mark_commits_as_synced( } /// Get commit +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/commits/{commit_id}", @@ -393,6 +406,7 @@ pub async fn mark_commits_as_synced( ) )] pub async fn show(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_commits_show_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -408,6 +422,7 @@ pub async fn show(req: HttpRequest) -> actix_web::Result actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_commits_parents_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -439,6 +455,7 @@ pub async fn parents(req: HttpRequest) -> actix_web::Result actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_commits_download_commits_db_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -488,6 +506,7 @@ fn compress_commits_db(repository: &LocalRepository) -> Result, OxenErro } /// Download dir hashes DB +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/commits/{base_head}/dir_hashes_db", @@ -507,6 +526,7 @@ fn compress_commits_db(repository: &LocalRepository) -> Result, OxenErro pub async fn download_dir_hashes_db( req: HttpRequest, ) -> actix_web::Result { + metrics::counter!("oxen_server_commits_download_dir_hashes_db_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -538,6 +558,7 @@ pub async fn download_dir_hashes_db( } /// Download commit entries DB +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/commits/{commit_or_branch}/commit_entries_db", @@ -556,6 +577,7 @@ pub async fn download_dir_hashes_db( pub async fn download_commit_entries_db( req: HttpRequest, ) -> actix_web::Result { + metrics::counter!("oxen_server_commits_download_commit_entries_db_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -650,6 +672,7 @@ fn compress_commit(repository: &LocalRepository, commit: &Commit) -> Result actix_web::Result { + metrics::counter!("oxen_server_commits_create_total").increment(1); log::debug!("Got commit data: {body}"); let app_data = app_data(&req)?; @@ -722,6 +746,7 @@ pub async fn create( } /// Upload data chunk +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( post, path = "/api/repos/{namespace}/{repo_name}/commits/upload_chunk", @@ -746,6 +771,7 @@ pub async fn upload_chunk( mut chunk: web::Payload, // the chunk of the file body, query: web::Query, // gives the file ) -> Result { + metrics::counter!("oxen_server_commits_upload_chunk_total").increment(1); log::debug!("in upload_chunk controller"); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -1000,6 +1026,7 @@ async fn unpack_compressed_data( } /// Upload commits DB +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( post, path = "/api/repos/{namespace}/{repo_name}/commits/upload", @@ -1022,6 +1049,8 @@ pub async fn upload( req: HttpRequest, mut body: web::Payload, // the actual file body ) -> Result { + metrics::counter!("oxen_server_commits_upload_total").increment(1); + let timer = std::time::Instant::now(); log::debug!("in regular upload controller"); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -1055,10 +1084,13 @@ pub async fn upload( unpack_entry_tarball_async(&repo, &bytes).await?; // }); + metrics::histogram!("oxen_server_commits_upload_duration_ms") + .record(timer.elapsed().as_millis() as f64); Ok(HttpResponse::Ok().json(StatusMessage::resource_created())) } /// Notify upload complete +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( post, path = "/api/repos/{namespace}/{repo_name}/commits/{commit_id}/complete", @@ -1075,6 +1107,7 @@ pub async fn upload( ) )] pub async fn complete(req: HttpRequest) -> Result { + metrics::counter!("oxen_server_commits_complete_total").increment(1); let app_data = req.app_data::().unwrap(); // name to the repo, should be in url path so okay to unwrap let namespace: &str = req.match_info().get("namespace").unwrap(); @@ -1114,6 +1147,7 @@ pub async fn complete(req: HttpRequest) -> Result { } /// Upload commit tree +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( post, path = "/api/repos/{namespace}/{repo_name}/commits/{commit_id}/upload_tree", @@ -1137,6 +1171,8 @@ pub async fn upload_tree( req: HttpRequest, mut body: web::Payload, ) -> Result { + metrics::counter!("oxen_server_commits_upload_tree_total").increment(1); + let timer = std::time::Instant::now(); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -1166,6 +1202,8 @@ pub async fn upload_tree( unpack_tree_tarball(&tmp_dir, &bytes).await?; + metrics::histogram!("oxen_server_commits_upload_tree_duration_ms") + .record(timer.elapsed().as_millis() as f64); Ok(HttpResponse::Ok().json(CommitResponse { status: StatusMessage::resource_found(), commit: server_head_commit.to_owned(), @@ -1173,6 +1211,7 @@ pub async fn upload_tree( } /// Get root commit +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/commits/root", @@ -1187,6 +1226,7 @@ pub async fn upload_tree( ) )] pub async fn root_commit(req: HttpRequest) -> Result { + metrics::counter!("oxen_server_commits_root_commit_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; diff --git a/oxen-rust/crates/server/src/controllers/data_frames.rs b/oxen-rust/crates/server/src/controllers/data_frames.rs index 998de0613..cd656e6b5 100644 --- a/oxen-rust/crates/server/src/controllers/data_frames.rs +++ b/oxen-rust/crates/server/src/controllers/data_frames.rs @@ -22,6 +22,7 @@ use utoipa; use uuid::Uuid; /// Get data frame slice +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/data_frames/{resource}", @@ -42,6 +43,7 @@ pub async fn get( req: HttpRequest, query: web::Query, ) -> actix_web::Result { + metrics::counter!("oxen_server_data_frames_get_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -123,6 +125,7 @@ pub async fn get( } /// Start data frame indexing +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( post, path = "/api/repos/{namespace}/{repo_name}/data_frames/{resource}/index", @@ -140,6 +143,7 @@ pub async fn get( ) )] pub async fn index(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_data_frames_index_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -176,6 +180,7 @@ pub async fn index(req: HttpRequest) -> actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_data_frames_from_directory_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; diff --git a/oxen-rust/crates/server/src/controllers/diff.rs b/oxen-rust/crates/server/src/controllers/diff.rs index 74180081a..847b0b442 100644 --- a/oxen-rust/crates/server/src/controllers/diff.rs +++ b/oxen-rust/crates/server/src/controllers/diff.rs @@ -36,6 +36,7 @@ use crate::params::{ }; /// List commits between two revisions +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/compare/{base_head}/commits", @@ -57,6 +58,7 @@ pub async fn commits( req: HttpRequest, query: web::Query, ) -> actix_web::Result { + metrics::counter!("oxen_server_diff_commits_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -94,6 +96,7 @@ pub async fn commits( } /// List file and directory entries changed between base and head +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/compare/{base_head}/entries", @@ -116,6 +119,7 @@ pub async fn entries( req: HttpRequest, query: web::Query, ) -> actix_web::Result { + metrics::counter!("oxen_server_diff_entries_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -179,6 +183,7 @@ pub async fn entries( } /// Get diff tree +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/compare/{base_head}/tree", @@ -195,6 +200,7 @@ pub async fn entries( ) )] pub async fn dir_tree(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_diff_dir_tree_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -225,6 +231,7 @@ pub async fn dir_tree(req: HttpRequest) -> actix_web::Result, ) -> actix_web::Result { + metrics::counter!("oxen_server_diff_dir_entries_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -310,6 +318,7 @@ pub async fn dir_entries( } /// Get file diff +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/compare/{base_head}/file/{resource}", @@ -332,6 +341,7 @@ pub async fn file( req: HttpRequest, query: web::Query, ) -> actix_web::Result { + metrics::counter!("oxen_server_diff_file_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -380,6 +390,7 @@ pub async fn file( } /// Create a tabular data frame diff +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( post, path = "/api/repos/{namespace}/{repo_name}/compare/data_frames", @@ -404,6 +415,7 @@ pub async fn create_df_diff( _query: web::Query, body: String, ) -> actix_web::Result { + metrics::counter!("oxen_server_diff_create_df_diff_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -487,6 +499,7 @@ pub async fn create_df_diff( } /// Update tabular data frame diff +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( put, path = "/api/repos/{namespace}/{repo_name}/compare/data_frames/{compare_id}", @@ -511,6 +524,7 @@ pub async fn update_df_diff( req: HttpRequest, body: String, ) -> actix_web::Result { + metrics::counter!("oxen_server_diff_update_df_diff_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -596,6 +610,7 @@ pub async fn update_df_diff( } /// Get a cached tabular data frame diff +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/compare/data_frames/{compare_id}", @@ -620,6 +635,7 @@ pub async fn get_df_diff( req: HttpRequest, body: String, ) -> actix_web::Result { + metrics::counter!("oxen_server_diff_get_df_diff_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -685,6 +701,7 @@ pub async fn get_df_diff( } /// Delete DF Diff +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( delete, path = "/api/repos/{namespace}/{repo_name}/compare/data_frames/{compare_id}", @@ -701,6 +718,7 @@ pub async fn get_df_diff( ) )] pub async fn delete_df_diff(req: HttpRequest) -> Result { + metrics::counter!("oxen_server_diff_delete_df_diff_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -713,6 +731,7 @@ pub async fn delete_df_diff(req: HttpRequest) -> Result, ) -> Result { + metrics::counter!("oxen_server_diff_get_derived_df_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; diff --git a/oxen-rust/crates/server/src/controllers/dir.rs b/oxen-rust/crates/server/src/controllers/dir.rs index 4b1453d1e..6da39a889 100644 --- a/oxen-rust/crates/server/src/controllers/dir.rs +++ b/oxen-rust/crates/server/src/controllers/dir.rs @@ -12,6 +12,7 @@ use actix_web::{HttpRequest, HttpResponse, web}; use utoipa; /// List directory contents +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/dir/{resource}", @@ -32,6 +33,7 @@ pub async fn get( req: HttpRequest, query: web::Query, ) -> actix_web::Result { + metrics::counter!("oxen_server_dir_get_total").increment(1); let _perf = perf_guard!("dir::get_endpoint"); let _perf_parse = perf_guard!("dir::get_parse_params"); diff --git a/oxen-rust/crates/server/src/controllers/entries.rs b/oxen-rust/crates/server/src/controllers/entries.rs index e84a02998..235703b13 100644 --- a/oxen-rust/crates/server/src/controllers/entries.rs +++ b/oxen-rust/crates/server/src/controllers/entries.rs @@ -26,10 +26,12 @@ pub struct ChunkQuery { } // Deprecated. Only kept to support older clients before v0.37.2 +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn download_data_from_version_paths( req: HttpRequest, mut body: web::Payload, ) -> actix_web::Result { + metrics::counter!("oxen_server_entries_download_data_from_version_paths_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -95,10 +97,12 @@ pub async fn download_data_from_version_paths( } /// Download a chunk of a larger file +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn download_chunk( req: HttpRequest, query: web::Query, ) -> actix_web::Result { + metrics::counter!("oxen_server_entries_download_chunk_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -127,10 +131,12 @@ pub async fn download_chunk( Ok(HttpResponse::Ok().body(chunk)) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn list_tabular( req: HttpRequest, query: web::Query, ) -> Result { + metrics::counter!("oxen_server_entries_list_tabular_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; diff --git a/oxen-rust/crates/server/src/controllers/export.rs b/oxen-rust/crates/server/src/controllers/export.rs index b7287cdb9..ba6a02338 100644 --- a/oxen-rust/crates/server/src/controllers/export.rs +++ b/oxen-rust/crates/server/src/controllers/export.rs @@ -10,6 +10,7 @@ use liboxen::view::FileWithHash; use liboxen::{constants, repositories}; /// Export resource as a zip +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/export/download/{resource}", @@ -27,6 +28,8 @@ use liboxen::{constants, repositories}; ) )] pub async fn download_zip(req: HttpRequest) -> Result { + metrics::counter!("oxen_server_export_download_zip_total").increment(1); + let timer = std::time::Instant::now(); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -75,5 +78,7 @@ pub async fn download_zip(req: HttpRequest) -> Result, ) -> Result { + metrics::counter!("oxen_server_fork_fork_total").increment(1); log::debug!("Forking repository with request: {req:?}"); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -65,6 +67,7 @@ pub async fn fork( } /// Fork Status +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/fork/status", @@ -80,6 +83,7 @@ pub async fn fork( ) )] pub async fn get_status(req: HttpRequest) -> Result { + metrics::counter!("oxen_server_fork_get_status_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; diff --git a/oxen-rust/crates/server/src/controllers/health.rs b/oxen-rust/crates/server/src/controllers/health.rs index 304ecadee..6f32ed611 100644 --- a/oxen-rust/crates/server/src/controllers/health.rs +++ b/oxen-rust/crates/server/src/controllers/health.rs @@ -4,7 +4,9 @@ use actix_web::{HttpRequest, HttpResponse}; use liboxen::util; use liboxen::view::{HealthResponse, StatusMessage}; +#[tracing::instrument(skip_all)] pub async fn index(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_health_index_total").increment(1); let app_data = app_data(&req)?; match util::fs::disk_usage_for_path(&app_data.path) { Ok(disk_usage) => { diff --git a/oxen-rust/crates/server/src/controllers/import.rs b/oxen-rust/crates/server/src/controllers/import.rs index aae3a5c27..ed8a201b8 100644 --- a/oxen-rust/crates/server/src/controllers/import.rs +++ b/oxen-rust/crates/server/src/controllers/import.rs @@ -60,6 +60,7 @@ pub struct ImportFileBody { } /// Import file from URL +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( post, path = "/api/repos/{namespace}/{repo_name}/import/{resource}", @@ -89,6 +90,8 @@ pub async fn import( req: HttpRequest, body: web::Json, ) -> Result { + metrics::counter!("oxen_server_import_import_total").increment(1); + let timer = std::time::Instant::now(); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -189,6 +192,8 @@ pub async fn import( let commit = repositories::workspaces::commit(&workspace, &commit_body, branch.name).await?; log::debug!("workspace::commit ✅ success! commit {commit:?}"); + metrics::histogram!("oxen_server_import_import_duration_ms") + .record(timer.elapsed().as_millis() as f64); Ok(HttpResponse::Ok().json(CommitResponse { status: StatusMessage::resource_created(), commit, @@ -196,6 +201,7 @@ pub async fn import( } /// Upload zip archive +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( post, path = "/api/repos/{namespace}/{repo_name}/import/upload/{resource}", @@ -219,6 +225,8 @@ pub async fn upload_zip( req: HttpRequest, payload: Multipart, ) -> actix_web::Result { + metrics::counter!("oxen_server_import_upload_zip_total").increment(1); + let timer = std::time::Instant::now(); log::debug!("file::upload_zip path {:?}", req.path()); let app_data = app_data(&req)?; @@ -276,6 +284,8 @@ pub async fn upload_zip( &branch, ) .await?; + metrics::histogram!("oxen_server_import_upload_zip_duration_ms") + .record(timer.elapsed().as_millis() as f64); Ok(HttpResponse::Ok().json(CommitResponse { status: StatusMessage::resource_created(), commit, diff --git a/oxen-rust/crates/server/src/controllers/merger.rs b/oxen-rust/crates/server/src/controllers/merger.rs index 9f35bc628..6b3107770 100644 --- a/oxen-rust/crates/server/src/controllers/merger.rs +++ b/oxen-rust/crates/server/src/controllers/merger.rs @@ -12,6 +12,7 @@ use liboxen::view::merge::{ }; /// Check if branches are mergeable +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/merge/{base_head}", @@ -28,6 +29,7 @@ use liboxen::view::merge::{ ) )] pub async fn show(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_merger_show_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -70,6 +72,7 @@ pub async fn show(req: HttpRequest) -> actix_web::Result actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_merger_merge_total").increment(1); + let timer = std::time::Instant::now(); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -108,7 +113,10 @@ pub async fn merge(req: HttpRequest) -> actix_web::Result { // If the merge was successful, update the branch repositories::branches::update(&repo, &base_branch.name, &merge_commit.id)?; diff --git a/oxen-rust/crates/server/src/controllers/metadata.rs b/oxen-rust/crates/server/src/controllers/metadata.rs index c42ba5e90..340d173a6 100644 --- a/oxen-rust/crates/server/src/controllers/metadata.rs +++ b/oxen-rust/crates/server/src/controllers/metadata.rs @@ -13,6 +13,7 @@ use actix_web::{HttpRequest, HttpResponse}; use utoipa; /// Get file metadata +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/metadata/{resource}", @@ -30,6 +31,7 @@ use utoipa; ) )] pub async fn file(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_metadata_file_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -100,6 +102,7 @@ pub async fn file(req: HttpRequest) -> actix_web::Result actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_metadata_update_metadata_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; diff --git a/oxen-rust/crates/server/src/controllers/migrations.rs b/oxen-rust/crates/server/src/controllers/migrations.rs index b5fee8dd3..3db9f1020 100644 --- a/oxen-rust/crates/server/src/controllers/migrations.rs +++ b/oxen-rust/crates/server/src/controllers/migrations.rs @@ -9,7 +9,9 @@ use crate::{ params::{app_data, path_param}, }; +#[tracing::instrument(skip_all)] pub async fn list_unmigrated(req: HttpRequest) -> Result { + metrics::counter!("oxen_server_migrations_list_unmigrated_total").increment(1); log::debug!("in the list_unmigrated controller"); let app_data = app_data(&req)?; let migration_tstamp = path_param(&req, "migration_tstamp")?; diff --git a/oxen-rust/crates/server/src/controllers/namespaces.rs b/oxen-rust/crates/server/src/controllers/namespaces.rs index 656d500e0..d80c12fbf 100644 --- a/oxen-rust/crates/server/src/controllers/namespaces.rs +++ b/oxen-rust/crates/server/src/controllers/namespaces.rs @@ -8,6 +8,7 @@ use actix_web::{HttpRequest, HttpResponse, Result}; use utoipa; /// List namespaces +#[tracing::instrument(skip_all)] #[utoipa::path( get, path = "/api/namespaces", @@ -18,6 +19,7 @@ use utoipa; ) )] pub async fn index(req: HttpRequest) -> Result { + metrics::counter!("oxen_server_namespaces_index_total").increment(1); let app_data = app_data(&req)?; let namespaces: Vec = namespaces::list(&app_data.path) @@ -34,6 +36,7 @@ pub async fn index(req: HttpRequest) -> Result { } /// Get namespace +#[tracing::instrument(skip_all)] #[utoipa::path( get, path = "/api/namespaces/{namespace}", @@ -49,6 +52,7 @@ pub async fn index(req: HttpRequest) -> Result { ) )] pub async fn show(req: HttpRequest) -> Result { + metrics::counter!("oxen_server_namespaces_show_total").increment(1); let app_data = app_data(&req)?; let namespace: Option<&str> = req.match_info().get("namespace"); diff --git a/oxen-rust/crates/server/src/controllers/not_found.rs b/oxen-rust/crates/server/src/controllers/not_found.rs index 7cdf88871..ffa3b919e 100644 --- a/oxen-rust/crates/server/src/controllers/not_found.rs +++ b/oxen-rust/crates/server/src/controllers/not_found.rs @@ -1,6 +1,8 @@ use actix_web::{HttpRequest, HttpResponse}; use liboxen::view::StatusMessage; +#[tracing::instrument(skip_all)] pub async fn index(_req: HttpRequest) -> HttpResponse { + metrics::counter!("oxen_server_not_found_index_total").increment(1); HttpResponse::NotFound().json(StatusMessage::resource_not_found()) } diff --git a/oxen-rust/crates/server/src/controllers/oxen_version.rs b/oxen-rust/crates/server/src/controllers/oxen_version.rs index db248bfcf..9bb4ad7c5 100644 --- a/oxen-rust/crates/server/src/controllers/oxen_version.rs +++ b/oxen-rust/crates/server/src/controllers/oxen_version.rs @@ -7,6 +7,7 @@ use liboxen::view::oxen_version::OxenVersionResponse; use serde::Serialize; /// Check Oxen server status +#[tracing::instrument(skip_all)] #[utoipa::path( get, path = "/api/version", @@ -17,11 +18,14 @@ use serde::Serialize; ) )] pub async fn index(_req: HttpRequest) -> HttpResponse { + metrics::counter!("oxen_server_oxen_version_index_total").increment(1); let response = StatusMessage::resource_found(); HttpResponse::Ok().json(response) } +#[tracing::instrument(skip_all)] pub async fn min_version(_req: HttpRequest) -> HttpResponse { + metrics::counter!("oxen_server_oxen_version_min_version_total").increment(1); let response = OxenVersionResponse { status: StatusMessage::resource_found(), version: MIN_OXEN_VERSION.to_string(), @@ -36,7 +40,9 @@ struct ResolveResponse { pub repository_api_url: String, } +#[tracing::instrument(skip_all)] pub async fn resolve(req: HttpRequest) -> HttpResponse { + metrics::counter!("oxen_server_oxen_version_resolve_total").increment(1); let app_data = app_data(&req).unwrap(); let namespace: Option<&str> = req.match_info().get("namespace"); diff --git a/oxen-rust/crates/server/src/controllers/prune.rs b/oxen-rust/crates/server/src/controllers/prune.rs index 8188eb9d5..f5136e21f 100644 --- a/oxen-rust/crates/server/src/controllers/prune.rs +++ b/oxen-rust/crates/server/src/controllers/prune.rs @@ -35,10 +35,13 @@ pub struct PruneResponse { /// POST /prune /// Trigger a prune operation on the repository +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn prune( req: HttpRequest, body: web::Json, ) -> actix_web::Result { + metrics::counter!("oxen_server_prune_prune_total").increment(1); + let timer = std::time::Instant::now(); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -51,7 +54,10 @@ pub async fn prune( log::info!("Prune requested for {namespace}/{repo_name} (dry_run: {dry_run})"); // Run the prune operation - match repositories::prune::prune(&repository, dry_run).await { + let result = repositories::prune::prune(&repository, dry_run).await; + metrics::histogram!("oxen_server_prune_prune_duration_ms") + .record(timer.elapsed().as_millis() as f64); + match result { Ok(stats) => { let status_message = if dry_run { "Prune dry-run completed successfully. No files were deleted.".to_string() diff --git a/oxen-rust/crates/server/src/controllers/repositories.rs b/oxen-rust/crates/server/src/controllers/repositories.rs index 2358eacb8..14bccaaec 100644 --- a/oxen-rust/crates/server/src/controllers/repositories.rs +++ b/oxen-rust/crates/server/src/controllers/repositories.rs @@ -28,6 +28,7 @@ use std::path::PathBuf; use utoipa; /// List repositories +#[tracing::instrument(skip_all, fields(namespace))] #[utoipa::path( get, path = "/api/repos/{namespace}", @@ -42,6 +43,7 @@ use utoipa; ) )] pub async fn index(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_repositories_index_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -63,6 +65,7 @@ pub async fn index(req: HttpRequest) -> actix_web::Result actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_repositories_show_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -119,6 +123,7 @@ pub async fn show(req: HttpRequest) -> actix_web::Result actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_repositories_stats_total").increment(1); let app_data = app_data(&req)?; let namespace: Option<&str> = req.match_info().get("namespace"); @@ -178,6 +184,7 @@ pub async fn stats(req: HttpRequest) -> actix_web::Result actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_repositories_update_size_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -204,6 +212,7 @@ pub async fn update_size(req: HttpRequest) -> actix_web::Result actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_repositories_get_size_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -229,6 +239,7 @@ pub async fn get_size(req: HttpRequest) -> actix_web::Result Result { + metrics::counter!("oxen_server_repositories_create_total").increment(1); let app_data = app_data(&req)?; if let Some(content_type) = req.headers().get("Content-Type") { @@ -490,6 +502,7 @@ async fn handle_multipart_creation( } /// Delete repository +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( delete, path = "/api/repos/{namespace}/{repo_name}", @@ -505,6 +518,7 @@ async fn handle_multipart_creation( ) )] pub async fn delete(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_repositories_delete_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -523,6 +537,7 @@ pub async fn delete(req: HttpRequest) -> actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_repositories_transfer_namespace_total").increment(1); let app_data = app_data(&req)?; // Parse body let from_namespace = path_param(&req, "namespace")?; diff --git a/oxen-rust/crates/server/src/controllers/revisions.rs b/oxen-rust/crates/server/src/controllers/revisions.rs index a37a1fa8a..c2e77dfdf 100644 --- a/oxen-rust/crates/server/src/controllers/revisions.rs +++ b/oxen-rust/crates/server/src/controllers/revisions.rs @@ -10,6 +10,7 @@ use log; use utoipa; /// Get the latest revision of a resource +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/revisions/{resource}", @@ -27,6 +28,7 @@ use utoipa; ) )] pub async fn get(req: HttpRequest) -> Result { + metrics::counter!("oxen_server_revisions_get_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; diff --git a/oxen-rust/crates/server/src/controllers/schemas.rs b/oxen-rust/crates/server/src/controllers/schemas.rs index eb0a49aff..fbfee3bed 100644 --- a/oxen-rust/crates/server/src/controllers/schemas.rs +++ b/oxen-rust/crates/server/src/controllers/schemas.rs @@ -12,7 +12,9 @@ use liboxen::error::OxenError; use liboxen::view::entries::ResourceVersion; use liboxen::view::{ListSchemaResponse, StatusMessage}; +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn list_or_get(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_schemas_list_or_get_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; diff --git a/oxen-rust/crates/server/src/controllers/tree.rs b/oxen-rust/crates/server/src/controllers/tree.rs index 23cf8ea7d..24a889d41 100644 --- a/oxen-rust/crates/server/src/controllers/tree.rs +++ b/oxen-rust/crates/server/src/controllers/tree.rs @@ -24,7 +24,9 @@ use crate::params::TreeDepthQuery; use crate::params::parse_resource; use crate::params::{app_data, path_param}; +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn get_node_by_id(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_tree_get_node_by_id_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -37,10 +39,12 @@ pub async fn get_node_by_id(req: HttpRequest) -> actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_tree_list_missing_node_hashes_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -67,11 +71,13 @@ pub async fn list_missing_node_hashes( })) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn list_missing_file_hashes_from_commits( req: HttpRequest, query: web::Query, mut body: web::Payload, ) -> actix_web::Result { + metrics::counter!("oxen_server_tree_list_missing_file_hashes_from_commits_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -105,9 +111,11 @@ pub async fn list_missing_file_hashes_from_commits( })) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn list_missing_file_hashes( req: HttpRequest, ) -> actix_web::Result { + metrics::counter!("oxen_server_tree_list_missing_file_hashes_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -127,10 +135,12 @@ pub async fn list_missing_file_hashes( })) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn mark_nodes_as_synced( req: HttpRequest, mut body: web::Payload, ) -> actix_web::Result { + metrics::counter!("oxen_server_tree_mark_nodes_as_synced_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -156,10 +166,12 @@ pub async fn mark_nodes_as_synced( })) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn create_nodes( req: HttpRequest, mut body: web::Payload, ) -> actix_web::Result { + metrics::counter!("oxen_server_tree_create_nodes_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -180,7 +192,9 @@ pub async fn create_nodes( Ok(HttpResponse::Ok().json(StatusMessage::resource_found())) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn download_tree(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_tree_download_tree_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -192,9 +206,11 @@ pub async fn download_tree(req: HttpRequest) -> actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_tree_get_node_hash_by_path_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -211,10 +227,12 @@ pub async fn get_node_hash_by_path( })) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn download_tree_nodes( req: HttpRequest, query: web::Query, ) -> actix_web::Result { + metrics::counter!("oxen_server_tree_download_tree_nodes_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; @@ -299,7 +317,9 @@ fn get_commit_list( Ok(commits) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn download_node(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_tree_download_node_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let name = path_param(&req, "repo_name")?; diff --git a/oxen-rust/crates/server/src/controllers/versions.rs b/oxen-rust/crates/server/src/controllers/versions.rs index a35fc751a..e72b8d242 100644 --- a/oxen-rust/crates/server/src/controllers/versions.rs +++ b/oxen-rust/crates/server/src/controllers/versions.rs @@ -34,6 +34,7 @@ use utoipa::ToSchema; const DOWNLOAD_BUFFER_SIZE: usize = 2 * 1024 * 1024; /// Get version file metadata +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/versions/{version_id}/metadata", @@ -50,6 +51,7 @@ const DOWNLOAD_BUFFER_SIZE: usize = 2 * 1024 * 1024; ) )] pub async fn metadata(req: HttpRequest) -> Result { + metrics::counter!("oxen_server_versions_metadata_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -73,7 +75,9 @@ pub async fn metadata(req: HttpRequest) -> Result { } // Clean corrupted version files for the remote repo +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn clean(req: HttpRequest) -> Result { + metrics::counter!("oxen_server_versions_clean_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -89,6 +93,7 @@ pub async fn clean(req: HttpRequest) -> Result { // TODO: Refactor places that call /file/{resource:*} to use this new version store download endpoint /// Download version file +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/versions/{resource}", @@ -114,6 +119,7 @@ pub async fn download( req: HttpRequest, query: web::Query, ) -> Result { + metrics::counter!("oxen_server_versions_download_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -166,6 +172,7 @@ pub async fn download( } /// Batch download version files +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( post, path = "/api/repos/{namespace}/{repo_name}/versions/batch-download", @@ -189,6 +196,7 @@ pub async fn batch_download( req: HttpRequest, mut body: web::Payload, ) -> Result { + metrics::counter!("oxen_server_versions_batch_download_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -477,6 +485,7 @@ pub struct UploadVersionFile { } /// Batch upload version files +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( post, path = "/api/repos/{namespace}/{repo_name}/versions", @@ -502,6 +511,8 @@ pub async fn batch_upload( req: HttpRequest, payload: Multipart, ) -> Result { + metrics::counter!("oxen_server_versions_batch_upload_total").increment(1); + let timer = std::time::Instant::now(); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -510,6 +521,8 @@ pub async fn batch_upload( let err_files = save_multiparts(payload, &repo).await?; log::debug!("batch upload complete with err_files: {}", err_files.len()); + metrics::histogram!("oxen_server_versions_batch_upload_duration_ms") + .record(timer.elapsed().as_millis() as f64); Ok(HttpResponse::Ok().json(ErrorFilesResponse { status: StatusMessage::resource_created(), err_files, diff --git a/oxen-rust/crates/server/src/controllers/versions/chunks.rs b/oxen-rust/crates/server/src/controllers/versions/chunks.rs index b295a37af..16612ec5d 100644 --- a/oxen-rust/crates/server/src/controllers/versions/chunks.rs +++ b/oxen-rust/crates/server/src/controllers/versions/chunks.rs @@ -20,11 +20,14 @@ pub struct ChunkQuery { pub size: Option, } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn upload( req: HttpRequest, query: web::Query, mut body: web::Payload, ) -> Result { + metrics::counter!("oxen_server_versions_chunks_upload_total").increment(1); + let timer = std::time::Instant::now(); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -61,10 +64,15 @@ pub async fn upload( .await .map_err(|e| OxenHttpError::BasicError(e.to_string().into()))?; + metrics::histogram!("oxen_server_versions_chunks_upload_duration_ms") + .record(timer.elapsed().as_millis() as f64); Ok(HttpResponse::Ok().json(StatusMessage::resource_created())) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn complete(req: HttpRequest, body: String) -> Result { + metrics::counter!("oxen_server_versions_chunks_complete_total").increment(1); + let timer = std::time::Instant::now(); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -138,18 +146,24 @@ pub async fn complete(req: HttpRequest, body: String) -> Result, ) -> Result { + metrics::counter!("oxen_server_versions_chunks_download_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -180,6 +194,8 @@ pub async fn download( .body(chunk_data)) } +#[tracing::instrument(skip_all)] pub async fn create(_req: HttpRequest, _body: String) -> Result { + metrics::counter!("oxen_server_versions_chunks_create_total").increment(1); Ok(HttpResponse::Ok().json(StatusMessage::resource_found())) } diff --git a/oxen-rust/crates/server/src/controllers/workspaces.rs b/oxen-rust/crates/server/src/controllers/workspaces.rs index 3ec1c3444..c7cb3bfe2 100644 --- a/oxen-rust/crates/server/src/controllers/workspaces.rs +++ b/oxen-rust/crates/server/src/controllers/workspaces.rs @@ -20,6 +20,7 @@ pub mod data_frames; pub mod files; /// Get or create workspace +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( put, path = "/api/repos/{namespace}/{repo_name}/workspaces/get_or_create", @@ -48,6 +49,7 @@ pub async fn get_or_create( req: HttpRequest, body: String, ) -> actix_web::Result { + metrics::counter!("oxen_server_workspaces_get_or_create_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -142,6 +144,7 @@ pub async fn get_or_create( } /// Get workspace +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}", @@ -158,6 +161,7 @@ pub async fn get_or_create( ) )] pub async fn get(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_workspaces_get_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -184,15 +188,18 @@ pub async fn get(req: HttpRequest) -> actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_workspaces_create_total").increment(1); // Delegate to get_or_create for consistent behavior get_or_create(req, body).await } /// List workspaces +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/workspaces", @@ -212,6 +219,7 @@ pub async fn list( req: HttpRequest, params: web::Query, ) -> actix_web::Result { + metrics::counter!("oxen_server_workspaces_list_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -244,6 +252,7 @@ pub async fn list( } /// Clear workspaces for repo +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( delete, path = "/api/repos/{namespace}/{repo_name}/workspaces/clear", @@ -259,6 +268,7 @@ pub async fn list( ) )] pub async fn clear(req: HttpRequest) -> actix_web::Result { + metrics::counter!("oxen_server_workspaces_clear_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -268,6 +278,7 @@ pub async fn clear(req: HttpRequest) -> actix_web::Result actix_web::Result actix_web::Result { + metrics::counter!("oxen_server_workspaces_delete_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -308,6 +320,7 @@ pub async fn delete(req: HttpRequest) -> actix_web::Result actix_web::Result Result { + metrics::counter!("oxen_server_workspaces_mergeability_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -346,6 +360,7 @@ pub async fn mergeability(req: HttpRequest) -> Result Result Result { + metrics::counter!("oxen_server_workspaces_commit_total").increment(1); + let timer = std::time::Instant::now(); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -405,7 +422,10 @@ pub async fn commit(req: HttpRequest, body: String) -> Result { log::debug!("workspace::commit ✅ success! commit {commit:?}"); Ok(HttpResponse::Ok().json(CommitResponse { diff --git a/oxen-rust/crates/server/src/controllers/workspaces/changes.rs b/oxen-rust/crates/server/src/controllers/workspaces/changes.rs index 180aea038..130b21464 100644 --- a/oxen-rust/crates/server/src/controllers/workspaces/changes.rs +++ b/oxen-rust/crates/server/src/controllers/workspaces/changes.rs @@ -17,10 +17,12 @@ use actix_web::{HttpRequest, HttpResponse, web}; use std::path::PathBuf; +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn list_root( req: HttpRequest, query: web::Query, ) -> actix_web::Result { + metrics::counter!("oxen_server_workspaces_changes_list_root_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -54,10 +56,12 @@ pub async fn list_root( Ok(HttpResponse::Ok().json(response)) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn list( req: HttpRequest, query: web::Query, ) -> actix_web::Result { + metrics::counter!("oxen_server_workspaces_changes_list_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -92,6 +96,7 @@ pub async fn list( } /// Unstage a file from the workspace +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( delete, path = "/api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/changes/{path}", @@ -109,6 +114,7 @@ pub async fn list( ) )] pub async fn unstage(req: HttpRequest) -> Result { + metrics::counter!("oxen_server_workspaces_changes_unstage_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -125,6 +131,7 @@ pub async fn unstage(req: HttpRequest) -> Result { } /// Unstage files +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( delete, path = "/api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/changes", @@ -150,6 +157,7 @@ pub async fn unstage_many( req: HttpRequest, payload: web::Json>, ) -> Result { + metrics::counter!("oxen_server_workspaces_changes_unstage_many_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; diff --git a/oxen-rust/crates/server/src/controllers/workspaces/data_frames.rs b/oxen-rust/crates/server/src/controllers/workspaces/data_frames.rs index 4621bad66..9cc921ae4 100644 --- a/oxen-rust/crates/server/src/controllers/workspaces/data_frames.rs +++ b/oxen-rust/crates/server/src/controllers/workspaces/data_frames.rs @@ -87,10 +87,12 @@ impl Stream for CleanupFileStream { } } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn get( req: HttpRequest, query: web::Query, ) -> Result { + metrics::counter!("oxen_server_workspaces_data_frames_get_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -216,7 +218,9 @@ pub async fn get( Ok(HttpResponse::Ok().json(response)) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn get_schema(req: HttpRequest) -> Result { + metrics::counter!("oxen_server_workspaces_data_frames_get_schema_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -268,10 +272,12 @@ fn determine_extension<'a>(opts: &'a DFOpts, file_path: &'a Path) -> &'a str { // } // } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn download( req: HttpRequest, query: web::Query, ) -> Result { + metrics::counter!("oxen_server_workspaces_data_frames_download_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -397,10 +403,12 @@ fn df_not_indexed_response() -> WorkspaceJsonDataFrameViewResponse { } } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn download_streaming( req: HttpRequest, query: web::Query, ) -> Result { + metrics::counter!("oxen_server_workspaces_data_frames_download_streaming_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -468,10 +476,12 @@ pub async fn download_streaming( .streaming(stream)) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn get_by_branch( req: HttpRequest, query: web::Query, ) -> actix_web::Result { + metrics::counter!("oxen_server_workspaces_data_frames_get_by_branch_total").increment(1); let app_data = app_data(&req).unwrap(); let namespace = path_param(&req, "namespace")?; @@ -518,10 +528,12 @@ pub async fn get_by_branch( })) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn diff( req: HttpRequest, query: web::Query, ) -> actix_web::Result { + metrics::counter!("oxen_server_workspaces_data_frames_diff_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -584,7 +596,9 @@ pub async fn diff( } /// Index a data frame into DuckDB for querying +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn put(req: HttpRequest, body: String) -> Result { + metrics::counter!("oxen_server_workspaces_data_frames_put_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -613,7 +627,9 @@ pub async fn put(req: HttpRequest, body: String) -> Result Result { + metrics::counter!("oxen_server_workspaces_data_frames_delete_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -631,7 +647,9 @@ pub async fn delete(req: HttpRequest) -> Result { Ok(HttpResponse::Ok().json(StatusMessage::resource_deleted())) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn rename(req: HttpRequest, body: String) -> Result { + metrics::counter!("oxen_server_workspaces_data_frames_rename_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; diff --git a/oxen-rust/crates/server/src/controllers/workspaces/data_frames/columns.rs b/oxen-rust/crates/server/src/controllers/workspaces/data_frames/columns.rs index 5219aeebd..2915cc5eb 100644 --- a/oxen-rust/crates/server/src/controllers/workspaces/data_frames/columns.rs +++ b/oxen-rust/crates/server/src/controllers/workspaces/data_frames/columns.rs @@ -19,7 +19,9 @@ use liboxen::view::{ }; use serde_json::{Value, json}; +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn create(req: HttpRequest, body: String) -> Result { + metrics::counter!("oxen_server_workspaces_data_frames_columns_create_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -100,7 +102,9 @@ pub async fn create(req: HttpRequest, body: String) -> Result Result { + metrics::counter!("oxen_server_workspaces_data_frames_columns_delete_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -175,7 +179,9 @@ pub async fn delete(req: HttpRequest) -> Result { Ok(HttpResponse::Ok().json(response)) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn update(req: HttpRequest, body: String) -> Result { + metrics::counter!("oxen_server_workspaces_data_frames_columns_update_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -293,10 +299,13 @@ pub async fn update(req: HttpRequest, body: String) -> Result Result { + metrics::counter!("oxen_server_workspaces_data_frames_columns_add_column_metadata_total") + .increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -329,7 +338,9 @@ pub async fn add_column_metadata( Ok(HttpResponse::Ok().json(StatusMessage::resource_updated())) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn restore(req: HttpRequest) -> Result { + metrics::counter!("oxen_server_workspaces_data_frames_columns_restore_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; diff --git a/oxen-rust/crates/server/src/controllers/workspaces/data_frames/embeddings.rs b/oxen-rust/crates/server/src/controllers/workspaces/data_frames/embeddings.rs index c1421d1d3..a0a127c35 100644 --- a/oxen-rust/crates/server/src/controllers/workspaces/data_frames/embeddings.rs +++ b/oxen-rust/crates/server/src/controllers/workspaces/data_frames/embeddings.rs @@ -14,7 +14,9 @@ use liboxen::view::json_data_frame_view::WorkspaceJsonDataFrameViewResponse; use liboxen::view::{JsonDataFrameViews, StatusMessage, StatusMessageDescription}; /// Get the embedding status for a data frame +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn get(req: HttpRequest) -> Result { + metrics::counter!("oxen_server_workspaces_data_frames_embeddings_get_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -39,7 +41,9 @@ pub async fn get(req: HttpRequest) -> Result { Ok(HttpResponse::Ok().json(response)) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn neighbors(req: HttpRequest, body: String) -> Result { + metrics::counter!("oxen_server_workspaces_data_frames_embeddings_neighbors_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -142,7 +146,9 @@ pub async fn neighbors(req: HttpRequest, body: String) -> Result Result { + metrics::counter!("oxen_server_workspaces_data_frames_embeddings_post_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; diff --git a/oxen-rust/crates/server/src/controllers/workspaces/data_frames/rows.rs b/oxen-rust/crates/server/src/controllers/workspaces/data_frames/rows.rs index 70d0c2f6b..9c272f8ba 100644 --- a/oxen-rust/crates/server/src/controllers/workspaces/data_frames/rows.rs +++ b/oxen-rust/crates/server/src/controllers/workspaces/data_frames/rows.rs @@ -17,7 +17,9 @@ use liboxen::view::{ JsonDataFrameView, JsonDataFrameViews, StatusMessage, StatusMessageDescription, }; +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn create(req: HttpRequest, bytes: Bytes) -> Result { + metrics::counter!("oxen_server_workspaces_data_frames_rows_create_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -85,7 +87,9 @@ pub async fn create(req: HttpRequest, bytes: Bytes) -> Result Result { + metrics::counter!("oxen_server_workspaces_data_frames_rows_get_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -128,7 +132,9 @@ pub async fn get(req: HttpRequest) -> Result { Ok(HttpResponse::Ok().json(response)) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn update(req: HttpRequest, bytes: Bytes) -> Result { + metrics::counter!("oxen_server_workspaces_data_frames_rows_update_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -189,7 +195,9 @@ pub async fn update(req: HttpRequest, bytes: Bytes) -> Result Result { + metrics::counter!("oxen_server_workspaces_data_frames_rows_delete_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -226,7 +234,9 @@ pub async fn delete(req: HttpRequest, _bytes: Bytes) -> Result Result { + metrics::counter!("oxen_server_workspaces_data_frames_rows_restore_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -269,7 +279,9 @@ pub async fn restore(req: HttpRequest) -> Result { })) } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn batch_update(req: HttpRequest, bytes: Bytes) -> Result { + metrics::counter!("oxen_server_workspaces_data_frames_rows_batch_update_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; diff --git a/oxen-rust/crates/server/src/controllers/workspaces/files.rs b/oxen-rust/crates/server/src/controllers/workspaces/files.rs index 7d5d3ccf4..cf4a0c3f8 100644 --- a/oxen-rust/crates/server/src/controllers/workspaces/files.rs +++ b/oxen-rust/crates/server/src/controllers/workspaces/files.rs @@ -50,6 +50,7 @@ pub struct WorkspaceFileQueryParams { } /// Get file from workspace +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( get, path = "/api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/files/{path}", @@ -80,6 +81,7 @@ pub async fn get( req: HttpRequest, query: web::Query, ) -> Result { + metrics::counter!("oxen_server_workspaces_files_get_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -199,6 +201,7 @@ pub async fn get( } /// Add files to workspace +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( post, path = "/api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/files/{path}", @@ -222,6 +225,8 @@ pub async fn get( ) )] pub async fn add(req: HttpRequest, payload: Multipart) -> Result { + metrics::counter!("oxen_server_workspaces_files_add_total").increment(1); + let timer = std::time::Instant::now(); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -266,6 +271,8 @@ pub async fn add(req: HttpRequest, payload: Multipart) -> Result Result>, ) -> Result { + metrics::counter!("oxen_server_workspaces_files_add_version_files_total").increment(1); // Add file to staging let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; @@ -337,6 +346,7 @@ pub async fn add_version_files( } /// Stage files for removal +#[tracing::instrument(skip_all, fields(namespace, repo_name))] #[utoipa::path( delete, path = "/api/repos/{namespace}/{repo_name}/workspaces/{workspace_id}/files", @@ -362,6 +372,7 @@ pub async fn rm_files( req: HttpRequest, payload: web::Json>, ) -> Result { + metrics::counter!("oxen_server_workspaces_files_rm_files_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -405,7 +416,9 @@ pub async fn rm_files( } } +#[tracing::instrument(skip_all, fields(namespace, repo_name))] pub async fn validate(req: HttpRequest, _body: String) -> Result { + metrics::counter!("oxen_server_workspaces_files_validate_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; @@ -421,6 +434,7 @@ pub async fn validate(req: HttpRequest, _body: String) -> Result Result Result { + metrics::counter!("oxen_server_workspaces_files_mv_total").increment(1); let app_data = app_data(&req)?; let namespace = path_param(&req, "namespace")?; let repo_name = path_param(&req, "repo_name")?; diff --git a/oxen-rust/crates/server/src/main.rs b/oxen-rust/crates/server/src/main.rs index 24a4aa1a2..f31e28bf8 100644 --- a/oxen-rust/crates/server/src/main.rs +++ b/oxen-rust/crates/server/src/main.rs @@ -378,9 +378,16 @@ async fn main() -> Result<(), ServerError> { Err(e) => log::debug!("Failed to load .env file: {e}"), } - util::logging::init_logging(); + let _tracing_guard = util::telemetry::init_tracing("oxen-server"); util::perf::init_perf_logging(); + let metrics_port: u16 = env::var("OXEN_METRICS_PORT") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(9090); + util::telemetry::init_metrics_prometheus(metrics_port); + log::info!("Prometheus metrics available at http://0.0.0.0:{metrics_port}/metrics"); + let sync_dir = match env::var("SYNC_DIR") { Ok(dir) => PathBuf::from(dir), Err(_) => PathBuf::from("data"), @@ -396,8 +403,8 @@ async fn main() -> Result<(), ServerError> { match ServerCli::parse().command { ServerCommand::Start { ip, port, auth } => { - println!("🐂 v{VERSION}"); - println!("{SUPPORT}"); + log::info!("🐂 v{VERSION}"); + log::info!("{SUPPORT}"); start( &ip, @@ -424,6 +431,7 @@ async fn main() -> Result<(), ServerError> { } => { log::debug!("Saving to sync dir: {sync_dir:?}"); let token = add_user(&email, &name, output.as_path(), &sync_dir)?; + // Keep this as println!: we want to send this to STDOUT only. println!( "User access token created:\n\n{token}\n\nTo give user access have them run the command `oxen config --auth `" ); @@ -467,8 +475,8 @@ async fn start( let data = app_data::OxenAppData::new(PathBuf::from(sync_dir)); - println!("Running on {host}:{port}"); - println!("Syncing to directory: {}", sync_dir.display()); + log::info!("Running on {host}:{port}"); + log::info!("Syncing to directory: {}", sync_dir.display()); HttpServer::new(move || { App::new()