prometheus-client-mmap 0.24.3 → 0.24.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a48a0240227250684dd1f0208b377fdf5c9af1d45e874f2c33d3a3597a99f3e2
4
- data.tar.gz: 13f39fe791dee05e33d5c5b74bbfca6020eaa25bed259434e21d2ab060fb6fe1
3
+ metadata.gz: 66c6ba03c473f782b6e00d5c138f11fda68c2e127b0fc325a49f55a741b50df5
4
+ data.tar.gz: f8c9f2e3e99e96e373cf2ef595f33abb73c5d6ae45c9ac2b7f02f8932da41362
5
5
  SHA512:
6
- metadata.gz: 18bc37ead65d8ef6f5c87e9473d742c4c62673da88651416ac727c5790db2f81de20ca5a3dc48e45098d2c5d0892bdbd10bcc9013c95fcb409d49450e08550ad
7
- data.tar.gz: c0eacc9535069bb51a96c3d636ccf750f15d54c570478f1e3127bcbe3ad83c64dea7c2d805f684da317e0791ecc4c5b86e813a2037e95b06c8c460120c084126
6
+ metadata.gz: 66529e56dfa0110bbc7fafb1f1145c807f7daba272483a75ae752418f7a0dcc5b66a98d122c14ae3bfb390d0195ebc5a3109c9540c11dfae776a49a414950057
7
+ data.tar.gz: c13e0e6bc6e941b5a81968002d0e1666454fdc69e3cb1293b2711407b1d96dfa2e44d07aec725fda33e259896c656e5690d4bcd14cf8f96fda4a37b14218e61f
@@ -169,6 +169,8 @@ dependencies = [
169
169
  "nix",
170
170
  "rand",
171
171
  "rb-sys",
172
+ "serde",
173
+ "serde_json",
172
174
  "sha2",
173
175
  "smallvec",
174
176
  "tempfile",
@@ -252,6 +254,12 @@ dependencies = [
252
254
  "windows-sys 0.48.0",
253
255
  ]
254
256
 
257
+ [[package]]
258
+ name = "itoa"
259
+ version = "1.0.6"
260
+ source = "registry+https://github.com/rust-lang/crates.io-index"
261
+ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
262
+
255
263
  [[package]]
256
264
  name = "lazy_static"
257
265
  version = "1.4.0"
@@ -515,6 +523,12 @@ dependencies = [
515
523
  "windows-sys 0.48.0",
516
524
  ]
517
525
 
526
+ [[package]]
527
+ name = "ryu"
528
+ version = "1.0.13"
529
+ source = "registry+https://github.com/rust-lang/crates.io-index"
530
+ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
531
+
518
532
  [[package]]
519
533
  name = "seq-macro"
520
534
  version = "0.3.3"
@@ -526,6 +540,31 @@ name = "serde"
526
540
  version = "1.0.159"
527
541
  source = "registry+https://github.com/rust-lang/crates.io-index"
528
542
  checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065"
543
+ dependencies = [
544
+ "serde_derive",
545
+ ]
546
+
547
+ [[package]]
548
+ name = "serde_derive"
549
+ version = "1.0.159"
550
+ source = "registry+https://github.com/rust-lang/crates.io-index"
551
+ checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585"
552
+ dependencies = [
553
+ "proc-macro2",
554
+ "quote",
555
+ "syn 2.0.13",
556
+ ]
557
+
558
+ [[package]]
559
+ name = "serde_json"
560
+ version = "1.0.96"
561
+ source = "registry+https://github.com/rust-lang/crates.io-index"
562
+ checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
563
+ dependencies = [
564
+ "itoa",
565
+ "ryu",
566
+ "serde",
567
+ ]
529
568
 
530
569
  [[package]]
531
570
  name = "sha2"
@@ -555,6 +594,9 @@ name = "smallvec"
555
594
  version = "1.10.0"
556
595
  source = "registry+https://github.com/rust-lang/crates.io-index"
557
596
  checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
597
+ dependencies = [
598
+ "serde",
599
+ ]
558
600
 
559
601
  [[package]]
560
602
  name = "syn"
@@ -13,7 +13,9 @@ memmap2 = "0.5"
13
13
  # v0.26 cannot be built on CentOS 7 https://github.com/nix-rust/nix/issues/1972
14
14
  nix = { version = "0.25", features = ["mman"] } # mman used for MsFlags
15
15
  rb-sys = "0.9"
16
- smallvec = "1.10"
16
+ serde = { version = "1.0", features = ["derive"] }
17
+ serde_json = { version = "1.0", features = ["raw_value"] }
18
+ smallvec = { version = "1.10", features = ["serde"] }
17
19
  thiserror = "1.0"
18
20
 
19
21
  [dev-dependencies]
@@ -1,10 +1,12 @@
1
1
  use magnus::Symbol;
2
+ use serde::Deserialize;
3
+ use serde_json::value::RawValue;
4
+ use smallvec::SmallVec;
2
5
  use std::fmt::Write;
3
6
  use std::str;
4
7
 
5
8
  use crate::error::{MmapError, RubyError};
6
9
  use crate::file_info::FileInfo;
7
- use crate::parser::{self, MetricText};
8
10
  use crate::raw_entry::RawEntry;
9
11
  use crate::Result;
10
12
  use crate::{SYM_GAUGE, SYM_LIVESUM, SYM_MAX, SYM_MIN};
@@ -16,6 +18,16 @@ pub struct FileEntry {
16
18
  pub meta: EntryMetadata,
17
19
  }
18
20
 
21
+ /// String slices pointing to the fields of a borrowed `Entry`'s JSON data.
22
+ #[derive(Deserialize, Debug)]
23
+ pub struct MetricText<'a> {
24
+ pub family_name: &'a str,
25
+ pub metric_name: &'a str,
26
+ pub labels: SmallVec<[&'a str; 4]>,
27
+ #[serde(borrow)]
28
+ pub values: SmallVec<[&'a RawValue; 4]>,
29
+ }
30
+
19
31
  /// The primary data payload for a `FileEntry`, the JSON string and the
20
32
  /// associated pid, if significant. Used as the key for `EntryMap`.
21
33
  #[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
@@ -134,11 +146,16 @@ impl FileEntry {
134
146
  let mut processed_count = 0;
135
147
 
136
148
  for entry in entries {
137
- let metrics_data = match parser::parse_metrics(&entry.data.json) {
138
- Some(m) => m,
149
+ let metrics_data = match serde_json::from_str::<MetricText>(&entry.data.json) {
150
+ Ok(m) => {
151
+ if m.labels.len() != m.values.len() {
152
+ continue;
153
+ }
154
+ m
155
+ }
139
156
  // We don't exit the function here so the total number of invalid
140
157
  // entries can be calculated below.
141
- None => continue,
158
+ Err(_) => continue,
142
159
  };
143
160
 
144
161
  match prev_name.as_ref() {
@@ -197,16 +214,20 @@ impl FileEntry {
197
214
 
198
215
  let it = json_data.labels.iter().zip(json_data.values.iter());
199
216
 
200
- for (i, (&key, &val)) in it.enumerate() {
217
+ for (i, (&key, val)) in it.enumerate() {
201
218
  out.push_str(key);
202
-
203
- out.push_str("=\"");
204
-
205
- // `null` values will be output as `""`.
206
- if val != "null" {
207
- out.push_str(val);
219
+ out.push('=');
220
+
221
+ match val.get() {
222
+ "null" => out.push_str("\"\""),
223
+ s if s.starts_with('"') => out.push_str(s),
224
+ s => {
225
+ // Quote numeric values.
226
+ out.push('"');
227
+ out.push_str(s);
228
+ out.push('"');
229
+ }
208
230
  }
209
- out.push('"');
210
231
 
211
232
  if i < json_data.labels.len() - 1 {
212
233
  out.push(',');
@@ -276,6 +297,20 @@ mod test {
276
297
  "##}),
277
298
  expected_err: None,
278
299
  },
300
+ TestCase {
301
+ name: "many labels",
302
+ multiprocess_mode: "min",
303
+ json: &[
304
+ r#"["family","name",["label_a","label_b","label_c","label_d","label_e"],["value_a","value_b","value_c","value_d","value_e"]]"#,
305
+ ],
306
+ values: &[1.0],
307
+ pids: &["worker-1"],
308
+ expected_out: Some(indoc! {r##"# HELP family Multiprocess metric
309
+ # TYPE family gauge
310
+ name{label_a="value_a",label_b="value_b",label_c="value_c",label_d="value_d",label_e="value_e"} 1
311
+ "##}),
312
+ expected_err: None,
313
+ },
279
314
  TestCase {
280
315
  name: "floating point shown",
281
316
  multiprocess_mode: "min",
@@ -288,6 +323,44 @@ mod test {
288
323
  "##}),
289
324
  expected_err: None,
290
325
  },
326
+ TestCase {
327
+ name: "numeric value",
328
+ multiprocess_mode: "min",
329
+ json: &[
330
+ r#"["family","name",["label_a","label_b","label_c"],["value_a",403,-0.2E5]]"#,
331
+ ],
332
+ values: &[1.5],
333
+ pids: &["worker-1"],
334
+ expected_out: Some(indoc! {r##"# HELP family Multiprocess metric
335
+ # TYPE family gauge
336
+ name{label_a="value_a",label_b="403",label_c="-0.2E5"} 1.5
337
+ "##}),
338
+ expected_err: None,
339
+ },
340
+ TestCase {
341
+ name: "null value",
342
+ multiprocess_mode: "min",
343
+ json: &[r#"["family","name",["label_a","label_b"],["value_a",null]]"#],
344
+ values: &[1.5],
345
+ pids: &["worker-1"],
346
+ expected_out: Some(indoc! {r##"# HELP family Multiprocess metric
347
+ # TYPE family gauge
348
+ name{label_a="value_a",label_b=""} 1.5
349
+ "##}),
350
+ expected_err: None,
351
+ },
352
+ TestCase {
353
+ name: "comma in value",
354
+ multiprocess_mode: "min",
355
+ json: &[r#"["family","name",["label_a","label_b"],["value_a","value,_b"]]"#],
356
+ values: &[1.5],
357
+ pids: &["worker-1"],
358
+ expected_out: Some(indoc! {r##"# HELP family Multiprocess metric
359
+ # TYPE family gauge
360
+ name{label_a="value_a",label_b="value,_b"} 1.5
361
+ "##}),
362
+ expected_err: None,
363
+ },
291
364
  TestCase {
292
365
  name: "no labels, pid significant",
293
366
  multiprocess_mode: "all",
@@ -413,6 +486,138 @@ mod test {
413
486
  RubyError::Runtime,
414
487
  )),
415
488
  },
489
+ TestCase {
490
+ name: "too many values",
491
+ multiprocess_mode: "min",
492
+ json: &[r#"["family","name",["label_a"],["value_a","value,_b"]]"#],
493
+ values: &[1.5],
494
+ pids: &["worker-1"],
495
+ expected_out: None,
496
+ expected_err: Some(MmapError::legacy(
497
+ "Processed entries 0 != map entries 1".to_owned(),
498
+ RubyError::Runtime,
499
+ )),
500
+ },
501
+ TestCase {
502
+ name: "no values",
503
+ multiprocess_mode: "min",
504
+ json: &[r#"["family","name",["label_a"]]"#],
505
+ values: &[1.5],
506
+ pids: &["worker-1"],
507
+ expected_out: None,
508
+ expected_err: Some(MmapError::legacy(
509
+ "Processed entries 0 != map entries 1".to_owned(),
510
+ RubyError::Runtime,
511
+ )),
512
+ },
513
+ TestCase {
514
+ name: "no labels or values",
515
+ multiprocess_mode: "min",
516
+ json: &[r#"["family","name","foo"]"#],
517
+ values: &[1.5],
518
+ pids: &["worker-1"],
519
+ expected_out: None,
520
+ expected_err: Some(MmapError::legacy(
521
+ "Processed entries 0 != map entries 1".to_owned(),
522
+ RubyError::Runtime,
523
+ )),
524
+ },
525
+ TestCase {
526
+ name: "too many leading brackets",
527
+ multiprocess_mode: "min",
528
+ json: &[r#"[["family","name",["label_a","label_b"],["value_a","value_b"]]"#],
529
+ values: &[1.5],
530
+ pids: &["worker-1"],
531
+ expected_out: None,
532
+ expected_err: Some(MmapError::legacy(
533
+ "Processed entries 0 != map entries 1".to_owned(),
534
+ RubyError::Runtime,
535
+ )),
536
+ },
537
+ TestCase {
538
+ name: "too many trailing brackets",
539
+ multiprocess_mode: "min",
540
+ json: &[r#"["family","name",["label_a","label_b"],["value_a","value_b"]]]"#],
541
+ values: &[1.5],
542
+ pids: &["worker-1"],
543
+ expected_out: None,
544
+ expected_err: Some(MmapError::legacy(
545
+ "Processed entries 0 != map entries 1".to_owned(),
546
+ RubyError::Runtime,
547
+ )),
548
+ },
549
+ TestCase {
550
+ name: "too many leading label brackets",
551
+ multiprocess_mode: "min",
552
+ json: &[r#"["family","name",[["label_a","label_b"],["value_a","value_b"]]"#],
553
+ values: &[1.5],
554
+ pids: &["worker-1"],
555
+ expected_out: None,
556
+ expected_err: Some(MmapError::legacy(
557
+ "Processed entries 0 != map entries 1".to_owned(),
558
+ RubyError::Runtime,
559
+ )),
560
+ },
561
+ TestCase {
562
+ name: "too many leading label brackets",
563
+ multiprocess_mode: "min",
564
+ json: &[r#"["family","name",[["label_a","label_b"],["value_a","value_b"]]"#],
565
+ values: &[1.5],
566
+ pids: &["worker-1"],
567
+ expected_out: None,
568
+ expected_err: Some(MmapError::legacy(
569
+ "Processed entries 0 != map entries 1".to_owned(),
570
+ RubyError::Runtime,
571
+ )),
572
+ },
573
+ TestCase {
574
+ name: "too many leading value brackets",
575
+ multiprocess_mode: "min",
576
+ json: &[r#"["family","name",["label_a","label_b"],[["value_a","value_b"]]"#],
577
+ values: &[1.5],
578
+ pids: &["worker-1"],
579
+ expected_out: None,
580
+ expected_err: Some(MmapError::legacy(
581
+ "Processed entries 0 != map entries 1".to_owned(),
582
+ RubyError::Runtime,
583
+ )),
584
+ },
585
+ TestCase {
586
+ name: "misplaced bracket",
587
+ multiprocess_mode: "min",
588
+ json: &[r#"["family","name",["label_a","label_b"],]["value_a","value_b"]]"#],
589
+ values: &[1.5],
590
+ pids: &["worker-1"],
591
+ expected_out: None,
592
+ expected_err: Some(MmapError::legacy(
593
+ "Processed entries 0 != map entries 1".to_owned(),
594
+ RubyError::Runtime,
595
+ )),
596
+ },
597
+ TestCase {
598
+ name: "comma in numeric",
599
+ multiprocess_mode: "min",
600
+ json: &[r#"["family","name",["label_a","label_b"],["value_a",403,0]]"#],
601
+ values: &[1.5],
602
+ pids: &["worker-1"],
603
+ expected_out: None,
604
+ expected_err: Some(MmapError::legacy(
605
+ "Processed entries 0 != map entries 1".to_owned(),
606
+ RubyError::Runtime,
607
+ )),
608
+ },
609
+ TestCase {
610
+ name: "non-e letter in numeric",
611
+ multiprocess_mode: "min",
612
+ json: &[r#"["family","name",["label_a","label_b"],["value_a",-2.0c5]]"#],
613
+ values: &[1.5],
614
+ pids: &["worker-1"],
615
+ expected_out: None,
616
+ expected_err: Some(MmapError::legacy(
617
+ "Processed entries 0 != map entries 1".to_owned(),
618
+ RubyError::Runtime,
619
+ )),
620
+ },
416
621
  ];
417
622
 
418
623
  for case in tc {
@@ -12,7 +12,6 @@ pub mod file_info;
12
12
  mod macros;
13
13
  pub mod map;
14
14
  pub mod mmap;
15
- pub mod parser;
16
15
  pub mod raw_entry;
17
16
  pub mod util;
18
17
 
@@ -1,5 +1,5 @@
1
1
  module Prometheus
2
2
  module Client
3
- VERSION = '0.24.3'.freeze
3
+ VERSION = '0.24.5'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prometheus-client-mmap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.3
4
+ version: 0.24.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Schmidt
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2023-05-20 00:00:00.000000000 Z
14
+ date: 2023-06-13 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rb_sys
@@ -161,7 +161,6 @@ files:
161
161
  - ext/fast_mmaped_file_rs/src/map.rs
162
162
  - ext/fast_mmaped_file_rs/src/mmap.rs
163
163
  - ext/fast_mmaped_file_rs/src/mmap/inner.rs
164
- - ext/fast_mmaped_file_rs/src/parser.rs
165
164
  - ext/fast_mmaped_file_rs/src/raw_entry.rs
166
165
  - ext/fast_mmaped_file_rs/src/testhelper.rs
167
166
  - ext/fast_mmaped_file_rs/src/util.rs
@@ -1,346 +0,0 @@
1
- use smallvec::SmallVec;
2
- use std::str;
3
-
4
- /// String slices pointing to the fields of a borrowed `Entry`'s JSON data.
5
- #[derive(PartialEq, Eq, Debug)]
6
- pub struct MetricText<'a> {
7
- pub family_name: &'a str,
8
- pub metric_name: &'a str,
9
- pub labels: SmallVec<[&'a str; 4]>,
10
- pub values: SmallVec<[&'a str; 4]>,
11
- }
12
-
13
- #[derive(PartialEq, Eq, Debug)]
14
- struct MetricNames<'a> {
15
- label_json: &'a str,
16
- family_name: &'a str,
17
- metric_name: &'a str,
18
- }
19
-
20
- #[derive(PartialEq, Eq, Debug)]
21
- struct MetricLabelVals<'a> {
22
- labels: SmallVec<[&'a str; 4]>,
23
- values: SmallVec<[&'a str; 4]>,
24
- }
25
-
26
- /// Parse Prometheus metric data stored in the following format:
27
- ///
28
- /// ["metric","name",["label_a","label_b"],["value_a","value_b"]]
29
- ///
30
- /// There will be 1-8 trailing spaces to ensure at least a one-byte
31
- /// gap between the json and value, and to pad to an 8-byte alignment.
32
- /// We strip the surrounding double quotes from all items. Values may
33
- /// or may not have surrounding double quotes, depending on their type.
34
- pub fn parse_metrics(json: &str) -> Option<MetricText> {
35
- // It would be preferable to use `serde_json` here, but the values
36
- // may be strings, numbers, or null, and so don't parse easily to a
37
- // defined struct. Using `serde_json::Value` is an option, but since
38
- // we're just copying the literal values into a buffer this will be
39
- // inefficient and verbose.
40
-
41
- // Trim trailing spaces from string before processing. We use
42
- // `trim_end_matches()` instead of `trim_end()` because we know the
43
- // trailing bytes are always ASCII 0x20 bytes. `trim_end()` will also
44
- // check for unicode spaces and consume a few more CPU cycles.
45
- let trimmed = json.trim_end_matches(' ');
46
-
47
- let names = parse_names(trimmed)?;
48
- let label_json = names.label_json;
49
-
50
- let label_vals = parse_label_values(label_json)?;
51
-
52
- Some(MetricText {
53
- family_name: names.family_name,
54
- metric_name: names.metric_name,
55
- labels: label_vals.labels,
56
- values: label_vals.values,
57
- })
58
- }
59
-
60
- fn parse_names(json: &str) -> Option<MetricNames> {
61
- // Starting with: ["family_name","metric_name",[...
62
- if !json.starts_with("[\"") {
63
- return None;
64
- }
65
-
66
- // Now: family_name","metric_name",[...
67
- let remainder = json.get(2..)?;
68
-
69
- let names_end = remainder.find('[')?;
70
-
71
- // Save the rest of the slice to parse for labels later.
72
- let label_json = remainder.get(names_end..)?;
73
-
74
- // Now: family_name","metric_name",
75
- let remainder = remainder.get(..names_end)?;
76
-
77
- // Split on commas into:
78
- // family_name","metric_name",
79
- // ^^^^one^^^^^ ^^^^^two^^^^^
80
- let mut token_iter = remainder.split(',');
81
-
82
- // Captured: family_name","metric_name",
83
- // ^^^^^^^^^^^
84
- let family_name = token_iter.next()?.trim_end_matches('"');
85
-
86
- // Captured: "family_name","metric_name",
87
- // ^^^^^^^^^^^
88
- let metric_name = token_iter.next()?.trim_matches('"');
89
-
90
- // Confirm the final entry of the iter is empty, the the trailing ','.
91
- if !token_iter.next()?.is_empty() {
92
- return None;
93
- }
94
-
95
- Some(MetricNames {
96
- label_json,
97
- family_name,
98
- metric_name,
99
- })
100
- }
101
-
102
- fn parse_label_values(json: &str) -> Option<MetricLabelVals> {
103
- // Starting with: ["label_a","label_b"],["value_a", "value_b"]]
104
- if !(json.starts_with('[') && json.ends_with("]]")) {
105
- return None;
106
- }
107
-
108
- // Validate we either have the start of a label string or an
109
- // empty array, e.g. `["` or `[]`.
110
- if !matches!(json.as_bytes().get(1)?, b'"' | b']') {
111
- return None;
112
- }
113
-
114
- // Now: "label_a","label_b"
115
- let labels_end = json.find(']')?;
116
- let label_range = json.get(1..labels_end)?;
117
-
118
- let mut labels = SmallVec::new();
119
-
120
- // Split on commas into:
121
- // "label_a","label_b"
122
- // ^^^one^^^ ^^^two^^^
123
- for label in label_range.split(',') {
124
- // Captured: "label_a","label_b"
125
- // ^^^^^^^
126
- // If there are no labels, e.g. `[][]`, then don't capture anything.
127
- if !label.is_empty() {
128
- labels.push(label.trim_matches('"'));
129
- }
130
- }
131
-
132
- // Now: ],["value_a", "value_b"]]
133
- let mut values_range = json.get(labels_end..)?;
134
-
135
- // Validate we have a separating comma with one and only one leading bracket.
136
- if !(values_range.starts_with("],[") && values_range.as_bytes().get(3)? != &b'[') {
137
- return None;
138
- }
139
-
140
- // Now: "value_a", "value_b"]]
141
- values_range = values_range.get(3..)?;
142
-
143
- let values_end = values_range.find(']')?;
144
-
145
- // Validate we have only two trailing brackets.
146
- if values_range.get(values_end..)?.len() > 2 {
147
- return None;
148
- }
149
-
150
- // Now: "value_a", "value_b"
151
- values_range = values_range.get(..values_end)?;
152
-
153
- let mut values = SmallVec::new();
154
-
155
- // Split on commas into:
156
- // "value_a","value_b"
157
- // ^^^one^^^ ^^^two^^^
158
- for value in values_range.split(',') {
159
- // Captured: "value_a","value_b"
160
- // ^^^^^^^^^
161
- // If there are no values, e.g. `[][]`, then don't capture anything.
162
- if !value.is_empty() {
163
- values.push(value.trim_matches('"'));
164
- }
165
- }
166
-
167
- if values.len() != labels.len() {
168
- return None;
169
- }
170
-
171
- Some(MetricLabelVals { labels, values })
172
- }
173
-
174
- #[cfg(test)]
175
- mod test {
176
- use smallvec::smallvec;
177
-
178
- use super::*;
179
-
180
- struct TestCase {
181
- name: &'static str,
182
- input: &'static str,
183
- expected: Option<MetricText<'static>>,
184
- }
185
-
186
- #[test]
187
- fn valid_json() {
188
- let tc = vec![
189
- TestCase {
190
- name: "basic",
191
- input: r#"["metric","name",["label_a","label_b"],["value_a","value_b"]]"#,
192
- expected: Some(MetricText {
193
- family_name: "metric",
194
- metric_name: "name",
195
- labels: smallvec!["label_a", "label_b"],
196
- values: smallvec!["value_a", "value_b"],
197
- }),
198
- },
199
- TestCase {
200
- name: "many labels",
201
- input: r#"["metric","name",["label_a","label_b","label_c","label_d","label_e"],["value_a","value_b","value_c","value_d","value_e"]]"#,
202
-
203
- expected: Some(MetricText {
204
- family_name: "metric",
205
- metric_name: "name",
206
- labels: smallvec!["label_a", "label_b", "label_c", "label_d", "label_e"],
207
- values: smallvec!["value_a", "value_b", "value_c", "value_d", "value_e"],
208
- }),
209
- },
210
- TestCase {
211
- name: "numeric value",
212
- input: r#"["metric","name",["label_a","label_b"],["value_a",403]]"#,
213
- expected: Some(MetricText {
214
- family_name: "metric",
215
- metric_name: "name",
216
- labels: smallvec!["label_a", "label_b"],
217
- values: smallvec!["value_a", "403"],
218
- }),
219
- },
220
- TestCase {
221
- name: "null value",
222
- input: r#"["metric","name",["label_a","label_b"],[null,"value_b"]]"#,
223
- expected: Some(MetricText {
224
- family_name: "metric",
225
- metric_name: "name",
226
- labels: smallvec!["label_a", "label_b"],
227
- values: smallvec!["null", "value_b"],
228
- }),
229
- },
230
- TestCase {
231
- name: "no labels",
232
- input: r#"["metric","name",[],[]]"#,
233
- expected: Some(MetricText {
234
- family_name: "metric",
235
- metric_name: "name",
236
- labels: smallvec![],
237
- values: smallvec![],
238
- }),
239
- },
240
- ];
241
-
242
- for case in tc {
243
- assert_eq!(
244
- parse_metrics(case.input),
245
- case.expected,
246
- "test case: {}",
247
- case.name,
248
- );
249
- }
250
- }
251
-
252
- #[test]
253
- fn invalid_json() {
254
- let tc = vec![
255
- TestCase {
256
- name: "not json",
257
- input: "hello, world",
258
- expected: None,
259
- },
260
- TestCase {
261
- name: "no names",
262
- input: r#"[["label_a","label_b"],["value_a","value_b"]]"#,
263
- expected: None,
264
- },
265
- TestCase {
266
- name: "too many names",
267
- input: r#"["metric","name","unexpected_name",["label_a","label_b"],["value_a","value_b"]]"#,
268
- expected: None,
269
- },
270
- TestCase {
271
- name: "too many labels",
272
- input: r#"["metric","name","unexpected_name",["label_a","label_b","label_c"],["value_a","value_b"]]"#,
273
- expected: None,
274
- },
275
- TestCase {
276
- name: "too many values",
277
- input: r#"["metric","name",["label_a","label_b"],["value_a","value_b",null]]"#,
278
- expected: None,
279
- },
280
- TestCase {
281
- name: "no values",
282
- input: r#"["metric","name",["label_a","label_b"]"#,
283
- expected: None,
284
- },
285
- TestCase {
286
- name: "no arrays",
287
- input: r#"["metric","name","label_a","value_a"]"#,
288
- expected: None,
289
- },
290
- TestCase {
291
- name: "too many leading brackets",
292
- input: r#"[["metric","name",["label_a","label_b"],["value_a","value_b"]]"#,
293
- expected: None,
294
- },
295
- TestCase {
296
- name: "too many trailing brackets",
297
- input: r#"["metric","name",["label_a","label_b"],["value_a","value_b"]]]"#,
298
- expected: None,
299
- },
300
- TestCase {
301
- name: "too many leading label brackets",
302
- input: r#"["metric","name",[["label_a","label_b"],["value_a","value_b"]]"#,
303
- expected: None,
304
- },
305
- TestCase {
306
- name: "too many trailing label brackets",
307
- input: r#"["metric","name",["label_a","label_b"]],["value_a","value_b"]]"#,
308
- expected: None,
309
- },
310
- TestCase {
311
- name: "too many leading value brackets",
312
- input: r#"["metric","name",["label_a","label_b"],[["value_a","value_b"]]"#,
313
- expected: None,
314
- },
315
- TestCase {
316
- name: "comma in family name",
317
- input: r#"["met,ric","name",["label_a","label_b"],["value_a","value_b"]]"#,
318
- expected: None,
319
- },
320
- TestCase {
321
- name: "comma in metric name",
322
- input: r#"["metric","na,me",["label_a","label_b"],["value_a","value_b"]]"#,
323
- expected: None,
324
- },
325
- TestCase {
326
- name: "comma in value",
327
- input: r#"["metric","na,me",["label_a","label_b"],["val,ue_a","value_b"]]"#,
328
- expected: None,
329
- },
330
- TestCase {
331
- name: "comma in numeric value",
332
- input: r#"["metric","name",["label_a","label_b"],[400,0,"value_b"]]"#,
333
- expected: None,
334
- },
335
- ];
336
-
337
- for case in tc {
338
- assert_eq!(
339
- case.expected,
340
- parse_metrics(case.input),
341
- "test case: {}",
342
- case.name,
343
- );
344
- }
345
- }
346
- }