tracers: pipeline_snapshot: Make it controllable inside apps

Exposing properties so that user can configure it when instantiating it

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1889>
This commit is contained in:
Thibault Saunier 2024-03-27 15:16:14 -03:00 committed by GStreamer Marge Bot
parent 27b02445d0
commit e531c7f625
2 changed files with 96 additions and 34 deletions

View file

@ -45,6 +45,7 @@ use std::sync::{Arc, Mutex, RwLock};
use gst::glib; use gst::glib;
use gst::glib::translate::ToGlibPtr; use gst::glib::translate::ToGlibPtr;
use gst::glib::Properties;
use gst::prelude::*; use gst::prelude::*;
use gst::subclass::prelude::*; use gst::subclass::prelude::*;
use std::sync::LazyLock; use std::sync::LazyLock;
@ -120,7 +121,7 @@ impl Settings {
if let Ok(dot_dir) = s.get("dot-dir") { if let Ok(dot_dir) = s.get("dot-dir") {
self.set_dot_dir(dot_dir); self.set_dot_dir(dot_dir);
gst::log!(CAT, imp: imp, "dot-dir = {:?}", self.dot_dir); gst::log!(CAT, imp = imp, "dot-dir = {:?}", self.dot_dir);
} }
if let Ok(dot_prefix) = s.get("dot-prefix") { if let Ok(dot_prefix) = s.get("dot-prefix") {
@ -135,12 +136,18 @@ impl Settings {
} }
} }
#[derive(Default)] #[derive(Properties, Debug, Default)]
#[properties(wrapper_type = super::PipelineSnapshot)]
pub struct PipelineSnapshot { pub struct PipelineSnapshot {
#[property(name="dot-dir", get, set = Self::set_dot_dir, construct_only, type = String, member = dot_dir, blurb = "Directory where to place dot files")]
#[property(name="dot-prefix", get, set, type = String, member = dot_prefix, blurb = "Prefix for dot files")]
#[property(name="dot-ts", get, set, type = bool, member = dot_ts, blurb = "Add timestamp to dot files")]
settings: RwLock<Settings>,
pipelines: Arc<Mutex<HashMap<ElementPtr, glib::WeakRef<gst::Element>>>>, pipelines: Arc<Mutex<HashMap<ElementPtr, glib::WeakRef<gst::Element>>>>,
handles: Mutex<Option<Handles>>, handles: Mutex<Option<Handles>>,
} }
#[derive(Debug)]
struct Handles { struct Handles {
#[cfg(unix)] #[cfg(unix)]
signal: signal_hook::iterator::Handle, signal: signal_hook::iterator::Handle,
@ -154,12 +161,13 @@ impl ObjectSubclass for PipelineSnapshot {
type ParentType = gst::Tracer; type ParentType = gst::Tracer;
} }
#[glib::derived_properties]
impl ObjectImpl for PipelineSnapshot { impl ObjectImpl for PipelineSnapshot {
fn constructed(&self) { fn constructed(&self) {
let _ = START_TIME.as_ref(); let _ = START_TIME.as_ref();
self.parent_constructed(); self.parent_constructed();
let mut settings = Settings::default(); let mut settings = self.settings.write().unwrap();
if let Some(params) = self.obj().property::<Option<String>>("params") { if let Some(params) = self.obj().property::<Option<String>>("params") {
settings.update_from_params(self, params); settings.update_from_params(self, params);
} }
@ -167,11 +175,26 @@ impl ObjectImpl for PipelineSnapshot {
self.register_hook(TracerHook::ElementNew); self.register_hook(TracerHook::ElementNew);
self.register_hook(TracerHook::ObjectDestroyed); self.register_hook(TracerHook::ObjectDestroyed);
if let Err(err) = self.setup_signal(settings) { if let Err(err) = self.setup_signal() {
gst::warning!(CAT, imp = self, "failed to setup UNIX signals: {}", err); gst::warning!(CAT, imp = self, "failed to setup UNIX signals: {}", err);
} }
} }
fn signals() -> &'static [glib::subclass::Signal] {
static SIGNALS: Lazy<Vec<glib::subclass::Signal>> = Lazy::new(|| {
vec![glib::subclass::Signal::builder("snapshot")
.action()
.class_handler(|_, args| {
args[0].get::<super::PipelineSnapshot>().unwrap().snapshot();
None
})
.build()]
});
SIGNALS.as_ref()
}
fn dispose(&self) { fn dispose(&self) {
let mut handles = self.handles.lock().unwrap(); let mut handles = self.handles.lock().unwrap();
if let Some(handles) = handles.take() { if let Some(handles) = handles.take() {
@ -203,49 +226,81 @@ impl TracerImpl for PipelineSnapshot {
} }
impl PipelineSnapshot { impl PipelineSnapshot {
#[cfg(unix)] fn set_dot_dir(&self, dot_dir: Option<String>) {
fn setup_signal(&self, settings: Settings) -> anyhow::Result<()> { let mut settings = self.settings.write().unwrap();
use signal_hook::consts::signal::*; settings.set_dot_dir(dot_dir);
use signal_hook::iterator::Signals; }
let mut signals = Signals::new([SIGUSR1])?;
let signal_handle = signals.handle();
let tracer_weak = self.obj().downgrade();
let pipelines = self.pipelines.clone();
let thread_handle = std::thread::spawn(move || {
for signal in &mut signals {
match signal {
SIGUSR1 => {
let Some(tracer) = tracer_weak.upgrade() else {
break;
};
pub(crate) fn snapshot(&self) {
let pipelines = { let pipelines = {
let weaks = pipelines.lock().unwrap(); let weaks = self.pipelines.lock().unwrap();
weaks weaks
.values() .values()
.filter_map(|w| w.upgrade()) .filter_map(|w| w.upgrade())
.collect::<Vec<_>>() .collect::<Vec<_>>()
}; };
let settings = self.settings.read().unwrap();
let dot_dir = if let Some(dot_dir) = settings.dot_dir.as_ref() {
dot_dir
} else {
gst::info!(CAT, imp = self, "No dot-dir set, not dumping pipelines");
return;
};
for pipeline in pipelines.into_iter() { for pipeline in pipelines.into_iter() {
let pipeline = pipeline.downcast::<gst::Pipeline>().unwrap(); let pipeline = pipeline.downcast::<gst::Pipeline>().unwrap();
gst::debug!(CAT, obj = tracer, "dump {}", pipeline.name()); gst::debug!(CAT, imp = self, "dump {}", pipeline.name());
let dump_name = format!("{}{}", settings.dot_prefix, pipeline.name()); let dump_name = if settings.dot_ts {
format!(
if settings.dot_ts { "{}-{}{}",
pipeline.debug_to_dot_file_with_ts( gst::get_timestamp() - *START_TIME,
gst::DebugGraphDetails::all(), settings.dot_prefix.as_ref().map_or("", |s| s.as_str()),
&dump_name, pipeline.name()
); )
} else { } else {
pipeline format!(
.debug_to_dot_file(gst::DebugGraphDetails::all(), &dump_name); "{}{}",
settings.dot_prefix.as_ref().map_or("", |s| s.as_str()),
pipeline.name()
)
};
let dot_path = format!("{}/{}.dot", dot_dir, dump_name);
gst::debug!(CAT, imp = self, "Writing {}", dot_path);
match std::fs::File::create(&dot_path) {
Ok(mut f) => {
let data = pipeline.debug_to_dot_data(gst::DebugGraphDetails::all());
if let Err(e) = f.write_all(data.as_bytes()) {
gst::warning!(CAT, imp = self, "Failed to write {}: {}", dot_path, e);
} }
} }
Err(e) => {
gst::warning!(CAT, imp = self, "Failed to create {}: {}", dot_path, e);
}
}
}
}
#[cfg(unix)]
fn setup_signal(&self) -> anyhow::Result<()> {
use signal_hook::consts::signal::*;
use signal_hook::iterator::Signals;
let mut signals = Signals::new([SIGUSR1])?;
let signal_handle = signals.handle();
let this_weak = self.obj().downgrade();
let thread_handle = std::thread::spawn(move || {
for signal in &mut signals {
match signal {
SIGUSR1 => {
if let Some(this) = this_weak.upgrade() {
this.snapshot();
} else {
break;
};
} }
_ => unreachable!(), _ => unreachable!(),
} }

View file

@ -8,6 +8,7 @@
use gst::glib; use gst::glib;
use gst::prelude::*; use gst::prelude::*;
use gst::subclass::prelude::*;
mod imp; mod imp;
@ -15,6 +16,12 @@ glib::wrapper! {
pub struct PipelineSnapshot(ObjectSubclass<imp::PipelineSnapshot>) @extends gst::Tracer, gst::Object; pub struct PipelineSnapshot(ObjectSubclass<imp::PipelineSnapshot>) @extends gst::Tracer, gst::Object;
} }
impl PipelineSnapshot {
fn snapshot(&self) {
self.imp().snapshot()
}
}
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
gst::Tracer::register( gst::Tracer::register(
Some(plugin), Some(plugin),