difftreelog
feat derivation graph to spans
in: trunk
6 files changed
cmds/fleet/Cargo.tomldiffbeforeafterboth50tracing-opentelemetry.workspace = true50tracing-opentelemetry.workspace = true515152[features]52[features]53default = []53default = ["indicatif"]54# Not quite stable54# Not quite stable55indicatif = [55indicatif = [56 "dep:tracing-indicatif",56 "dep:tracing-indicatif",cmds/fleet/src/main.rsdiffbeforeafterboth120 let indicatif_layer = {120 let indicatif_layer = {121 use std::time::Duration;121 use std::time::Duration;122122123 IndicatifLayer::new().with_progress_style(123 IndicatifLayer::new().with_max_progress_bars(10, Some(ProgressStyle::default_spinner()))124 .with_progress_style(124 ProgressStyle::with_template(125 ProgressStyle::with_template(125 "{color_start}{span_child_prefix} {span_name}{{{span_fields}}}{color_end} {wide_msg} {color_start}{download_progress} {elapsed}{color_end}",126 "{color_start}{span_child_prefix} {span_name}{{{span_fields}}}{color_end} {wide_msg} {color_start}{download_progress} {elapsed}{color_end}",crates/nix-eval/build.rsdiffbeforeafterboth18 "nix-util",18 "nix-util",19 "nix-util-c",19 "nix-util-c",20 "nix-store",20 "nix-store",21 "nix-store-c",21 "nix-expr",22 "nix-expr",22 "nix-flake",23 "nix-flake",23 "nix-fetchers",24 "nix-fetchers",71 .expect("nix-expr-c")72 .expect("nix-expr-c")72 .include_paths73 .include_paths73 .into_iter()74 .into_iter()75 .chain(76 pkg_config::probe_library("nix-store-c")77 .expect("nix-store-c")78 .include_paths79 .into_iter(),80 )74 .chain(81 .chain(75 pkg_config::probe_library("nix-flake-c")82 pkg_config::probe_library("nix-flake-c")76 .expect("nix-flake-c")83 .expect("nix-flake-c")crates/nix-eval/src/drv.rsdiffbeforeafterbothno changes
crates/nix-eval/src/lib.rsdiffbeforeafterboth13use std::mem::transmute;13use std::mem::transmute;141415pub use anyhow::Result;15pub use anyhow::Result;16use tracing::{Instrument, info, instrument, warn};16use tracing::{Span, instrument, warn};171718use self::logging::{ErrorInfoBuilder, nix_logging_cxx};18use self::logging::{ErrorInfoBuilder, nix_logging_cxx};19use self::nix_cxx::set_fetcher_setting;19use self::nix_cxx::set_fetcher_setting;43};44};444545// Contains macros helpers46// Contains macros helpers47pub mod drv;46pub mod logging;48pub mod logging;47#[doc(hidden)]49#[doc(hidden)]48pub mod macros;50pub mod macros;321thread_local! {323thread_local! {322 static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));324 static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));323}325}324fn with_default_context<T>(f: impl FnOnce(*mut c_context, *mut c_eval_state) -> T) -> Result<T> {326pub(crate) fn with_default_context<T>(f: impl FnOnce(*mut c_context, *mut c_eval_state) -> T) -> Result<T> {325 let global = &GLOBAL_STATE.state;327 let global = &GLOBAL_STATE.state;326 let (ctx, state) = THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.0));328 let (ctx, state) = THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.0));327 let mut ctx = NixContext(ctx);329 let mut ctx = NixContext(ctx);331 v333 v332}334}335336/// Same as with_default_context, but also passes store...337/// Yep, this code is garbage and needs to be refactored.338pub(crate) fn with_store_context<T>(339 f: impl FnOnce(*mut c_context, *mut c_store, *mut c_eval_state) -> T,340) -> Result<T> {341 let global = &GLOBAL_STATE;342 let (ctx, store, state) =343 THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.store.0, global.state.0));344 let mut ctx = NixContext(ctx);345 let v = ctx.run_in_context(|c| f(c, store, state));346 std::mem::forget(ctx);347 v348}333349334pub fn set_setting(s: &CStr, v: &CStr) -> Result<()> {350pub fn set_setting(s: &CStr, v: &CStr) -> Result<()> {335 with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())351 with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())423 }439 }424}440}425441426unsafe extern "C" fn copy_nix_str(start: *const c_char, n: c_uint, user_data: *mut c_void) {442pub(crate) unsafe extern "C" fn copy_nix_str(start: *const c_char, n: c_uint, user_data: *mut c_void) {427 let s = unsafe { slice::from_raw_parts(start.cast::<u8>(), n as usize) };443 let s = unsafe { slice::from_raw_parts(start.cast::<u8>(), n as usize) };428 let s = std::str::from_utf8(s).expect("c string has invalid utf-8");444 let s = std::str::from_utf8(s).expect("c string has invalid utf-8");429 unsafe { *user_data.cast::<String>() = s.to_owned() };445 unsafe { *user_data.cast::<String>() = s.to_owned() };836 })?;852 })?;837 Ok(out)853 Ok(out)838 }854 }855 #[instrument(name = "build", skip(self), fields(output))]839 pub fn build(&self, output: &str) -> Result<PathBuf> {856 pub fn build(&self, output: &str) -> Result<PathBuf> {840 if !self.is_derivation() {857 if !self.is_derivation() {841 bail!("expected derivation to build")858 bail!("expected derivation to build")854 self.clone()871 self.clone()855 };872 };873874 let drv_path = v875 .get_field("drvPath")876 .context("getting drvPath")?877 .to_string()?;878 let graph = drv::DrvGraph::resolve(&drv_path)?;879 let _guard = logging::register_build_graph(&Span::current(), &graph);880856 // to_string here blocks until the path is built881 // to_string here blocks until the path is built857 let s = v.builtin_to_string()?;882 let s = v.builtin_to_string()?;858 let rs = s.to_realised_string()?;883 let rs = s.to_realised_string()?;859 let drv_path = rs.as_str().to_owned();884 let out_path = rs.as_str().to_owned();860 Ok(PathBuf::from(drv_path))885 Ok(PathBuf::from(out_path))861 }886 }862 pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {887 pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {863 let to_json = Self::eval("builtins.toJSON")?;888 let to_json = Self::eval("builtins.toJSON")?;1103 let test_result: String = nix_go_json!(builtins.uppercaseSuffix2("test")("suffix"));1128 let test_result: String = nix_go_json!(builtins.uppercaseSuffix2("test")("suffix"));1104 assert_eq!(test_result, "TESTsuffix");1129 assert_eq!(test_result, "TESTsuffix");110511301106 let nix_ctx = NixContext::new();1131 let drv_path = nix_go!(attrs.packages["x86_64-linux"]["fleet-install-secrets"].drvPath)1132 .to_string()?;1107 let store = GLOBAL_STATE.store.parse_path(s.as_c_str())?;1133 let graph = drv::DrvGraph::resolve(&drv_path)?;11081134 eprintln!(1109 // nix_raw::store_get_fs_closure(1);1135 "fleet-install-secrets dependency graph: {} nodes",1136 graph.nodes.len()1137 );1138 for (path, node) in &graph.nodes {1139 if !node.input_drvs.is_empty() {1140 eprintln!(" {} ({} deps)", node.name, node.input_drvs.len());1141 }1142 }111011431111 Ok(())1144 Ok(())1112}1145}crates/nix-eval/src/logging.rsdiffbeforeafterboth1use std::collections::HashMap;1use std::collections::{HashMap, VecDeque};2use std::fmt::Arguments;2use std::fmt::Arguments;3use std::sync::{LazyLock, Mutex};3use std::sync::{LazyLock, Mutex};44285static NIX_SPAN_MAPPING: LazyLock<Mutex<HashMap<u64, Span>>> =285static NIX_SPAN_MAPPING: LazyLock<Mutex<HashMap<u64, Span>>> =286 LazyLock::new(|| Mutex::new(HashMap::new()));286 LazyLock::new(|| Mutex::new(HashMap::new()));287288struct DrvGraphEntry {289 name: String,290 parent: Option<String>,291 span: Option<Span>,292 refcount: usize,293}294295static DRV_GRAPH: LazyLock<Mutex<HashMap<String, DrvGraphEntry>>> =296 LazyLock::new(|| Mutex::new(HashMap::new()));297298static ACTIVITY_TO_DRV: LazyLock<Mutex<HashMap<u64, String>>> =299 LazyLock::new(|| Mutex::new(HashMap::new()));300301pub struct BuildGraphGuard {302 paths: Vec<String>,303}304305impl Drop for BuildGraphGuard {306 fn drop(&mut self) {307 let mut drv_graph = DRV_GRAPH.lock().expect("not poisoned");308 for path in &self.paths {309 if let Some(entry) = drv_graph.get_mut(path) {310 entry.refcount -= 1;311 if entry.refcount == 0 {312 drv_graph.remove(path);313 }314 }315 }316 }317}318319pub fn register_build_graph(parent: &Span, graph: &crate::drv::DrvGraph) -> BuildGraphGuard {320 let mut drv_graph = DRV_GRAPH.lock().expect("not poisoned");321 let mut paths = Vec::new();322323 drv_graph324 .entry(graph.root.clone())325 .and_modify(|e| e.refcount += 1)326 .or_insert_with(|| DrvGraphEntry {327 name: graph.nodes[&graph.root].name.clone(),328 parent: None,329 span: Some(parent.clone()),330 refcount: 1,331 });332 paths.push(graph.root.clone());333334 let mut queue = VecDeque::new();335 queue.push_back(graph.root.clone());336337 let mut visited = std::collections::HashSet::new();338 visited.insert(graph.root.clone());339340 while let Some(path) = queue.pop_front() {341 let Some(node) = graph.nodes.get(&path) else {342 continue;343 };344 for dep_path in node.input_drvs.keys() {345 if !visited.insert(dep_path.clone()) {346 continue;347 }348 let Some(dep_node) = graph.nodes.get(dep_path) else {349 continue;350 };351 if let Some(entry) = drv_graph.get_mut(dep_path) {352 entry.refcount += 1;353 } else {354 drv_graph.insert(dep_path.clone(), DrvGraphEntry {355 name: dep_node.name.clone(),356 parent: Some(path.clone()),357 span: None,358 refcount: 1,359 });360 }361 paths.push(dep_path.clone());362 queue.push_back(dep_path.clone());363 }364 }365366 BuildGraphGuard { paths }367}368369fn ensure_drv_span(drv_path: &str) -> Option<Span> {370 let mut drv_graph = DRV_GRAPH.lock().expect("not poisoned");371372 if let Some(span) = drv_graph.get(drv_path).and_then(|e| e.span.clone()) {373 return Some(span);374 }375376 let mut chain = vec![];377 let mut current = drv_path.to_owned();378 loop {379 let Some(entry) = drv_graph.get(¤t) else {380 break;381 };382 if entry.span.is_some() {383 chain.push(current);384 break;385 }386 chain.push(current.clone());387 match &entry.parent {388 Some(p) => current = p.clone(),389 None => break,390 }391 }392393 if chain.is_empty() {394 return None;395 }396397 for i in (0..chain.len()).rev() {398 let path = &chain[i];399 if drv_graph.get(path).unwrap().span.is_some() {400 continue;401 }402 let parent_span = chain403 .get(i + 1)404 .and_then(|p| drv_graph.get(p))405 .and_then(|e| e.span.clone());406 let name = drv_graph.get(path).unwrap().name.clone();407 let span = {408 let _enter = parent_span.as_ref().map(|s| s.enter());409 info_span!(target: "nix::build", "building", drv = %name)410 };411 drv_graph.get_mut(path).unwrap().span = Some(span);412 }413414 drv_graph.get(drv_path).and_then(|e| e.span.clone())415}287416288#[derive(Debug)]417#[derive(Debug)]289enum FieldValue {418enum FieldValue {306 self.fields.push(FieldValue::Str(v.to_string()));435 self.fields.push(FieldValue::Str(v.to_string()));307 }436 }308 fn emit(&mut self, parent: u64, s: &str) {437 fn emit(&mut self, parent: u64, s: &str) {438 let graph_span = if matches!(self.typ, ActivityType::Build) {439 self.fields.first().and_then(|f| match f {440 FieldValue::Str(drv_path) => {441 let clean = parse_path(drv_path);442 let span = ensure_drv_span(clean);443 if span.is_some() {444 ACTIVITY_TO_DRV445 .lock()446 .expect("not poisoned")447 .insert(self.activity_id, clean.to_owned());448 }449 span450 }451 _ => None,452 })453 } else {454 None455 };456309 let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");457 let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");310458459 let span = if let Some(span) = graph_span {460 #[cfg(feature = "indicatif")]461 span.pb_start();462 span463 } else {311 let parent = mapping.get(&parent);464 let parent = mapping.get(&parent);312313 // let meta = spans.alloc_metadata(314 // self.typ.name(),315 // self.verbosity.into(),316 // MetadataKind::Span,317 // "nix activity start",318 // None,319 // None,320 // None,321 // self.typ.fields(),322 // );323 //324 // let mut fields = meta.fields().iter();325 // let span = if let Some(parent) = parent {326 // let s = Span::new(327 // meta,328 // &match meta.fields().len() {329 // 1 => meta.fields().value_set(330 // &<[_; 1]>::try_from([(331 // &fields.next().expect("has field"),332 // Some(&format_args!("Test") as &dyn tracing::Value),333 // )])334 // .expect("valid size"),335 // ),336 // _ => unreachable!(),337 // },338 // );339 // s.follows_from(parent);340 // s341 // } else {342 // Span::new_root(343 // meta,344 // &match meta.fields().len() {345 // 1 => meta.fields().value_set(346 // &<[_; 1]>::try_from([(347 // &fields.next().expect("has field"),348 // Some(&format_args!("Test") as &dyn tracing::Value),349 // )])350 // .expect("valid size"),351 // ),352 // _ => unreachable!(),353 // },354 // )355 // };356 //357 // let id = span.id().expect("id created");358359 let span = {360 let _in_parent = parent.map(|p| p.enter());465 let _in_parent = parent.map(|p| p.enter());361 let level: Level = self.verbosity.into();466 let level: Level = self.verbosity.into();362 if level == Level::ERROR {467 if level == Level::ERROR {375 self.typ480 self.typ376 .format(&self.fields, s, |v| trace_span!("action", v))481 .format(&self.fields, s, |v| trace_span!("action", v))377 }482 }378 };483 };379 if !s.trim().is_empty() {484 if !s.trim().is_empty() {380 let s = ansi_filter(s);485 let s = ansi_filter(s);381 #[cfg(feature = "indicatif")]486 #[cfg(feature = "indicatif")]382 {487 {383 span.pb_set_message(s);488 span.pb_set_message(&s);384 }489 }385 let _e = span.enter();490 let _e = span.enter();386 let level: Level = self.verbosity.into();491 let level: Level = self.verbosity.into();454 warn!(target: "nix::eval", "{v}")559 warn!(target: "nix::eval", "{v}")455}560}456fn emit_stop(v: u64) {561fn emit_stop(v: u64) {562 {457 let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");563 let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");458 mapping.remove(&v);564 mapping.remove(&v);459}565 }566 if let Some(drv_path) = ACTIVITY_TO_DRV.lock().expect("not poisoned").remove(&v) {567 if let Some(entry) = DRV_GRAPH.lock().expect("not poisoned").get_mut(&drv_path) {568 entry.span = None;569 }570 }571}460fn emit_log(lvl: u32, v: &[u8]) {572fn emit_log(lvl: u32, v: &[u8]) {461 let verbosity = Verbosity::from_int(lvl);573 let verbosity = Verbosity::from_int(lvl);