prometheus-client-mmap 0.20.3-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +253 -0
  3. data/ext/fast_mmaped_file/extconf.rb +30 -0
  4. data/ext/fast_mmaped_file/fast_mmaped_file.c +122 -0
  5. data/ext/fast_mmaped_file/file_format.c +5 -0
  6. data/ext/fast_mmaped_file/file_format.h +11 -0
  7. data/ext/fast_mmaped_file/file_parsing.c +195 -0
  8. data/ext/fast_mmaped_file/file_parsing.h +27 -0
  9. data/ext/fast_mmaped_file/file_reading.c +102 -0
  10. data/ext/fast_mmaped_file/file_reading.h +30 -0
  11. data/ext/fast_mmaped_file/globals.h +14 -0
  12. data/ext/fast_mmaped_file/mmap.c +427 -0
  13. data/ext/fast_mmaped_file/mmap.h +61 -0
  14. data/ext/fast_mmaped_file/rendering.c +199 -0
  15. data/ext/fast_mmaped_file/rendering.h +8 -0
  16. data/ext/fast_mmaped_file/utils.c +56 -0
  17. data/ext/fast_mmaped_file/utils.h +22 -0
  18. data/ext/fast_mmaped_file/value_access.c +242 -0
  19. data/ext/fast_mmaped_file/value_access.h +15 -0
  20. data/ext/fast_mmaped_file_rs/.cargo/config.toml +23 -0
  21. data/ext/fast_mmaped_file_rs/Cargo.lock +790 -0
  22. data/ext/fast_mmaped_file_rs/Cargo.toml +30 -0
  23. data/ext/fast_mmaped_file_rs/README.md +52 -0
  24. data/ext/fast_mmaped_file_rs/extconf.rb +30 -0
  25. data/ext/fast_mmaped_file_rs/src/error.rs +174 -0
  26. data/ext/fast_mmaped_file_rs/src/file_entry.rs +579 -0
  27. data/ext/fast_mmaped_file_rs/src/file_info.rs +190 -0
  28. data/ext/fast_mmaped_file_rs/src/lib.rs +79 -0
  29. data/ext/fast_mmaped_file_rs/src/macros.rs +14 -0
  30. data/ext/fast_mmaped_file_rs/src/map.rs +492 -0
  31. data/ext/fast_mmaped_file_rs/src/mmap.rs +151 -0
  32. data/ext/fast_mmaped_file_rs/src/parser.rs +346 -0
  33. data/ext/fast_mmaped_file_rs/src/raw_entry.rs +473 -0
  34. data/ext/fast_mmaped_file_rs/src/testhelper.rs +222 -0
  35. data/ext/fast_mmaped_file_rs/src/util.rs +121 -0
  36. data/lib/2.7/fast_mmaped_file.so +0 -0
  37. data/lib/2.7/fast_mmaped_file_rs.so +0 -0
  38. data/lib/3.0/fast_mmaped_file.so +0 -0
  39. data/lib/3.0/fast_mmaped_file_rs.so +0 -0
  40. data/lib/3.1/fast_mmaped_file.so +0 -0
  41. data/lib/3.1/fast_mmaped_file_rs.so +0 -0
  42. data/lib/3.2/fast_mmaped_file.so +0 -0
  43. data/lib/3.2/fast_mmaped_file_rs.so +0 -0
  44. data/lib/prometheus/client/configuration.rb +23 -0
  45. data/lib/prometheus/client/counter.rb +27 -0
  46. data/lib/prometheus/client/formats/text.rb +118 -0
  47. data/lib/prometheus/client/gauge.rb +40 -0
  48. data/lib/prometheus/client/helper/entry_parser.rb +132 -0
  49. data/lib/prometheus/client/helper/file_locker.rb +50 -0
  50. data/lib/prometheus/client/helper/json_parser.rb +23 -0
  51. data/lib/prometheus/client/helper/metrics_processing.rb +45 -0
  52. data/lib/prometheus/client/helper/metrics_representation.rb +51 -0
  53. data/lib/prometheus/client/helper/mmaped_file.rb +64 -0
  54. data/lib/prometheus/client/helper/plain_file.rb +29 -0
  55. data/lib/prometheus/client/histogram.rb +80 -0
  56. data/lib/prometheus/client/label_set_validator.rb +86 -0
  57. data/lib/prometheus/client/metric.rb +80 -0
  58. data/lib/prometheus/client/mmaped_dict.rb +79 -0
  59. data/lib/prometheus/client/mmaped_value.rb +154 -0
  60. data/lib/prometheus/client/page_size.rb +17 -0
  61. data/lib/prometheus/client/push.rb +203 -0
  62. data/lib/prometheus/client/rack/collector.rb +88 -0
  63. data/lib/prometheus/client/rack/exporter.rb +96 -0
  64. data/lib/prometheus/client/registry.rb +65 -0
  65. data/lib/prometheus/client/simple_value.rb +31 -0
  66. data/lib/prometheus/client/summary.rb +69 -0
  67. data/lib/prometheus/client/support/unicorn.rb +35 -0
  68. data/lib/prometheus/client/uses_value_type.rb +20 -0
  69. data/lib/prometheus/client/version.rb +5 -0
  70. data/lib/prometheus/client.rb +58 -0
  71. data/lib/prometheus.rb +3 -0
  72. data/vendor/c/hashmap/.gitignore +52 -0
  73. data/vendor/c/hashmap/LICENSE +21 -0
  74. data/vendor/c/hashmap/README.md +90 -0
  75. data/vendor/c/hashmap/_config.yml +1 -0
  76. data/vendor/c/hashmap/src/hashmap.c +692 -0
  77. data/vendor/c/hashmap/src/hashmap.h +267 -0
  78. data/vendor/c/hashmap/test/Makefile +22 -0
  79. data/vendor/c/hashmap/test/hashmap_test.c +608 -0
  80. data/vendor/c/jsmn/.travis.yml +4 -0
  81. data/vendor/c/jsmn/LICENSE +20 -0
  82. data/vendor/c/jsmn/Makefile +41 -0
  83. data/vendor/c/jsmn/README.md +168 -0
  84. data/vendor/c/jsmn/example/jsondump.c +126 -0
  85. data/vendor/c/jsmn/example/simple.c +76 -0
  86. data/vendor/c/jsmn/jsmn.c +314 -0
  87. data/vendor/c/jsmn/jsmn.h +76 -0
  88. data/vendor/c/jsmn/library.json +16 -0
  89. data/vendor/c/jsmn/test/test.h +27 -0
  90. data/vendor/c/jsmn/test/tests.c +407 -0
  91. data/vendor/c/jsmn/test/testutil.h +94 -0
  92. metadata +243 -0
