initial commit
This commit is contained in:
commit
2e88d799b2
5 changed files with 1201 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
1023
Cargo.lock
generated
Normal file
1023
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "mpris_exporter"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.95"
|
||||||
|
clap = { version = "4.5.27", features = ["derive"] }
|
||||||
|
mpris = "2.0.1"
|
||||||
|
prometheus = "0.13.4"
|
||||||
|
prometheus_exporter = "0.8.5"
|
||||||
121
src/exporter.rs
Normal file
121
src/exporter.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use mpris::PlayerFinder;
|
||||||
|
use prometheus_exporter::{
|
||||||
|
self,
|
||||||
|
prometheus::register_gauge_vec
|
||||||
|
};
|
||||||
|
use std::f64;
|
||||||
|
|
||||||
|
pub struct MediaPlayerExporter {
|
||||||
|
gauge_playback_status: prometheus::GaugeVec,
|
||||||
|
gauge_playback_position: prometheus::GaugeVec,
|
||||||
|
gauge_player_volume: prometheus::GaugeVec,
|
||||||
|
|
||||||
|
gauge_track_length: prometheus::GaugeVec,
|
||||||
|
gauge_track_album_info: prometheus::GaugeVec,
|
||||||
|
|
||||||
|
player_finder: PlayerFinder,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MediaPlayerExporter {
|
||||||
|
pub fn new() -> Result<Self> {
|
||||||
|
let gauge_playback_status = register_gauge_vec!(
|
||||||
|
"mpris_playback_status",
|
||||||
|
"Playback status of media player",
|
||||||
|
&["player", "bus_name"]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let gauge_playback_position = register_gauge_vec!(
|
||||||
|
"mpris_playback_position",
|
||||||
|
"Playback position of current track",
|
||||||
|
&["bus_name"]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let gauge_player_volume = register_gauge_vec!(
|
||||||
|
"mpris_player_volume",
|
||||||
|
"Volume of media player",
|
||||||
|
&["bus_name"]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let gauge_track_length = register_gauge_vec!(
|
||||||
|
"mpris_track_length",
|
||||||
|
"Length of currently playing track",
|
||||||
|
&["bus_name", "title", "artist", "url"]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let gauge_track_album_info = register_gauge_vec!(
|
||||||
|
"mpris_track_album_info",
|
||||||
|
"Album metadata of currently playing track",
|
||||||
|
&["bus_name", "name", "artist"]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let player_finder = PlayerFinder::new().context("Could not connect to D-Bus")?;
|
||||||
|
|
||||||
|
Ok(MediaPlayerExporter {
|
||||||
|
gauge_playback_status,
|
||||||
|
gauge_playback_position,
|
||||||
|
gauge_player_volume,
|
||||||
|
gauge_track_length,
|
||||||
|
gauge_track_album_info,
|
||||||
|
player_finder,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collect_metrics(&self) -> Result<()> {
|
||||||
|
let players = self.player_finder.find_all()?;
|
||||||
|
|
||||||
|
self.gauge_playback_status.reset();
|
||||||
|
self.gauge_playback_position.reset();
|
||||||
|
self.gauge_player_volume.reset();
|
||||||
|
self.gauge_track_length.reset();
|
||||||
|
self.gauge_track_album_info.reset();
|
||||||
|
|
||||||
|
for player in players {
|
||||||
|
self.gauge_playback_status.with_label_values(&[
|
||||||
|
player.identity(),
|
||||||
|
player.bus_name(),
|
||||||
|
]).set(player.get_playback_status()? as u8 as f64);
|
||||||
|
|
||||||
|
self.gauge_playback_position.with_label_values(&[
|
||||||
|
player.bus_name(),
|
||||||
|
]).set(player.get_position_in_microseconds()? as f64);
|
||||||
|
|
||||||
|
match player.get_volume() {
|
||||||
|
Ok(volume) => {
|
||||||
|
self.gauge_player_volume.with_label_values(&[
|
||||||
|
player.bus_name(),
|
||||||
|
]).set(volume);
|
||||||
|
},
|
||||||
|
Err(e) => println!("Error collecting volume from {}: {}", player.bus_name(), e),
|
||||||
|
}
|
||||||
|
|
||||||
|
let metadata = player.get_metadata()?;
|
||||||
|
|
||||||
|
let artists;
|
||||||
|
match metadata.artists() {
|
||||||
|
Some(v) => artists = v.join(", "),
|
||||||
|
None => artists = "Unknown".to_string(),
|
||||||
|
}
|
||||||
|
self.gauge_track_length.with_label_values(&[
|
||||||
|
player.bus_name(),
|
||||||
|
metadata.title().unwrap_or("Unknown"),
|
||||||
|
artists.as_str(),
|
||||||
|
metadata.url().unwrap_or(""),
|
||||||
|
]).set(metadata.length_in_microseconds().unwrap_or(0) as f64);
|
||||||
|
|
||||||
|
|
||||||
|
let album_artists;
|
||||||
|
match metadata.album_artists() {
|
||||||
|
Some(v) => album_artists = v.join(", "),
|
||||||
|
None => album_artists = "Unknown".to_string(),
|
||||||
|
}
|
||||||
|
self.gauge_track_album_info.with_label_values(&[
|
||||||
|
player.bus_name(),
|
||||||
|
metadata.album_name().unwrap_or("Unknown"),
|
||||||
|
album_artists.as_str(),
|
||||||
|
]).set(1f64);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/main.rs
Normal file
45
src/main.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
mod exporter;
|
||||||
|
use crate::exporter::MediaPlayerExporter;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
#[arg(short, long)]
|
||||||
|
bind_addr: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
let binding = handle_errors(args.bind_addr.parse().context("Failed to parse bind address"));
|
||||||
|
let exporter = handle_errors(prometheus_exporter::start(binding).context("Failed to start exporter http server"));
|
||||||
|
|
||||||
|
println!("Started http server on {}", binding);
|
||||||
|
|
||||||
|
let mpe = handle_errors(MediaPlayerExporter::new());
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let guard = exporter.wait_request();
|
||||||
|
|
||||||
|
handle_errors(mpe.collect_metrics().context("Failed to collect metrics"));
|
||||||
|
|
||||||
|
drop(guard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_errors<T>(r: Result<T>) -> T {
|
||||||
|
match r {
|
||||||
|
Ok(v) => return v,
|
||||||
|
Err(error) => {
|
||||||
|
println!("Error: {}", error);
|
||||||
|
for (i, cause) in error.chain().skip(1).enumerate() {
|
||||||
|
print!("{}", " ".repeat(i + 1));
|
||||||
|
println!("Caused by: {}", cause);
|
||||||
|
}
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue