prometheus-client-mmap 0.20.3-arm64-darwin
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +253 -0
- data/ext/fast_mmaped_file/extconf.rb +30 -0
- data/ext/fast_mmaped_file/fast_mmaped_file.c +122 -0
- data/ext/fast_mmaped_file/file_format.c +5 -0
- data/ext/fast_mmaped_file/file_format.h +11 -0
- data/ext/fast_mmaped_file/file_parsing.c +195 -0
- data/ext/fast_mmaped_file/file_parsing.h +27 -0
- data/ext/fast_mmaped_file/file_reading.c +102 -0
- data/ext/fast_mmaped_file/file_reading.h +30 -0
- data/ext/fast_mmaped_file/globals.h +14 -0
- data/ext/fast_mmaped_file/mmap.c +427 -0
- data/ext/fast_mmaped_file/mmap.h +61 -0
- data/ext/fast_mmaped_file/rendering.c +199 -0
- data/ext/fast_mmaped_file/rendering.h +8 -0
- data/ext/fast_mmaped_file/utils.c +56 -0
- data/ext/fast_mmaped_file/utils.h +22 -0
- data/ext/fast_mmaped_file/value_access.c +242 -0
- data/ext/fast_mmaped_file/value_access.h +15 -0
- 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/2.7/fast_mmaped_file.bundle +0 -0
- data/lib/2.7/fast_mmaped_file_rs.bundle +0 -0
- data/lib/3.0/fast_mmaped_file.bundle +0 -0
- data/lib/3.0/fast_mmaped_file_rs.bundle +0 -0
- data/lib/3.1/fast_mmaped_file.bundle +0 -0
- data/lib/3.1/fast_mmaped_file_rs.bundle +0 -0
- data/lib/3.2/fast_mmaped_file.bundle +0 -0
- data/lib/3.2/fast_mmaped_file_rs.bundle +0 -0
- data/lib/prometheus/client/configuration.rb +23 -0
- data/lib/prometheus/client/counter.rb +27 -0
- data/lib/prometheus/client/formats/text.rb +118 -0
- data/lib/prometheus/client/gauge.rb +40 -0
- data/lib/prometheus/client/helper/entry_parser.rb +132 -0
- data/lib/prometheus/client/helper/file_locker.rb +50 -0
- data/lib/prometheus/client/helper/json_parser.rb +23 -0
- data/lib/prometheus/client/helper/metrics_processing.rb +45 -0
- data/lib/prometheus/client/helper/metrics_representation.rb +51 -0
- data/lib/prometheus/client/helper/mmaped_file.rb +64 -0
- data/lib/prometheus/client/helper/plain_file.rb +29 -0
- data/lib/prometheus/client/histogram.rb +80 -0
- data/lib/prometheus/client/label_set_validator.rb +86 -0
- data/lib/prometheus/client/metric.rb +80 -0
- data/lib/prometheus/client/mmaped_dict.rb +79 -0
- data/lib/prometheus/client/mmaped_value.rb +154 -0
- data/lib/prometheus/client/page_size.rb +17 -0
- data/lib/prometheus/client/push.rb +203 -0
- data/lib/prometheus/client/rack/collector.rb +88 -0
- data/lib/prometheus/client/rack/exporter.rb +96 -0
- data/lib/prometheus/client/registry.rb +65 -0
- data/lib/prometheus/client/simple_value.rb +31 -0
- data/lib/prometheus/client/summary.rb +69 -0
- data/lib/prometheus/client/support/unicorn.rb +35 -0
- data/lib/prometheus/client/uses_value_type.rb +20 -0
- data/lib/prometheus/client/version.rb +5 -0
- data/lib/prometheus/client.rb +58 -0
- data/lib/prometheus.rb +3 -0
- data/vendor/c/hashmap/.gitignore +52 -0
- data/vendor/c/hashmap/LICENSE +21 -0
- data/vendor/c/hashmap/README.md +90 -0
- data/vendor/c/hashmap/_config.yml +1 -0
- data/vendor/c/hashmap/src/hashmap.c +692 -0
- data/vendor/c/hashmap/src/hashmap.h +267 -0
- data/vendor/c/hashmap/test/Makefile +22 -0
- data/vendor/c/hashmap/test/hashmap_test.c +608 -0
- data/vendor/c/jsmn/.travis.yml +4 -0
- data/vendor/c/jsmn/LICENSE +20 -0
- data/vendor/c/jsmn/Makefile +41 -0
- data/vendor/c/jsmn/README.md +168 -0
- data/vendor/c/jsmn/example/jsondump.c +126 -0
- data/vendor/c/jsmn/example/simple.c +76 -0
- data/vendor/c/jsmn/jsmn.c +314 -0
- data/vendor/c/jsmn/jsmn.h +76 -0
- data/vendor/c/jsmn/library.json +16 -0
- data/vendor/c/jsmn/test/test.h +27 -0
- data/vendor/c/jsmn/test/tests.c +407 -0
- data/vendor/c/jsmn/test/testutil.h +94 -0
- metadata +243 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
use nix::errno::Errno;
|
2
|
+
use nix::libc::c_long;
|
3
|
+
use std::fmt::Display;
|
4
|
+
use std::io;
|
5
|
+
use std::mem::size_of;
|
6
|
+
|
7
|
+
use crate::error::MmapError;
|
8
|
+
use crate::Result;
|
9
|
+
|
10
|
+
/// Wrapper around `checked_add()` that converts failures
|
11
|
+
/// to `MmapError::Overflow`.
|
12
|
+
pub trait CheckedOps: Sized {
|
13
|
+
fn add_chk(self, rhs: Self) -> Result<Self>;
|
14
|
+
fn mul_chk(self, rhs: Self) -> Result<Self>;
|
15
|
+
}
|
16
|
+
|
17
|
+
impl CheckedOps for usize {
|
18
|
+
fn add_chk(self, rhs: Self) -> Result<Self> {
|
19
|
+
self.checked_add(rhs)
|
20
|
+
.ok_or_else(|| MmapError::overflowed(self, rhs, "adding"))
|
21
|
+
}
|
22
|
+
|
23
|
+
fn mul_chk(self, rhs: Self) -> Result<Self> {
|
24
|
+
self.checked_mul(rhs)
|
25
|
+
.ok_or_else(|| MmapError::overflowed(self, rhs, "multiplying"))
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
impl CheckedOps for c_long {
|
30
|
+
fn add_chk(self, rhs: Self) -> Result<Self> {
|
31
|
+
self.checked_add(rhs)
|
32
|
+
.ok_or_else(|| MmapError::overflowed(self, rhs, "adding"))
|
33
|
+
}
|
34
|
+
|
35
|
+
fn mul_chk(self, rhs: Self) -> Result<Self> {
|
36
|
+
self.checked_mul(rhs)
|
37
|
+
.ok_or_else(|| MmapError::overflowed(self, rhs, "multiplying"))
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
/// A wrapper around `TryFrom`, returning `MmapError::FailedCast` on error.
|
42
|
+
pub fn cast_chk<T, U>(val: T, name: &str) -> Result<U>
|
43
|
+
where
|
44
|
+
T: Copy + Display,
|
45
|
+
U: std::convert::TryFrom<T>,
|
46
|
+
{
|
47
|
+
U::try_from(val).map_err(|_| MmapError::failed_cast::<T, U>(val, name))
|
48
|
+
}
|
49
|
+
|
50
|
+
/// Retrieve errno(3).
|
51
|
+
pub fn errno() -> i32 {
|
52
|
+
// UNWRAP: This will always return `Some` when called from `last_os_error()`.
|
53
|
+
io::Error::last_os_error().raw_os_error().unwrap()
|
54
|
+
}
|
55
|
+
|
56
|
+
/// Get the error string associated with errno(3).
|
57
|
+
/// Equivalent to strerror(3).
|
58
|
+
pub fn strerror(errno: i32) -> &'static str {
|
59
|
+
Errno::from_i32(errno).desc()
|
60
|
+
}
|
61
|
+
|
62
|
+
/// Read a `u32` value from a byte slice starting from `offset`.
|
63
|
+
#[inline]
|
64
|
+
pub fn read_u32(buf: &[u8], offset: usize) -> Result<u32> {
|
65
|
+
if let Some(slice) = buf.get(offset..offset + size_of::<u32>()) {
|
66
|
+
// UNWRAP: We can safely unwrap the conversion from slice to array as we
|
67
|
+
// the source and targets are constructed here with the same length.
|
68
|
+
let out: &[u8; size_of::<u32>()] = slice.try_into().unwrap();
|
69
|
+
|
70
|
+
return Ok(u32::from_ne_bytes(*out));
|
71
|
+
}
|
72
|
+
Err(MmapError::out_of_bounds(offset, buf.len()))
|
73
|
+
}
|
74
|
+
|
75
|
+
/// Read an `f64` value from a byte slice starting from `offset`.
|
76
|
+
#[inline]
|
77
|
+
pub fn read_f64(buf: &[u8], offset: usize) -> Result<f64> {
|
78
|
+
if let Some(slice) = buf.get(offset..offset + size_of::<f64>()) {
|
79
|
+
// UNWRAP: We can safely unwrap the conversion from slice to array as we
|
80
|
+
// can be sure the target array has same length as the source slice.
|
81
|
+
let out: &[u8; size_of::<f64>()] = slice.try_into().unwrap();
|
82
|
+
|
83
|
+
return Ok(f64::from_ne_bytes(*out));
|
84
|
+
}
|
85
|
+
Err(MmapError::out_of_bounds(
|
86
|
+
offset + size_of::<f64>(),
|
87
|
+
buf.len(),
|
88
|
+
))
|
89
|
+
}
|
90
|
+
|
91
|
+
#[cfg(test)]
|
92
|
+
mod test {
|
93
|
+
use super::*;
|
94
|
+
|
95
|
+
#[test]
|
96
|
+
fn test_read_u32() {
|
97
|
+
let buf = 1u32.to_ne_bytes();
|
98
|
+
|
99
|
+
assert!(matches!(read_u32(&buf, 0), Ok(1)), "index ok");
|
100
|
+
assert!(read_u32(&buf, 10).is_err(), "index out of range");
|
101
|
+
assert!(
|
102
|
+
read_u32(&buf, 1).is_err(),
|
103
|
+
"index in range but end out of range"
|
104
|
+
);
|
105
|
+
}
|
106
|
+
|
107
|
+
#[test]
|
108
|
+
fn test_read_f64() {
|
109
|
+
let buf = 1.00f64.to_ne_bytes();
|
110
|
+
|
111
|
+
let ok = read_f64(&buf, 0);
|
112
|
+
assert!(ok.is_ok());
|
113
|
+
assert_eq!(ok.unwrap(), 1.00);
|
114
|
+
|
115
|
+
assert!(read_f64(&buf, 10).is_err(), "index out of range");
|
116
|
+
assert!(
|
117
|
+
read_f64(&buf, 1).is_err(),
|
118
|
+
"index in range but end out of range"
|
119
|
+
);
|
120
|
+
}
|
121
|
+
}
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'prometheus/client/registry'
|
2
|
+
require 'prometheus/client/mmaped_value'
|
3
|
+
require 'prometheus/client/page_size'
|
4
|
+
require 'logger'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
module Prometheus
|
8
|
+
module Client
|
9
|
+
class Configuration
|
10
|
+
attr_accessor :value_class, :multiprocess_files_dir, :initial_mmap_file_size, :logger, :pid_provider
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@value_class = ::Prometheus::Client::MmapedValue
|
14
|
+
@initial_mmap_file_size = ::Prometheus::Client::PageSize.page_size(fallback_page_size: 4096)
|
15
|
+
@logger = Logger.new($stdout)
|
16
|
+
@pid_provider = Process.method(:pid)
|
17
|
+
@multiprocess_files_dir = ENV.fetch('prometheus_multiproc_dir') do
|
18
|
+
Dir.mktmpdir("prometheus-mmap")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'prometheus/client/metric'
|
4
|
+
|
5
|
+
module Prometheus
|
6
|
+
module Client
|
7
|
+
# Counter is a metric that exposes merely a sum or tally of things.
|
8
|
+
class Counter < Metric
|
9
|
+
def type
|
10
|
+
:counter
|
11
|
+
end
|
12
|
+
|
13
|
+
def increment(labels = {}, by = 1)
|
14
|
+
raise ArgumentError, 'increment must be a non-negative number' if by < 0
|
15
|
+
|
16
|
+
label_set = label_set_for(labels)
|
17
|
+
synchronize { @values[label_set].increment(by) }
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def default(labels)
|
23
|
+
value_object(type, @name, @name, labels)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'prometheus/client/uses_value_type'
|
2
|
+
require 'prometheus/client/helper/json_parser'
|
3
|
+
require 'prometheus/client/helper/plain_file'
|
4
|
+
require 'prometheus/client/helper/metrics_processing'
|
5
|
+
require 'prometheus/client/helper/metrics_representation'
|
6
|
+
|
7
|
+
module Prometheus
|
8
|
+
module Client
|
9
|
+
module Formats
|
10
|
+
# Text format is human readable mainly used for manual inspection.
|
11
|
+
module Text
|
12
|
+
MEDIA_TYPE = 'text/plain'.freeze
|
13
|
+
VERSION = '0.0.4'.freeze
|
14
|
+
CONTENT_TYPE = "#{MEDIA_TYPE}; version=#{VERSION}".freeze
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def marshal(registry)
|
18
|
+
metrics = registry.metrics.map do |metric|
|
19
|
+
samples = metric.values.flat_map do |label_set, value|
|
20
|
+
representation(metric, label_set, value)
|
21
|
+
end
|
22
|
+
|
23
|
+
[metric.name, { type: metric.type, help: metric.docstring, samples: samples }]
|
24
|
+
end
|
25
|
+
|
26
|
+
Helper::MetricsRepresentation.to_text(metrics)
|
27
|
+
end
|
28
|
+
|
29
|
+
def marshal_multiprocess(path = Prometheus::Client.configuration.multiprocess_files_dir, use_rust: false)
|
30
|
+
file_list = Dir.glob(File.join(path, '*.db')).sort
|
31
|
+
.map {|f| Helper::PlainFile.new(f) }
|
32
|
+
.map {|f| [f.filepath, f.multiprocess_mode.to_sym, f.type.to_sym, f.pid] }
|
33
|
+
|
34
|
+
if use_rust && rust_impl_available?
|
35
|
+
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
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
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
|
+
def load_metrics(path)
|
73
|
+
metrics = {}
|
74
|
+
Dir.glob(File.join(path, '*.db')).sort.each do |f|
|
75
|
+
Helper::PlainFile.new(f).to_metrics(metrics)
|
76
|
+
end
|
77
|
+
|
78
|
+
metrics
|
79
|
+
end
|
80
|
+
|
81
|
+
def representation(metric, label_set, value)
|
82
|
+
labels = metric.base_labels.merge(label_set)
|
83
|
+
|
84
|
+
if metric.type == :summary
|
85
|
+
summary(metric.name, labels, value)
|
86
|
+
elsif metric.type == :histogram
|
87
|
+
histogram(metric.name, labels, value)
|
88
|
+
else
|
89
|
+
[[metric.name, labels, value.get]]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def summary(name, set, value)
|
94
|
+
rv = value.get.map do |q, v|
|
95
|
+
[name, set.merge(quantile: q), v]
|
96
|
+
end
|
97
|
+
|
98
|
+
rv << ["#{name}_sum", set, value.get.sum]
|
99
|
+
rv << ["#{name}_count", set, value.get.total]
|
100
|
+
rv
|
101
|
+
end
|
102
|
+
|
103
|
+
def histogram(name, set, value)
|
104
|
+
# |metric_name, labels, value|
|
105
|
+
rv = value.get.map do |q, v|
|
106
|
+
[name, set.merge(le: q), v]
|
107
|
+
end
|
108
|
+
|
109
|
+
rv << [name, set.merge(le: '+Inf'), value.get.total]
|
110
|
+
rv << ["#{name}_sum", set, value.get.sum]
|
111
|
+
rv << ["#{name}_count", set, value.get.total]
|
112
|
+
rv
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'prometheus/client/metric'
|
4
|
+
|
5
|
+
module Prometheus
|
6
|
+
module Client
|
7
|
+
# A Gauge is a metric that exposes merely an instantaneous value or some
|
8
|
+
# snapshot thereof.
|
9
|
+
class Gauge < Metric
|
10
|
+
def initialize(name, docstring, base_labels = {}, multiprocess_mode=:all)
|
11
|
+
super(name, docstring, base_labels)
|
12
|
+
if value_class.multiprocess and ![:min, :max, :livesum, :liveall, :all].include?(multiprocess_mode)
|
13
|
+
raise ArgumentError, 'Invalid multiprocess mode: ' + multiprocess_mode
|
14
|
+
end
|
15
|
+
@multiprocess_mode = multiprocess_mode
|
16
|
+
end
|
17
|
+
|
18
|
+
def type
|
19
|
+
:gauge
|
20
|
+
end
|
21
|
+
|
22
|
+
def default(labels)
|
23
|
+
value_object(type, @name, @name, labels, @multiprocess_mode)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Sets the value for the given label set
|
27
|
+
def set(labels, value)
|
28
|
+
@values[label_set_for(labels)].set(value)
|
29
|
+
end
|
30
|
+
|
31
|
+
def increment(labels, value)
|
32
|
+
@values[label_set_for(labels)].increment(value)
|
33
|
+
end
|
34
|
+
|
35
|
+
def decrement(labels, value)
|
36
|
+
@values[label_set_for(labels)].decrement(value)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'prometheus/client/helper/json_parser'
|
2
|
+
|
3
|
+
module Prometheus
|
4
|
+
module Client
|
5
|
+
module Helper
|
6
|
+
module EntryParser
|
7
|
+
class ParsingError < RuntimeError;
|
8
|
+
end
|
9
|
+
|
10
|
+
MINIMUM_SIZE = 8
|
11
|
+
START_POSITION = 8
|
12
|
+
VALUE_BYTES = 8
|
13
|
+
ENCODED_LENGTH_BYTES = 4
|
14
|
+
|
15
|
+
def used
|
16
|
+
slice(0..3).unpack('l')[0]
|
17
|
+
end
|
18
|
+
|
19
|
+
def parts
|
20
|
+
@parts ||= File.basename(filepath, '.db')
|
21
|
+
.split('_')
|
22
|
+
.map { |e| e.gsub(/-\d+$/, '') } # remove trailing -number
|
23
|
+
end
|
24
|
+
|
25
|
+
def type
|
26
|
+
parts[0].to_sym
|
27
|
+
end
|
28
|
+
|
29
|
+
def pid
|
30
|
+
(parts[2..-1] || []).join('_')
|
31
|
+
end
|
32
|
+
|
33
|
+
def multiprocess_mode
|
34
|
+
parts[1]
|
35
|
+
end
|
36
|
+
|
37
|
+
def empty?
|
38
|
+
size < MINIMUM_SIZE || used.zero?
|
39
|
+
end
|
40
|
+
|
41
|
+
def entries(ignore_errors = false)
|
42
|
+
return Enumerator.new {} if empty?
|
43
|
+
|
44
|
+
Enumerator.new do |yielder|
|
45
|
+
used_ = used # cache used to avoid unnecessary unpack operations
|
46
|
+
|
47
|
+
pos = START_POSITION # used + padding offset
|
48
|
+
while pos < used_ && pos < size && pos > 0
|
49
|
+
data = slice(pos..-1)
|
50
|
+
unless data
|
51
|
+
raise ParsingError, "data slice is nil at pos #{pos}" unless ignore_errors
|
52
|
+
pos += 8
|
53
|
+
next
|
54
|
+
end
|
55
|
+
|
56
|
+
encoded_len, first_encoded_bytes = data.unpack('LL')
|
57
|
+
if encoded_len.nil? || encoded_len.zero? || first_encoded_bytes.nil? || first_encoded_bytes.zero?
|
58
|
+
# do not parse empty data
|
59
|
+
pos += 8
|
60
|
+
next
|
61
|
+
end
|
62
|
+
|
63
|
+
entry_len = ENCODED_LENGTH_BYTES + encoded_len
|
64
|
+
padding_len = 8 - entry_len % 8
|
65
|
+
|
66
|
+
value_offset = entry_len + padding_len # align to 8 bytes
|
67
|
+
pos += value_offset
|
68
|
+
|
69
|
+
if value_offset > 0 && (pos + VALUE_BYTES) <= size # if positions are safe
|
70
|
+
yielder.yield data, encoded_len, value_offset, pos
|
71
|
+
else
|
72
|
+
raise ParsingError, "data slice is nil at pos #{pos}" unless ignore_errors
|
73
|
+
end
|
74
|
+
pos += VALUE_BYTES
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def parsed_entries(ignore_errors = false)
|
80
|
+
result = entries(ignore_errors).map do |data, encoded_len, value_offset, _|
|
81
|
+
begin
|
82
|
+
encoded, value = data.unpack(format('@4A%d@%dd', encoded_len, value_offset))
|
83
|
+
[encoded, value]
|
84
|
+
rescue ArgumentError => e
|
85
|
+
Prometheus::Client.logger.debug("Error processing data: #{bin_to_hex(data[0, 7])} len: #{encoded_len} value_offset: #{value_offset}")
|
86
|
+
raise ParsingError, e unless ignore_errors
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
result.reject!(&:nil?) if ignore_errors
|
91
|
+
result
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_metrics(metrics = {}, ignore_errors = false)
|
95
|
+
parsed_entries(ignore_errors).each do |key, value|
|
96
|
+
begin
|
97
|
+
metric_name, name, labelnames, labelvalues = JsonParser.load(key)
|
98
|
+
labelnames ||= []
|
99
|
+
labelvalues ||= []
|
100
|
+
|
101
|
+
metric = metrics.fetch(metric_name,
|
102
|
+
metric_name: metric_name,
|
103
|
+
help: 'Multiprocess metric',
|
104
|
+
type: type,
|
105
|
+
samples: [])
|
106
|
+
if type == :gauge
|
107
|
+
metric[:multiprocess_mode] = multiprocess_mode
|
108
|
+
metric[:samples] += [[name, labelnames.zip(labelvalues) + [['pid', pid]], value]]
|
109
|
+
else
|
110
|
+
# The duplicates and labels are fixed in the next for.
|
111
|
+
metric[:samples] += [[name, labelnames.zip(labelvalues), value]]
|
112
|
+
end
|
113
|
+
metrics[metric_name] = metric
|
114
|
+
|
115
|
+
rescue JSON::ParserError => e
|
116
|
+
raise ParsingError(e) unless ignore_errors
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
metrics.reject! { |e| e.nil? } if ignore_errors
|
121
|
+
metrics
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def bin_to_hex(s)
|
127
|
+
s.each_byte.map { |b| b.to_s(16) }.join
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Prometheus
|
2
|
+
module Client
|
3
|
+
module Helper
|
4
|
+
class FileLocker
|
5
|
+
class << self
|
6
|
+
LOCK_FILE_MUTEX = Mutex.new
|
7
|
+
|
8
|
+
def lock_to_process(filepath)
|
9
|
+
LOCK_FILE_MUTEX.synchronize do
|
10
|
+
@file_locks ||= {}
|
11
|
+
return false if @file_locks[filepath]
|
12
|
+
|
13
|
+
file = File.open(filepath, 'ab')
|
14
|
+
if file.flock(File::LOCK_NB | File::LOCK_EX)
|
15
|
+
@file_locks[filepath] = file
|
16
|
+
return true
|
17
|
+
else
|
18
|
+
return false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def unlock(filepath)
|
24
|
+
LOCK_FILE_MUTEX.synchronize do
|
25
|
+
@file_locks ||= {}
|
26
|
+
return false unless @file_locks[filepath]
|
27
|
+
|
28
|
+
file = @file_locks[filepath]
|
29
|
+
file.flock(File::LOCK_UN)
|
30
|
+
file.close
|
31
|
+
@file_locks.delete(filepath)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def unlock_all
|
36
|
+
LOCK_FILE_MUTEX.synchronize do
|
37
|
+
@file_locks ||= {}
|
38
|
+
@file_locks.values.each do |file|
|
39
|
+
file.flock(File::LOCK_UN)
|
40
|
+
file.close
|
41
|
+
end
|
42
|
+
|
43
|
+
@file_locks = {}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Prometheus
|
4
|
+
module Client
|
5
|
+
module Helper
|
6
|
+
module JsonParser
|
7
|
+
class << self
|
8
|
+
if defined?(Oj)
|
9
|
+
def load(s)
|
10
|
+
Oj.load(s)
|
11
|
+
rescue Oj::ParseError, EncodingError => e
|
12
|
+
raise JSON::ParserError.new(e.message)
|
13
|
+
end
|
14
|
+
else
|
15
|
+
def load(s)
|
16
|
+
JSON.parse(s)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Prometheus
|
2
|
+
module Client
|
3
|
+
module Helper
|
4
|
+
module MetricsProcessing
|
5
|
+
def self.merge_metrics(metrics)
|
6
|
+
metrics.each_value do |metric|
|
7
|
+
metric[:samples] = merge_samples(metric[:samples], metric[:type], metric[:multiprocess_mode]).map do |(name, labels), value|
|
8
|
+
[name, labels.to_h, value]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.merge_samples(raw_samples, metric_type, multiprocess_mode)
|
14
|
+
samples = {}
|
15
|
+
raw_samples.each do |name, labels, value|
|
16
|
+
without_pid = labels.reject { |l| l[0] == 'pid' }
|
17
|
+
|
18
|
+
case metric_type
|
19
|
+
when :gauge
|
20
|
+
case multiprocess_mode
|
21
|
+
when 'min'
|
22
|
+
s = samples.fetch([name, without_pid], value)
|
23
|
+
samples[[name, without_pid]] = [s, value].min
|
24
|
+
when 'max'
|
25
|
+
s = samples.fetch([name, without_pid], value)
|
26
|
+
samples[[name, without_pid]] = [s, value].max
|
27
|
+
when 'livesum'
|
28
|
+
s = samples.fetch([name, without_pid], 0.0)
|
29
|
+
samples[[name, without_pid]] = s + value
|
30
|
+
else # all/liveall
|
31
|
+
samples[[name, labels]] = value
|
32
|
+
end
|
33
|
+
else
|
34
|
+
# Counter, Histogram and Summary.
|
35
|
+
s = samples.fetch([name, without_pid], 0.0)
|
36
|
+
samples[[name, without_pid]] = s + value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
samples
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Prometheus
|
2
|
+
module Client
|
3
|
+
module Helper
|
4
|
+
module MetricsRepresentation
|
5
|
+
METRIC_LINE = '%s%s %s'.freeze
|
6
|
+
TYPE_LINE = '# TYPE %s %s'.freeze
|
7
|
+
HELP_LINE = '# HELP %s %s'.freeze
|
8
|
+
|
9
|
+
LABEL = '%s="%s"'.freeze
|
10
|
+
SEPARATOR = ','.freeze
|
11
|
+
DELIMITER = "\n".freeze
|
12
|
+
|
13
|
+
REGEX = { doc: /[\n\\]/, label: /[\n\\"]/ }.freeze
|
14
|
+
REPLACE = { "\n" => '\n', '\\' => '\\\\', '"' => '\"' }.freeze
|
15
|
+
|
16
|
+
def self.to_text(metrics)
|
17
|
+
lines = []
|
18
|
+
|
19
|
+
metrics.each do |name, metric|
|
20
|
+
lines << format(HELP_LINE, name, escape(metric[:help]))
|
21
|
+
lines << format(TYPE_LINE, name, metric[:type])
|
22
|
+
metric[:samples].each do |metric_name, labels, value|
|
23
|
+
lines << metric(metric_name, format_labels(labels), value)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# there must be a trailing delimiter
|
28
|
+
(lines << nil).join(DELIMITER)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.metric(name, labels, value)
|
32
|
+
format(METRIC_LINE, name, labels, value)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.format_labels(set)
|
36
|
+
return if set.empty?
|
37
|
+
|
38
|
+
strings = set.each_with_object([]) do |(key, value), memo|
|
39
|
+
memo << format(LABEL, key, escape(value, :label))
|
40
|
+
end
|
41
|
+
|
42
|
+
"{#{strings.join(SEPARATOR)}}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.escape(string, format = :doc)
|
46
|
+
string.to_s.gsub(REGEX[format], REPLACE)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|