prometheus_exporter 0.1.6 → 0.1.7
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 +4 -4
- data/README.md +50 -6
- data/bin/prometheus_exporter +58 -0
- data/lib/prometheus_exporter.rb +2 -0
- data/lib/prometheus_exporter/client.rb +9 -1
- data/lib/prometheus_exporter/instrumentation.rb +2 -0
- data/lib/prometheus_exporter/instrumentation/method_profiler.rb +55 -0
- data/lib/prometheus_exporter/instrumentation/process.rb +77 -0
- data/lib/prometheus_exporter/middleware.rb +47 -0
- data/lib/prometheus_exporter/server.rb +1 -0
- data/lib/prometheus_exporter/server/collector.rb +117 -7
- data/lib/prometheus_exporter/server/collector_base.rb +14 -0
- data/lib/prometheus_exporter/server/web_server.rb +21 -4
- data/lib/prometheus_exporter/version.rb +1 -1
- data/prometheus_exporter.gemspec +2 -1
- metadata +24 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6957162d157420b2b8fb6d2a4b9e08483ce846497fe6bb21f1695b0e70460dc8
|
4
|
+
data.tar.gz: 9a74c21c97efad9ef3720df426d14a5e1d879eb34bebcafc625de8d45f3b232b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 695472a08f8f9ede18d6f0fdaa8722d2e4efb01b20bf23269d26afae580cb559da4f409bb3499da7a80dcd5d47e6156a6e8c7e54f01c770485212e21369fb61d
|
7
|
+
data.tar.gz: af55f7e9d025a925f0add89a61628d236691df0a613d82d8c4e622fc9b83b7651187d9e78121113de872632459e95015772ed9af43a12b85ce4a1f820be9b7fc
|
data/README.md
CHANGED
@@ -61,20 +61,20 @@ In some cases, for example unicorn or puma clusters you may want to aggregate me
|
|
61
61
|
|
62
62
|
Simplest way to acheive this is use the built-in collector.
|
63
63
|
|
64
|
-
First, run an exporter on your desired port:
|
64
|
+
First, run an exporter on your desired port, we use the default port of 9394:
|
65
65
|
|
66
66
|
```
|
67
|
-
# prometheus_exporter
|
67
|
+
# prometheus_exporter
|
68
68
|
```
|
69
69
|
|
70
|
-
At this point an exporter is running on port
|
70
|
+
At this point an exporter is running on port 9394
|
71
71
|
|
72
72
|
In your application:
|
73
73
|
|
74
74
|
```ruby
|
75
75
|
require 'prometheus_exporter/client'
|
76
76
|
|
77
|
-
client = PrometheusExporter::Client.
|
77
|
+
client = PrometheusExporter::Client.default
|
78
78
|
gauge = client.register(:gauge, "awesome", "amount of awesome")
|
79
79
|
|
80
80
|
gauge.observe(10)
|
@@ -85,7 +85,7 @@ gauge.observe(99, day: "friday")
|
|
85
85
|
Then you will get the metrics:
|
86
86
|
|
87
87
|
```bash
|
88
|
-
% curl localhost:
|
88
|
+
% curl localhost:9394/metrics
|
89
89
|
# HELP collector_working Is the master process collector able to collect metrics
|
90
90
|
# TYPE collector_working gauge
|
91
91
|
collector_working 1
|
@@ -97,6 +97,50 @@ awesome 10
|
|
97
97
|
|
98
98
|
```
|
99
99
|
|
100
|
+
### Easy integration into Rails
|
101
|
+
|
102
|
+
You can easily integrate into any Rack application:
|
103
|
+
|
104
|
+
In your Gemfile:
|
105
|
+
|
106
|
+
```
|
107
|
+
gem 'prometheus_exporter'
|
108
|
+
```
|
109
|
+
|
110
|
+
|
111
|
+
```
|
112
|
+
# in an initializer
|
113
|
+
|
114
|
+
unless Rails.env == "test"
|
115
|
+
require 'prometheus_exporter/middleware'
|
116
|
+
# insert in position 1
|
117
|
+
# instrument means method profiler will be injected in Redis and PG
|
118
|
+
Rails.application.middleware.unshift PrometheusExporter::Middleware
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
You may also be interested in per-process stats, this collects memory and GC stats
|
123
|
+
|
124
|
+
```
|
125
|
+
# in an initializer
|
126
|
+
unless Rails.env == "test"
|
127
|
+
require 'prometheus_exporter/instrumentation'
|
128
|
+
PrometheusExporter::Instrumentation::Process.start(type: "master")
|
129
|
+
end
|
130
|
+
|
131
|
+
after_fork do
|
132
|
+
require 'prometheus_exporter/instrumentation'
|
133
|
+
PrometheusExporter::Instrumentation::Process.start(type:"web")
|
134
|
+
end
|
135
|
+
|
136
|
+
```
|
137
|
+
|
138
|
+
Ensure you run the exporter via
|
139
|
+
|
140
|
+
```
|
141
|
+
% bundle exec prometheus_exporter
|
142
|
+
```
|
143
|
+
|
100
144
|
### Multi process mode with custom collector
|
101
145
|
|
102
146
|
You can opt for custom collector logic in a multi process environment.
|
@@ -108,7 +152,7 @@ The standard collector ships "help", "type" and "name" for every metric, in some
|
|
108
152
|
First, define a custom collector, it is critical you inherit off `PrometheusExporter::Server::Collector`, also it is critical you have custom implementations for #process and #prometheus_metrics_text
|
109
153
|
|
110
154
|
```ruby
|
111
|
-
class MyCustomCollector < PrometheusExporter::Server::
|
155
|
+
class MyCustomCollector < PrometheusExporter::Server::CollectorBase
|
112
156
|
def initialize
|
113
157
|
@gauge1 = PrometheusExporter::Metric::Gauge.new("thing1", "I am thing 1")
|
114
158
|
@gauge2 = PrometheusExporter::Metric::Gauge.new("thing2", "I am thing 2")
|
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
require_relative "./../lib/prometheus_exporter"
|
6
|
+
require_relative "./../lib/prometheus_exporter/server"
|
7
|
+
|
8
|
+
def run
|
9
|
+
port = PrometheusExporter::DEFAULT_PORT
|
10
|
+
prefix = "ruby_"
|
11
|
+
collector = nil
|
12
|
+
verbose = false
|
13
|
+
|
14
|
+
OptionParser.new do |opt|
|
15
|
+
opt.on('-p',
|
16
|
+
'--port INTEGER',
|
17
|
+
Integer,
|
18
|
+
"Port exporter should listen on (default: #{port})") do |o|
|
19
|
+
port = o.to_i
|
20
|
+
end
|
21
|
+
opt.on('--prefix METRIC_PREFIX', String, "Prefix to apply to all metrics (default: #{prefix})") do |o|
|
22
|
+
prefix = o.to_s
|
23
|
+
end
|
24
|
+
opt.on('-c', '--collector CUSTOM_COLLECTOR', String, "(optional) Custom collector to run") do |o|
|
25
|
+
collector = o.to_s
|
26
|
+
end
|
27
|
+
opt.on('-v', '--verbose') do |o|
|
28
|
+
verbose = true
|
29
|
+
end
|
30
|
+
|
31
|
+
end.parse!
|
32
|
+
|
33
|
+
PrometheusExporter::Metric::Base.default_prefix = prefix
|
34
|
+
|
35
|
+
if collector
|
36
|
+
eval File.read(collector)
|
37
|
+
|
38
|
+
ObjectSpace.each_object(Class) do |klass|
|
39
|
+
if klass < PrometheusExporter::Server::CollectorBase
|
40
|
+
collector = klass
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if !collector
|
45
|
+
STDERR.puts "Can not find a class inheriting off PrometheusExporter::Server::Collector"
|
46
|
+
usage
|
47
|
+
exit 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
puts "#{Time.now} Starting prometheus exporter on port #{port}"
|
52
|
+
server = PrometheusExporter::Server::WebServer.new port: port, collector: collector&.new, verbose: verbose
|
53
|
+
server.start
|
54
|
+
sleep
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
run
|
data/lib/prometheus_exporter.rb
CHANGED
@@ -24,10 +24,18 @@ class PrometheusExporter::Client
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
+
def self.default
|
28
|
+
@default ||= new
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.default=(client)
|
32
|
+
@default = client
|
33
|
+
end
|
34
|
+
|
27
35
|
MAX_SOCKET_AGE = 25
|
28
36
|
MAX_QUEUE_SIZE = 10_000
|
29
37
|
|
30
|
-
def initialize(host: 'localhost', port
|
38
|
+
def initialize(host: 'localhost', port: PrometheusExporter::DEFAULT_PORT, max_queue_size: nil, thread_sleep: 0.5)
|
31
39
|
@metrics = []
|
32
40
|
|
33
41
|
@queue = Queue.new
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# see https://samsaffron.com/archive/2017/10/18/fastest-way-to-profile-a-method-in-ruby
|
2
|
+
module PrometheusExporter::Instrumentation; end
|
3
|
+
|
4
|
+
class PrometheusExporter::Instrumentation::MethodProfiler
|
5
|
+
def self.patch(klass, methods, name)
|
6
|
+
patches = methods.map do |method_name|
|
7
|
+
<<~RUBY
|
8
|
+
unless defined?(#{method_name}__mp_unpatched)
|
9
|
+
alias_method :#{method_name}__mp_unpatched, :#{method_name}
|
10
|
+
def #{method_name}(*args, &blk)
|
11
|
+
unless prof = Thread.current[:_method_profiler]
|
12
|
+
return #{method_name}__mp_unpatched(*args, &blk)
|
13
|
+
end
|
14
|
+
begin
|
15
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
16
|
+
#{method_name}__mp_unpatched(*args, &blk)
|
17
|
+
ensure
|
18
|
+
data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
|
19
|
+
data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
20
|
+
data[:calls] += 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
RUBY
|
25
|
+
end.join("\n")
|
26
|
+
|
27
|
+
klass.class_eval patches
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.transfer
|
31
|
+
result = Thread.current[:_method_profiler]
|
32
|
+
Thread.current[:_method_profiler] = nil
|
33
|
+
result
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.start(transfer = nil)
|
37
|
+
Thread.current[:_method_profiler] = transfer || {
|
38
|
+
__start: Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.clear
|
43
|
+
Thread.current[:_method_profiler] = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.stop
|
47
|
+
finish = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
48
|
+
if data = Thread.current[:_method_profiler]
|
49
|
+
Thread.current[:_method_profiler] = nil
|
50
|
+
start = data.delete(:__start)
|
51
|
+
data[:total_duration] = finish - start
|
52
|
+
end
|
53
|
+
data
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# collects stats from currently running process
|
2
|
+
module PrometheusExporter::Instrumentation
|
3
|
+
class Process
|
4
|
+
def self.start(client: nil, type: "ruby", frequency: 30)
|
5
|
+
process_collector = new(type)
|
6
|
+
client ||= PrometheusExporter::Client.default
|
7
|
+
Thread.new do
|
8
|
+
while true
|
9
|
+
begin
|
10
|
+
metric = process_collector.collect
|
11
|
+
client.send_json metric
|
12
|
+
rescue => e
|
13
|
+
STDERR.puts("Prometheus Discoruse Failed To Collect Process Stats #{e}")
|
14
|
+
ensure
|
15
|
+
sleep frequency
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(type)
|
22
|
+
@type = type
|
23
|
+
end
|
24
|
+
|
25
|
+
def collect
|
26
|
+
metric = {}
|
27
|
+
metric[:type] = "process"
|
28
|
+
metric[:process_type] = @type
|
29
|
+
collect_gc_stats(metric)
|
30
|
+
collect_v8_stats(metric)
|
31
|
+
collect_process_stats(metric)
|
32
|
+
metric
|
33
|
+
end
|
34
|
+
|
35
|
+
def pid
|
36
|
+
@pid = ::Process.pid
|
37
|
+
end
|
38
|
+
|
39
|
+
def rss
|
40
|
+
@pagesize ||= `getconf PAGESIZE`.to_i rescue 4096
|
41
|
+
File.read("/proc/#{pid}/statm").split(' ')[1].to_i * @pagesize rescue 0
|
42
|
+
end
|
43
|
+
|
44
|
+
def collect_process_stats(metric)
|
45
|
+
metric[:pid] = pid
|
46
|
+
metric[:rss] = rss
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
def collect_gc_stats(metric)
|
51
|
+
stat = GC.stat
|
52
|
+
metric[:heap_live_slots] = stat[:heap_live_slots]
|
53
|
+
metric[:heap_free_slots] = stat[:heap_free_slots]
|
54
|
+
metric[:major_gc_count] = stat[:major_gc_count]
|
55
|
+
metric[:minor_gc_count] = stat[:minor_gc_count]
|
56
|
+
metric[:total_allocated_objects] = stat[:total_allocated_objects]
|
57
|
+
end
|
58
|
+
|
59
|
+
def collect_v8_stats(metric)
|
60
|
+
return if !defined? MiniRacer
|
61
|
+
|
62
|
+
metric[:v8_heap_count] = metric[:v8_heap_size] = 0
|
63
|
+
metric[:v8_heap_size] = metric[:v8_physical_size] = 0
|
64
|
+
metric[:v8_used_heap_size] = 0
|
65
|
+
|
66
|
+
ObjectSpace.each_object(MiniRacer::Context) do |context|
|
67
|
+
stats = context.heap_stats
|
68
|
+
if stats
|
69
|
+
metric[:v8_heap_count] += 1
|
70
|
+
metric[:v8_heap_size] += stats[:total_heap_size].to_i
|
71
|
+
metric[:v8_used_heap_size] += stats[:used_heap_size].to_i
|
72
|
+
metric[:v8_physical_size] += stats[:total_physical_size].to_i
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'prometheus_exporter/instrumentation/method_profiler'
|
4
|
+
require 'prometheus_exporter/client'
|
5
|
+
|
6
|
+
class PrometheusExporter::Middleware
|
7
|
+
MethodProfiler = PrometheusExporter::Instrumentation::MethodProfiler
|
8
|
+
|
9
|
+
def initialize(app, config = { instrument: true, client: nil })
|
10
|
+
@app = app
|
11
|
+
@client = config[:client] || PrometheusExporter::Client.default
|
12
|
+
|
13
|
+
if config[:instrument]
|
14
|
+
if defined? Redis::Client
|
15
|
+
MethodProfiler.patch(Redis::Client, [:call, :call_pipeline], :redis)
|
16
|
+
end
|
17
|
+
if defined? PG::Connection
|
18
|
+
MethodProfiler.patch(PG::Connection, [
|
19
|
+
:exec, :async_exec, :exec_prepared, :send_query_prepared, :query
|
20
|
+
], :sql)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(env)
|
26
|
+
MethodProfiler.start
|
27
|
+
result = @app.call(env)
|
28
|
+
info = MethodProfiler.stop
|
29
|
+
result
|
30
|
+
ensure
|
31
|
+
status = (result && result[0]) || -1
|
32
|
+
params = env["action_dispatch.request.parameters"]
|
33
|
+
action, controller = nil
|
34
|
+
if params
|
35
|
+
action = params["action"]
|
36
|
+
controller = params["controller"]
|
37
|
+
end
|
38
|
+
|
39
|
+
@client.send_json(
|
40
|
+
type: "web",
|
41
|
+
timings: info,
|
42
|
+
action: action,
|
43
|
+
controller: controller,
|
44
|
+
status: status
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
@@ -2,9 +2,26 @@
|
|
2
2
|
|
3
3
|
module PrometheusExporter::Server
|
4
4
|
|
5
|
-
class Collector
|
5
|
+
class Collector < CollectorBase
|
6
|
+
MAX_PROCESS_METRIC_AGE = 60
|
7
|
+
PROCESS_GAUGES = {
|
8
|
+
heap_free_slots: "Free ruby heap slots",
|
9
|
+
heap_live_slots: "Used ruby heap slots",
|
10
|
+
v8_heap_size: "Total JavaScript V8 heap size (bytes)",
|
11
|
+
v8_used_heap_size: "Total used JavaScript V8 heap size (bytes)",
|
12
|
+
v8_physical_size: "Physical size consumed by V8 heaps",
|
13
|
+
v8_heap_count: "Number of V8 contexts running",
|
14
|
+
rss: "Total RSS used by process",
|
15
|
+
}
|
16
|
+
|
17
|
+
PROCESS_COUNTERS = {
|
18
|
+
major_gc_count: "Major GC operations by process",
|
19
|
+
minor_gc_count: "Minor GC operations by process",
|
20
|
+
total_allocated_objects: "Total number of allocateds objects by process",
|
21
|
+
}
|
6
22
|
|
7
23
|
def initialize
|
24
|
+
@process_metrics = []
|
8
25
|
@metrics = {}
|
9
26
|
@buffer = []
|
10
27
|
@mutex = Mutex.new
|
@@ -13,27 +30,120 @@ module PrometheusExporter::Server
|
|
13
30
|
def process(str)
|
14
31
|
obj = JSON.parse(str)
|
15
32
|
@mutex.synchronize do
|
16
|
-
|
17
|
-
|
18
|
-
|
33
|
+
if obj["type"] == "web"
|
34
|
+
observe_web(obj)
|
35
|
+
elsif obj["type"] == "process"
|
36
|
+
observe_process(obj)
|
37
|
+
else
|
38
|
+
metric = @metrics[obj["name"]]
|
39
|
+
if !metric
|
40
|
+
metric = register_metric_unsafe(obj)
|
41
|
+
end
|
42
|
+
metric.observe(obj["value"], obj["keys"])
|
19
43
|
end
|
20
|
-
metric.observe(obj["value"], obj["keys"])
|
21
44
|
end
|
22
45
|
end
|
23
46
|
|
24
47
|
def prometheus_metrics_text
|
25
48
|
@mutex.synchronize do
|
26
|
-
@metrics.values.map(&:to_prometheus_text).join("\n")
|
49
|
+
val = @metrics.values.map(&:to_prometheus_text).join("\n")
|
50
|
+
|
51
|
+
metrics = {}
|
52
|
+
|
53
|
+
if @process_metrics.length > 0
|
54
|
+
val << "\n"
|
55
|
+
|
56
|
+
@process_metrics.map do |m|
|
57
|
+
metric_key = { pid: m["pid"], type: m["process_type"] }
|
58
|
+
|
59
|
+
PROCESS_GAUGES.map do |k, help|
|
60
|
+
k = k.to_s
|
61
|
+
if v = m[k]
|
62
|
+
g = metrics[k] ||= PrometheusExporter::Metric::Gauge.new(k, help)
|
63
|
+
g.observe(v, metric_key)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
PROCESS_COUNTERS.map do |k, help|
|
68
|
+
k = k.to_s
|
69
|
+
if v = m[k]
|
70
|
+
c = metrics[k] ||= PrometheusExporter::Metric::Counter.new(k, help)
|
71
|
+
c.observe(v, metric_key)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
val << metrics.values.map(&:to_prometheus_text).join("\n")
|
78
|
+
end
|
79
|
+
|
80
|
+
val
|
27
81
|
end
|
28
82
|
end
|
29
83
|
|
84
|
+
protected
|
85
|
+
|
30
86
|
def register_metric(metric)
|
31
87
|
@mutex.synchronize do
|
32
88
|
@metrics << metric
|
33
89
|
end
|
34
90
|
end
|
35
91
|
|
36
|
-
|
92
|
+
def ensure_web_metrics
|
93
|
+
unless @http_requests
|
94
|
+
@metrics["http_requests"] = @http_requests = PrometheusExporter::Metric::Counter.new(
|
95
|
+
"http_requests",
|
96
|
+
"Total HTTP requests from web app"
|
97
|
+
)
|
98
|
+
|
99
|
+
@metrics["http_duration_seconds"] = @http_duration_seconds = PrometheusExporter::Metric::Summary.new(
|
100
|
+
"http_duration_seconds",
|
101
|
+
"Time spent in HTTP reqs in seconds"
|
102
|
+
)
|
103
|
+
|
104
|
+
@metrics["http_redis_duration_seconds"] = @http_redis_duration_seconds = PrometheusExporter::Metric::Summary.new(
|
105
|
+
"http_redis_duration_seconds",
|
106
|
+
"Time spent in HTTP reqs in redis seconds"
|
107
|
+
)
|
108
|
+
|
109
|
+
@metrics["http_sql_duration_seconds"] = @http_sql_duration_seconds = PrometheusExporter::Metric::Summary.new(
|
110
|
+
"http_sql_duration_seconds",
|
111
|
+
"Time spent in HTTP reqs in SQL in seconds"
|
112
|
+
)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def observe_web(obj)
|
117
|
+
ensure_web_metrics
|
118
|
+
|
119
|
+
labels = {
|
120
|
+
controller: obj["controller"] || "other",
|
121
|
+
action: obj["action"] || "other"
|
122
|
+
}
|
123
|
+
|
124
|
+
@http_requests.observe(1, labels.merge(status: obj["status"]))
|
125
|
+
|
126
|
+
if timings = obj["timings"]
|
127
|
+
@http_duration_seconds.observe(timings["total_duration"], labels)
|
128
|
+
if redis = timings["redis"]
|
129
|
+
@http_redis_duration_seconds.observe(redis["duration"], labels)
|
130
|
+
end
|
131
|
+
if sql = timings["sql"]
|
132
|
+
@http_sql_duration_seconds.observe(sql["duration"], labels)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def observe_process(obj)
|
138
|
+
now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
139
|
+
|
140
|
+
obj["created_at"] = now
|
141
|
+
|
142
|
+
@process_metrics.delete_if do |current|
|
143
|
+
obj["pid"] == current["pid"] || (current["created_at"] + MAX_PROCESS_METRIC_AGE < now)
|
144
|
+
end
|
145
|
+
@process_metrics << obj
|
146
|
+
end
|
37
147
|
|
38
148
|
def register_metric_unsafe(obj)
|
39
149
|
name = obj["name"]
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module PrometheusExporter::Server
|
2
|
+
|
3
|
+
# minimal interface to implement a customer collector
|
4
|
+
class CollectorBase
|
5
|
+
|
6
|
+
# called each time a string is delivered from the web
|
7
|
+
def process(str)
|
8
|
+
end
|
9
|
+
|
10
|
+
# a string denoting the metrics
|
11
|
+
def prometheus_metrics_text(str)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -9,7 +9,9 @@ module PrometheusExporter::Server
|
|
9
9
|
class WebServer
|
10
10
|
attr_reader :collector
|
11
11
|
|
12
|
-
def initialize(port: , collector: nil)
|
12
|
+
def initialize(port: , collector: nil, verbose: false)
|
13
|
+
|
14
|
+
@verbose = verbose
|
13
15
|
|
14
16
|
@total_metrics = PrometheusExporter::Metric::Counter.new("total_collector_metrics", "total metrics processed by exporter web")
|
15
17
|
|
@@ -21,10 +23,19 @@ module PrometheusExporter::Server
|
|
21
23
|
@total_sessions.observe(0)
|
22
24
|
@total_bad_metrics.observe(0)
|
23
25
|
|
26
|
+
access_log = []
|
27
|
+
if verbose
|
28
|
+
access_log = [
|
29
|
+
[$stderr, WEBrick::AccessLog::COMMON_LOG_FORMAT],
|
30
|
+
[$stderr, WEBrick::AccessLog::REFERER_LOG_FORMAT],
|
31
|
+
]
|
32
|
+
logger = WEBrick::Log.new($stderr)
|
33
|
+
end
|
34
|
+
|
24
35
|
@server = WEBrick::HTTPServer.new(
|
25
36
|
Port: port,
|
26
|
-
|
27
|
-
|
37
|
+
Logger: logger,
|
38
|
+
AccessLog: access_log
|
28
39
|
)
|
29
40
|
|
30
41
|
@collector = collector || Collector.new
|
@@ -52,7 +63,7 @@ module PrometheusExporter::Server
|
|
52
63
|
handle_metrics(req, res)
|
53
64
|
else
|
54
65
|
res.status = 404
|
55
|
-
res.body = "Not Found! The Prometheus
|
66
|
+
res.body = "Not Found! The Prometheus Ruby Exporter only listens on /metrics and /send-metrics"
|
56
67
|
end
|
57
68
|
end
|
58
69
|
end
|
@@ -64,6 +75,12 @@ module PrometheusExporter::Server
|
|
64
75
|
@total_metrics.observe
|
65
76
|
@collector.process(block)
|
66
77
|
rescue => e
|
78
|
+
if @verbose
|
79
|
+
STDERR.puts
|
80
|
+
STDERR.puts e.inspect
|
81
|
+
STDERR.puts e.backtrace
|
82
|
+
STDERR.puts
|
83
|
+
end
|
67
84
|
@total_bad_metrics.observe
|
68
85
|
res.body = "Bad Metrics #{e}"
|
69
86
|
res.status = e.respond_to?(:status_code) ? e.status_code : 500
|
data/prometheus_exporter.gemspec
CHANGED
@@ -18,12 +18,13 @@ Gem::Specification.new do |spec|
|
|
18
18
|
f.match(%r{^(test|spec|features|bin)/})
|
19
19
|
end
|
20
20
|
spec.bindir = "bin"
|
21
|
-
spec.executables =
|
21
|
+
spec.executables = ["prometheus_exporter"]
|
22
22
|
spec.require_paths = ["lib"]
|
23
23
|
|
24
24
|
spec.add_development_dependency "bundler", "~> 1.16"
|
25
25
|
spec.add_development_dependency "rake", "~> 10.0"
|
26
26
|
spec.add_development_dependency "minitest", "~> 5.0"
|
27
27
|
spec.add_development_dependency "guard", "~> 2.0"
|
28
|
+
spec.add_development_dependency "mini_racer", "~> 0.1"
|
28
29
|
spec.add_development_dependency "guard-minitest", "~> 2.0"
|
29
30
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prometheus_exporter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Saffron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-01-
|
11
|
+
date: 2018-01-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '2.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: mini_racer
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.1'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.1'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: guard-minitest
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -83,7 +97,8 @@ dependencies:
|
|
83
97
|
description: Prometheus metric collector and exporter for Ruby
|
84
98
|
email:
|
85
99
|
- sam.saffron@gmail.com
|
86
|
-
executables:
|
100
|
+
executables:
|
101
|
+
- prometheus_exporter
|
87
102
|
extensions: []
|
88
103
|
extra_rdoc_files: []
|
89
104
|
files:
|
@@ -97,16 +112,22 @@ files:
|
|
97
112
|
- README.md
|
98
113
|
- Rakefile
|
99
114
|
- bench/bench.rb
|
115
|
+
- bin/prometheus_exporter
|
100
116
|
- examples/custom_collector.rb
|
101
117
|
- lib/prometheus_exporter.rb
|
102
118
|
- lib/prometheus_exporter/client.rb
|
119
|
+
- lib/prometheus_exporter/instrumentation.rb
|
120
|
+
- lib/prometheus_exporter/instrumentation/method_profiler.rb
|
121
|
+
- lib/prometheus_exporter/instrumentation/process.rb
|
103
122
|
- lib/prometheus_exporter/metric.rb
|
104
123
|
- lib/prometheus_exporter/metric/base.rb
|
105
124
|
- lib/prometheus_exporter/metric/counter.rb
|
106
125
|
- lib/prometheus_exporter/metric/gauge.rb
|
107
126
|
- lib/prometheus_exporter/metric/summary.rb
|
127
|
+
- lib/prometheus_exporter/middleware.rb
|
108
128
|
- lib/prometheus_exporter/server.rb
|
109
129
|
- lib/prometheus_exporter/server/collector.rb
|
130
|
+
- lib/prometheus_exporter/server/collector_base.rb
|
110
131
|
- lib/prometheus_exporter/server/web_server.rb
|
111
132
|
- lib/prometheus_exporter/version.rb
|
112
133
|
- prometheus_exporter.gemspec
|