@@ -0,0 +1,492 @@
1
+ use hashbrown::hash_map::RawEntryMut;
2
+ use hashbrown::HashMap;
3
+ use magnus::{exception::*, Error, RArray};
4
+ use std::hash::{BuildHasher, Hash, Hasher};
5
+ use std::mem::size_of;
6
+
7
+ use crate::error::MmapError;
8
+ use crate::file_entry::{BorrowedData, EntryData, EntryMetadata, FileEntry};
9
+ use crate::file_info::FileInfo;
10
+ use crate::raw_entry::RawEntry;
11
+ use crate::util::read_u32;
12
+ use crate::Result;
13
+ use crate::{err, HEADER_SIZE};
14
+
15
+ /// A HashMap of JSON strings and their associated metadata.
16
+ /// Used to print metrics in text format.
17
+ ///
18
+ /// The map key is the entry's JSON string and an optional pid string. The latter
19
+ /// allows us to have multiple entries on the map for multiple pids using the
20
+ /// same string.
21
+ #[derive(Default, Debug)]
22
+ pub struct EntryMap(HashMap<EntryData, EntryMetadata>);
23
+
24
+ impl EntryMap {
25
+ /// Construct a new EntryMap.
26
+ pub fn new() -> Self {
27
+ Self(HashMap::new())
28
+ }
29
+
30
+ /// Given a list of files, read each one into memory and parse the metrics it contains.
31
+ pub fn aggregate_files(&mut self, list_of_files: RArray) -> magnus::error::Result<()> {
32
+ // Pre-allocate the `HashMap` and validate we don't OOM. The C implementation
33
+ // ignores allocation failures here. We perform this check to avoid potential
34
+ // panics. We assume ~1,000 entries per file, so 72 KiB allocated per file.
35
+ self.0
36
+ .try_reserve(list_of_files.len() * 1024)
37
+ .map_err(|_| {
38
+ err!(
39
+ no_mem_error(),
40
+ "Couldn't allocate for {} memory",
41
+ size_of::<FileEntry>() * list_of_files.len() * 1024
42
+ )
43
+ })?;
44
+
45
+ // We expect file sizes between 4KiB and 4MiB. Pre-allocate 16KiB to reduce reallocations
46
+ // a bit.
47
+ let mut buf = Vec::new();
48
+ buf.try_reserve(16_384)
49
+ .map_err(|_| err!(no_mem_error(), "Couldn't allocate for {} memory", 16_384))?;
50
+
51
+ for item in list_of_files.each() {
52
+ let params = RArray::from_value(item?).expect("file list was not a Ruby Array");
53
+ if params.len() != 4 {
54
+ return Err(err!(
55
+ arg_error(),
56
+ "wrong number of arguments {} instead of 4",
57
+ params.len()
58
+ ));
59
+ }
60
+
61
+ let params = params.to_value_array::<4>()?;
62
+
63
+ let mut file_info = FileInfo::open_from_params(&params)?;
64
+ file_info.read_from_file(&mut buf)?;
65
+ self.process_buffer(file_info, &buf)?;
66
+ }
67
+ Ok(())
68
+ }
69
+
70
+ /// Consume the `EntryMap` and convert the key/value into`FileEntry`
71
+ /// objects, sorting them by their JSON strings.
72
+ pub fn into_sorted(self) -> Result<Vec<FileEntry>> {
73
+ let mut sorted = Vec::new();
74
+
75
+ // To match the behavior of the C version, pre-allocate the entries
76
+ // and check for allocation failure. Generally idiomatic Rust would
77
+ // `collect` the iterator into a new `Vec` in place, but this panics
78
+ // if it can't allocate and we want to continue execution in that
79
+ // scenario.
80
+ if sorted.try_reserve_exact(self.0.len()).is_err() {
81
+ return Err(MmapError::OutOfMemory(
82
+ self.0.len() * size_of::<FileEntry>(),
83
+ ));
84
+ }
85
+
86
+ sorted.extend(
87
+ self.0
88
+ .into_iter()
89
+ .map(|(data, meta)| FileEntry { data, meta }),
90
+ );
91
+
92
+ sorted.sort_unstable_by(|x, y| x.data.cmp(&y.data));
93
+
94
+ Ok(sorted)
95
+ }
96
+
97
+ /// Check if the `EntryMap` already contains the JSON string.
98
+ /// If yes, update the associated value, if not insert the
99
+ /// entry into the map.
100
+ pub fn merge_or_store(&mut self, data: BorrowedData, meta: EntryMetadata) -> Result<()> {
101
+ // Manually hash the `BorrowedData` and perform an equality check on the
102
+ // key. This allows us to perform the comparison without allocating a
103
+ // new `EntryData` that may not be needed.
104
+ let mut state = self.0.hasher().build_hasher();
105
+ data.hash(&mut state);
106
+ let hash = state.finish();
107
+
108
+ match self.0.raw_entry_mut().from_hash(hash, |k| k == &data) {
109
+ RawEntryMut::Vacant(entry) => {
110
+ // Allocate a new `EntryData` as the JSON/pid combination is
111
+ // not present in the map.
112
+ let owned = EntryData::try_from(data)?;
113
+ entry.insert(owned, meta);
114
+ }
115
+ RawEntryMut::Occupied(mut entry) => {
116
+ let existing = entry.get_mut();
117
+ existing.merge(&meta);
118
+ }
119
+ }
120
+
121
+ Ok(())
122
+ }
123
+
124
+ /// Parse metrics data from a `.db` file and store in the `EntryMap`.
125
+ fn process_buffer(&mut self, file_info: FileInfo, source: &[u8]) -> Result<()> {
126
+ if source.len() < HEADER_SIZE {
127
+ // Nothing to read, OK.
128
+ return Ok(());
129
+ }
130
+
131
+ // CAST: no-op on 32-bit, widening on 64-bit.
132
+ let used = read_u32(source, 0)? as usize;
133
+
134
+ if used > source.len() {
135
+ return Err(MmapError::PromParsing(format!(
136
+ "source file {} corrupted, used {used} > file size {}",
137
+ file_info.path.display(),
138
+ source.len()
139
+ )));
140
+ }
141
+
142
+ let mut pos = HEADER_SIZE;
143
+
144
+ while pos + size_of::<u32>() < used {
145
+ let raw_entry = RawEntry::from_slice(&source[pos..used])?;
146
+
147
+ if pos + raw_entry.total_len() > used {
148
+ return Err(MmapError::PromParsing(format!(
149
+ "source file {} corrupted, used {used} < stored data length {}",
150
+ file_info.path.display(),
151
+ pos + raw_entry.total_len()
152
+ )));
153
+ }
154
+
155
+ let meta = EntryMetadata::new(&raw_entry, &file_info)?;
156
+ let data = BorrowedData::new(&raw_entry, &file_info, meta.is_pid_significant())?;
157
+
158
+ self.merge_or_store(data, meta)?;
159
+
160
+ pos += raw_entry.total_len();
161
+ }
162
+
163
+ Ok(())
164
+ }
165
+ }
166
+
167
+ #[cfg(test)]
168
+ mod test {
169
+ use magnus::{StaticSymbol, Symbol};
170
+ use std::mem;
171
+
172
+ use super::*;
173
+ use crate::file_entry::FileEntry;
174
+ use crate::testhelper::{self, TestFile};
175
+
176
+ impl EntryData {
177
+ /// A helper function for tests to convert owned data to references.
178
+ fn as_borrowed(&self) -> BorrowedData {
179
+ BorrowedData {
180
+ json: &self.json,
181
+ pid: self.pid.as_deref(),
182
+ }
183
+ }
184
+ }
185
+
186
+ #[test]
187
+ fn test_into_sorted() {
188
+ let _cleanup = unsafe { magnus::embed::init() };
189
+ let ruby = magnus::Ruby::get().unwrap();
190
+ crate::init(&ruby).unwrap();
191
+
192
+ let entries = vec![
193
+ FileEntry {
194
+ data: EntryData {
195
+ json: "zzzzzz".to_string(),
196
+ pid: Some("worker-0_0".to_string()),
197
+ },
198
+ meta: EntryMetadata {
199
+ multiprocess_mode: Symbol::new("max"),
200
+ type_: StaticSymbol::new("gauge"),
201
+ value: 1.0,
202
+ },
203
+ },
204
+ FileEntry {
205
+ data: EntryData {
206
+ json: "zzz".to_string(),
207
+ pid: Some("worker-0_0".to_string()),
208
+ },
209
+ meta: EntryMetadata {
210
+ multiprocess_mode: Symbol::new("max"),
211
+ type_: StaticSymbol::new("gauge"),
212
+ value: 1.0,
213
+ },
214
+ },
215
+ FileEntry {
216
+ data: EntryData {
217
+ json: "zzzaaa".to_string(),
218
+ pid: Some("worker-0_0".to_string()),
219
+ },
220
+ meta: EntryMetadata {
221
+ multiprocess_mode: Symbol::new("max"),
222
+ type_: StaticSymbol::new("gauge"),
223
+ value: 1.0,
224
+ },
225
+ },
226
+ FileEntry {
227
+ data: EntryData {
228
+ json: "aaa".to_string(),
229
+ pid: Some("worker-0_0".to_string()),
230
+ },
231
+ meta: EntryMetadata {
232
+ multiprocess_mode: Symbol::new("max"),
233
+ type_: StaticSymbol::new("gauge"),
234
+ value: 1.0,
235
+ },
236
+ },
237
+ FileEntry {
238
+ data: EntryData {
239
+ json: "ooo".to_string(),
240
+ pid: Some("worker-1_0".to_string()),
241
+ },
242
+ meta: EntryMetadata {
243
+ multiprocess_mode: Symbol::new("all"),
244
+ type_: StaticSymbol::new("gauge"),
245
+ value: 1.0,
246
+ },
247
+ },
248
+ FileEntry {
249
+ data: EntryData {
250
+ json: "ooo".to_string(),
251
+ pid: Some("worker-0_0".to_string()),
252
+ },
253
+ meta: EntryMetadata {
254
+ multiprocess_mode: Symbol::new("all"),
255
+ type_: StaticSymbol::new("gauge"),
256
+ value: 1.0,
257
+ },
258
+ },
259
+ ];
260
+
261
+ let mut map = EntryMap::new();
262
+
263
+ for entry in entries {
264
+ map.0.insert(entry.data, entry.meta);
265
+ }
266
+
267
+ let result = map.into_sorted();
268
+ assert!(result.is_ok());
269
+ let sorted = result.unwrap();
270
+ assert_eq!(sorted.len(), 6);
271
+ assert_eq!(sorted[0].data.json, "aaa");
272
+ assert_eq!(sorted[1].data.json, "ooo");
273
+ assert_eq!(sorted[1].data.pid.as_deref(), Some("worker-0_0"));
274
+ assert_eq!(sorted[2].data.json, "ooo");
275
+ assert_eq!(sorted[2].data.pid.as_deref(), Some("worker-1_0"));
276
+ assert_eq!(sorted[3].data.json, "zzz");
277
+ assert_eq!(sorted[4].data.json, "zzzaaa");
278
+ assert_eq!(sorted[5].data.json, "zzzzzz");
279
+ }
280
+
281
+ #[test]
282
+ fn test_merge_or_store() {
283
+ let _cleanup = unsafe { magnus::embed::init() };
284
+ let ruby = magnus::Ruby::get().unwrap();
285
+ crate::init(&ruby).unwrap();
286
+
287
+ let key = "foobar";
288
+
289
+ let starting_entry = FileEntry {
290
+ data: EntryData {
291
+ json: key.to_string(),
292
+ pid: Some("worker-0_0".to_string()),
293
+ },
294
+ meta: EntryMetadata {
295
+ multiprocess_mode: Symbol::new("all"),
296
+ type_: StaticSymbol::new("gauge"),
297
+ value: 1.0,
298
+ },
299
+ };
300
+
301
+ let matching_entry = FileEntry {
302
+ data: EntryData {
303
+ json: key.to_string(),
304
+ pid: Some("worker-0_0".to_string()),
305
+ },
306
+ meta: EntryMetadata {
307
+ multiprocess_mode: Symbol::new("all"),
308
+ type_: StaticSymbol::new("gauge"),
309
+ value: 5.0,
310
+ },
311
+ };
312
+
313
+ let same_key_different_worker = FileEntry {
314
+ data: EntryData {
315
+ json: key.to_string(),
316
+ pid: Some("worker-1_0".to_string()),
317
+ },
318
+ meta: EntryMetadata {
319
+ multiprocess_mode: Symbol::new("all"),
320
+ type_: StaticSymbol::new("gauge"),
321
+ value: 100.0,
322
+ },
323
+ };
324
+
325
+ let unmatched_entry = FileEntry {
326
+ data: EntryData {
327
+ json: "another key".to_string(),
328
+ pid: Some("worker-0_0".to_string()),
329
+ },
330
+ meta: EntryMetadata {
331
+ multiprocess_mode: Symbol::new("all"),
332
+ type_: StaticSymbol::new("gauge"),
333
+ value: 1.0,
334
+ },
335
+ };
336
+
337
+ let mut map = EntryMap::new();
338
+
339
+ map.0
340
+ .insert(starting_entry.data.clone(), starting_entry.meta.clone());
341
+
342
+ let matching_borrowed = matching_entry.data.as_borrowed();
343
+ map.merge_or_store(matching_borrowed, matching_entry.meta)
344
+ .unwrap();
345
+
346
+ assert_eq!(
347
+ 5.0,
348
+ map.0.get(&starting_entry.data).unwrap().value,
349
+ "value updated"
350
+ );
351
+ assert_eq!(1, map.0.len(), "no entry added");
352
+
353
+ let same_key_different_worker_borrowed = same_key_different_worker.data.as_borrowed();
354
+ map.merge_or_store(
355
+ same_key_different_worker_borrowed,
356
+ same_key_different_worker.meta,
357
+ )
358
+ .unwrap();
359
+
360
+ assert_eq!(
361
+ 5.0,
362
+ map.0.get(&starting_entry.data).unwrap().value,
363
+ "value unchanged"
364
+ );
365
+
366
+ assert_eq!(2, map.0.len(), "additional entry added");
367
+
368
+ let unmatched_entry_borrowed = unmatched_entry.data.as_borrowed();
369
+ map.merge_or_store(unmatched_entry_borrowed, unmatched_entry.meta)
370
+ .unwrap();
371
+
372
+ assert_eq!(
373
+ 5.0,
374
+ map.0.get(&starting_entry.data).unwrap().value,
375
+ "value unchanged"
376
+ );
377
+ assert_eq!(3, map.0.len(), "entry added");
378
+ }
379
+
380
+ #[test]
381
+ fn test_process_buffer() {
382
+ struct TestCase {
383
+ name: &'static str,
384
+ json: &'static [&'static str],
385
+ values: &'static [f64],
386
+ used: Option<u32>,
387
+ expected_ct: usize,
388
+ expected_err: Option<MmapError>,
389
+ }
390
+
391
+ let _cleanup = unsafe { magnus::embed::init() };
392
+ let ruby = magnus::Ruby::get().unwrap();
393
+ crate::init(&ruby).unwrap();
394
+
395
+ let tc = vec![
396
+ TestCase {
397
+ name: "single entry",
398
+ json: &[
399
+ r#"["first_family","first_name",["label_a","label_b"],["value_a","value_b"]]"#,
400
+ ],
401
+ values: &[1.0],
402
+ used: None,
403
+ expected_ct: 1,
404
+ expected_err: None,
405
+ },
406
+ TestCase {
407
+ name: "multiple entries",
408
+ json: &[
409
+ r#"["first_family","first_name",["label_a","label_b"],["value_a","value_b"]]"#,
410
+ r#"["second_family","first_name",["label_a","label_b"],["value_a","value_b"]]"#,
411
+ ],
412
+ values: &[1.0, 2.0],
413
+ used: None,
414
+ expected_ct: 2,
415
+ expected_err: None,
416
+ },
417
+ TestCase {
418
+ name: "empty",
419
+ json: &[],
420
+ values: &[],
421
+ used: None,
422
+ expected_ct: 0,
423
+ expected_err: None,
424
+ },
425
+ TestCase {
426
+ name: "used too long",
427
+ json: &[
428
+ r#"["first_family","first_name",["label_a","label_b"],["value_a","value_b"]]"#,
429
+ ],
430
+ values: &[1.0],
431
+ used: Some(9999),
432
+ expected_ct: 0,
433
+ expected_err: Some(MmapError::PromParsing(String::new())),
434
+ },
435
+ TestCase {
436
+ name: "used too short",
437
+ json: &[
438
+ r#"["first_family","first_name",["label_a","label_b"],["value_a","value_b"]]"#,
439
+ ],
440
+ values: &[1.0],
441
+ used: Some(15),
442
+ expected_ct: 0,
443
+ expected_err: Some(MmapError::out_of_bounds(88, 7)),
444
+ },
445
+ ];
446
+
447
+ for case in tc {
448
+ let name = case.name;
449
+
450
+ let input_bytes = testhelper::entries_to_db(case.json, case.values, case.used);
451
+
452
+ let TestFile {
453
+ file,
454
+ path,
455
+ dir: _dir,
456
+ } = TestFile::new(&input_bytes);
457
+
458
+ let info = FileInfo {
459
+ file,
460
+ path,
461
+ len: case.json.len(),
462
+ multiprocess_mode: Symbol::new("max"),
463
+ type_: StaticSymbol::new("gauge"),
464
+ pid: "worker-1".to_string(),
465
+ };
466
+
467
+ let mut map = EntryMap::new();
468
+ let result = map.process_buffer(info, &input_bytes);
469
+
470
+ assert_eq!(case.expected_ct, map.0.len(), "test case: {name} - count");
471
+
472
+ if let Some(expected_err) = case.expected_err {
473
+ // Validate we have the right enum type for the error. Error
474
+ // messages contain the temp dir path and can't be predicted
475
+ // exactly.
476
+ assert_eq!(
477
+ mem::discriminant(&expected_err),
478
+ mem::discriminant(&result.unwrap_err()),
479
+ "test case: {name} - failure"
480
+ );
481
+ } else {
482
+ assert_eq!(Ok(()), result, "test case: {name} - success");
483
+
484
+ assert_eq!(
485
+ case.json.len(),
486
+ map.0.len(),
487
+ "test case: {name} - all entries captured"
488
+ );
489
+ }
490
+ }
491
+ }
492
+ }
@@ -0,0 +1,151 @@
1
+ use magnus::typed_data::Obj;
2
+ use magnus::value::Fixnum;
3
+ use magnus::{Integer, RArray, RClass, RHash, RString, Value};
4
+
5
+ use crate::file_entry::FileEntry;
6
+ use crate::map::EntryMap;
7
+
8
+ /// A Rust struct wrapped in a Ruby object, providing access to a memory-mapped
9
+ /// file used to store, update, and read out Prometheus metrics.
10
+ ///
11
+ /// - File format:
12
+ /// - Header:
13
+ /// - 4 bytes: u32 - total size of metrics in file.
14
+ /// - 4 bytes: NUL byte padding.
15
+ /// - Repeating metrics entries:
16
+ /// - 4 bytes: u32 - entry JSON string size.
17
+ /// - `N` bytes: UTF-8 encoded JSON string used as entry key.
18
+ /// - (8 - (4 + `N`) % 8) bytes: 1 to 8 padding space (0x20) bytes to
19
+ /// reach 8-byte alignment.
20
+ /// - 8 bytes: f64 - entry value.
21
+ ///
22
+ /// All numbers are saved in native-endian format.
23
+ ///
24
+ /// Generated via [luismartingarcia/protocol](https://github.com/luismartingarcia/protocol):
25
+ ///
26
+ ///
27
+ /// ```
28
+ /// protocol "Used:4,Pad:4,K1 Size:4,K1 Name:4,K1 Value:8,K2 Size:4,K2 Name:4,K2 Value:8"
29
+ ///
30
+ /// 0 1 2 3
31
+ /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
32
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
33
+ /// | Used | Pad |K1 Size|K1 Name| K1 Value |K2 Size|K2 Name|
34
+ /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
35
+ /// | K2 Value |
36
+ /// +-+-+-+-+-+-+-+
37
+ /// ```
38
+ #[derive(Debug, Default)]
39
+ #[magnus::wrap(class = "FastMmapedFileRs", free_immediately, size)]
40
+ pub struct MmapedFile;
41
+
42
+ impl MmapedFile {
43
+ /// call-seq:
44
+ /// new(file)
45
+ ///
46
+ /// create a new Mmap object
47
+ ///
48
+ /// * <em>file</em>
49
+ ///
50
+ ///
51
+ /// Creates a mapping that's shared with all other processes
52
+ /// mapping the same area of the file.
53
+ pub fn new(_klass: RClass, _args: &[Value]) -> magnus::error::Result<Obj<Self>> {
54
+ Ok(Obj::wrap(Self))
55
+ }
56
+
57
+ /// Initialize a new `FastMmapedFileRs` object. This must be defined in
58
+ /// order for inheritance to work.
59
+ pub fn initialize(_rb_self: Obj<Self>, _fname: String) -> magnus::error::Result<()> {
60
+ unimplemented!();
61
+ }
62
+
63
+ /// Read the list of files provided from Ruby and convert them to a Prometheus
64
+ /// metrics String.
65
+ pub fn to_metrics(file_list: RArray) -> magnus::error::Result<String> {
66
+ let mut map = EntryMap::new();
67
+ map.aggregate_files(file_list)?;
68
+
69
+ let sorted = map.into_sorted()?;
70
+
71
+ FileEntry::entries_to_string(sorted).map_err(|e| e.into())
72
+ }
73
+
74
+ /// Document-method: []
75
+ /// Document-method: slice
76
+ ///
77
+ /// call-seq: [](args)
78
+ ///
79
+ /// Element reference - with the following syntax:
80
+ ///
81
+ /// self[nth]
82
+ ///
83
+ /// retrieve the <em>nth</em> character
84
+ ///
85
+ /// self[start..last]
86
+ ///
87
+ /// return a substring from <em>start</em> to <em>last</em>
88
+ ///
89
+ /// self[start, length]
90
+ ///
91
+ /// return a substring of <em>lenght</em> characters from <em>start</em>
92
+ pub fn slice(_rb_self: Obj<Self>, _args: &[Value]) -> magnus::error::Result<RString> {
93
+ unimplemented!();
94
+ }
95
+
96
+ /// Document-method: msync
97
+ /// Document-method: sync
98
+ /// Document-method: flush
99
+ ///
100
+ /// call-seq: msync
101
+ ///
102
+ /// flush the file
103
+ pub fn sync(&self, _args: &[Value]) -> magnus::error::Result<()> {
104
+ unimplemented!();
105
+ }
106
+
107
+ /// Document-method: munmap
108
+ /// Document-method: unmap
109
+ ///
110
+ /// call-seq: munmap
111
+ ///
112
+ /// terminate the association
113
+ pub fn munmap(_rb_self: Obj<Self>) -> magnus::error::Result<()> {
114
+ unimplemented!();
115
+ }
116
+
117
+ /// Fetch the `used` header from the `.db` file, the length
118
+ /// in bytes of the data written to the file.
119
+ pub fn load_used(&self) -> magnus::error::Result<Integer> {
120
+ unimplemented!();
121
+ }
122
+
123
+ /// Update the `used` header for the `.db` file, the length
124
+ /// in bytes of the data written to the file.
125
+ pub fn save_used(_rb_self: Obj<Self>, _used: Fixnum) -> magnus::error::Result<Fixnum> {
126
+ unimplemented!();
127
+ }
128
+
129
+ /// Fetch the value associated with a key from the mmap.
130
+ /// If no entry is present, initialize with the default
131
+ /// value provided.
132
+ pub fn fetch_entry(
133
+ _rb_self: Obj<Self>,
134
+ _positions: RHash,
135
+ _key: RString,
136
+ _default_value: f64,
137
+ ) -> magnus::error::Result<f64> {
138
+ unimplemented!();
139
+ }
140
+
141
+ /// Update the value of an existing entry, if present. Otherwise create a new entry
142
+ /// for the key.
143
+ pub fn upsert_entry(
144
+ _rb_self: Obj<Self>,
145
+ _positions: RHash,
146
+ _key: RString,
147
+ _value: f64,
148
+ ) -> magnus::error::Result<f64> {
149
+ unimplemented!();
150
+ }
151
+ }