prometheus-client-mmap 0.23.1-arm64-darwin → 0.24.3-arm64-darwin

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8ea951275144fc292944ab87ce2d6e4161bb55e3c191ba91b5a43b522ac3d7b
4
- data.tar.gz: 7fc8439f76c6811bfae84116bf27d5832dbf54ed1a2982288fa32f82f505219a
3
+ metadata.gz: '0833beb2c5dd6aae40621e64e628857ee3611ad967609cf306db43bf64d1dd22'
4
+ data.tar.gz: ee565934732cac569631b4431ff131bc67583b0a582628fad2e9ea97bedfc422
5
5
  SHA512:
6
- metadata.gz: b294089810b766a30906d726a69752c31a9df0a3731cbe1951462bbbe5dd9f18b3847ab83caab127b48d28c609130574ffed01f987a1ad2c735d00344d5b8803
7
- data.tar.gz: 9934350ad838fcba346876230e59e1b25065e3ea8401086681b045f7268798bd986d6a0558065174476084406e5ea4dc3a24e77edcee33fd967d127b0973446b
6
+ metadata.gz: c5a27d9097d1528816b40fefd49df207402cae37cc1ec1d2e15f33819d5dd00fb1013750035b6d7b29b375dbe7279be59da028d18780375617bd08c75b2caed4
7
+ data.tar.gz: e50b65659e94a39528c9e432411bc7260b8a8e8e5973adb34e0429ecc113eed58b3e613a408a2c40c6bcc537ebd9b2e9368eaa9711facc03a0a59baf29e3d248
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::{StaticSymbol, Symbol};
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_: StaticSymbol,
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_: StaticSymbol::new("gauge"),
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_: StaticSymbol::new(case.metric_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, StaticSymbol, Symbol, Value};
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_: StaticSymbol,
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_ = StaticSymbol::from_value(params[2])
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, StaticSymbol, Symbol};
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_: StaticSymbol::new("max"),
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::{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<()> {
@@ -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::{block::Proc, eval, scan_args, Error, Integer, RArray, RClass, RHash, RString, Value};
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 implemenation would trigger a GC cycle via `rb_gc_force_recycle`
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
- let str = (*rb_self).str(rb_self)?;
160
- let res = str.funcall("[]", args);
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
- res
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.update_weak_map(rb_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
- let tracker: Value = rb_self.ivar_get("@weak_obj_tracker")?;
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
- // Use the string's Id as the key in the `WeakMap`.
352
- let key = str.as_raw();
353
- let _: Value = tracker.funcall("[]=", (key, str))?;
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(&self, rb_self: Obj<Self>) -> magnus::error::Result<()> {
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 (new_ptr, new_len) = (*rb_self).inner(|inner| {
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
- Ok((new_ptr, new_len))
370
- })?;
371
-
372
- // Allocate a block with a closure that updates the string details.
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
- raw_str.as_mut().as_.heap.ptr = new_ptr as _;
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
- Ok(())
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
- 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();
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 str", str = value_slice).unwrap();
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]>,
@@ -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
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
- class MmapedFile < FastMmapedFile
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
@@ -1,5 +1,5 @@
1
1
  module Prometheus
2
2
  module Client
3
- VERSION = '0.23.1'.freeze
3
+ VERSION = '0.24.3'.freeze
4
4
  end
5
5
  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.23.1
4
+ version: 0.24.3
5
5
  platform: arm64-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-05-02 00:00:00.000000000 Z
14
+ date: 2023-05-20 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