prometheus-client-mmap 0.7.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +183 -0
- data/lib/prometheus.rb +5 -0
- data/lib/prometheus/client.rb +13 -0
- data/lib/prometheus/client/counter.rb +28 -0
- data/lib/prometheus/client/formats/text.rb +200 -0
- data/lib/prometheus/client/gauge.rb +32 -0
- data/lib/prometheus/client/histogram.rb +84 -0
- data/lib/prometheus/client/label_set_validator.rb +69 -0
- data/lib/prometheus/client/metric.rb +70 -0
- data/lib/prometheus/client/push.rb +72 -0
- data/lib/prometheus/client/rack/collector.rb +82 -0
- data/lib/prometheus/client/rack/exporter.rb +91 -0
- data/lib/prometheus/client/registry.rb +65 -0
- data/lib/prometheus/client/summary.rb +71 -0
- data/lib/prometheus/client/valuetype.rb +204 -0
- data/lib/prometheus/client/version.rb +7 -0
- metadata +75 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
require 'prometheus/client/counter'
|
6
|
+
require 'prometheus/client/summary'
|
7
|
+
require 'prometheus/client/gauge'
|
8
|
+
require 'prometheus/client/histogram'
|
9
|
+
|
10
|
+
module Prometheus
|
11
|
+
module Client
|
12
|
+
# Registry
|
13
|
+
class Registry
|
14
|
+
class AlreadyRegisteredError < StandardError; end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@metrics = {}
|
18
|
+
@mutex = Mutex.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def register(metric)
|
22
|
+
name = metric.name
|
23
|
+
|
24
|
+
@mutex.synchronize do
|
25
|
+
if exist?(name.to_sym)
|
26
|
+
raise AlreadyRegisteredError, "#{name} has already been registered"
|
27
|
+
else
|
28
|
+
@metrics[name.to_sym] = metric
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
metric
|
33
|
+
end
|
34
|
+
|
35
|
+
def counter(name, docstring, base_labels = {})
|
36
|
+
register(Counter.new(name, docstring, base_labels))
|
37
|
+
end
|
38
|
+
|
39
|
+
def summary(name, docstring, base_labels = {})
|
40
|
+
register(Summary.new(name, docstring, base_labels))
|
41
|
+
end
|
42
|
+
|
43
|
+
def gauge(name, docstring, base_labels = {})
|
44
|
+
register(Gauge.new(name, docstring, base_labels))
|
45
|
+
end
|
46
|
+
|
47
|
+
def histogram(name, docstring, base_labels = {},
|
48
|
+
buckets = Histogram::DEFAULT_BUCKETS)
|
49
|
+
register(Histogram.new(name, docstring, base_labels, buckets))
|
50
|
+
end
|
51
|
+
|
52
|
+
def exist?(name)
|
53
|
+
@metrics.key?(name)
|
54
|
+
end
|
55
|
+
|
56
|
+
def get(name)
|
57
|
+
@metrics[name.to_sym]
|
58
|
+
end
|
59
|
+
|
60
|
+
def metrics
|
61
|
+
@metrics.values
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'quantile'
|
4
|
+
require 'prometheus/client/metric'
|
5
|
+
|
6
|
+
module Prometheus
|
7
|
+
module Client
|
8
|
+
# Summary is an accumulator for samples. It captures Numeric data and
|
9
|
+
# provides an efficient quantile calculation mechanism.
|
10
|
+
class Summary < Metric
|
11
|
+
extend Gem::Deprecate
|
12
|
+
|
13
|
+
# Value represents the state of a Summary at a given point.
|
14
|
+
class Value < Hash
|
15
|
+
attr_accessor :sum, :total
|
16
|
+
|
17
|
+
def initialize(name, labels, estimator)
|
18
|
+
@sum = ValueClass.new(name, name + '_sum', labels, estimator.sum)
|
19
|
+
@total = ValueClass.new(name, name + '_count', labels, estimator.observations)
|
20
|
+
|
21
|
+
estimator.invariants.each do |invariant|
|
22
|
+
self[invariant.quantile] = ValueClass.new(type, name, labels, estimator.query(invariant.quantile), nil)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(name, docstring, base_labels = {}, multiprocess_mode)
|
28
|
+
if ENV['prometheus_multiproc_dir']
|
29
|
+
raise ArgumentError, "Summary metric type does not have multiprocess support"
|
30
|
+
end
|
31
|
+
super(name, docstring, base_labels)
|
32
|
+
end
|
33
|
+
|
34
|
+
def type
|
35
|
+
:summary
|
36
|
+
end
|
37
|
+
|
38
|
+
# Records a given value.
|
39
|
+
def observe(labels, value)
|
40
|
+
label_set = label_set_for(labels)
|
41
|
+
synchronize { @values[label_set].observe(value) }
|
42
|
+
end
|
43
|
+
alias add observe
|
44
|
+
deprecate :add, :observe, 2016, 10
|
45
|
+
|
46
|
+
# Returns the value for the given label set
|
47
|
+
def get(labels = {})
|
48
|
+
@validator.valid?(labels)
|
49
|
+
|
50
|
+
synchronize do
|
51
|
+
Value.new(@values[labels])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns all label sets with their values
|
56
|
+
def values
|
57
|
+
synchronize do
|
58
|
+
@values.each_with_object({}) do |(labels, value), memo|
|
59
|
+
memo[labels] = Value.new(value)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def default(labels)
|
67
|
+
Quantile::Estimator.new
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "mmap"
|
5
|
+
|
6
|
+
module Prometheus
|
7
|
+
module Client
|
8
|
+
class SimpleValue
|
9
|
+
def initialize(type, metric_name, name, labels, value = 0)
|
10
|
+
@value = value
|
11
|
+
end
|
12
|
+
|
13
|
+
def set(value)
|
14
|
+
@value = value
|
15
|
+
end
|
16
|
+
|
17
|
+
def increment(by = 1)
|
18
|
+
@value += by
|
19
|
+
end
|
20
|
+
|
21
|
+
def get
|
22
|
+
@value
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.multiprocess
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# A float protected by a mutex backed by a per-process mmaped file.
|
31
|
+
class MmapedValue
|
32
|
+
@@files = {}
|
33
|
+
@@files_lock = Mutex.new
|
34
|
+
@@pid = Process.pid
|
35
|
+
|
36
|
+
def initialize(type, metric_name, name, labels, multiprocess_mode='')
|
37
|
+
@@pid = Process.pid
|
38
|
+
file_prefix = type.to_s
|
39
|
+
if type == :gauge
|
40
|
+
file_prefix += '_' + multiprocess_mode.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
@@files_lock.synchronize do
|
44
|
+
if !@@files.has_key?(file_prefix)
|
45
|
+
filename = File.join(ENV['prometheus_multiproc_dir'], "#{file_prefix}_#{@@pid}.db")
|
46
|
+
@@files[file_prefix] = MmapedDict.new(filename)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
@file = @@files[file_prefix]
|
51
|
+
labelnames = []
|
52
|
+
labelvalues = []
|
53
|
+
labels.each do |k, v|
|
54
|
+
labelnames << k
|
55
|
+
labelvalues << v
|
56
|
+
end
|
57
|
+
|
58
|
+
@key = [metric_name, name, labelnames, labelvalues].to_json
|
59
|
+
@value = @file.read_value(@key)
|
60
|
+
@mutex = Mutex.new
|
61
|
+
end
|
62
|
+
|
63
|
+
def increment(amount=1)
|
64
|
+
@mutex.synchronize do
|
65
|
+
@value += amount
|
66
|
+
@file.write_value(@key, @value)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def set(value)
|
71
|
+
@mutex.synchronize do
|
72
|
+
@value = value
|
73
|
+
@file.write_value(@key, @value)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def get
|
78
|
+
@mutex.synchronize do
|
79
|
+
return @value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.multiprocess
|
84
|
+
true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Should we enable multi-process mode?
|
89
|
+
# This needs to be chosen before the first metric is constructed,
|
90
|
+
# and as that may be in some arbitrary library the user/admin has
|
91
|
+
# no control over we use an enviroment variable.
|
92
|
+
if ENV.has_key?('prometheus_multiproc_dir')
|
93
|
+
ValueClass = MmapedValue
|
94
|
+
else
|
95
|
+
ValueClass = SimpleValue
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# A dict of doubles, backed by an mmapped file.
|
101
|
+
#
|
102
|
+
# The file starts with a 4 byte int, indicating how much of it is used.
|
103
|
+
# Then 4 bytes of padding.
|
104
|
+
# There's then a number of entries, consisting of a 4 byte int which is the
|
105
|
+
# size of the next field, a utf-8 encoded string key, padding to an 8 byte
|
106
|
+
# alignment, and then a 8 byte float which is the value.
|
107
|
+
#
|
108
|
+
# TODO(julius): dealing with Mmap.new, truncate etc. errors?
|
109
|
+
class MmapedDict
|
110
|
+
@@INITIAL_MMAP_SIZE = 1024*1024
|
111
|
+
|
112
|
+
attr_reader :m, :capacity, :used, :positions
|
113
|
+
|
114
|
+
def initialize(filename)
|
115
|
+
@mutex = Mutex.new
|
116
|
+
@f = File.open(filename, 'a+b')
|
117
|
+
if @f.size == 0
|
118
|
+
@f.truncate(@@INITIAL_MMAP_SIZE)
|
119
|
+
end
|
120
|
+
@capacity = @f.size
|
121
|
+
@m = Mmap.new(filename, 'rw', Mmap::MAP_SHARED)
|
122
|
+
# @m.mlock # TODO: Why does this raise an error?
|
123
|
+
|
124
|
+
@positions = {}
|
125
|
+
@used = @m[0..3].unpack('l')[0]
|
126
|
+
if @used == 0
|
127
|
+
@used = 8
|
128
|
+
@m[0..3] = [@used].pack('l')
|
129
|
+
else
|
130
|
+
read_all_values.each do |key, _, pos|
|
131
|
+
@positions[key] = pos
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Yield (key, value, pos). No locking is performed.
|
137
|
+
def all_values
|
138
|
+
read_all_values.map { |k, v, p| [k, v] }
|
139
|
+
end
|
140
|
+
|
141
|
+
def read_value(key)
|
142
|
+
@mutex.synchronize do
|
143
|
+
if !@positions.has_key?(key)
|
144
|
+
init_value(key)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
pos = @positions[key]
|
148
|
+
# We assume that reading from an 8 byte aligned value is atomic.
|
149
|
+
@m[pos..pos+7].unpack('d')[0]
|
150
|
+
end
|
151
|
+
|
152
|
+
def write_value(key, value)
|
153
|
+
@mutex.synchronize do
|
154
|
+
if !@positions.has_key?(key)
|
155
|
+
init_value(key)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
pos = @positions[key]
|
159
|
+
# We assume that writing to an 8 byte aligned value is atomic.
|
160
|
+
@m[pos..pos+7] = [value].pack('d')
|
161
|
+
end
|
162
|
+
|
163
|
+
def close()
|
164
|
+
@m.munmap
|
165
|
+
@f.close
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
# Initialize a value. Lock must be held by caller.
|
171
|
+
def init_value(key)
|
172
|
+
# Pad to be 8-byte aligned.
|
173
|
+
padded = key + (' ' * (8 - (key.length + 4) % 8))
|
174
|
+
value = [key.length, padded, 0.0].pack("lA#{padded.length}d")
|
175
|
+
while @used + value.length > @capacity
|
176
|
+
@capacity *= 2
|
177
|
+
@f.truncate(@capacity)
|
178
|
+
@m = Mmap.new(@f.path, 'rw', Mmap::MAP_SHARED)
|
179
|
+
end
|
180
|
+
@m[@used..@used + value.length] = value
|
181
|
+
|
182
|
+
# Update how much space we've used.
|
183
|
+
@used += value.length
|
184
|
+
@m[0..3] = [@used].pack('l')
|
185
|
+
@positions[key] = @used - 8
|
186
|
+
end
|
187
|
+
|
188
|
+
# Yield (key, value, pos). No locking is performed.
|
189
|
+
def read_all_values
|
190
|
+
pos = 8
|
191
|
+
values = []
|
192
|
+
while pos < @used
|
193
|
+
encoded_len = @m[pos..-1].unpack('l')[0]
|
194
|
+
pos += 4
|
195
|
+
encoded = @m[pos..-1].unpack("A#{encoded_len}")[0]
|
196
|
+
padded_len = encoded_len + (8 - (encoded_len + 4) % 8)
|
197
|
+
pos += padded_len
|
198
|
+
value = @m[pos..-1].unpack('d')[0]
|
199
|
+
values << [encoded, value, pos]
|
200
|
+
pos += 8
|
201
|
+
end
|
202
|
+
values
|
203
|
+
end
|
204
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: prometheus-client-mmap
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.7.0.beta1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tobias Schmidt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-04-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: quantile
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.2.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.2.0
|
27
|
+
description:
|
28
|
+
email:
|
29
|
+
- ts@soundcloud.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- README.md
|
35
|
+
- lib/prometheus.rb
|
36
|
+
- lib/prometheus/client.rb
|
37
|
+
- lib/prometheus/client/counter.rb
|
38
|
+
- lib/prometheus/client/formats/text.rb
|
39
|
+
- lib/prometheus/client/gauge.rb
|
40
|
+
- lib/prometheus/client/histogram.rb
|
41
|
+
- lib/prometheus/client/label_set_validator.rb
|
42
|
+
- lib/prometheus/client/metric.rb
|
43
|
+
- lib/prometheus/client/push.rb
|
44
|
+
- lib/prometheus/client/rack/collector.rb
|
45
|
+
- lib/prometheus/client/rack/exporter.rb
|
46
|
+
- lib/prometheus/client/registry.rb
|
47
|
+
- lib/prometheus/client/summary.rb
|
48
|
+
- lib/prometheus/client/valuetype.rb
|
49
|
+
- lib/prometheus/client/version.rb
|
50
|
+
homepage: https://github.com/pchojnacki/client_ruby
|
51
|
+
licenses:
|
52
|
+
- Apache 2.0
|
53
|
+
metadata: {}
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 1.3.1
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 2.6.8
|
71
|
+
signing_key:
|
72
|
+
specification_version: 4
|
73
|
+
summary: A suite of instrumentation metric primitivesthat can be exposed through a
|
74
|
+
web services interface.
|
75
|
+
test_files: []
|