prometheus-client-mmap 0.24.4-x86_64-linux → 0.24.5-x86_64-linux

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: 141d4232e008441a87bf9379f2c05a22e745278dd20d319a9ce7e25782293487
4
- data.tar.gz: 0572e1ed26fb7a1d2cdb63df84407ed845fa5f278610167972039344b0477976
3
+ metadata.gz: 1207aad86766a410bc9b90eee5b83ae1785f9c36fb34ac79a3bc56a44ce9d7ed
4
+ data.tar.gz: b1cc8b946f96ff5f26de048492159e162cb7831d11f3b724f01988aca1e1da94
5
5
  SHA512:
6
- metadata.gz: b6b77d80551ca1accaef527fe55147efc97f2244503bfb5575092208c3234433e13013673f144802535e8f09c5ca7933f00ab0539d38503c587da5a7f495294c
7
- data.tar.gz: b6954547ccff1ff8224dbf9841393f4c929c1938402a85fa57ddcfaac1c9f3bfd8145406011bcd17d20d6a9f8d097ef947bd59c6774673ec2bb1fa8590be9ec4
6
+ metadata.gz: ca3048cd9a5829d78a1a091554d33434188b6a49babe0b73ece6c508c0c7514cf54da7dc89c74bf26ecae9642fd54a89da9990be03877631942d99c81113306c
7
+ data.tar.gz: 554ca7fa38267fa42e13d024e22f03cb96c34cc882f97d21a351b1248827cc6ab41104ad067ed2f33e3b03f4f507b344e94a9887a6d5bff737ef0878c6175d40
@@ -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
 
Binary file
Binary file
Binary file
Binary file
@@ -1,5 +1,5 @@
1
1
  module Prometheus
2
2
  module Client
3
- VERSION = '0.24.4'.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.4
4
+ version: 0.24.5
5
5
  platform: x86_64-linux
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-06-12 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
@@ -159,7 +159,6 @@ files:
159
159
  - ext/fast_mmaped_file_rs/src/map.rs
160
160
  - ext/fast_mmaped_file_rs/src/mmap.rs
161
161
  - ext/fast_mmaped_file_rs/src/mmap/inner.rs
162
- - ext/fast_mmaped_file_rs/src/parser.rs
163
162
  - ext/fast_mmaped_file_rs/src/raw_entry.rs
164
163
  - ext/fast_mmaped_file_rs/src/testhelper.rs
165
164
  - ext/fast_mmaped_file_rs/src/util.rs
