trashed 3.0.1 → 3.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: fc92cf23f413c7dbc13b7e974f0ff667050296dd
4
- data.tar.gz: 83a9dacf210062f9aec4d1632095989ec20be08b
2
+ SHA1:
3
+ metadata.gz: 403348c2e748309e00d851be66bd0428116c9961
4
+ data.tar.gz: ad21d476079f3aa50de9290d2ee338e060df9fd7
5
5
  SHA512:
6
- metadata.gz: a13685415df07c31c6b3a95afc25ac2b22c18325b8b799c9b3eb4fca7a4bf805bc0c5bf4acb5f787f4a401214b3c79e05f5d5464deb33936cc5488fbe5934d2f
7
- data.tar.gz: ad71d73f6ded5cd28c032b54b45ccec80eccbd2cf129c50582740b0d187331fa8af13db2384e26312b4ba8721f37ee11a098c6fa886280e2b03d7e48e3d71ea3
6
+ metadata.gz: 14c777b0892850a0402d084919b3fabf9932f812f6a41085985ca83ad4cc38d35c495b72ba7bd6cae8fd5de1c366342aa6fc2dfe5103b1f1574e1cb8445ac3de
7
+ data.tar.gz: d51091fb467bc565d4b4aa854ca509745d8aed287c577aaf1498ca37793a1a6a1d12515fff7a37670ef330c1addf0b6ad784ba7afc40b0165e5612a6fdc380fe
@@ -0,0 +1,27 @@
1
+ module Trashed
2
+ module Instruments
3
+ # Tracks out of band GCs that occurred *since* the last request.
4
+ class GctoolsOobgc
5
+ def start(state, timings, gauges)
6
+ last = state[:persistent][:oobgc] || Hash.new(0)
7
+
8
+ current = {
9
+ :count => GC::OOB.stat(:count),
10
+ :major => GC::OOB.stat(:major),
11
+ :minor => GC::OOB.stat(:minor),
12
+ :sweep => GC::OOB.stat(:sweep) }
13
+
14
+ timings.update \
15
+ :'OOBGC.count' => current[:count] - last[:count],
16
+ :'OOBGC.major_count' => current[:major] - last[:major],
17
+ :'OOBGC.minor_count' => current[:minor] - last[:minor],
18
+ :'OOBGC.sweep_count' => current[:sweep] - last[:sweep]
19
+
20
+ state[:persistent][:oobgc] = current
21
+ end
22
+
23
+ def measure(state, timings, gauges)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -5,20 +5,22 @@ module Trashed
5
5
  GC.enable_stats
6
6
  end
7
7
 
8
- def start(state)
9
- state.update \
10
- :'Objects.total' => ObjectSpace.allocated_objects,
11
- :'GC.count' => GC.collections,
12
- :'GC.elapsed' => GC.time,
13
- :'GC.memory' => GC.allocated_size
8
+ def start(state, timings, gauges)
9
+ state[:ruby18_gc] = {
10
+ :objects => ObjectSpace.allocated_objects,
11
+ :gc_count => GC.collections,
12
+ :gc_time => GC.time,
13
+ :gc_memory => GC.allocated_size }
14
14
  end
15
15
 
16
16
  def measure(state, timings, gauges)
17
+ before = state[:ruby18_gc]
18
+
17
19
  timings.update \
18
- :'Objects.total' => ObjectSpace.allocated_objects - state[:'Objects.total'],
19
- :'GC.count' => GC.collections - state[:'GC.count'],
20
- :'GC.elapsed' => GC.time - state[:'GC.elapsed'],
21
- :'GC.memory' => GC.allocated_size - state[:'GC.memory']
20
+ :'GC.count' => GC.collections - before[:gc_count],
21
+ :'GC.time' => GC.time - before[:gc_time],
22
+ :'GC.memory' => GC.allocated_size - before[:gc_memory],
23
+ :'GC.allocated_objects' => ObjectSpace.allocated_objects - before[:objects]
22
24
 
23
25
  gauges << [ :'Objects.live', ObjectSpace.live_objects ]
24
26
  gauges << [ :'GC.growth', GC.growth ]
