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.
@@ -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: []