prometheus-client-mmap 0.7.0.beta8 → 0.7.0.beta9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 994fda67c8a970a53d796534315b82fd06f130c4
4
- data.tar.gz: f9c0d71024d0f8f31e5578d063e78e1e784a4496
3
+ metadata.gz: 24351728cd7ccff16974ea7cf72a781f3b393831
4
+ data.tar.gz: aba591bd2540b26541949b3536a2b5fbc2850e72
5
5
  SHA512:
6
- metadata.gz: e2a9d9727c6b7b29b14a7edcd38f1a4d1ef93be325ec0018c1f66811683f79d8f84e3c8333fda88260b6bb6f18003cc6a77256b1184a9e75c67cb6bfe1c2e69c
7
- data.tar.gz: f71da51891d436487fbfb854a1bd65d79c4e471f37ce8f4e7043333e1719620277d762bac16960eb9aa354b4690f69150a6db0e4a29cb620cf6c86d70ecf7d57
6
+ metadata.gz: 5a6d7c3669a14ff150f3a7d79c119db34ed7a30404a6e69f0c6183e8c50b53325f6147fad8bbf5efd55137d00eef3c6e4c89b60c54e4dad8618753edc69bf06b
7
+ data.tar.gz: ff9e4880b4847076b887d5c2ee07fe09fefb88d0806b840d6a65f21a8d4e1c3aca05e07ca94561ec97bf79f6450a95f4129a48c1eb9cdf982ac898797d4101a4
@@ -0,0 +1,18 @@
1
+ require 'prometheus/client/registry'
2
+ require 'prometheus/client/mmaped_value'
3
+ require 'logger'
4
+
5
+ module Prometheus
6
+ module Client
7
+ class Configuration
8
+ attr_accessor :value_class, :multiprocess_files_dir, :initial_mmap_file_size, :logger
9
+
10
+ def initialize
11
+ @value_class = ::Prometheus::Client::MmapedValue
12
+ @multiprocess_files_dir = ENV['prometheus_multiproc_dir']
13
+ @initial_mmap_file_size = 4 * 1024
14
+ @logger = Logger.new($stdout)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,7 +1,6 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  require 'prometheus/client/metric'
4
- require 'prometheus/client/valuetype'
5
4
 
6
5
  module Prometheus
7
6
  module Client
@@ -21,7 +20,7 @@ module Prometheus
21
20
  private
22
21
 
23
22
  def default(labels)
24
- ValueClass.new(type, @name, @name, labels)
23
+ value_object(type, @name, @name, labels)
25
24
  end
26
25
  end
27
26
  end
@@ -1,6 +1,4 @@
1
- # encoding: UTF-8
2
-
3
- require 'prometheus/client/valuetype'
1
+ require 'prometheus/client/uses_value_type'
4
2
 
5
3
  module Prometheus
6
4
  module Client
@@ -38,32 +36,8 @@ module Prometheus
38
36
  (lines << nil).join(DELIMITER)
39
37
  end
40
38
 
41
- def self.marshal_multiprocess(path=Prometheus::Client::Multiprocdir)
42
- metrics = {}
43
- Dir.glob(File.join(path, "*.db")).each do |f|
44
- parts = File.basename(f,'.db').split("_")
45
- type = parts[0].to_sym
46
- d = MmapedDict.new(f)
47
- d.all_values.each do |key, value|
48
- metric_name, name, labelnames, labelvalues = JSON.parse(key)
49
- metric = metrics.fetch(metric_name, {
50
- metric_name: metric_name,
51
- help: 'Multiprocess metric',
52
- type: type,
53
- samples: [],
54
- })
55
- if type == :gauge
56
- pid = parts[2]
57
- metric[:multiprocess_mode] = parts[1]
58
- metric[:samples] += [[name, labelnames.zip(labelvalues) + [['pid', pid]], value]]
59
- else
60
- # The duplicates and labels are fixed in the next for.
61
- metric[:samples] += [[name, labelnames.zip(labelvalues), value]]
62
- end
63
- metrics[metric_name] = metric
64
- end
65
- d.close
66
- end
39
+ def self.marshal_multiprocess(path = Prometheus::Client.configuration.multiprocess_files_dir)
40
+ metrics = load_metrics(path)
67
41
 
68
42
  metrics.each_value do |metric|
69
43
  samples = {}
@@ -118,6 +92,39 @@ module Prometheus
118
92
  class << self
119
93
  private
120
94
 
