vinted-prometheus-client-mmap 1.2.2 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/fast_mmaped_file_rs/src/error.rs +1 -1
- data/ext/fast_mmaped_file_rs/src/exemplars.rs +10 -10
- data/ext/fast_mmaped_file_rs/src/file_entry.rs +102 -40
- data/ext/fast_mmaped_file_rs/src/lib.rs +2 -0
- data/ext/fast_mmaped_file_rs/src/map.rs +51 -24
- data/ext/fast_mmaped_file_rs/src/mmap/inner.rs +72 -1
- data/ext/fast_mmaped_file_rs/src/mmap.rs +82 -1
- data/ext/fast_mmaped_file_rs/src/raw_entry.rs +74 -0
- data/ext/fast_mmaped_file_rs/src/util.rs +19 -0
- data/lib/prometheus/client/counter.rb +2 -2
- data/lib/prometheus/client/formats/protobuf.rb +2 -1
- data/lib/prometheus/client/histogram.rb +2 -2
- data/lib/prometheus/client/mmaped_dict.rb +4 -0
- data/lib/prometheus/client/mmaped_value.rb +12 -6
- data/lib/prometheus/client/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86a6083d5e7a88ac1e5e96a6660ebbd70fa31cecb1b1e6e324e26c4e91279713
|
4
|
+
data.tar.gz: a14011ec2a97f717bf0a18913fdde846b29ad742acb69b846b9f77c93ee7004f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e735c20e4bca0539a7e1219a1c9a0f7d3c61f838af04928e3d75d293bbf090be50ca9708aa0819234dc60025baa578795c65c8100da0550022cf4c0867186a65
|
7
|
+
data.tar.gz: bad6fe000c9a94c57797f3337fa961759a25660a337794d644b1edad8402b3a95314e51add28d6c9d3f4749ee09af3112658f13db8e95b2bb18fade77c580d6e
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#[derive(Clone, Debug)]
|
1
|
+
#[derive(Clone, Debug, Serialize, Deserialize)]
|
2
2
|
pub struct Exemplar {
|
3
3
|
// Labels (set of label names/values). Only 1 for now.
|
4
4
|
// Value -> f64.
|
@@ -8,18 +8,18 @@ pub struct Exemplar {
|
|
8
8
|
// The combined length of the label names and values of an Exemplar's LabelSet MUST NOT exceed 128 UTF-8 character code points.
|
9
9
|
// 4 bytes max per code point.
|
10
10
|
// So, we need to allocate 128*4 = 512 bytes for the label names and values.
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
pub label_name: String,
|
12
|
+
|
13
|
+
pub label_value: String,
|
14
|
+
pub value: f64,
|
15
|
+
pub timestamp: u128,
|
15
16
|
}
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
}
|
18
|
+
use serde::{Deserialize, Serialize};
|
19
|
+
|
20
|
+
use crate::size_of;
|
21
21
|
|
22
|
-
pub const EXEMPLAR_ENTRY_MAX_SIZE_BYTES:
|
22
|
+
pub const EXEMPLAR_ENTRY_MAX_SIZE_BYTES:usize = 512 + size_of::<f64>() + size_of::<u64>();
|
23
23
|
|
24
24
|
// Key -> use the old one.
|
25
25
|
// Value -> allocate EXEMPLAR_ENTRY_MAX_SIZE_BYTES. If it exceeds this, we need to return an error. Use JSON.
|
@@ -7,6 +7,7 @@ use std::fmt::Write;
|
|
7
7
|
use std::str;
|
8
8
|
|
9
9
|
use crate::error::{MmapError, RubyError};
|
10
|
+
use crate::exemplars::Exemplar;
|
10
11
|
use crate::file_info::FileInfo;
|
11
12
|
use crate::raw_entry::RawEntry;
|
12
13
|
use crate::Result;
|
@@ -104,34 +105,72 @@ impl<'a> BorrowedData<'a> {
|
|
104
105
|
pub struct EntryMetadata {
|
105
106
|
pub multiprocess_mode: Symbol,
|
106
107
|
pub type_: Symbol,
|
107
|
-
pub value: f64
|
108
|
+
pub value: Option<f64>,
|
109
|
+
pub ex: Option<Exemplar>,
|
108
110
|
}
|
109
111
|
|
110
112
|
impl EntryMetadata {
|
111
113
|
/// Construct a new `FileEntry`, copying the JSON string from the `RawEntry`
|
112
114
|
/// into an internal buffer.
|
113
115
|
pub fn new(mmap_entry: &RawEntry, file: &FileInfo) -> Result<Self> {
|
116
|
+
if file.type_.to_string() == "exemplar" {
|
117
|
+
let ex = mmap_entry.exemplar();
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
return Ok(EntryMetadata {
|
122
|
+
multiprocess_mode: file.multiprocess_mode,
|
123
|
+
type_: file.type_,
|
124
|
+
value: None,
|
125
|
+
ex: Some(ex),
|
126
|
+
})
|
127
|
+
}
|
128
|
+
|
114
129
|
let value = mmap_entry.value();
|
115
130
|
|
116
131
|
Ok(EntryMetadata {
|
117
132
|
multiprocess_mode: file.multiprocess_mode,
|
118
133
|
type_: file.type_,
|
119
|
-
value,
|
134
|
+
value: Some(value),
|
135
|
+
ex: None,
|
120
136
|
})
|
121
137
|
}
|
122
138
|
|
123
139
|
/// Combine values with another `EntryMetadata`.
|
124
140
|
pub fn merge(&mut self, other: &Self) {
|
125
|
-
if
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
141
|
+
if other.ex.is_some() {
|
142
|
+
let otherex = other.ex.clone().unwrap();
|
143
|
+
|
144
|
+
if self.ex.is_some() {
|
145
|
+
let selfex = self.ex.clone().unwrap();
|
146
|
+
|
147
|
+
if selfex.timestamp < otherex.timestamp {
|
148
|
+
self.ex = other.ex.clone();
|
149
|
+
}
|
150
|
+
} else {
|
151
|
+
self.ex = other.ex.clone();
|
131
152
|
}
|
132
|
-
} else {
|
133
|
-
self.value += other.value;
|
134
153
|
}
|
154
|
+
if other.value.is_some() {
|
155
|
+
if self.value.is_none() {
|
156
|
+
self.value = other.value;
|
157
|
+
} else {
|
158
|
+
let other_value = other.value.unwrap();
|
159
|
+
let self_value = self.value.unwrap();
|
160
|
+
|
161
|
+
if self.type_ == SYM_GAUGE {
|
162
|
+
match self.multiprocess_mode {
|
163
|
+
s if s == SYM_MIN => self.value = Some(self_value.min(other_value)),
|
164
|
+
s if s == SYM_MAX => self.value = Some(self_value.max(other_value)),
|
165
|
+
s if s == SYM_LIVESUM => self.value = Some(self_value + other_value),
|
166
|
+
_ => self.value = Some(other_value),
|
167
|
+
}
|
168
|
+
} else {
|
169
|
+
self.value = Some(self_value + other_value);
|
170
|
+
}
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
135
174
|
}
|
136
175
|
|
137
176
|
/// Validate if pid is significant for metric.
|
@@ -149,9 +188,26 @@ use std::collections::hash_map::DefaultHasher;
|
|
149
188
|
use std::collections::HashMap;
|
150
189
|
use std::hash::Hash;
|
151
190
|
use std::hash::Hasher;
|
152
|
-
use std::time::{SystemTime, UNIX_EPOCH};
|
153
191
|
|
154
192
|
use std::io::Write as OtherWrite;
|
193
|
+
|
194
|
+
fn exemplar_to_proto(e: &Exemplar) -> io::prometheus::client::Exemplar {
|
195
|
+
let seconds = e.timestamp / (1000 * 1000 * 1000);
|
196
|
+
let nanos = e.timestamp % (1000 * 1000 * 1000);
|
197
|
+
|
198
|
+
io::prometheus::client::Exemplar {
|
199
|
+
label: vec![io::prometheus::client::LabelPair {
|
200
|
+
name: Some(e.label_name.clone()),
|
201
|
+
value: Some(e.label_value.clone()),
|
202
|
+
}],
|
203
|
+
value: Some(e.value),
|
204
|
+
timestamp: Some(prost_types::Timestamp {
|
205
|
+
seconds: seconds as i64,
|
206
|
+
nanos: nanos as i32,
|
207
|
+
}),
|
208
|
+
}
|
209
|
+
}
|
210
|
+
|
155
211
|
impl FileEntry {
|
156
212
|
pub fn trim_quotes(s: &str) -> String {
|
157
213
|
let mut chars = s.chars();
|
@@ -229,26 +285,17 @@ impl FileEntry {
|
|
229
285
|
// Get the final u64 hash value
|
230
286
|
let hash_value = hasher.finish();
|
231
287
|
|
232
|
-
let start = SystemTime::now();
|
233
|
-
let since_the_epoch = start
|
234
|
-
.duration_since(UNIX_EPOCH)
|
235
|
-
.expect("Time went backwards");
|
236
|
-
|
237
288
|
m.counter = Some(io::prometheus::client::Counter {
|
238
|
-
value:
|
289
|
+
value: gr.0.meta.value,
|
239
290
|
created_timestamp: None,
|
240
|
-
exemplar:
|
241
|
-
label: vec![
|
242
|
-
io::prometheus::client::LabelPair {
|
243
|
-
name: Some("traceID".to_string()),
|
244
|
-
value: Some("123456789".to_string()),
|
245
|
-
}
|
246
|
-
],
|
247
|
-
value: Some(gr.0.meta.value),
|
248
|
-
timestamp: Some(prost_types::Timestamp { seconds:since_the_epoch.as_secs() as i64 , nanos: since_the_epoch.as_nanos() as i32 }),
|
249
|
-
}),
|
291
|
+
exemplar: None,
|
250
292
|
});
|
251
293
|
|
294
|
+
if gr.0.meta.ex.is_some() {
|
295
|
+
m.counter.as_mut().unwrap().exemplar =
|
296
|
+
Some(exemplar_to_proto(gr.0.meta.ex.as_ref().unwrap()));
|
297
|
+
}
|
298
|
+
|
252
299
|
mtrcs.insert(hash_value, m);
|
253
300
|
metric_types.insert(hash_value, "counter");
|
254
301
|
metric_names.insert(hash_value, gr.1.metric_name);
|
@@ -266,7 +313,7 @@ impl FileEntry {
|
|
266
313
|
let hash_value = hasher.finish();
|
267
314
|
|
268
315
|
m.gauge = Some(io::prometheus::client::Gauge {
|
269
|
-
value:
|
316
|
+
value: gr.0.meta.value,
|
270
317
|
});
|
271
318
|
mtrcs.insert(hash_value, m);
|
272
319
|
metric_types.insert(hash_value, "gauge");
|
@@ -312,9 +359,14 @@ impl FileEntry {
|
|
312
359
|
|
313
360
|
let mut curf: f64 =
|
314
361
|
bucket.cumulative_count_float.unwrap_or_default();
|
315
|
-
curf += gr.0.meta.value;
|
362
|
+
curf += gr.0.meta.value.unwrap();
|
316
363
|
|
317
364
|
bucket.cumulative_count_float = Some(curf);
|
365
|
+
|
366
|
+
if gr.0.meta.ex.is_some() {
|
367
|
+
bucket.exemplar =
|
368
|
+
Some(exemplar_to_proto(gr.0.meta.ex.as_ref().unwrap()));
|
369
|
+
}
|
318
370
|
}
|
319
371
|
}
|
320
372
|
None => {
|
@@ -333,9 +385,9 @@ impl FileEntry {
|
|
333
385
|
final_metric_name = stripped;
|
334
386
|
}
|
335
387
|
|
336
|
-
let buckets = vec![io::prometheus::client::Bucket {
|
388
|
+
let mut buckets = vec![io::prometheus::client::Bucket {
|
337
389
|
cumulative_count: None,
|
338
|
-
cumulative_count_float:
|
390
|
+
cumulative_count_float: gr.0.meta.value,
|
339
391
|
upper_bound: Some(
|
340
392
|
le.expect(
|
341
393
|
&format!("got no LE for {}", gr.1.metric_name)
|
@@ -344,6 +396,11 @@ impl FileEntry {
|
|
344
396
|
),
|
345
397
|
exemplar: None,
|
346
398
|
}];
|
399
|
+
|
400
|
+
if gr.0.meta.ex.is_some() {
|
401
|
+
buckets[0].exemplar =
|
402
|
+
Some(exemplar_to_proto(gr.0.meta.ex.as_ref().unwrap()));
|
403
|
+
}
|
347
404
|
m.label = m
|
348
405
|
.label
|
349
406
|
.into_iter()
|
@@ -413,10 +470,10 @@ impl FileEntry {
|
|
413
470
|
if gr.1.metric_name.ends_with("_count") {
|
414
471
|
let samplecount = smry.sample_count.unwrap_or_default();
|
415
472
|
smry.sample_count =
|
416
|
-
Some((gr.0.meta.value as u64) + samplecount);
|
473
|
+
Some((gr.0.meta.value.unwrap() as u64) + samplecount);
|
417
474
|
} else if gr.1.metric_name.ends_with("_sum") {
|
418
475
|
let samplesum: f64 = smry.sample_sum.unwrap_or_default();
|
419
|
-
smry.sample_sum = Some(gr.0.meta.value + samplesum);
|
476
|
+
smry.sample_sum = Some(gr.0.meta.value.unwrap() + samplesum);
|
420
477
|
} else {
|
421
478
|
let mut found_quantile = false;
|
422
479
|
for qntl in &mut smry.quantile {
|
@@ -425,7 +482,7 @@ impl FileEntry {
|
|
425
482
|
}
|
426
483
|
|
427
484
|
let mut curq: f64 = qntl.quantile.unwrap_or_default();
|
428
|
-
curq += gr.0.meta.value;
|
485
|
+
curq += gr.0.meta.value.unwrap();
|
429
486
|
|
430
487
|
qntl.quantile = Some(curq);
|
431
488
|
found_quantile = true;
|
@@ -434,7 +491,7 @@ impl FileEntry {
|
|
434
491
|
if !found_quantile {
|
435
492
|
smry.quantile.push(io::prometheus::client::Quantile {
|
436
493
|
quantile: quantile,
|
437
|
-
value:
|
494
|
+
value: gr.0.meta.value,
|
438
495
|
});
|
439
496
|
}
|
440
497
|
}
|
@@ -455,7 +512,7 @@ impl FileEntry {
|
|
455
512
|
gr.1.metric_name.strip_suffix("_count").unwrap();
|
456
513
|
m.summary = Some(io::prometheus::client::Summary {
|
457
514
|
quantile: vec![],
|
458
|
-
sample_count: Some(gr.0.meta.value as u64),
|
515
|
+
sample_count: Some(gr.0.meta.value.unwrap() as u64),
|
459
516
|
sample_sum: None,
|
460
517
|
created_timestamp: None,
|
461
518
|
});
|
@@ -464,14 +521,14 @@ impl FileEntry {
|
|
464
521
|
gr.1.metric_name.strip_suffix("_sum").unwrap();
|
465
522
|
m.summary = Some(io::prometheus::client::Summary {
|
466
523
|
quantile: vec![],
|
467
|
-
sample_sum: Some(gr.0.meta.value),
|
524
|
+
sample_sum: Some(gr.0.meta.value.unwrap()),
|
468
525
|
sample_count: None,
|
469
526
|
created_timestamp: None,
|
470
527
|
});
|
471
528
|
} else {
|
472
529
|
let quantiles = vec![io::prometheus::client::Quantile {
|
473
530
|
quantile: quantile,
|
474
|
-
value:
|
531
|
+
value: gr.0.meta.value,
|
475
532
|
}];
|
476
533
|
m.summary = Some(io::prometheus::client::Summary {
|
477
534
|
quantile: quantiles,
|
@@ -487,6 +544,9 @@ impl FileEntry {
|
|
487
544
|
}
|
488
545
|
}
|
489
546
|
}
|
547
|
+
"exemplar" => {
|
548
|
+
// Exemplars are handled later on.
|
549
|
+
}
|
490
550
|
mtype => {
|
491
551
|
panic!("unhandled metric type {}", mtype)
|
492
552
|
}
|
@@ -532,6 +592,8 @@ impl FileEntry {
|
|
532
592
|
unsafe { Ok(str::from_utf8_unchecked(buffer.get_ref()).to_string()) }
|
533
593
|
}
|
534
594
|
|
595
|
+
|
596
|
+
|
535
597
|
/// Convert the sorted entries into a String in Prometheus metrics format.
|
536
598
|
pub fn entries_to_string(entries: Vec<FileEntry>) -> Result<String> {
|
537
599
|
// We guesstimate that lines are ~100 bytes long, preallocate the string to
|
@@ -568,7 +630,7 @@ impl FileEntry {
|
|
568
630
|
|
569
631
|
entry.append_entry(metrics_data, &mut out)?;
|
570
632
|
|
571
|
-
writeln!(&mut out, " {}", entry.meta.value)
|
633
|
+
writeln!(&mut out, " {}", entry.meta.value.unwrap())
|
572
634
|
.map_err(|e| MmapError::Other(format!("Failed to append to output: {e}")))?;
|
573
635
|
|
574
636
|
processed_count += 1;
|
@@ -1182,7 +1244,7 @@ mod test {
|
|
1182
1244
|
entry_a.meta.merge(&entry_b.meta);
|
1183
1245
|
|
1184
1246
|
assert_eq!(
|
1185
|
-
case.expected_value, entry_a.meta.value,
|
1247
|
+
case.expected_value, entry_a.meta.value.unwrap(),
|
1186
1248
|
"test case: {name} - value"
|
1187
1249
|
);
|
1188
1250
|
}
|
@@ -14,6 +14,7 @@ pub mod map;
|
|
14
14
|
pub mod mmap;
|
15
15
|
pub mod raw_entry;
|
16
16
|
pub mod util;
|
17
|
+
pub mod exemplars;
|
17
18
|
|
18
19
|
pub mod io {
|
19
20
|
pub mod prometheus {
|
@@ -82,6 +83,7 @@ fn init(ruby: &Ruby) -> magnus::error::Result<()> {
|
|
82
83
|
klass.define_method("used=", method!(MmapedFile::save_used, 1))?;
|
83
84
|
klass.define_method("fetch_entry", method!(MmapedFile::fetch_entry, 3))?;
|
84
85
|
klass.define_method("upsert_entry", method!(MmapedFile::upsert_entry, 3))?;
|
86
|
+
klass.define_method("upsert_exemplar", method!(MmapedFile::upsert_exemplar, 5))?;
|
85
87
|
|
86
88
|
Ok(())
|
87
89
|
}
|
@@ -142,22 +142,39 @@ impl EntryMap {
|
|
142
142
|
let mut pos = HEADER_SIZE;
|
143
143
|
|
144
144
|
while pos + size_of::<u32>() < used {
|
145
|
-
let raw_entry
|
146
|
-
|
147
|
-
if
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
145
|
+
let raw_entry: RawEntry;
|
146
|
+
|
147
|
+
if file_info.type_.to_string() == "exemplar" {
|
148
|
+
raw_entry = RawEntry::from_slice_exemplar(&source[pos..used])?;
|
149
|
+
|
150
|
+
if pos + raw_entry.total_len_exemplar() > used {
|
151
|
+
return Err(MmapError::PromParsing(format!(
|
152
|
+
"source file {} corrupted, used {used} < stored data length {}",
|
153
|
+
file_info.path.display(),
|
154
|
+
pos + raw_entry.total_len()
|
155
|
+
)));
|
156
|
+
}
|
154
157
|
|
158
|
+
pos += raw_entry.total_len_exemplar();
|
159
|
+
|
160
|
+
} else {
|
161
|
+
raw_entry = RawEntry::from_slice(&source[pos..used])?;
|
162
|
+
|
163
|
+
if pos + raw_entry.total_len() > used {
|
164
|
+
return Err(MmapError::PromParsing(format!(
|
165
|
+
"source file {} corrupted, used {used} < stored data length {}",
|
166
|
+
file_info.path.display(),
|
167
|
+
pos + raw_entry.total_len()
|
168
|
+
)));
|
169
|
+
}
|
170
|
+
|
171
|
+
pos += raw_entry.total_len();
|
172
|
+
}
|
173
|
+
|
155
174
|
let meta = EntryMetadata::new(&raw_entry, &file_info)?;
|
156
175
|
let data = BorrowedData::new(&raw_entry, &file_info, meta.is_pid_significant())?;
|
157
176
|
|
158
177
|
self.merge_or_store(data, meta)?;
|
159
|
-
|
160
|
-
pos += raw_entry.total_len();
|
161
178
|
}
|
162
179
|
|
163
180
|
Ok(())
|
@@ -198,7 +215,8 @@ mod test {
|
|
198
215
|
meta: EntryMetadata {
|
199
216
|
multiprocess_mode: Symbol::new("max"),
|
200
217
|
type_: Symbol::new("gauge"),
|
201
|
-
value: 1.0,
|
218
|
+
value: Some(1.0),
|
219
|
+
ex: None,
|
202
220
|
},
|
203
221
|
},
|
204
222
|
FileEntry {
|
@@ -209,7 +227,8 @@ mod test {
|
|
209
227
|
meta: EntryMetadata {
|
210
228
|
multiprocess_mode: Symbol::new("max"),
|
211
229
|
type_: Symbol::new("gauge"),
|
212
|
-
value: 1.0,
|
230
|
+
value: Some(1.0),
|
231
|
+
ex: None,
|
213
232
|
},
|
214
233
|
},
|
215
234
|
FileEntry {
|
@@ -220,7 +239,8 @@ mod test {
|
|
220
239
|
meta: EntryMetadata {
|
221
240
|
multiprocess_mode: Symbol::new("max"),
|
222
241
|
type_: Symbol::new("gauge"),
|
223
|
-
value: 1.0,
|
242
|
+
value: Some(1.0),
|
243
|
+
ex: None,
|
224
244
|
},
|
225
245
|
},
|
226
246
|
FileEntry {
|
@@ -231,7 +251,8 @@ mod test {
|
|
231
251
|
meta: EntryMetadata {
|
232
252
|
multiprocess_mode: Symbol::new("max"),
|
233
253
|
type_: Symbol::new("gauge"),
|
234
|
-
value: 1.0,
|
254
|
+
value: Some(1.0),
|
255
|
+
ex: None,
|
235
256
|
},
|
236
257
|
},
|
237
258
|
FileEntry {
|
@@ -242,7 +263,8 @@ mod test {
|
|
242
263
|
meta: EntryMetadata {
|
243
264
|
multiprocess_mode: Symbol::new("all"),
|
244
265
|
type_: Symbol::new("gauge"),
|
245
|
-
value: 1.0,
|
266
|
+
value: Some(1.0),
|
267
|
+
ex: None,
|
246
268
|
},
|
247
269
|
},
|
248
270
|
FileEntry {
|
@@ -253,7 +275,8 @@ mod test {
|
|
253
275
|
meta: EntryMetadata {
|
254
276
|
multiprocess_mode: Symbol::new("all"),
|
255
277
|
type_: Symbol::new("gauge"),
|
256
|
-
value: 1.0,
|
278
|
+
value: Some(1.0),
|
279
|
+
ex: None,
|
257
280
|
},
|
258
281
|
},
|
259
282
|
];
|
@@ -294,7 +317,8 @@ mod test {
|
|
294
317
|
meta: EntryMetadata {
|
295
318
|
multiprocess_mode: Symbol::new("all"),
|
296
319
|
type_: Symbol::new("gauge"),
|
297
|
-
value: 1.0,
|
320
|
+
value: Some(1.0),
|
321
|
+
ex: None,
|
298
322
|
},
|
299
323
|
};
|
300
324
|
|
@@ -306,7 +330,8 @@ mod test {
|
|
306
330
|
meta: EntryMetadata {
|
307
331
|
multiprocess_mode: Symbol::new("all"),
|
308
332
|
type_: Symbol::new("gauge"),
|
309
|
-
value: 5.0,
|
333
|
+
value: Some(5.0),
|
334
|
+
ex: None,
|
310
335
|
},
|
311
336
|
};
|
312
337
|
|
@@ -318,7 +343,8 @@ mod test {
|
|
318
343
|
meta: EntryMetadata {
|
319
344
|
multiprocess_mode: Symbol::new("all"),
|
320
345
|
type_: Symbol::new("gauge"),
|
321
|
-
value: 100.0,
|
346
|
+
value: Some(100.0),
|
347
|
+
ex: None,
|
322
348
|
},
|
323
349
|
};
|
324
350
|
|
@@ -330,7 +356,8 @@ mod test {
|
|
330
356
|
meta: EntryMetadata {
|
331
357
|
multiprocess_mode: Symbol::new("all"),
|
332
358
|
type_: Symbol::new("gauge"),
|
333
|
-
value:
|
359
|
+
value: Some(100.0),
|
360
|
+
ex: None,
|
334
361
|
},
|
335
362
|
};
|
336
363
|
|
@@ -345,7 +372,7 @@ mod test {
|
|
345
372
|
|
346
373
|
assert_eq!(
|
347
374
|
5.0,
|
348
|
-
map.0.get(&starting_entry.data).unwrap().value,
|
375
|
+
map.0.get(&starting_entry.data).unwrap().value.unwrap(),
|
349
376
|
"value updated"
|
350
377
|
);
|
351
378
|
assert_eq!(1, map.0.len(), "no entry added");
|
@@ -359,7 +386,7 @@ mod test {
|
|
359
386
|
|
360
387
|
assert_eq!(
|
361
388
|
5.0,
|
362
|
-
map.0.get(&starting_entry.data).unwrap().value,
|
389
|
+
map.0.get(&starting_entry.data).unwrap().value.unwrap(),
|
363
390
|
"value unchanged"
|
364
391
|
);
|
365
392
|
|
@@ -371,7 +398,7 @@ mod test {
|
|
371
398
|
|
372
399
|
assert_eq!(
|
373
400
|
5.0,
|
374
|
-
map.0.get(&starting_entry.data).unwrap().value,
|
401
|
+
map.0.get(&starting_entry.data).unwrap().value.unwrap(),
|
375
402
|
"value unchanged"
|
376
403
|
);
|
377
404
|
assert_eq!(3, map.0.len(), "entry added");
|
@@ -9,10 +9,13 @@ use std::path::PathBuf;
|
|
9
9
|
|
10
10
|
use crate::error::{MmapError, RubyError};
|
11
11
|
use crate::raw_entry::RawEntry;
|
12
|
-
use crate::
|
12
|
+
use crate::exemplars::{Exemplar, EXEMPLAR_ENTRY_MAX_SIZE_BYTES};
|
13
|
+
|
14
|
+
use crate::util::{read_exemplar, CheckedOps};
|
13
15
|
use crate::util::{self, errno, read_f64, read_u32};
|
14
16
|
use crate::Result;
|
15
17
|
use crate::HEADER_SIZE;
|
18
|
+
use std::iter;
|
16
19
|
|
17
20
|
/// A mmapped file and its metadata. Ruby never directly interfaces
|
18
21
|
/// with this struct.
|
@@ -139,6 +142,63 @@ impl InnerMmap {
|
|
139
142
|
Ok(position)
|
140
143
|
}
|
141
144
|
|
145
|
+
pub unsafe fn initialize_entry_exemplar(&mut self, key: &[u8], ex: Exemplar) -> Result<usize> {
|
146
|
+
// CAST: no-op on 32-bit, widening on 64-bit.
|
147
|
+
let current_used = self.load_used()? as usize;
|
148
|
+
let entry_length = RawEntry::calc_total_len_exemplar(key.len())?;
|
149
|
+
|
150
|
+
let new_used = current_used.add_chk(entry_length)?;
|
151
|
+
|
152
|
+
// Increasing capacity requires expanding the file and re-mmapping it, we can't
|
153
|
+
// perform this from `InnerMmap`.
|
154
|
+
if self.capacity() < new_used {
|
155
|
+
return Err(MmapError::Other(format!(
|
156
|
+
"mmap capacity {} less than {}",
|
157
|
+
self.capacity(),
|
158
|
+
new_used
|
159
|
+
)));
|
160
|
+
}
|
161
|
+
|
162
|
+
let bytes = self.map.as_mut();
|
163
|
+
let value_offset = RawEntry::save_exemplar(&mut bytes[current_used..new_used], key, ex)?;
|
164
|
+
|
165
|
+
// Won't overflow as value_offset is less than new_used.
|
166
|
+
let position = current_used + value_offset;
|
167
|
+
let new_used32 = util::cast_chk::<_, u32>(new_used, "used")?;
|
168
|
+
|
169
|
+
self.save_used(new_used32)?;
|
170
|
+
Ok(position)
|
171
|
+
}
|
172
|
+
|
173
|
+
pub fn save_exemplar(&mut self, offset: usize, exemplar: Exemplar) -> Result<()> {
|
174
|
+
if self.len.add_chk(size_of::<Exemplar>())? <= offset {
|
175
|
+
return Err(MmapError::out_of_bounds(
|
176
|
+
offset + size_of::<f64>(),
|
177
|
+
self.len,
|
178
|
+
));
|
179
|
+
}
|
180
|
+
|
181
|
+
if offset < HEADER_SIZE {
|
182
|
+
return Err(MmapError::Other(format!(
|
183
|
+
"writing to offset {offset} would overwrite file header"
|
184
|
+
)));
|
185
|
+
}
|
186
|
+
|
187
|
+
let val = serde_json::to_string(&exemplar).unwrap();
|
188
|
+
|
189
|
+
let value_bytes = val.as_bytes();
|
190
|
+
|
191
|
+
let mut value_bytes = value_bytes.to_vec();
|
192
|
+
value_bytes.extend(iter::repeat(0).take(EXEMPLAR_ENTRY_MAX_SIZE_BYTES - value_bytes.len()));
|
193
|
+
|
194
|
+
let value_range = self.item_range(offset, EXEMPLAR_ENTRY_MAX_SIZE_BYTES)?;
|
195
|
+
|
196
|
+
let bytes = self.map.as_mut();
|
197
|
+
bytes[value_range].copy_from_slice(&value_bytes);
|
198
|
+
|
199
|
+
Ok(())
|
200
|
+
}
|
201
|
+
|
142
202
|
/// Save a metrics value to an existing entry in the mmap.
|
143
203
|
pub fn save_value(&mut self, offset: usize, value: f64) -> Result<()> {
|
144
204
|
if self.len.add_chk(size_of::<f64>())? <= offset {
|
@@ -174,6 +234,17 @@ impl InnerMmap {
|
|
174
234
|
read_f64(self.map.as_ref(), offset)
|
175
235
|
}
|
176
236
|
|
237
|
+
pub fn load_exemplar(&mut self, offset: usize) -> Result<Exemplar> {
|
238
|
+
if self.len.add_chk(EXEMPLAR_ENTRY_MAX_SIZE_BYTES)? <= offset {
|
239
|
+
return Err(MmapError::out_of_bounds(
|
240
|
+
offset + EXEMPLAR_ENTRY_MAX_SIZE_BYTES,
|
241
|
+
self.len,
|
242
|
+
));
|
243
|
+
}
|
244
|
+
|
245
|
+
read_exemplar(self.map.as_mut(), offset)
|
246
|
+
}
|
247
|
+
|
177
248
|
/// The length of data written to the file.
|
178
249
|
/// With a new file this is only set when Ruby calls `slice` on
|
179
250
|
/// `FastMmapedFileRs`, so even if data has been written to the
|
@@ -15,6 +15,7 @@ use std::sync::RwLock;
|
|
15
15
|
|
16
16
|
use crate::err;
|
17
17
|
use crate::error::MmapError;
|
18
|
+
use crate::exemplars::Exemplar;
|
18
19
|
use crate::file_entry::FileEntry;
|
19
20
|
use crate::map::EntryMap;
|
20
21
|
use crate::raw_entry::RawEntry;
|
@@ -76,6 +77,8 @@ const STR_SHARED: c_ulong = 1 << (14);
|
|
76
77
|
#[magnus::wrap(class = "FastMmapedFileRs", free_immediately, size)]
|
77
78
|
pub struct MmapedFile(RwLock<Option<InnerMmap>>);
|
78
79
|
|
80
|
+
use std::time::{SystemTime, UNIX_EPOCH};
|
81
|
+
|
79
82
|
impl MmapedFile {
|
80
83
|
/// call-seq:
|
81
84
|
/// new(file)
|
@@ -320,6 +323,61 @@ impl MmapedFile {
|
|
320
323
|
rs_self.load_value(value_offset)
|
321
324
|
}
|
322
325
|
|
326
|
+
pub fn upsert_exemplar(
|
327
|
+
rb_self: Obj<Self>,
|
328
|
+
positions: RHash,
|
329
|
+
key: RString,
|
330
|
+
value: f64,
|
331
|
+
exemplar_name: RString,
|
332
|
+
exemplar_value: RString,
|
333
|
+
) -> magnus::error::Result<f64> {
|
334
|
+
let rs_self = &*rb_self;
|
335
|
+
let position: Option<Fixnum> = positions.lookup(key)?;
|
336
|
+
|
337
|
+
let start = SystemTime::now();
|
338
|
+
let since_the_epoch = start
|
339
|
+
.duration_since(UNIX_EPOCH)
|
340
|
+
.expect("Time went backwards");
|
341
|
+
|
342
|
+
let ex: Exemplar = Exemplar {
|
343
|
+
label_name: unsafe { exemplar_name.as_str().unwrap().into() },
|
344
|
+
label_value: unsafe { exemplar_value.as_str().unwrap().into() },
|
345
|
+
value: value,
|
346
|
+
timestamp: since_the_epoch.as_nanos(),
|
347
|
+
};
|
348
|
+
|
349
|
+
if let Some(pos) = position {
|
350
|
+
let pos = pos.to_usize()?;
|
351
|
+
return rs_self
|
352
|
+
.inner_mut(|inner| {
|
353
|
+
inner.save_exemplar(pos, ex)?;
|
354
|
+
|
355
|
+
// TODO just return `value` here instead of loading it?
|
356
|
+
// This is how the C implementation did it, but I don't
|
357
|
+
// see what the extra load gains us.
|
358
|
+
let ex = inner.load_exemplar(pos);
|
359
|
+
|
360
|
+
Ok(ex.unwrap().value)
|
361
|
+
})
|
362
|
+
.map_err(|e| e.into());
|
363
|
+
}
|
364
|
+
|
365
|
+
|
366
|
+
rs_self.check_expand_exemplar(rb_self, key.len())?;
|
367
|
+
|
368
|
+
let value_offset: usize = rs_self.inner_mut(|inner| {
|
369
|
+
// SAFETY: We must not call any Ruby code for the lifetime of this borrow.
|
370
|
+
unsafe { inner.initialize_entry_exemplar(key.as_slice(), ex) }
|
371
|
+
})?;
|
372
|
+
|
373
|
+
// CAST: no-op on 64-bit, widening on 32-bit.
|
374
|
+
positions.aset(key, Integer::from_u64(value_offset as u64))?;
|
375
|
+
|
376
|
+
let ex = rs_self.load_exemplar(value_offset);
|
377
|
+
|
378
|
+
Ok(ex.unwrap().value)
|
379
|
+
}
|
380
|
+
|
323
381
|
/// Update the value of an existing entry, if present. Otherwise create a new entry
|
324
382
|
/// for the key.
|
325
383
|
pub fn upsert_entry(
|
@@ -469,6 +527,23 @@ impl MmapedFile {
|
|
469
527
|
Ok(())
|
470
528
|
}
|
471
529
|
|
530
|
+
/// Check that the mmap is large enough to contain the value to be added,
|
531
|
+
/// and expand it to fit if necessary.
|
532
|
+
fn check_expand_exemplar(&self, rb_self: Obj<Self>, key_len: usize) -> magnus::error::Result<()> {
|
533
|
+
// CAST: no-op on 32-bit, widening on 64-bit.
|
534
|
+
let used = self.inner(|inner| inner.load_used())? as usize;
|
535
|
+
let entry_len = RawEntry::calc_total_len_exemplar(key_len)?;
|
536
|
+
|
537
|
+
// We need the mmapped region to contain at least one byte beyond the
|
538
|
+
// written data to create a NUL- terminated C string. Validate that
|
539
|
+
// new length does not exactly match or exceed the length of the mmap.
|
540
|
+
while self.capacity() <= used.add_chk(entry_len)? {
|
541
|
+
self.expand_to_fit(rb_self, self.capacity().mul_chk(2)?)?;
|
542
|
+
}
|
543
|
+
|
544
|
+
Ok(())
|
545
|
+
}
|
546
|
+
|
472
547
|
/// Expand the underlying file until it is long enough to fit `target_cap`.
|
473
548
|
/// This will remove the existing mmap, expand the file, then update any
|
474
549
|
/// strings held by the `WeakMap` to point to the newly mmapped address.
|
@@ -555,6 +630,11 @@ impl MmapedFile {
|
|
555
630
|
.map_err(|e| e.into())
|
556
631
|
}
|
557
632
|
|
633
|
+
fn load_exemplar<'a, 'b>(&'a self, position: usize) -> magnus::error::Result<Exemplar> {
|
634
|
+
self.inner_mut(|inner| inner.load_exemplar(position))
|
635
|
+
.map_err(|e| e.into())
|
636
|
+
}
|
637
|
+
|
558
638
|
fn as_mut_ptr(&self) -> *mut c_char {
|
559
639
|
// UNWRAP: This is actually infallible, but we need to
|
560
640
|
// wrap it in a `Result` for use with `inner()`.
|
@@ -655,13 +735,14 @@ impl MmapedFile {
|
|
655
735
|
|
656
736
|
#[cfg(test)]
|
657
737
|
mod test {
|
738
|
+
use super::*;
|
739
|
+
use core::panic;
|
658
740
|
use magnus::error::Error;
|
659
741
|
use magnus::eval;
|
660
742
|
use magnus::Range;
|
661
743
|
use nix::unistd::{sysconf, SysconfVar};
|
662
744
|
use std::mem::size_of;
|
663
745
|
|
664
|
-
use super::*;
|
665
746
|
use crate::raw_entry::RawEntry;
|
666
747
|
use crate::testhelper::TestFile;
|
667
748
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
use std::mem::size_of;
|
2
2
|
|
3
3
|
use crate::error::MmapError;
|
4
|
+
use crate::exemplars::{Exemplar, EXEMPLAR_ENTRY_MAX_SIZE_BYTES};
|
4
5
|
use crate::util;
|
5
6
|
use crate::util::CheckedOps;
|
6
7
|
use crate::Result;
|
@@ -13,6 +14,42 @@ pub struct RawEntry<'a> {
|
|
13
14
|
}
|
14
15
|
|
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
|
+
|
16
53
|
/// Save an entry to the mmap, returning the value offset in the newly created entry.
|
17
54
|
pub fn save(bytes: &'a mut [u8], key: &[u8], value: f64) -> Result<usize> {
|
18
55
|
let total_len = Self::calc_total_len(key.len())?;
|
@@ -66,6 +103,23 @@ impl<'a> RawEntry<'a> {
|
|
66
103
|
Ok(Self { bytes, encoded_len })
|
67
104
|
}
|
68
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
|
+
|
69
123
|
/// Read the `f64` value of an entry from memory.
|
70
124
|
#[inline]
|
71
125
|
pub fn value(&self) -> f64 {
|
@@ -77,6 +131,15 @@ impl<'a> RawEntry<'a> {
|
|
77
131
|
util::read_f64(self.bytes, offset).unwrap()
|
78
132
|
}
|
79
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
|
+
|
80
143
|
/// The length of the entry key without padding.
|
81
144
|
#[inline]
|
82
145
|
pub fn encoded_len(&self) -> usize {
|
@@ -97,6 +160,12 @@ impl<'a> RawEntry<'a> {
|
|
97
160
|
Self::calc_total_len(self.encoded_len).unwrap()
|
98
161
|
}
|
99
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
|
+
|
100
169
|
/// Calculate the total length of an `MmapEntry`, including the string length,
|
101
170
|
/// string, padding, and value. Validates encoding_len is within expected bounds.
|
102
171
|
#[inline]
|
@@ -104,6 +173,11 @@ impl<'a> RawEntry<'a> {
|
|
104
173
|
Self::calc_value_offset(encoded_len)?.add_chk(size_of::<f64>())
|
105
174
|
}
|
106
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
|
+
|
107
181
|
/// Calculate the value offset of an `MmapEntry`, including the string length,
|
108
182
|
/// string, padding. Validates encoding_len is within expected bounds.
|
109
183
|
#[inline]
|
@@ -5,6 +5,7 @@ use std::io;
|
|
5
5
|
use std::mem::size_of;
|
6
6
|
|
7
7
|
use crate::error::MmapError;
|
8
|
+
use crate::exemplars::{Exemplar, EXEMPLAR_ENTRY_MAX_SIZE_BYTES};
|
8
9
|
use crate::Result;
|
9
10
|
|
10
11
|
/// Wrapper around `checked_add()` that converts failures
|
@@ -88,6 +89,24 @@ pub fn read_f64(buf: &[u8], offset: usize) -> Result<f64> {
|
|
88
89
|
))
|
89
90
|
}
|
90
91
|
|
92
|
+
pub fn read_exemplar(buf: &[u8], offset: usize) -> Result<Exemplar> {
|
93
|
+
if let Some(slice) = buf.get(offset..offset + EXEMPLAR_ENTRY_MAX_SIZE_BYTES) {
|
94
|
+
// UNWRAP: We can safely unwrap the conversion from slice to array as we
|
95
|
+
// can be sure the target array has same length as the source slice.
|
96
|
+
let out: &[u8; EXEMPLAR_ENTRY_MAX_SIZE_BYTES] = slice.try_into().expect("failed to convert slice to array");
|
97
|
+
|
98
|
+
let res: Vec<u8> = out.iter().cloned().filter(|&x| x != 0).collect();
|
99
|
+
|
100
|
+
let v: Exemplar = serde_json::from_slice(&res).expect("failed to convert string to Exemplar");
|
101
|
+
|
102
|
+
return Ok(v)
|
103
|
+
}
|
104
|
+
Err(MmapError::out_of_bounds(
|
105
|
+
offset + EXEMPLAR_ENTRY_MAX_SIZE_BYTES,
|
106
|
+
buf.len(),
|
107
|
+
))
|
108
|
+
}
|
109
|
+
|
91
110
|
#[cfg(test)]
|
92
111
|
mod test {
|
93
112
|
use super::*;
|
@@ -10,11 +10,11 @@ module Prometheus
|
|
10
10
|
:counter
|
11
11
|
end
|
12
12
|
|
13
|
-
def increment(labels = {}, by = 1)
|
13
|
+
def increment(labels = {}, by = 1, exemplar_name = '', exemplar_value = '')
|
14
14
|
raise ArgumentError, 'increment must be a non-negative number' if by < 0
|
15
15
|
|
16
16
|
label_set = label_set_for(labels)
|
17
|
-
synchronize { @values[label_set].increment(by) }
|
17
|
+
synchronize { @values[label_set].increment(by, exemplar_name, exemplar_value) }
|
18
18
|
end
|
19
19
|
|
20
20
|
private
|
@@ -29,7 +29,8 @@ module Prometheus
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def marshal_multiprocess(path = Prometheus::Client.configuration.multiprocess_files_dir, use_rust: true)
|
32
|
-
|
32
|
+
# NOTE(GiedriusS): need to ensure exemplar files go at the end because they add extra data.
|
33
|
+
file_list = Dir.glob(File.join(path, '*.db')).sort_by { |f| [f.include?('exemplar') ? 1 : 0, f] }
|
33
34
|
.map {|f| Helper::PlainFile.new(f) }
|
34
35
|
.map {|f| [f.filepath, f.multiprocess_mode.to_sym, f.type.to_sym, f.pid] }
|
35
36
|
|
@@ -60,9 +60,9 @@ module Prometheus
|
|
60
60
|
:histogram
|
61
61
|
end
|
62
62
|
|
63
|
-
def observe(labels, value)
|
63
|
+
def observe(labels, value, exemplar_name = '', exemplar_value = '')
|
64
64
|
label_set = label_set_for(labels)
|
65
|
-
synchronize { @values[label_set].observe(value) }
|
65
|
+
synchronize { @values[label_set].observe(value, exemplar_name, exemplar_value) }
|
66
66
|
end
|
67
67
|
|
68
68
|
private
|
@@ -31,11 +31,7 @@ module Prometheus
|
|
31
31
|
initialize_file if pid_changed?
|
32
32
|
|
33
33
|
@value += amount
|
34
|
-
|
35
|
-
if @file_prefix != 'gauge'
|
36
|
-
puts "#{@name} exemplar name = #{exemplar_name}, exemplar_value = #{exemplar_value}"
|
37
|
-
end
|
38
|
-
write_value(@key, @value)
|
34
|
+
write_value(@key, @value, exemplar_name, exemplar_value)
|
39
35
|
@value
|
40
36
|
end
|
41
37
|
end
|
@@ -120,12 +116,18 @@ module Prometheus
|
|
120
116
|
unless @file.nil?
|
121
117
|
@file.close
|
122
118
|
end
|
119
|
+
unless @exemplar_file.nil?
|
120
|
+
@exemplar_file.close
|
121
|
+
end
|
123
122
|
mmaped_file = Helper::MmapedFile.open_exclusive_file(@file_prefix)
|
123
|
+
exemplar_file = Helper::MmapedFile.open_exclusive_file('exemplar')
|
124
124
|
|
125
125
|
@@files[@file_prefix] = MmapedDict.new(mmaped_file)
|
126
|
+
@@files['exemplar'] = MmapedDict.new(exemplar_file)
|
126
127
|
end
|
127
128
|
|
128
129
|
@file = @@files[@file_prefix]
|
130
|
+
@exemplar_file = @@files['exemplar']
|
129
131
|
@key = rebuild_key
|
130
132
|
|
131
133
|
@value = read_value(@key)
|
@@ -139,8 +141,12 @@ module Prometheus
|
|
139
141
|
[@metric_name, @name, keys, values].to_json
|
140
142
|
end
|
141
143
|
|
142
|
-
def write_value(key, val)
|
144
|
+
def write_value(key, val, exemplar_name = '', exemplar_value = '')
|
143
145
|
@file.write_value(key, val)
|
146
|
+
# Exemplars are only defined on counters or histograms.
|
147
|
+
if @file_prefix == 'counter' or @file_prefix == 'histogram' and exemplar_name != '' and exemplar_value != ''
|
148
|
+
@exemplar_file.write_exemplar(key, val, exemplar_name, exemplar_value)
|
149
|
+
end
|
144
150
|
rescue StandardError => e
|
145
151
|
Prometheus::Client.logger.warn("writing value to #{@file.path} failed with #{e}")
|
146
152
|
Prometheus::Client.logger.debug(e.backtrace.join("\n"))
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vinted-prometheus-client-mmap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tobias Schmidt
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2024-03-
|
14
|
+
date: 2024-03-27 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rb_sys
|