prometheus-client-mmap 0.7.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|