prometheus-client-mmap 0.23.1-x86_64-darwin → 0.24.4-x86_64-darwin
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +11 -0
- data/ext/fast_mmaped_file_rs/src/error.rs +2 -2
- data/ext/fast_mmaped_file_rs/src/file_entry.rs +5 -5
- data/ext/fast_mmaped_file_rs/src/file_info.rs +5 -5
- data/ext/fast_mmaped_file_rs/src/map.rs +12 -12
- data/ext/fast_mmaped_file_rs/src/mmap/inner.rs +6 -0
- data/ext/fast_mmaped_file_rs/src/mmap.rs +202 -37
- data/ext/fast_mmaped_file_rs/src/parser.rs +169 -67
- data/ext/fast_mmaped_file_rs/src/raw_entry.rs +1 -1
- data/ext/fast_mmaped_file_rs/src/testhelper.rs +1 -1
- data/lib/2.7/fast_mmaped_file_rs.bundle +0 -0
- data/lib/3.0/fast_mmaped_file_rs.bundle +0 -0
- data/lib/3.1/fast_mmaped_file.bundle +0 -0
- data/lib/3.1/fast_mmaped_file_rs.bundle +0 -0
- data/lib/3.2/fast_mmaped_file.bundle +0 -0
- data/lib/3.2/fast_mmaped_file_rs.bundle +0 -0
- data/lib/prometheus/client/configuration.rb +2 -1
- data/lib/prometheus/client/formats/text.rb +2 -24
- data/lib/prometheus/client/helper/loader.rb +40 -0
- data/lib/prometheus/client/helper/mmaped_file.rb +12 -1
- data/lib/prometheus/client/rack/exporter.rb +3 -1
- data/lib/prometheus/client/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11eecc761b0bd7cbcc8137e7097cb81595b0bbbe5b01058b1a601f01fd0405e9
|
4
|
+
data.tar.gz: c9d175042f2a64f0735d5a4b973506c56690a555e20bb115062c207e4389ba01
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f5428ee9d38050a212df5d313c73ac4734ee6d0d174608ee5b72cd1d8ba586db82b41d30f356373b33a350ce9fa291d515b4e965dde309b83ca449dcd0d0b6b
|
7
|
+
data.tar.gz: e67748cf4f5d979b25068bf1d86fc828419301fc09425c40f54cb24aa0247dcf69aa9a295daca22b8458cd04597182603a62ae1b02e9195a88350c33c2efc885
|
data/README.md
CHANGED
@@ -200,6 +200,17 @@ Set `prometheus_multiproc_dir` environment variable to the path where you want m
|
|
200
200
|
prometheus_multiproc_dir=/tmp
|
201
201
|
```
|
202
202
|
|
203
|
+
### Multiprocess metrics via Rust extension
|
204
|
+
|
205
|
+
If the environment variable `prometheus_rust_multiprocess_metrics=true` is set or if the `rust_multiprocess_metrics`
|
206
|
+
configuration setting is `true` and the `fast_mmaped_file_rs` extension is available, it will be used to generate
|
207
|
+
multiprocess metrics. This should be significantly faster than the C extension.
|
208
|
+
|
209
|
+
### Read and write metrics via Rust extension
|
210
|
+
|
211
|
+
If the environment variable `prometheus_rust_mmaped_file=true` is set then if the `fast_mmaped_file_rs`
|
212
|
+
extension is available it will be used to read and write metrics from the mmapped file.
|
213
|
+
|
203
214
|
## Pitfalls
|
204
215
|
|
205
216
|
### PID cardinality
|
@@ -9,7 +9,7 @@ use crate::util;
|
|
9
9
|
use crate::PROM_EPARSING_ERROR;
|
10
10
|
|
11
11
|
/// A lightweight representation of Ruby ExceptionClasses.
|
12
|
-
#[derive(PartialEq, Clone, Copy, Debug)]
|
12
|
+
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
13
13
|
pub enum RubyError {
|
14
14
|
Arg,
|
15
15
|
Encoding,
|
@@ -45,7 +45,7 @@ impl From<RubyError> for magnus::ExceptionClass {
|
|
45
45
|
/// Errors returned internally within the crate. Methods called directly by Ruby return
|
46
46
|
/// `magnus::error::Error` as do functions that interact heavily with Ruby. This can be
|
47
47
|
/// converted into a `magnus::error::Error` at the boundary between Rust and Ruby.
|
48
|
-
#[derive(PartialEq, Error, Debug)]
|
48
|
+
#[derive(PartialEq, Eq, Error, Debug)]
|
49
49
|
pub enum MmapError {
|
50
50
|
/// A read or write was made while another thread had mutable access to the mmap.
|
51
51
|
#[error("read/write operation attempted while mmap was being written to")]
|
@@ -1,4 +1,4 @@
|
|
1
|
-
use magnus::
|
1
|
+
use magnus::Symbol;
|
2
2
|
use std::fmt::Write;
|
3
3
|
use std::str;
|
4
4
|
|
@@ -80,7 +80,7 @@ impl<'a> BorrowedData<'a> {
|
|
80
80
|
#[derive(Clone, Debug)]
|
81
81
|
pub struct EntryMetadata {
|
82
82
|
pub multiprocess_mode: Symbol,
|
83
|
-
pub type_:
|
83
|
+
pub type_: Symbol,
|
84
84
|
pub value: f64,
|
85
85
|
}
|
86
86
|
|
@@ -176,7 +176,7 @@ impl FileEntry {
|
|
176
176
|
out.push_str(family_name);
|
177
177
|
out.push(' ');
|
178
178
|
|
179
|
-
out.push_str(self.meta.type_.name().expect("name was invalid UTF-8"));
|
179
|
+
out.push_str(&self.meta.type_.name().expect("name was invalid UTF-8"));
|
180
180
|
out.push('\n');
|
181
181
|
}
|
182
182
|
|
@@ -438,7 +438,7 @@ mod test {
|
|
438
438
|
path,
|
439
439
|
len: case.json.len(),
|
440
440
|
multiprocess_mode: Symbol::new(case.multiprocess_mode),
|
441
|
-
type_:
|
441
|
+
type_: Symbol::new("gauge"),
|
442
442
|
pid: pid.to_string(),
|
443
443
|
};
|
444
444
|
file_infos.push(info);
|
@@ -544,7 +544,7 @@ mod test {
|
|
544
544
|
path,
|
545
545
|
len: json.len(),
|
546
546
|
multiprocess_mode: Symbol::new(case.multiprocess_mode),
|
547
|
-
type_:
|
547
|
+
type_: Symbol::new(case.metric_type),
|
548
548
|
pid: "worker-1".to_string(),
|
549
549
|
};
|
550
550
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
use magnus::exception::*;
|
2
|
-
use magnus::{Error, RString,
|
2
|
+
use magnus::{Error, RString, Symbol, Value};
|
3
3
|
use std::ffi::OsString;
|
4
4
|
use std::fs::File;
|
5
5
|
use std::io::{self, Read, Seek};
|
@@ -18,7 +18,7 @@ pub struct FileInfo {
|
|
18
18
|
pub path: PathBuf,
|
19
19
|
pub len: usize,
|
20
20
|
pub multiprocess_mode: Symbol,
|
21
|
-
pub type_:
|
21
|
+
pub type_: Symbol,
|
22
22
|
pub pid: String,
|
23
23
|
}
|
24
24
|
|
@@ -59,7 +59,7 @@ impl FileInfo {
|
|
59
59
|
let multiprocess_mode = Symbol::from_value(params[1])
|
60
60
|
.ok_or_else(|| err!(arg_error(), "expected multiprocess_mode to be a symbol"))?;
|
61
61
|
|
62
|
-
let type_ =
|
62
|
+
let type_ = Symbol::from_value(params[2])
|
63
63
|
.ok_or_else(|| err!(arg_error(), "expected file type to be a symbol"))?;
|
64
64
|
|
65
65
|
let pid = RString::from_value(params[3])
|
@@ -103,7 +103,7 @@ impl FileInfo {
|
|
103
103
|
|
104
104
|
#[cfg(test)]
|
105
105
|
mod test {
|
106
|
-
use magnus::{eval, RArray,
|
106
|
+
use magnus::{eval, RArray, Symbol};
|
107
107
|
use rand::{thread_rng, Rng};
|
108
108
|
use sha2::{Digest, Sha256};
|
109
109
|
|
@@ -168,7 +168,7 @@ mod test {
|
|
168
168
|
path,
|
169
169
|
len: buf.len(),
|
170
170
|
multiprocess_mode: Symbol::new("puma"),
|
171
|
-
type_:
|
171
|
+
type_: Symbol::new("max"),
|
172
172
|
pid: "worker-0_0".to_string(),
|
173
173
|
};
|
174
174
|
|
@@ -166,7 +166,7 @@ impl EntryMap {
|
|
166
166
|
|
167
167
|
#[cfg(test)]
|
168
168
|
mod test {
|
169
|
-
use magnus::
|
169
|
+
use magnus::Symbol;
|
170
170
|
use std::mem;
|
171
171
|
|
172
172
|
use super::*;
|
@@ -197,7 +197,7 @@ mod test {
|
|
197
197
|
},
|
198
198
|
meta: EntryMetadata {
|
199
199
|
multiprocess_mode: Symbol::new("max"),
|
200
|
-
type_:
|
200
|
+
type_: Symbol::new("gauge"),
|
201
201
|
value: 1.0,
|
202
202
|
},
|
203
203
|
},
|
@@ -208,7 +208,7 @@ mod test {
|
|
208
208
|
},
|
209
209
|
meta: EntryMetadata {
|
210
210
|
multiprocess_mode: Symbol::new("max"),
|
211
|
-
type_:
|
211
|
+
type_: Symbol::new("gauge"),
|
212
212
|
value: 1.0,
|
213
213
|
},
|
214
214
|
},
|
@@ -219,7 +219,7 @@ mod test {
|
|
219
219
|
},
|
220
220
|
meta: EntryMetadata {
|
221
221
|
multiprocess_mode: Symbol::new("max"),
|
222
|
-
type_:
|
222
|
+
type_: Symbol::new("gauge"),
|
223
223
|
value: 1.0,
|
224
224
|
},
|
225
225
|
},
|
@@ -230,7 +230,7 @@ mod test {
|
|
230
230
|
},
|
231
231
|
meta: EntryMetadata {
|
232
232
|
multiprocess_mode: Symbol::new("max"),
|
233
|
-
type_:
|
233
|
+
type_: Symbol::new("gauge"),
|
234
234
|
value: 1.0,
|
235
235
|
},
|
236
236
|
},
|
@@ -241,7 +241,7 @@ mod test {
|
|
241
241
|
},
|
242
242
|
meta: EntryMetadata {
|
243
243
|
multiprocess_mode: Symbol::new("all"),
|
244
|
-
type_:
|
244
|
+
type_: Symbol::new("gauge"),
|
245
245
|
value: 1.0,
|
246
246
|
},
|
247
247
|
},
|
@@ -252,7 +252,7 @@ mod test {
|
|
252
252
|
},
|
253
253
|
meta: EntryMetadata {
|
254
254
|
multiprocess_mode: Symbol::new("all"),
|
255
|
-
type_:
|
255
|
+
type_: Symbol::new("gauge"),
|
256
256
|
value: 1.0,
|
257
257
|
},
|
258
258
|
},
|
@@ -293,7 +293,7 @@ mod test {
|
|
293
293
|
},
|
294
294
|
meta: EntryMetadata {
|
295
295
|
multiprocess_mode: Symbol::new("all"),
|
296
|
-
type_:
|
296
|
+
type_: Symbol::new("gauge"),
|
297
297
|
value: 1.0,
|
298
298
|
},
|
299
299
|
};
|
@@ -305,7 +305,7 @@ mod test {
|
|
305
305
|
},
|
306
306
|
meta: EntryMetadata {
|
307
307
|
multiprocess_mode: Symbol::new("all"),
|
308
|
-
type_:
|
308
|
+
type_: Symbol::new("gauge"),
|
309
309
|
value: 5.0,
|
310
310
|
},
|
311
311
|
};
|
@@ -317,7 +317,7 @@ mod test {
|
|
317
317
|
},
|
318
318
|
meta: EntryMetadata {
|
319
319
|
multiprocess_mode: Symbol::new("all"),
|
320
|
-
type_:
|
320
|
+
type_: Symbol::new("gauge"),
|
321
321
|
value: 100.0,
|
322
322
|
},
|
323
323
|
};
|
@@ -329,7 +329,7 @@ mod test {
|
|
329
329
|
},
|
330
330
|
meta: EntryMetadata {
|
331
331
|
multiprocess_mode: Symbol::new("all"),
|
332
|
-
type_:
|
332
|
+
type_: Symbol::new("gauge"),
|
333
333
|
value: 1.0,
|
334
334
|
},
|
335
335
|
};
|
@@ -460,7 +460,7 @@ mod test {
|
|
460
460
|
path,
|
461
461
|
len: case.json.len(),
|
462
462
|
multiprocess_mode: Symbol::new("max"),
|
463
|
-
type_:
|
463
|
+
type_: Symbol::new("gauge"),
|
464
464
|
pid: "worker-1".to_string(),
|
465
465
|
};
|
466
466
|
|
@@ -204,6 +204,12 @@ impl InnerMmap {
|
|
204
204
|
self.map.as_ptr()
|
205
205
|
}
|
206
206
|
|
207
|
+
/// Returns a mutable raw pointer to the mmap.
|
208
|
+
/// For use in updating RString internals which requires a mutable pointer.
|
209
|
+
pub fn as_mut_ptr(&self) -> *mut u8 {
|
210
|
+
self.map.as_ptr().cast_mut()
|
211
|
+
}
|
212
|
+
|
207
213
|
/// Perform an msync(2) on the mmap, flushing all changes written
|
208
214
|
/// to disk. The sync may optionally be performed asynchronously.
|
209
215
|
pub fn flush(&mut self, f_async: bool) -> Result<()> {
|
@@ -3,8 +3,8 @@ use magnus::prelude::*;
|
|
3
3
|
use magnus::rb_sys::{AsRawValue, FromRawValue};
|
4
4
|
use magnus::typed_data::Obj;
|
5
5
|
use magnus::value::Fixnum;
|
6
|
-
use magnus::{
|
7
|
-
use nix::libc::c_long;
|
6
|
+
use magnus::{eval, scan_args, Error, Integer, RArray, RClass, RHash, RString, Value};
|
7
|
+
use nix::libc::{c_char, c_long, c_ulong};
|
8
8
|
use rb_sys::rb_str_new_static;
|
9
9
|
use std::fs::File;
|
10
10
|
use std::io::{prelude::*, SeekFrom};
|
@@ -25,6 +25,11 @@ use inner::InnerMmap;
|
|
25
25
|
|
26
26
|
mod inner;
|
27
27
|
|
28
|
+
/// The Ruby `STR_NOEMBED` flag, aka `FL_USER1`.
|
29
|
+
const STR_NOEMBED: c_ulong = 1 << (13);
|
30
|
+
/// The Ruby `STR_SHARED` flag, aka `FL_USER2`.
|
31
|
+
const STR_SHARED: c_ulong = 1 << (14);
|
32
|
+
|
28
33
|
/// A Rust struct wrapped in a Ruby object, providing access to a memory-mapped
|
29
34
|
/// file used to store, update, and read out Prometheus metrics.
|
30
35
|
///
|
@@ -151,13 +156,28 @@ impl MmapedFile {
|
|
151
156
|
///
|
152
157
|
/// return a substring of <em>lenght</em> characters from <em>start</em>
|
153
158
|
pub fn slice(rb_self: Obj<Self>, args: &[Value]) -> magnus::error::Result<RString> {
|
154
|
-
// The C
|
159
|
+
// The C implementation would trigger a GC cycle via `rb_gc_force_recycle`
|
155
160
|
// if the `MM_PROTECT` flag is set, but in practice this is never used.
|
156
161
|
// We omit this logic, particularly because `rb_gc_force_recycle` is a
|
157
162
|
// no-op as of Ruby 3.1.
|
163
|
+
let rs_self = &*rb_self;
|
164
|
+
|
165
|
+
let str = rs_self.str(rb_self)?;
|
166
|
+
rs_self._slice(rb_self, str, args)
|
167
|
+
}
|
158
168
|
|
159
|
-
|
160
|
-
|
169
|
+
fn _slice(
|
170
|
+
&self,
|
171
|
+
rb_self: Obj<Self>,
|
172
|
+
str: RString,
|
173
|
+
args: &[Value],
|
174
|
+
) -> magnus::error::Result<RString> {
|
175
|
+
let substr: RString = str.funcall("[]", args)?;
|
176
|
+
|
177
|
+
// Track shared child strings which use the same backing storage.
|
178
|
+
if Self::rb_string_is_shared(substr) {
|
179
|
+
(*rb_self).track_rstring(rb_self, substr)?;
|
180
|
+
}
|
161
181
|
|
162
182
|
// The C implementation does this, perhaps to validate that the len we
|
163
183
|
// provided is actually being used.
|
@@ -166,7 +186,7 @@ impl MmapedFile {
|
|
166
186
|
Ok(())
|
167
187
|
})?;
|
168
188
|
|
169
|
-
|
189
|
+
Ok(substr)
|
170
190
|
}
|
171
191
|
|
172
192
|
/// Document-method: msync
|
@@ -223,7 +243,8 @@ impl MmapedFile {
|
|
223
243
|
})?;
|
224
244
|
|
225
245
|
// Update each String object to be zero-length.
|
226
|
-
rs_self.
|
246
|
+
let cap = util::cast_chk::<_, c_long>(rs_self.capacity(), "capacity")?;
|
247
|
+
rs_self.update_weak_map(rb_self, rs_self.as_mut_ptr(), cap)?;
|
227
248
|
|
228
249
|
// Remove the `InnerMmap` from the `RwLock`. This will drop
|
229
250
|
// end of this function, unmapping and closing the file.
|
@@ -341,16 +362,19 @@ impl MmapedFile {
|
|
341
362
|
Ok(unsafe { rb_str_new_static(ptr as _, len as _) })
|
342
363
|
})?;
|
343
364
|
|
365
|
+
// SAFETY: We know that rb_str_new_static returns a VALUE.
|
344
366
|
let val = unsafe { Value::from_raw(val_id) };
|
345
367
|
|
346
368
|
// UNWRAP: We created this value as a string above.
|
347
369
|
let str = RString::from_value(val).unwrap();
|
348
370
|
|
349
|
-
|
371
|
+
// Freeze the root string so it can't be mutated out from under any
|
372
|
+
// substrings created. This object is never exposed to callers.
|
373
|
+
str.freeze();
|
350
374
|
|
351
|
-
//
|
352
|
-
|
353
|
-
|
375
|
+
// Track the RString in our `WeakMap` so we can update its address if
|
376
|
+
// we re-mmap the backing file.
|
377
|
+
(*rb_self).track_rstring(rb_self, str)?;
|
354
378
|
|
355
379
|
Ok(str)
|
356
380
|
}
|
@@ -358,23 +382,20 @@ impl MmapedFile {
|
|
358
382
|
/// If we reallocate, any live Ruby strings provided by the `str()` method
|
359
383
|
/// will be invalidated. We need to iterate over them using and update their
|
360
384
|
/// heap pointers to the newly allocated memory region.
|
361
|
-
fn update_weak_map(
|
385
|
+
fn update_weak_map(
|
386
|
+
&self,
|
387
|
+
rb_self: Obj<Self>,
|
388
|
+
old_ptr: *const c_char,
|
389
|
+
old_cap: c_long,
|
390
|
+
) -> magnus::error::Result<()> {
|
362
391
|
let tracker: Value = rb_self.ivar_get("@weak_obj_tracker")?;
|
363
392
|
|
364
|
-
let
|
365
|
-
// Pointers are not `Send`, convert it to usize to allow capture in closure.
|
366
|
-
let new_ptr = inner.as_ptr() as usize;
|
367
|
-
let new_len = util::cast_chk::<_, c_long>(inner.len(), "mmap len")?;
|
393
|
+
let new_len = self.inner(|inner| util::cast_chk::<_, c_long>(inner.len(), "mmap len"))?;
|
368
394
|
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
let block = Proc::from_fn(move |args, _block| {
|
374
|
-
let val = args
|
375
|
-
.get(0)
|
376
|
-
.ok_or_else(|| err!(arg_error(), "no argument received from `each_value`"))?;
|
377
|
-
let str = RString::from_value(*val)
|
395
|
+
// Iterate over the values of the `WeakMap`.
|
396
|
+
for val in tracker.enumeratorize("each_value", ()) {
|
397
|
+
let rb_string = val?;
|
398
|
+
let str = RString::from_value(rb_string)
|
378
399
|
.ok_or_else(|| err!(arg_error(), "weakmap value was not a string"))?;
|
379
400
|
|
380
401
|
// SAFETY: We're messing with Ruby's internals here, YOLO.
|
@@ -383,18 +404,42 @@ impl MmapedFile {
|
|
383
404
|
// which provides access to its internals.
|
384
405
|
let mut raw_str = Self::rb_string_internal(str);
|
385
406
|
|
407
|
+
// Shared string have their own `ptr` and `len` values, but `aux`
|
408
|
+
// is the id of the parent string so the GC can track this
|
409
|
+
// dependency. The `ptr` will always be an offset from the base
|
410
|
+
// address of the mmap, and `len` will be the length of the mmap
|
411
|
+
// less the offset from the base.
|
412
|
+
if Self::rb_string_is_shared(str) && new_len > 0 {
|
413
|
+
// Calculate how far into the original mmap the shared string
|
414
|
+
// started and update to the equivalent address in the new
|
415
|
+
// one.
|
416
|
+
let substr_ptr = raw_str.as_ref().as_.heap.ptr;
|
417
|
+
let offset = substr_ptr.offset_from(old_ptr);
|
418
|
+
|
419
|
+
raw_str.as_mut().as_.heap.ptr = self.as_mut_ptr().offset(offset);
|
420
|
+
|
421
|
+
let current_len = raw_str.as_ref().as_.heap.len;
|
422
|
+
let new_shared_len = old_cap + current_len;
|
423
|
+
|
424
|
+
raw_str.as_mut().as_.heap.len = new_shared_len;
|
425
|
+
continue;
|
426
|
+
}
|
427
|
+
|
386
428
|
// Update the string to point to the new mmapped file.
|
387
429
|
// We're matching the behavior of Ruby's `str_new_static` function.
|
388
430
|
// See https://github.com/ruby/ruby/blob/e51014f9c05aa65cbf203442d37fef7c12390015/string.c#L1030-L1053
|
389
|
-
|
431
|
+
//
|
432
|
+
// We deliberately do _NOT_ increment the `capa` field of the
|
433
|
+
// string to match the new `len`. We were initially doing this,
|
434
|
+
// but consistently triggered GCs in the middle of updating the
|
435
|
+
// string pointers, causing a segfault.
|
436
|
+
//
|
437
|
+
// See https://gitlab.com/gitlab-org/ruby/gems/prometheus-client-mmap/-/issues/45
|
438
|
+
raw_str.as_mut().as_.heap.ptr = self.as_mut_ptr();
|
390
439
|
raw_str.as_mut().as_.heap.len = new_len;
|
391
|
-
raw_str.as_mut().as_.heap.aux.capa = new_len;
|
392
440
|
}
|
393
|
-
|
394
|
-
});
|
441
|
+
}
|
395
442
|
|
396
|
-
// Execute the block.
|
397
|
-
let _: Value = tracker.funcall_with_block("each_value", (), block)?;
|
398
443
|
Ok(())
|
399
444
|
}
|
400
445
|
|
@@ -429,6 +474,9 @@ impl MmapedFile {
|
|
429
474
|
}
|
430
475
|
|
431
476
|
if new_cap != self.capacity() {
|
477
|
+
let old_ptr = self.as_mut_ptr();
|
478
|
+
let old_cap = util::cast_chk::<_, c_long>(self.capacity(), "capacity")?;
|
479
|
+
|
432
480
|
// Drop the old mmap.
|
433
481
|
let (mut file, path) = self.take_inner()?.munmap();
|
434
482
|
|
@@ -439,7 +487,7 @@ impl MmapedFile {
|
|
439
487
|
|
440
488
|
self.insert_inner(new_inner)?;
|
441
489
|
|
442
|
-
return self.update_weak_map(rb_self);
|
490
|
+
return self.update_weak_map(rb_self, old_ptr, old_cap);
|
443
491
|
}
|
444
492
|
|
445
493
|
Ok(())
|
@@ -476,6 +524,15 @@ impl MmapedFile {
|
|
476
524
|
Ok(())
|
477
525
|
}
|
478
526
|
|
527
|
+
fn track_rstring(&self, rb_self: Obj<Self>, str: RString) -> magnus::error::Result<()> {
|
528
|
+
let tracker: Value = rb_self.ivar_get("@weak_obj_tracker")?;
|
529
|
+
|
530
|
+
// Use the string's Id as the key in the `WeakMap`.
|
531
|
+
let key = str.as_raw();
|
532
|
+
let _: Value = tracker.funcall("[]=", (key, str))?;
|
533
|
+
Ok(())
|
534
|
+
}
|
535
|
+
|
479
536
|
/// The total capacity of the underlying mmap.
|
480
537
|
#[inline]
|
481
538
|
fn capacity(&self) -> usize {
|
@@ -489,6 +546,13 @@ impl MmapedFile {
|
|
489
546
|
.map_err(|e| e.into())
|
490
547
|
}
|
491
548
|
|
549
|
+
fn as_mut_ptr(&self) -> *mut c_char {
|
550
|
+
// UNWRAP: This is actually infallible, but we need to
|
551
|
+
// wrap it in a `Result` for use with `inner()`.
|
552
|
+
self.inner(|inner| Ok(inner.as_mut_ptr() as *mut c_char))
|
553
|
+
.unwrap()
|
554
|
+
}
|
555
|
+
|
492
556
|
/// Takes a closure with immutable access to InnerMmap. Will fail if the inner
|
493
557
|
/// object has a mutable borrow or has been dropped.
|
494
558
|
fn inner<F, T>(&self, func: F) -> Result<T>
|
@@ -544,9 +608,27 @@ impl MmapedFile {
|
|
544
608
|
Ok(())
|
545
609
|
}
|
546
610
|
|
611
|
+
/// Check if an RString is shared. Shared string use the same underlying
|
612
|
+
/// storage as their parent, taking an offset from the start. By default
|
613
|
+
/// they must run to the end of the parent string.
|
614
|
+
fn rb_string_is_shared(rb_str: RString) -> bool {
|
615
|
+
// SAFETY: We only hold a reference to the raw object for the duration
|
616
|
+
// of this function, and no Ruby code is called.
|
617
|
+
let flags = unsafe {
|
618
|
+
let raw_str = Self::rb_string_internal(rb_str);
|
619
|
+
raw_str.as_ref().basic.flags
|
620
|
+
};
|
621
|
+
let shared_flags = STR_SHARED | STR_NOEMBED;
|
622
|
+
|
623
|
+
flags & shared_flags == shared_flags
|
624
|
+
}
|
625
|
+
|
547
626
|
/// Convert `magnus::RString` into the raw binding used by `rb_sys::RString`.
|
548
627
|
/// We need this to manually change the pointer and length values for strings
|
549
628
|
/// when moving the mmap to a new file.
|
629
|
+
///
|
630
|
+
/// SAFETY: Calling Ruby code while the returned object is held may result
|
631
|
+
/// in it being mutated or dropped.
|
550
632
|
unsafe fn rb_string_internal(rb_str: RString) -> NonNull<rb_sys::RString> {
|
551
633
|
mem::transmute::<RString, NonNull<rb_sys::RString>>(rb_str)
|
552
634
|
}
|
@@ -666,18 +748,101 @@ mod test {
|
|
666
748
|
let ruby = magnus::Ruby::get().unwrap();
|
667
749
|
crate::init(&ruby).unwrap();
|
668
750
|
|
751
|
+
fn assert_internals(
|
752
|
+
obj: Obj<MmapedFile>,
|
753
|
+
parent_id: c_ulong,
|
754
|
+
child_id: c_ulong,
|
755
|
+
unshared_id: c_ulong,
|
756
|
+
) {
|
757
|
+
let rs_self = &*obj;
|
758
|
+
let tracker: Value = obj.ivar_get("@weak_obj_tracker").unwrap();
|
759
|
+
|
760
|
+
let mmap_ptr = rs_self.as_mut_ptr();
|
761
|
+
let mmap_len = rs_self.capacity();
|
762
|
+
|
763
|
+
let mut parent_checked = false;
|
764
|
+
let mut child_checked = false;
|
765
|
+
|
766
|
+
for val in tracker.enumeratorize("each_value", ()) {
|
767
|
+
let rb_string = val.unwrap();
|
768
|
+
let str = RString::from_value(rb_string).unwrap();
|
769
|
+
|
770
|
+
unsafe {
|
771
|
+
let raw_str = MmapedFile::rb_string_internal(str);
|
772
|
+
if str.as_raw() == child_id {
|
773
|
+
assert_eq!(parent_id, raw_str.as_ref().as_.heap.aux.shared);
|
774
|
+
|
775
|
+
let child_offset =
|
776
|
+
mmap_len as isize - raw_str.as_ref().as_.heap.len as isize;
|
777
|
+
assert_eq!(mmap_ptr.offset(child_offset), raw_str.as_ref().as_.heap.ptr);
|
778
|
+
|
779
|
+
child_checked = true;
|
780
|
+
} else if str.as_raw() == parent_id {
|
781
|
+
assert_eq!(parent_id, str.as_raw());
|
782
|
+
|
783
|
+
assert_eq!(mmap_ptr, raw_str.as_ref().as_.heap.ptr);
|
784
|
+
assert_eq!(mmap_len as c_long, raw_str.as_ref().as_.heap.len);
|
785
|
+
assert!(raw_str.as_ref().basic.flags & (STR_SHARED | STR_NOEMBED) > 0);
|
786
|
+
assert!(str.is_frozen());
|
787
|
+
|
788
|
+
parent_checked = true;
|
789
|
+
} else if str.as_raw() == unshared_id {
|
790
|
+
panic!("tracking unshared string");
|
791
|
+
} else {
|
792
|
+
panic!("unknown string");
|
793
|
+
}
|
794
|
+
}
|
795
|
+
}
|
796
|
+
assert!(parent_checked && child_checked);
|
797
|
+
}
|
798
|
+
|
669
799
|
let obj = create_obj();
|
670
800
|
let _ = populate_entries(&obj);
|
671
801
|
|
672
802
|
let rs_self = &*obj;
|
673
803
|
|
674
|
-
|
675
|
-
let
|
676
|
-
|
677
|
-
|
804
|
+
// Create a string containing the full mmap.
|
805
|
+
let parent_str = rs_self.str(obj).unwrap();
|
806
|
+
let parent_id = parent_str.as_raw();
|
807
|
+
|
808
|
+
// Ruby's shared strings are only created when they go to the end of
|
809
|
+
// original string.
|
810
|
+
let len = rs_self.inner(|inner| Ok(inner.len())).unwrap();
|
811
|
+
let shareable_range = Range::new(1, len - 1, false).unwrap().as_value();
|
812
|
+
|
813
|
+
// This string should re-use the parent's buffer with an offset and have
|
814
|
+
// the parent's id in `as.heap.aux.shared`
|
815
|
+
let child_str = rs_self._slice(obj, parent_str, &[shareable_range]).unwrap();
|
816
|
+
let child_id = child_str.as_raw();
|
817
|
+
|
818
|
+
// A range that does not reach the end of the parent will not be shared.
|
819
|
+
assert!(len > 4);
|
820
|
+
let unshareable_range = Range::new(0, 4, false).unwrap().as_value();
|
821
|
+
|
822
|
+
// This string should NOT be tracked, it should own its own buffer.
|
823
|
+
let unshared_str = rs_self
|
824
|
+
._slice(obj, parent_str, &[unshareable_range])
|
825
|
+
.unwrap();
|
826
|
+
let unshared_id = unshared_str.as_raw();
|
827
|
+
assert!(!MmapedFile::rb_string_is_shared(unshared_str));
|
828
|
+
|
829
|
+
assert_internals(obj, parent_id, child_id, unshared_id);
|
830
|
+
|
831
|
+
let orig_ptr = rs_self.as_mut_ptr();
|
832
|
+
// Expand a bunch to ensure we remap
|
833
|
+
for _ in 0..16 {
|
834
|
+
rs_self.expand_to_fit(obj, rs_self.capacity() * 2).unwrap();
|
835
|
+
}
|
836
|
+
let new_ptr = rs_self.as_mut_ptr();
|
837
|
+
assert!(orig_ptr != new_ptr);
|
678
838
|
|
679
839
|
// If we haven't updated the pointer to the newly remapped file this will segfault.
|
680
|
-
let _: Value = eval!("puts
|
840
|
+
let _: Value = eval!("puts parent", parent = parent_str).unwrap();
|
841
|
+
let _: Value = eval!("puts child", child = child_str).unwrap();
|
842
|
+
let _: Value = eval!("puts unshared", unshared = unshared_str).unwrap();
|
843
|
+
|
844
|
+
// Confirm that tracked strings are still valid.
|
845
|
+
assert_internals(obj, parent_id, child_id, unshared_id);
|
681
846
|
}
|
682
847
|
|
683
848
|
#[test]
|
@@ -2,7 +2,7 @@ use smallvec::SmallVec;
|
|
2
2
|
use std::str;
|
3
3
|
|
4
4
|
/// String slices pointing to the fields of a borrowed `Entry`'s JSON data.
|
5
|
-
#[derive(PartialEq, Debug)]
|
5
|
+
#[derive(PartialEq, Eq, Debug)]
|
6
6
|
pub struct MetricText<'a> {
|
7
7
|
pub family_name: &'a str,
|
8
8
|
pub metric_name: &'a str,
|
@@ -10,14 +10,14 @@ pub struct MetricText<'a> {
|
|
10
10
|
pub values: SmallVec<[&'a str; 4]>,
|
11
11
|
}
|
12
12
|
|
13
|
-
#[derive(PartialEq, Debug)]
|
13
|
+
#[derive(PartialEq, Eq, Debug)]
|
14
14
|
struct MetricNames<'a> {
|
15
15
|
label_json: &'a str,
|
16
16
|
family_name: &'a str,
|
17
17
|
metric_name: &'a str,
|
18
18
|
}
|
19
19
|
|
20
|
-
#[derive(PartialEq, Debug)]
|
20
|
+
#[derive(PartialEq, Eq, Debug)]
|
21
21
|
struct MetricLabelVals<'a> {
|
22
22
|
labels: SmallVec<[&'a str; 4]>,
|
23
23
|
values: SmallVec<[&'a str; 4]>,
|
@@ -63,35 +63,35 @@ fn parse_names(json: &str) -> Option<MetricNames> {
|
|
63
63
|
return None;
|
64
64
|
}
|
65
65
|
|
66
|
-
//
|
67
|
-
|
68
|
-
|
69
|
-
let names_end = remainder.find('[')?;
|
70
|
-
|
71
|
-
// Save the rest of the slice to parse for labels later.
|
72
|
-
let label_json = remainder.get(names_end..)?;
|
73
|
-
|
74
|
-
// Now: family_name","metric_name",
|
75
|
-
let remainder = remainder.get(..names_end)?;
|
66
|
+
// Captured: "family_name","metric_name",
|
67
|
+
// ^^^^^^^^^^^
|
68
|
+
let (family_name, remainder) = scan_str(json.get(1..)?)?;
|
76
69
|
|
77
|
-
//
|
78
|
-
// family_name","metric_name",
|
79
|
-
//
|
80
|
-
|
70
|
+
// Validate comma separated names
|
71
|
+
// ["family_name","metric_name",[...
|
72
|
+
// ^
|
73
|
+
if !remainder.starts_with("\",") {
|
74
|
+
return None;
|
75
|
+
}
|
81
76
|
|
82
|
-
//
|
83
|
-
|
84
|
-
let family_name = token_iter.next()?.trim_end_matches('"');
|
77
|
+
// Now: "metric_name",[...
|
78
|
+
let remainder = remainder.get(2..)?;
|
85
79
|
|
86
80
|
// Captured: "family_name","metric_name",
|
87
81
|
// ^^^^^^^^^^^
|
88
|
-
let metric_name =
|
82
|
+
let (metric_name, remainder) = scan_str(remainder)?;
|
89
83
|
|
90
|
-
//
|
91
|
-
|
84
|
+
// Validate comma separated names
|
85
|
+
// ["family_name","metric_name",[...
|
86
|
+
// ^^
|
87
|
+
if !remainder.starts_with("\",") {
|
92
88
|
return None;
|
93
89
|
}
|
94
90
|
|
91
|
+
// Save the rest of the slice to parse for labels later.
|
92
|
+
// Now: [...
|
93
|
+
let label_json = remainder.get(2..)?;
|
94
|
+
|
95
95
|
Some(MetricNames {
|
96
96
|
label_json,
|
97
97
|
family_name,
|
@@ -101,76 +101,143 @@ fn parse_names(json: &str) -> Option<MetricNames> {
|
|
101
101
|
|
102
102
|
fn parse_label_values(json: &str) -> Option<MetricLabelVals> {
|
103
103
|
// Starting with: ["label_a","label_b"],["value_a", "value_b"]]
|
104
|
-
if !
|
104
|
+
if !json.starts_with('[') {
|
105
105
|
return None;
|
106
106
|
}
|
107
107
|
|
108
|
+
// Now: "label_a","label_b"],["value_a", "value_b"]]
|
109
|
+
let mut remainder = json.get(1..)?;
|
110
|
+
|
108
111
|
// Validate we either have the start of a label string or an
|
109
112
|
// empty array, e.g. `["` or `[]`.
|
110
113
|
if !matches!(json.as_bytes().get(1)?, b'"' | b']') {
|
111
114
|
return None;
|
112
115
|
}
|
113
116
|
|
114
|
-
// Now: "label_a","label_b"
|
115
|
-
let labels_end = json.find(']')?;
|
116
|
-
let label_range = json.get(1..labels_end)?;
|
117
|
-
|
118
117
|
let mut labels = SmallVec::new();
|
119
118
|
|
120
119
|
// Split on commas into:
|
121
120
|
// "label_a","label_b"
|
122
121
|
// ^^^one^^^ ^^^two^^^
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
122
|
+
loop {
|
123
|
+
let Some((label, label_rem)) = scan_str(remainder) else {
|
124
|
+
// No further keys.
|
125
|
+
break;
|
126
|
+
};
|
127
|
+
labels.push(label);
|
128
|
+
|
129
|
+
// Advance past trailing quote.
|
130
|
+
remainder = label_rem.get(1..)?;
|
131
|
+
match remainder.as_bytes().first() {
|
132
|
+
Some(b']') => break, // No further labels.
|
133
|
+
Some(b',') => {} // More labels expected.
|
134
|
+
_ => return None, // Invalid.
|
135
|
+
};
|
136
|
+
|
137
|
+
// Advance past comma.
|
138
|
+
remainder = remainder.get(1..)?;
|
130
139
|
}
|
131
140
|
|
132
|
-
|
133
|
-
let mut values_range = json.get(labels_end..)?;
|
134
|
-
|
135
|
-
// Validate we have a separating comma with one and only one leading bracket.
|
136
|
-
if !(values_range.starts_with("],[") && values_range.as_bytes().get(3)? != &b'[') {
|
141
|
+
if !remainder.starts_with("],[") {
|
137
142
|
return None;
|
138
143
|
}
|
139
|
-
|
140
144
|
// Now: "value_a", "value_b"]]
|
141
|
-
|
142
|
-
|
143
|
-
let values_end = values_range.find(']')?;
|
145
|
+
remainder = remainder.get(3..)?;
|
144
146
|
|
145
|
-
// Validate we have
|
146
|
-
if
|
147
|
+
// Validate we don't have extra brackets.
|
148
|
+
if remainder.starts_with('[') {
|
147
149
|
return None;
|
148
150
|
}
|
149
151
|
|
150
|
-
// Now: "value_a", "value_b"
|
151
|
-
values_range = values_range.get(..values_end)?;
|
152
|
-
|
153
152
|
let mut values = SmallVec::new();
|
154
|
-
|
155
153
|
// Split on commas into:
|
156
154
|
// "value_a","value_b"
|
157
155
|
// ^^^one^^^ ^^^two^^^
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
156
|
+
loop {
|
157
|
+
if remainder.starts_with('"') {
|
158
|
+
let (value, value_rem) = scan_str(remainder)?;
|
159
|
+
values.push(value);
|
160
|
+
|
161
|
+
// Advance past trailing quote.
|
162
|
+
remainder = value_rem.get(1..)?;
|
163
|
+
} else {
|
164
|
+
// An unquoted value, such as `true` or `404`.
|
165
|
+
let i = remainder.find(|c| c == ',' || c == ']')?;
|
166
|
+
let value = &remainder[..i];
|
167
|
+
|
168
|
+
match (value.is_empty(), is_valid_json_literal(value)) {
|
169
|
+
(true, _) => {} // Empty string, do nothing.
|
170
|
+
(false, true) => values.push(value), // Valid literal.
|
171
|
+
(false, false) => return None, // Invalid literal, fail.
|
172
|
+
}
|
173
|
+
|
174
|
+
remainder = &remainder[i..];
|
164
175
|
}
|
176
|
+
|
177
|
+
match remainder.as_bytes().first() {
|
178
|
+
Some(b']') => break, // End of values.
|
179
|
+
Some(b',') => {} // More values expected.
|
180
|
+
_ => return None, // Invalid.
|
181
|
+
};
|
182
|
+
|
183
|
+
// Advance past comma.
|
184
|
+
remainder = remainder.get(1..)?;
|
165
185
|
}
|
166
186
|
|
167
187
|
if values.len() != labels.len() {
|
168
188
|
return None;
|
169
189
|
}
|
170
190
|
|
191
|
+
// Now: ]]
|
192
|
+
if remainder != "]]" {
|
193
|
+
return None;
|
194
|
+
}
|
195
|
+
|
171
196
|
Some(MetricLabelVals { labels, values })
|
172
197
|
}
|
173
198
|
|
199
|
+
// Read a JSON-encoded str that includes starting and ending double quotes.
|
200
|
+
// Returns the validated str with the double quotes trimmed and the remainder
|
201
|
+
// of the input str.
|
202
|
+
fn scan_str(json: &str) -> Option<(&str, &str)> {
|
203
|
+
let mut escaping = false;
|
204
|
+
|
205
|
+
if !json.starts_with('"') {
|
206
|
+
return None;
|
207
|
+
}
|
208
|
+
|
209
|
+
// Trim leading double quote.
|
210
|
+
let json = json.get(1..)?;
|
211
|
+
|
212
|
+
for (i, &c) in json.as_bytes().iter().enumerate() {
|
213
|
+
if c == b'\\' {
|
214
|
+
escaping = true;
|
215
|
+
continue;
|
216
|
+
}
|
217
|
+
|
218
|
+
if c == b'"' && !escaping {
|
219
|
+
return Some((json.get(..i)?, json.get(i..)?));
|
220
|
+
}
|
221
|
+
|
222
|
+
escaping = false;
|
223
|
+
}
|
224
|
+
|
225
|
+
None
|
226
|
+
}
|
227
|
+
|
228
|
+
// Confirm an unquoted JSON literal is a boolean, null, or has a passing
|
229
|
+
// resemblance to a number. We do not confirm numeric formatting, only
|
230
|
+
// that all characters are valid. See https://www.json.org/json-en.html
|
231
|
+
// for details.
|
232
|
+
fn is_valid_json_literal(s: &str) -> bool {
|
233
|
+
match s {
|
234
|
+
"true" | "false" | "null" => true,
|
235
|
+
_ => s.chars().all(|c| {
|
236
|
+
c.is_ascii_digit() || c == '.' || c == '+' || c == '-' || c == 'e' || c == 'E'
|
237
|
+
}),
|
238
|
+
}
|
239
|
+
}
|
240
|
+
|
174
241
|
#[cfg(test)]
|
175
242
|
mod test {
|
176
243
|
use smallvec::smallvec;
|
@@ -217,6 +284,16 @@ mod test {
|
|
217
284
|
values: smallvec!["value_a", "403"],
|
218
285
|
}),
|
219
286
|
},
|
287
|
+
TestCase {
|
288
|
+
name: "scientific notation literal",
|
289
|
+
input: r#"["metric","name",["label_a"],[-2.0e-5]]"#,
|
290
|
+
expected: Some(MetricText {
|
291
|
+
family_name: "metric",
|
292
|
+
metric_name: "name",
|
293
|
+
labels: smallvec!["label_a"],
|
294
|
+
values: smallvec!["-2.0e-5"],
|
295
|
+
}),
|
296
|
+
},
|
220
297
|
TestCase {
|
221
298
|
name: "null value",
|
222
299
|
input: r#"["metric","name",["label_a","label_b"],[null,"value_b"]]"#,
|
@@ -237,6 +314,36 @@ mod test {
|
|
237
314
|
values: smallvec![],
|
238
315
|
}),
|
239
316
|
},
|
317
|
+
TestCase {
|
318
|
+
name: "comma in items",
|
319
|
+
input: r#"["met,ric","na,me",["label,_a","label_b"],["value,_a","value_b"]]"#,
|
320
|
+
expected: Some(MetricText {
|
321
|
+
family_name: "met,ric",
|
322
|
+
metric_name: "na,me",
|
323
|
+
labels: smallvec!["label,_a", "label_b"],
|
324
|
+
values: smallvec!["value,_a", "value_b"],
|
325
|
+
}),
|
326
|
+
},
|
327
|
+
TestCase {
|
328
|
+
name: "bracket in value",
|
329
|
+
input: r#"["met[r]ic","na[m]e",["label[_]a","label_b"],["value_a","val[ue]_b"]]"#,
|
330
|
+
expected: Some(MetricText {
|
331
|
+
family_name: "met[r]ic",
|
332
|
+
metric_name: "na[m]e",
|
333
|
+
labels: smallvec!["label[_]a", "label_b"],
|
334
|
+
values: smallvec!["value_a", "val[ue]_b"],
|
335
|
+
}),
|
336
|
+
},
|
337
|
+
TestCase {
|
338
|
+
name: "escaped quotes",
|
339
|
+
input: r#"["met\"ric","na\"me",["label\"_a","label_b"],["value\"_a","value_b"]]"#,
|
340
|
+
expected: Some(MetricText {
|
341
|
+
family_name: r#"met\"ric"#,
|
342
|
+
metric_name: r#"na\"me"#,
|
343
|
+
labels: smallvec![r#"label\"_a"#, "label_b"],
|
344
|
+
values: smallvec![r#"value\"_a"#, "value_b"],
|
345
|
+
}),
|
346
|
+
},
|
240
347
|
];
|
241
348
|
|
242
349
|
for case in tc {
|
@@ -313,23 +420,18 @@ mod test {
|
|
313
420
|
expected: None,
|
314
421
|
},
|
315
422
|
TestCase {
|
316
|
-
name: "
|
317
|
-
input: r#"["
|
318
|
-
expected: None,
|
319
|
-
},
|
320
|
-
TestCase {
|
321
|
-
name: "comma in metric name",
|
322
|
-
input: r#"["metric","na,me",["label_a","label_b"],["value_a","value_b"]]"#,
|
423
|
+
name: "misplaced bracket",
|
424
|
+
input: r#"["metric","name",["label_a","label_b"],]["value_a","value_b"]]"#,
|
323
425
|
expected: None,
|
324
426
|
},
|
325
427
|
TestCase {
|
326
|
-
name: "comma in value",
|
327
|
-
input: r#"["metric","
|
428
|
+
name: "comma in numeric value",
|
429
|
+
input: r#"["metric","name",["label_a","label_b"],[400,0,"value_b"]]"#,
|
328
430
|
expected: None,
|
329
431
|
},
|
330
432
|
TestCase {
|
331
|
-
name: "
|
332
|
-
input: r#"["metric","name",["label_a","label_b"],[
|
433
|
+
name: "non-e letter in numeric value",
|
434
|
+
input: r#"["metric","name",["label_a","label_b"],[400x0,"value_b"]]"#,
|
333
435
|
expected: None,
|
334
436
|
},
|
335
437
|
];
|
@@ -6,7 +6,7 @@ use crate::util::CheckedOps;
|
|
6
6
|
use crate::Result;
|
7
7
|
|
8
8
|
/// The logic to save a `MetricsEntry`, or parse one from a byte slice.
|
9
|
-
#[derive(PartialEq, Clone, Debug)]
|
9
|
+
#[derive(PartialEq, Eq, Clone, Debug)]
|
10
10
|
pub struct RawEntry<'a> {
|
11
11
|
bytes: &'a [u8],
|
12
12
|
encoded_len: usize,
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -7,13 +7,14 @@ require 'tmpdir'
|
|
7
7
|
module Prometheus
|
8
8
|
module Client
|
9
9
|
class Configuration
|
10
|
-
attr_accessor :value_class, :multiprocess_files_dir, :initial_mmap_file_size, :logger, :pid_provider
|
10
|
+
attr_accessor :value_class, :multiprocess_files_dir, :initial_mmap_file_size, :logger, :pid_provider, :rust_multiprocess_metrics
|
11
11
|
|
12
12
|
def initialize
|
13
13
|
@value_class = ::Prometheus::Client::MmapedValue
|
14
14
|
@initial_mmap_file_size = ::Prometheus::Client::PageSize.page_size(fallback_page_size: 4096)
|
15
15
|
@logger = Logger.new($stdout)
|
16
16
|
@pid_provider = Process.method(:pid)
|
17
|
+
@rust_multiprocess_metrics = ENV.fetch('prometheus_rust_multiprocess_metrics', nil) == 'true'
|
17
18
|
@multiprocess_files_dir = ENV.fetch('prometheus_multiproc_dir') do
|
18
19
|
Dir.mktmpdir("prometheus-mmap")
|
19
20
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'prometheus/client/uses_value_type'
|
2
2
|
require 'prometheus/client/helper/json_parser'
|
3
|
+
require 'prometheus/client/helper/loader'
|
3
4
|
require 'prometheus/client/helper/plain_file'
|
4
5
|
require 'prometheus/client/helper/metrics_processing'
|
5
6
|
require 'prometheus/client/helper/metrics_representation'
|
@@ -31,7 +32,7 @@ module Prometheus
|
|
31
32
|
.map {|f| Helper::PlainFile.new(f) }
|
32
33
|
.map {|f| [f.filepath, f.multiprocess_mode.to_sym, f.type.to_sym, f.pid] }
|
33
34
|
|
34
|
-
if use_rust && rust_impl_available?
|
35
|
+
if use_rust && Prometheus::Client::Helper::Loader.rust_impl_available?
|
35
36
|
FastMmapedFileRs.to_metrics(file_list.to_a)
|
36
37
|
else
|
37
38
|
FastMmapedFile.to_metrics(file_list.to_a)
|
@@ -46,29 +47,6 @@ module Prometheus
|
|
46
47
|
|
47
48
|
private
|
48
49
|
|
49
|
-
def load_rust_extension
|
50
|
-
begin
|
51
|
-
ruby_version = /(\d+\.\d+)/.match(RUBY_VERSION)
|
52
|
-
require_relative "../../../#{ruby_version}/fast_mmaped_file_rs"
|
53
|
-
rescue LoadError
|
54
|
-
require 'fast_mmaped_file_rs'
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def check_for_rust
|
59
|
-
# This will be evaluated on each invocation even with `||=` if
|
60
|
-
# `@rust_available` if false. Running a `require` statement is slow,
|
61
|
-
# so the `rust_impl_available?` method memoizes the result, external
|
62
|
-
# callers can only trigger this method a single time.
|
63
|
-
@rust_available = begin
|
64
|
-
load_rust_extension
|
65
|
-
true
|
66
|
-
rescue LoadError
|
67
|
-
Prometheus::Client.logger.info('FastMmapedFileRs unavailable')
|
68
|
-
false
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
50
|
def load_metrics(path)
|
73
51
|
metrics = {}
|
74
52
|
Dir.glob(File.join(path, '*.db')).sort.each do |f|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Prometheus
|
2
|
+
module Client
|
3
|
+
module Helper
|
4
|
+
module Loader
|
5
|
+
class << self
|
6
|
+
def rust_impl_available?
|
7
|
+
return @rust_available unless @rust_available.nil?
|
8
|
+
|
9
|
+
check_for_rust
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def load_rust_extension
|
15
|
+
begin
|
16
|
+
ruby_version = /(\d+\.\d+)/.match(RUBY_VERSION)
|
17
|
+
require_relative "../../../#{ruby_version}/fast_mmaped_file_rs"
|
18
|
+
rescue LoadError
|
19
|
+
require 'fast_mmaped_file_rs'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def check_for_rust
|
24
|
+
# This will be evaluated on each invocation even with `||=` if
|
25
|
+
# `@rust_available` if false. Running a `require` statement is slow,
|
26
|
+
# so the `rust_impl_available?` method memoizes the result, external
|
27
|
+
# callers can only trigger this method a single time.
|
28
|
+
@rust_available = begin
|
29
|
+
load_rust_extension
|
30
|
+
true
|
31
|
+
rescue LoadError
|
32
|
+
Prometheus::Client.logger.info('FastMmapedFileRs unavailable')
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'prometheus/client/helper/entry_parser'
|
2
2
|
require 'prometheus/client/helper/file_locker'
|
3
|
+
require 'prometheus/client/helper/loader'
|
3
4
|
|
4
5
|
# load precompiled extension if available
|
5
6
|
begin
|
@@ -12,7 +13,17 @@ end
|
|
12
13
|
module Prometheus
|
13
14
|
module Client
|
14
15
|
module Helper
|
15
|
-
|
16
|
+
# We can't check `Prometheus::Client.configuration` as this creates a circular dependency
|
17
|
+
if (ENV.fetch('prometheus_rust_mmaped_file', nil) == "true" &&
|
18
|
+
Prometheus::Client::Helper::Loader.rust_impl_available?)
|
19
|
+
class MmapedFile < FastMmapedFileRs
|
20
|
+
end
|
21
|
+
else
|
22
|
+
class MmapedFile < FastMmapedFile
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class MmapedFile
|
16
27
|
include EntryParser
|
17
28
|
|
18
29
|
attr_reader :filepath, :size
|
@@ -62,8 +62,10 @@ module Prometheus
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def respond_with(format)
|
65
|
+
rust_enabled = Prometheus::Client.configuration.rust_multiprocess_metrics
|
66
|
+
|
65
67
|
response = if Prometheus::Client.configuration.value_class.multiprocess
|
66
|
-
format.marshal_multiprocess
|
68
|
+
format.marshal_multiprocess(use_rust: rust_enabled)
|
67
69
|
else
|
68
70
|
format.marshal
|
69
71
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prometheus-client-mmap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.24.4
|
5
5
|
platform: x86_64-darwin
|
6
6
|
authors:
|
7
7
|
- Tobias Schmidt
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2023-
|
14
|
+
date: 2023-06-12 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rb_sys
|
@@ -180,6 +180,7 @@ files:
|
|
180
180
|
- lib/prometheus/client/helper/entry_parser.rb
|
181
181
|
- lib/prometheus/client/helper/file_locker.rb
|
182
182
|
- lib/prometheus/client/helper/json_parser.rb
|
183
|
+
- lib/prometheus/client/helper/loader.rb
|
183
184
|
- lib/prometheus/client/helper/metrics_processing.rb
|
184
185
|
- lib/prometheus/client/helper/metrics_representation.rb
|
185
186
|
- lib/prometheus/client/helper/mmaped_file.rb
|