g5_prom_rails 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +145 -0
- data/Rakefile +37 -0
- data/app/assets/config/g5_prom_rails_manifest.js +2 -0
- data/app/assets/javascripts/g5_prom_rails/application.js +13 -0
- data/app/assets/stylesheets/g5_prom_rails/application.css +15 -0
- data/app/controllers/g5_prom_rails/application_controller.rb +5 -0
- data/app/helpers/g5_prom_rails/application_helper.rb +4 -0
- data/app/jobs/g5_prom_rails/application_job.rb +4 -0
- data/app/mailers/g5_prom_rails/application_mailer.rb +6 -0
- data/app/models/g5_prom_rails/application_record.rb +5 -0
- data/app/views/layouts/g5_prom_rails/application.html.erb +14 -0
- data/config/routes.rb +2 -0
- data/lib/g5_prom_rails.rb +26 -0
- data/lib/g5_prom_rails/engine.rb +90 -0
- data/lib/g5_prom_rails/metrics.rb +35 -0
- data/lib/g5_prom_rails/refreshing_exporter.rb +6 -0
- data/lib/g5_prom_rails/settable_counter.rb +15 -0
- data/lib/g5_prom_rails/sidekiq_application_metrics.rb +38 -0
- data/lib/g5_prom_rails/sidekiq_timing_middleware.rb +37 -0
- data/lib/g5_prom_rails/version.rb +3 -0
- data/lib/tasks/g5_prom_rails_tasks.rake +4 -0
- metadata +136 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9b989ca4a0078f71ac841968d0856eee42d502b3
|
4
|
+
data.tar.gz: f3b202a9f269331b49a22451115929e456ba5dff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 45635669ba0ee1ec30a31c16103e2bd8363ea088405cb8e91e4811c6ee14d717c148324412d76c2bcdb947da9c9f8745966bd277b83eccfe462b890cfe8f91f8
|
7
|
+
data.tar.gz: 8f07da93804aa4cb94cde9f4317e041e9a70ef435135c2a753211c44e705c9f4433afc8aec88c58405262810f29a33bf50272998252379516cbe9000388cbcd1
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 Don Petersen
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
# G5 Prom Rails
|
2
|
+
|
3
|
+
This gem provides a rails engine that provides very basic help in integrating Prometheus into your Rails app. It brings in Prometheus Exporter middleware, initializes a metrics registry, and can add metrics for common use-cases and Rails gems.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'g5_prom_rails'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
```bash
|
15
|
+
$ bundle
|
16
|
+
```
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
The engine brings in the `prometheus-client` ruby gem. It is well-documented, and `g5_prom_rails` doesn't insulate you from how that gem works. You should [read its documentation](https://github.com/prometheus/client_ruby) to understand what metric types are available to you and how to update them. Registries and metrics endpoints are configured for you.
|
21
|
+
|
22
|
+
This gem is designed to work when Prometheus scrapes both individual instances of Rails (horizontally scaled processes) and the application as a whole. Application-level metrics are those that are shared between every Rails process, like database model counts or background job queue sizes. Per-process metrics are instrumented events that hit individual processes, like a particular controller action being called. Prometheus deals with aggregating events that happen in multiple disconnected processes, like load balanced Rails web servers.
|
23
|
+
|
24
|
+
## Configuration
|
25
|
+
|
26
|
+
There are a few configuration points for the engine, which you should specify in an initializer. Here's an example of a simple recommended setup:
|
27
|
+
|
28
|
+
**`config/initializers/metrics.rb`**
|
29
|
+
```ruby
|
30
|
+
METRICS = Metrics.new
|
31
|
+
|
32
|
+
G5PromRails.initialize_per_application = -> (registry) {
|
33
|
+
METRICS.initialize_per_application(registry)
|
34
|
+
}
|
35
|
+
|
36
|
+
G5PromRails.initialize_per_application = -> (registry) {
|
37
|
+
METRICS.initialize_per_process(registry)
|
38
|
+
}
|
39
|
+
|
40
|
+
G5PromRails.add_refresh_hook do
|
41
|
+
METRICS.published_posts.set({}, Post.where(published: true).count)
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
**`lib/metrics.rb`**
|
46
|
+
```ruby
|
47
|
+
class Metrics
|
48
|
+
attr_reader :published_posts, :post_shares
|
49
|
+
|
50
|
+
def initialize_per_application(registry)
|
51
|
+
@published_posts = registry.gauge(:my_app_posts, "published blog posts")
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize_per_application(registry)
|
55
|
+
@post_shares = registry.counter(:my_app_post_shares, "blog post shares")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
**`app/controllers/posts_controller.rb`**
|
61
|
+
```ruby
|
62
|
+
class PostsController < ApplicationController
|
63
|
+
def share
|
64
|
+
METRICS.post_shares.increment
|
65
|
+
# ... a bunch of important business logic
|
66
|
+
end
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
There are a few things going on here. In the initializer, you have hooks to initialize new metric types depending on their scope. Each lambda is passed a Prometheus registry instance, which is where you attach the metric types. They are safe for concurrent access, and the individual metric instances you're instantiating are the objects you'll be interacting with in your code when you want to instrument something.
|
71
|
+
|
72
|
+
The `Metrics` class is part of your codebase, and this structure is only a recommendation. I'm suggesting that fewer global variables are easier to deal with, but you could reimplement what I've done here with multiple globals and keep it all within your initializer (except the controller instrumentation).
|
73
|
+
|
74
|
+
We're instrumenting two things in this example: the number of Posts in the database, and the number of times any Post was shared.
|
75
|
+
|
76
|
+
Post count hits the database, and no matter which process you asked, the answer would be the same. You do *not* want to ask every process individually as it would generate pointless database queries and multiple copies of the same data.
|
77
|
+
|
78
|
+
Share count is per-process. With a load balancer and scaled web processes, different instances of your application might serve the request to share a blog post. You increment a counter in your controller code, and Prometheus deals with aggregating the differing values for this metric from each process.
|
79
|
+
|
80
|
+
An additional wrinkle is the `add_refresh_hook` in the initializer. This is a hook to update your metrics when the application-level metrics endpoint is hit. For a metric like database row counts, this is the easiest place to make sure the metrics up-to-date before you provide them to Prometheus. Because this hook is called every time the endpoint is hit, you should avoid any extremely strenuous queries, as it will be called at Prometheus's regular scrape interval.
|
81
|
+
|
82
|
+
## Warnings
|
83
|
+
|
84
|
+
There are a couple of general Prometheus tips things to keep in mind.
|
85
|
+
|
86
|
+
* Namespace your metrics. If you named a metric `users`, that's the name of the metric for every other user of your Prometheus server. You should name it `your_app_users`. This engine does not force namespaces on you, because it's possible you'll want to aggregate metrics across multiple applications.
|
87
|
+
* Read [the official Prometheus guidelines on metric naming](https://prometheus.io/docs/practices/naming/). It's short and extremely helpful.
|
88
|
+
* Did you see the big warning at the end of the naming guidelines? Labels in Prometheus are extremely powerful; I encourage you to use them, but don't abuse them. A new time series database is created for every combination of label values. Feel free to use a label that could have dozens of possible values, for instance in your blog post counter to differentiate drafts from published articles. Do *not* use `author_id` as a label to count posts by author. *Especially* do not use multiple labels that could have many possible values, because the effect on the total number of time series databases is multiplicative.
|
89
|
+
|
90
|
+
## App Name
|
91
|
+
|
92
|
+
As you will soon learn, G5PromRails can provide you with some automatic metrics. Most of them are scoped to your application's name. It will attempt to infer the application's name from the Rails Application class's (`config/application.rb`) dasherized parent module name. If that doesn't work for you, it can be manually defined with:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
G5PromRails.app_name = "my_app_name"
|
96
|
+
```
|
97
|
+
|
98
|
+
## Sidekiq
|
99
|
+
|
100
|
+
Prometheus's strategy is to scrape metrics. If you take a moment to think about it, we have a problem: how do you scrape in-memory metrics from a Sidekiq worker? That process doesn't start a web server.
|
101
|
+
|
102
|
+
Well, it does now. If you have Sidekiq and the process is a worker, it will start a thread with a simple Rack server that only serves metric. By default this will run on port 3000, but it can be configured with:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
G5PromRails.sidekiq_scrape_server_port = 3001
|
106
|
+
```
|
107
|
+
|
108
|
+
If your application includes Sidekiq, G5PromRails will detect it and include several metrics using Sidekiq's built-in statistics classes. It also adds a benchmarking middleware to the Sidekiq server middleware chain.
|
109
|
+
|
110
|
+
Metrics include:
|
111
|
+
|
112
|
+
* *`sidekiq_processed`* Counter for jobs processed.
|
113
|
+
* *`sidekiq_failed`* Counter for jobs failed.
|
114
|
+
* *`sidekiq_retry`* Gauge for current retries length.
|
115
|
+
* *`sidekiq_queued`* Gauge for current queue length. The label `queue` is applied to allow per-queue analysis.
|
116
|
+
* *`sidekiq_job_seconds`*,*`sidekiq_job_seconds_sum`*,*`sidekiq_job_seconds_count`* Histogram for job execution time. The label `job_name` is applied, and will be identical to the Ruby class name of the job (e.g. `MyImportantWorker`). To understand how to use this data, look at [the official documentation](https://prometheus.io/docs/practices/histograms/). See particularly the section about aggregation.
|
117
|
+
|
118
|
+
Each metric also has the `app` label applied with the name of your application.
|
119
|
+
|
120
|
+
## Helpers
|
121
|
+
|
122
|
+
There are some common instrumentation tasks that this gem can help you with.
|
123
|
+
|
124
|
+
#### Row Counts
|
125
|
+
|
126
|
+
When you'd like to instrument the count of certain ActiveRecord models, in an initializer you can:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
G5PromRails.count_models(:my_app, Post, Comment)
|
130
|
+
```
|
131
|
+
|
132
|
+
Will result in a gauge metric named `model_rows` with a `model` label set to the tableized name of your model and an `app` label set to the `my_app`. In PromQL this will look like:
|
133
|
+
```promql
|
134
|
+
model_rows{model="posts", app="my_app"}
|
135
|
+
```
|
136
|
+
|
137
|
+
This metric is left un-namespaced because it gives you the ability to compare these values across applications, while still allowing them to be limited to a single app via PromQL. The values will automatically be refreshed when the application-level metrics endpoint is hit.
|
138
|
+
|
139
|
+
## Development
|
140
|
+
|
141
|
+
To run this engine's tests, you need redis running. Sorry. You need to do some finagling to get Sidekiq using fakeredis, and I didn't feel like spending the time on it. I'm using Sidekiq::Stats, which isn't part of any of sidekiq's normal testing setup.
|
142
|
+
|
143
|
+
## License
|
144
|
+
|
145
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'G5PromRails'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
load 'rails/tasks/statistics.rake'
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
require 'bundler/gem_tasks'
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.libs << 'lib'
|
31
|
+
t.libs << 'test'
|
32
|
+
t.pattern = 'test/**/*_test.rb'
|
33
|
+
t.verbose = false
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
task default: :test
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file. JavaScript code in this file should be added after the last require_* statement.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
+
* It is generally better to create a new file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>G5 prom rails</title>
|
5
|
+
<%= stylesheet_link_tag "g5_prom_rails/application", media: "all" %>
|
6
|
+
<%= javascript_include_tag "g5_prom_rails/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require "g5_prom_rails/engine"
|
2
|
+
|
3
|
+
module G5PromRails
|
4
|
+
PER_PROCESS_PATH = "/metrics"
|
5
|
+
PER_APPLICATION_PATH = "/probe"
|
6
|
+
|
7
|
+
cattr_accessor :app_name
|
8
|
+
cattr_accessor :initialize_per_application, :initialize_per_process
|
9
|
+
cattr_accessor :sidekiq_scrape_server_port
|
10
|
+
|
11
|
+
def self.add_refresh_hook(&block)
|
12
|
+
@@refresh_hooks ||= []
|
13
|
+
@@refresh_hooks << block
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.refresh_gauges
|
17
|
+
return if @@refresh_hooks.nil?
|
18
|
+
@@refresh_hooks.each { |b| b.call }
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.count_models(*models)
|
22
|
+
add_refresh_hook do
|
23
|
+
Metrics.update_model_count_gauge(*models)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'prometheus/client/rack/exporter'
|
2
|
+
require_relative 'metrics'
|
3
|
+
require_relative 'refreshing_exporter'
|
4
|
+
require_relative 'settable_counter'
|
5
|
+
require_relative 'sidekiq_timing_middleware'
|
6
|
+
|
7
|
+
module G5PromRails
|
8
|
+
class Engine < ::Rails::Engine
|
9
|
+
isolate_namespace G5PromRails
|
10
|
+
|
11
|
+
config.generators do |g|
|
12
|
+
g.test_framework :rspec
|
13
|
+
end
|
14
|
+
|
15
|
+
initializer "g5_prom_rails.configure_global" do |app|
|
16
|
+
app_name = G5PromRails.app_name || app.class.parent_name.underscore.dasherize
|
17
|
+
G5PromRails::Metrics = MetricsContainer.new(app_name)
|
18
|
+
|
19
|
+
if G5PromRails.initialize_per_application.present?
|
20
|
+
G5PromRails::Metrics.per_application.instance_eval(
|
21
|
+
&G5PromRails.initialize_per_application
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
if G5PromRails.initialize_per_process.present?
|
26
|
+
G5PromRails::Metrics.per_process.instance_eval(
|
27
|
+
&G5PromRails.initialize_per_process
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
initializer "g5_prom_rails.add_exporter" do |app|
|
33
|
+
Prometheus::Client::Rack::Exporter.send(
|
34
|
+
:prepend,
|
35
|
+
G5PromRails::RefreshingExporter
|
36
|
+
)
|
37
|
+
|
38
|
+
per_application_opts = {
|
39
|
+
path: G5PromRails::PER_APPLICATION_PATH,
|
40
|
+
registry: G5PromRails::Metrics.per_application
|
41
|
+
}
|
42
|
+
per_process_opts = {
|
43
|
+
path: G5PromRails::PER_PROCESS_PATH,
|
44
|
+
registry: G5PromRails::Metrics.per_process
|
45
|
+
}
|
46
|
+
|
47
|
+
# has sidekiq and is a worker
|
48
|
+
if defined?(Sidekiq) && Sidekiq.server?.present?
|
49
|
+
app = Rack::Builder.new do
|
50
|
+
use Rack::ShowExceptions
|
51
|
+
use Rack::Lint
|
52
|
+
use Prometheus::Client::Rack::Exporter, per_process_opts
|
53
|
+
run -> { [ '404', {}, ["Not Found"] ] }
|
54
|
+
end
|
55
|
+
|
56
|
+
Thread.new do
|
57
|
+
Rails.logger.info("started g5_prom_rails metrics endpoint...")
|
58
|
+
Rack::Server.start(
|
59
|
+
app: app,
|
60
|
+
Port: (G5PromRails.sidekiq_scrape_server_port || 3000),
|
61
|
+
)
|
62
|
+
end
|
63
|
+
else
|
64
|
+
app.middleware.use(Prometheus::Client::Rack::Exporter, per_process_opts)
|
65
|
+
app.middleware.use(Prometheus::Client::Rack::Exporter, per_application_opts)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
initializer "g5_prom_rails.maybe_configure_sidekiq" do |app|
|
70
|
+
if defined?(Sidekiq)
|
71
|
+
G5PromRails.add_refresh_hook do
|
72
|
+
Metrics.update_sidekiq_statistics
|
73
|
+
end
|
74
|
+
|
75
|
+
timing_metric = G5PromRails::SidekiqTimingMiddleware.build_metric(
|
76
|
+
G5PromRails::Metrics.per_process
|
77
|
+
)
|
78
|
+
Sidekiq.configure_server do |config|
|
79
|
+
config.server_middleware do |chain|
|
80
|
+
chain.add(
|
81
|
+
G5PromRails::SidekiqTimingMiddleware,
|
82
|
+
app: G5PromRails::Metrics.app,
|
83
|
+
metric: timing_metric
|
84
|
+
)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative 'sidekiq_application_metrics'
|
2
|
+
|
3
|
+
class G5PromRails::MetricsContainer
|
4
|
+
if defined?(Sidekiq)
|
5
|
+
include G5PromRails::SidekiqApplicationMetrics
|
6
|
+
end
|
7
|
+
|
8
|
+
MODEL_COUNT_NAME = :model_rows
|
9
|
+
|
10
|
+
attr_reader :app
|
11
|
+
attr_reader :per_process, :per_application
|
12
|
+
|
13
|
+
def initialize(app)
|
14
|
+
@app = app
|
15
|
+
@per_process = Prometheus::Client::Registry.new
|
16
|
+
@per_application = Prometheus::Client::Registry.new
|
17
|
+
@model_count_gauge = @per_application.gauge(MODEL_COUNT_NAME, "model row counts")
|
18
|
+
try(:initialize_sidekiq_application)
|
19
|
+
end
|
20
|
+
|
21
|
+
def update_model_count_gauge(*models)
|
22
|
+
models.each do |model|
|
23
|
+
@model_count_gauge.set(
|
24
|
+
{ app: app, model: model.name.tableize },
|
25
|
+
model.count
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def app_hash(h = {})
|
33
|
+
{ app: app }.merge(h)
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# prometheus-client provides a counter that can only be incremented. That works
|
2
|
+
# great for instrumenting events, but in the case of something like Sidekiq
|
3
|
+
# Processed count, prometheus (the server) can handle resets and all kinds of
|
4
|
+
# great stuff if I simply pass the count as-is. Rather than having to monkey
|
5
|
+
# with saving the previous value and all that nonsense, I just want to set the
|
6
|
+
# value and let the server deal with it.
|
7
|
+
class G5PromRails::SettableCounter < Prometheus::Client::Metric
|
8
|
+
def type
|
9
|
+
:counter
|
10
|
+
end
|
11
|
+
|
12
|
+
def set(labels, value)
|
13
|
+
@values[label_set_for(labels)] = value
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'sidekiq/api'
|
2
|
+
|
3
|
+
module G5PromRails::SidekiqApplicationMetrics
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def initialize_sidekiq_application
|
7
|
+
@processed_counter = G5PromRails::SettableCounter.new(
|
8
|
+
:sidekiq_processed,
|
9
|
+
"jobs processed"
|
10
|
+
)
|
11
|
+
per_application.register(@processed_counter)
|
12
|
+
@failed_counter = G5PromRails::SettableCounter.new(
|
13
|
+
:sidekiq_failed,
|
14
|
+
"jobs failed"
|
15
|
+
)
|
16
|
+
per_application.register(@failed_counter)
|
17
|
+
|
18
|
+
@retry_gauge = per_application.gauge(
|
19
|
+
:sidekiq_retry,
|
20
|
+
"jobs to be retried"
|
21
|
+
)
|
22
|
+
@queues_gauge = per_application.gauge(
|
23
|
+
:sidekiq_queued,
|
24
|
+
"job queue lengths"
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def update_sidekiq_statistics
|
29
|
+
stats = Sidekiq::Stats.new
|
30
|
+
@processed_counter.set(app_hash, stats.processed)
|
31
|
+
@failed_counter.set(app_hash, stats.failed)
|
32
|
+
@retry_gauge.set(app_hash, stats.retry_size)
|
33
|
+
|
34
|
+
Sidekiq::Stats::Queues.new.lengths.each do |queue, length|
|
35
|
+
@queues_gauge.set(app_hash(queue: queue), length)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class G5PromRails::SidekiqTimingMiddleware
|
2
|
+
def self.build_metric(reg)
|
3
|
+
reg.histogram(
|
4
|
+
:sidekiq_job_seconds,
|
5
|
+
"job running time in seconds",
|
6
|
+
{},
|
7
|
+
[
|
8
|
+
10,
|
9
|
+
30,
|
10
|
+
90,
|
11
|
+
3.minutes.to_i,
|
12
|
+
7.minutes.to_i,
|
13
|
+
12.minutes.to_i,
|
14
|
+
20.minutes.to_i,
|
15
|
+
35.minutes.to_i,
|
16
|
+
60.minutes.to_i,
|
17
|
+
80.minutes.to_i,
|
18
|
+
2.hours.to_i,
|
19
|
+
3.hours.to_i,
|
20
|
+
5.hours.to_i,
|
21
|
+
10.hours.to_i,
|
22
|
+
]
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(options = nil)
|
27
|
+
@app = options[:app]
|
28
|
+
@metric = options[:metric]
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(worker, msg, queue)
|
32
|
+
@metric.observe(
|
33
|
+
{ app: @app, job_class: worker.class.name },
|
34
|
+
Benchmark.realtime { yield }
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: g5_prom_rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Don Petersen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-10-31 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: prometheus-client
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.6'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sqlite3
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-rails
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sidekiq
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Rails-friendly prometheus base
|
84
|
+
email:
|
85
|
+
- don@donpetersen.net
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- MIT-LICENSE
|
91
|
+
- README.md
|
92
|
+
- Rakefile
|
93
|
+
- app/assets/config/g5_prom_rails_manifest.js
|
94
|
+
- app/assets/javascripts/g5_prom_rails/application.js
|
95
|
+
- app/assets/stylesheets/g5_prom_rails/application.css
|
96
|
+
- app/controllers/g5_prom_rails/application_controller.rb
|
97
|
+
- app/helpers/g5_prom_rails/application_helper.rb
|
98
|
+
- app/jobs/g5_prom_rails/application_job.rb
|
99
|
+
- app/mailers/g5_prom_rails/application_mailer.rb
|
100
|
+
- app/models/g5_prom_rails/application_record.rb
|
101
|
+
- app/views/layouts/g5_prom_rails/application.html.erb
|
102
|
+
- config/routes.rb
|
103
|
+
- lib/g5_prom_rails.rb
|
104
|
+
- lib/g5_prom_rails/engine.rb
|
105
|
+
- lib/g5_prom_rails/metrics.rb
|
106
|
+
- lib/g5_prom_rails/refreshing_exporter.rb
|
107
|
+
- lib/g5_prom_rails/settable_counter.rb
|
108
|
+
- lib/g5_prom_rails/sidekiq_application_metrics.rb
|
109
|
+
- lib/g5_prom_rails/sidekiq_timing_middleware.rb
|
110
|
+
- lib/g5_prom_rails/version.rb
|
111
|
+
- lib/tasks/g5_prom_rails_tasks.rake
|
112
|
+
homepage: http://github.com/G5/g5_prom_rails
|
113
|
+
licenses:
|
114
|
+
- MIT
|
115
|
+
metadata: {}
|
116
|
+
post_install_message:
|
117
|
+
rdoc_options: []
|
118
|
+
require_paths:
|
119
|
+
- lib
|
120
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
requirements: []
|
131
|
+
rubyforge_project:
|
132
|
+
rubygems_version: 2.5.1
|
133
|
+
signing_key:
|
134
|
+
specification_version: 4
|
135
|
+
summary: Rails-friendly prometheus base
|
136
|
+
test_files: []
|