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),
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+57
-57
@@ -1,10 +1,9 @@
|
||||
use crate::otel::global_tracer;
|
||||
use crate::thumbnails::{is_video, update_media_counts};
|
||||
use crate::video::ffmpeg::{Ffmpeg, GifType};
|
||||
use log::info;
|
||||
use opentelemetry::trace::Tracer;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing::Instrument;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub mod actors;
|
||||
@@ -14,67 +13,68 @@ pub mod legacy_migration;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn generate_video_gifs() {
|
||||
tokio::spawn(async {
|
||||
info!("Starting to make video gifs");
|
||||
tokio::spawn(
|
||||
async {
|
||||
info!("Starting to make video gifs");
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
let tracer = global_tracer();
|
||||
tracer.start("creating video gifs");
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let gif_base_path = &dotenv::var("GIFS_DIRECTORY").unwrap_or(String::from("gifs"));
|
||||
let gif_directory: &Path = Path::new(gif_base_path);
|
||||
fs::create_dir_all(gif_base_path).expect("There was an issue creating directory");
|
||||
let gif_base_path = &dotenv::var("GIFS_DIRECTORY").unwrap_or(String::from("gifs"));
|
||||
let gif_directory: &Path = Path::new(gif_base_path);
|
||||
fs::create_dir_all(gif_base_path).expect("There was an issue creating directory");
|
||||
|
||||
let files = PathBuf::from(dotenv::var("BASE_PATH").unwrap());
|
||||
// EXCLUDED_DIRS read here for the update_media_counts call below.
|
||||
// The WalkDir walk itself is left raw — this function is currently
|
||||
// dead code (`#[allow(dead_code)]`) and not reachable from main.
|
||||
// If revived, swap to file_scan::walk_library_files (dual lib/bin
|
||||
// module path makes that non-trivial here).
|
||||
let excluded_dirs: Vec<String> = std::env::var("EXCLUDED_DIRS")
|
||||
.unwrap_or_default()
|
||||
.split(',')
|
||||
.filter(|s| !s.trim().is_empty())
|
||||
.map(|s| s.trim().to_string())
|
||||
.collect();
|
||||
let files = PathBuf::from(dotenv::var("BASE_PATH").unwrap());
|
||||
// EXCLUDED_DIRS read here for the update_media_counts call below.
|
||||
// The WalkDir walk itself is left raw — this function is currently
|
||||
// dead code (`#[allow(dead_code)]`) and not reachable from main.
|
||||
// If revived, swap to file_scan::walk_library_files (dual lib/bin
|
||||
// module path makes that non-trivial here).
|
||||
let excluded_dirs: Vec<String> = std::env::var("EXCLUDED_DIRS")
|
||||
.unwrap_or_default()
|
||||
.split(',')
|
||||
.filter(|s| !s.trim().is_empty())
|
||||
.map(|s| s.trim().to_string())
|
||||
.collect();
|
||||
|
||||
let ffmpeg = Ffmpeg;
|
||||
for file in WalkDir::new(&files)
|
||||
.into_iter()
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter(|entry| entry.file_type().is_file())
|
||||
.filter(is_video)
|
||||
.filter(|entry| {
|
||||
let path = entry.path();
|
||||
let ffmpeg = Ffmpeg;
|
||||
for file in WalkDir::new(&files)
|
||||
.into_iter()
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter(|entry| entry.file_type().is_file())
|
||||
.filter(is_video)
|
||||
.filter(|entry| {
|
||||
let path = entry.path();
|
||||
let relative_path = &path.strip_prefix(&files).unwrap();
|
||||
let thumb_path = Path::new(gif_directory).join(relative_path);
|
||||
let gif_path = thumb_path.with_extension("gif");
|
||||
!gif_path.exists()
|
||||
})
|
||||
{
|
||||
let path = file.path();
|
||||
let relative_path = &path.strip_prefix(&files).unwrap();
|
||||
let thumb_path = Path::new(gif_directory).join(relative_path);
|
||||
let gif_path = thumb_path.with_extension("gif");
|
||||
!gif_path.exists()
|
||||
})
|
||||
{
|
||||
let path = file.path();
|
||||
let relative_path = &path.strip_prefix(&files).unwrap();
|
||||
let gif_path = Path::new(gif_directory).join(relative_path);
|
||||
let gif_path = gif_path.with_extension("gif");
|
||||
if let Some(parent_dir) = gif_path.parent() {
|
||||
fs::create_dir_all(parent_dir).unwrap_or_else(|_| {
|
||||
panic!("There was an issue creating gif directory {:?}", gif_path)
|
||||
});
|
||||
let gif_path = Path::new(gif_directory).join(relative_path);
|
||||
let gif_path = gif_path.with_extension("gif");
|
||||
if let Some(parent_dir) = gif_path.parent() {
|
||||
fs::create_dir_all(parent_dir).unwrap_or_else(|_| {
|
||||
panic!("There was an issue creating gif directory {:?}", gif_path)
|
||||
});
|
||||
}
|
||||
info!("Generating gif for {:?}", path);
|
||||
|
||||
ffmpeg
|
||||
.generate_video_gif(
|
||||
path.to_str().unwrap(),
|
||||
gif_path.to_str().unwrap(),
|
||||
GifType::Overview,
|
||||
)
|
||||
.await
|
||||
.expect("There was an issue generating the gif");
|
||||
}
|
||||
info!("Generating gif for {:?}", path);
|
||||
|
||||
ffmpeg
|
||||
.generate_video_gif(
|
||||
path.to_str().unwrap(),
|
||||
gif_path.to_str().unwrap(),
|
||||
GifType::Overview,
|
||||
)
|
||||
.await
|
||||
.expect("There was an issue generating the gif");
|
||||
info!("Finished making video gifs in {:?}", start.elapsed());
|
||||
|
||||
update_media_counts(&files, &excluded_dirs);
|
||||
}
|
||||
|
||||
info!("Finished making video gifs in {:?}", start.elapsed());
|
||||
|
||||
update_media_counts(&files, &excluded_dirs);
|
||||
});
|
||||
.instrument(tracing::info_span!("video.create_gifs")),
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user