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.
@@ -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
+ }