Commit Diff


commit - f41a8ff739d22ac29d0fd238f209a0f68995c9d9
commit + f1abd5434f0239fd4c8fb2bc97d01a69da79e528
blob - d0490dd0d7296458eb055fdedc681d76dd95a5ff
blob + 4d59ee624b6ece9c5272a292ba3f21eea6d1cb7d
--- src/lib.rs
+++ src/lib.rs
@@ -1,2 +1,3 @@
 pub mod metrics;
 pub mod s6;
+mod tai;
blob - c58c1bc412b83b3fa7cabbcc6b500f807b2e5ea1
blob + 86a23cf98588dad1cef5194f9335c4c2cfab2ec1
--- src/metrics.rs
+++ src/metrics.rs
@@ -43,6 +43,12 @@ static DESCRIPTORS: &[u8] = concat!(
     "# HELP s6_service_exitcode Exit code of the service.\n",
     "# TYPE s6_service_signum stateset\n",
     "# HELP s6_service_signum Signal number of the service.\n",
+    "# TYPE s6_service_updownsince_seconds gauge\n",
+    "# HELP s6_service_updownsince_seconds Since the service was up or down.\n",
+    "# UNIT s6_service_updownsince_seconds seconds\n",
+    "# TYPE s6_service_readysince_seconds gauge\n",
+    "# HELP s6_service_readysince_seconds Since the service was ready.\n",
+    "# UNIT s6_service_readysince_seconds seconds\n",
 )
 .as_bytes();
 
@@ -91,6 +97,8 @@ impl<'w, W: Write, I: Iterator<Item = ServiceDir>> Wri
                 "s6_service_status{{service=\"{0}\",s6_service_status=\"paused\"}} {}\n",
                 "s6_service_status{{service=\"{0}\",s6_service_status=\"normallyup\"}} {}\n",
                 "s6_service_pid{{service=\"{0}\"}} {}\n",
+                "s6_service_updownsince_seconds{{service=\"{0}\"}} {}\n",
+                "s6_service_readysince_seconds{{service=\"{0}\"}} {}\n",
             ),
             service,
             if status.up { 1 } else { 0 },
@@ -99,6 +107,8 @@ impl<'w, W: Write, I: Iterator<Item = ServiceDir>> Wri
             if status.paused { 1 } else { 0 },
             if status.normallyup { 1 } else { 0 },
             status.pid,
+            status.updownsince.as_secs(),
+            status.readysince.as_secs(),
         )?;
         if let Some(exitcode) = status.exitcode {
             writeln!(
blob - c9dd9c521a76cc3c9d97cb208735a71cc227070d
blob + a7dc88f478952bbe005f95a24e45c686a60dcf09
--- src/s6.rs
+++ src/s6.rs
@@ -9,8 +9,11 @@ use std::{
     path::{Path, PathBuf},
     process::ExitStatus,
     ptr::addr_of_mut,
+    time::Duration,
 };
 
+use crate::tai::Tain;
+
 fn locked(fd: BorrowedFd) -> io::Result<bool> {
     let mut flock = libc::flock {
         l_type: libc::F_RDLCK as libc::c_short,
@@ -27,6 +30,8 @@ fn locked(fd: BorrowedFd) -> io::Result<bool> {
 }
 
 struct SvStatus {
+    stamp: Duration,
+    ready_stamp: Duration,
     pid: u64,
     wstat: ExitStatus,
     paused: bool,
@@ -38,6 +43,8 @@ struct SvStatus {
 impl From<[u8; 35]> for SvStatus {
     fn from(pack: [u8; 35]) -> Self {
         Self {
+            stamp: Tain::from_be_bytes(pack[0..12].try_into().expect("12 bytes")).into(),
+            ready_stamp: Tain::from_be_bytes(pack[12..24].try_into().expect("12 bytes")).into(),
             pid: u64::from_be_bytes(pack[24..32].try_into().expect("8 bytes")),
             wstat: ExitStatus::from_raw(u16::from_be_bytes(
                 pack[32..34].try_into().expect("2 bytes"),
@@ -69,8 +76,8 @@ pub struct SupervisedStatus {
     pub normallyup: bool,
     pub exitcode: Option<i32>,
     pub signum: Option<i32>,
-    // tain, stamp, readystamp
-    // seconds, upseconds, readyseconds
+    pub updownsince: Duration,
+    pub readysince: Duration,
     // signal
 }
 
@@ -130,6 +137,8 @@ impl ServiceDir {
                     status.wstat.signal()
                 },
                 normallyup,
+                updownsince: status.stamp,
+                readysince: status.ready_stamp,
             }))
         } else {
             Ok(Status::Unsupervised)
blob - /dev/null
blob + becc759a36616afb1054bb276b82aa6b12787674 (mode 644)
--- /dev/null
+++ src/tai.rs
@@ -0,0 +1,31 @@
+use std::time::Duration;
+
+struct Tai(u64);
+
+impl Tai {
+    const MAGIC: u64 = 4611686018427387904;
+    const EPOCH: u64 = Self::MAGIC + 10;
+    fn from_be_bytes(pack: [u8; 8]) -> Self {
+        Self(u64::from_be_bytes(pack))
+    }
+}
+
+pub(crate) struct Tain {
+    sec: Tai,
+    nano: u32,
+}
+
+impl Tain {
+    pub(crate) fn from_be_bytes(pack: [u8; 12]) -> Self {
+        Self {
+            sec: Tai::from_be_bytes(pack[0..8].try_into().expect("8 bytes")),
+            nano: u32::from_be_bytes(pack[8..12].try_into().expect("4 bytes")),
+        }
+    }
+}
+
+impl From<Tain> for Duration {
+    fn from(tain: Tain) -> Self {
+        Self::new(tain.sec.0 - Tai::EPOCH, tain.nano)
+    }
+}