prometheus-client-mmap 0.22.0-x86_64-linux-musl → 1.2.4-x86_64-linux-musl
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.tool-versions +1 -0
- data/README.md +32 -17
- data/ext/fast_mmaped_file_rs/Cargo.toml +14 -9
- data/ext/fast_mmaped_file_rs/build.rs +5 -0
- data/ext/fast_mmaped_file_rs/extconf.rb +1 -3
- data/ext/fast_mmaped_file_rs/src/error.rs +2 -2
- data/ext/fast_mmaped_file_rs/src/file_entry.rs +222 -17
- data/ext/fast_mmaped_file_rs/src/file_info.rs +56 -6
- data/ext/fast_mmaped_file_rs/src/lib.rs +0 -1
- data/ext/fast_mmaped_file_rs/src/map.rs +12 -12
- data/ext/fast_mmaped_file_rs/src/mmap/inner.rs +6 -10
- data/ext/fast_mmaped_file_rs/src/mmap.rs +225 -47
- 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/3.1/fast_mmaped_file_rs.so +0 -0
- data/lib/3.2/fast_mmaped_file_rs.so +0 -0
- data/lib/3.3/fast_mmaped_file_rs.so +0 -0
- data/lib/3.4/fast_mmaped_file_rs.so +0 -0
- data/lib/prometheus/client/formats/text.rb +1 -34
- data/lib/prometheus/client/helper/mmaped_file.rb +3 -3
- data/lib/prometheus/client/label_set_validator.rb +1 -2
- data/lib/prometheus/client/support/puma.rb +44 -0
- data/lib/prometheus/client/version.rb +1 -1
- metadata +62 -61
- data/ext/fast_mmaped_file/extconf.rb +0 -30
- data/ext/fast_mmaped_file/fast_mmaped_file.c +0 -122
- data/ext/fast_mmaped_file/file_format.c +0 -5
- data/ext/fast_mmaped_file/file_format.h +0 -11
- data/ext/fast_mmaped_file/file_parsing.c +0 -195
- data/ext/fast_mmaped_file/file_parsing.h +0 -27
- data/ext/fast_mmaped_file/file_reading.c +0 -102
- data/ext/fast_mmaped_file/file_reading.h +0 -30
- data/ext/fast_mmaped_file/globals.h +0 -14
- data/ext/fast_mmaped_file/mmap.c +0 -427
- data/ext/fast_mmaped_file/mmap.h +0 -61
- data/ext/fast_mmaped_file/rendering.c +0 -199
- data/ext/fast_mmaped_file/rendering.h +0 -8
- data/ext/fast_mmaped_file/utils.c +0 -56
- data/ext/fast_mmaped_file/utils.h +0 -22
- data/ext/fast_mmaped_file/value_access.c +0 -242
- data/ext/fast_mmaped_file/value_access.h +0 -15
- data/ext/fast_mmaped_file_rs/.cargo/config.toml +0 -23
- data/ext/fast_mmaped_file_rs/Cargo.lock +0 -790
- data/ext/fast_mmaped_file_rs/src/parser.rs +0 -346
- data/lib/2.7/fast_mmaped_file.so +0 -0
- data/lib/2.7/fast_mmaped_file_rs.so +0 -0
- data/lib/3.0/fast_mmaped_file.so +0 -0
- data/lib/3.0/fast_mmaped_file_rs.so +0 -0
- data/lib/3.1/fast_mmaped_file.so +0 -0
- data/lib/3.2/fast_mmaped_file.so +0 -0
- data/vendor/c/hashmap/.gitignore +0 -52
- data/vendor/c/hashmap/LICENSE +0 -21
- data/vendor/c/hashmap/README.md +0 -90
- data/vendor/c/hashmap/_config.yml +0 -1
- data/vendor/c/hashmap/src/hashmap.c +0 -692
- data/vendor/c/hashmap/src/hashmap.h +0 -267
- data/vendor/c/hashmap/test/Makefile +0 -22
- data/vendor/c/hashmap/test/hashmap_test.c +0 -608
- data/vendor/c/jsmn/.travis.yml +0 -4
- data/vendor/c/jsmn/LICENSE +0 -20
- data/vendor/c/jsmn/Makefile +0 -41
- data/vendor/c/jsmn/README.md +0 -168
- data/vendor/c/jsmn/example/jsondump.c +0 -126
- data/vendor/c/jsmn/example/simple.c +0 -76
- data/vendor/c/jsmn/jsmn.c +0 -314
- data/vendor/c/jsmn/jsmn.h +0 -76
- data/vendor/c/jsmn/library.json +0 -16
- data/vendor/c/jsmn/test/test.h +0 -27
- data/vendor/c/jsmn/test/tests.c +0 -407
- data/vendor/c/jsmn/test/testutil.h +0 -94
@@ -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<()> {
|
@@ -218,16 +224,6 @@ impl InnerMmap {
|
|
218
224
|
}
|
219
225
|
}
|
220
226
|
|
221
|
-
/// Truncate the mmapped file to the end of the metrics data.
|
222
|
-
pub fn truncate_file(&mut self) -> Result<()> {
|
223
|
-
// CAST: no-op on 64-bit, widening on 32-bit.
|
224
|
-
let trunc_len = self.len as u64;
|
225
|
-
|
226
|
-
self.file
|
227
|
-
.set_len(trunc_len)
|
228
|
-
.map_err(|e| MmapError::legacy(format!("truncate: {e}"), RubyError::Type))
|
229
|
-
}
|
230
|
-
|
231
227
|
/// Load the `used` header containing the size of the metrics data written.
|
232
228
|
pub fn load_used(&self) -> Result<u32> {
|
233
229
|
match read_u32(self.map.as_ref(), 0) {
|
@@ -3,7 +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::{
|
6
|
+
use magnus::{eval, scan_args, Error, Integer, RArray, RClass, RHash, RString, Value};
|
7
|
+
use nix::libc::{c_char, c_long, c_ulong};
|
7
8
|
use rb_sys::rb_str_new_static;
|
8
9
|
use std::fs::File;
|
9
10
|
use std::io::{prelude::*, SeekFrom};
|
@@ -24,6 +25,18 @@ use inner::InnerMmap;
|
|
24
25
|
|
25
26
|
mod inner;
|
26
27
|
|
28
|
+
#[cfg(ruby_gte_3_4)]
|
29
|
+
/// The Ruby `STR_SHARED` flag, aka `FL_USER0`.
|
30
|
+
/// This was changed from `FL_USER2` in https://github.com/ruby/ruby/commit/6deeec5d459ecff5ec4628523b14ac7379fd942e.
|
31
|
+
const STR_SHARED: c_ulong = 1 << (12);
|
32
|
+
|
33
|
+
#[cfg(ruby_lte_3_3)]
|
34
|
+
/// The Ruby `STR_SHARED` flag, aka `FL_USER2`.
|
35
|
+
const STR_SHARED: c_ulong = 1 << (14);
|
36
|
+
|
37
|
+
/// The Ruby `STR_NOEMBED` flag, aka `FL_USER1`.
|
38
|
+
const STR_NOEMBED: c_ulong = 1 << (13);
|
39
|
+
|
27
40
|
/// A Rust struct wrapped in a Ruby object, providing access to a memory-mapped
|
28
41
|
/// file used to store, update, and read out Prometheus metrics.
|
29
42
|
///
|
@@ -150,13 +163,28 @@ impl MmapedFile {
|
|
150
163
|
///
|
151
164
|
/// return a substring of <em>lenght</em> characters from <em>start</em>
|
152
165
|
pub fn slice(rb_self: Obj<Self>, args: &[Value]) -> magnus::error::Result<RString> {
|
153
|
-
// The C
|
166
|
+
// The C implementation would trigger a GC cycle via `rb_gc_force_recycle`
|
154
167
|
// if the `MM_PROTECT` flag is set, but in practice this is never used.
|
155
168
|
// We omit this logic, particularly because `rb_gc_force_recycle` is a
|
156
169
|
// no-op as of Ruby 3.1.
|
170
|
+
let rs_self = &*rb_self;
|
157
171
|
|
158
|
-
let str =
|
159
|
-
|
172
|
+
let str = rs_self.str(rb_self)?;
|
173
|
+
rs_self._slice(rb_self, str, args)
|
174
|
+
}
|
175
|
+
|
176
|
+
fn _slice(
|
177
|
+
&self,
|
178
|
+
rb_self: Obj<Self>,
|
179
|
+
str: RString,
|
180
|
+
args: &[Value],
|
181
|
+
) -> magnus::error::Result<RString> {
|
182
|
+
let substr: RString = str.funcall("[]", args)?;
|
183
|
+
|
184
|
+
// Track shared child strings which use the same backing storage.
|
185
|
+
if Self::rb_string_is_shared(substr) {
|
186
|
+
(*rb_self).track_rstring(rb_self, substr)?;
|
187
|
+
}
|
160
188
|
|
161
189
|
// The C implementation does this, perhaps to validate that the len we
|
162
190
|
// provided is actually being used.
|
@@ -165,7 +193,7 @@ impl MmapedFile {
|
|
165
193
|
Ok(())
|
166
194
|
})?;
|
167
195
|
|
168
|
-
|
196
|
+
Ok(substr)
|
169
197
|
}
|
170
198
|
|
171
199
|
/// Document-method: msync
|
@@ -204,9 +232,6 @@ impl MmapedFile {
|
|
204
232
|
let rs_self = &*rb_self;
|
205
233
|
|
206
234
|
rs_self.inner_mut(|inner| {
|
207
|
-
// truncate file to actual used size
|
208
|
-
inner.truncate_file()?;
|
209
|
-
|
210
235
|
// We are about to release the backing mmap for Ruby's String
|
211
236
|
// objects. If Ruby attempts to read from them the program will
|
212
237
|
// segfault. We update the length of all Strings to zero so Ruby
|
@@ -222,7 +247,8 @@ impl MmapedFile {
|
|
222
247
|
})?;
|
223
248
|
|
224
249
|
// Update each String object to be zero-length.
|
225
|
-
rs_self.
|
250
|
+
let cap = util::cast_chk::<_, c_long>(rs_self.capacity(), "capacity")?;
|
251
|
+
rs_self.update_weak_map(rb_self, rs_self.as_mut_ptr(), cap)?;
|
226
252
|
|
227
253
|
// Remove the `InnerMmap` from the `RwLock`. This will drop
|
228
254
|
// end of this function, unmapping and closing the file.
|
@@ -336,44 +362,44 @@ impl MmapedFile {
|
|
336
362
|
|
337
363
|
// SAFETY: This is safe so long as the data provided to Ruby meets its
|
338
364
|
// requirements. When unmapping the file this will no longer be the
|
339
|
-
// case, see the comment on `munmap` how we handle this.
|
365
|
+
// case, see the comment on `munmap` for how we handle this.
|
340
366
|
Ok(unsafe { rb_str_new_static(ptr as _, len as _) })
|
341
367
|
})?;
|
342
368
|
|
369
|
+
// SAFETY: We know that rb_str_new_static returns a VALUE.
|
343
370
|
let val = unsafe { Value::from_raw(val_id) };
|
344
371
|
|
345
372
|
// UNWRAP: We created this value as a string above.
|
346
373
|
let str = RString::from_value(val).unwrap();
|
347
374
|
|
348
|
-
|
375
|
+
// Freeze the root string so it can't be mutated out from under any
|
376
|
+
// substrings created. This object is never exposed to callers.
|
377
|
+
str.freeze();
|
349
378
|
|
350
|
-
//
|
351
|
-
|
352
|
-
|
379
|
+
// Track the RString in our `WeakMap` so we can update its address if
|
380
|
+
// we re-mmap the backing file.
|
381
|
+
(*rb_self).track_rstring(rb_self, str)?;
|
353
382
|
|
354
383
|
Ok(str)
|
355
384
|
}
|
356
385
|
|
357
386
|
/// If we reallocate, any live Ruby strings provided by the `str()` method
|
358
|
-
/// will be
|
359
|
-
///
|
360
|
-
fn update_weak_map(
|
387
|
+
/// will be invalidated. We need to iterate over them using and update their
|
388
|
+
/// heap pointers to the newly allocated memory region.
|
389
|
+
fn update_weak_map(
|
390
|
+
&self,
|
391
|
+
rb_self: Obj<Self>,
|
392
|
+
old_ptr: *const c_char,
|
393
|
+
old_cap: c_long,
|
394
|
+
) -> magnus::error::Result<()> {
|
361
395
|
let tracker: Value = rb_self.ivar_get("@weak_obj_tracker")?;
|
362
396
|
|
363
|
-
let
|
364
|
-
// Pointers are not `Send`, convert it to usize to allow capture in closure.
|
365
|
-
let new_ptr = inner.as_ptr() as usize;
|
366
|
-
let new_len = util::cast_chk::<_, i64>(inner.len(), "mmap len")?;
|
367
|
-
|
368
|
-
Ok((new_ptr, new_len))
|
369
|
-
})?;
|
397
|
+
let new_len = self.inner(|inner| util::cast_chk::<_, c_long>(inner.len(), "mmap len"))?;
|
370
398
|
|
371
|
-
//
|
372
|
-
|
373
|
-
let
|
374
|
-
|
375
|
-
.ok_or_else(|| err!(arg_error(), "no argument received from `each_value`"))?;
|
376
|
-
let str = RString::from_value(*val)
|
399
|
+
// Iterate over the values of the `WeakMap`.
|
400
|
+
for val in tracker.enumeratorize("each_value", ()) {
|
401
|
+
let rb_string = val?;
|
402
|
+
let str = RString::from_value(rb_string)
|
377
403
|
.ok_or_else(|| err!(arg_error(), "weakmap value was not a string"))?;
|
378
404
|
|
379
405
|
// SAFETY: We're messing with Ruby's internals here, YOLO.
|
@@ -382,18 +408,42 @@ impl MmapedFile {
|
|
382
408
|
// which provides access to its internals.
|
383
409
|
let mut raw_str = Self::rb_string_internal(str);
|
384
410
|
|
411
|
+
// Shared string have their own `ptr` and `len` values, but `aux`
|
412
|
+
// is the id of the parent string so the GC can track this
|
413
|
+
// dependency. The `ptr` will always be an offset from the base
|
414
|
+
// address of the mmap, and `len` will be the length of the mmap
|
415
|
+
// less the offset from the base.
|
416
|
+
if Self::rb_string_is_shared(str) && new_len > 0 {
|
417
|
+
// Calculate how far into the original mmap the shared string
|
418
|
+
// started and update to the equivalent address in the new
|
419
|
+
// one.
|
420
|
+
let substr_ptr = raw_str.as_ref().as_.heap.ptr;
|
421
|
+
let offset = substr_ptr.offset_from(old_ptr);
|
422
|
+
|
423
|
+
raw_str.as_mut().as_.heap.ptr = self.as_mut_ptr().offset(offset);
|
424
|
+
|
425
|
+
let current_len = str.len() as c_long;
|
426
|
+
let new_shared_len = old_cap + current_len;
|
427
|
+
|
428
|
+
self.update_rstring_len(raw_str, new_shared_len);
|
429
|
+
continue;
|
430
|
+
}
|
431
|
+
|
385
432
|
// Update the string to point to the new mmapped file.
|
386
433
|
// We're matching the behavior of Ruby's `str_new_static` function.
|
387
434
|
// See https://github.com/ruby/ruby/blob/e51014f9c05aa65cbf203442d37fef7c12390015/string.c#L1030-L1053
|
388
|
-
|
389
|
-
|
390
|
-
|
435
|
+
//
|
436
|
+
// We deliberately do _NOT_ increment the `capa` field of the
|
437
|
+
// string to match the new `len`. We were initially doing this,
|
438
|
+
// but consistently triggered GCs in the middle of updating the
|
439
|
+
// string pointers, causing a segfault.
|
440
|
+
//
|
441
|
+
// See https://gitlab.com/gitlab-org/ruby/gems/prometheus-client-mmap/-/issues/45
|
442
|
+
raw_str.as_mut().as_.heap.ptr = self.as_mut_ptr();
|
443
|
+
self.update_rstring_len(raw_str, new_len);
|
391
444
|
}
|
392
|
-
|
393
|
-
});
|
445
|
+
}
|
394
446
|
|
395
|
-
// Execute the block.
|
396
|
-
let _: Value = tracker.funcall_with_block("each_value", (), block)?;
|
397
447
|
Ok(())
|
398
448
|
}
|
399
449
|
|
@@ -406,8 +456,7 @@ impl MmapedFile {
|
|
406
456
|
|
407
457
|
// We need the mmapped region to contain at least one byte beyond the
|
408
458
|
// written data to create a NUL- terminated C string. Validate that
|
409
|
-
// new length does not exactly match the length of the mmap
|
410
|
-
// it.
|
459
|
+
// new length does not exactly match or exceed the length of the mmap.
|
411
460
|
while self.capacity() <= used.add_chk(entry_len)? {
|
412
461
|
self.expand_to_fit(rb_self, self.capacity().mul_chk(2)?)?;
|
413
462
|
}
|
@@ -429,6 +478,9 @@ impl MmapedFile {
|
|
429
478
|
}
|
430
479
|
|
431
480
|
if new_cap != self.capacity() {
|
481
|
+
let old_ptr = self.as_mut_ptr();
|
482
|
+
let old_cap = util::cast_chk::<_, c_long>(self.capacity(), "capacity")?;
|
483
|
+
|
432
484
|
// Drop the old mmap.
|
433
485
|
let (mut file, path) = self.take_inner()?.munmap();
|
434
486
|
|
@@ -439,7 +491,7 @@ impl MmapedFile {
|
|
439
491
|
|
440
492
|
self.insert_inner(new_inner)?;
|
441
493
|
|
442
|
-
return self.update_weak_map(rb_self);
|
494
|
+
return self.update_weak_map(rb_self, old_ptr, old_cap);
|
443
495
|
}
|
444
496
|
|
445
497
|
Ok(())
|
@@ -459,14 +511,14 @@ impl MmapedFile {
|
|
459
511
|
match file.seek(SeekFrom::Start(len - 1)) {
|
460
512
|
Ok(_) => {}
|
461
513
|
Err(_) => {
|
462
|
-
return Err(MmapError::
|
514
|
+
return Err(MmapError::with_errno(format!("Can't lseek {}", len - 1)));
|
463
515
|
}
|
464
516
|
}
|
465
517
|
|
466
518
|
match file.write(&[0x0]) {
|
467
519
|
Ok(1) => {}
|
468
520
|
_ => {
|
469
|
-
return Err(MmapError::
|
521
|
+
return Err(MmapError::with_errno(format!(
|
470
522
|
"Can't extend {}",
|
471
523
|
path.display()
|
472
524
|
)));
|
@@ -476,6 +528,15 @@ impl MmapedFile {
|
|
476
528
|
Ok(())
|
477
529
|
}
|
478
530
|
|
531
|
+
fn track_rstring(&self, rb_self: Obj<Self>, str: RString) -> magnus::error::Result<()> {
|
532
|
+
let tracker: Value = rb_self.ivar_get("@weak_obj_tracker")?;
|
533
|
+
|
534
|
+
// Use the string's Id as the key in the `WeakMap`.
|
535
|
+
let key = str.as_raw();
|
536
|
+
let _: Value = tracker.funcall("[]=", (key, str))?;
|
537
|
+
Ok(())
|
538
|
+
}
|
539
|
+
|
479
540
|
/// The total capacity of the underlying mmap.
|
480
541
|
#[inline]
|
481
542
|
fn capacity(&self) -> usize {
|
@@ -489,6 +550,13 @@ impl MmapedFile {
|
|
489
550
|
.map_err(|e| e.into())
|
490
551
|
}
|
491
552
|
|
553
|
+
fn as_mut_ptr(&self) -> *mut c_char {
|
554
|
+
// UNWRAP: This is actually infallible, but we need to
|
555
|
+
// wrap it in a `Result` for use with `inner()`.
|
556
|
+
self.inner(|inner| Ok(inner.as_mut_ptr() as *mut c_char))
|
557
|
+
.unwrap()
|
558
|
+
}
|
559
|
+
|
492
560
|
/// Takes a closure with immutable access to InnerMmap. Will fail if the inner
|
493
561
|
/// object has a mutable borrow or has been dropped.
|
494
562
|
fn inner<F, T>(&self, func: F) -> Result<T>
|
@@ -544,12 +612,40 @@ impl MmapedFile {
|
|
544
612
|
Ok(())
|
545
613
|
}
|
546
614
|
|
615
|
+
/// Check if an RString is shared. Shared string use the same underlying
|
616
|
+
/// storage as their parent, taking an offset from the start. By default
|
617
|
+
/// they must run to the end of the parent string.
|
618
|
+
fn rb_string_is_shared(rb_str: RString) -> bool {
|
619
|
+
// SAFETY: We only hold a reference to the raw object for the duration
|
620
|
+
// of this function, and no Ruby code is called.
|
621
|
+
let flags = unsafe {
|
622
|
+
let raw_str = Self::rb_string_internal(rb_str);
|
623
|
+
raw_str.as_ref().basic.flags
|
624
|
+
};
|
625
|
+
let shared_flags = STR_SHARED | STR_NOEMBED;
|
626
|
+
|
627
|
+
flags & shared_flags == shared_flags
|
628
|
+
}
|
629
|
+
|
547
630
|
/// Convert `magnus::RString` into the raw binding used by `rb_sys::RString`.
|
548
631
|
/// We need this to manually change the pointer and length values for strings
|
549
632
|
/// when moving the mmap to a new file.
|
633
|
+
///
|
634
|
+
/// SAFETY: Calling Ruby code while the returned object is held may result
|
635
|
+
/// in it being mutated or dropped.
|
550
636
|
unsafe fn rb_string_internal(rb_str: RString) -> NonNull<rb_sys::RString> {
|
551
637
|
mem::transmute::<RString, NonNull<rb_sys::RString>>(rb_str)
|
552
638
|
}
|
639
|
+
|
640
|
+
#[cfg(ruby_lte_3_2)]
|
641
|
+
unsafe fn update_rstring_len(&self, mut raw_str: NonNull<rb_sys::RString>, new_len: c_long) {
|
642
|
+
raw_str.as_mut().as_.heap.len = new_len;
|
643
|
+
}
|
644
|
+
|
645
|
+
#[cfg(ruby_gte_3_3)]
|
646
|
+
unsafe fn update_rstring_len(&self, mut raw_str: NonNull<rb_sys::RString>, new_len: c_long) {
|
647
|
+
raw_str.as_mut().len = new_len;
|
648
|
+
}
|
553
649
|
}
|
554
650
|
|
555
651
|
#[cfg(test)]
|
@@ -666,18 +762,100 @@ mod test {
|
|
666
762
|
let ruby = magnus::Ruby::get().unwrap();
|
667
763
|
crate::init(&ruby).unwrap();
|
668
764
|
|
765
|
+
fn assert_internals(
|
766
|
+
obj: Obj<MmapedFile>,
|
767
|
+
parent_id: c_ulong,
|
768
|
+
child_id: c_ulong,
|
769
|
+
unshared_id: c_ulong,
|
770
|
+
) {
|
771
|
+
let rs_self = &*obj;
|
772
|
+
let tracker: Value = obj.ivar_get("@weak_obj_tracker").unwrap();
|
773
|
+
|
774
|
+
let mmap_ptr = rs_self.as_mut_ptr();
|
775
|
+
let mmap_len = rs_self.capacity();
|
776
|
+
|
777
|
+
let mut parent_checked = false;
|
778
|
+
let mut child_checked = false;
|
779
|
+
|
780
|
+
for val in tracker.enumeratorize("each_value", ()) {
|
781
|
+
let rb_string = val.unwrap();
|
782
|
+
let str = RString::from_value(rb_string).unwrap();
|
783
|
+
|
784
|
+
unsafe {
|
785
|
+
let raw_str = MmapedFile::rb_string_internal(str);
|
786
|
+
if str.as_raw() == child_id {
|
787
|
+
assert_eq!(parent_id, raw_str.as_ref().as_.heap.aux.shared);
|
788
|
+
|
789
|
+
let child_offset = mmap_len as isize - str.len() as isize;
|
790
|
+
assert_eq!(mmap_ptr.offset(child_offset), raw_str.as_ref().as_.heap.ptr);
|
791
|
+
|
792
|
+
child_checked = true;
|
793
|
+
} else if str.as_raw() == parent_id {
|
794
|
+
assert_eq!(parent_id, str.as_raw());
|
795
|
+
|
796
|
+
assert_eq!(mmap_ptr, raw_str.as_ref().as_.heap.ptr);
|
797
|
+
assert_eq!(mmap_len as c_long, str.len() as c_long);
|
798
|
+
assert!(raw_str.as_ref().basic.flags & (STR_SHARED | STR_NOEMBED) > 0);
|
799
|
+
assert!(str.is_frozen());
|
800
|
+
|
801
|
+
parent_checked = true;
|
802
|
+
} else if str.as_raw() == unshared_id {
|
803
|
+
panic!("tracking unshared string");
|
804
|
+
} else {
|
805
|
+
panic!("unknown string");
|
806
|
+
}
|
807
|
+
}
|
808
|
+
}
|
809
|
+
assert!(parent_checked && child_checked);
|
810
|
+
}
|
811
|
+
|
669
812
|
let obj = create_obj();
|
670
813
|
let _ = populate_entries(&obj);
|
671
814
|
|
672
815
|
let rs_self = &*obj;
|
673
816
|
|
674
|
-
|
675
|
-
let
|
676
|
-
|
677
|
-
|
817
|
+
// Create a string containing the full mmap.
|
818
|
+
let parent_str = rs_self.str(obj).unwrap();
|
819
|
+
let parent_id = parent_str.as_raw();
|
820
|
+
|
821
|
+
// Ruby's shared strings are only created when they go to the end of
|
822
|
+
// original string.
|
823
|
+
let len = rs_self.inner(|inner| Ok(inner.len())).unwrap();
|
824
|
+
let shareable_range = Range::new(1, len - 1, false).unwrap().as_value();
|
825
|
+
|
826
|
+
// This string should re-use the parent's buffer with an offset and have
|
827
|
+
// the parent's id in `as.heap.aux.shared`
|
828
|
+
let child_str = rs_self._slice(obj, parent_str, &[shareable_range]).unwrap();
|
829
|
+
let child_id = child_str.as_raw();
|
830
|
+
|
831
|
+
// A range that does not reach the end of the parent will not be shared.
|
832
|
+
assert!(len > 4);
|
833
|
+
let unshareable_range = Range::new(0, 4, false).unwrap().as_value();
|
834
|
+
|
835
|
+
// This string should NOT be tracked, it should own its own buffer.
|
836
|
+
let unshared_str = rs_self
|
837
|
+
._slice(obj, parent_str, &[unshareable_range])
|
838
|
+
.unwrap();
|
839
|
+
let unshared_id = unshared_str.as_raw();
|
840
|
+
assert!(!MmapedFile::rb_string_is_shared(unshared_str));
|
841
|
+
|
842
|
+
assert_internals(obj, parent_id, child_id, unshared_id);
|
843
|
+
|
844
|
+
let orig_ptr = rs_self.as_mut_ptr();
|
845
|
+
// Expand a bunch to ensure we remap
|
846
|
+
for _ in 0..16 {
|
847
|
+
rs_self.expand_to_fit(obj, rs_self.capacity() * 2).unwrap();
|
848
|
+
}
|
849
|
+
let new_ptr = rs_self.as_mut_ptr();
|
850
|
+
assert!(orig_ptr != new_ptr);
|
678
851
|
|
679
852
|
// If we haven't updated the pointer to the newly remapped file this will segfault.
|
680
|
-
let _: Value = eval!("puts
|
853
|
+
let _: Value = eval!("puts parent", parent = parent_str).unwrap();
|
854
|
+
let _: Value = eval!("puts child", child = child_str).unwrap();
|
855
|
+
let _: Value = eval!("puts unshared", unshared = unshared_str).unwrap();
|
856
|
+
|
857
|
+
// Confirm that tracked strings are still valid.
|
858
|
+
assert_internals(obj, parent_id, child_id, unshared_id);
|
681
859
|
}
|
682
860
|
|
683
861
|
#[test]
|
@@ -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
|
@@ -26,49 +26,16 @@ module Prometheus
|
|
26
26
|
Helper::MetricsRepresentation.to_text(metrics)
|
27
27
|
end
|
28
28
|
|
29
|
-
def marshal_multiprocess(path = Prometheus::Client.configuration.multiprocess_files_dir
|
29
|
+
def marshal_multiprocess(path = Prometheus::Client.configuration.multiprocess_files_dir)
|
30
30
|
file_list = Dir.glob(File.join(path, '*.db')).sort
|
31
31
|
.map {|f| Helper::PlainFile.new(f) }
|
32
32
|
.map {|f| [f.filepath, f.multiprocess_mode.to_sym, f.type.to_sym, f.pid] }
|
33
33
|
|
34
|
-
if use_rust && rust_impl_available?
|
35
34
|
FastMmapedFileRs.to_metrics(file_list.to_a)
|
36
|
-
else
|
37
|
-
FastMmapedFile.to_metrics(file_list.to_a)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def rust_impl_available?
|
42
|
-
return @rust_available unless @rust_available.nil?
|
43
|
-
|
44
|
-
check_for_rust
|
45
35
|
end
|
46
36
|
|
47
37
|
private
|
48
38
|
|
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
39
|
def load_metrics(path)
|
73
40
|
metrics = {}
|
74
41
|
Dir.glob(File.join(path, '*.db')).sort.each do |f|
|
@@ -4,15 +4,15 @@ require 'prometheus/client/helper/file_locker'
|
|
4
4
|
# load precompiled extension if available
|
5
5
|
begin
|
6
6
|
ruby_version = /(\d+\.\d+)/.match(RUBY_VERSION)
|
7
|
-
require_relative "../../../#{ruby_version}/
|
7
|
+
require_relative "../../../#{ruby_version}/fast_mmaped_file_rs"
|
8
8
|
rescue LoadError
|
9
|
-
require '
|
9
|
+
require 'fast_mmaped_file_rs'
|
10
10
|
end
|
11
11
|
|
12
12
|
module Prometheus
|
13
13
|
module Client
|
14
14
|
module Helper
|
15
|
-
class MmapedFile <
|
15
|
+
class MmapedFile < FastMmapedFileRs
|
16
16
|
include EntryParser
|
17
17
|
|
18
18
|
attr_reader :filepath, :size
|
@@ -5,8 +5,7 @@ module Prometheus
|
|
5
5
|
# LabelSetValidator ensures that all used label sets comply with the
|
6
6
|
# Prometheus specification.
|
7
7
|
class LabelSetValidator
|
8
|
-
|
9
|
-
RESERVED_LABELS = [:job, :instance].freeze
|
8
|
+
RESERVED_LABELS = [].freeze
|
10
9
|
|
11
10
|
class LabelSetError < StandardError; end
|
12
11
|
class InvalidLabelSetError < LabelSetError; end
|