@@ -0,0 +1,28 @@
1
+ module Trashed
2
+ module Instruments
3
+ class RubyGC
4
+ def start(state, timings, gauges)
5
+ state[:ruby_gc] = GC.stat
6
+ end
7
+
8
+ MEASUREMENTS = {
9
+ :count => :'GC.count',
10
+ :major_gc_count => :'GC.major_count',
11
+ :minor_gc_count => :'GC.minor_gc_count',
12
+ :total_allocated_object => :'GC.allocated_objects',
13
+ :total_freed_object => :'GC.freed_objects'
14
+ }
15
+
16
+ def measure(state, timings, gauges)
17
+ gc = GC.stat
18
+ before = state[:ruby_gc]
19
+
20
+ MEASUREMENTS.each do |stat, metric|
21
+ timings[metric] = gc[stat] - before[stat] if gc.include? stat
22
+ end
23
+
24
+ gauges.concat gc.map { |k, v| [ :"GC.#{k}", v ] }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -5,21 +5,26 @@ module Trashed
5
5
  @has_raw_data = GC::Profiler.respond_to?(:raw_data)
6
6
  end
7
7
 
8
- def start(state)
8
+ # Captures out-of-band GC time and stats.
9
+ def start(state, timings, gauges)
9
10
  GC::Profiler.enable
10
- GC::Profiler.clear
11
+ measure state, timings, gauges, :OOBGC
11
12
  end
12
13
 
13
- def measure(state, timings, gauges)
14
- timings[:'GC.time'] = GC::Profiler.total_time
14
+ # Captures in-band GC time and stats.
15
+ def measure(state, timings, gauges, captured = :GC)
16
+ timings[:"#{captured}.time"] ||= GC::Profiler.total_time
15
17
 
16
18
  if @has_raw_data
19
+ timings[:"#{captured}.count"] ||= GC::Profiler.raw_data.size
20
+
21
+ timings[:'GC.interval'] = GC::Profiler.raw_data.map { |data| data[:GC_INVOKE_TIME] }
22
+
17
23
  GC::Profiler.raw_data.each do |data|
18
24
  gauges.concat data.map { |k, v| [ :"GC.Profiler.#{k}", v ] }
19
25
  end
20
26
  end
21
27
 
22
- GC::Profiler.disable
23
28
  GC::Profiler.clear
24
29
  end
25
30
  end
@@ -6,37 +6,47 @@ module Trashed
6
6
  @has_cpu_time = timepiece.respond_to?(:cpu)
7
7
  end
8
8
 
9
- def start(state)
10
- state[:'Time.wall'] = @timepiece.wall
11
- state[:'Time.cpu'] = @timepiece.cpu if @has_cpu_time
9
+ def start(state, timings, gauges)
10
+ state[:stopwatch_wall] = @timepiece.wall
11
+ state[:stopwatch_cpu] = @timepiece.cpu if @has_cpu_time
12
12
  end
13
13
 
14
14
  def measure(state, timings, gauges)
15
- wall_elapsed = @timepiece.wall - state.delete(:'Time.wall')
15
+ wall_elapsed = @timepiece.wall - state.delete(:stopwatch_wall)
16
16
  timings[:'Time.wall'] = wall_elapsed
17
17
  if @has_cpu_time
18
- cpu_elapsed = @timepiece.cpu - state.delete(:'Time.cpu')
19
- timings[:'Time.cpu'] = cpu_elapsed
20
- timings[:'Time.idle'] = wall_elapsed - cpu_elapsed
18
+ cpu_elapsed = @timepiece.cpu - state.delete(:stopwatch_cpu)
19
+ idle_elapsed = wall_elapsed - cpu_elapsed
20
+
21
+ timings[:'Time.cpu'] = cpu_elapsed
22
+ timings[:'Time.idle'] = idle_elapsed
23
+
24
+ if wall_elapsed == 0
25
+ timings[:'Time.pct.cpu'] = 0
26
+ timings[:'Time.pct.idle'] = 0
27
+ else
28
+ timings[:'Time.pct.cpu'] = 100.0 * cpu_elapsed / wall_elapsed
29
+ timings[:'Time.pct.idle'] = 100.0 * idle_elapsed / wall_elapsed
30
+ end
21
31
  end
22
32
  end
23
33
  end
24
34
 
25
35
  module Timepiece
26
36
  def self.wall
27
- (::Time.now.to_f * 1000).to_i
37
+ ::Time.now.to_f * 1000
28
38
  end
29
39
 
30
40
  # Ruby 2.1+
31
41
  if Process.respond_to?(:clock_gettime)
32
42
  def self.cpu
33
- Process.clock_gettime Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond
43
+ Process.clock_gettime Process::CLOCK_PROCESS_CPUTIME_ID, :float_millisecond
34
44
  end
35
45
 
36
46
  # ruby-prof installed
37
47
  elsif defined? RubyProf::Measure::ProcessTime
38
48
  def self.cpu