@@ -1,448 +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
- // Captured: "family_name","metric_name",
67
- // ^^^^^^^^^^^
68
- let (family_name, remainder) = scan_str(json.get(1..)?)?;
69
-
70
- // Validate comma separated names
71
- // ["family_name","metric_name",[...
72
- // ^
73
- if !remainder.starts_with("\",") {
74
- return None;
75
- }
76
-
77
- // Now: "metric_name",[...
78
- let remainder = remainder.get(2..)?;
79
-
80
- // Captured: "family_name","metric_name",
81
- // ^^^^^^^^^^^
82
- let (metric_name, remainder) = scan_str(remainder)?;
83
-
84
- // Validate comma separated names
85
- // ["family_name","metric_name",[...
86
- // ^^
87
- if !remainder.starts_with("\",") {
88
- return None;
89
- }
90
-
91
- // Save the rest of the slice to parse for labels later.
92
- // Now: [...
93
- let label_json = remainder.get(2..)?;
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('[') {
105
- return None;
106
- }
107
-
108
- // Now: "label_a","label_b"],["value_a", "value_b"]]
109
- let mut remainder = json.get(1..)?;
110
-
111
- // Validate we either have the start of a label string or an
112
- // empty array, e.g. `["` or `[]`.
113
- if !matches!(json.as_bytes().get(1)?, b'"' | b']') {
114
- return None;
115
- }
116
-
117
- let mut labels = SmallVec::new();
118
-
119
- // Split on commas into:
120
- // "label_a","label_b"
121
- // ^^^one^^^ ^^^two^^^
122
- loop {
123
- let Some((label, label_rem)) = scan_str(remainder) else {
124
- // No further keys.
125
- break;
126
- };
127
- labels.push(label);
128
-
129
- // Advance past trailing quote.
130
- remainder = label_rem.get(1..)?;
131
- match remainder.as_bytes().first() {
132
- Some(b']') => break, // No further labels.
133
- Some(b',') => {} // More labels expected.
134
- _ => return None, // Invalid.
135
- };
136
-
137
- // Advance past comma.
138
- remainder = remainder.get(1..)?;
139
- }
140
-
141
- if !remainder.starts_with("],[") {
142
- return None;
143
- }
144
- // Now: "value_a", "value_b"]]
145
- remainder = remainder.get(3..)?;
146
-
147
- // Validate we don't have extra brackets.
148
- if remainder.starts_with('[') {
149
- return None;
150
- }
151
-
152
- let mut values = SmallVec::new();
153
- // Split on commas into:
154
- // "value_a","value_b"
155
- // ^^^one^^^ ^^^two^^^
156
- loop {
157
- if remainder.starts_with('"') {
158
- let (value, value_rem) = scan_str(remainder)?;
159
- values.push(value);
160
-
161
- // Advance past trailing quote.
162
- remainder = value_rem.get(1..)?;
163
- } else {
164
- // An unquoted value, such as `true` or `404`.
165
- let i = remainder.find(|c| c == ',' || c == ']')?;
166
- let value = &remainder[..i];
167
-
168
- match (value.is_empty(), is_valid_json_literal(value)) {
169
- (true, _) => {} // Empty string, do nothing.
170
- (false, true) => values.push(value), // Valid literal.
171
- (false, false) => return None, // Invalid literal, fail.
172
- }
173
-
174
- remainder = &remainder[i..];
175
- }
176
-
177
- match remainder.as_bytes().first() {
178
- Some(b']') => break, // End of values.
179
- Some(b',') => {} // More values expected.
180
- _ => return None, // Invalid.
181
- };
182
-
183
- // Advance past comma.
184
- remainder = remainder.get(1..)?;
185
- }
186
-
187
- if values.len() != labels.len() {
188
- return None;
189
- }
190
-
191
- // Now: ]]
192
- if remainder != "]]" {
193
- return None;
194
- }
195
-
196
- Some(MetricLabelVals { labels, values })
197
- }
198
-
199
- // Read a JSON-encoded str that includes starting and ending double quotes.
200
- // Returns the validated str with the double quotes trimmed and the remainder
201
- // of the input str.
202
- fn scan_str(json: &str) -> Option<(&str, &str)> {
203
- let mut escaping = false;
204
-
205
- if !json.starts_with('"') {
206
- return None;
207
- }
208
-
209
- // Trim leading double quote.
210
- let json = json.get(1..)?;
211
-
212
- for (i, &c) in json.as_bytes().iter().enumerate() {
213
- if c == b'\\' {
214
- escaping = true;
215
- continue;
216
- }
217
-
218
- if c == b'"' && !escaping {
219
- return Some((json.get(..i)?, json.get(i..)?));
220
- }
221
-
222
- escaping = false;
223
- }
224
-
225
- None
226
- }
227
-
228
- // Confirm an unquoted JSON literal is a boolean, null, or has a passing
229
- // resemblance to a number. We do not confirm numeric formatting, only
230
- // that all characters are valid. See https://www.json.org/json-en.html
231
- // for details.
232
- fn is_valid_json_literal(s: &str) -> bool {
233
- match s {
234
- "true" | "false" | "null" => true,
235
- _ => s.chars().all(|c| {
236
- c.is_ascii_digit() || c == '.' || c == '+' || c == '-' || c == 'e' || c == 'E'
237
- }),
238
- }
239
- }
240
-
241
- #[cfg(test)]
242
- mod test {
243
- use smallvec::smallvec;
244
-
245
- use super::*;
246
-
247
- struct TestCase {
248
- name: &'static str,
249
- input: &'static str,
250
- expected: Option<MetricText<'static>>,
251
- }
252
-
253
- #[test]
254
- fn valid_json() {
255
- let tc = vec![
256
- TestCase {
257
- name: "basic",
258
- input: r#"["metric","name",["label_a","label_b"],["value_a","value_b"]]"#,
259
- expected: Some(MetricText {
260
- family_name: "metric",
261
- metric_name: "name",
262
- labels: smallvec!["label_a", "label_b"],
263
- values: smallvec!["value_a", "value_b"],
264
- }),
265
- },
266
- TestCase {
267
- name: "many labels",
268
- input: r#"["metric","name",["label_a","label_b","label_c","label_d","label_e"],["value_a","value_b","value_c","value_d","value_e"]]"#,
269
-
270
- expected: Some(MetricText {
271
- family_name: "metric",
272
- metric_name: "name",
273
- labels: smallvec!["label_a", "label_b", "label_c", "label_d", "label_e"],
274
- values: smallvec!["value_a", "value_b", "value_c", "value_d", "value_e"],
275
- }),
276
- },
277
- TestCase {
278
- name: "numeric value",
279
- input: r#"["metric","name",["label_a","label_b"],["value_a",403]]"#,
280
- expected: Some(MetricText {
281
- family_name: "metric",
282
- metric_name: "name",
283
- labels: smallvec!["label_a", "label_b"],
284
- values: smallvec!["value_a", "403"],
285
- }),
286
- },
287
- TestCase {
288
- name: "scientific notation literal",
289
- input: r#"["metric","name",["label_a"],[-2.0e-5]]"#,
290
- expected: Some(MetricText {
291
- family_name: "metric",
292
- metric_name: "name",
293
- labels: smallvec!["label_a"],
294
- values: smallvec!["-2.0e-5"],
295
- }),
296
- },
297
- TestCase {
298
- name: "null value",
299
- input: r#"["metric","name",["label_a","label_b"],[null,"value_b"]]"#,
300
- expected: Some(MetricText {
301
- family_name: "metric",
302
- metric_name: "name",
303
- labels: smallvec!["label_a", "label_b"],
304
- values: smallvec!["null", "value_b"],
305
- }),
306
- },
307
- TestCase {
308
- name: "no labels",
309
- input: r#"["metric","name",[],[]]"#,
310
- expected: Some(MetricText {
311
- family_name: "metric",
312
- metric_name: "name",
313
- labels: smallvec![],
314
- values: smallvec![],
315
- }),
316
- },
317
- TestCase {
318
- name: "comma in items",
319
- input: r#"["met,ric","na,me",["label,_a","label_b"],["value,_a","value_b"]]"#,
320
- expected: Some(MetricText {
321
- family_name: "met,ric",
322
- metric_name: "na,me",
323
- labels: smallvec!["label,_a", "label_b"],
324
- values: smallvec!["value,_a", "value_b"],
325
- }),
326
- },
327
- TestCase {
328
- name: "bracket in value",
329
- input: r#"["met[r]ic","na[m]e",["label[_]a","label_b"],["value_a","val[ue]_b"]]"#,
330
- expected: Some(MetricText {
331
- family_name: "met[r]ic",
332
- metric_name: "na[m]e",
333
- labels: smallvec!["label[_]a", "label_b"],
334
- values: smallvec!["value_a", "val[ue]_b"],
335
- }),
336
- },
337
- TestCase {
338
- name: "escaped quotes",
339
- input: r#"["met\"ric","na\"me",["label\"_a","label_b"],["value\"_a","value_b"]]"#,
340
- expected: Some(MetricText {
341
- family_name: r#"met\"ric"#,
342
- metric_name: r#"na\"me"#,
343
- labels: smallvec![r#"label\"_a"#, "label_b"],
344
- values: smallvec![r#"value\"_a"#, "value_b"],
345
- }),
346
- },
347
- ];
348
-
349
- for case in tc {
350
- assert_eq!(
351
- parse_metrics(case.input),
352
- case.expected,
353
- "test case: {}",
354
- case.name,
355
- );
356
- }
357
- }
358
-
359
- #[test]
360
- fn invalid_json() {
361
- let tc = vec![
362
- TestCase {
363
- name: "not json",
364
- input: "hello, world",
365
- expected: None,
366
- },
367
- TestCase {
368
- name: "no names",
369
- input: r#"[["label_a","label_b"],["value_a","value_b"]]"#,
370
- expected: None,
371
- },
372
- TestCase {
373
- name: "too many names",
374
- input: r#"["metric","name","unexpected_name",["label_a","label_b"],["value_a","value_b"]]"#,
375
- expected: None,
376
- },
377
- TestCase {
378
- name: "too many labels",
379
- input: r#"["metric","name","unexpected_name",["label_a","label_b","label_c"],["value_a","value_b"]]"#,
380
- expected: None,
381
- },
382
- TestCase {
383
- name: "too many values",
384
- input: r#"["metric","name",["label_a","label_b"],["value_a","value_b",null]]"#,
385
- expected: None,
386
- },
387
- TestCase {
388
- name: "no values",
389
- input: r#"["metric","name",["label_a","label_b"]"#,
390
- expected: None,
391
- },
392
- TestCase {
393
- name: "no arrays",
394
- input: r#"["metric","name","label_a","value_a"]"#,
395
- expected: None,
396
- },
397
- TestCase {
398
- name: "too many leading brackets",
399
- input: r#"[["metric","name",["label_a","label_b"],["value_a","value_b"]]"#,
400
- expected: None,
401
- },
402
- TestCase {
403
- name: "too many trailing brackets",
404
- input: r#"["metric","name",["label_a","label_b"],["value_a","value_b"]]]"#,
405
- expected: None,
406
- },
407
- TestCase {
408
- name: "too many leading label brackets",
409
- input: r#"["metric","name",[["label_a","label_b"],["value_a","value_b"]]"#,
410
- expected: None,
411
- },
412
- TestCase {
413
- name: "too many trailing label brackets",
414
- input: r#"["metric","name",["label_a","label_b"]],["value_a","value_b"]]"#,
415
- expected: None,
416
- },
417
- TestCase {
418
- name: "too many leading value brackets",
419
- input: r#"["metric","name",["label_a","label_b"],[["value_a","value_b"]]"#,
420
- expected: None,
421
- },
422
- TestCase {
423
- name: "misplaced bracket",
424
- input: r#"["metric","name",["label_a","label_b"],]["value_a","value_b"]]"#,
425
- expected: None,
426
- },
427
- TestCase {
428
- name: "comma in numeric value",
429
- input: r#"["metric","name",["label_a","label_b"],[400,0,"value_b"]]"#,
430
- expected: None,
431
- },
432
- TestCase {
433
- name: "non-e letter in numeric value",
434
- input: r#"["metric","name",["label_a","label_b"],[400x0,"value_b"]]"#,
435
- expected: None,
436
- },
437
- ];
438
-
439
- for case in tc {
440
- assert_eq!(
441
- case.expected,
442
- parse_metrics(case.input),
443
- "test case: {}",
444
- case.name,
445
- );
446
- }
447
- }
448
- }