barnes 0.1.0 → 1.0.0
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/.github/workflows/ci.yml +1 -1
- data/CHANGELOG.md +9 -0
- data/README.md +33 -18
- data/barnes.gemspec +3 -4
- data/lib/barnes/instruments/ruby_gc.rb +10 -54
- data/lib/barnes/instruments/stopwatch.rb +15 -29
- data/lib/barnes/periodic.rb +5 -8
- data/lib/barnes/railtie.rb +4 -6
- data/lib/barnes/reporter.rb +75 -25
- data/lib/barnes/resource_usage.rb +5 -22
- data/lib/barnes/version.rb +1 -1
- data/lib/barnes.rb +22 -29
- metadata +5 -20
- data/lib/barnes/instruments/gctools_oobgc.rb +0 -53
- data/lib/barnes/instruments/ree_gc.rb +0 -59
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a6cb33851e3d78680af4ac681b92eacc508b2b265fa6a2ea263e711b6676d5e6
|
|
4
|
+
data.tar.gz: e0d7596baff290abae04eeae0a10e4ac61a3843a145251ea26b7e22050de8f48
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 11565b5d7196d00ba28d8d616ba5c3417f6b68851fd20b567432578e14779cf8c99af4a27f816fff708f192999330ede8ee18020af0ba57137e641cf878c7bda
|
|
7
|
+
data.tar.gz: 6d18ba6866a60f201bce1074a2bd5ce5486d697efd195e8191cce518169541484c9fa2806a389691c6894baaf223376bfb9f6811a8462513a6407b9c0f0df104
|
data/.github/workflows/ci.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
## HEAD (unreleased)
|
|
2
2
|
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
- **Breaking**: Replace StatsD with direct HTTP reporting to HEROKU_METRICS_URL
|
|
6
|
+
- **Breaking**: Remove `statsd:` and `aggregation_period:` parameters from `Barnes.start`
|
|
7
|
+
- **Breaking**: Require Ruby >= 3.1
|
|
8
|
+
- Remove `statsd-ruby` runtime dependency (zero runtime deps)
|
|
9
|
+
- Remove `sample_rate` scaling from instruments
|
|
10
|
+
- Barnes is a no-op when `HEROKU_METRICS_URL` is not set
|
|
11
|
+
|
|
3
12
|
## 0.1.0
|
|
4
13
|
|
|
5
14
|
- Add GitHub Actions to test against multiple Ruby versions
|
data/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
## Barnes
|
|
1
|
+
## Barnes
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Ruby runtime metrics for [Heroku Runtime Metrics](https://devcenter.heroku.com/articles/language-runtime-metrics-ruby). Collects GC stats, ObjectSpace counts, and Puma thread/pool metrics and POSTs them directly to `HEROKU_METRICS_URL`.
|
|
4
|
+
|
|
5
|
+
Originally a fork of [trashed](https://github.com/basecamp/trashed).
|
|
4
6
|
|
|
5
7
|
## Setup
|
|
6
8
|
|
|
7
|
-
### Rails
|
|
9
|
+
### Rails
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
Add this to your Gemfile:
|
|
10
12
|
|
|
11
13
|
```
|
|
12
14
|
gem "barnes"
|
|
@@ -18,33 +20,46 @@ Then run:
|
|
|
18
20
|
$ bundle install
|
|
19
21
|
```
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
Barnes will start automatically via a Railtie when `HEROKU_METRICS_URL` is set in the environment (this is provided automatically on Heroku dynos).
|
|
24
|
+
|
|
25
|
+
### Non-Rails (Puma)
|
|
22
26
|
|
|
23
|
-
Add the gem to
|
|
27
|
+
Add the gem to your Gemfile:
|
|
24
28
|
|
|
25
29
|
```
|
|
26
30
|
gem "barnes"
|
|
27
31
|
```
|
|
28
32
|
|
|
29
|
-
Then
|
|
33
|
+
Then in your `puma.rb`:
|
|
30
34
|
|
|
31
|
-
```
|
|
32
|
-
|
|
35
|
+
```ruby
|
|
36
|
+
require 'barnes'
|
|
37
|
+
|
|
38
|
+
before_fork do
|
|
39
|
+
Barnes.start
|
|
40
|
+
end
|
|
33
41
|
```
|
|
34
42
|
|
|
35
|
-
|
|
43
|
+
## How it works
|
|
36
44
|
|
|
45
|
+
Barnes starts a background thread that collects metrics at a configurable interval (default: 10 seconds) and POSTs them as JSON to the URL in `HEROKU_METRICS_URL`.
|
|
37
46
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
Barnes is a **silent no-op** when:
|
|
48
|
+
|
|
49
|
+
- `HEROKU_METRICS_URL` is not set (e.g. local development)
|
|
50
|
+
- The dyno is a one-off `run.*` dyno
|
|
41
51
|
|
|
42
|
-
|
|
52
|
+
No external dependencies or sidecar processes are required.
|
|
53
|
+
|
|
54
|
+
## Configuration
|
|
43
55
|
|
|
44
56
|
```ruby
|
|
45
|
-
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
57
|
+
Barnes.start(
|
|
58
|
+
interval: 10, # seconds between reports (default: 10)
|
|
59
|
+
panels: [] # custom instrumentation panels (default: built-in ResourceUsage)
|
|
60
|
+
)
|
|
49
61
|
```
|
|
50
62
|
|
|
63
|
+
## Requirements
|
|
64
|
+
|
|
65
|
+
- Ruby >= 3.1
|
data/barnes.gemspec
CHANGED
|
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
|
|
|
9
9
|
spec.authors = ["schneems"]
|
|
10
10
|
spec.email = ["richard.schneeman@gmail.com"]
|
|
11
11
|
|
|
12
|
-
spec.summary = 'Ruby
|
|
13
|
-
spec.description = '
|
|
12
|
+
spec.summary = 'Report Ruby runtime metrics to Heroku'
|
|
13
|
+
spec.description = 'Collect Ruby GC, ObjectSpace, and Puma metrics and report them directly to Heroku Runtime Metrics via HTTP.'
|
|
14
14
|
spec.homepage = 'https://github.com/heroku/barnes'
|
|
15
15
|
spec.license = "MIT"
|
|
16
16
|
|
|
@@ -21,12 +21,11 @@ Gem::Specification.new do |spec|
|
|
|
21
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
22
22
|
spec.require_paths = ["lib"]
|
|
23
23
|
|
|
24
|
-
spec.required_ruby_version = '>=
|
|
24
|
+
spec.required_ruby_version = '>= 3.1.0'
|
|
25
25
|
|
|
26
26
|
spec.add_runtime_dependency 'json'
|
|
27
27
|
spec.add_runtime_dependency 'logger'
|
|
28
28
|
spec.add_runtime_dependency 'ostruct'
|
|
29
|
-
spec.add_runtime_dependency 'statsd-ruby', '~> 1.1'
|
|
30
29
|
|
|
31
30
|
spec.add_development_dependency 'rack', '~> 2'
|
|
32
31
|
spec.add_development_dependency 'rake', '>= 10'
|
|
@@ -1,59 +1,20 @@
|
|
|
1
|
-
# A note on GAUGE_COUNTERS.
|
|
2
|
-
#
|
|
3
|
-
# The sample_rate argument allows for the parameterization
|
|
4
|
-
# of instruments that decide to report data as gauges, that
|
|
5
|
-
# would typically be reported as counters.
|
|
6
|
-
#
|
|
7
|
-
# Aggregating counters is typically done simply with the `+`
|
|
8
|
-
# operator, which doesn't preserve the number of unique
|
|
9
|
-
# reporters that contributed to the count, or allow for one
|
|
10
|
-
# to learn the *average* of the counts posted.
|
|
11
|
-
#
|
|
12
|
-
# A gauge is typically aggregated by simply *replacing* the
|
|
13
|
-
# previous value, however, some systems do *more* with gauges
|
|
14
|
-
# when aggregating across multiple sources of that gauge, like,
|
|
15
|
-
# average, or compute stdev.
|
|
16
|
-
#
|
|
17
|
-
# This is problematic, however, when a gauge is being used as
|
|
18
|
-
# a counter, to preserve the average / stdev computational
|
|
19
|
-
# properties from above, because the interval that the gauge
|
|
20
|
-
# is being read it, affects the derivative of the increasing
|
|
21
|
-
# count. Instead of the derivative over 60s, the derivative is
|
|
22
|
-
# taken every 10s, giving us a derivative value that's approximately
|
|
23
|
-
# 1/6th of the actual derivative over 60s.
|
|
24
|
-
#
|
|
25
|
-
# We compensate for this by allowing Instruments to correct for
|
|
26
|
-
# this, and ensure that, even though it's an estimate, the data
|
|
27
|
-
# is scaled appropriately to the target aggregation interval, not
|
|
28
|
-
# just the collection interval.
|
|
29
|
-
|
|
30
1
|
module Barnes
|
|
31
2
|
module Instruments
|
|
32
3
|
class RubyGC
|
|
4
|
+
# Both sets are delta-computed (cur - last), but they land in
|
|
5
|
+
# different reporting buckets. COUNTERS go to `counters` (accumulated
|
|
6
|
+
# totals the receiver can sum). GAUGE_COUNTERS go to `gauges` so each
|
|
7
|
+
# sample is a standalone per-interval value. Keeping them separate also
|
|
8
|
+
# excludes them from the raw-value gauge loop below.
|
|
33
9
|
COUNTERS = {
|
|
34
10
|
:count => :'GC.count',
|
|
35
11
|
:major_gc_count => :'GC.major_count',
|
|
36
12
|
:minor_gc_count => :'GC.minor_gc_count' }
|
|
37
13
|
|
|
38
|
-
GAUGE_COUNTERS = {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
GC.stat :total_allocated_objects
|
|
43
|
-
rescue ArgumentError
|
|
44
|
-
GAUGE_COUNTERS.update \
|
|
45
|
-
:total_allocated_object => :'GC.total_allocated_objects',
|
|
46
|
-
:total_freed_object => :'GC.total_freed_objects'
|
|
47
|
-
else
|
|
48
|
-
GAUGE_COUNTERS.update \
|
|
49
|
-
:total_allocated_objects => :'GC.total_allocated_objects',
|
|
50
|
-
:total_freed_objects => :'GC.total_freed_objects'
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def initialize(sample_rate)
|
|
54
|
-
# see header for an explanation of how this sample_rate is used
|
|
55
|
-
@sample_rate = sample_rate
|
|
56
|
-
end
|
|
14
|
+
GAUGE_COUNTERS = {
|
|
15
|
+
:total_allocated_objects => :'GC.total_allocated_objects',
|
|
16
|
+
:total_freed_objects => :'GC.total_freed_objects'
|
|
17
|
+
}
|
|
57
18
|
|
|
58
19
|
def start!(state)
|
|
59
20
|
state[:ruby_gc] = GC.stat
|
|
@@ -67,15 +28,10 @@ module Barnes
|
|
|
67
28
|
counters[metric] = cur[stat] - last[stat] if cur.include? stat
|
|
68
29
|
end
|
|
69
30
|
|
|
70
|
-
# special treatment gauges
|
|
71
31
|
GAUGE_COUNTERS.each do |stat, metric|
|
|
72
|
-
if cur.include? stat
|
|
73
|
-
val = cur[stat] - last[stat] if cur.include? stat
|
|
74
|
-
gauges[metric] = val * (1/@sample_rate)
|
|
75
|
-
end
|
|
32
|
+
gauges[metric] = cur[stat] - last[stat] if cur.include? stat
|
|
76
33
|
end
|
|
77
34
|
|
|
78
|
-
# the rest of the gauges
|
|
79
35
|
cur.each do |k, v|
|
|
80
36
|
unless GAUGE_COUNTERS.include? k
|
|
81
37
|
gauges[:"GC.#{k}"] = v
|
|
@@ -26,7 +26,6 @@ module Barnes
|
|
|
26
26
|
class Stopwatch
|
|
27
27
|
def initialize(timepiece = Timepiece)
|
|
28
28
|
@timepiece = timepiece
|
|
29
|
-
@has_cpu_time = timepiece.respond_to?(:cpu)
|
|
30
29
|
end
|
|
31
30
|
|
|
32
31
|
def start!(state)
|
|
@@ -36,33 +35,29 @@ module Barnes
|
|
|
36
35
|
def instrument!(state, counters, gauges)
|
|
37
36
|
last = state[:stopwatch]
|
|
38
37
|
wall_elapsed = @timepiece.wall - last[:wall]
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if @has_cpu_time
|
|
42
|
-
cpu_elapsed = @timepiece.cpu - last[:cpu]
|
|
43
|
-
idle_elapsed = wall_elapsed - cpu_elapsed
|
|
38
|
+
cpu_elapsed = @timepiece.cpu - last[:cpu]
|
|
39
|
+
idle_elapsed = wall_elapsed - cpu_elapsed
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
counters[:'Time.wall'] = wall_elapsed
|
|
42
|
+
counters[:'Time.cpu'] = cpu_elapsed
|
|
43
|
+
counters[:'Time.idle'] = idle_elapsed
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
end
|
|
45
|
+
if wall_elapsed == 0
|
|
46
|
+
counters[:'Time.pct.cpu'] = 0
|
|
47
|
+
counters[:'Time.pct.idle'] = 0
|
|
48
|
+
else
|
|
49
|
+
counters[:'Time.pct.cpu'] = 100.0 * cpu_elapsed / wall_elapsed
|
|
50
|
+
counters[:'Time.pct.idle'] = 100.0 * idle_elapsed / wall_elapsed
|
|
55
51
|
end
|
|
56
52
|
|
|
57
53
|
state[:stopwatch] = current
|
|
58
54
|
end
|
|
59
55
|
|
|
60
56
|
private def current
|
|
61
|
-
|
|
57
|
+
{
|
|
62
58
|
:wall => @timepiece.wall,
|
|
59
|
+
:cpu => @timepiece.cpu,
|
|
63
60
|
}
|
|
64
|
-
state[:cpu] = @timepiece.cpu if @has_cpu_time
|
|
65
|
-
state
|
|
66
61
|
end
|
|
67
62
|
end
|
|
68
63
|
|
|
@@ -71,17 +66,8 @@ module Barnes
|
|
|
71
66
|
::Time.now.to_f * 1000
|
|
72
67
|
end
|
|
73
68
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def self.cpu
|
|
77
|
-
Process.clock_gettime Process::CLOCK_PROCESS_CPUTIME_ID, :float_millisecond
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# ruby-prof installed
|
|
81
|
-
elsif defined? RubyProf::Measure::ProcessTime
|
|
82
|
-
def self.cpu
|
|
83
|
-
RubyProf::Measure::Process.measure * 1000
|
|
84
|
-
end
|
|
69
|
+
def self.cpu
|
|
70
|
+
Process.clock_gettime Process::CLOCK_PROCESS_CPUTIME_ID, :float_millisecond
|
|
85
71
|
end
|
|
86
72
|
end
|
|
87
73
|
end
|
data/lib/barnes/periodic.rb
CHANGED
|
@@ -22,18 +22,14 @@
|
|
|
22
22
|
#
|
|
23
23
|
|
|
24
24
|
require 'barnes/consts'
|
|
25
|
+
require 'json'
|
|
25
26
|
|
|
26
27
|
module Barnes
|
|
27
|
-
# The periodic class is used to send occasional metrics
|
|
28
|
-
# to a reporting instance of `Barnes::Reporter` at a semi-regular
|
|
29
|
-
# rate.
|
|
30
28
|
class Periodic
|
|
31
|
-
def initialize(reporter:,
|
|
29
|
+
def initialize(reporter:, interval: 10, debug: false, panels: [])
|
|
32
30
|
@reporter = reporter
|
|
33
|
-
@reporter.sample_rate = sample_rate
|
|
34
31
|
@debug = debug
|
|
35
|
-
|
|
36
|
-
@interval = sample_rate * 60.0
|
|
32
|
+
@interval = interval
|
|
37
33
|
@panels = panels
|
|
38
34
|
|
|
39
35
|
@thread = Thread.new {
|
|
@@ -47,7 +43,6 @@ module Barnes
|
|
|
47
43
|
begin
|
|
48
44
|
sleep @interval
|
|
49
45
|
|
|
50
|
-
# read the current values
|
|
51
46
|
env = {
|
|
52
47
|
STATE => Thread.current[:barnes_state],
|
|
53
48
|
COUNTERS => {},
|
|
@@ -60,6 +55,8 @@ module Barnes
|
|
|
60
55
|
|
|
61
56
|
puts env.to_json if @debug
|
|
62
57
|
@reporter.report env
|
|
58
|
+
rescue => e
|
|
59
|
+
$stderr.puts "barnes: error during metrics collection: #{e.class}: #{e.message}"
|
|
63
60
|
end
|
|
64
61
|
end
|
|
65
62
|
}
|
data/lib/barnes/railtie.rb
CHANGED
|
@@ -25,8 +25,8 @@ require 'rails/railtie'
|
|
|
25
25
|
|
|
26
26
|
module Barnes
|
|
27
27
|
# Automatically configures barnes to run with
|
|
28
|
-
# rails
|
|
29
|
-
# in the application.rb. For example
|
|
28
|
+
# rails. Configuration can be changed
|
|
29
|
+
# in the application.rb. For example:
|
|
30
30
|
#
|
|
31
31
|
# module YourApp
|
|
32
32
|
# class Application < Rails::Application
|
|
@@ -34,10 +34,8 @@ module Barnes
|
|
|
34
34
|
#
|
|
35
35
|
class Railtie < ::Rails::Railtie
|
|
36
36
|
config.barnes = {
|
|
37
|
-
interval:
|
|
38
|
-
|
|
39
|
-
statsd: DEFAULT_STATSD,
|
|
40
|
-
panels: DEFAULT_PANELS,
|
|
37
|
+
interval: DEFAULT_INTERVAL,
|
|
38
|
+
panels: DEFAULT_PANELS,
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
initializer 'barnes' do |app|
|
data/lib/barnes/reporter.rb
CHANGED
|
@@ -21,42 +21,92 @@
|
|
|
21
21
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
22
|
#
|
|
23
23
|
|
|
24
|
+
require 'net/http'
|
|
25
|
+
require 'json'
|
|
26
|
+
require 'uri'
|
|
27
|
+
|
|
24
28
|
module Barnes
|
|
25
|
-
# The reporter is used to send stats to the server.
|
|
26
|
-
#
|
|
27
|
-
# Example:
|
|
28
|
-
#
|
|
29
|
-
# statsd = Statsd.new('127.0.0.1', "8125")
|
|
30
|
-
# reporter = Reporter.new(statsd: , sample_rate: 10)
|
|
31
|
-
# reporter.report_statsd('barnes.counters' => {"hello" => 2})
|
|
32
29
|
class Reporter
|
|
33
|
-
|
|
30
|
+
MAX_RETRIES = 3
|
|
31
|
+
HTTP_TIMEOUT = 5
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
@statsd = statsd
|
|
37
|
-
@sample_rate = sample_rate.to_f
|
|
33
|
+
ServerError = Class.new(StandardError)
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
def initialize(url:, backoff_sleep: ->(n) { sleep(n) })
|
|
36
|
+
@uri = URI.parse(url)
|
|
37
|
+
@backoff_sleep = backoff_sleep
|
|
38
|
+
@mutex = Mutex.new
|
|
39
|
+
@http = nil
|
|
44
40
|
end
|
|
45
41
|
|
|
46
42
|
def report(env)
|
|
47
|
-
|
|
43
|
+
counters = {}
|
|
44
|
+
env[Barnes::COUNTERS].each { |k, v| counters["Rack.Server.All.#{k}"] = v }
|
|
45
|
+
|
|
46
|
+
gauges = {}
|
|
47
|
+
env[Barnes::GAUGES].each { |k, v| gauges["Rack.Server.All.#{k}"] = v }
|
|
48
|
+
|
|
49
|
+
count = counters.size + gauges.size
|
|
50
|
+
return if count == 0
|
|
51
|
+
|
|
52
|
+
body = JSON.generate(counters: counters, gauges: gauges)
|
|
53
|
+
@mutex.synchronize { post(body, count) }
|
|
48
54
|
end
|
|
49
55
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
end
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def connection
|
|
59
|
+
return @http if @http&.started?
|
|
55
60
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
@http = Net::HTTP.new(@uri.host, @uri.port)
|
|
62
|
+
@http.use_ssl = @uri.scheme == "https"
|
|
63
|
+
@http.open_timeout = HTTP_TIMEOUT
|
|
64
|
+
@http.read_timeout = HTTP_TIMEOUT
|
|
65
|
+
@http.write_timeout = HTTP_TIMEOUT
|
|
66
|
+
@http.keep_alive_timeout = 30
|
|
67
|
+
@http.start
|
|
68
|
+
@http
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def post(body, count)
|
|
72
|
+
retries = 0
|
|
73
|
+
pause = 0.1
|
|
74
|
+
timestamp = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
75
|
+
|
|
76
|
+
begin
|
|
77
|
+
request = Net::HTTP::Post.new(@uri)
|
|
78
|
+
request["Content-Type"] = "application/json"
|
|
79
|
+
request["Measurements-Count"] = count.to_s
|
|
80
|
+
request["Measurements-Time"] = timestamp
|
|
81
|
+
request.body = body
|
|
82
|
+
|
|
83
|
+
response = connection.request(request)
|
|
84
|
+
|
|
85
|
+
case response.code.to_i
|
|
86
|
+
when 200..299
|
|
87
|
+
# success
|
|
88
|
+
when 400..499
|
|
89
|
+
$stderr.puts "barnes: metrics POST rejected (#{response.code}): #{response.body}"
|
|
90
|
+
when 500..599
|
|
91
|
+
raise ServerError, "server error #{response.code}"
|
|
92
|
+
end
|
|
93
|
+
rescue ServerError, Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout,
|
|
94
|
+
Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EHOSTUNREACH,
|
|
95
|
+
Errno::EPIPE, SocketError, IOError => e
|
|
96
|
+
@http&.finish rescue nil
|
|
97
|
+
@http = nil
|
|
98
|
+
if retries < MAX_RETRIES
|
|
99
|
+
retries += 1
|
|
100
|
+
@backoff_sleep.call(pause)
|
|
101
|
+
pause *= 2
|
|
102
|
+
retry
|
|
103
|
+
else
|
|
104
|
+
$stderr.puts "barnes: failed to POST metrics after #{MAX_RETRIES} retries: #{e.class}: #{e.message}"
|
|
59
105
|
end
|
|
106
|
+
rescue => e
|
|
107
|
+
@http&.finish rescue nil
|
|
108
|
+
@http = nil
|
|
109
|
+
$stderr.puts "barnes: unexpected error posting metrics: #{e.class}: #{e.message}"
|
|
60
110
|
end
|
|
61
111
|
end
|
|
62
112
|
end
|
|
@@ -25,7 +25,7 @@ require 'barnes/panel'
|
|
|
25
25
|
|
|
26
26
|
module Barnes
|
|
27
27
|
class ResourceUsage < Panel
|
|
28
|
-
def initialize
|
|
28
|
+
def initialize
|
|
29
29
|
super()
|
|
30
30
|
|
|
31
31
|
require 'barnes/instruments/puma_instrument'
|
|
@@ -38,28 +38,11 @@ module Barnes
|
|
|
38
38
|
require 'barnes/instruments/stopwatch'
|
|
39
39
|
instrument Barnes::Instruments::Stopwatch.new
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
instrument Barnes::Instruments::Ruby18GC.new
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Ruby 1.9+
|
|
47
|
-
if ObjectSpace.respond_to? :count_objects
|
|
48
|
-
require 'barnes/instruments/object_space_counter'
|
|
49
|
-
instrument Barnes::Instruments::ObjectSpaceCounter.new
|
|
50
|
-
end
|
|
41
|
+
require 'barnes/instruments/object_space_counter'
|
|
42
|
+
instrument Barnes::Instruments::ObjectSpaceCounter.new
|
|
51
43
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
require 'barnes/instruments/ruby_gc'
|
|
55
|
-
instrument Barnes::Instruments::RubyGC.new(sample_rate)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Ruby 2.1+ with https://github.com/tmm1/gctools
|
|
59
|
-
if defined? GC::OOB
|
|
60
|
-
require 'barnes/instruments/gctools_oobgc'
|
|
61
|
-
instrument Barnes::Instruments::GctoolsOobgc.new
|
|
62
|
-
end
|
|
44
|
+
require 'barnes/instruments/ruby_gc'
|
|
45
|
+
instrument Barnes::Instruments::RubyGC.new
|
|
63
46
|
end
|
|
64
47
|
end
|
|
65
48
|
end
|
data/lib/barnes/version.rb
CHANGED
data/lib/barnes.rb
CHANGED
|
@@ -22,45 +22,38 @@
|
|
|
22
22
|
#
|
|
23
23
|
|
|
24
24
|
module Barnes
|
|
25
|
-
DEFAULT_INTERVAL
|
|
26
|
-
|
|
27
|
-
DEFAULT_STATSD = :default
|
|
28
|
-
DEFAULT_PANELS = []
|
|
25
|
+
DEFAULT_INTERVAL = 10
|
|
26
|
+
DEFAULT_PANELS = [].freeze
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
#
|
|
28
|
+
# Starts the metrics reporting client.
|
|
29
|
+
#
|
|
30
|
+
# Collects Ruby runtime metrics (GC stats, ObjectSpace counts,
|
|
31
|
+
# Puma pool stats) and POSTs them to HEROKU_METRICS_URL.
|
|
32
32
|
#
|
|
33
33
|
# Arguments:
|
|
34
34
|
#
|
|
35
|
-
# - interval: How often, in seconds, to instrument and report
|
|
36
|
-
# - aggregation_period: The minimal aggregation period in use, in seconds.
|
|
37
|
-
# - statsd: The statsd reporter. This should be an instance of statsd-ruby
|
|
35
|
+
# - interval: How often, in seconds, to instrument and report.
|
|
38
36
|
# - panels: The instrumentation "panels" in use. See `resource_usage.rb` for
|
|
39
37
|
# an example panel, which is the default if none are provided.
|
|
40
|
-
def self.start(interval: DEFAULT_INTERVAL,
|
|
41
|
-
|
|
42
|
-
statsd_client = statsd
|
|
43
|
-
panels = panels
|
|
44
|
-
sample_rate = interval.to_f / aggregation_period.to_f
|
|
38
|
+
def self.start(interval: DEFAULT_INTERVAL, panels: DEFAULT_PANELS)
|
|
39
|
+
return if ENV["DYNO"]&.start_with?("run.")
|
|
45
40
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
end
|
|
41
|
+
url = ENV["HEROKU_METRICS_URL"]
|
|
42
|
+
return unless url
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
reporter = Barnes::Reporter.new(statsd: statsd_client, sample_rate: sample_rate)
|
|
44
|
+
reporter = Barnes::Reporter.new(url: url)
|
|
52
45
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
Periodic.new(
|
|
58
|
-
reporter: reporter,
|
|
59
|
-
sample_rate: sample_rate,
|
|
60
|
-
panels: panels,
|
|
61
|
-
debug: ENV['BARNES_DEBUG']
|
|
62
|
-
)
|
|
46
|
+
panels = panels.dup
|
|
47
|
+
if panels.empty?
|
|
48
|
+
panels << Barnes::ResourceUsage.new
|
|
63
49
|
end
|
|
50
|
+
|
|
51
|
+
Periodic.new(
|
|
52
|
+
reporter: reporter,
|
|
53
|
+
interval: interval,
|
|
54
|
+
panels: panels,
|
|
55
|
+
debug: ENV['BARNES_DEBUG']
|
|
56
|
+
)
|
|
64
57
|
end
|
|
65
58
|
end
|
|
66
59
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: barnes
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- schneems
|
|
@@ -51,20 +51,6 @@ dependencies:
|
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '0'
|
|
54
|
-
- !ruby/object:Gem::Dependency
|
|
55
|
-
name: statsd-ruby
|
|
56
|
-
requirement: !ruby/object:Gem::Requirement
|
|
57
|
-
requirements:
|
|
58
|
-
- - "~>"
|
|
59
|
-
- !ruby/object:Gem::Version
|
|
60
|
-
version: '1.1'
|
|
61
|
-
type: :runtime
|
|
62
|
-
prerelease: false
|
|
63
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
-
requirements:
|
|
65
|
-
- - "~>"
|
|
66
|
-
- !ruby/object:Gem::Version
|
|
67
|
-
version: '1.1'
|
|
68
54
|
- !ruby/object:Gem::Dependency
|
|
69
55
|
name: rack
|
|
70
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -135,7 +121,8 @@ dependencies:
|
|
|
135
121
|
- - "~>"
|
|
136
122
|
- !ruby/object:Gem::Version
|
|
137
123
|
version: '0.1'
|
|
138
|
-
description:
|
|
124
|
+
description: Collect Ruby GC, ObjectSpace, and Puma metrics and report them directly
|
|
125
|
+
to Heroku Runtime Metrics via HTTP.
|
|
139
126
|
email:
|
|
140
127
|
- richard.schneeman@gmail.com
|
|
141
128
|
executables: []
|
|
@@ -160,11 +147,9 @@ files:
|
|
|
160
147
|
- init.rb
|
|
161
148
|
- lib/barnes.rb
|
|
162
149
|
- lib/barnes/consts.rb
|
|
163
|
-
- lib/barnes/instruments/gctools_oobgc.rb
|
|
164
150
|
- lib/barnes/instruments/object_space_counter.rb
|
|
165
151
|
- lib/barnes/instruments/puma_instrument.rb
|
|
166
152
|
- lib/barnes/instruments/puma_stats_value.rb
|
|
167
|
-
- lib/barnes/instruments/ree_gc.rb
|
|
168
153
|
- lib/barnes/instruments/ruby_gc.rb
|
|
169
154
|
- lib/barnes/instruments/stopwatch.rb
|
|
170
155
|
- lib/barnes/panel.rb
|
|
@@ -184,7 +169,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
184
169
|
requirements:
|
|
185
170
|
- - ">="
|
|
186
171
|
- !ruby/object:Gem::Version
|
|
187
|
-
version:
|
|
172
|
+
version: 3.1.0
|
|
188
173
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
189
174
|
requirements:
|
|
190
175
|
- - ">="
|
|
@@ -193,5 +178,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
193
178
|
requirements: []
|
|
194
179
|
rubygems_version: 3.6.9
|
|
195
180
|
specification_version: 4
|
|
196
|
-
summary: Ruby
|
|
181
|
+
summary: Report Ruby runtime metrics to Heroku
|
|
197
182
|
test_files: []
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2017 Salesforce
|
|
2
|
-
# Copyright (c) 2009 37signals, LLC
|
|
3
|
-
#
|
|
4
|
-
# Permission is hereby granted, free of charge, to any person obtaining
|
|
5
|
-
# a copy of this software and associated documentation files (the
|
|
6
|
-
# "Software"), to deal in the Software without restriction, including
|
|
7
|
-
# without limitation the rights to use, copy, modify, merge, publish,
|
|
8
|
-
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
9
|
-
# permit persons to whom the Software is furnished to do so, subject to
|
|
10
|
-
# the following conditions:
|
|
11
|
-
|
|
12
|
-
# The above copyright notice and this permission notice shall be
|
|
13
|
-
# included in all copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
19
|
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
20
|
-
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
21
|
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
module Barnes
|
|
25
|
-
module Instruments
|
|
26
|
-
# Tracks out of band GCs that occurred *since* the last request.
|
|
27
|
-
class GctoolsOobgc
|
|
28
|
-
def start!(state)
|
|
29
|
-
state[:oobgc] = current
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def instrument!(state, counters, gauges)
|
|
33
|
-
last = state[:oobgc]
|
|
34
|
-
cur = state[:oobgc] = current
|
|
35
|
-
|
|
36
|
-
counters.update \
|
|
37
|
-
:'OOBGC.count' => cur[:count] - last[:count],
|
|
38
|
-
:'OOBGC.major_count' => cur[:major] - last[:major],
|
|
39
|
-
:'OOBGC.minor_count' => cur[:minor] - last[:minor],
|
|
40
|
-
:'OOBGC.sweep_count' => cur[:sweep] - last[:sweep]
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
private def current
|
|
44
|
-
{
|
|
45
|
-
:count => GC::OOB.stat(:count).to_i,
|
|
46
|
-
:major => GC::OOB.stat(:major).to_i,
|
|
47
|
-
:minor => GC::OOB.stat(:minor).to_i,
|
|
48
|
-
:sweep => GC::OOB.stat(:sweep).to_i
|
|
49
|
-
}
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2017 Salesforce
|
|
2
|
-
# Copyright (c) 2009 37signals, LLC
|
|
3
|
-
#
|
|
4
|
-
# Permission is hereby granted, free of charge, to any person obtaining
|
|
5
|
-
# a copy of this software and associated documentation files (the
|
|
6
|
-
# "Software"), to deal in the Software without restriction, including
|
|
7
|
-
# without limitation the rights to use, copy, modify, merge, publish,
|
|
8
|
-
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
9
|
-
# permit persons to whom the Software is furnished to do so, subject to
|
|
10
|
-
# the following conditions:
|
|
11
|
-
|
|
12
|
-
# The above copyright notice and this permission notice shall be
|
|
13
|
-
# included in all copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
19
|
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
20
|
-
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
21
|
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
module Barnes
|
|
25
|
-
module Instruments
|
|
26
|
-
class Ruby18GC
|
|
27
|
-
def initialize
|
|
28
|
-
GC.enable_stats
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def start!(state)
|
|
32
|
-
state[:ruby18_gc] = current
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def instrument!(state, counters, gauges)
|
|
36
|
-
last = state[:ruby18_gc]
|
|
37
|
-
cur = state[:ruby18_gc] = current
|
|
38
|
-
|
|
39
|
-
counters.update \
|
|
40
|
-
:'GC.count' => cur[:gc_count] - before[:gc_count],
|
|
41
|
-
:'GC.time' => cur[:gc_time] - before[:gc_time],
|
|
42
|
-
:'GC.memory' => cur[:gc_memory] - before[:gc_memory],
|
|
43
|
-
:'GC.allocated_objects' => cur[:objects] - before[:objects]
|
|
44
|
-
|
|
45
|
-
gauges[:'Objects.live'] = ObjectSpace.live_objects
|
|
46
|
-
gauges[:'GC.growth'] = GC.growth
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
private def current
|
|
50
|
-
{
|
|
51
|
-
:objects => ObjectSpace.allocated_objects,
|
|
52
|
-
:gc_count => GC.collections,
|
|
53
|
-
:gc_time => GC.time,
|
|
54
|
-
:gc_memory => GC.allocated_size
|
|
55
|
-
}
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|