39
- (RubyProf::Measure::Process.measure * 1000).to_i
49
+ RubyProf::Measure::Process.measure * 1000
40
50
  end
41
51
  end
42
52
  end
data/lib/trashed/meter.rb CHANGED
@@ -25,7 +25,7 @@ module Trashed
25
25
  end
26
26
 
27
27
  def instrument!(state, timings, gauges)
28
- @needs_start.each { |i| i.start state }
28
+ @needs_start.each { |i| i.start state, timings, gauges }
29
29
  yield.tap do
30
30
  @instruments.reverse_each { |i| i.measure state, timings, gauges }
31
31
  end
@@ -36,7 +36,7 @@ module Trashed
36
36
  @name, @probe = name, probe
37
37
  end
38
38
 
39
- def start(state)
39
+ def start(state, timings, gauges)
40
40
  state[@name] = @probe.call
41
41
  end
42
42
 
data/lib/trashed/rack.rb CHANGED
@@ -11,7 +11,7 @@ module Trashed
11
11
  end
12
12
 
13
13
  def call(env)
14
- env[STATE] = {}
14
+ env[STATE] = { :persistent => persistent_thread_state }
15
15
  env[TIMINGS] = {}
16
16
  env[GAUGES] = []
17
17
 
@@ -19,6 +19,10 @@ module Trashed
19
19
  end
20
20
 
21
21
  private
22
+ def persistent_thread_state
23
+ Thread.current[:trashed_rack_state] ||= {}
24
+ end
25
+
22
26
  def build_sampled_instrumented_app(app, meters)
23
27
  build_sampled_app app, build_instrumented_app(app, meters)
24
28
  end
@@ -6,6 +6,21 @@ module Trashed
6
6
  class Railtie < ::Rails::Railtie
7
7
  config.trashed = Trashed::Reporter.new
8
8
 
9
+ # Middleware would like to emit tagged logs after Rails::Rack::Logger
10
+ # pops its tags. Introduce this haxware to stash the tags in the Rack
11
+ # env so we can reuse them later.
12
+ class ExposeLoggerTagsToRackEnv
13
+ def initialize(app)
14
+ @app = app
15
+ end
16
+
17
+ def call(env)
18
+ @app.call(env).tap do
19
+ env['trashed.logger.tags'] = Rails.logger.formatter.current_tags.dup
20
+ end
21
+ end
22
+ end
23
+
9
24
  initializer 'trashed' do |app|
10
25
  require 'statsd'
11
26
 
@@ -13,6 +28,7 @@ module Trashed
13
28
  app.config.trashed.logger ||= Rails.logger
14
29
 
15
30
  app.middleware.insert_after 'Rack::Runtime', Trashed::Rack, app.config.trashed
31
+ app.middleware.insert_after 'Rails::Rack::Logger', ExposeLoggerTagsToRackEnv
16
32
  end
17
33
  end
18
34
  end
@@ -32,7 +32,51 @@ module Trashed
32
32
 
33
33
  def report_logger(env)
34
34
  timings = env[Trashed::Rack::TIMINGS]
35
- @logger.info "Rack handled in %dms (GC runs: %d)" % timings.values_at(:'Time.wall', :'GC.count')
35
+ parts = []
36
+
37
+ elapsed = '%.2fms' % timings[:'Time.wall']
38
+ if timings[:'Time.pct.cpu']
39
+ elapsed << ' (%.1f%% cpu, %.1f%% idle)' % timings.values_at(:'Time.pct.cpu', :'Time.pct.idle').map(&:to_i)
40
+ end
41
+ parts << elapsed
42
+
43
+ obj = timings[:'GC.allocated_objects'].to_i
44
+ parts << '%d objects' % obj unless obj.zero?
45
+
46
+ if gcs = timings[:'GC.count'].to_i
47
+ gc = '%d GCs' % gcs
48
+ unless gcs.zero?
49
+ if timings.include?(:'GC.major_count')
50
+ gc << ' (%d major, %d minor)' % timings.values_at(:'GC.major_count', :'GC.minor_count').map(&:to_i)
51
+ end
52
+ if timings.include?(:'GC.time')
53
+ gc << ' took %.2fms' % timings[:'GC.time']
54
+ end
55
+ end
56
+ parts << gc
57
+ end
58
+
59
+ oobgcs = timings[:'OOBGC.count'].to_i
60
+ if !oobgcs.zero?
61
+ oobgc = 'Avoided %d OOB GCs' % oobgcs
62
+ if timings[:'OOBGC.major_count']
63
+ oobgc << ' (%d major, %d minor, %d sweep)' % timings.values_at(:'OOBGC.major_count', :'OOBGC.minor_count', :'OOBGC.sweep_count').map(&:to_i)
64
+ end
65
+ if timings[:'OOBGC.time']
66
+ oobgc << ' saving %.2fms' % timings[:'OOBGC.time']
67
+ end
68
+ parts << oobgc
69
+ end
70
+
71
+ message = "Rack handled in #{parts * '. '}."
72
+
73
+ if @logger.respond_to?(:tagged) && env.include?('trashed.logger.tags')
74
+ @logger.tagged env['trashed.logger.tags'] do
75
+ @logger.info message
76
+ end
77
+ else
78
+ @logger.info message
79
+ end
36
80
  end
