prometheus-client-mmap 0.24.4-x86_64-darwin → 0.24.5-x86_64-darwin
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/fast_mmaped_file_rs/Cargo.lock +42 -0
- data/ext/fast_mmaped_file_rs/Cargo.toml +3 -1
- data/ext/fast_mmaped_file_rs/src/file_entry.rs +217 -12
- data/ext/fast_mmaped_file_rs/src/lib.rs +0 -1
- data/lib/2.7/fast_mmaped_file_rs.bundle +0 -0
- data/lib/3.0/fast_mmaped_file_rs.bundle +0 -0
- data/lib/3.1/fast_mmaped_file.bundle +0 -0
- data/lib/3.1/fast_mmaped_file_rs.bundle +0 -0
- data/lib/3.2/fast_mmaped_file.bundle +0 -0
- data/lib/3.2/fast_mmaped_file_rs.bundle +0 -0
- data/lib/prometheus/client/version.rb +1 -1
- metadata +2 -3
- data/ext/fast_mmaped_file_rs/src/parser.rs +0 -448
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c288a4602307bbeedfe916df669c95d80d555f1c95beb7a2c946c58368c0f86b
|
4
|
+
data.tar.gz: 41b3bc2a51e0aee5de6d1511a9feb7285270c1d8366e683befdb1564a025f3f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7c4bb28ac28b8ca1b12716fe291516aeb9404b1fb553ce8c6aee0126554e2bcec8a9372cef93f2be4b429b09b32c7096f9c0fe8fbeb52876ccdd44d1d179a411
|
7
|
+
data.tar.gz: 1fce85abecdbdd8c0f85090eae0294b39f6cf00524dd67d950a046f8890ed31bb52ea0a5c9952c2c6b2aabc66918d766356c72683b4116731002e298b16fbac4
|
@@ -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
|
-
|
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
|
138
|
-
|
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
|
-
|
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,
|
217
|
+
for (i, (&key, val)) in it.enumerate() {
|
201
218
|
out.push_str(key);
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
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 {
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
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
|
+
version: 0.24.5
|
5
5
|
platform: x86_64-darwin
|
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-
|
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
|
-
}
|