prometheus-client-mmap 0.19.1 → 0.20.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +14 -0
- data/ext/fast_mmaped_file/extconf.rb +1 -1
- data/ext/fast_mmaped_file_rs/.cargo/config.toml +23 -0
- data/ext/fast_mmaped_file_rs/Cargo.lock +790 -0
- data/ext/fast_mmaped_file_rs/Cargo.toml +30 -0
- data/ext/fast_mmaped_file_rs/README.md +52 -0
- data/ext/fast_mmaped_file_rs/extconf.rb +30 -0
- data/ext/fast_mmaped_file_rs/src/error.rs +174 -0
- data/ext/fast_mmaped_file_rs/src/file_entry.rs +579 -0
- data/ext/fast_mmaped_file_rs/src/file_info.rs +190 -0
- data/ext/fast_mmaped_file_rs/src/lib.rs +79 -0
- data/ext/fast_mmaped_file_rs/src/macros.rs +14 -0
- data/ext/fast_mmaped_file_rs/src/map.rs +492 -0
- data/ext/fast_mmaped_file_rs/src/mmap.rs +151 -0
- data/ext/fast_mmaped_file_rs/src/parser.rs +346 -0
- data/ext/fast_mmaped_file_rs/src/raw_entry.rs +473 -0
- data/ext/fast_mmaped_file_rs/src/testhelper.rs +222 -0
- data/ext/fast_mmaped_file_rs/src/util.rs +121 -0
- data/lib/prometheus/client/configuration.rb +2 -1
- data/lib/prometheus/client/formats/text.rb +26 -2
- data/lib/prometheus/client/page_size.rb +17 -0
- data/lib/prometheus/client/version.rb +1 -1
- data/vendor/c/hashmap/.gitignore +52 -0
- data/vendor/c/jsmn/.travis.yml +4 -0
- metadata +37 -3
@@ -0,0 +1,30 @@
|
|
1
|
+
[package]
|
2
|
+
name = "fast_mmaped_file_rs"
|
3
|
+
version = "0.1.0"
|
4
|
+
edition = "2021"
|
5
|
+
|
6
|
+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
7
|
+
|
8
|
+
[dependencies]
|
9
|
+
hashbrown = "0.13"
|
10
|
+
libc = "0.2"
|
11
|
+
magnus = { git = "https://github.com/matsadler/magnus", branch = "main", features = ["rb-sys"] }
|
12
|
+
memmap2 = "0.5"
|
13
|
+
# v0.26 cannot be built on CentOS 7 https://github.com/nix-rust/nix/issues/1972
|
14
|
+
nix = { version = "0.25", features = ["mman"] } # mman used for MsFlags
|
15
|
+
rb-sys = "0.9"
|
16
|
+
smallvec = "1.10"
|
17
|
+
thiserror = "1.0"
|
18
|
+
|
19
|
+
[dev-dependencies]
|
20
|
+
bstr = "1.4"
|
21
|
+
indoc = "2.0"
|
22
|
+
# We need the `embed` feature to run tests, but this triggers failures when building as a Gem.
|
23
|
+
magnus = { git = "https://github.com/matsadler/magnus", branch = "main", features = ["rb-sys","embed"] }
|
24
|
+
rand = "0.8"
|
25
|
+
sha2 = "0.10"
|
26
|
+
tempfile = "3.5"
|
27
|
+
|
28
|
+
[lib]
|
29
|
+
# Integration tests won't work if crate is only `cdylib`.
|
30
|
+
crate-type = ["cdylib","lib"]
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Testing
|
2
|
+
|
3
|
+
## Running Tests
|
4
|
+
|
5
|
+
Use [cargo nextest](https://nexte.st/) to execute the Rust unit tests.
|
6
|
+
|
7
|
+
```sh
|
8
|
+
$ cargo nextest run
|
9
|
+
```
|
10
|
+
|
11
|
+
## Why not use 'cargo test'?
|
12
|
+
|
13
|
+
We need to embed Ruby into the test binary to access Ruby types. This requires
|
14
|
+
us to run `magnus::embed::init()` no more than once before calling Ruby.
|
15
|
+
See [the magnus docs](https://docs.rs/magnus/latest/magnus/embed/fn.init.html)
|
16
|
+
for more details.
|
17
|
+
|
18
|
+
If we try to create separate `#[test]` functions that call `init()` these will
|
19
|
+
conflict, as Cargo runs tests in parallel using a single process with separate
|
20
|
+
threads. Running `cargo test` will result in errors like:
|
21
|
+
|
22
|
+
```
|
23
|
+
---- file_info::test::with_ruby stdout ----
|
24
|
+
thread 'file_info::test::with_ruby' panicked at 'Ruby already initialized'
|
25
|
+
```
|
26
|
+
|
27
|
+
The simplest workaround for this is to avoid using `cargo test` to run unit
|
28
|
+
tests. [nextest](https://nexte.st/) is an alternate test harness that runs each
|
29
|
+
test as its own process, enabling each test to intitialize Ruby without
|
30
|
+
conflict.
|
31
|
+
|
32
|
+
## 'symbol not found' errors when running tests
|
33
|
+
|
34
|
+
If you see errors like the following when running tests:
|
35
|
+
|
36
|
+
```
|
37
|
+
Caused by:
|
38
|
+
for `fast_mmaped_file_rs`, command `/Users/myuser/prometheus-client-mmap/ext/fast_mmaped_file_rs/target/debug/deps/fast_mmaped_file_rs-c81ccc96a6484e04 --list --format terse` exited with signal 6 (SIGABRT)
|
39
|
+
--- stdout:
|
40
|
+
|
41
|
+
--- stderr:
|
42
|
+
dyld[17861]: symbol not found in flat namespace '_rb_cArray'
|
43
|
+
```
|
44
|
+
|
45
|
+
Clearing the build cache will resolve the problem.
|
46
|
+
|
47
|
+
```sh
|
48
|
+
$ cargo clean
|
49
|
+
```
|
50
|
+
|
51
|
+
This is probably due to separate features being used with `magnus` in
|
52
|
+
development builds.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "mkmf"
|
2
|
+
require "rb_sys/mkmf"
|
3
|
+
|
4
|
+
if find_executable('rustc')
|
5
|
+
create_rust_makefile("fast_mmaped_file_rs") do |r|
|
6
|
+
r.auto_install_rust_toolchain = false
|
7
|
+
|
8
|
+
if enable_config('fail-on-warning')
|
9
|
+
r.extra_rustflags = ["-Dwarnings"]
|
10
|
+
end
|
11
|
+
|
12
|
+
if enable_config('debug')
|
13
|
+
r.profile = :dev
|
14
|
+
end
|
15
|
+
|
16
|
+
if enable_config('address-sanitizer')
|
17
|
+
r.extra_rustflags = ["-Zsanitizer=address"]
|
18
|
+
end
|
19
|
+
|
20
|
+
# `rb_sys/mkmf` passes all arguments after `--` directly to `cargo rustc`.
|
21
|
+
# We use this awful hack to keep compatibility with existing flags used by
|
22
|
+
# the C implementation.
|
23
|
+
trimmed_argv = ARGV.take_while { |arg| arg != "--" }
|
24
|
+
ARGV = trimmed_argv
|
25
|
+
end
|
26
|
+
else
|
27
|
+
puts 'rustc not found, skipping Rust extension.'
|
28
|
+
|
29
|
+
File.write('Makefile', dummy_makefile($srcdir).join(''))
|
30
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
use magnus::{exception, Ruby};
|
2
|
+
use std::any;
|
3
|
+
use std::fmt::Display;
|
4
|
+
use std::io;
|
5
|
+
use std::path::Path;
|
6
|
+
use thiserror::Error;
|
7
|
+
|
8
|
+
use crate::util;
|
9
|
+
use crate::PROM_EPARSING_ERROR;
|
10
|
+
|
11
|
+
/// A lightweight representation of Ruby ExceptionClasses.
|
12
|
+
#[derive(PartialEq, Clone, Copy, Debug)]
|
13
|
+
pub enum RubyError {
|
14
|
+
Arg,
|
15
|
+
Encoding,
|
16
|
+
Frozen,
|
17
|
+
Index,
|
18
|
+
Io,
|
19
|
+
NoMem,
|
20
|
+
PromParsing,
|
21
|
+
Runtime,
|
22
|
+
Type,
|
23
|
+
}
|
24
|
+
|
25
|
+
impl From<RubyError> for magnus::ExceptionClass {
|
26
|
+
fn from(err: RubyError) -> magnus::ExceptionClass {
|
27
|
+
match err {
|
28
|
+
RubyError::Arg => exception::arg_error(),
|
29
|
+
RubyError::Encoding => exception::encoding_error(),
|
30
|
+
RubyError::Frozen => exception::frozen_error(),
|
31
|
+
RubyError::Index => exception::index_error(),
|
32
|
+
RubyError::Io => exception::io_error(),
|
33
|
+
RubyError::NoMem => exception::no_mem_error(),
|
34
|
+
RubyError::Runtime => exception::runtime_error(),
|
35
|
+
RubyError::PromParsing => {
|
36
|
+
// UNWRAP: this will panic if called outside of a Ruby thread.
|
37
|
+
let ruby = Ruby::get().unwrap();
|
38
|
+
ruby.get_inner(&PROM_EPARSING_ERROR)
|
39
|
+
}
|
40
|
+
RubyError::Type => exception::type_error(),
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
/// Errors returned internally within the crate. Methods called directly by Ruby return
|
46
|
+
/// `magnus::error::Error` as do functions that interact heavily with Ruby. This can be
|
47
|
+
/// converted into a `magnus::error::Error` at the boundary between Rust and Ruby.
|
48
|
+
#[derive(PartialEq, Error, Debug)]
|
49
|
+
pub enum MmapError {
|
50
|
+
/// A read or write was made while another thread had mutable access to the mmap.
|
51
|
+
#[error("read/write operation attempted while mmap was being written to")]
|
52
|
+
ConcurrentAccess,
|
53
|
+
/// An error message used to exactly match the messages returned by the C
|
54
|
+
/// implementation.
|
55
|
+
#[error("{0}")]
|
56
|
+
Legacy(String, RubyError),
|
57
|
+
/// A String had invalid UTF-8 sequences.
|
58
|
+
#[error("{0}")]
|
59
|
+
Encoding(String),
|
60
|
+
/// A failed attempt to cast an integer from one type to another.
|
61
|
+
#[error("failed to cast {object_name} {value} from {from} to {to}")]
|
62
|
+
FailedCast {
|
63
|
+
from: &'static str,
|
64
|
+
to: &'static str,
|
65
|
+
value: String,
|
66
|
+
object_name: String,
|
67
|
+
},
|
68
|
+
/// The mmap was frozen when a mutable operation was attempted.
|
69
|
+
#[error("mmap")]
|
70
|
+
Frozen,
|
71
|
+
/// An io operation failed.
|
72
|
+
#[error("failed to {operation} path '{path}': {err}")]
|
73
|
+
Io {
|
74
|
+
operation: String,
|
75
|
+
path: String,
|
76
|
+
err: String,
|
77
|
+
},
|
78
|
+
#[error("string length gt {}", i32::MAX)]
|
79
|
+
KeyLength,
|
80
|
+
/// Failed to allocate memory.
|
81
|
+
#[error("Couldn't allocate for {0} memory")]
|
82
|
+
OutOfMemory(usize),
|
83
|
+
/// A memory operation fell outside of the containers bounds.
|
84
|
+
#[error("offset {index} out of bounds of len {len}")]
|
85
|
+
OutOfBounds { index: String, len: String },
|
86
|
+
/// A numeric operation overflowed.
|
87
|
+
#[error("overflow when {op} {value} and {added} of type {ty}")]
|
88
|
+
Overflow {
|
89
|
+
value: String,
|
90
|
+
added: String,
|
91
|
+
op: String,
|
92
|
+
ty: &'static str,
|
93
|
+
},
|
94
|
+
/// A miscellaneous error.
|
95
|
+
#[error("{0}")]
|
96
|
+
Other(String),
|
97
|
+
/// A failure when parsing a `.db` file containing Prometheus metrics.
|
98
|
+
#[error("{0}")]
|
99
|
+
PromParsing(String),
|
100
|
+
/// No mmap open.
|
101
|
+
#[error("unmapped file")]
|
102
|
+
UnmappedFile,
|
103
|
+
/// A custom error message with `strerror(3)` appended.
|
104
|
+
#[error("{0}")]
|
105
|
+
WithErrno(String),
|
106
|
+
}
|
107
|
+
|
108
|
+
impl MmapError {
|
109
|
+
pub fn legacy<T: Into<String>>(msg: T, ruby_err: RubyError) -> Self {
|
110
|
+
MmapError::Legacy(msg.into(), ruby_err)
|
111
|
+
}
|
112
|
+
|
113
|
+
pub fn failed_cast<T: Display, U>(value: T, object_name: &str) -> Self {
|
114
|
+
MmapError::FailedCast {
|
115
|
+
from: any::type_name::<T>(),
|
116
|
+
to: any::type_name::<U>(),
|
117
|
+
value: value.to_string(),
|
118
|
+
object_name: object_name.to_string(),
|
119
|
+
}
|
120
|
+
}
|
121
|
+
pub fn io(operation: &str, path: &Path, err: io::Error) -> Self {
|
122
|
+
MmapError::Io {
|
123
|
+
operation: operation.to_string(),
|
124
|
+
path: path.display().to_string(),
|
125
|
+
err: err.to_string(),
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
pub fn overflowed<T: Display>(value: T, added: T, op: &str) -> Self {
|
130
|
+
MmapError::Overflow {
|
131
|
+
value: value.to_string(),
|
132
|
+
added: added.to_string(),
|
133
|
+
op: op.to_string(),
|
134
|
+
ty: any::type_name::<T>(),
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
pub fn out_of_bounds<T: Display>(index: T, len: T) -> Self {
|
139
|
+
MmapError::OutOfBounds {
|
140
|
+
index: index.to_string(),
|
141
|
+
len: len.to_string(),
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
pub fn with_errno<T: Into<String>>(msg: T) -> Self {
|
146
|
+
let strerror = util::strerror(util::errno());
|
147
|
+
MmapError::WithErrno(format!("{}: ({strerror})", msg.into()))
|
148
|
+
}
|
149
|
+
|
150
|
+
pub fn ruby_err(&self) -> RubyError {
|
151
|
+
match self {
|
152
|
+
MmapError::ConcurrentAccess => RubyError::Arg,
|
153
|
+
MmapError::Legacy(_, e) => *e,
|
154
|
+
MmapError::Encoding(_) => RubyError::Encoding,
|
155
|
+
MmapError::Io { .. } => RubyError::Io,
|
156
|
+
MmapError::FailedCast { .. } => RubyError::Arg,
|
157
|
+
MmapError::Frozen => RubyError::Frozen,
|
158
|
+
MmapError::KeyLength => RubyError::Arg,
|
159
|
+
MmapError::Overflow { .. } => RubyError::Arg,
|
160
|
+
MmapError::OutOfBounds { .. } => RubyError::Index,
|
161
|
+
MmapError::OutOfMemory { .. } => RubyError::NoMem,
|
162
|
+
MmapError::Other(_) => RubyError::Arg,
|
163
|
+
MmapError::PromParsing(_) => RubyError::PromParsing,
|
164
|
+
MmapError::UnmappedFile => RubyError::Io,
|
165
|
+
MmapError::WithErrno(_) => RubyError::Io,
|
166
|
+
}
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
impl From<MmapError> for magnus::error::Error {
|
171
|
+
fn from(err: MmapError) -> magnus::error::Error {
|
172
|
+
magnus::error::Error::new(err.ruby_err().into(), err.to_string())
|
173
|
+
}
|
174
|
+
}
|