37
81
 
38
82
  def report_statsd(env)
@@ -44,7 +88,12 @@ module Trashed
44
88
 
45
89
  def send_to_statsd(statsd, method, measurements, namespace, dimensions)
46
90
  measurements.each do |metric, value|
47
- if value.is_a? Numeric
91
+ case value
92
+ when Array
93
+ value.each do |v|
94
+ send_to_statsd statsd, method, { metric => v }, namespace, dimensions
95
+ end
96
+ when Numeric
48
97
  Array(dimensions || :All).each do |dimension|
49
98
  statsd.send method, :"#{namespace}.#{dimension}.#{metric}", value, @sample_rate
50
99
  end
@@ -21,20 +21,8 @@ module Trashed
21
21
 
22
22
  # Ruby 1.9+
23
23
  if GC.respond_to?(:stat)
24
- case
25
- # Ruby 2.1+
26
- when GC.stat[:major_gc_count]
27
- require 'trashed/instruments/ruby21_gc'
28
- meter.instrument Trashed::Instruments::Ruby21GC.new
29
- # Ruby 2.0+
30
- when GC.stat[:total_allocated_object]
31
- require 'trashed/instruments/ruby20_gc'
32
- meter.instrument Trashed::Instruments::Ruby20GC.new
33
- # Ruby 1.9
34
- else
35
- require 'trashed/instruments/ruby19_gc'
36
- meter.instrument Trashed::Instruments::Ruby19GC.new
37
- end
24
+ require 'trashed/instruments/ruby_gc'
25
+ meter.instrument Trashed::Instruments::RubyGC.new
38
26
  end
39
27
 
40
28
  # Ruby 1.9+
@@ -42,5 +30,11 @@ module Trashed
42
30
  require 'trashed/instruments/ruby_gc_profiler'
43
31
  meter.instrument Trashed::Instruments::RubyGCProfiler.new
44
32
  end
33
+
34
+ # Ruby 2.1+ with https://github.com/tmm1/gctools
35
+ if defined? GC::OOB
36
+ require 'trashed/instruments/gctools_oobgc'
37
+ meter.instrument Trashed::Instruments::GctoolsOobgc.new
38
+ end
45
39
  end
46
40
  end
metadata CHANGED
@@ -1,79 +1,80 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trashed
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.1
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Kemper
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-30 00:00:00.000000000 Z
11
+ date: 2014-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: statsd-ruby
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ! '>='
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '10.2'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ! '>='
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '10.2'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '5.3'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5.3'
55
- description:
55
+ description: Each Rack request eats up time, objects, and GC. Report usage to logs
56
+ and StatsD.
56
57
  email: jeremykemper@gmail.com
57
58
  executables: []
58
59
  extensions: []
59
60
  extra_rdoc_files: []
60
61
  files:
61
- - ./init.rb
62
- - ./lib/trashed.rb
63
- - ./lib/trashed/instruments/object_space_counter.rb
64
- - ./lib/trashed/instruments/ree_gc.rb
65
- - ./lib/trashed/instruments/ruby19_gc.rb
66
- - ./lib/trashed/instruments/ruby20_gc.rb
67
- - ./lib/trashed/instruments/ruby21_gc.rb
68
- - ./lib/trashed/instruments/ruby_gc_profiler.rb
69
- - ./lib/trashed/instruments/stopwatch.rb
70
- - ./lib/trashed/meter.rb
71
- - ./lib/trashed/rack.rb
72
- - ./lib/trashed/railtie.rb
73
- - ./lib/trashed/reporter.rb
74
- - ./lib/trashed/resource_usage.rb
62
+ - "./init.rb"
63
+ - "./lib/trashed.rb"
64
+ - "./lib/trashed/instruments/gctools_oobgc.rb"
65
+ - "./lib/trashed/instruments/object_space_counter.rb"
66
+ - "./lib/trashed/instruments/ree_gc.rb"
67
+ - "./lib/trashed/instruments/ruby_gc.rb"
68
+ - "./lib/trashed/instruments/ruby_gc_profiler.rb"
69
+ - "./lib/trashed/instruments/stopwatch.rb"
70
+ - "./lib/trashed/meter.rb"
71
+ - "./lib/trashed/rack.rb"
72
+ - "./lib/trashed/railtie.rb"
73
+ - "./lib/trashed/reporter.rb"
74
+ - "./lib/trashed/resource_usage.rb"
75
75
  homepage: https://github.com/basecamp/trashed
