custom-prometheus-client 0.4.3
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 +7 -0
- data/README.md +27 -0
- data/lib/prometheus.rb +5 -0
- data/lib/prometheus/client.rb +13 -0
- data/lib/prometheus/client/counter.rb +27 -0
- data/lib/prometheus/client/formats/json.rb +33 -0
- data/lib/prometheus/client/formats/text.rb +81 -0
- data/lib/prometheus/client/gauge.rb +20 -0
- data/lib/prometheus/client/label_set_validator.rb +69 -0
- data/lib/prometheus/client/metric.rb +74 -0
- data/lib/prometheus/client/push.rb +68 -0
- data/lib/prometheus/client/rack/collector.rb +92 -0
- data/lib/prometheus/client/rack/exporter.rb +92 -0
- data/lib/prometheus/client/registry.rb +59 -0
- data/lib/prometheus/client/summary.rb +60 -0
- data/lib/prometheus/client/version.rb +7 -0
- metadata +74 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 59513bdd6f293705f4f34c864ba6406d0fa9522d
|
4
|
+
data.tar.gz: a5f20ee8c59ff151cec16de150afff6924669fc9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c2f628acc7b16dbfe82db00b854705a8a20d53660d6e03e295afe5373284656b404852234ab209b40e895bcecfcc2534bf6f29609767026d77d32a8f7c8f97d7
|
7
|
+
data.tar.gz: da87469693d2646d160927edb6a928530a1f57cfb7bb4f5aafb79bdf4058dc162f450b7f15738a3558dfbd982e8ae12d81b83deba3400d8cdaafc0a55e2396b4
|
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# Custom Prometheus Ruby Client
|
2
|
+
|
3
|
+
This repo is essentially a fork of https://github.com/prometheus/client_ruby and adds the following behaviour to the captured metrics.
|
4
|
+
|
5
|
+
- It takes the service_name as an options to the rake middleware (see usage)
|
6
|
+
- It adds the following labels to the metrics
|
7
|
+
* User Agent
|
8
|
+
* Accept
|
9
|
+
* Content Type
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
Add the following to your Gemfile
|
13
|
+
```
|
14
|
+
gem 'custom-prometheus-client'
|
15
|
+
```
|
16
|
+
|
17
|
+
Then in your config.ru add the following (Change 'my_app' to the name of your app being monitored)
|
18
|
+
```
|
19
|
+
require 'prometheus/client/rack/collector'
|
20
|
+
require 'prometheus/client/rack/exporter'
|
21
|
+
|
22
|
+
options = { service_name: 'my_app'}
|
23
|
+
use Prometheus::Client::Rack::Collector, options
|
24
|
+
use Prometheus::Client::Rack::Exporter
|
25
|
+
```
|
26
|
+
|
27
|
+
Configure your prometheus.yml to scrape /metrics from rails service
|
data/lib/prometheus.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'prometheus/client/registry'
|
4
|
+
|
5
|
+
module Prometheus
|
6
|
+
# Client is a ruby implementation for a Prometheus compatible client.
|
7
|
+
module Client
|
8
|
+
# Returns a default registry object
|
9
|
+
def self.registry
|
10
|
+
@registry ||= Registry.new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'prometheus/client/metric'
|
4
|
+
|
5
|
+
module Prometheus
|
6
|
+
module Client
|
7
|
+
# Counter is a metric that exposes merely a sum or tally of things.
|
8
|
+
class Counter < Metric
|
9
|
+
def type
|
10
|
+
:counter
|
11
|
+
end
|
12
|
+
|
13
|
+
def increment(labels = {}, by = 1)
|
14
|
+
fail ArgumentError, 'increment must be a non-negative number' if by < 0
|
15
|
+
|
16
|
+
label_set = label_set_for(labels)
|
17
|
+
synchronize { @values[label_set] += by }
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def default
|
23
|
+
0
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Prometheus
|
6
|
+
module Client
|
7
|
+
module Formats
|
8
|
+
# JSON format is a deprecated, human-readable format to expose the state
|
9
|
+
# of a given registry.
|
10
|
+
module JSON
|
11
|
+
MEDIA_TYPE = 'application/json'
|
12
|
+
SCHEMA = 'prometheus/telemetry'
|
13
|
+
VERSION = '0.0.2'
|
14
|
+
CONTENT_TYPE = %(#{MEDIA_TYPE}; schema="#{SCHEMA}"; version=#{VERSION})
|
15
|
+
|
16
|
+
MAPPING = { summary: :histogram }
|
17
|
+
|
18
|
+
def self.marshal(registry)
|
19
|
+
registry.metrics.map do |metric|
|
20
|
+
{
|
21
|
+
baseLabels: metric.base_labels.merge(__name__: metric.name),
|
22
|
+
docstring: metric.docstring,
|
23
|
+
metric: {
|
24
|
+
type: MAPPING[metric.type] || metric.type,
|
25
|
+
value: metric.values.map { |l, v| { labels: l, value: v } },
|
26
|
+
},
|
27
|
+
}
|
28
|
+
end.to_json
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Prometheus
|
4
|
+
module Client
|
5
|
+
module Formats
|
6
|
+
# Text format is human readable mainly used for manual inspection.
|
7
|
+
module Text
|
8
|
+
MEDIA_TYPE = 'text/plain'
|
9
|
+
VERSION = '0.0.4'
|
10
|
+
CONTENT_TYPE = "#{MEDIA_TYPE}; version=#{VERSION}"
|
11
|
+
|
12
|
+
METRIC_LINE = '%s%s %s'
|
13
|
+
TYPE_LINE = '# TYPE %s %s'
|
14
|
+
HELP_LINE = '# HELP %s %s'
|
15
|
+
|
16
|
+
LABEL = '%s="%s"'
|
17
|
+
SEPARATOR = ','
|
18
|
+
DELIMITER = "\n"
|
19
|
+
|
20
|
+
REGEX = { doc: /[\n\\]/, label: /[\n\\"]/ }
|
21
|
+
REPLACE = { "\n" => '\n', '\\' => '\\\\', '"' => '\"' }
|
22
|
+
|
23
|
+
def self.marshal(registry)
|
24
|
+
lines = []
|
25
|
+
|
26
|
+
registry.metrics.each do |metric|
|
27
|
+
lines << format(TYPE_LINE, metric.name, metric.type)
|
28
|
+
lines << format(HELP_LINE, metric.name, escape(metric.docstring))
|
29
|
+
|
30
|
+
metric.values.each do |label_set, value|
|
31
|
+
representation(metric, label_set, value) { |l| lines << l }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# there must be a trailing delimiter
|
36
|
+
(lines << nil).join(DELIMITER)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def self.representation(metric, label_set, value, &block)
|
42
|
+
set = metric.base_labels.merge(label_set)
|
43
|
+
|
44
|
+
if metric.type == :summary
|
45
|
+
summary(metric.name, set, value, &block)
|
46
|
+
else
|
47
|
+
yield metric(metric.name, labels(set), value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.summary(name, set, value)
|
52
|
+
value.each do |q, v|
|
53
|
+
yield metric(name, labels(set.merge(quantile: q)), v)
|
54
|
+
end
|
55
|
+
|
56
|
+
l = labels(set)
|
57
|
+
yield metric("#{name}_sum", l, value.sum)
|
58
|
+
yield metric("#{name}_count", l, value.total)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.metric(name, labels, value)
|
62
|
+
format(METRIC_LINE, name, labels, value)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.labels(set)
|
66
|
+
return if set.empty?
|
67
|
+
|
68
|
+
strings = set.each_with_object([]) do |(key, value), memo|
|
69
|
+
memo << format(LABEL, key, escape(value, :label))
|
70
|
+
end
|
71
|
+
|
72
|
+
"{#{strings.join(SEPARATOR)}}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.escape(string, format = :doc)
|
76
|
+
string.to_s.gsub(REGEX[format], REPLACE)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'prometheus/client/metric'
|
4
|
+
|
5
|
+
module Prometheus
|
6
|
+
module Client
|
7
|
+
# A Gauge is a metric that exposes merely an instantaneous value or some
|
8
|
+
# snapshot thereof.
|
9
|
+
class Gauge < Metric
|
10
|
+
def type
|
11
|
+
:gauge
|
12
|
+
end
|
13
|
+
|
14
|
+
# Sets the value for the given label set
|
15
|
+
def set(labels, value)
|
16
|
+
@values[label_set_for(labels)] = value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Prometheus
|
4
|
+
module Client
|
5
|
+
# LabelSetValidator ensures that all used label sets comply with the
|
6
|
+
# Prometheus specification.
|
7
|
+
class LabelSetValidator
|
8
|
+
# TODO: we might allow setting :instance in the future
|
9
|
+
RESERVED_LABELS = [:job, :instance]
|
10
|
+
|
11
|
+
class LabelSetError < StandardError; end
|
12
|
+
class InvalidLabelSetError < LabelSetError; end
|
13
|
+
class InvalidLabelError < LabelSetError; end
|
14
|
+
class ReservedLabelError < LabelSetError; end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@validated = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def valid?(labels)
|
21
|
+
unless labels.respond_to?(:all?)
|
22
|
+
fail InvalidLabelSetError, "#{labels} is not a valid label set"
|
23
|
+
end
|
24
|
+
|
25
|
+
labels.all? do |key, _|
|
26
|
+
validate_symbol(key)
|
27
|
+
validate_name(key)
|
28
|
+
validate_reserved_key(key)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate(labels)
|
33
|
+
return labels if @validated.key?(labels.hash)
|
34
|
+
|
35
|
+
valid?(labels)
|
36
|
+
|
37
|
+
unless @validated.empty? || match?(labels, @validated.first.last)
|
38
|
+
fail InvalidLabelSetError, 'labels must have the same signature'
|
39
|
+
end
|
40
|
+
|
41
|
+
@validated[labels.hash] = labels
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def match?(a, b)
|
47
|
+
a.keys.sort == b.keys.sort
|
48
|
+
end
|
49
|
+
|
50
|
+
def validate_symbol(key)
|
51
|
+
return true if key.is_a?(Symbol)
|
52
|
+
|
53
|
+
fail InvalidLabelError, "label #{key} is not a symbol"
|
54
|
+
end
|
55
|
+
|
56
|
+
def validate_name(key)
|
57
|
+
return true unless key.to_s.start_with?('__')
|
58
|
+
|
59
|
+
fail ReservedLabelError, "label #{key} must not start with __"
|
60
|
+
end
|
61
|
+
|
62
|
+
def validate_reserved_key(key)
|
63
|
+
return true unless RESERVED_LABELS.include?(key)
|
64
|
+
|
65
|
+
fail ReservedLabelError, "#{key} is reserved"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
require 'prometheus/client/label_set_validator'
|
5
|
+
|
6
|
+
module Prometheus
|
7
|
+
module Client
|
8
|
+
# Metric
|
9
|
+
class Metric
|
10
|
+
attr_reader :name, :docstring, :base_labels
|
11
|
+
|
12
|
+
def initialize(name, docstring, base_labels = {})
|
13
|
+
@mutex = Mutex.new
|
14
|
+
@validator = LabelSetValidator.new
|
15
|
+
@values = Hash.new { |hash, key| hash[key] = default }
|
16
|
+
|
17
|
+
validate_name(name)
|
18
|
+
validate_docstring(docstring)
|
19
|
+
@validator.valid?(base_labels)
|
20
|
+
|
21
|
+
@name = name
|
22
|
+
@docstring = docstring
|
23
|
+
@base_labels = base_labels
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the metric type
|
27
|
+
def type
|
28
|
+
fail NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the value for the given label set
|
32
|
+
def get(labels = {})
|
33
|
+
@validator.valid?(labels)
|
34
|
+
|
35
|
+
@values[labels]
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns all label sets with their values
|
39
|
+
def values
|
40
|
+
synchronize do
|
41
|
+
@values.each_with_object({}) do |(labels, value), memo|
|
42
|
+
memo[labels] = value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def default
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def validate_name(name)
|
54
|
+
return true if name.is_a?(Symbol)
|
55
|
+
|
56
|
+
fail ArgumentError, 'given name must be a symbol'
|
57
|
+
end
|
58
|
+
|
59
|
+
def validate_docstring(docstring)
|
60
|
+
return true if docstring.respond_to?(:empty?) && !docstring.empty?
|
61
|
+
|
62
|
+
fail ArgumentError, 'docstring must be given'
|
63
|
+
end
|
64
|
+
|
65
|
+
def label_set_for(labels)
|
66
|
+
@validator.validate(labels)
|
67
|
+
end
|
68
|
+
|
69
|
+
def synchronize(&block)
|
70
|
+
@mutex.synchronize(&block)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
require 'prometheus/client'
|
7
|
+
require 'prometheus/client/formats/text'
|
8
|
+
|
9
|
+
module Prometheus
|
10
|
+
# Client is a ruby implementation for a Prometheus compatible client.
|
11
|
+
module Client
|
12
|
+
# Push implements a simple way to transmit a given registry to a given
|
13
|
+
# Pushgateway.
|
14
|
+
class Push
|
15
|
+
DEFAULT_GATEWAY = 'http://localhost:9091'
|
16
|
+
PATH = '/metrics/jobs/%s'
|
17
|
+
INSTANCE_PATH = '/metrics/jobs/%s/instances/%s'
|
18
|
+
HEADER = { 'Content-Type' => Formats::Text::CONTENT_TYPE }
|
19
|
+
|
20
|
+
attr_reader :job, :instance, :gateway, :path
|
21
|
+
|
22
|
+
def initialize(job, instance = nil, gateway = nil)
|
23
|
+
@job = job
|
24
|
+
@instance = instance
|
25
|
+
@gateway = gateway || DEFAULT_GATEWAY
|
26
|
+
@uri = parse(@gateway)
|
27
|
+
@path = build_path(job, instance)
|
28
|
+
@http = Net::HTTP.new(@uri.host, @uri.port)
|
29
|
+
end
|
30
|
+
|
31
|
+
def add(registry)
|
32
|
+
request('POST', registry)
|
33
|
+
end
|
34
|
+
|
35
|
+
def replace(registry)
|
36
|
+
request('PUT', registry)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def parse(url)
|
42
|
+
uri = URI.parse(url)
|
43
|
+
|
44
|
+
if uri.scheme == 'http'
|
45
|
+
uri
|
46
|
+
else
|
47
|
+
fail ArgumentError, 'only HTTP gateway URLs are supported currently.'
|
48
|
+
end
|
49
|
+
rescue URI::InvalidURIError => e
|
50
|
+
raise ArgumentError, "#{url} is not a valid URL: #{e}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def build_path(job, instance)
|
54
|
+
if instance
|
55
|
+
format(INSTANCE_PATH, URI.escape(job), URI.escape(instance))
|
56
|
+
else
|
57
|
+
format(PATH, URI.escape(job))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def request(method, registry)
|
62
|
+
data = Formats::Text.marshal(registry)
|
63
|
+
|
64
|
+
@http.send_request(method, path, data, HEADER)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'prometheus/client'
|
4
|
+
|
5
|
+
module Prometheus
|
6
|
+
module Client
|
7
|
+
module Rack
|
8
|
+
# Collector is a Rack middleware that provides a sample implementation of
|
9
|
+
# a HTTP tracer. The default label builder can be modified to export a
|
10
|
+
# different set of labels per recorded metric.
|
11
|
+
class Collector
|
12
|
+
attr_reader :app, :registry
|
13
|
+
|
14
|
+
def initialize(app, options = {}, &label_builder)
|
15
|
+
@app = app
|
16
|
+
@registry = options[:registry] || Client.registry
|
17
|
+
@label_builder = label_builder || DEFAULT_LABEL_BUILDER
|
18
|
+
# The service name should be read from config or could be git repo name
|
19
|
+
@service_name = options[:service_name]
|
20
|
+
init_request_metrics
|
21
|
+
init_exception_metrics
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(env) # :nodoc:
|
25
|
+
trace(env) { @app.call(env) }
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
DEFAULT_LABEL_BUILDER = proc do |env|
|
31
|
+
{
|
32
|
+
method: env['REQUEST_METHOD'].downcase,
|
33
|
+
ua: env['HTTP_USER_AGENT'].to_s,
|
34
|
+
accept: env['HTTP_ACCEPT'],
|
35
|
+
host: env['HTTP_HOST'].to_s,
|
36
|
+
path: env['PATH_INFO'].to_s,
|
37
|
+
content_type: env['CONTENT_TYPE'].to_s
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def init_request_metrics
|
42
|
+
@request_metrics = @service_name + "__counter_requests_total"
|
43
|
+
@response_metrics = @service_name + "__counter_responses_total"
|
44
|
+
@response_time_metrics = @service_name + "__summary_responses_duration_microseconds"
|
45
|
+
|
46
|
+
@requests = @registry.counter(
|
47
|
+
@request_metrics.to_sym,
|
48
|
+
'Total number of HTTP requests.')
|
49
|
+
@requests_duration = @registry.counter(
|
50
|
+
@response_metrics.to_sym,
|
51
|
+
'Total time spent answering HTTP requests ' \
|
52
|
+
'(microseconds).')
|
53
|
+
@durations = @registry.summary(
|
54
|
+
@response_time_metrics.to_sym,
|
55
|
+
'A histogram of the response latency (microseconds).')
|
56
|
+
end
|
57
|
+
|
58
|
+
def init_exception_metrics
|
59
|
+
@exceptions = @registry.counter(
|
60
|
+
:http_exceptions_total,
|
61
|
+
'A counter of the total number of exceptions raised.')
|
62
|
+
end
|
63
|
+
|
64
|
+
def trace(env)
|
65
|
+
start = Time.now
|
66
|
+
yield.tap do |response|
|
67
|
+
duration = ((Time.now - start) * 1_000_000).to_i
|
68
|
+
record(labels(env, response), duration)
|
69
|
+
end
|
70
|
+
rescue => exception
|
71
|
+
@exceptions.increment(exception: exception.class.name)
|
72
|
+
raise
|
73
|
+
end
|
74
|
+
|
75
|
+
def labels(env, response)
|
76
|
+
@label_builder.call(env).tap do |labels|
|
77
|
+
labels[:code] = response.first.to_s
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def record(labels, duration)
|
82
|
+
@requests.increment(labels)
|
83
|
+
@requests_duration.increment(labels, duration)
|
84
|
+
@durations.add(labels, duration)
|
85
|
+
rescue
|
86
|
+
# TODO: log unexpected exception during request recording
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'prometheus/client'
|
4
|
+
require 'prometheus/client/formats/json'
|
5
|
+
require 'prometheus/client/formats/text'
|
6
|
+
|
7
|
+
module Prometheus
|
8
|
+
module Client
|
9
|
+
module Rack
|
10
|
+
# Exporter is a Rack middleware that provides a sample implementation of
|
11
|
+
# a Prometheus HTTP client API.
|
12
|
+
class Exporter
|
13
|
+
attr_reader :app, :registry, :path
|
14
|
+
|
15
|
+
FORMATS = [Formats::Text, Formats::JSON]
|
16
|
+
FALLBACK = Formats::JSON
|
17
|
+
|
18
|
+
def initialize(app, options = {})
|
19
|
+
@app = app
|
20
|
+
@registry = options[:registry] || Client.registry
|
21
|
+
@path = options[:path] || '/metrics'
|
22
|
+
@acceptable = build_dictionary(FORMATS, FALLBACK)
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(env)
|
26
|
+
if env['PATH_INFO'] == @path
|
27
|
+
format = negotiate(env['HTTP_ACCEPT'], @acceptable)
|
28
|
+
format ? respond_with(format) : not_acceptable(FORMATS)
|
29
|
+
else
|
30
|
+
@app.call(env)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def negotiate(accept, formats)
|
37
|
+
accept = '*/*' if accept.to_s.empty?
|
38
|
+
|
39
|
+
parse(accept).each do |content_type, _|
|
40
|
+
return formats[content_type] if formats.key?(content_type)
|
41
|
+
end
|
42
|
+
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse(header)
|
47
|
+
header.to_s.split(/\s*,\s*/).map do |type|
|
48
|
+
attributes = type.split(/\s*;\s*/)
|
49
|
+
quality = extract_quality(attributes)
|
50
|
+
|
51
|
+
[attributes.join('; '), quality]
|
52
|
+
end.sort_by(&:last).reverse
|
53
|
+
end
|
54
|
+
|
55
|
+
def extract_quality(attributes, default = 1.0)
|
56
|
+
quality = default
|
57
|
+
|
58
|
+
attributes.delete_if do |attr|
|
59
|
+
quality = attr.split('q=').last.to_f if attr.start_with?('q=')
|
60
|
+
end
|
61
|
+
|
62
|
+
quality
|
63
|
+
end
|
64
|
+
|
65
|
+
def respond_with(format)
|
66
|
+
[
|
67
|
+
200,
|
68
|
+
{ 'Content-Type' => format::CONTENT_TYPE },
|
69
|
+
[format.marshal(@registry)],
|
70
|
+
]
|
71
|
+
end
|
72
|
+
|
73
|
+
def not_acceptable(formats)
|
74
|
+
types = formats.map { |format| format::MEDIA_TYPE }
|
75
|
+
|
76
|
+
[
|
77
|
+
406,
|
78
|
+
{ 'Content-Type' => 'text/plain' },
|
79
|
+
["Supported media types: #{types.join(', ')}"],
|
80
|
+
]
|
81
|
+
end
|
82
|
+
|
83
|
+
def build_dictionary(formats, fallback)
|
84
|
+
formats.each_with_object('*/*' => fallback) do |format, memo|
|
85
|
+
memo[format::CONTENT_TYPE] = format
|
86
|
+
memo[format::MEDIA_TYPE] = format
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,59 @@
|
|
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
|
+
|
9
|
+
module Prometheus
|
10
|
+
module Client
|
11
|
+
# Registry
|
12
|
+
class Registry
|
13
|
+
class AlreadyRegisteredError < StandardError; end
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@metrics = {}
|
17
|
+
@mutex = Mutex.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def register(metric)
|
21
|
+
name = metric.name
|
22
|
+
|
23
|
+
@mutex.synchronize do
|
24
|
+
if exist?(name.to_sym)
|
25
|
+
fail AlreadyRegisteredError, "#{name} has already been registered"
|
26
|
+
else
|
27
|
+
@metrics[name.to_sym] = metric
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
metric
|
32
|
+
end
|
33
|
+
|
34
|
+
def counter(name, docstring, base_labels = {})
|
35
|
+
register(Counter.new(name, docstring, base_labels))
|
36
|
+
end
|
37
|
+
|
38
|
+
def summary(name, docstring, base_labels = {})
|
39
|
+
register(Summary.new(name, docstring, base_labels))
|
40
|
+
end
|
41
|
+
|
42
|
+
def gauge(name, docstring, base_labels = {})
|
43
|
+
register(Gauge.new(name, docstring, base_labels))
|
44
|
+
end
|
45
|
+
|
46
|
+
def exist?(name)
|
47
|
+
@metrics.key?(name)
|
48
|
+
end
|
49
|
+
|
50
|
+
def get(name)
|
51
|
+
@metrics[name.to_sym]
|
52
|
+
end
|
53
|
+
|
54
|
+
def metrics
|
55
|
+
@metrics.values
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,60 @@
|
|
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
|
+
# Value represents the state of a Summary at a given point.
|
12
|
+
class Value < Hash
|
13
|
+
attr_accessor :sum, :total
|
14
|
+
|
15
|
+
def initialize(estimator)
|
16
|
+
@sum = estimator.sum
|
17
|
+
@total = estimator.observations
|
18
|
+
|
19
|
+
estimator.invariants.each do |invariant|
|
20
|
+
self[invariant.quantile] = estimator.query(invariant.quantile)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def type
|
26
|
+
:summary
|
27
|
+
end
|
28
|
+
|
29
|
+
# Records a given value.
|
30
|
+
def add(labels, value)
|
31
|
+
label_set = label_set_for(labels)
|
32
|
+
synchronize { @values[label_set].observe(value) }
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the value for the given label set
|
36
|
+
def get(labels = {})
|
37
|
+
@validator.valid?(labels)
|
38
|
+
|
39
|
+
synchronize do
|
40
|
+
Value.new(@values[labels])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns all label sets with their values
|
45
|
+
def values
|
46
|
+
synchronize do
|
47
|
+
@values.each_with_object({}) do |(labels, value), memo|
|
48
|
+
memo[labels] = Value.new(value)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def default
|
56
|
+
Quantile::Estimator.new
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: custom-prometheus-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Shah Bagdadi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-10 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
|
+
- email.shanu@gmail.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/json.rb
|
39
|
+
- lib/prometheus/client/formats/text.rb
|
40
|
+
- lib/prometheus/client/gauge.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/version.rb
|
49
|
+
homepage: https://github.com/prometheus/client_ruby
|
50
|
+
licenses:
|
51
|
+
- Apache 2.0
|
52
|
+
metadata: {}
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
requirements: []
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 2.4.8
|
70
|
+
signing_key:
|
71
|
+
specification_version: 4
|
72
|
+
summary: A suite of instrumentation metric primitivesthat can be exposed through a
|
73
|
+
web services interface.
|
74
|
+
test_files: []
|