vinted-prometheus-client-mmap 1.5.0-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +5 -0
  3. data/ext/fast_mmaped_file_rs/Cargo.toml +40 -0
  4. data/ext/fast_mmaped_file_rs/README.md +52 -0
  5. data/ext/fast_mmaped_file_rs/build.rs +7 -0
  6. data/ext/fast_mmaped_file_rs/extconf.rb +28 -0
  7. data/ext/fast_mmaped_file_rs/src/error.rs +174 -0
  8. data/ext/fast_mmaped_file_rs/src/exemplars.rs +25 -0
  9. data/ext/fast_mmaped_file_rs/src/file_entry.rs +1252 -0
  10. data/ext/fast_mmaped_file_rs/src/file_info.rs +240 -0
  11. data/ext/fast_mmaped_file_rs/src/lib.rs +89 -0
  12. data/ext/fast_mmaped_file_rs/src/macros.rs +14 -0
  13. data/ext/fast_mmaped_file_rs/src/map.rs +519 -0
  14. data/ext/fast_mmaped_file_rs/src/metrics.proto +153 -0
  15. data/ext/fast_mmaped_file_rs/src/mmap/inner.rs +775 -0
  16. data/ext/fast_mmaped_file_rs/src/mmap.rs +977 -0
  17. data/ext/fast_mmaped_file_rs/src/raw_entry.rs +547 -0
  18. data/ext/fast_mmaped_file_rs/src/testhelper.rs +222 -0
  19. data/ext/fast_mmaped_file_rs/src/util.rs +140 -0
  20. data/lib/.DS_Store +0 -0
  21. data/lib/2.7/fast_mmaped_file_rs.so +0 -0
  22. data/lib/3.0/fast_mmaped_file_rs.so +0 -0
  23. data/lib/3.1/fast_mmaped_file_rs.so +0 -0
  24. data/lib/3.2/fast_mmaped_file_rs.so +0 -0
  25. data/lib/3.3/fast_mmaped_file_rs.so +0 -0
  26. data/lib/prometheus/.DS_Store +0 -0
  27. data/lib/prometheus/client/configuration.rb +24 -0
  28. data/lib/prometheus/client/counter.rb +27 -0
  29. data/lib/prometheus/client/formats/protobuf.rb +93 -0
  30. data/lib/prometheus/client/formats/text.rb +85 -0
  31. data/lib/prometheus/client/gauge.rb +40 -0
  32. data/lib/prometheus/client/helper/entry_parser.rb +132 -0
  33. data/lib/prometheus/client/helper/file_locker.rb +50 -0
  34. data/lib/prometheus/client/helper/json_parser.rb +23 -0
  35. data/lib/prometheus/client/helper/metrics_processing.rb +45 -0
  36. data/lib/prometheus/client/helper/metrics_representation.rb +51 -0
  37. data/lib/prometheus/client/helper/mmaped_file.rb +64 -0
  38. data/lib/prometheus/client/helper/plain_file.rb +29 -0
  39. data/lib/prometheus/client/histogram.rb +80 -0
  40. data/lib/prometheus/client/label_set_validator.rb +85 -0
  41. data/lib/prometheus/client/metric.rb +80 -0
  42. data/lib/prometheus/client/mmaped_dict.rb +83 -0
  43. data/lib/prometheus/client/mmaped_value.rb +164 -0
  44. data/lib/prometheus/client/page_size.rb +17 -0
  45. data/lib/prometheus/client/push.rb +203 -0
  46. data/lib/prometheus/client/rack/collector.rb +88 -0
  47. data/lib/prometheus/client/rack/exporter.rb +102 -0
  48. data/lib/prometheus/client/registry.rb +65 -0
  49. data/lib/prometheus/client/simple_value.rb +31 -0
  50. data/lib/prometheus/client/summary.rb +69 -0
  51. data/lib/prometheus/client/support/puma.rb +44 -0
  52. data/lib/prometheus/client/support/unicorn.rb +35 -0
  53. data/lib/prometheus/client/uses_value_type.rb +20 -0
  54. data/lib/prometheus/client/version.rb +5 -0
  55. data/lib/prometheus/client.rb +58 -0
  56. data/lib/prometheus.rb +3 -0
  57. metadata +210 -0
