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

Sign up to get free protection for your applications and to get access to all the features.
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