Tracing: root spans for background jobs, retire raw OTel spans (#142)
Background work has no request parent, so each job now opens a named tracing root and carries it into its fan-out: - watcher: `watcher.tick` (scan=quick|full) entered per tick on the watcher's std thread, `watcher.library` per library, and `watcher.playlist_cleanup` per orphaned-HLS pass. face_watch / clip_watch carry the ambient span into every per-candidate tokio::spawn and give each call its own `face_watch.detect` / `clip_watch.encode` span. - thumbnails: `creating thumbnails` via #[instrument]; per-video spans on rayon workers name their parent explicitly. - video actors: `playlistgenerator.generate_playlist` and `preview_clip.generate` are tracing roots that instrument their futures. - reels: `reel.job` (created before the spawn, so on-demand jobs stay in the request trace), `reel.pregen_batch`, `reel.cache_sweep`. - RefreshThumbnailsMessage and the dead-code GIF job move to tracing. No raw OpenTelemetry span creation is left outside otel.rs; `global_tracer` is removed. tests/otel_background.rs pins the std-thread tick -> block_on -> spawn shape and the rayon explicit-parent shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b6deca2a52
commit
26115b3378
11 files changed
+330
-179
No files matched your search
+53
-43
@@ -1,18 +1,19 @@
|
||||
use crate::content_hash;
|
||||
use crate::database::PreviewDao;
|
||||
use crate::libraries::Library;
|
||||
use crate::otel::global_tracer;
|
||||
use crate::video::ffmpeg::{generate_preview_clip, get_duration_seconds_blocking};
|
||||
use crate::video::hls_paths;
|
||||
use actix::prelude::*;
|
||||
use log::{debug, error, info, warn};
|
||||
use opentelemetry::KeyValue;
|
||||
use opentelemetry::trace::{Span, Status, Tracer};
|
||||
use opentelemetry::trace::Status;
|
||||
use std::io::Result;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::sync::Semaphore;
|
||||
use tracing::Instrument;
|
||||
use tracing_opentelemetry::OpenTelemetrySpanExt;
|
||||
// ffmpeg -i test.mp4 -c:v h264 -flags +cgop -g 30 -hls_time 3 out.m3u8
|
||||
// ffmpeg -i "filename.mp4" -preset veryfast -c:v libx264 -f hls -hls_list_size 100 -hls_time 2 -crf 24 -vf scale=1080:-2,setsar=1:1 attempt/vid_out.m3u8
|
||||
|
||||
@@ -448,15 +449,16 @@ impl Handler<GeneratePlaylistMessage> for PlaylistGenerator {
|
||||
let playlist_file = playlist_path.to_string_lossy().to_string();
|
||||
let segment_pattern = segment_template.to_string_lossy().to_string();
|
||||
|
||||
let tracer = global_tracer();
|
||||
let mut span = tracer
|
||||
.span_builder("playlistgenerator.generate_playlist")
|
||||
.with_attributes(vec![
|
||||
KeyValue::new("video_file", video_file.clone()),
|
||||
KeyValue::new("content_hash", content_hash_str.clone()),
|
||||
KeyValue::new("playlist_file", playlist_file.clone()),
|
||||
])
|
||||
.start(&tracer);
|
||||
// Job root: actix messages don't carry the sender's span, so each
|
||||
// playlist generation is its own trace. The future is instrumented
|
||||
// so any DB / child spans inside nest under it.
|
||||
let span = tracing::info_span!(
|
||||
"playlistgenerator.generate_playlist",
|
||||
video_file = %video_file,
|
||||
content_hash = %content_hash_str,
|
||||
playlist_file = %playlist_file,
|
||||
);
|
||||
let fut_span = span.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
let wait_start = std::time::Instant::now();
|
||||
@@ -691,7 +693,8 @@ impl Handler<GeneratePlaylistMessage> for PlaylistGenerator {
|
||||
span.set_status(Status::error(detail.clone()));
|
||||
Err(std::io::Error::other(detail))
|
||||
}
|
||||
})
|
||||
}
|
||||
.instrument(fut_span))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -818,39 +821,46 @@ impl Handler<GeneratePreviewClipMessage> for PreviewClipGenerator {
|
||||
|
||||
let video_path_owned = video_path.clone();
|
||||
let relative_path_owned = relative_path.clone();
|
||||
tokio::spawn(async move {
|
||||
match generate_preview_clip(&video_path_owned, &output_str).await {
|
||||
Ok((duration, size)) => {
|
||||
info!(
|
||||
"Preview clip complete for '{}' ({:.1}s, {} bytes)",
|
||||
relative_path_owned, duration, size
|
||||
);
|
||||
let mut dao = preview_dao.lock().expect("Unable to lock PreviewDao");
|
||||
let _ = dao.update_status(
|
||||
&relative_path_owned,
|
||||
"complete",
|
||||
Some(duration as f32),
|
||||
Some(size as i32),
|
||||
None,
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Failed to generate preview clip for '{}': {}",
|
||||
relative_path_owned, e
|
||||
);
|
||||
let mut dao = preview_dao.lock().expect("Unable to lock PreviewDao");
|
||||
let _ = dao.update_status(
|
||||
&relative_path_owned,
|
||||
"failed",
|
||||
None,
|
||||
None,
|
||||
Some(&e.to_string()),
|
||||
);
|
||||
let clip_span = tracing::info_span!(
|
||||
"preview_clip.generate",
|
||||
rel_path = %relative_path_owned,
|
||||
);
|
||||
tokio::spawn(
|
||||
async move {
|
||||
match generate_preview_clip(&video_path_owned, &output_str).await {
|
||||
Ok((duration, size)) => {
|
||||
info!(
|
||||
"Preview clip complete for '{}' ({:.1}s, {} bytes)",
|
||||
relative_path_owned, duration, size
|
||||
);
|
||||
let mut dao = preview_dao.lock().expect("Unable to lock PreviewDao");
|
||||
let _ = dao.update_status(
|
||||
&relative_path_owned,
|
||||
"complete",
|
||||
Some(duration as f32),
|
||||
Some(size as i32),
|
||||
None,
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Failed to generate preview clip for '{}': {}",
|
||||
relative_path_owned, e
|
||||
);
|
||||
let mut dao = preview_dao.lock().expect("Unable to lock PreviewDao");
|
||||
let _ = dao.update_status(
|
||||
&relative_path_owned,
|
||||
"failed",
|
||||
None,
|
||||
None,
|
||||
Some(&e.to_string()),
|
||||
);
|
||||
}
|
||||
}
|
||||
drop(permit);
|
||||
}
|
||||
drop(permit);
|
||||
});
|
||||
.instrument(clip_span),
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user