95
+ def load_metrics(path)
96
+ metrics = {}
97
+ Dir.glob(File.join(path, '*.db')).each do |f|
98
+ parts = File.basename(f, '.db').split('_')
99
+ type = parts[0].to_sym
100
+ d = MmapedDict.new(f)
101
+
102
+ begin
103
+ d.all_values.each do |key, value|
104
+ metric_name, name, labelnames, labelvalues = JSON.parse(key)
105
+ metric = metrics.fetch(metric_name,
106
+ metric_name: metric_name,
107
+ help: 'Multiprocess metric',
108
+ type: type,
109
+ samples: []
110
+ )
111
+ if type == :gauge
112
+ pid = parts[2]
113
+ metric[:multiprocess_mode] = parts[1]
114
+ metric[:samples] += [[name, labelnames.zip(labelvalues) + [['pid', pid]], value]]
115
+ else
116
+ # The duplicates and labels are fixed in the next for.
117
+ metric[:samples] += [[name, labelnames.zip(labelvalues), value]]
118
+ end
119
+ metrics[metric_name] = metric
120
+ end
121
+ ensure
122
+ d.close
123
+ end
124
+ end
125
+ metrics
126
+ end
127
+
121
128
  def representation(metric, label_set, value, &block)
122
129
  set = metric.base_labels.merge(label_set)
123
130
 
@@ -9,7 +9,7 @@ module Prometheus
9
9
  class Gauge < Metric
10
10
  def initialize(name, docstring, base_labels = {}, multiprocess_mode=:all)
11
11
  super(name, docstring, base_labels)
12
- if ValueClass.multiprocess and ![:min, :max, :livesum, :liveall, :all].include?(multiprocess_mode)
12
+ if value_class.multiprocess and ![:min, :max, :livesum, :liveall, :all].include?(multiprocess_mode)
13
13
  raise ArgumentError, 'Invalid multiprocess mode: ' + multiprocess_mode
14
14
  end
15
15
  @multiprocess_mode = multiprocess_mode
@@ -20,7 +20,7 @@ module Prometheus
20
20
  end
21
21
 
22
22
  def default(labels)
23
- ValueClass.new(type, @name, @name, labels, @multiprocess_mode)
23
+ value_object(type, @name, @name, labels, @multiprocess_mode)
24
24
  end
25
25
 
26
26
  # Sets the value for the given label set
@@ -1,6 +1,5 @@
1
- # encoding: UTF-8
2
-
3
1
  require 'prometheus/client/metric'
2
+ require 'prometheus/client/uses_value_type'
4
3
 
5
4
  module Prometheus
6
5
  module Client
@@ -10,15 +9,16 @@ module Prometheus
10
9
  class Histogram < Metric
11
10
  # Value represents the state of a Histogram at a given point.
12
11
  class Value < Hash
12
+ include UsesValueType
13
13
  attr_accessor :sum, :total
14
14
 
15
15
  def initialize(type, name, labels, buckets)
16
- @sum = ValueClass.new(type, name, "#{name}_sum", labels)
16
+ @sum = value_object(type, name, "#{name}_sum", labels)
17
17
  # TODO: get rid of total and use +Inf bucket instead.
18
- @total = ValueClass.new(type, name, "#{name}_count", labels)
18
+ @total = value_object(type, name, "#{name}_count", labels)
19
19
 
20
20
  buckets.each do |bucket|
21
- self[bucket] = ValueClass.new(type, name, "#{name}_bucket", labels.merge({:le => bucket.to_s}))
21
+ self[bucket] = value_object(type, name, "#{name}_bucket", labels.merge({ :le => bucket.to_s }))
22
22
  end
23
23
  end
24
24
 
@@ -1,25 +1,24 @@
1
- # encoding: UTF-8
2
-
3
1
  require 'thread'
4
2
  require 'prometheus/client/label_set_validator'
5
- require 'prometheus/client/valuetype'
3
+ require 'prometheus/client/uses_value_type'
6
4
 
7
5
  module Prometheus
8
6
  module Client
9
7
  # Metric
10
8
  class Metric
9
+ include UsesValueType
11
10
  attr_reader :name, :docstring, :base_labels
12
11
 
13
12
  def initialize(name, docstring, base_labels = {})
14
13
  @mutex = Mutex.new
