vinted-prometheus-client-mmap 1.2.1 → 1.3.0
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/README.md +3 -0
- 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 +92 -40
- data/ext/fast_mmaped_file_rs/src/lib.rs +2 -0
- data/ext/fast_mmaped_file_rs/src/map.rs +53 -25
- data/ext/fast_mmaped_file_rs/src/mmap/inner.rs +67 -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 +20 -0
- data/lib/prometheus/client/configuration.rb +1 -0
- data/lib/prometheus/client/counter.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 +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9c2172a64911eb3a109b866611b4167e9dafacedf7bdb4ee45da71340d0314e
|
4
|
+
data.tar.gz: '018047827caaea20f96e7ca8b62a717c1c17b0aab14ad1a0bf7ea2735f8ee18a'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f09730605d1c7904ec72b3ac0afc0fd61a79da99e78f93da27551ec2340dbde87b47c72d3b59e934df72e5af92139f488364d1ea161bf2ab1052f118aed1e57
|
7
|
+
data.tar.gz: 6bf4a9f9aeac52d16f15f3d8b3cc44306b009487891b22d431cc2a96aa46a0bba70b9364845e9f382d35729172ce4171ce980c5907c3c4aac47501d3b9710be5
|
data/README.md
CHANGED
@@ -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.
|
@@ -1,5 +1,5 @@
|
|
1
1
|
use core::panic;
|
2
|
-
use magnus::Symbol;
|
2
|
+
use magnus::{eval, Symbol, Value};
|
3
3
|
use serde::Deserialize;
|
4
4
|
use serde_json::value::RawValue;
|
5
5
|
use smallvec::SmallVec;
|
@@ -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();
|
152
|
+
}
|
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
|
+
}
|
131
171
|
}
|
132
|
-
} else {
|
133
|
-
self.value += other.value;
|
134
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,7 +359,7 @@ 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);
|
318
365
|
}
|
@@ -335,7 +382,7 @@ impl FileEntry {
|
|
335
382
|
|
336
383
|
let buckets = vec![io::prometheus::client::Bucket {
|
337
384
|
cumulative_count: None,
|
338
|
-
cumulative_count_float:
|
385
|
+
cumulative_count_float: gr.0.meta.value,
|
339
386
|
upper_bound: Some(
|
340
387
|
le.expect(
|
341
388
|
&format!("got no LE for {}", gr.1.metric_name)
|
@@ -413,10 +460,10 @@ impl FileEntry {
|
|
413
460
|
if gr.1.metric_name.ends_with("_count") {
|
414
461
|
let samplecount = smry.sample_count.unwrap_or_default();
|
415
462
|
smry.sample_count =
|
416
|
-
Some((gr.0.meta.value as u64) + samplecount);
|
463
|
+
Some((gr.0.meta.value.unwrap() as u64) + samplecount);
|
417
464
|
} else if gr.1.metric_name.ends_with("_sum") {
|
418
465
|
let samplesum: f64 = smry.sample_sum.unwrap_or_default();
|
419
|
-
smry.sample_sum = Some(gr.0.meta.value + samplesum);
|
466
|
+
smry.sample_sum = Some(gr.0.meta.value.unwrap() + samplesum);
|
420
467
|
} else {
|
421
468
|
let mut found_quantile = false;
|
422
469
|
for qntl in &mut smry.quantile {
|
@@ -425,7 +472,7 @@ impl FileEntry {
|
|
425
472
|
}
|
426
473
|
|
427
474
|
let mut curq: f64 = qntl.quantile.unwrap_or_default();
|
428
|
-
curq += gr.0.meta.value;
|
475
|
+
curq += gr.0.meta.value.unwrap();
|
429
476
|
|
430
477
|
qntl.quantile = Some(curq);
|
431
478
|
found_quantile = true;
|
@@ -434,7 +481,7 @@ impl FileEntry {
|
|
434
481
|
if !found_quantile {
|
435
482
|
smry.quantile.push(io::prometheus::client::Quantile {
|
436
483
|
quantile: quantile,
|
437
|
-
value:
|
484
|
+
value: gr.0.meta.value,
|
438
485
|
});
|
439
486
|
}
|
440
487
|
}
|
@@ -455,7 +502,7 @@ impl FileEntry {
|
|
455
502
|
gr.1.metric_name.strip_suffix("_count").unwrap();
|
456
503
|
m.summary = Some(io::prometheus::client::Summary {
|
457
504
|
quantile: vec![],
|
458
|
-
sample_count: Some(gr.0.meta.value as u64),
|
505
|
+
sample_count: Some(gr.0.meta.value.unwrap() as u64),
|
459
506
|
sample_sum: None,
|
460
507
|
created_timestamp: None,
|
461
508
|
});
|
@@ -464,14 +511,14 @@ impl FileEntry {
|
|
464
511
|
gr.1.metric_name.strip_suffix("_sum").unwrap();
|
465
512
|
m.summary = Some(io::prometheus::client::Summary {
|
466
513
|
quantile: vec![],
|
467
|
-
sample_sum: Some(gr.0.meta.value),
|
514
|
+
sample_sum: Some(gr.0.meta.value.unwrap()),
|
468
515
|
sample_count: None,
|
469
516
|
created_timestamp: None,
|
470
517
|
});
|
471
518
|
} else {
|
472
519
|
let quantiles = vec![io::prometheus::client::Quantile {
|
473
520
|
quantile: quantile,
|
474
|
-
value:
|
521
|
+
value: gr.0.meta.value,
|
475
522
|
}];
|
476
523
|
m.summary = Some(io::prometheus::client::Summary {
|
477
524
|
quantile: quantiles,
|
@@ -487,6 +534,9 @@ impl FileEntry {
|
|
487
534
|
}
|
488
535
|
}
|
489
536
|
}
|
537
|
+
"exemplar" => {
|
538
|
+
// Exemplars are handled later on.
|
539
|
+
}
|
490
540
|
mtype => {
|
491
541
|
panic!("unhandled metric type {}", mtype)
|
492
542
|
}
|
@@ -532,6 +582,8 @@ impl FileEntry {
|
|
532
582
|
unsafe { Ok(str::from_utf8_unchecked(buffer.get_ref()).to_string()) }
|
533
583
|
}
|
534
584
|
|
585
|
+
|
586
|
+
|
535
587
|
/// Convert the sorted entries into a String in Prometheus metrics format.
|
536
588
|
pub fn entries_to_string(entries: Vec<FileEntry>) -> Result<String> {
|
537
589
|
// We guesstimate that lines are ~100 bytes long, preallocate the string to
|
@@ -568,7 +620,7 @@ impl FileEntry {
|
|
568
620
|
|
569
621
|
entry.append_entry(metrics_data, &mut out)?;
|
570
622
|
|
571
|
-
writeln!(&mut out, " {}", entry.meta.value)
|
623
|
+
writeln!(&mut out, " {}", entry.meta.value.unwrap())
|
572
624
|
.map_err(|e| MmapError::Other(format!("Failed to append to output: {e}")))?;
|
573
625
|
|
574
626
|
processed_count += 1;
|
@@ -1182,7 +1234,7 @@ mod test {
|
|
1182
1234
|
entry_a.meta.merge(&entry_b.meta);
|
1183
1235
|
|
1184
1236
|
assert_eq!(
|
1185
|
-
case.expected_value, entry_a.meta.value,
|
1237
|
+
case.expected_value, entry_a.meta.value.unwrap(),
|
1186
1238
|
"test case: {name} - value"
|
1187
1239
|
);
|
1188
1240
|
}
|
@@ -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
|
}
|
@@ -1,6 +1,7 @@
|
|
1
1
|
use hashbrown::hash_map::RawEntryMut;
|
2
2
|
use hashbrown::HashMap;
|
3
|
-
use magnus::
|
3
|
+
use magnus::class::file;
|
4
|
+
use magnus::{eval, exception::*, Error, RArray, Value};
|
4
5
|
use std::hash::{BuildHasher, Hash, Hasher};
|
5
6
|
use std::mem::size_of;
|
6
7
|
|
@@ -142,22 +143,39 @@ impl EntryMap {
|
|
142
143
|
let mut pos = HEADER_SIZE;
|
143
144
|
|
144
145
|
while pos + size_of::<u32>() < used {
|
145
|
-
let raw_entry
|
146
|
-
|
147
|
-
if
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
146
|
+
let raw_entry: RawEntry;
|
147
|
+
|
148
|
+
if file_info.type_.to_string() == "exemplar" {
|
149
|
+
raw_entry = RawEntry::from_slice_exemplar(&source[pos..used])?;
|
150
|
+
|
151
|
+
if pos + raw_entry.total_len_exemplar() > used {
|
152
|
+
return Err(MmapError::PromParsing(format!(
|
153
|
+
"source file {} corrupted, used {used} < stored data length {}",
|
154
|
+
file_info.path.display(),
|
155
|
+
pos + raw_entry.total_len()
|
156
|
+
)));
|
157
|
+
}
|
154
158
|
|
159
|
+
pos += raw_entry.total_len_exemplar();
|
160
|
+
|
161
|
+
} else {
|
162
|
+
raw_entry = RawEntry::from_slice(&source[pos..used])?;
|
163
|
+
|
164
|
+
if pos + raw_entry.total_len() > used {
|
165
|
+
return Err(MmapError::PromParsing(format!(
|
166
|
+
"source file {} corrupted, used {used} < stored data length {}",
|
167
|
+
file_info.path.display(),
|
168
|
+
pos + raw_entry.total_len()
|
169
|
+
)));
|
170
|
+
}
|
171
|
+
|
172
|
+
pos += raw_entry.total_len();
|
173
|
+
}
|
174
|
+
|
155
175
|
let meta = EntryMetadata::new(&raw_entry, &file_info)?;
|
156
176
|
let data = BorrowedData::new(&raw_entry, &file_info, meta.is_pid_significant())?;
|
157
177
|
|
158
178
|
self.merge_or_store(data, meta)?;
|
159
|
-
|
160
|
-
pos += raw_entry.total_len();
|
161
179
|
}
|
162
180
|
|
163
181
|
Ok(())
|
@@ -198,7 +216,8 @@ mod test {
|
|
198
216
|
meta: EntryMetadata {
|
199
217
|
multiprocess_mode: Symbol::new("max"),
|
200
218
|
type_: Symbol::new("gauge"),
|
201
|
-
value: 1.0,
|
219
|
+
value: Some(1.0),
|
220
|
+
ex: None,
|
202
221
|
},
|
203
222
|
},
|
204
223
|
FileEntry {
|
@@ -209,7 +228,8 @@ mod test {
|
|
209
228
|
meta: EntryMetadata {
|
210
229
|
multiprocess_mode: Symbol::new("max"),
|
211
230
|
type_: Symbol::new("gauge"),
|
212
|
-
value: 1.0,
|
231
|
+
value: Some(1.0),
|
232
|
+
ex: None,
|
213
233
|
},
|
214
234
|
},
|
215
235
|
FileEntry {
|
@@ -220,7 +240,8 @@ mod test {
|
|
220
240
|
meta: EntryMetadata {
|
221
241
|
multiprocess_mode: Symbol::new("max"),
|
222
242
|
type_: Symbol::new("gauge"),
|
223
|
-
value: 1.0,
|
243
|
+
value: Some(1.0),
|
244
|
+
ex: None,
|
224
245
|
},
|
225
246
|
},
|
226
247
|
FileEntry {
|
@@ -231,7 +252,8 @@ mod test {
|
|
231
252
|
meta: EntryMetadata {
|
232
253
|
multiprocess_mode: Symbol::new("max"),
|
233
254
|
type_: Symbol::new("gauge"),
|
234
|
-
value: 1.0,
|
255
|
+
value: Some(1.0),
|
256
|
+
ex: None,
|
235
257
|
},
|
236
258
|
},
|
237
259
|
FileEntry {
|
@@ -242,7 +264,8 @@ mod test {
|
|
242
264
|
meta: EntryMetadata {
|
243
265
|
multiprocess_mode: Symbol::new("all"),
|
244
266
|
type_: Symbol::new("gauge"),
|
245
|
-
value: 1.0,
|
267
|
+
value: Some(1.0),
|
268
|
+
ex: None,
|
246
269
|
},
|
247
270
|
},
|
248
271
|
FileEntry {
|
@@ -253,7 +276,8 @@ mod test {
|
|
253
276
|
meta: EntryMetadata {
|
254
277
|
multiprocess_mode: Symbol::new("all"),
|
255
278
|
type_: Symbol::new("gauge"),
|
256
|
-
value: 1.0,
|
279
|
+
value: Some(1.0),
|
280
|
+
ex: None,
|
257
281
|
},
|
258
282
|
},
|
259
283
|
];
|
@@ -294,7 +318,8 @@ mod test {
|
|
294
318
|
meta: EntryMetadata {
|
295
319
|
multiprocess_mode: Symbol::new("all"),
|
296
320
|
type_: Symbol::new("gauge"),
|
297
|
-
value: 1.0,
|
321
|
+
value: Some(1.0),
|
322
|
+
ex: None,
|
298
323
|
},
|
299
324
|
};
|
300
325
|
|
@@ -306,7 +331,8 @@ mod test {
|
|
306
331
|
meta: EntryMetadata {
|
307
332
|
multiprocess_mode: Symbol::new("all"),
|
308
333
|
type_: Symbol::new("gauge"),
|
309
|
-
value: 5.0,
|
334
|
+
value: Some(5.0),
|
335
|
+
ex: None,
|
310
336
|
},
|
311
337
|
};
|
312
338
|
|
@@ -318,7 +344,8 @@ mod test {
|
|
318
344
|
meta: EntryMetadata {
|
319
345
|
multiprocess_mode: Symbol::new("all"),
|
320
346
|
type_: Symbol::new("gauge"),
|
321
|
-
value: 100.0,
|
347
|
+
value: Some(100.0),
|
348
|
+
ex: None,
|
322
349
|
},
|
323
350
|
};
|
324
351
|
|
@@ -330,7 +357,8 @@ mod test {
|
|
330
357
|
meta: EntryMetadata {
|
331
358
|
multiprocess_mode: Symbol::new("all"),
|
332
359
|
type_: Symbol::new("gauge"),
|
333
|
-
value:
|
360
|
+
value: Some(100.0),
|
361
|
+
ex: None,
|
334
362
|
},
|
335
363
|
};
|
336
364
|
|
@@ -345,7 +373,7 @@ mod test {
|
|
345
373
|
|
346
374
|
assert_eq!(
|
347
375
|
5.0,
|
348
|
-
map.0.get(&starting_entry.data).unwrap().value,
|
376
|
+
map.0.get(&starting_entry.data).unwrap().value.unwrap(),
|
349
377
|
"value updated"
|
350
378
|
);
|
351
379
|
assert_eq!(1, map.0.len(), "no entry added");
|
@@ -359,7 +387,7 @@ mod test {
|
|
359
387
|
|
360
388
|
assert_eq!(
|
361
389
|
5.0,
|
362
|
-
map.0.get(&starting_entry.data).unwrap().value,
|
390
|
+
map.0.get(&starting_entry.data).unwrap().value.unwrap(),
|
363
391
|
"value unchanged"
|
364
392
|
);
|
365
393
|
|
@@ -371,7 +399,7 @@ mod test {
|
|
371
399
|
|
372
400
|
assert_eq!(
|
373
401
|
5.0,
|
374
|
-
map.0.get(&starting_entry.data).unwrap().value,
|
402
|
+
map.0.get(&starting_entry.data).unwrap().value.unwrap(),
|
375
403
|
"value unchanged"
|
376
404
|
);
|
377
405
|
assert_eq!(3, map.0.len(), "entry added");
|
@@ -9,7 +9,9 @@ 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;
|
@@ -139,6 +141,59 @@ impl InnerMmap {
|
|
139
141
|
Ok(position)
|
140
142
|
}
|
141
143
|
|
144
|
+
pub unsafe fn initialize_entry_exemplar(&mut self, key: &[u8], ex: Exemplar) -> Result<usize> {
|
145
|
+
// CAST: no-op on 32-bit, widening on 64-bit.
|
146
|
+
let current_used = self.load_used()? as usize;
|
147
|
+
let entry_length = RawEntry::calc_total_len_exemplar(key.len())?;
|
148
|
+
|
149
|
+
let new_used = current_used.add_chk(entry_length)?;
|
150
|
+
|
151
|
+
// Increasing capacity requires expanding the file and re-mmapping it, we can't
|
152
|
+
// perform this from `InnerMmap`.
|
153
|
+
if self.capacity() < new_used {
|
154
|
+
return Err(MmapError::Other(format!(
|
155
|
+
"mmap capacity {} less than {}",
|
156
|
+
self.capacity(),
|
157
|
+
new_used
|
158
|
+
)));
|
159
|
+
}
|
160
|
+
|
161
|
+
let bytes = self.map.as_mut();
|
162
|
+
let value_offset = RawEntry::save_exemplar(&mut bytes[current_used..new_used], key, ex)?;
|
163
|
+
|
164
|
+
// Won't overflow as value_offset is less than new_used.
|
165
|
+
let position = current_used + value_offset;
|
166
|
+
let new_used32 = util::cast_chk::<_, u32>(new_used, "used")?;
|
167
|
+
|
168
|
+
self.save_used(new_used32)?;
|
169
|
+
Ok(position)
|
170
|
+
}
|
171
|
+
|
172
|
+
pub fn save_exemplar(&mut self, offset: usize, exemplar: Exemplar) -> Result<()> {
|
173
|
+
if self.len.add_chk(size_of::<Exemplar>())? <= offset {
|
174
|
+
return Err(MmapError::out_of_bounds(
|
175
|
+
offset + size_of::<f64>(),
|
176
|
+
self.len,
|
177
|
+
));
|
178
|
+
}
|
179
|
+
|
180
|
+
if offset < HEADER_SIZE {
|
181
|
+
return Err(MmapError::Other(format!(
|
182
|
+
"writing to offset {offset} would overwrite file header"
|
183
|
+
)));
|
184
|
+
}
|
185
|
+
|
186
|
+
let val = serde_json::to_string(&exemplar).unwrap();
|
187
|
+
|
188
|
+
let value_bytes = val.as_bytes();
|
189
|
+
let value_range = self.item_range(offset, value_bytes.len())?;
|
190
|
+
|
191
|
+
let bytes = self.map.as_mut();
|
192
|
+
bytes[value_range].copy_from_slice(&value_bytes);
|
193
|
+
|
194
|
+
Ok(())
|
195
|
+
}
|
196
|
+
|
142
197
|
/// Save a metrics value to an existing entry in the mmap.
|
143
198
|
pub fn save_value(&mut self, offset: usize, value: f64) -> Result<()> {
|
144
199
|
if self.len.add_chk(size_of::<f64>())? <= offset {
|
@@ -174,6 +229,17 @@ impl InnerMmap {
|
|
174
229
|
read_f64(self.map.as_ref(), offset)
|
175
230
|
}
|
176
231
|
|
232
|
+
pub fn load_exemplar(&mut self, offset: usize) -> Result<Exemplar> {
|
233
|
+
if self.len.add_chk(EXEMPLAR_ENTRY_MAX_SIZE_BYTES)? <= offset {
|
234
|
+
return Err(MmapError::out_of_bounds(
|
235
|
+
offset + EXEMPLAR_ENTRY_MAX_SIZE_BYTES,
|
236
|
+
self.len,
|
237
|
+
));
|
238
|
+
}
|
239
|
+
|
240
|
+
read_exemplar(self.map.as_mut(), offset)
|
241
|
+
}
|
242
|
+
|
177
243
|
/// The length of data written to the file.
|
178
244
|
/// With a new file this is only set when Ruby calls `slice` on
|
179
245
|
/// `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]
|
@@ -1,10 +1,12 @@
|
|
1
1
|
use nix::errno::Errno;
|
2
2
|
use nix::libc::c_long;
|
3
|
+
use std::borrow::Cow;
|
3
4
|
use std::fmt::Display;
|
4
5
|
use std::io;
|
5
6
|
use std::mem::size_of;
|
6
7
|
|
7
8
|
use crate::error::MmapError;
|
9
|
+
use crate::exemplars::{Exemplar, EXEMPLAR_ENTRY_MAX_SIZE_BYTES};
|
8
10
|
use crate::Result;
|
9
11
|
|
10
12
|
/// Wrapper around `checked_add()` that converts failures
|
@@ -88,6 +90,24 @@ pub fn read_f64(buf: &[u8], offset: usize) -> Result<f64> {
|
|
88
90
|
))
|
89
91
|
}
|
90
92
|
|
93
|
+
pub fn read_exemplar(buf: &[u8], offset: usize) -> Result<Exemplar> {
|
94
|
+
if let Some(slice) = buf.get(offset..offset + EXEMPLAR_ENTRY_MAX_SIZE_BYTES) {
|
95
|
+
// UNWRAP: We can safely unwrap the conversion from slice to array as we
|
96
|
+
// can be sure the target array has same length as the source slice.
|
97
|
+
let out: &[u8; EXEMPLAR_ENTRY_MAX_SIZE_BYTES] = slice.try_into().expect("failed to convert slice to array");
|
98
|
+
|
99
|
+
let res: Vec<u8> = out.iter().cloned().filter(|&x| x != 0).collect();
|
100
|
+
|
101
|
+
let v: Exemplar = serde_json::from_slice(&res).expect("failed to convert string to Exemplar");
|
102
|
+
|
103
|
+
return Ok(v)
|
104
|
+
}
|
105
|
+
Err(MmapError::out_of_bounds(
|
106
|
+
offset + EXEMPLAR_ENTRY_MAX_SIZE_BYTES,
|
107
|
+
buf.len(),
|
108
|
+
))
|
109
|
+
}
|
110
|
+
|
91
111
|
#[cfg(test)]
|
92
112
|
mod test {
|
93
113
|
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
|
@@ -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"))
|