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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.tool-versions +1 -0
  3. data/README.md +32 -17
  4. data/ext/fast_mmaped_file_rs/Cargo.toml +14 -9
  5. data/ext/fast_mmaped_file_rs/build.rs +5 -0
  6. data/ext/fast_mmaped_file_rs/extconf.rb +1 -3
  7. data/ext/fast_mmaped_file_rs/src/error.rs +2 -2
  8. data/ext/fast_mmaped_file_rs/src/file_entry.rs +222 -17
  9. data/ext/fast_mmaped_file_rs/src/file_info.rs +56 -6
  10. data/ext/fast_mmaped_file_rs/src/lib.rs +0 -1
  11. data/ext/fast_mmaped_file_rs/src/map.rs +12 -12
  12. data/ext/fast_mmaped_file_rs/src/mmap/inner.rs +6 -10
  13. data/ext/fast_mmaped_file_rs/src/mmap.rs +225 -47
  14. data/ext/fast_mmaped_file_rs/src/raw_entry.rs +1 -1
  15. data/ext/fast_mmaped_file_rs/src/testhelper.rs +1 -1
  16. data/lib/3.1/fast_mmaped_file_rs.so +0 -0
  17. data/lib/3.2/fast_mmaped_file_rs.so +0 -0
  18. data/lib/3.3/fast_mmaped_file_rs.so +0 -0
  19. data/lib/3.4/fast_mmaped_file_rs.so +0 -0
  20. data/lib/prometheus/client/formats/text.rb +1 -34
  21. data/lib/prometheus/client/helper/mmaped_file.rb +3 -3
  22. data/lib/prometheus/client/label_set_validator.rb +1 -2
  23. data/lib/prometheus/client/support/puma.rb +44 -0
  24. data/lib/prometheus/client/version.rb +1 -1
  25. metadata +62 -61
  26. data/ext/fast_mmaped_file/extconf.rb +0 -30
  27. data/ext/fast_mmaped_file/fast_mmaped_file.c +0 -122
  28. data/ext/fast_mmaped_file/file_format.c +0 -5
  29. data/ext/fast_mmaped_file/file_format.h +0 -11
  30. data/ext/fast_mmaped_file/file_parsing.c +0 -195
  31. data/ext/fast_mmaped_file/file_parsing.h +0 -27
  32. data/ext/fast_mmaped_file/file_reading.c +0 -102
  33. data/ext/fast_mmaped_file/file_reading.h +0 -30
  34. data/ext/fast_mmaped_file/globals.h +0 -14
  35. data/ext/fast_mmaped_file/mmap.c +0 -427
  36. data/ext/fast_mmaped_file/mmap.h +0 -61
  37. data/ext/fast_mmaped_file/rendering.c +0 -199
  38. data/ext/fast_mmaped_file/rendering.h +0 -8
  39. data/ext/fast_mmaped_file/utils.c +0 -56
  40. data/ext/fast_mmaped_file/utils.h +0 -22
  41. data/ext/fast_mmaped_file/value_access.c +0 -242
  42. data/ext/fast_mmaped_file/value_access.h +0 -15
  43. data/ext/fast_mmaped_file_rs/.cargo/config.toml +0 -23
  44. data/ext/fast_mmaped_file_rs/Cargo.lock +0 -790
  45. data/ext/fast_mmaped_file_rs/src/parser.rs +0 -346
  46. data/lib/2.7/fast_mmaped_file.so +0 -0
  47. data/lib/2.7/fast_mmaped_file_rs.so +0 -0
  48. data/lib/3.0/fast_mmaped_file.so +0 -0
  49. data/lib/3.0/fast_mmaped_file_rs.so +0 -0
  50. data/lib/3.1/fast_mmaped_file.so +0 -0
  51. data/lib/3.2/fast_mmaped_file.so +0 -0
  52. data/vendor/c/hashmap/.gitignore +0 -52
  53. data/vendor/c/hashmap/LICENSE +0 -21
  54. data/vendor/c/hashmap/README.md +0 -90
  55. data/vendor/c/hashmap/_config.yml +0 -1
  56. data/vendor/c/hashmap/src/hashmap.c +0 -692
  57. data/vendor/c/hashmap/src/hashmap.h +0 -267
  58. data/vendor/c/hashmap/test/Makefile +0 -22
  59. data/vendor/c/hashmap/test/hashmap_test.c +0 -608
  60. data/vendor/c/jsmn/.travis.yml +0 -4
  61. data/vendor/c/jsmn/LICENSE +0 -20
  62. data/vendor/c/jsmn/Makefile +0 -41
  63. data/vendor/c/jsmn/README.md +0 -168
  64. data/vendor/c/jsmn/example/jsondump.c +0 -126
  65. data/vendor/c/jsmn/example/simple.c +0 -76
  66. data/vendor/c/jsmn/jsmn.c +0 -314
  67. data/vendor/c/jsmn/jsmn.h +0 -76
  68. data/vendor/c/jsmn/library.json +0 -16
  69. data/vendor/c/jsmn/test/test.h +0 -27
  70. data/vendor/c/jsmn/test/tests.c +0 -407
  71. 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::{StaticSymbol, Symbol};
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_: StaticSymbol::new("gauge"),
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_: StaticSymbol::new("gauge"),
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_: StaticSymbol::new("gauge"),
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_: StaticSymbol::new("gauge"),
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_: StaticSymbol::new("gauge"),
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_: StaticSymbol::new("gauge"),
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_: StaticSymbol::new("gauge"),
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_: StaticSymbol::new("gauge"),
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_: StaticSymbol::new("gauge"),
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_: StaticSymbol::new("gauge"),
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_: StaticSymbol::new("gauge"),
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::{block::Proc, eval, scan_args, Error, Integer, RArray, RClass, RHash, RString, Value};
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 implemenation would trigger a GC cycle via `rb_gc_force_recycle`
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 = (*rb_self).str(rb_self)?;
159
- let res = str.funcall("[]", args);
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
- res
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.update_weak_map(rb_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
- let tracker: Value = rb_self.ivar_get("@weak_obj_tracker")?;
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
- // Use the string's Id as the key in the `WeakMap`.
351
- let key = str.as_raw();
352
- let _: Value = tracker.funcall("[]=", (key, str))?;
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 will be invalidated. We need to iterate over them using and
359
- /// update their heap pointers to the newly allocated memory region.
360
- fn update_weak_map(&self, rb_self: Obj<Self>) -> magnus::error::Result<()> {
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 (new_ptr, new_len) = (*rb_self).inner(|inner| {
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
- // Allocate a block with a closure that updates the string details.
372
- let block = Proc::from_fn(move |args, _block| {
373
- let val = args
374
- .get(0)
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
- raw_str.as_mut().as_.heap.ptr = new_ptr as _;
389
- raw_str.as_mut().as_.heap.len = new_len;
390
- raw_str.as_mut().as_.heap.aux.capa = new_len;
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
- Ok(())
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 or exceed
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::WithErrno(format!("Can't lseek {}", len - 1)));
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::WithErrno(format!(
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
- let value_range = Range::new(HEADER_SIZE, 24, false).unwrap().as_value();
675
- let value_slice = MmapedFile::slice(obj, &[value_range]).unwrap();
676
-
677
- rs_self.expand_to_fit(obj, rs_self.capacity() * 2).unwrap();
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 str", str = value_slice).unwrap();
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,
@@ -73,7 +73,7 @@ pub fn entries_to_db(entries: &[&'static str], values: &[f64], header: Option<u3
73
73
  };
74
74
 
75
75
  out.extend(used.to_ne_bytes());
76
- out.extend(&[0x0u8; 4]); // Padding.
76
+ out.extend([0x0u8; 4]); // Padding.
77
77
  out.extend(entry_bytes);
78
78
 
79
79
  out
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, use_rust: false)
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}/fast_mmaped_file"
7
+ require_relative "../../../#{ruby_version}/fast_mmaped_file_rs"
8
8
  rescue LoadError
9
- require 'fast_mmaped_file'
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 < FastMmapedFile
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
- # TODO: we might allow setting :instance in the future
9
- RESERVED_LABELS = [:job, :instance].freeze
8
+ RESERVED_LABELS = [].freeze
10
9
 
11
10
  class LabelSetError < StandardError; end
12
11
  class InvalidLabelSetError < LabelSetError; end