sidekiq_prometheus 0.8.1
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/.gitignore +10 -0
- data/.rubocop.yml +133 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +177 -0
- data/Rakefile +19 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/sidekiq_prometheus.rb +177 -0
- data/lib/sidekiq_prometheus/job_metrics.rb +44 -0
- data/lib/sidekiq_prometheus/metrics.rb +155 -0
- data/lib/sidekiq_prometheus/periodic_metrics.rb +155 -0
- data/lib/sidekiq_prometheus/version.rb +5 -0
- data/sidekiq_prometheus.gemspec +33 -0
- metadata +172 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f2382deff51bcba928543262df3da7a6c1acd5c90b79a9aa427349d1d190cdf3
|
|
4
|
+
data.tar.gz: 905e723a9efa06ae33df67872e254af30a9f145fb2ef45dfd4bcf5bbe447c120
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 35eb0aecd12715f0e2c29d500a89ee4ed33042f1a004fef4e8db62ddf942bcd85b1f41aac228f66e3d7e9bc29360871d7b0034d194a4057846a5afb5827093e4
|
|
7
|
+
data.tar.gz: a7f66949d6db7dde393c77a19872cb2384be079988f3d598bf7031510d30c766d28311223c56fa54353e72b5b1f12ce65f986317bc97e37ed1a143fd1a65b068
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 2.5
|
|
3
|
+
DisplayCopNames: true
|
|
4
|
+
Include:
|
|
5
|
+
- '**/*.gemspec'
|
|
6
|
+
- '**/*.rake'
|
|
7
|
+
- '**/Gemfile'
|
|
8
|
+
- '**/Rakefile'
|
|
9
|
+
- '**/*.rb'
|
|
10
|
+
Exclude:
|
|
11
|
+
- db/schema.rb
|
|
12
|
+
- bin/setup
|
|
13
|
+
- bin/update
|
|
14
|
+
- vendor/**/*
|
|
15
|
+
|
|
16
|
+
Style/RedundantReturn:
|
|
17
|
+
AllowMultipleReturnValues: true
|
|
18
|
+
|
|
19
|
+
HashSyntax:
|
|
20
|
+
Enabled: false
|
|
21
|
+
|
|
22
|
+
BlockNesting:
|
|
23
|
+
Enabled: true
|
|
24
|
+
|
|
25
|
+
AssignmentInCondition:
|
|
26
|
+
Enabled: false
|
|
27
|
+
|
|
28
|
+
CollectionMethods:
|
|
29
|
+
Enabled: false
|
|
30
|
+
|
|
31
|
+
Lint/MissingCopEnableDirective:
|
|
32
|
+
Enabled: false
|
|
33
|
+
|
|
34
|
+
Metrics/AbcSize:
|
|
35
|
+
Max: 75
|
|
36
|
+
|
|
37
|
+
Metrics/LineLength:
|
|
38
|
+
Enabled: false
|
|
39
|
+
|
|
40
|
+
Metrics/MethodLength:
|
|
41
|
+
Max: 50
|
|
42
|
+
|
|
43
|
+
Metrics/ClassLength:
|
|
44
|
+
Max: 300
|
|
45
|
+
|
|
46
|
+
Metrics/ModuleLength:
|
|
47
|
+
Max: 300
|
|
48
|
+
|
|
49
|
+
Metrics/CyclomaticComplexity:
|
|
50
|
+
Max: 10
|
|
51
|
+
|
|
52
|
+
Metrics/PerceivedComplexity:
|
|
53
|
+
Max: 10
|
|
54
|
+
|
|
55
|
+
Metrics/BlockLength:
|
|
56
|
+
Enabled: true
|
|
57
|
+
ExcludedMethods:
|
|
58
|
+
- class_methods
|
|
59
|
+
- included
|
|
60
|
+
|
|
61
|
+
Exclude:
|
|
62
|
+
- Gemfile
|
|
63
|
+
- spec/**/*
|
|
64
|
+
- '*.gemspec'
|
|
65
|
+
|
|
66
|
+
Naming/PredicateName:
|
|
67
|
+
Enabled: false
|
|
68
|
+
|
|
69
|
+
Naming/FileName:
|
|
70
|
+
Exclude:
|
|
71
|
+
- Capfile
|
|
72
|
+
- Appraisals
|
|
73
|
+
|
|
74
|
+
Performance/TimesMap:
|
|
75
|
+
Enabled: false
|
|
76
|
+
|
|
77
|
+
HandleExceptions:
|
|
78
|
+
Enabled: false
|
|
79
|
+
|
|
80
|
+
Documentation:
|
|
81
|
+
Enabled: false
|
|
82
|
+
|
|
83
|
+
RegexpLiteral:
|
|
84
|
+
Enabled: false
|
|
85
|
+
|
|
86
|
+
SignalException:
|
|
87
|
+
EnforcedStyle: only_raise
|
|
88
|
+
|
|
89
|
+
Style/AsciiComments:
|
|
90
|
+
Enabled: false
|
|
91
|
+
|
|
92
|
+
Style/BlockDelimiters:
|
|
93
|
+
EnforcedStyle: braces_for_chaining
|
|
94
|
+
|
|
95
|
+
Style/BracesAroundHashParameters:
|
|
96
|
+
EnforcedStyle: context_dependent
|
|
97
|
+
|
|
98
|
+
Style/TrailingCommaInArrayLiteral:
|
|
99
|
+
EnforcedStyleForMultiline: consistent_comma
|
|
100
|
+
|
|
101
|
+
Style/TrailingCommaInHashLiteral:
|
|
102
|
+
EnforcedStyleForMultiline: consistent_comma
|
|
103
|
+
|
|
104
|
+
Style/TrailingCommaInArguments:
|
|
105
|
+
Enabled: false
|
|
106
|
+
|
|
107
|
+
Style/ClassAndModuleChildren:
|
|
108
|
+
EnforcedStyle: compact
|
|
109
|
+
|
|
110
|
+
Style/TrivialAccessors:
|
|
111
|
+
Enabled: true
|
|
112
|
+
ExactNameMatch: true
|
|
113
|
+
|
|
114
|
+
Style/LineEndConcatenation:
|
|
115
|
+
Enabled: false
|
|
116
|
+
|
|
117
|
+
Style/EachWithObject:
|
|
118
|
+
Enabled: false
|
|
119
|
+
|
|
120
|
+
Style/NumericPredicate:
|
|
121
|
+
Enabled: false
|
|
122
|
+
|
|
123
|
+
Metrics/ParameterLists:
|
|
124
|
+
CountKeywordArgs: false
|
|
125
|
+
|
|
126
|
+
Style/FormatStringToken:
|
|
127
|
+
Enabled: false
|
|
128
|
+
|
|
129
|
+
Naming/MemoizedInstanceVariableName:
|
|
130
|
+
Enabled: false
|
|
131
|
+
|
|
132
|
+
Style/RescueModifier:
|
|
133
|
+
Enabled: false
|
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
|
10
|
+
orientation.
|
|
11
|
+
|
|
12
|
+
## Our Standards
|
|
13
|
+
|
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
|
15
|
+
include:
|
|
16
|
+
|
|
17
|
+
* Using welcoming and inclusive language
|
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
|
19
|
+
* Gracefully accepting constructive criticism
|
|
20
|
+
* Focusing on what is best for the community
|
|
21
|
+
* Showing empathy towards other community members
|
|
22
|
+
|
|
23
|
+
Examples of unacceptable behavior by participants include:
|
|
24
|
+
|
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
|
26
|
+
advances
|
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
|
28
|
+
* Public or private harassment
|
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
|
30
|
+
address, without explicit permission
|
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
32
|
+
professional setting
|
|
33
|
+
|
|
34
|
+
## Our Responsibilities
|
|
35
|
+
|
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
|
38
|
+
response to any instances of unacceptable behavior.
|
|
39
|
+
|
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
|
44
|
+
threatening, offensive, or harmful.
|
|
45
|
+
|
|
46
|
+
## Scope
|
|
47
|
+
|
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
|
49
|
+
when an individual is representing the project or its community. Examples of
|
|
50
|
+
representing a project or community include using an official project e-mail
|
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
|
53
|
+
further defined and clarified by project maintainers.
|
|
54
|
+
|
|
55
|
+
## Enforcement
|
|
56
|
+
|
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
58
|
+
reported by contacting the project team at leklund@fastly.com. All
|
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
|
63
|
+
|
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
|
66
|
+
members of the project's leadership.
|
|
67
|
+
|
|
68
|
+
## Attribution
|
|
69
|
+
|
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
|
72
|
+
|
|
73
|
+
[homepage]: http://contributor-covenant.org
|
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Fastly, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# SidekiqPrometheus
|
|
2
|
+
|
|
3
|
+
Prometheus Instrumentation for Sidekiq.
|
|
4
|
+
|
|
5
|
+
* Sidekiq server middleware for reporting job metrics
|
|
6
|
+
* Global metrics reporter using the Sidekiq API for reporting Sidekiq cluster stats (requires Sidekiq::Enterprise)
|
|
7
|
+
* Sidecar Rack server to provide scrape-able endpoint for Prometheus
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
Add this line to your application's Gemfile:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
gem 'sidekiq_prometheus'
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
And then execute:
|
|
19
|
+
|
|
20
|
+
$ bundle
|
|
21
|
+
|
|
22
|
+
Or install it yourself as:
|
|
23
|
+
|
|
24
|
+
$ gem install sidekiq_prometheus
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
To run with the defaults add this to your Sidekiq initializer
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
SidekiqPrometheus.setup
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This will register metrics, start the global reporter (if available), and start the Rack server for scraping. The default port is 9357 but this is easily configurable.
|
|
35
|
+
|
|
36
|
+
If you are running multiple services that will be reporting Sidekiq metrics you will want to take advantage of the `base_labels` configuration option. For example:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
SidekiqPrometheus.configure do |config|
|
|
40
|
+
config.base_labels = { service: 'image_api' }
|
|
41
|
+
config.metrics_port = 9090
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# always call setup after configure.
|
|
45
|
+
SidekiqPrometheus.setup
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The call to `setup` is necessary as that it what registers the metrics and instruments Sidekiq.
|
|
49
|
+
There is also a helper method: `SidekiqPrometheus.configure!` which can be used to configure and setup in one step:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
SidekiqPrometheus.configure! do |config|
|
|
53
|
+
config.base_labels = { service: 'dogs_api' }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# No need to call SidekiqPrometheus.setup because the bang method did it for us/
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Once sidekiq server is running you can see your metrics or scrape them with Prometheus:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
curl http://localhost:8675/metrics
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### Configuration options
|
|
66
|
+
|
|
67
|
+
* `base_labels`: Hash of labels that will be included with every metric when they are registered.
|
|
68
|
+
* `gc_metrics_enabled`: Boolean that determines whether to record object allocation metrics per job. The default is `true`. Setting this to `false` if you don't need this metric.
|
|
69
|
+
* `global_metrics_enabled`: Boolean that determines whether to report global metrics from the PeriodicMetrics reporter. When `true` this will report on a number of stats from the Sidekiq API for the cluster. This requires Sidekiq::Enterprise as the reporter uses the leader election functionality to ensure that only one worker per cluster is reporting metrics.
|
|
70
|
+
* `periodic_metrics_enabled`: Boolean that determines whether to run the periodic metrics reporter. `PeriodicMetrics` runs a separate thread that reports on global metrics (if enabled) as well worker GC stats (if enabled). It reports metrics on the interval defined by `periodic_reporting_interval`. Defatuls to `true`.
|
|
71
|
+
* `periodic_reporting_interval`: interval in seconds for reporting periodic metrics. Default: `30`
|
|
72
|
+
* `metrics_port`: Port on which the rack server will listen. Defaults to `9357`
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
SidekiqPrometheus.configure do |config|
|
|
76
|
+
config.base_labels = { service: 'myapp' }
|
|
77
|
+
config.gc_metrics_enabled = false
|
|
78
|
+
config.global_metrics_enabled = true
|
|
79
|
+
config.periodic_metrics_enabled = true
|
|
80
|
+
config.periodic_reporting_interval = 20
|
|
81
|
+
config.metrics_port = 8675
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Custom labels may be added by defining the `prometheus_labels` method in the worker class:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
class SomeWorker
|
|
89
|
+
include Sidekiq::Worker
|
|
90
|
+
|
|
91
|
+
def prometheus_labels
|
|
92
|
+
{ some: 'label' }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Metrics
|
|
98
|
+
|
|
99
|
+
### JobMetrics
|
|
100
|
+
|
|
101
|
+
All Sidekiq job metrics are reported with these labels:
|
|
102
|
+
|
|
103
|
+
* `class`: Sidekiq worker class name
|
|
104
|
+
* `queue`: Sidekiq queue name
|
|
105
|
+
|
|
106
|
+
| Metric | Type | Description |
|
|
107
|
+
|--------|------|-------------|
|
|
108
|
+
| sidekiq_job_count | counter | Count of Sidekiq jobs |
|
|
109
|
+
| sidekiq_job_duration | histogram | Sidekiq job processing duration |
|
|
110
|
+
| sidekiq_job_success | counter | Count of successful Sidekiq jobs |
|
|
111
|
+
| sidekiq_job_allocated_objects | histogram | Count of ruby objects allocated by a Sidekiq job |
|
|
112
|
+
| sidekiq_job_failed | counter | Count of failed Sidekiq jobs |
|
|
113
|
+
|
|
114
|
+
Notes:
|
|
115
|
+
|
|
116
|
+
* when a job fails only `sidekiq_job_count` and `sidekiq_job_failed` will be reported.
|
|
117
|
+
* `sidekiq_job_allocated_objects` will only be reported if `SidekiqPrometheus.gc_metrics_enabled? == true`
|
|
118
|
+
|
|
119
|
+
### Periodic GC Metrics
|
|
120
|
+
|
|
121
|
+
These require `SidekiqPrometheus.gc_metrics_enabled? == true` and `SidekiqPrometheus.periodic_metrics_enabled? == true`
|
|
122
|
+
|
|
123
|
+
| Metric | Type | Description |
|
|
124
|
+
|--------|------|-------------|
|
|
125
|
+
| sidekiq_allocated_objects | counter | Count of allocated objects by the worker |
|
|
126
|
+
| sidekiq_heap_free_slots | gauge | Number of free heap slots as reported by GC.stat |
|
|
127
|
+
| sidekiq_heap_live_slots | gauge | Number of live heap slots as reported by GC.stat |
|
|
128
|
+
| sidekiq_major_gc_count | counter | Count of major GC runs |
|
|
129
|
+
| sidekiq_minor_gc_count | counter | Count of minor GC runs |
|
|
130
|
+
| sidekiq_rss | gauge | RSS memory usage for worker process |
|
|
131
|
+
|
|
132
|
+
### Periodic Global Metrics
|
|
133
|
+
|
|
134
|
+
These require `SidekiqPrometheus.global_metrics_enabled? == true` and `SidekiqPrometheus.periodic_metrics_enabled? == true`
|
|
135
|
+
|
|
136
|
+
Periodic metric reporting relies onSidekiq Enterprise's leader election functionality ([Ent Leader Election ](https://github.com/mperham/sidekiq/wiki/Ent-Leader-Election))
|
|
137
|
+
which ensures that metrics are only reported once per cluster.
|
|
138
|
+
|
|
139
|
+
| Metric | Type | Description |
|
|
140
|
+
|--------|------|-------------|
|
|
141
|
+
| sidekiq_workers_size | gauge | Total number of workers processing jobs |
|
|
142
|
+
| sidekiq_dead_size | gauge | Total Dead Size |
|
|
143
|
+
| sidekiq_enqueued | gauge | Total Size of all known queues |
|
|
144
|
+
| sidekiq_queue_latency | summary | Latency (in seconds) of all queues |
|
|
145
|
+
| sidekiq_failed | gauge | Number of job executions which raised an error |
|
|
146
|
+
| sidekiq_processed | gauge | Number of job executions completed (success or failure) |
|
|
147
|
+
| sidekiq_retry_size | gauge | Total Retries Size |
|
|
148
|
+
| sidekiq_scheduled_size | gauge | Total Scheduled Size |
|
|
149
|
+
| sidekiq_redis_connected_clients | gauge | Number of clients connected to Redis instance for Sidekiq |
|
|
150
|
+
| sidekiq_redis_used_memory | gauge | Used memory from Redis.info
|
|
151
|
+
| sidekiq_redis_used_memory_peak | gauge | Used memory peak from Redis.info |
|
|
152
|
+
| sidekiq_redis_keys | gauge | Number of redis keys |
|
|
153
|
+
| sidekiq_redis_expires | gauge | Number of redis keys with expiry set |
|
|
154
|
+
|
|
155
|
+
The global metrics are reported with the only the `base_labels` with the exception of `sidekiq_enqueued` which will add a `queue` label and record a metric per Sidekiq queue.
|
|
156
|
+
|
|
157
|
+
## Development
|
|
158
|
+
|
|
159
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
160
|
+
|
|
161
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
162
|
+
|
|
163
|
+
## Contributing
|
|
164
|
+
|
|
165
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/fastly/sidekiq_prometheus. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
166
|
+
|
|
167
|
+
## Copyright
|
|
168
|
+
|
|
169
|
+
Copyright 2019 Fastly, Inc.
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
174
|
+
|
|
175
|
+
## Code of Conduct
|
|
176
|
+
|
|
177
|
+
Everyone interacting in the SidekiqPrometheus project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/sidekiq_prometheus/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/gem_tasks'
|
|
4
|
+
require 'rspec/core/rake_task'
|
|
5
|
+
|
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
7
|
+
|
|
8
|
+
begin
|
|
9
|
+
require 'rubocop/rake_task'
|
|
10
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
|
11
|
+
task.options = ['-c.rubocop.yml']
|
|
12
|
+
end
|
|
13
|
+
rescue LoadError
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
task default: %w[
|
|
17
|
+
rubocop
|
|
18
|
+
spec
|
|
19
|
+
]
|
data/bin/console
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'sidekiq_prometheus'
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
+
# require "pry"
|
|
12
|
+
# Pry.start
|
|
13
|
+
|
|
14
|
+
require 'irb'
|
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'benchmark'
|
|
4
|
+
require 'rack'
|
|
5
|
+
require 'prometheus/client'
|
|
6
|
+
require 'prometheus/middleware/exporter'
|
|
7
|
+
require 'sidekiq'
|
|
8
|
+
require 'sidekiq/api'
|
|
9
|
+
|
|
10
|
+
begin
|
|
11
|
+
require 'sidekiq/ent'
|
|
12
|
+
rescue LoadError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module SidekiqPrometheus
|
|
16
|
+
class << self
|
|
17
|
+
# @return [Hash] Base labels applied to every registered metric
|
|
18
|
+
attr_accessor :base_labels
|
|
19
|
+
|
|
20
|
+
# @return [Hash] Custom labels applied to specific metrics
|
|
21
|
+
attr_accessor :custom_labels
|
|
22
|
+
|
|
23
|
+
# @return [Boolean] Setting to control enabling/disabling GC metrics. Default: true
|
|
24
|
+
attr_accessor :gc_metrics_enabled
|
|
25
|
+
|
|
26
|
+
# @return [Boolean] Setting to control enabling/disabling global metrics. Default: true
|
|
27
|
+
attr_accessor :global_metrics_enabled
|
|
28
|
+
|
|
29
|
+
# @return [Boolean] Setting to control enabling/disabling periodic metrics. Default: true
|
|
30
|
+
attr_accessor :periodic_metrics_enabled
|
|
31
|
+
|
|
32
|
+
# @return [Integer] Interval in seconds to record metrics. Default: 30
|
|
33
|
+
attr_accessor :periodic_reporting_interval
|
|
34
|
+
|
|
35
|
+
# @return [Integer] Port on which the metrics server will listen. Default: 9357
|
|
36
|
+
attr_accessor :metrics_port
|
|
37
|
+
|
|
38
|
+
# Override the default Prometheus::Client
|
|
39
|
+
# @return [Prometheus::Client]
|
|
40
|
+
attr_writer :client
|
|
41
|
+
|
|
42
|
+
# Orverride the default Prometheus Metric Registry
|
|
43
|
+
# @return [Prometheus::Client::Registry]
|
|
44
|
+
attr_writer :registry
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
self.gc_metrics_enabled = true
|
|
48
|
+
self.periodic_metrics_enabled = true
|
|
49
|
+
self.global_metrics_enabled = true
|
|
50
|
+
self.periodic_reporting_interval = 30
|
|
51
|
+
self.metrics_port = 9359
|
|
52
|
+
self.custom_labels = {}
|
|
53
|
+
|
|
54
|
+
module_function
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# @return Prometheus::Client
|
|
58
|
+
def client
|
|
59
|
+
@client ||= Prometheus::Client
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# Configure SidekiqPrometheus
|
|
64
|
+
# @example
|
|
65
|
+
# SidekiqPrometheus.configure do |config|
|
|
66
|
+
# config.base_labels = { service: 'images_api' }
|
|
67
|
+
# config.custom_labels = { sidekiq_job_count: { object_klass: nil } }
|
|
68
|
+
# config.gc_metrics_enabled = true
|
|
69
|
+
# end
|
|
70
|
+
def configure
|
|
71
|
+
yield self
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
##
|
|
75
|
+
# Configure and call setup immediately after
|
|
76
|
+
def configure!
|
|
77
|
+
yield self
|
|
78
|
+
setup
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
##
|
|
82
|
+
# Helper method for +gc_metrics_enabled+ configuration setting
|
|
83
|
+
# @return [Boolean] defaults to true
|
|
84
|
+
def gc_metrics_enabled?
|
|
85
|
+
gc_metrics_enabled
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
##
|
|
89
|
+
# Helper method for +global_metrics_enabled+ configuration setting
|
|
90
|
+
# Requires +Sidekiq::Enterprise+ as it uses the leader election functionality
|
|
91
|
+
# @return [Boolean] defaults to true if +Sidekiq::Enterprise+ is available
|
|
92
|
+
def global_metrics_enabled?
|
|
93
|
+
Object.const_defined?('Sidekiq::Enterprise') && global_metrics_enabled
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
##
|
|
97
|
+
# Helper method for +periodic_metrics_enabled+ configuration setting
|
|
98
|
+
# Requires +Sidekiq::Enterprise+ as it uses the leader election functionality
|
|
99
|
+
# @return [Boolean] defaults to true if +Sidekiq::Enterprise+ is available
|
|
100
|
+
def periodic_metrics_enabled?
|
|
101
|
+
periodic_metrics_enabled
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
##
|
|
105
|
+
# Get a metric from the registry
|
|
106
|
+
# @param metric [Symbol] name of metric to fetch
|
|
107
|
+
# @return [Prometheus::Client::Metric]
|
|
108
|
+
def [](metric)
|
|
109
|
+
registry.get(metric.to_sym)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
class << self
|
|
113
|
+
alias get []
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
##
|
|
117
|
+
# Prometheus client metric registry
|
|
118
|
+
# @return [Prometheus::Client::Registry]
|
|
119
|
+
def registry
|
|
120
|
+
@registry ||= client.registry
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
##
|
|
124
|
+
# register metrics and instrument sidekiq
|
|
125
|
+
def setup
|
|
126
|
+
SidekiqPrometheus::Metrics.register_sidekiq_job_metrics
|
|
127
|
+
SidekiqPrometheus::Metrics.register_sidekiq_gc_metric if gc_metrics_enabled?
|
|
128
|
+
SidekiqPrometheus::Metrics.register_sidekiq_worker_gc_metrics if gc_metrics_enabled? && periodic_metrics_enabled?
|
|
129
|
+
SidekiqPrometheus::Metrics.register_sidekiq_global_metrics if global_metrics_enabled? && periodic_metrics_enabled?
|
|
130
|
+
sidekiq_setup
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
##
|
|
134
|
+
# Add Prometheus instrumentation to sidekiq
|
|
135
|
+
def sidekiq_setup
|
|
136
|
+
Sidekiq.configure_server do |config|
|
|
137
|
+
config.server_middleware do |chain|
|
|
138
|
+
chain.add SidekiqPrometheus::JobMetrics
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
if periodic_metrics_enabled?
|
|
142
|
+
config.on(:startup) { SidekiqPrometheus::PeriodicMetrics.reporter.start }
|
|
143
|
+
config.on(:shutdown) { SidekiqPrometheus::PeriodicMetrics.reporter.stop }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
config.on(:startup) { SidekiqPrometheus.metrics_server }
|
|
147
|
+
config.on(:shutdown) { SidekiqPrometheus.metrics_server.kill }
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
##
|
|
152
|
+
# Start a new Prometheus exporter in a new thread.
|
|
153
|
+
# Will listen on SidekiqPrometheus.metrics_port
|
|
154
|
+
def metrics_server
|
|
155
|
+
@_metrics_server ||= Thread.new do
|
|
156
|
+
Rack::Handler::WEBrick.run(
|
|
157
|
+
Rack::Builder.new {
|
|
158
|
+
use Prometheus::Middleware::Exporter, registry: SidekiqPrometheus.registry
|
|
159
|
+
run ->(_) { [301, { 'Location' => '/metrics' }, []] }
|
|
160
|
+
},
|
|
161
|
+
Port: SidekiqPrometheus.metrics_port,
|
|
162
|
+
BindAddress: '127.0.0.1',
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
def metrics
|
|
170
|
+
SidekiqPrometheus::Metrics
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
require 'sidekiq_prometheus/job_metrics'
|
|
175
|
+
require 'sidekiq_prometheus/metrics'
|
|
176
|
+
require 'sidekiq_prometheus/periodic_metrics'
|
|
177
|
+
require 'sidekiq_prometheus/version'
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class SidekiqPrometheus::JobMetrics
|
|
4
|
+
def call(worker, _job, queue)
|
|
5
|
+
before = GC.stat(:total_allocated_objects) if SidekiqPrometheus.gc_metrics_enabled?
|
|
6
|
+
|
|
7
|
+
labels = { class: worker.class.to_s, queue: queue }
|
|
8
|
+
|
|
9
|
+
begin
|
|
10
|
+
labels.merge!(custom_labels(worker))
|
|
11
|
+
|
|
12
|
+
result = nil
|
|
13
|
+
duration = Benchmark.realtime { result = yield }
|
|
14
|
+
|
|
15
|
+
# In case the labels have changed after the worker perform method has been called
|
|
16
|
+
labels.merge!(custom_labels(worker))
|
|
17
|
+
|
|
18
|
+
registry[:sidekiq_job_duration].observe(labels, duration)
|
|
19
|
+
registry[:sidekiq_job_success].increment(labels)
|
|
20
|
+
|
|
21
|
+
if SidekiqPrometheus.gc_metrics_enabled?
|
|
22
|
+
allocated = GC.stat(:total_allocated_objects) - before
|
|
23
|
+
registry[:sidekiq_job_allocated_objects].observe(labels, allocated)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
result
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
registry[:sidekiq_job_failed].increment(labels)
|
|
29
|
+
raise e
|
|
30
|
+
ensure
|
|
31
|
+
registry[:sidekiq_job_count].increment(labels)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def registry
|
|
38
|
+
SidekiqPrometheus
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def custom_labels(worker)
|
|
42
|
+
worker.respond_to?(:prometheus_labels) ? worker.prometheus_labels : {}
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SidekiqPrometheus::Metrics
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
UNKNOWN = 'unknown'
|
|
7
|
+
|
|
8
|
+
VALID_TYPES = %i[counter gauge histogram summary].freeze
|
|
9
|
+
SIDEKIQ_GLOBAL_METRICS = [
|
|
10
|
+
{ name: :sidekiq_workers_size,
|
|
11
|
+
type: :gauge,
|
|
12
|
+
docstring: 'Total number of workers processing jobs', },
|
|
13
|
+
{ name: :sidekiq_dead_size,
|
|
14
|
+
type: :gauge,
|
|
15
|
+
docstring: 'Total Dead Size', },
|
|
16
|
+
{ name: :sidekiq_enqueued,
|
|
17
|
+
type: :gauge,
|
|
18
|
+
docstring: 'Total Size of all known queues', },
|
|
19
|
+
{ name: :sidekiq_queue_latency,
|
|
20
|
+
type: :summary,
|
|
21
|
+
docstring: 'Latency (in seconds) of all queues', },
|
|
22
|
+
{ name: :sidekiq_failed,
|
|
23
|
+
type: :gauge,
|
|
24
|
+
docstring: 'Number of job executions which raised an error', },
|
|
25
|
+
{ name: :sidekiq_processed,
|
|
26
|
+
type: :gauge,
|
|
27
|
+
docstring: 'Number of job executions completed (success or failure)', },
|
|
28
|
+
{ name: :sidekiq_retry_size,
|
|
29
|
+
type: :gauge,
|
|
30
|
+
docstring: 'Total Retries Size', },
|
|
31
|
+
{ name: :sidekiq_scheduled_size,
|
|
32
|
+
type: :gauge,
|
|
33
|
+
docstring: 'Total Scheduled Size', },
|
|
34
|
+
{ name: :sidekiq_redis_connected_clients,
|
|
35
|
+
type: :gauge,
|
|
36
|
+
docstring: 'Number of clients connected to Redis instance for Sidekiq', },
|
|
37
|
+
{ name: :sidekiq_redis_used_memory,
|
|
38
|
+
type: :gauge,
|
|
39
|
+
docstring: 'Used memory from Redis.info', },
|
|
40
|
+
{ name: :sidekiq_redis_used_memory_peak,
|
|
41
|
+
type: :gauge,
|
|
42
|
+
docstring: 'Used memory peak from Redis.info', },
|
|
43
|
+
{ name: :sidekiq_redis_keys,
|
|
44
|
+
type: :gauge,
|
|
45
|
+
docstring: 'Number of redis keys', },
|
|
46
|
+
{ name: :sidekiq_redis_expires,
|
|
47
|
+
type: :gauge,
|
|
48
|
+
docstring: 'Number of redis keys with expiry set', },
|
|
49
|
+
].freeze
|
|
50
|
+
SIDEKIQ_JOB_METRICS = [
|
|
51
|
+
{ name: :sidekiq_job_count,
|
|
52
|
+
type: :counter,
|
|
53
|
+
docstring: 'Count of Sidekiq jobs', },
|
|
54
|
+
{ name: :sidekiq_job_duration,
|
|
55
|
+
type: :histogram,
|
|
56
|
+
docstring: 'Sidekiq job processing duration', },
|
|
57
|
+
{ name: :sidekiq_job_failed,
|
|
58
|
+
type: :counter,
|
|
59
|
+
docstring: 'Count of failed Sidekiq jobs', },
|
|
60
|
+
{ name: :sidekiq_job_success,
|
|
61
|
+
type: :counter,
|
|
62
|
+
docstring: 'Count of successful Sidekiq jobs', },
|
|
63
|
+
].freeze
|
|
64
|
+
SIDEKIQ_GC_METRIC = {
|
|
65
|
+
name: :sidekiq_job_allocated_objects,
|
|
66
|
+
type: :histogram,
|
|
67
|
+
docstring: 'Count of ruby objects allocated by a Sidekiq job',
|
|
68
|
+
buckets: [10, 50, 100, 500, 1_000, 2_500, 5_000, 10_000, 50_000, 100_000, 500_000, 1_000_000, 5_000_000, 10_000_000, 25_000_000],
|
|
69
|
+
}.freeze
|
|
70
|
+
SIDEKIQ_WORKER_GC_METRICS = [
|
|
71
|
+
{ name: :sidekiq_allocated_objects,
|
|
72
|
+
type: :counter,
|
|
73
|
+
docstring: 'Count of ruby objects allocated by a Sidekiq worker', },
|
|
74
|
+
{ name: :sidekiq_heap_free_slots,
|
|
75
|
+
type: :gauge,
|
|
76
|
+
docstring: 'Sidekiq worker GC.stat[:heap_free_slots]', },
|
|
77
|
+
{ name: :sidekiq_heap_live_slots,
|
|
78
|
+
type: :gauge,
|
|
79
|
+
docstring: 'Sidekiq worker GC.stat[:heap_live_slots]', },
|
|
80
|
+
{ name: :sidekiq_major_gc_count,
|
|
81
|
+
type: :counter,
|
|
82
|
+
docstring: 'Sidekiq worker GC.stat[:major_gc_count]', },
|
|
83
|
+
{ name: :sidekiq_minor_gc_count,
|
|
84
|
+
type: :counter,
|
|
85
|
+
docstring: 'Sidekiq worker GC.stat[:minor_gc_count]', },
|
|
86
|
+
{ name: :sidekiq_rss,
|
|
87
|
+
type: :gauge,
|
|
88
|
+
docstring: 'Sidekiq process RSS', },
|
|
89
|
+
].freeze
|
|
90
|
+
|
|
91
|
+
def registry
|
|
92
|
+
SidekiqPrometheus.registry
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def register_sidekiq_job_metrics
|
|
96
|
+
register_metrics SIDEKIQ_JOB_METRICS
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def register_sidekiq_gc_metric
|
|
100
|
+
register SIDEKIQ_GC_METRIC
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def register_sidekiq_worker_gc_metrics
|
|
104
|
+
register_metrics SIDEKIQ_WORKER_GC_METRICS
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def register_sidekiq_global_metrics
|
|
108
|
+
register_metrics SIDEKIQ_GLOBAL_METRICS
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def register_metrics(metrics)
|
|
112
|
+
metrics.each do |metric|
|
|
113
|
+
register(metric)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
##
|
|
118
|
+
# Fetch a metric from the registry
|
|
119
|
+
# @param name [Symbol] name of metric to fetch
|
|
120
|
+
def [](name)
|
|
121
|
+
registry.get(name.to_sym)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
class << self
|
|
125
|
+
alias get []
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
##
|
|
129
|
+
# Register a new metric
|
|
130
|
+
# @param types [Symbol] type of metric to register. Valid types: %w(counter gauge summary histogram)
|
|
131
|
+
# @param name [Symbol] name of metric
|
|
132
|
+
# @param docstring [String] help text for metric
|
|
133
|
+
# @param base_labels [Hash] Optional hash of base labels to use for every instance of this metric
|
|
134
|
+
# @param buckets [Hash] Optional hash of bucket values. Only used for histogram metrics.
|
|
135
|
+
def register(type:, name:, docstring:, base_labels: {}, buckets: nil)
|
|
136
|
+
raise InvalidMetricType, type unless VALID_TYPES.include? type
|
|
137
|
+
|
|
138
|
+
custom_labels = SidekiqPrometheus.custom_labels[name]
|
|
139
|
+
|
|
140
|
+
base_labels.merge! custom_labels if custom_labels&.is_a?(Hash)
|
|
141
|
+
base_labels.merge! SidekiqPrometheus.base_labels if SidekiqPrometheus.base_labels
|
|
142
|
+
|
|
143
|
+
args = [name.to_sym, docstring]
|
|
144
|
+
args << base_labels
|
|
145
|
+
args << buckets if buckets
|
|
146
|
+
|
|
147
|
+
registry.send(type, *args)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def unregister(name:)
|
|
151
|
+
registry.unregister(name.to_sym)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
class InvalidMetricType < StandardError; end
|
|
155
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Report Sidekiq::Stats to prometheus on a defined interval
|
|
5
|
+
#
|
|
6
|
+
# Global Metrics reporting requires Sidekiq::Enterprise as it uses the leader
|
|
7
|
+
# election functionality to ensure that the global metrics are only reported by
|
|
8
|
+
# one worker.
|
|
9
|
+
#
|
|
10
|
+
# @see https://github.com/mperham/sidekiq/wiki/Ent-Leader-Election
|
|
11
|
+
# @see https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/api.rb
|
|
12
|
+
|
|
13
|
+
class SidekiqPrometheus::PeriodicMetrics
|
|
14
|
+
# @return [Boolean] When +true+ will stop the reporting loop.
|
|
15
|
+
attr_accessor :done
|
|
16
|
+
|
|
17
|
+
# @return [Integer] Interval in seconds to record metrics. Default: [SidekiqPrometheus.periodic_reporting_interval]
|
|
18
|
+
attr_reader :interval
|
|
19
|
+
attr_reader :senate, :sidekiq_stats, :sidekiq_queue
|
|
20
|
+
|
|
21
|
+
GLOBAL_STATS = %i[failed processed retry_size dead_size scheduled_size workers_size].freeze
|
|
22
|
+
GC_STATS = {
|
|
23
|
+
counters: %i[major_gc_count minor_gc_count total_allocated_objects],
|
|
24
|
+
gauges: %i[heap_live_slots heap_free_slots],
|
|
25
|
+
}.freeze
|
|
26
|
+
REDIS_STATS = %w[connected_clients used_memory used_memory_peak].freeze
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# Instance of SidekiqPrometheus::PeriodicMetrics
|
|
30
|
+
# @return [SidekiqPrometheus:PeriodicMetrics]
|
|
31
|
+
def self.reporter
|
|
32
|
+
@reporter ||= new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# @param interval [Integer] Interval in seconds to record metrics.
|
|
37
|
+
# @param sidekiq_stats [Sidekiq::Stats]
|
|
38
|
+
# @param senate [#leader?] Sidekiq::Senate
|
|
39
|
+
def initialize(interval: SidekiqPrometheus.periodic_reporting_interval, sidekiq_stats: Sidekiq::Stats, sidekiq_queue: Sidekiq::Queue, senate: nil)
|
|
40
|
+
self.done = false
|
|
41
|
+
@interval = interval
|
|
42
|
+
|
|
43
|
+
@sidekiq_stats = sidekiq_stats
|
|
44
|
+
@sidekiq_queue = sidekiq_queue
|
|
45
|
+
@senate = if senate.nil?
|
|
46
|
+
if Object.const_defined?('Sidekiq::Senate')
|
|
47
|
+
Sidekiq::Senate
|
|
48
|
+
else
|
|
49
|
+
Senate
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
senate
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# Start the period mettric reporter
|
|
58
|
+
def start
|
|
59
|
+
Sidekiq.logger.info('SidekiqPrometheus: Starting periodic metrics reporting')
|
|
60
|
+
@thread = Thread.new(&method(:run))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
## Stop the periodic metric reporter
|
|
65
|
+
def stop
|
|
66
|
+
self.done = true
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# Record GC and RSS metrics
|
|
71
|
+
def report_gc_metrics
|
|
72
|
+
stats = GC.stat
|
|
73
|
+
GC_STATS[:counters].each do |stat|
|
|
74
|
+
SidekiqPrometheus["sidekiq_#{stat}"]&.increment({}, stats[stat])
|
|
75
|
+
end
|
|
76
|
+
GC_STATS[:gauges].each do |stat|
|
|
77
|
+
SidekiqPrometheus["sidekiq_#{stat}"]&.set({}, stats[stat])
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
SidekiqPrometheus[:sidekiq_rss]&.set({}, rss)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
##
|
|
84
|
+
# Records Sidekiq global metrics
|
|
85
|
+
def report_global_metrics
|
|
86
|
+
current_stats = sidekiq_stats.new
|
|
87
|
+
GLOBAL_STATS.each do |stat|
|
|
88
|
+
SidekiqPrometheus["sidekiq_#{stat}"]&.set({}, current_stats.send(stat))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
sidekiq_queue.all.each do |queue|
|
|
92
|
+
SidekiqPrometheus[:sidekiq_enqueued]&.set({ queue: queue.name }, queue.size)
|
|
93
|
+
SidekiqPrometheus[:sidekiq_queue_latency]&.observe({ queue: queue.name }, queue.latency)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
##
|
|
98
|
+
# Records metrics from Redis
|
|
99
|
+
def report_redis_metrics
|
|
100
|
+
redis_info = begin
|
|
101
|
+
Sidekiq.redis_info
|
|
102
|
+
rescue Redis::BaseConnectionError
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
return if redis_info.nil?
|
|
107
|
+
|
|
108
|
+
REDIS_STATS.each do |stat|
|
|
109
|
+
SidekiqPrometheus["sidekiq_redis_#{stat}"]&.set({}, redis_info[stat].to_i)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
db_stats = redis_info.select { |k, _v| k.match(/^db/) }
|
|
113
|
+
db_stats.each do |db, stat|
|
|
114
|
+
label = { database: db }
|
|
115
|
+
values = stat.scan(/\d+/)
|
|
116
|
+
SidekiqPrometheus[:sidekiq_redis_keys]&.set(label, values[0].to_i)
|
|
117
|
+
SidekiqPrometheus[:sidekiq_redis_expires]&.set(label, values[1].to_i)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
##
|
|
122
|
+
# Fetch rss from proc filesystem
|
|
123
|
+
# @see https://github.com/discourse/prometheus_exporter/blob/v0.3.3/lib/prometheus_exporter/instrumentation/process.rb#L39-L42
|
|
124
|
+
def rss
|
|
125
|
+
pid = Process.pid
|
|
126
|
+
@pagesize ||= `getconf PAGESIZE`.to_i rescue 4096
|
|
127
|
+
File.read("/proc/#{pid}/statm").split(' ')[1].to_i * @pagesize rescue 0
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
##
|
|
131
|
+
# Report metrics and sleep for @interval seconds in a loop.
|
|
132
|
+
# Runs until @done is true
|
|
133
|
+
def run
|
|
134
|
+
until done
|
|
135
|
+
begin
|
|
136
|
+
report_global_metrics if SidekiqPrometheus.global_metrics_enabled? && senate.leader?
|
|
137
|
+
report_redis_metrics if SidekiqPrometheus.global_metrics_enabled? && senate.leader?
|
|
138
|
+
report_gc_metrics if SidekiqPrometheus.gc_metrics_enabled?
|
|
139
|
+
rescue StandardError => e
|
|
140
|
+
Sidekiq.logger.error e
|
|
141
|
+
ensure
|
|
142
|
+
sleep interval
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
##
|
|
148
|
+
# Fake Senate class to guard against undefined constant errors.
|
|
149
|
+
# @private
|
|
150
|
+
class Senate
|
|
151
|
+
def leader?
|
|
152
|
+
false
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require 'sidekiq_prometheus/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = 'sidekiq_prometheus'
|
|
9
|
+
spec.version = SidekiqPrometheus::VERSION
|
|
10
|
+
spec.authors = ['Lukas Eklund']
|
|
11
|
+
spec.email = ['leklund@fastly.com']
|
|
12
|
+
|
|
13
|
+
spec.summary = 'Prometheus Instrumentation for Sidekiq'
|
|
14
|
+
spec.homepage = 'https://github.com/fastly/sidekiq-prometheus'
|
|
15
|
+
spec.license = 'MIT'
|
|
16
|
+
|
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
|
19
|
+
end
|
|
20
|
+
spec.bindir = 'exe'
|
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
22
|
+
spec.require_paths = ['lib']
|
|
23
|
+
|
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
|
25
|
+
spec.add_development_dependency 'pry'
|
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
|
27
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
28
|
+
spec.add_development_dependency 'rubocop', '~> 0.58.0'
|
|
29
|
+
|
|
30
|
+
spec.add_runtime_dependency 'prometheus-client', '~> 0.8.0'
|
|
31
|
+
spec.add_runtime_dependency 'rack'
|
|
32
|
+
spec.add_runtime_dependency 'sidekiq', '~> 5.1'
|
|
33
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sidekiq_prometheus
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.8.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Lukas Eklund
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2019-02-08 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.16'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.16'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: pry
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '10.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '10.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rubocop
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 0.58.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.58.0
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: prometheus-client
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: 0.8.0
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: 0.8.0
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rack
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :runtime
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: sidekiq
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '5.1'
|
|
118
|
+
type: :runtime
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '5.1'
|
|
125
|
+
description:
|
|
126
|
+
email:
|
|
127
|
+
- leklund@fastly.com
|
|
128
|
+
executables: []
|
|
129
|
+
extensions: []
|
|
130
|
+
extra_rdoc_files: []
|
|
131
|
+
files:
|
|
132
|
+
- ".gitignore"
|
|
133
|
+
- ".rubocop.yml"
|
|
134
|
+
- ".travis.yml"
|
|
135
|
+
- CODE_OF_CONDUCT.md
|
|
136
|
+
- Gemfile
|
|
137
|
+
- LICENSE.txt
|
|
138
|
+
- README.md
|
|
139
|
+
- Rakefile
|
|
140
|
+
- bin/console
|
|
141
|
+
- bin/setup
|
|
142
|
+
- lib/sidekiq_prometheus.rb
|
|
143
|
+
- lib/sidekiq_prometheus/job_metrics.rb
|
|
144
|
+
- lib/sidekiq_prometheus/metrics.rb
|
|
145
|
+
- lib/sidekiq_prometheus/periodic_metrics.rb
|
|
146
|
+
- lib/sidekiq_prometheus/version.rb
|
|
147
|
+
- sidekiq_prometheus.gemspec
|
|
148
|
+
homepage: https://github.com/fastly/sidekiq-prometheus
|
|
149
|
+
licenses:
|
|
150
|
+
- MIT
|
|
151
|
+
metadata: {}
|
|
152
|
+
post_install_message:
|
|
153
|
+
rdoc_options: []
|
|
154
|
+
require_paths:
|
|
155
|
+
- lib
|
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
157
|
+
requirements:
|
|
158
|
+
- - ">="
|
|
159
|
+
- !ruby/object:Gem::Version
|
|
160
|
+
version: '0'
|
|
161
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
|
+
requirements:
|
|
163
|
+
- - ">="
|
|
164
|
+
- !ruby/object:Gem::Version
|
|
165
|
+
version: '0'
|
|
166
|
+
requirements: []
|
|
167
|
+
rubyforge_project:
|
|
168
|
+
rubygems_version: 2.7.3
|
|
169
|
+
signing_key:
|
|
170
|
+
specification_version: 4
|
|
171
|
+
summary: Prometheus Instrumentation for Sidekiq
|
|
172
|
+
test_files: []
|