15
- case type
16
- when :summary
17
- @validator = LabelSetValidator.new(['quantile'])
18
- when :histogram
19
- @validator = LabelSetValidator.new(['le'])
20
- else
21
- @validator = LabelSetValidator.new
22
- end
14
+ @validator = case type
15
+ when :summary
16
+ LabelSetValidator.new(['quantile'])
17
+ when :histogram
18
+ LabelSetValidator.new(['le'])
19
+ else
20
+ LabelSetValidator.new
21
+ end
23
22
  @values = Hash.new { |hash, key| hash[key] = default(key) }
24
23
 
25
24
  validate_name(name)
@@ -50,7 +49,7 @@ module Prometheus
50
49
  private
51
50
 
52
51
  def default(labels)
53
- ValueClass.new(type, @name, @name, labels)
52
+ value_object(type, @name, @name, labels)
54
53
  end
55
54
 
56
55
  def validate_name(name)
@@ -0,0 +1,123 @@
1
+ module Prometheus
2
+ module Client
3
+ class ParsingError < StandardError; end
4
+
5
+ # A dict of doubles, backed by an mmapped file.
6
+ #
7
+ # The file starts with a 4 byte int, indicating how much of it is used.
8
+ # Then 4 bytes of padding.
9
+ # There's then a number of entries, consisting of a 4 byte int which is the
10
+ # size of the next field, a utf-8 encoded string key, padding to an 8 byte
11
+ # alignment, and then a 8 byte float which is the value.
12
+ #
13
+ # TODO(julius): dealing with Mmap.new, truncate etc. errors?
14
+ class MmapedDict
15
+ MINIMUM_SIZE = 4.freeze
16
+ attr_reader :m, :capacity, :used, :positions
17
+
18
+ def initialize(filename)
19
+ @mutex = Mutex.new
20
+ @f = File.open(filename, 'a+b')
21
+ process_file_wrappe_error
22
+ end
23
+
24
+ # Yield (key, value, pos). No locking is performed.
25
+ def all_values
26
+ read_all_values.map { |k, v, p| [k, v] }
27
+ end
28
+
29
+ def read_value(key)
30
+ @mutex.synchronize do
31
+ init_value(key) unless @positions.has_key?(key)
32
+ end
33
+ pos = @positions[key]
34
+ # We assume that reading from an 8 byte aligned value is atomic.
35
+ @m[pos..pos+7].unpack('d')[0]
36
+ end
37
+
38
+ def write_value(key, value)
39
+ @mutex.synchronize do
40
+ init_value(key) unless @positions.has_key?(key)
41
+ end
42
+ pos = @positions[key]
43
+ # We assume that writing to an 8 byte aligned value is atomic.
44
+ @m[pos..pos+7] = [value].pack('d')
45
+ end
46
+
47
+ def close()
48
+ @m.munmap
49
+ @f.close
50
+ end
51
+
52
+ private
53
+
54
+ def process_file_wrappe_error
55
+ process_file
56
+ rescue StandardError => e
57
+ raise ParsingError.new("exception #{e} while processing metrics file #{@f.path}")
58
+ end
59
+
60
+ def process_file
61
+ if @f.size < MINIMUM_SIZE
62
+ @f.truncate(initial_mmap_file_size)
63
+ end
64
+ @f.truncate(initial_mmap_file_size)
65
+
66
+ @capacity = @f.size
67
+ @m = Mmap.new(@f.path, 'rw', Mmap::MAP_SHARED)
68
+ # @m.mlock # TODO: Why does this raise an error?
69
+
70
+ @positions = {}
71
+ @used = @m[0..3].unpack('l')[0]
72
+ if @used == 0
73
+ @used = 8
74
+ @m[0..3] = [@used].pack('l')
75
+ else
76
+ read_all_values.each do |key, _, pos|
77
+ @positions[key] = pos
78
+ end
79
+ end
80
+ end
81
+
82
+ def initial_mmap_file_size
83
+ Prometheus::Client.configuration.initial_mmap_file_size
84
+ end
85
+
86
+ # Initialize a value. Lock must be held by caller.
87
+ def init_value(key)
88
+ # Pad to be 8-byte aligned.
89
+ padded = key + (' ' * (8 - (key.length + 4) % 8))
90
+ value = [key.length, padded, 0.0].pack("lA#{padded.length}d")
91
+ while @used + value.length > @capacity
92
+ @capacity *= 2
93
+ @f.truncate(@capacity)
94
+ @m.unmap
95
+ @m = Mmap.new(@f.path, 'rw', Mmap::MAP_SHARED)
96
+ end
97
+ @m[@used..@used + value.length] = value
98
+
99
+ # Update how much space we've used.
100
+ @used += value.length
101
+ @m[0..3] = [@used].pack('l')
102
+ @positions[key] = @used - 8
103
+ end
104
+
105
+ # Yield (key, value, pos). No locking is performed.
106
+ def read_all_values
107
+ pos = 8
108
+ values = []
109
+ while pos < @used
110
+ encoded_len = @m[pos..-1].unpack('l')[0]
111
+ pos += 4
112
+ encoded = @m[pos..-1].unpack("A#{encoded_len}")[0]
113
+ padded_len = encoded_len + (8 - (encoded_len + 4) % 8)
114
+ pos += padded_len
115
+ value = @m[pos..-1].unpack('d')[0]
116
+ values << [encoded, value, pos]
117
+ pos += 8
118
+ end
119
+ values
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,80 @@
1
+ require 'prometheus/client/mmaped_dict'
2
+ require 'json'
3
+
4
+ module Prometheus
5
+ module Client
6
+ # A float protected by a mutex backed by a per-process mmaped file.
7
+ class MmapedValue
8
+ @@files = {}
9
+ @@files_lock = Mutex.new
10
+ @@pid = Process.pid
11
+
12
+ def initialize(type, metric_name, name, labels, multiprocess_mode='')
13
+ file_prefix = type.to_s
14
+ if type == :gauge
15
+ file_prefix += '_' + multiprocess_mode.to_s
16
+ end
17
+
18
+ @@files_lock.synchronize do
19
+ unless @@files.has_key?(file_prefix)
20
+ filename = File.join(Prometheus::Client.configuration.multiprocess_files_dir, "#{file_prefix}_#{@@pid}.db")
21
+ @@files[file_prefix] = MmapedDict.new(filename)
22
+ end
23
+ end
24
+
25
+ @file = @@files[file_prefix]
26
+ labelnames = []
27
+ labelvalues = []
28
+ labels.each do |k, v|
29
+ labelnames << k
30
+ labelvalues << v
31
+ end
32
+
33
+ @key = [metric_name, name, labelnames, labelvalues].to_json
34
+ @value = read_value(@key)
35
+ @mutex = Mutex.new
36
+ end
37
+
38
+ def increment(amount=1)
39
+ @mutex.synchronize do
40
+ @value += amount
41
+ write_value(@key, @value)
42
+ @value
43
+ end
44
+ end
45
+
46
+ def set(value)
47
+ @mutex.synchronize do
48
+ @value = value
49
+ write_value(@key, @value)
50
+ @value
51
+ end
52
+ end
53
+
54
+ def get
55
+ @mutex.synchronize do
56
+ return @value
57
+ end
58
+ end
59
+
60
+ def self.multiprocess
61
+ true
62
+ end
63
+
64
+ private
65
+
66
+ def write_value(key, val)
67
+ @file.write_value(key, val)
68
+ rescue StandardError => e
69
+ Prometheus::Client.logger.warn("writing value to #{@file.path} failed with #{e}")
70
+ end
71
+
72
+ def read_value(key)
73
+ @file.read_value(key)
74
+ rescue StandardError => e
75
+ Prometheus::Client.logger.warn("readomg value from #{@file.path} failed with #{e}")
76
+ end
77
+ end
78
+ end
79
+ end
80
+
@@ -11,7 +11,7 @@ module Prometheus
11
11
  class Exporter