@@ -0,0 +1,547 @@
1
+ use std::mem::size_of;
2
+
3
+ use crate::error::MmapError;
4
+ use crate::exemplars::{Exemplar, EXEMPLAR_ENTRY_MAX_SIZE_BYTES};
5
+ use crate::util;
6
+ use crate::util::CheckedOps;
7
+ use crate::Result;
8
+
9
+ /// The logic to save a `MetricsEntry`, or parse one from a byte slice.
10
+ #[derive(PartialEq, Eq, Clone, Debug)]
11
+ pub struct RawEntry<'a> {
12
+ bytes: &'a [u8],
13
+ encoded_len: usize,
14
+ }
15
+
16
+ impl<'a> RawEntry<'a> {
17
+ pub fn save_exemplar(bytes: &'a mut [u8], key: &[u8], value: Exemplar) -> Result<usize> {
18
+ let total_len = Self::calc_total_len_exemplar(key.len())?;
19
+
20
+ if total_len > bytes.len() {
21
+ return Err(MmapError::Other(format!(
22
+ "entry length {total_len} larger than slice length {}",
23
+ bytes.len()
24
+ )));
25
+ }
26
+
27
+ let val = serde_json::to_string(&value).unwrap();
28
+
29
+ // CAST: `calc_len` runs `check_encoded_len`, we know the key len
30
+ // is less than i32::MAX. No risk of overflows or failed casts.
31
+ let key_len: u32 = key.len() as u32;
32
+
33
+ // Write the key length to the mmap.
34
+ bytes[..size_of::<u32>()].copy_from_slice(&key_len.to_ne_bytes());
35
+
36
+ // Advance slice past the size.
37
+ let bytes = &mut bytes[size_of::<u32>()..];
38
+
39
+ bytes[..key.len()].copy_from_slice(key);
40
+
41
+ // Advance to end of key.
42
+ let bytes = &mut bytes[key.len()..];
43
+
44
+ let pad_len = Self::padding_len(key.len());
45
+ bytes[..pad_len].fill(b' ');
46
+ let bytes = &mut bytes[pad_len..];
47
+
48
+ bytes[..val.len()].copy_from_slice(val.as_bytes());
49
+
50
+ Self::calc_value_offset(key.len())
51
+ }
52
+
53
+ /// Save an entry to the mmap, returning the value offset in the newly created entry.
54
+ pub fn save(bytes: &'a mut [u8], key: &[u8], value: f64) -> Result<usize> {
55
+ let total_len = Self::calc_total_len(key.len())?;
56
+
57
+ if total_len > bytes.len() {
58
+ return Err(MmapError::Other(format!(
59
+ "entry length {total_len} larger than slice length {}",
60
+ bytes.len()
61
+ )));
62
+ }
63
+
64
+ // CAST: `calc_len` runs `check_encoded_len`, we know the key len
65
+ // is less than i32::MAX. No risk of overflows or failed casts.
66
+ let key_len: u32 = key.len() as u32;
67
+
68
+ // Write the key length to the mmap.
69
+ bytes[..size_of::<u32>()].copy_from_slice(&key_len.to_ne_bytes());
70
+
71
+ // Advance slice past the size.
72
+ let bytes = &mut bytes[size_of::<u32>()..];
73
+
74
+ bytes[..key.len()].copy_from_slice(key);
75
+
76
+ // Advance to end of key.
77
+ let bytes = &mut bytes[key.len()..];
78
+
79
+ let pad_len = Self::padding_len(key.len());
80
+ bytes[..pad_len].fill(b' ');
81
+ let bytes = &mut bytes[pad_len..];
82
+
83
+ bytes[..size_of::<f64>()].copy_from_slice(&value.to_ne_bytes());
84
+
85
+ Self::calc_value_offset(key.len())
86
+ }
87
+
88
+ /// Parse a byte slice starting into an `MmapEntry`.
89
+ pub fn from_slice(bytes: &'a [u8]) -> Result<Self> {
90
+ // CAST: no-op on 32-bit, widening on 64-bit.
91
+ let encoded_len = util::read_u32(bytes, 0)? as usize;
92
+
93
+ let total_len = Self::calc_total_len(encoded_len)?;
94
+
95
+ // Confirm the value is in bounds of the slice provided.
96
+ if total_len > bytes.len() {
97
+ return Err(MmapError::out_of_bounds(total_len, bytes.len()));
98
+ }
99
+
100
+ // Advance slice past length int and cut at end of entry.
101
+ let bytes = &bytes[size_of::<u32>()..total_len];
102
+
103
+ Ok(Self { bytes, encoded_len })
104
+ }
105
+
106
+ pub fn from_slice_exemplar(bytes: &'a [u8]) -> Result<Self> {
107
+ // CAST: no-op on 32-bit, widening on 64-bit.
108
+ let encoded_len = util::read_u32(bytes, 0)? as usize;
109
+
110
+ let total_len = Self::calc_total_len_exemplar(encoded_len)?;
111
+
112
+ // Confirm the value is in bounds of the slice provided.
113
+ if total_len > bytes.len() {
114
+ return Err(MmapError::out_of_bounds(total_len, bytes.len()));
115
+ }
116
+
117
+ // Advance slice past length int and cut at end of entry.
118
+ let bytes = &bytes[size_of::<u32>()..total_len];
119
+
120
+ Ok(Self { bytes, encoded_len })
121
+ }
122
+
123
+ /// Read the `f64` value of an entry from memory.
124
+ #[inline]
125
+ pub fn value(&self) -> f64 {
126
+ // We've stripped off the leading u32, don't include that here.
127
+ let offset = self.encoded_len + Self::padding_len(self.encoded_len);
128
+
129
+ // UNWRAP: We confirm in the constructor that the value offset
130
+ // is in-range for the slice.
131
+ util::read_f64(self.bytes, offset).unwrap()
132
+ }
133
+
134
+ pub fn exemplar(&self) -> Exemplar {
135
+ // We've stripped off the leading u32, don't include that here.
136
+ let offset = self.encoded_len + Self::padding_len(self.encoded_len);
137
+
138
+ // UNWRAP: We confirm in the constructor that the value offset
139
+ // is in-range for the slice.
140
+ util::read_exemplar(self.bytes, offset).unwrap()
141
+ }
142
+
143
+ /// The length of the entry key without padding.
144
+ #[inline]
145
+ pub fn encoded_len(&self) -> usize {
146
+ self.encoded_len
147
+ }
148
+
149
+ /// Returns a slice with the JSON string in the entry, excluding padding.
150
+ #[inline]
151
+ pub fn json(&self) -> &[u8] {
152
+ &self.bytes[..self.encoded_len]
153
+ }
154
+
155
+ /// Calculate the total length of an `MmapEntry`, including the string length,
156
+ /// string, padding, and value.
157
+ #[inline]
158
+ pub fn total_len(&self) -> usize {
159
+ // UNWRAP:: We confirmed in the constructor that this doesn't overflow.
160
+ Self::calc_total_len(self.encoded_len).unwrap()
161
+ }
162
+
163
+ #[inline]
164
+ pub fn total_len_exemplar(&self) -> usize {
165
+ // UNWRAP:: We confirmed in the constructor that this doesn't overflow.
166
+ Self::calc_total_len_exemplar(self.encoded_len).unwrap()
167
+ }
168
+
169
+ /// Calculate the total length of an `MmapEntry`, including the string length,
170
+ /// string, padding, and value. Validates encoding_len is within expected bounds.
171
+ #[inline]
172
+ pub fn calc_total_len(encoded_len: usize) -> Result<usize> {
173
+ Self::calc_value_offset(encoded_len)?.add_chk(size_of::<f64>())
174
+ }
175
+
176
+ #[inline]
177
+ pub fn calc_total_len_exemplar(encoded_len: usize) -> Result<usize> {
178
+ Self::calc_value_offset(encoded_len)?.add_chk(EXEMPLAR_ENTRY_MAX_SIZE_BYTES)
179
+ }
180
+
181
+ /// Calculate the value offset of an `MmapEntry`, including the string length,
182
+ /// string, padding. Validates encoding_len is within expected bounds.
183
+ #[inline]
184
+ pub fn calc_value_offset(encoded_len: usize) -> Result<usize> {
185
+ Self::check_encoded_len(encoded_len)?;
186
+
187
+ Ok(size_of::<u32>() + encoded_len + Self::padding_len(encoded_len))
188
+ }
189
+
190
+ /// Calculate the number of padding bytes to add to the value key to reach
191
+ /// 8-byte alignment. Does not validate key length.
192
+ #[inline]
193
+ pub fn padding_len(encoded_len: usize) -> usize {
194
+ 8 - (size_of::<u32>() + encoded_len) % 8
195
+ }
196
+
197
+ #[inline]
198
+ fn check_encoded_len(encoded_len: usize) -> Result<()> {
199
+ if encoded_len as u64 > i32::MAX as u64 {
200
+ return Err(MmapError::KeyLength);
201
+ }
202
+ Ok(())
203
+ }
204
+ }
205
+
206
+ #[cfg(test)]
207
+ mod test {
208
+ use bstr::ByteSlice;
209
+
210
+ use super::*;
211
+ use crate::testhelper::TestEntry;
212
+
213
+ #[test]
214
+ fn test_from_slice() {
215
+ #[derive(PartialEq, Default, Debug)]
216
+ struct TestCase {
217
+ name: &'static str,
218
+ input: TestEntry,
219
+ expected_enc_len: Option<usize>,
220
+ expected_err: Option<MmapError>,
221
+ }
222
+
223
+ let tc = vec![
224
+ TestCase {
225
+ name: "ok",
226
+ input: TestEntry {
227
+ header: 61,
228
+ json: r#"["metric","name",["label_a","label_b"],["value_a","value_b"]]"#,
229
+ padding_len: 7,
230
+ value: 1.0,
231
+ },
232
+ expected_enc_len: Some(61),
233
+ ..Default::default()
234
+ },
235
+ TestCase {
236
+ name: "zero length key",
237
+ input: TestEntry {
238
+ header: 0,
239
+ json: "",
240
+ padding_len: 4,
241
+ value: 1.0,
242
+ },
243
+ expected_enc_len: Some(0),
244
+ ..Default::default()
245
+ },
246
+ TestCase {
247
+ name: "header value too large",
248
+ input: TestEntry {
249
+ header: i32::MAX as u32 + 1,
250
+ json: "foo",
251
+ padding_len: 1,
252
+ value: 0.0,
253
+ },
254
+ expected_err: Some(MmapError::KeyLength),
255
+ ..Default::default()
256
+ },
257
+ TestCase {
258
+ name: "header value much longer than json len",
259
+ input: TestEntry {
260
+ header: 256,
261
+ json: "foobar",
262
+ padding_len: 6,
263
+ value: 1.0,
264
+ },
265
+ expected_err: Some(MmapError::out_of_bounds(272, 24)),
266
+ ..Default::default()
267
+ },
268
+ TestCase {
269
+ // Situations where encoded_len is wrong but padding makes the
270
+ // value offset the same are not caught.
271
+ name: "header off by one",
272
+ input: TestEntry {
273
+ header: 4,
274
+ json: "123",
275
+ padding_len: 1,
276
+ value: 1.0,
277
+ },
278
+ expected_err: Some(MmapError::out_of_bounds(24, 16)),
279
+ ..Default::default()
280
+ },
281
+ ];
282
+
283
+ for case in tc {
284
+ let name = case.name;
285
+ let input = case.input.as_bstring();
286
+
287
+ let resp = RawEntry::from_slice(&input);
288
+
289
+ if case.expected_err.is_none() {
290
+ let expected_buf = case.input.as_bytes_no_header();
291
+ let resp = resp.as_ref().unwrap();
292
+ let bytes = resp.bytes;
293
+
294
+ assert_eq!(expected_buf, bytes.as_bstr(), "test case: {name} - bytes",);
295
+
296
+ assert_eq!(
297
+ resp.json(),
298
+ case.input.json.as_bytes(),
299
+ "test case: {name} - json matches"
300
+ );
301
+
302
+ assert_eq!(
303
+ resp.total_len(),
304
+ case.input.as_bstring().len(),
305
+ "test case: {name} - total_len matches"
306
+ );
307
+
308
+ assert_eq!(
309
+ resp.encoded_len(),
310
+ case.input.json.len(),
311
+ "test case: {name} - encoded_len matches"
312
+ );
313
+
314
+ assert!(
315
+ resp.json().iter().all(|&c| c != b' '),
316
+ "test case: {name} - no spaces in json"
317
+ );
318
+
319
+ let padding_len = RawEntry::padding_len(case.input.json.len());
320
+ assert!(
321
+ bytes[resp.encoded_len..resp.encoded_len + padding_len]
322
+ .iter()
323
+ .all(|&c| c == b' '),
324
+ "test case: {name} - padding is spaces"
325
+ );
326
+
327
+ assert_eq!(
328
+ resp.value(),
329
+ case.input.value,
330
+ "test case: {name} - value is correct"
331
+ );
332
+ }
333
+
334
+ if let Some(expected_enc_len) = case.expected_enc_len {
335
+ assert_eq!(
336
+ expected_enc_len,
337
+ resp.as_ref().unwrap().encoded_len,
338
+ "test case: {name} - encoded len",
339
+ );
340
+ }
341
+
342
+ if let Some(expected_err) = case.expected_err {
343
+ assert_eq!(expected_err, resp.unwrap_err(), "test case: {name} - error",);
344
+ }
345
+ }
346
+ }
347
+
348
+ #[test]
349
+ fn test_save() {
350
+ struct TestCase {
351
+ name: &'static str,
352
+ key: &'static [u8],
353
+ value: f64,
354
+ buf_len: usize,
355
+ expected_entry: Option<TestEntry>,
356
+ expected_resp: Result<usize>,
357
+ }
358
+
359
+ // TODO No test case to validate keys with len > i32::MAX, adding a static that large crashes
360
+ // the test binary.
361
+ let tc = vec![
362
+ TestCase {
363
+ name: "ok",
364
+ key: br#"["metric","name",["label_a","label_b"],["value_a","value_b"]]"#,
365
+ value: 256.0,
366
+ buf_len: 256,
367
+ expected_entry: Some(TestEntry {
368
+ header: 61,
369
+ json: r#"["metric","name",["label_a","label_b"],["value_a","value_b"]]"#,
370
+ padding_len: 7,
371
+ value: 256.0,
372
+ }),
373
+ expected_resp: Ok(72),
374
+ },
375
+ TestCase {
376
+ name: "zero length key",
377
+ key: b"",
378
+ value: 1.0,
379
+ buf_len: 256,
380
+ expected_entry: Some(TestEntry {
381
+ header: 0,
382
+ json: "",
383
+ padding_len: 4,
384
+ value: 1.0,
385
+ }),
386
+ expected_resp: Ok(8),
387
+ },
388
+ TestCase {
389
+ name: "infinite value",
390
+ key: br#"["metric","name",["label_a","label_b"],["value_a","value_b"]]"#,
391
+ value: f64::INFINITY,
392
+ buf_len: 256,
393
+ expected_entry: Some(TestEntry {
394
+ header: 61,
395
+ json: r#"["metric","name",["label_a","label_b"],["value_a","value_b"]]"#,
396
+ padding_len: 7,
397
+ value: f64::INFINITY,
398
+ }),
399
+ expected_resp: Ok(72),
400
+ },
401
+ TestCase {
402
+ name: "buf len matches entry len",
403
+ key: br#"["metric","name",["label_a","label_b"],["value_a","value_b"]]"#,
404
+ value: 1.0,
405
+ buf_len: 80,
406
+ expected_entry: Some(TestEntry {
407
+ header: 61,
408
+ json: r#"["metric","name",["label_a","label_b"],["value_a","value_b"]]"#,
409
+ padding_len: 7,
410
+ value: 1.0,
411
+ }),
412
+ expected_resp: Ok(72),
413
+ },
414
+ TestCase {
415
+ name: "buf much too short",
416
+ key: br#"["metric","name",["label_a","label_b"],["value_a","value_b"]]"#,
417
+ value: 1.0,
418
+ buf_len: 5,
419
+ expected_entry: None,
420
+ expected_resp: Err(MmapError::Other(format!(
421
+ "entry length {} larger than slice length {}",
422
+ 80, 5,
423
+ ))),
424
+ },
425
+ TestCase {
426
+ name: "buf short by one",
427
+ key: br#"["metric","name",["label_a","label_b"],["value_a","value_b"]]"#,
428
+ value: 1.0,
429
+ buf_len: 79,
430
+ expected_entry: None,
431
+ expected_resp: Err(MmapError::Other(format!(
432
+ "entry length {} larger than slice length {}",
433
+ 80, 79,
434
+ ))),
435
+ },
436
+ ];
437
+
438
+ for case in tc {
439
+ let mut buf = vec![0; case.buf_len];
440
+ let resp = RawEntry::save(&mut buf, case.key, case.value);
441
+
442
+ assert_eq!(
443
+ case.expected_resp, resp,
444
+ "test case: {} - response",
445
+ case.name,
446
+ );
447
+
448
+ if let Some(e) = case.expected_entry {
449
+ let expected_buf = e.as_bstring();
450
+
451
+ assert_eq!(
452
+ expected_buf,
453
+ buf[..expected_buf.len()].as_bstr(),
454
+ "test case: {} - buffer state",
455
+ case.name
456
+ );
457
+
458
+ let header_len = u32::from_ne_bytes(buf[..size_of::<u32>()].try_into().unwrap());
459
+ assert_eq!(
460
+ case.key.len(),
461
+ header_len as usize,
462
+ "test case: {} - size header",
463
+ case.name,
464
+ );
465
+ }
466
+ }
467
+ }
468
+
469
+ #[test]
470
+ fn test_calc_value_offset() {
471
+ struct TestCase {
472
+ name: &'static str,
473
+ encoded_len: usize,
474
+ expected_value_offset: Option<usize>,
475
+ expected_total_len: Option<usize>,
476
+ expected_err: Option<MmapError>,
477
+ }
478
+
479
+ let tc = vec![
480
+ TestCase {
481
+ name: "ok",
482
+ encoded_len: 8,
483
+ expected_value_offset: Some(16),
484
+ expected_total_len: Some(24),
485
+ expected_err: None,
486
+ },
487
+ TestCase {
488
+ name: "padding length one",
489
+ encoded_len: 3,
490
+ expected_value_offset: Some(8),
491
+ expected_total_len: Some(16),
492
+ expected_err: None,
493
+ },
494
+ TestCase {
495
+ name: "padding length eight",
496
+ encoded_len: 4,
497
+ expected_value_offset: Some(16),
498
+ expected_total_len: Some(24),
499
+ expected_err: None,
500
+ },
501
+ TestCase {
502
+ name: "encoded len gt i32::MAX",
503
+ encoded_len: i32::MAX as usize + 1,
504
+ expected_value_offset: None,
505
+ expected_total_len: None,
506
+ expected_err: Some(MmapError::KeyLength),
507
+ },
508
+ ];
509
+
510
+ for case in tc {
511
+ let name = case.name;
512
+ if let Some(expected_value_offset) = case.expected_value_offset {
513
+ assert_eq!(
514
+ expected_value_offset,
515
+ RawEntry::calc_value_offset(case.encoded_len).unwrap(),
516
+ "test case: {name} - value offset"
517
+ );
518
+ }
519
+
520
+ if let Some(expected_total_len) = case.expected_total_len {
521
+ assert_eq!(
522
+ expected_total_len,
523
+ RawEntry::calc_total_len(case.encoded_len).unwrap(),
524
+ "test case: {name} - total len"
525
+ );
526
+ }
527
+
528
+ if let Some(expected_err) = case.expected_err {
529
+ assert_eq!(
530
+ expected_err,
531
+ RawEntry::calc_value_offset(case.encoded_len).unwrap_err(),
532
+ "test case: {name} - err"
533
+ );
534
+ }
535
+ }
536
+ }
537
+
538
+ #[test]
539
+ fn test_padding_len() {
540
+ for encoded_len in 0..64 {
541
+ let padding = RawEntry::padding_len(encoded_len);
542
+
543
+ // Validate we're actually aligning to 8 bytes.
544
+ assert!((size_of::<u32>() + encoded_len + padding) % 8 == 0)
545
+ }
546
+ }
547
+ }