prometheus-client-mmap 0.7.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,7 @@
1
+ # encoding: UTF-8
2
+
3
+ module Prometheus
4
+ module Client
5
+ VERSION = '0.7.0.beta1'
6
+ end
7
+ 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: []