1#![deny(clippy::all)]
16
17use std::collections::BTreeMap;
18use std::fmt::Display;
19use std::fmt::Formatter;
20use std::fmt::Write;
21
22mod default_configs;
23
24use common::open_source_shim;
25use common::util::convert_bytes;
26use common::util::convert_duration;
27use common::util::convert_freq;
28use common::util::fold_string;
29use model::Field;
30use model::Queriable;
31
32open_source_shim!();
33
34#[derive(Clone)]
36pub enum RenderFormat {
37 Precision(usize),
39 ReadableSize,
42 PageReadableSize,
45 SectorReadableSize,
48 MaxOrReadableSize,
51 ReadableFrequency,
53 Duration,
56 MaxOrDuration,
59}
60
61#[derive(Clone)]
63pub enum FoldOption {
64 Name,
66 Path,
68}
69
70#[derive(Default, Clone)]
73pub struct RenderConfig {
74 pub title: Option<String>,
75 pub format: Option<RenderFormat>,
77 pub indented_prefix: Option<String>,
85 pub suffix: Option<String>,
86 pub fold: Option<FoldOption>,
90 pub width: Option<usize>,
92}
93
94#[derive(Default, Clone)]
95pub struct RenderConfigBuilder {
96 rc: RenderConfig,
97}
98
99#[derive(Clone)]
100pub enum OpenMetricsType {
101 Counter,
105 Gauge,
108}
109
110#[derive(Clone)]
115pub struct RenderOpenMetricsConfig {
116 ty: OpenMetricsType,
117 help: Option<String>,
118 unit: Option<String>,
119 labels: BTreeMap<String, String>,
120}
121
122#[derive(Clone)]
123pub struct RenderOpenMetricsConfigBuilder {
124 config: RenderOpenMetricsConfig,
125}
126
127impl Display for OpenMetricsType {
128 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
129 match self {
130 OpenMetricsType::Counter => write!(f, "counter"),
131 OpenMetricsType::Gauge => write!(f, "gauge"),
132 }
133 }
134}
135
136impl RenderOpenMetricsConfigBuilder {
137 pub fn new(ty: OpenMetricsType) -> Self {
138 Self {
139 config: RenderOpenMetricsConfig {
140 ty,
141 help: None,
142 unit: None,
143 labels: BTreeMap::new(),
144 },
145 }
146 }
147
148 pub fn help(mut self, help: &str) -> Self {
150 self.config.help = Some(help.to_owned());
151 self
152 }
153
154 pub fn unit(mut self, unit: &str) -> Self {
156 self.config.unit = Some(unit.to_owned());
157 self
158 }
159
160 pub fn label(mut self, key: &str, value: &str) -> Self {
164 let mut value_escaped = String::with_capacity(value.len());
167 for c in value.chars() {
168 match c {
169 '\\' => value_escaped.push_str("\\\\"),
170 '\"' => value_escaped.push_str("\\\""),
171 '\n' => value_escaped.push_str("\\n"),
172 _ => value_escaped.push(c),
173 }
174 }
175
176 self.config.labels.insert(key.to_owned(), value_escaped);
177 self
178 }
179
180 pub fn build(self) -> RenderOpenMetricsConfig {
182 self.config
183 }
184}
185
186fn gauge() -> RenderOpenMetricsConfigBuilder {
187 RenderOpenMetricsConfigBuilder::new(OpenMetricsType::Gauge)
188}
189
190fn counter() -> RenderOpenMetricsConfigBuilder {
191 RenderOpenMetricsConfigBuilder::new(OpenMetricsType::Counter)
192}
193
194impl RenderOpenMetricsConfig {
195 fn normalize_key(&self, key: &str) -> String {
197 let mut ret = key.to_owned();
198 if let Some(unit) = &self.unit {
199 if !key.ends_with(unit) {
202 ret = format!("{}_{}", key, unit);
203 }
204 }
205 ret
206 }
207
208 fn render_field(&self, key: &str, field: Field, timestamp: i64) -> String {
209 let mut res = String::new();
210
211 let key = self.normalize_key(key);
212 let metric_type = self.ty.to_string();
213 let labels = if self.labels.is_empty() {
214 "".to_owned()
215 } else {
216 let body = self
217 .labels
218 .iter()
219 .map(|(k, v)| format!("{}=\"{}\"", k, v))
220 .collect::<Vec<_>>()
221 .join(",");
222 format!("{{{}}}", body)
223 };
224
225 writeln!(&mut res, "# TYPE {key} {metric_type}").unwrap();
227 if let Some(help) = &self.help {
228 writeln!(&mut res, "# HELP {key} {help}").unwrap();
229 }
230 if let Some(unit) = &self.unit {
231 writeln!(&mut res, "# UNIT {key} {unit}").unwrap();
232 }
233 writeln!(&mut res, "{key}{labels} {field} {timestamp}").unwrap();
234
235 res
236 }
237
238 pub fn render(&self, key: &str, field: Field, timestamp: i64) -> String {
240 match field {
241 Field::StrU64Map(map) => {
242 let mut res = String::new();
243 for (k, v) in map {
244 let key = format!("{}_{}", key, k);
245 let out = self.render_field(&key, Field::U64(v), timestamp);
246 res.push_str(&out);
247 }
248 res
249 }
250 _ => self.render_field(key, field, timestamp),
251 }
252 }
253}
254
255impl RenderConfigBuilder {
256 pub fn new() -> Self {
257 Default::default()
258 }
259 pub fn get(self) -> RenderConfig {
260 self.rc
261 }
262 pub fn title<T: AsRef<str>>(mut self, title: T) -> Self {
263 self.rc.title = Some(title.as_ref().to_owned());
264 self
265 }
266 pub fn format(mut self, format: RenderFormat) -> Self {
267 self.rc.format = Some(format);
268 self
269 }
270 pub fn indented_prefix<T: AsRef<str>>(mut self, indented_prefix: T) -> Self {
271 self.rc.indented_prefix = Some(indented_prefix.as_ref().to_owned());
272 self
273 }
274 pub fn suffix<T: AsRef<str>>(mut self, suffix: T) -> Self {
275 self.rc.suffix = Some(suffix.as_ref().to_owned());
276 self
277 }
278 pub fn fold(mut self, fold: FoldOption) -> Self {
279 self.rc.fold = Some(fold);
280 self
281 }
282 pub fn width(mut self, width: usize) -> Self {
283 self.rc.width = Some(width);
284 self
285 }
286}
287
288impl From<RenderConfigBuilder> for RenderConfig {
289 fn from(b: RenderConfigBuilder) -> Self {
290 b.rc
291 }
292}
293
294impl From<RenderConfig> for RenderConfigBuilder {
295 fn from(rc: RenderConfig) -> Self {
296 RenderConfigBuilder { rc }
297 }
298}
299
300pub fn get_fixed_width(val: &str, width: usize) -> String {
301 format!("{val:width$.width$}", val = val, width = width)
302}
303
304impl RenderConfig {
305 pub fn update<T: Into<Self>>(mut self, overrides: T) -> Self {
306 let overrides = overrides.into();
307 self.title = overrides.title.or(self.title);
308 self.format = overrides.format.or(self.format);
309 self.indented_prefix = overrides.indented_prefix.or(self.indented_prefix);
310 self.suffix = overrides.suffix.or(self.suffix);
311 self.fold = overrides.fold.or(self.fold);
312 self.width = overrides.width.or(self.width);
313 self
314 }
315
316 pub fn get_title(&self) -> &str {
317 self.title.as_deref().unwrap_or("unknown")
318 }
319
320 fn get_width(&self) -> usize {
323 const MIN_WIDTH: usize = 10;
324 std::cmp::max(MIN_WIDTH, self.width.unwrap_or(self.get_title().len() + 2))
325 }
326
327 pub fn render_title(&self, fixed_width: bool) -> String {
328 if fixed_width {
329 get_fixed_width(self.get_title(), self.get_width())
330 } else {
331 self.get_title().to_owned()
332 }
333 }
334
335 fn format(&self, field: Field) -> String {
337 use RenderFormat::*;
338 match &self.format {
339 Some(format) => match format {
340 Precision(precision) => format!("{:.precision$}", field, precision = precision),
341 ReadableSize => convert_bytes(f64::from(field)),
342 PageReadableSize => convert_bytes(4096.0 * f64::from(field)),
343 SectorReadableSize => convert_bytes(512.0 * f64::from(field)),
344 MaxOrReadableSize => {
345 let field = i64::from(field);
346 if field == -1 {
347 "max".to_owned()
348 } else {
349 convert_bytes(field as f64)
350 }
351 }
352 ReadableFrequency => convert_freq(u64::from(field)),
353 Duration => {
354 let field = u64::from(field);
355 convert_duration(field)
356 }
357 MaxOrDuration => {
358 let field = i64::from(field);
359 if field == -1 {
360 "max".to_owned()
361 } else {
362 convert_duration(field as u64)
363 }
364 }
365 },
366 None => field.to_string(),
367 }
368 }
369
370 fn fold_str(&self, val: &str, width: usize) -> String {
371 match self.fold {
372 Some(FoldOption::Name) => fold_string(val, width, 0, |c: char| !c.is_alphanumeric()),
373 Some(FoldOption::Path) => fold_string(val, width, 1, |c: char| c == '/'),
374 None => val.to_owned(),
375 }
376 }
377
378 pub fn render_indented(&self, field: Option<Field>, fixed_width: bool, depth: usize) -> String {
382 let res = match field {
383 Some(field) => self.format(field),
384 None => {
385 return if fixed_width {
386 get_fixed_width("?", self.get_width())
387 } else {
388 "?".to_owned()
389 };
390 }
391 };
392 let indented_prefix = self.indented_prefix.as_deref().unwrap_or("");
393 let suffix = self.suffix.as_deref().unwrap_or("");
394 let indented_prefix_len = indented_prefix.chars().count();
396 let suffix_len = suffix.chars().count();
397 let indented_prefix_width = indented_prefix_len * depth;
399 if fixed_width {
400 let remain_len = self
403 .get_width()
404 .saturating_sub(indented_prefix_width + suffix_len);
405 let res = self.fold_str(&res, remain_len);
406 let res = format!(
407 "{:>prefix_width$.prefix_width$}{}{}",
408 indented_prefix,
409 res,
410 suffix,
411 prefix_width = indented_prefix_width
412 );
413 get_fixed_width(&res, self.get_width())
414 } else {
415 format!(
416 "{:>prefix_width$.prefix_width$}{}{}",
417 indented_prefix,
418 res,
419 suffix,
420 prefix_width = indented_prefix_width
421 )
422 }
423 }
424
425 pub fn render(&self, field: Option<Field>, fixed_width: bool) -> String {
427 self.render_indented(field, fixed_width, 0)
428 }
429}
430
431pub trait HasRenderConfig: Queriable {
433 fn get_render_config_builder(field_id: &Self::FieldId) -> RenderConfigBuilder;
434 fn get_render_config(field_id: &Self::FieldId) -> RenderConfig {
435 Self::get_render_config_builder(field_id).get()
436 }
437}
438
439pub trait HasRenderConfigForDump: HasRenderConfig {
440 fn get_render_config_for_dump(field_id: &Self::FieldId) -> RenderConfig {
441 Self::get_render_config(field_id)
442 }
443
444 fn get_openmetrics_config_for_dump(
449 &self,
450 _field_id: &Self::FieldId,
451 ) -> Option<RenderOpenMetricsConfigBuilder>;
452}
453
454#[test]
455fn test_openmetrics_gauge() {
456 let config = RenderOpenMetricsConfigBuilder::new(OpenMetricsType::Gauge)
457 .help("gauge help")
458 .build();
459 let text = config.render("my_key", Field::U64(123), 1234);
460 let expected = r#"# TYPE my_key gauge
461# HELP my_key gauge help
462my_key 123 1234
463"#;
464 assert_eq!(text, expected);
465}
466
467#[test]
468fn test_openmetrics_counter() {
469 let config = RenderOpenMetricsConfigBuilder::new(OpenMetricsType::Counter)
470 .help("counter help")
471 .build();
472 let text = config.render("my_key", Field::F32(1.23), 1234);
473 let expected = r#"# TYPE my_key counter
474# HELP my_key counter help
475my_key 1.23 1234
476"#;
477 assert_eq!(text, expected);
478}
479
480#[test]
482fn test_openmetrics_unit() {
483 let config = RenderOpenMetricsConfigBuilder::new(OpenMetricsType::Counter)
484 .unit("foobars")
485 .build();
486 let text = config.render("my_key", Field::F32(1.23), 1234);
487 let expected = r#"# TYPE my_key_foobars counter
488# UNIT my_key_foobars foobars
489my_key_foobars 1.23 1234
490"#;
491 assert_eq!(text, expected);
492}
493
494#[test]
496fn test_openmetrics_unit_exists() {
497 let config = RenderOpenMetricsConfigBuilder::new(OpenMetricsType::Counter)
498 .unit("foobars")
499 .build();
500 let text = config.render("my_key_foobars", Field::F32(1.23), 1234);
501 let expected = r#"# TYPE my_key_foobars counter
502# UNIT my_key_foobars foobars
503my_key_foobars 1.23 1234
504"#;
505 assert_eq!(text, expected);
506}
507
508#[test]
509fn test_openmetrics_label() {
510 let config = RenderOpenMetricsConfigBuilder::new(OpenMetricsType::Counter)
511 .help("counter help")
512 .label("label1", "value1")
513 .build();
514 let text = config.render("my_key", Field::F32(1.23), 1234);
515 let expected = r#"# TYPE my_key counter
516# HELP my_key counter help
517my_key{label1="value1"} 1.23 1234
518"#;
519 assert_eq!(text, expected);
520}
521
522#[test]
523fn test_openmetrics_label_escaped_quotes() {
524 let config = RenderOpenMetricsConfigBuilder::new(OpenMetricsType::Counter)
525 .help("counter help")
526 .label("label1", r#"quotes""between"#)
527 .build();
528 let text = config.render("my_key", Field::F32(1.23), 1234);
529 let expected = r#"# TYPE my_key counter
530# HELP my_key counter help
531my_key{label1="quotes\"\"between"} 1.23 1234
532"#;
533 assert_eq!(text, expected);
534}
535
536#[test]
537fn test_openmetrics_label_escaped_newline() {
538 let config = RenderOpenMetricsConfigBuilder::new(OpenMetricsType::Counter)
539 .help("counter help")
540 .label("label1", "newline\nbetween")
541 .build();
542 let text = config.render("my_key", Field::F32(1.23), 1234);
543 let expected = r#"# TYPE my_key counter
544# HELP my_key counter help
545my_key{label1="newline\nbetween"} 1.23 1234
546"#;
547 assert_eq!(text, expected);
548}
549
550#[test]
551fn test_openmetrics_label_escaped_backslash() {
552 let config = RenderOpenMetricsConfigBuilder::new(OpenMetricsType::Counter)
553 .help("counter help")
554 .label("label1", r#"newline\between"#)
555 .build();
556 let text = config.render("my_key", Field::F32(1.23), 1234);
557 let expected = r#"# TYPE my_key counter
558# HELP my_key counter help
559my_key{label1="newline\\between"} 1.23 1234
560"#;
561 assert_eq!(text, expected);
562}
563
564#[test]
565fn test_openmetrics_label_escaped_newline_and_backslash() {
566 let config = RenderOpenMetricsConfigBuilder::new(OpenMetricsType::Counter)
567 .help("counter help")
568 .label("label1", "newline\\\nbetween")
569 .build();
570 let text = config.render("my_key", Field::F32(1.23), 1234);
571 let expected = r#"# TYPE my_key counter
572# HELP my_key counter help
573my_key{label1="newline\\\nbetween"} 1.23 1234
574"#;
575 assert_eq!(text, expected);
576}
577
578#[test]
579fn test_openmetrics_labels() {
580 let config = RenderOpenMetricsConfigBuilder::new(OpenMetricsType::Counter)
581 .help("counter help")
582 .label("label1", "value1")
583 .label("label2", "value2")
584 .label("label3", "zzz")
585 .build();
586 let text = config.render("my_key", Field::F32(1.23), 1234);
587 let expected = r#"# TYPE my_key counter
588# HELP my_key counter help
589my_key{label1="value1",label2="value2",label3="zzz"} 1.23 1234
590"#;
591 assert_eq!(text, expected);
592}