Blob


1 use std::{
2 borrow::Cow,
3 io::{self, Write},
4 iter::FusedIterator,
5 time::UNIX_EPOCH,
6 };
8 use crate::s6::{ServiceDir, Status, SupervisedStatus};
10 fn escape(string: &str) -> Cow<str> {
11 let escapes = string
12 .chars()
13 .filter(|c| *c == '\n' || *c == '"' || *c == '\\')
14 .count();
15 if escapes == 0 {
16 Cow::Borrowed(string)
17 } else {
18 let mut escaped = String::with_capacity(string.len() + escapes);
19 for c in string.chars() {
20 match c {
21 '\n' => escaped.push_str("\\n"),
22 '"' => escaped.push_str("\\\""),
23 '\\' => escaped.push_str("\\\\"),
24 _ => escaped.push(c),
25 }
26 }
27 Cow::Owned(escaped)
28 }
29 }
31 pub struct Writer<'w, W: Write, I: Iterator<Item = ServiceDir>> {
32 writer: &'w mut W,
33 iter: I,
34 first: bool,
35 fused: bool,
36 }
38 static DESCRIPTORS: &[u8] = concat!(
39 "# TYPE s6_service_status stateset\n",
40 "# HELP s6_service_status Status of the service.\n",
41 "# TYPE s6_service_pid gauge\n",
42 "# HELP s6_service_pid Process id of the service.\n",
43 "# TYPE s6_service_up_down_since_seconds gauge\n",
44 "# HELP s6_service_up_down_since_seconds Since the service was up or down.\n",
45 "# UNIT s6_service_up_down_since_seconds seconds\n",
46 "# TYPE s6_service_ready_since_seconds gauge\n",
47 "# HELP s6_service_ready_since_seconds Since the service was ready.\n",
48 "# UNIT s6_service_ready_since_seconds seconds\n",
49 "# TYPE s6_service_exit_code stateset\n",
50 "# HELP s6_service_exit_code Exit code of the service.\n",
51 "# TYPE s6_service_signal_number stateset\n",
52 "# HELP s6_service_signal_number Signal number of the service.\n",
53 )
54 .as_bytes();
56 static EOF: &[u8] = b"# EOF\n";
58 impl<'w, W: Write, I: Iterator<Item = ServiceDir>> Writer<'w, W, I> {
59 pub fn new(writer: &'w mut W, iter: I) -> Self {
60 Self {
61 writer,
62 iter,
63 first: true,
64 fused: false,
65 }
66 }
68 fn write_descriptors(&mut self) -> io::Result<()> {
69 self.writer.write_all(DESCRIPTORS)
70 }
72 fn write_status(&mut self, service: &str, status: &Status) -> io::Result<()> {
73 match status {
74 Status::Unsupervised => self.write_unsupervised_status(service),
75 Status::Supervised(status) => self.write_supervised_status(service, status),
76 }
77 }
79 fn write_unsupervised_status(&mut self, service: &str) -> io::Result<()> {
80 writeln!(
81 self.writer,
82 "s6_service_status{{service=\"{service}\",s6_service_status=\"supervised\"}} 0"
83 )
84 }
86 fn write_supervised_status(
87 &mut self,
88 service: &str,
89 status: &SupervisedStatus,
90 ) -> io::Result<()> {
91 write!(
92 self.writer,
93 concat!(
94 "s6_service_status{{service=\"{}\",s6_service_status=\"supervised\"}} 1\n",
95 "s6_service_status{{service=\"{0}\",s6_service_status=\"up\"}} {}\n",
96 "s6_service_status{{service=\"{0}\",s6_service_status=\"wanted_up\"}} {}\n",
97 "s6_service_status{{service=\"{0}\",s6_service_status=\"normally_up\"}} {}\n",
98 "s6_service_status{{service=\"{0}\",s6_service_status=\"ready\"}} {}\n",
99 "s6_service_status{{service=\"{0}\",s6_service_status=\"paused\"}} {}\n",
100 "s6_service_pid{{service=\"{0}\"}} {}\n",
101 "s6_service_up_down_since_seconds{{service=\"{0}\"}} {}\n",
102 "s6_service_ready_since_seconds{{service=\"{0}\"}} {}\n",
103 ),
104 service,
105 if status.up { 1 } else { 0 },
106 if status.wanted_up { 1 } else { 0 },
107 if status.normally_up { 1 } else { 0 },
108 if status.ready { 1 } else { 0 },
109 if status.paused { 1 } else { 0 },
110 status.pid,
111 status
112 .up_down_since
113 .duration_since(UNIX_EPOCH)
114 .expect("positive timestamp")
115 .as_secs(),
116 status
117 .ready_since
118 .duration_since(UNIX_EPOCH)
119 .expect("positive timestamp")
120 .as_secs(),
121 )?;
122 if let Some(exit_code) = status.exit_code {
123 writeln!(
124 self.writer,
125 concat!("s6_service_exit_code{{service=\"{}\",s6_service_exit_code=\"{}\"}} 1"),
126 service, exit_code
127 )?
129 if let Some(signum) = status.signal_number {
130 writeln!(
131 self.writer,
132 concat!(
133 "s6_service_signal_number{{service=\"{}\",s6_service_signal_number=\"{}\"}} 1"
134 ),
135 service, signum
136 )?
138 Ok(())
141 fn write_eof(&mut self) -> io::Result<()> {
142 self.writer.write_all(EOF)
146 impl<'w, W: Write, I: Iterator<Item = ServiceDir>> Iterator for Writer<'w, W, I> {
147 type Item = io::Result<()>;
149 fn next(&mut self) -> Option<Self::Item> {
150 if self.fused {
151 None
152 } else {
153 self.iter
154 .next()
155 .map(|service_dir| {
156 let unescaped = service_dir.name().to_str().unwrap();
157 let service = escape(unescaped);
158 let status = service_dir.status()?;
159 if self.first {
160 self.first = false;
161 self.write_descriptors()?;
163 self.write_status(&service, &status)
164 })
165 .or_else(|| {
166 self.fused = true;
167 Some(self.write_eof())
168 })
173 impl<'w, W: Write, I: Iterator<Item = ServiceDir>> FusedIterator for Writer<'w, W, I> {}