trashed 3.0.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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