git.delta.rocks / jrsonnet / refs/commits / f0286d779205

difftreelog

feat derivation graph to spans

xykwwsssYaroslav Bolyukin2026-03-12parent: #d5b0f6a.patch.diff
in: trunk

6 files changed

modifiedcmds/fleet/Cargo.tomldiffbeforeafterboth
50tracing-opentelemetry.workspace = true50tracing-opentelemetry.workspace = true
5151
52[features]52[features]
53default = []53default = ["indicatif"]
54# Not quite stable54# Not quite stable
55indicatif = [55indicatif = [
56 "dep:tracing-indicatif",56 "dep:tracing-indicatif",
modifiedcmds/fleet/src/main.rsdiffbeforeafterboth
120 let indicatif_layer = {120 let indicatif_layer = {
121 use std::time::Duration;121 use std::time::Duration;
122122
123 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}",
modifiedcrates/nix-eval/build.rsdiffbeforeafterboth
18 "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_paths
73 .into_iter()74 .into_iter()
75 .chain(
76 pkg_config::probe_library("nix-store-c")
77 .expect("nix-store-c")
78 .include_paths
79 .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")
addedcrates/nix-eval/src/drv.rsdiffbeforeafterboth

no changes

modifiedcrates/nix-eval/src/lib.rsdiffbeforeafterboth
13use std::mem::transmute;13use std::mem::transmute;
1414
15pub use anyhow::Result;15pub use anyhow::Result;
16use tracing::{Instrument, info, instrument, warn};16use tracing::{Span, instrument, warn};
1717
18use 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};
4445
45// Contains macros helpers46// Contains macros helpers
47pub 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 v
332}334}
335
336/// 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 v
348}
333349
334pub 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}
425441
426unsafe 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 };
873
874 let drv_path = v
875 .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);
880
856 // to_string here blocks until the path is built881 // to_string here blocks until the path is built
857 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");
11051130
1106 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 }
11101143
1111 Ok(())1144 Ok(())
1112}1145}
modifiedcrates/nix-eval/src/logging.rsdiffbeforeafterboth
1use 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};
44
285static 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()));
287
288struct DrvGraphEntry {
289 name: String,
290 parent: Option<String>,
291 span: Option<Span>,
292 refcount: usize,
293}
294
295static DRV_GRAPH: LazyLock<Mutex<HashMap<String, DrvGraphEntry>>> =
296 LazyLock::new(|| Mutex::new(HashMap::new()));
297
298static ACTIVITY_TO_DRV: LazyLock<Mutex<HashMap<u64, String>>> =
299 LazyLock::new(|| Mutex::new(HashMap::new()));
300
301pub struct BuildGraphGuard {
302 paths: Vec<String>,
303}
304
305impl 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}
318
319pub 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();
322
323 drv_graph
324 .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());
333
334 let mut queue = VecDeque::new();
335 queue.push_back(graph.root.clone());
336
337 let mut visited = std::collections::HashSet::new();
338 visited.insert(graph.root.clone());
339
340 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 }
365
366 BuildGraphGuard { paths }
367}
368
369fn ensure_drv_span(drv_path: &str) -> Option<Span> {
370 let mut drv_graph = DRV_GRAPH.lock().expect("not poisoned");
371
372 if let Some(span) = drv_graph.get(drv_path).and_then(|e| e.span.clone()) {
373 return Some(span);
374 }
375
376 let mut chain = vec![];
377 let mut current = drv_path.to_owned();
378 loop {
379 let Some(entry) = drv_graph.get(&current) 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 }
392
393 if chain.is_empty() {
394 return None;
395 }
396
397 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 = chain
403 .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 }
413
414 drv_graph.get(drv_path).and_then(|e| e.span.clone())
415}
287416
288#[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_DRV
445 .lock()
446 .expect("not poisoned")
447 .insert(self.activity_id, clean.to_owned());
448 }
449 span
450 }
451 _ => None,
452 })
453 } else {
454 None
455 };
456
309 let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");457 let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");
310458
459 let span = if let Some(span) = graph_span {
460 #[cfg(feature = "indicatif")]
461 span.pb_start();
462 span
463 } else {
311 let parent = mapping.get(&parent);464 let parent = mapping.get(&parent);
312
313 // 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 // s
341 // } 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");
358
359 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.typ
376 .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);