76
- licenses: []
76
+ licenses:
77
+ - MIT
77
78
  metadata: {}
78
79
  post_install_message:
79
80
  rdoc_options: []
@@ -81,18 +82,18 @@ require_paths:
81
82
  - lib
82
83
  required_ruby_version: !ruby/object:Gem::Requirement
83
84
  requirements:
84
- - - ! '>='
85
+ - - ">="
85
86
  - !ruby/object:Gem::Version
86
87
  version: '0'
87
88
  required_rubygems_version: !ruby/object:Gem::Requirement
88
89
  requirements:
89
- - - ! '>='
90
+ - - ">="
90
91
  - !ruby/object:Gem::Version
91
92
  version: '0'
92
93
  requirements: []
93
94
  rubyforge_project:
94
- rubygems_version: 2.2.2
95
+ rubygems_version: 2.2.0
95
96
  signing_key:
96
97
  specification_version: 4
97
- summary: Report per-request object allocations, GC time, and more to StatsD
98
+ summary: Rack request GC stats => logs + StatsD
98
99
  test_files: []
@@ -1,16 +0,0 @@
1
- module Trashed
2
- module Instruments
3
- class Ruby19GC
4
- def start(state)
5
- gc = GC.stat
6
- state[:'GC.count'] = gc[:count]
7
- end
8
-
9
- def measure(state, timings, gauges)
10
- gc = GC.stat
11
- timings[:'GC.count'] = gc[:count] - state.delete(:'GC.count')
12
- gauges.concat gc.map { |k, v| [ :"GC.#{k}", v ] }
13
- end
14
- end
15
- end
16
- end
@@ -1,24 +0,0 @@
1
- module Trashed
2
- module Instruments
3
- class Ruby20GC
4
- def start(state)
5
- gc = GC.stat
6
- state.update \
7
- :'GC.count' => gc[:count],
8
- :'GC.allocated_objects' => gc[:total_allocated_object],
9
- :'GC.freed_objects' => gc[:total_freed_object]
10
- end
11
-
12
- def measure(state, timings, gauges)
13
- gc = GC.stat
14
-
15
- timings.update \
16
- :'GC.count' => gc[:count] - state.delete(:'GC.count'),
17
- :'GC.allocated_objects' => gc[:total_allocated_object] - state.delete(:'GC.allocated_objects'),
18
- :'GC.freed_objects' => gc[:total_freed_object] - state.delete(:'GC.freed_objects')
19
-
20
- gauges.concat gc.map { |k, v| [ :"GC.#{k}", v ] }
21
- end
22
- end
23
- end
24
- end
@@ -1,28 +0,0 @@
1
- module Trashed
2
- module Instruments
3
- class Ruby21GC
4
- def start(state)
5
- gc = GC.stat
6
- state.update \
7
- :'GC.count' => gc[:count],
8
- :'GC.major_count' => gc[:major_gc_count],
9
- :'GC.minor_count' => gc[:minor_gc_count],
10
- :'GC.allocated_objects' => gc[:total_allocated_object],
11
- :'GC.freed_objects' => gc[:total_freed_object]
12
- end
13
-
14
- def measure(state, timings, gauges)
15
- gc = GC.stat
16
-
17
- timings.update \
18
- :'GC.count' => gc[:count] - state.delete(:'GC.count'),
19
- :'GC.major_count' => gc[:major_gc_count] - state.delete(:'GC.major_count'),
20
- :'GC.minor_count' => gc[:minor_gc_count] - state.delete(:'GC.minor_count'),
21
- :'GC.allocated_objects' => gc[:total_allocated_object] - state.delete(:'GC.allocated_objects'),
22
- :'GC.freed_objects' => gc[:total_freed_object] - state.delete(:'GC.freed_objects')
23
-
24
- gauges.concat gc.map { |k, v| [ :"GC.#{k}", v ] }
25
- end
26
- end
27
- end
28
- end