initial commit

This commit is contained in:
soffee 2025-01-26 17:02:30 +03:00
commit 2e88d799b2
5 changed files with 1201 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

1023
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

11
Cargo.toml Normal file
View 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
View 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
View 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);
}
}
}