12
12
  attr_reader :app, :registry, :path
13
13
 
14
- FORMATS = [Formats::Text].freeze
14
+ FORMATS = [Formats::Text].freeze
15
15
  FALLBACK = Formats::Text
16
16
 
17
17
  def initialize(app, options = {})
@@ -62,8 +62,11 @@ module Prometheus
62
62
  end
63
63
 
64
64
  def respond_with(format)
65
- # For now we're only supporting mmapped ValueClass.
66
- response = format.marshal_multiprocess
65
+ response = if Prometheus::Client.configuration.value_class.multiprocess
66
+ format.marshal_multiprocess
67
+ else
68
+ format.marshal
69
+ end
67
70
  [
68
71
  200,
69
72
  { 'Content-Type' => format::CONTENT_TYPE },
@@ -0,0 +1,28 @@
1
+ require 'json'
2
+ require 'mmap'
3
+
4
+ module Prometheus
5
+ module Client
6
+ class SimpleValue
7
+ def initialize(type, metric_name, name, labels, value = 0)
8
+ @value = value
9
+ end
10
+
11
+ def set(value)
12
+ @value = value
13
+ end
14
+
15
+ def increment(by = 1)
16
+ @value += by
17
+ end
18
+
19
+ def get
20
+ @value
21
+ end
22
+
23
+ def self.multiprocess
24
+ false
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,6 +1,5 @@
1
- # encoding: UTF-8
2
-
3
1
  require 'prometheus/client/metric'
2
+ require 'prometheus/client/uses_value_type'
4
3
 
5
4
  module Prometheus
6
5
  module Client
@@ -11,16 +10,17 @@ module Prometheus
11
10
 
12
11
  # Value represents the state of a Summary at a given point.
13
12
  class Value < Hash
13
+ include UsesValueType
14
14
  attr_accessor :sum, :total
15
15
 
16
16
  def initialize(type, name, labels)
17
- @sum = ValueClass.new(type, name, "#{name}_sum", labels)
18
- @total = ValueClass.new(type, name, "#{name}_count", labels)
17
+ @sum = value_object(type, name, "#{name}_sum", labels)
18
+ @total = value_object(type, name, "#{name}_count", labels)
19
19
  end
20
20
 
21
21
  def observe(value)
22
22
  @sum.increment(value)
23
- @total.increment()
23
+ @total.increment
24
24
  end
25
25
  end
26
26
 
@@ -37,6 +37,7 @@ module Prometheus
37
37
  label_set = label_set_for(labels)
38
38
  synchronize { @values[label_set].observe(value) }
39
39
  end
40
+
40
41
  alias add observe
41
42
  deprecate :add, :observe, 2016, 10
42
43
 
@@ -0,0 +1,19 @@
1
+ require 'prometheus/client/simple_value'
2
+
3
+ module Prometheus
4
+ module Client
5
+ # Module providing convenience methods for creating value_object
6
+ module UsesValueType
7
+ def value_class
8
+ Prometheus::Client.configuration.value_class
9
+ end
10
+
11
+ def value_object(type, metric_name, name, labels, *args)
12
+ value_class.new(type, metric_name, name, labels, *args)
13
+ rescue StandardError => e
14
+ Prometheus::Client.logger.info("error #{e} while creating instance of #{value_class} defaultig to SimpleValue")
15
+ Prometheus::Client::SimpleValue.new(type, metric_name, name, labels)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Prometheus
4
4
  module Client
5
- VERSION = '0.7.0.beta8'
5
+ VERSION = '0.7.0.beta9'
6
6
  end
7
7
  end
@@ -1,13 +1,28 @@
1
- # encoding: UTF-8
2
-
3
1
  require 'prometheus/client/registry'
2
+ require 'prometheus/client/configuration'
4
3
 
5
4
  module Prometheus
6
5
  # Client is a ruby implementation for a Prometheus compatible client.
7
6
  module Client
8
- # Returns a default registry object
9
- def self.registry
10
- @registry ||= Registry.new
7
+ class << self
8
+ attr_writer :configuration
9
+
10
+ def configuration
11
+ @configuration ||= Configuration.new
12
+ end
13
+
14
+ def configure
15
+ yield(configuration)
16
+ end
17
+
18
+ # Returns a default registry object
19
+ def registry
20
+ @registry ||= Registry.new
21
+ end
22
+
23
+ def logger
24
+ configuration.logger
25
+ end
11
26
  end
12
27
  end
13
28
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prometheus-client-mmap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0.beta8
4
+ version: 0.7.0.beta9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Schmidt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-04 00:00:00.000000000 Z
11
+ date: 2017-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mmap2
@@ -40,18 +40,22 @@ files:
40
40
  - README.md
41
41
  - lib/prometheus.rb
42
42
  - lib/prometheus/client.rb
43
+ - lib/prometheus/client/configuration.rb
43
44
  - lib/prometheus/client/counter.rb
44
45
  - lib/prometheus/client/formats/text.rb
45
46
  - lib/prometheus/client/gauge.rb
46
47
  - lib/prometheus/client/histogram.rb
47
48
  - lib/prometheus/client/label_set_validator.rb
48
49
  - lib/prometheus/client/metric.rb
50
+ - lib/prometheus/client/mmaped_dict.rb
51
+ - lib/prometheus/client/mmaped_value.rb
49
52
  - lib/prometheus/client/push.rb
50
53
  - lib/prometheus/client/rack/collector.rb
51
54
  - lib/prometheus/client/rack/exporter.rb
52
55
  - lib/prometheus/client/registry.rb
56
+ - lib/prometheus/client/simple_value.rb
53
57
  - lib/prometheus/client/summary.rb
54
- - lib/prometheus/client/valuetype.rb
58
+ - lib/prometheus/client/uses_value_type.rb
55
59
  - lib/prometheus/client/version.rb
56
60
  homepage: https://gitlab.com/gitlab-org/prometheus-client-mmap
57
61
  licenses:
@@ -1,207 +0,0 @@
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(Prometheus::Client::Multiprocdir, "#{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
- Multiprocdir = ENV['prometheus_multiproc_dir']
94
- ValueClass = MmapedValue
95
- else
96
- # Only support mmap values for now.
97
- Multiprocdir = '/var/run/gitlab/unicorn'
98
- ValueClass = MmapedValue
99
- end
100
- end
101
- end
102
-
103
- # A dict of doubles, backed by an mmapped file.
104
- #
105
- # The file starts with a 4 byte int, indicating how much of it is used.
106
- # Then 4 bytes of padding.
107
- # There's then a number of entries, consisting of a 4 byte int which is the
108
- # size of the next field, a utf-8 encoded string key, padding to an 8 byte
109
- # alignment, and then a 8 byte float which is the value.
110
- #
111
- # TODO(julius): dealing with Mmap.new, truncate etc. errors?
112
- class MmapedDict
113
- @@INITIAL_MMAP_SIZE = 1024*1024
114
-
115
- attr_reader :m, :capacity, :used, :positions
116
-
117
- def initialize(filename)
118
- @mutex = Mutex.new
119
- @f = File.open(filename, 'a+b')
120
- if @f.size == 0
121
- @f.truncate(@@INITIAL_MMAP_SIZE)
122
- end
123
- @capacity = @f.size
124
- @m = Mmap.new(filename, 'rw', Mmap::MAP_SHARED)
125
- # @m.mlock # TODO: Why does this raise an error?
126
-
127
- @positions = {}
128
- @used = @m[0..3].unpack('l')[0]
129
- if @used == 0
130
- @used = 8
131
- @m[0..3] = [@used].pack('l')
132
- else
133
- read_all_values.each do |key, _, pos|
134
- @positions[key] = pos
135
- end
136
- end
137
- end
138
-
139
- # Yield (key, value, pos). No locking is performed.
140
- def all_values
141
- read_all_values.map { |k, v, p| [k, v] }
142
- end
143
-
144
- def read_value(key)
145
- @mutex.synchronize do
146
- if !@positions.has_key?(key)
147
- init_value(key)
148
- end
149
- end
150
- pos = @positions[key]
151
- # We assume that reading from an 8 byte aligned value is atomic.
152
- @m[pos..pos+7].unpack('d')[0]
153
- end
154
-
155
- def write_value(key, value)
156
- @mutex.synchronize do
157
- if !@positions.has_key?(key)
158
- init_value(key)
159
- end
160
- end
161
- pos = @positions[key]
162
- # We assume that writing to an 8 byte aligned value is atomic.
163
- @m[pos..pos+7] = [value].pack('d')
164
- end
165
-
166
- def close()
167
- @m.munmap
168
- @f.close
169
- end
170
-
171
- private
172
-
173
- # Initialize a value. Lock must be held by caller.
174
- def init_value(key)
175
- # Pad to be 8-byte aligned.
176
- padded = key + (' ' * (8 - (key.length + 4) % 8))
177
- value = [key.length, padded, 0.0].pack("lA#{padded.length}d")
178
- while @used + value.length > @capacity
179
- @capacity *= 2
180
- @f.truncate(@capacity)
181
- @m = Mmap.new(@f.path, 'rw', Mmap::MAP_SHARED)
182
- end
183
- @m[@used..@used + value.length] = value
184
-
185
- # Update how much space we've used.
186
- @used += value.length
187
- @m[0..3] = [@used].pack('l')
188
- @positions[key] = @used - 8
189
- end
190
-
191
- # Yield (key, value, pos). No locking is performed.
192
- def read_all_values
193
- pos = 8
194
- values = []
195
- while pos < @used
196
- encoded_len = @m[pos..-1].unpack('l')[0]
197
- pos += 4
198
- encoded = @m[pos..-1].unpack("A#{encoded_len}")[0]
199
- padded_len = encoded_len + (8 - (encoded_len + 4) % 8)
200
- pos += padded_len
201
- value = @m[pos..-1].unpack('d')[0]
202
- values << [encoded, value, pos]
203
- pos += 8
204
- end
205
- values
206
- end
207
- end