appsignal 3.1.3-java → 3.1.5-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/ext/agent.yml +27 -27
- data/gemfiles/padrino.gemfile +2 -1
- data/gemfiles/que.gemfile +1 -1
- data/lib/appsignal/config.rb +0 -3
- data/lib/appsignal/garbage_collection.rb +90 -0
- data/lib/appsignal/probes/mri.rb +5 -3
- data/lib/appsignal/transaction.rb +5 -12
- data/lib/appsignal/version.rb +1 -1
- data/lib/appsignal.rb +1 -6
- data/lib/puma/plugin/appsignal.rb +17 -5
- data/spec/lib/appsignal/config_spec.rb +0 -1
- data/spec/lib/appsignal/{garbage_collection_profiler_spec.rb → garbage_collection_spec.rb} +28 -6
- data/spec/lib/appsignal/probes/mri_spec.rb +35 -0
- data/spec/lib/appsignal/transaction_spec.rb +30 -62
- data/spec/lib/appsignal_spec.rb +3 -23
- data/spec/lib/puma/appsignal_spec.rb +45 -6
- metadata +6 -6
- data/lib/appsignal/garbage_collection_profiler.rb +0 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 20749c0f3826167272d727c37dd1e881502a9654f8b499d4f3fcdf40dd386178
|
4
|
+
data.tar.gz: 90ddbe20ee5243338aefdfbefbb1989bbe1df5cc57a56a4f277bcfa789086b41
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c9f2133fe6f929ebbbee6b9c8f47f85bd3c895b518daf6a69b5ad8a2e9a29ebcd72ca1c25a7aef8512a8493f8941ab711fda9b0f16fff30cac73c40701376a47
|
7
|
+
data.tar.gz: bfe585b6c6ce5f8cc5e470c9a164e6690fc1834e40bb4bd27491f719cbdaf6ebd4b92d1f1ba2bc3001e157f5c35786f12143b29d9908e9ce73d9ca9712c7c5ef
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,33 @@
|
|
1
1
|
# AppSignal for Ruby gem Changelog
|
2
2
|
|
3
|
+
## 3.1.5
|
4
|
+
|
5
|
+
### Changed
|
6
|
+
|
7
|
+
- [4035c3c2](https://github.com/appsignal/appsignal-ruby/commit/4035c3c2d5c0b002119054014daddd193bd820f0) patch - Bump agent to version 813a59b
|
8
|
+
|
9
|
+
- Fix http proxy config option parsing for port 80.
|
10
|
+
- Fix the return value for appsignal_import_opentelemetry_span extension
|
11
|
+
function in `appsignal.h`.
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
|
15
|
+
- [feb60fb8](https://github.com/appsignal/appsignal-ruby/commit/feb60fb877a2b264e587fe3d5d546e40d86c9c38) patch - Fix NoMethodError for AppSignal Puma plugin for Puma 6. Puma 5 is also still supported.
|
16
|
+
|
17
|
+
## 3.1.4
|
18
|
+
|
19
|
+
### Added
|
20
|
+
|
21
|
+
- [ffe49cfe](https://github.com/appsignal/appsignal-ruby/commit/ffe49cfe94f5269e59d6f168a73114f7a3914f79) patch - Support temporarily disabling GC profiling without reporting inaccurate `gc_time` metric durations. The MRI probe's `gc_time` will not report any value when the `GC::Profiler.enabled?` returns `false`.
|
22
|
+
|
23
|
+
### Changed
|
24
|
+
|
25
|
+
- [af7e666c](https://github.com/appsignal/appsignal-ruby/commit/af7e666cf173ec1f42e9cf3fce2ab6c8e658440c) patch - Listen if the Ruby Garbage Collection profiler is enabled and collect how long the GC is running for the Ruby VM magic dashboard. An app will need to call `GC::Profiler.enable` to enable the GC profiler. Do not enable this in production environments, or at least not for long, because this can negatively impact performance of apps.
|
26
|
+
|
27
|
+
### Fixed
|
28
|
+
|
29
|
+
- [b3a163be](https://github.com/appsignal/appsignal-ruby/commit/b3a163be154796e1f358c5061eaee99845c960ee) patch - Fix the MRI probe using the Garbage Collection profiler instead of the NilProfiler when garbage collection instrumentation is not enabled for MRI probe. This caused unnecessary overhead.
|
30
|
+
|
3
31
|
## 3.1.3
|
4
32
|
|
5
33
|
### Added
|
data/ext/agent.yml
CHANGED
@@ -3,99 +3,99 @@
|
|
3
3
|
# appsignal-agent repository.
|
4
4
|
# Modifications to this file will be overwritten with the next agent release.
|
5
5
|
---
|
6
|
-
version:
|
6
|
+
version: 813a59b
|
7
7
|
mirrors:
|
8
8
|
- https://appsignal-agent-releases.global.ssl.fastly.net
|
9
9
|
- https://d135dj0rjqvssy.cloudfront.net
|
10
10
|
triples:
|
11
11
|
x86_64-darwin:
|
12
12
|
static:
|
13
|
-
checksum:
|
13
|
+
checksum: c8919a19a28950f726221829ea4d2b3312f1595a5e28ea134f8c41ed0814d7cd
|
14
14
|
filename: appsignal-x86_64-darwin-all-static.tar.gz
|
15
15
|
dynamic:
|
16
|
-
checksum:
|
16
|
+
checksum: 1c10139bc4df56048a71766b64eba76462ee38d3cc814dc230e0fdb3c7e4fba3
|
17
17
|
filename: appsignal-x86_64-darwin-all-dynamic.tar.gz
|
18
18
|
universal-darwin:
|
19
19
|
static:
|
20
|
-
checksum:
|
20
|
+
checksum: c8919a19a28950f726221829ea4d2b3312f1595a5e28ea134f8c41ed0814d7cd
|
21
21
|
filename: appsignal-x86_64-darwin-all-static.tar.gz
|
22
22
|
dynamic:
|
23
|
-
checksum:
|
23
|
+
checksum: 1c10139bc4df56048a71766b64eba76462ee38d3cc814dc230e0fdb3c7e4fba3
|
24
24
|
filename: appsignal-x86_64-darwin-all-dynamic.tar.gz
|
25
25
|
aarch64-darwin:
|
26
26
|
static:
|
27
|
-
checksum:
|
27
|
+
checksum: 40a38896132f418362af9fb2e9796eb4479e13cc0691b61f0f0b81b77e66ded6
|
28
28
|
filename: appsignal-aarch64-darwin-all-static.tar.gz
|
29
29
|
dynamic:
|
30
|
-
checksum:
|
30
|
+
checksum: 40ec0c7db246cfc9b8eeebc882b07ba625948f376a53d8e24add7148d0f8c067
|
31
31
|
filename: appsignal-aarch64-darwin-all-dynamic.tar.gz
|
32
32
|
arm64-darwin:
|
33
33
|
static:
|
34
|
-
checksum:
|
34
|
+
checksum: 40a38896132f418362af9fb2e9796eb4479e13cc0691b61f0f0b81b77e66ded6
|
35
35
|
filename: appsignal-aarch64-darwin-all-static.tar.gz
|
36
36
|
dynamic:
|
37
|
-
checksum:
|
37
|
+
checksum: 40ec0c7db246cfc9b8eeebc882b07ba625948f376a53d8e24add7148d0f8c067
|
38
38
|
filename: appsignal-aarch64-darwin-all-dynamic.tar.gz
|
39
39
|
arm-darwin:
|
40
40
|
static:
|
41
|
-
checksum:
|
41
|
+
checksum: 40a38896132f418362af9fb2e9796eb4479e13cc0691b61f0f0b81b77e66ded6
|
42
42
|
filename: appsignal-aarch64-darwin-all-static.tar.gz
|
43
43
|
dynamic:
|
44
|
-
checksum:
|
44
|
+
checksum: 40ec0c7db246cfc9b8eeebc882b07ba625948f376a53d8e24add7148d0f8c067
|
45
45
|
filename: appsignal-aarch64-darwin-all-dynamic.tar.gz
|
46
46
|
aarch64-linux:
|
47
47
|
static:
|
48
|
-
checksum:
|
48
|
+
checksum: c73b6e9de849a40290a0d90eaad43ea41a9a0293ba4b8bf99f69965c45c85514
|
49
49
|
filename: appsignal-aarch64-linux-all-static.tar.gz
|
50
50
|
dynamic:
|
51
|
-
checksum:
|
51
|
+
checksum: 90226eefe2e2f66833ca3e31c69ce70763ed57916bd0b5c1809bd99d61ff3429
|
52
52
|
filename: appsignal-aarch64-linux-all-dynamic.tar.gz
|
53
53
|
i686-linux:
|
54
54
|
static:
|
55
|
-
checksum:
|
55
|
+
checksum: 6741b9a068dc405b3d6d07953fab7fc876c21b4add1cbb2b4c4c4dfdeca5d387
|
56
56
|
filename: appsignal-i686-linux-all-static.tar.gz
|
57
57
|
dynamic:
|
58
|
-
checksum:
|
58
|
+
checksum: 1af902b37af378a06251365fb637f86298380d3627c54f2945a85c1b7f075fda
|
59
59
|
filename: appsignal-i686-linux-all-dynamic.tar.gz
|
60
60
|
x86-linux:
|
61
61
|
static:
|
62
|
-
checksum:
|
62
|
+
checksum: 6741b9a068dc405b3d6d07953fab7fc876c21b4add1cbb2b4c4c4dfdeca5d387
|
63
63
|
filename: appsignal-i686-linux-all-static.tar.gz
|
64
64
|
dynamic:
|
65
|
-
checksum:
|
65
|
+
checksum: 1af902b37af378a06251365fb637f86298380d3627c54f2945a85c1b7f075fda
|
66
66
|
filename: appsignal-i686-linux-all-dynamic.tar.gz
|
67
67
|
x86_64-linux:
|
68
68
|
static:
|
69
|
-
checksum:
|
69
|
+
checksum: 8355b017093db606014023cc617d84d6375d503d7ffa54f62c7b3dc56fb64ead
|
70
70
|
filename: appsignal-x86_64-linux-all-static.tar.gz
|
71
71
|
dynamic:
|
72
|
-
checksum:
|
72
|
+
checksum: 7c239a7ffe18cb173120bd67fb96563f4a81f0744bbbb47082f077a38ccbe5f1
|
73
73
|
filename: appsignal-x86_64-linux-all-dynamic.tar.gz
|
74
74
|
x86_64-linux-musl:
|
75
75
|
static:
|
76
|
-
checksum:
|
76
|
+
checksum: e9d98ed23b872dbf1e67a081473918cf88c4af775b1caadbfd93deda2635d9f8
|
77
77
|
filename: appsignal-x86_64-linux-musl-all-static.tar.gz
|
78
78
|
dynamic:
|
79
|
-
checksum:
|
79
|
+
checksum: 5538172a95dfca1a4cf8e111ba61eab5e9c16314fa902259711cb9e8e0d2f85e
|
80
80
|
filename: appsignal-x86_64-linux-musl-all-dynamic.tar.gz
|
81
81
|
aarch64-linux-musl:
|
82
82
|
static:
|
83
|
-
checksum:
|
83
|
+
checksum: 0fb3eacfb8c8bc01c4acc8916327626720de376bcdd95104be71bb11a4ff9215
|
84
84
|
filename: appsignal-aarch64-linux-musl-all-static.tar.gz
|
85
85
|
dynamic:
|
86
|
-
checksum:
|
86
|
+
checksum: b87bcedaa2aa886acf3a93ce6e32762a843f3bee1ca7a8f9e0d17ca32f7a7d39
|
87
87
|
filename: appsignal-aarch64-linux-musl-all-dynamic.tar.gz
|
88
88
|
x86_64-freebsd:
|
89
89
|
static:
|
90
|
-
checksum:
|
90
|
+
checksum: b7d3c244b7068213840f5970df2e318d98f7909eb3b2b4ab42441d064ffb19ee
|
91
91
|
filename: appsignal-x86_64-freebsd-all-static.tar.gz
|
92
92
|
dynamic:
|
93
|
-
checksum:
|
93
|
+
checksum: 0a739134f11d50318d14f247df1f8cc0f8aec1fbcb70a8bc48d5e1f22dc4aaba
|
94
94
|
filename: appsignal-x86_64-freebsd-all-dynamic.tar.gz
|
95
95
|
amd64-freebsd:
|
96
96
|
static:
|
97
|
-
checksum:
|
97
|
+
checksum: b7d3c244b7068213840f5970df2e318d98f7909eb3b2b4ab42441d064ffb19ee
|
98
98
|
filename: appsignal-x86_64-freebsd-all-static.tar.gz
|
99
99
|
dynamic:
|
100
|
-
checksum:
|
100
|
+
checksum: 0a739134f11d50318d14f247df1f8cc0f8aec1fbcb70a8bc48d5e1f22dc4aaba
|
101
101
|
filename: appsignal-x86_64-freebsd-all-dynamic.tar.gz
|
data/gemfiles/padrino.gemfile
CHANGED
data/gemfiles/que.gemfile
CHANGED
data/lib/appsignal/config.rb
CHANGED
@@ -15,7 +15,6 @@ module Appsignal
|
|
15
15
|
:debug => false,
|
16
16
|
:dns_servers => [],
|
17
17
|
:enable_allocation_tracking => true,
|
18
|
-
:enable_gc_instrumentation => false,
|
19
18
|
:enable_host_metrics => true,
|
20
19
|
:enable_minutely_probes => true,
|
21
20
|
:enable_statsd => true,
|
@@ -63,7 +62,6 @@ module Appsignal
|
|
63
62
|
"APPSIGNAL_DEBUG" => :debug,
|
64
63
|
"APPSIGNAL_DNS_SERVERS" => :dns_servers,
|
65
64
|
"APPSIGNAL_ENABLE_ALLOCATION_TRACKING" => :enable_allocation_tracking,
|
66
|
-
"APPSIGNAL_ENABLE_GC_INSTRUMENTATION" => :enable_gc_instrumentation,
|
67
65
|
"APPSIGNAL_ENABLE_HOST_METRICS" => :enable_host_metrics,
|
68
66
|
"APPSIGNAL_ENABLE_MINUTELY_PROBES" => :enable_minutely_probes,
|
69
67
|
"APPSIGNAL_ENABLE_STATSD" => :enable_statsd,
|
@@ -114,7 +112,6 @@ module Appsignal
|
|
114
112
|
APPSIGNAL_ACTIVE
|
115
113
|
APPSIGNAL_DEBUG
|
116
114
|
APPSIGNAL_ENABLE_ALLOCATION_TRACKING
|
117
|
-
APPSIGNAL_ENABLE_GC_INSTRUMENTATION
|
118
115
|
APPSIGNAL_ENABLE_HOST_METRICS
|
119
116
|
APPSIGNAL_ENABLE_MINUTELY_PROBES
|
120
117
|
APPSIGNAL_ENABLE_STATSD
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Appsignal
|
4
|
+
# @api private
|
5
|
+
module GarbageCollection
|
6
|
+
# Return the GC profiler wrapper.
|
7
|
+
#
|
8
|
+
# Returns {Profiler} if the Ruby Garbage Collection profiler is enabled.
|
9
|
+
# This is checked by calling `GC::Profiler.enabled?`.
|
10
|
+
#
|
11
|
+
# GC profiling is disabled by default due to the overhead it causes. Do not
|
12
|
+
# enable this in production for long periods of time.
|
13
|
+
def self.profiler
|
14
|
+
# Cached instances so it doesn't create a new object every time this
|
15
|
+
# method is called. Especially necessary for the {Profiler} because a new
|
16
|
+
# instance will have a new internal time counter.
|
17
|
+
@real_profiler ||= Profiler.new
|
18
|
+
@nil_profiler ||= NilProfiler.new
|
19
|
+
|
20
|
+
enabled? ? @real_profiler : @nil_profiler
|
21
|
+
end
|
22
|
+
|
23
|
+
# Check if Garbage Collection is enabled at the moment.
|
24
|
+
#
|
25
|
+
# @return [Boolean]
|
26
|
+
def self.enabled?
|
27
|
+
GC::Profiler.enabled?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Unset the currently cached profilers.
|
31
|
+
#
|
32
|
+
# @return [void]
|
33
|
+
def self.clear_profiler!
|
34
|
+
@real_profiler = nil
|
35
|
+
@nil_profiler = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
# A wrapper around Ruby's `GC::Profiler` that tracks garbage collection
|
39
|
+
# time, while clearing `GC::Profiler`'s total_time to make sure it doesn't
|
40
|
+
# leak memory by keeping garbage collection run samples in memory.
|
41
|
+
class Profiler
|
42
|
+
def self.lock
|
43
|
+
@lock ||= Mutex.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
@total_time = 0
|
48
|
+
end
|
49
|
+
|
50
|
+
# Whenever {#total_time} is called, the current `GC::Profiler#total_time`
|
51
|
+
# gets added to `@total_time`, after which `GC::Profiler.clear` is called
|
52
|
+
# to prevent it from leaking memory. A class-level lock is used to make
|
53
|
+
# sure garbage collection time is never counted more than once.
|
54
|
+
#
|
55
|
+
# Whenever `@total_time` gets above two billion milliseconds (about 23
|
56
|
+
# days), it's reset to make sure the result fits in a signed 32-bit
|
57
|
+
# integer.
|
58
|
+
#
|
59
|
+
# @return [Integer]
|
60
|
+
def total_time
|
61
|
+
lock.synchronize do
|
62
|
+
@total_time += (internal_profiler.total_time * 1000).round
|
63
|
+
internal_profiler.clear
|
64
|
+
end
|
65
|
+
|
66
|
+
@total_time = 0 if @total_time > 2_000_000_000
|
67
|
+
|
68
|
+
@total_time
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def internal_profiler
|
74
|
+
GC::Profiler
|
75
|
+
end
|
76
|
+
|
77
|
+
def lock
|
78
|
+
self.class.lock
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# A dummy profiler that always returns 0 as the total time. Used when GC
|
83
|
+
# profiler is disabled.
|
84
|
+
class NilProfiler
|
85
|
+
def total_time
|
86
|
+
0
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/appsignal/probes/mri.rb
CHANGED
@@ -8,7 +8,7 @@ module Appsignal
|
|
8
8
|
defined?(::RubyVM) && ::RubyVM.respond_to?(:stat)
|
9
9
|
end
|
10
10
|
|
11
|
-
def initialize(appsignal: Appsignal, gc_profiler: Appsignal::
|
11
|
+
def initialize(appsignal: Appsignal, gc_profiler: Appsignal::GarbageCollection.profiler)
|
12
12
|
Appsignal.logger.debug("Initializing VM probe")
|
13
13
|
@appsignal = appsignal
|
14
14
|
@gc_profiler = gc_profiler
|
@@ -31,8 +31,10 @@ module Appsignal
|
|
31
31
|
)
|
32
32
|
|
33
33
|
set_gauge("thread_count", Thread.list.size)
|
34
|
-
|
35
|
-
|
34
|
+
if Appsignal::GarbageCollection.enabled?
|
35
|
+
gauge_delta(:gc_time, @gc_profiler.total_time) do |gc_time|
|
36
|
+
set_gauge("gc_time", gc_time) if gc_time > 0
|
37
|
+
end
|
36
38
|
end
|
37
39
|
|
38
40
|
gc_stats = GC.stat
|
@@ -66,11 +66,6 @@ module Appsignal
|
|
66
66
|
def clear_current_transaction!
|
67
67
|
Thread.current[:appsignal_transaction] = nil
|
68
68
|
end
|
69
|
-
|
70
|
-
def garbage_collection_profiler
|
71
|
-
@garbage_collection_profiler ||=
|
72
|
-
Appsignal.config[:enable_gc_instrumentation] ? Appsignal::GarbageCollectionProfiler.new : NilGarbageCollectionProfiler.new
|
73
|
-
end
|
74
69
|
end
|
75
70
|
|
76
71
|
attr_reader :ext, :transaction_id, :action, :namespace, :request, :paused, :tags, :options, :discarded, :breadcrumbs
|
@@ -103,7 +98,7 @@ module Appsignal
|
|
103
98
|
@ext = Appsignal::Extension.start_transaction(
|
104
99
|
@transaction_id,
|
105
100
|
@namespace,
|
106
|
-
|
101
|
+
0
|
107
102
|
) || Appsignal::Extension::MockTransaction.new
|
108
103
|
end
|
109
104
|
|
@@ -117,9 +112,7 @@ module Appsignal
|
|
117
112
|
"because it was manually discarded."
|
118
113
|
return
|
119
114
|
end
|
120
|
-
if @ext.finish(
|
121
|
-
sample_data
|
122
|
-
end
|
115
|
+
sample_data if @ext.finish(0)
|
123
116
|
@ext.complete
|
124
117
|
end
|
125
118
|
|
@@ -355,7 +348,7 @@ module Appsignal
|
|
355
348
|
|
356
349
|
def start_event
|
357
350
|
return if paused?
|
358
|
-
@ext.start_event(
|
351
|
+
@ext.start_event(0)
|
359
352
|
end
|
360
353
|
|
361
354
|
def finish_event(name, title, body, body_format = Appsignal::EventFormatter::DEFAULT)
|
@@ -365,7 +358,7 @@ module Appsignal
|
|
365
358
|
title || BLANK,
|
366
359
|
body || BLANK,
|
367
360
|
body_format || Appsignal::EventFormatter::DEFAULT,
|
368
|
-
|
361
|
+
0
|
369
362
|
)
|
370
363
|
end
|
371
364
|
|
@@ -377,7 +370,7 @@ module Appsignal
|
|
377
370
|
body || BLANK,
|
378
371
|
body_format || Appsignal::EventFormatter::DEFAULT,
|
379
372
|
duration,
|
380
|
-
|
373
|
+
0
|
381
374
|
)
|
382
375
|
end
|
383
376
|
|
data/lib/appsignal/version.rb
CHANGED
data/lib/appsignal.rb
CHANGED
@@ -115,11 +115,6 @@ module Appsignal
|
|
115
115
|
Appsignal::Environment.report_enabled("allocation_tracking")
|
116
116
|
end
|
117
117
|
|
118
|
-
if config[:enable_gc_instrumentation]
|
119
|
-
GC::Profiler.enable
|
120
|
-
Appsignal::Environment.report_enabled("gc_instrumentation")
|
121
|
-
end
|
122
|
-
|
123
118
|
Appsignal::Minutely.start if config[:enable_minutely_probes]
|
124
119
|
|
125
120
|
collect_environment_metadata
|
@@ -298,7 +293,7 @@ require "appsignal/hooks"
|
|
298
293
|
require "appsignal/probes"
|
299
294
|
require "appsignal/marker"
|
300
295
|
require "appsignal/minutely"
|
301
|
-
require "appsignal/
|
296
|
+
require "appsignal/garbage_collection"
|
302
297
|
require "appsignal/integrations/railtie" if defined?(::Rails)
|
303
298
|
require "appsignal/transaction"
|
304
299
|
require "appsignal/version"
|
@@ -11,9 +11,9 @@ require "json"
|
|
11
11
|
Puma::Plugin.create do # rubocop:disable Metrics/BlockLength
|
12
12
|
def start(launcher)
|
13
13
|
@launcher = launcher
|
14
|
-
|
14
|
+
log_debug "AppSignal: Puma plugin start."
|
15
15
|
in_background do
|
16
|
-
|
16
|
+
log_debug "AppSignal: Start Puma stats collection loop."
|
17
17
|
plugin = AppsignalPumaPlugin.new
|
18
18
|
|
19
19
|
loop do
|
@@ -24,12 +24,12 @@ Puma::Plugin.create do # rubocop:disable Metrics/BlockLength
|
|
24
24
|
# metrics.
|
25
25
|
sleep sleep_time
|
26
26
|
|
27
|
-
|
27
|
+
log_debug "AppSignal: Collecting Puma stats."
|
28
28
|
stats = fetch_puma_stats
|
29
29
|
if stats
|
30
30
|
plugin.call(stats)
|
31
31
|
else
|
32
|
-
|
32
|
+
log_debug "AppSignal: No Puma stats to report."
|
33
33
|
end
|
34
34
|
rescue StandardError => error
|
35
35
|
log_error "Error while processing metrics.", error
|
@@ -44,8 +44,20 @@ Puma::Plugin.create do # rubocop:disable Metrics/BlockLength
|
|
44
44
|
60 # seconds
|
45
45
|
end
|
46
46
|
|
47
|
+
def logger
|
48
|
+
if @launcher.respond_to? :log_writer
|
49
|
+
@launcher.log_writer
|
50
|
+
else
|
51
|
+
@launcher.events
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def log_debug(message)
|
56
|
+
logger.debug message
|
57
|
+
end
|
58
|
+
|
47
59
|
def log_error(message, error)
|
48
|
-
|
60
|
+
logger.error "AppSignal: #{message}\n" \
|
49
61
|
"#{error.class}: #{error.message}\n#{error.backtrace.join("\n")}"
|
50
62
|
end
|
51
63
|
|
@@ -156,7 +156,6 @@ describe Appsignal::Config do
|
|
156
156
|
:debug => false,
|
157
157
|
:dns_servers => [],
|
158
158
|
:enable_allocation_tracking => true,
|
159
|
-
:enable_gc_instrumentation => false,
|
160
159
|
:enable_host_metrics => true,
|
161
160
|
:enable_minutely_probes => true,
|
162
161
|
:enable_statsd => true,
|
@@ -1,11 +1,33 @@
|
|
1
|
-
describe Appsignal::
|
1
|
+
describe Appsignal::GarbageCollection do
|
2
|
+
describe ".profiler" do
|
3
|
+
before do
|
4
|
+
# Unset the internal memoized variable to avoid state leaking
|
5
|
+
described_class.clear_profiler!
|
6
|
+
end
|
7
|
+
|
8
|
+
context "when GC instrumentation is disabled" do
|
9
|
+
it "returns the NilProfiler" do
|
10
|
+
expect(described_class.profiler).to be_a(Appsignal::GarbageCollection::NilProfiler)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "when GC profiling is enabled" do
|
15
|
+
before { GC::Profiler.enable }
|
16
|
+
after { GC::Profiler.disable }
|
17
|
+
|
18
|
+
it "returns the Profiler" do
|
19
|
+
expect(described_class.profiler).to be_a(Appsignal::GarbageCollection::Profiler)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe Appsignal::GarbageCollection::Profiler do
|
2
26
|
let(:internal_profiler) { FakeGCProfiler.new }
|
3
27
|
let(:profiler) { described_class.new }
|
4
28
|
|
5
29
|
before do
|
6
|
-
|
7
|
-
.to receive(:internal_profiler)
|
8
|
-
.and_return(internal_profiler)
|
30
|
+
stub_const("GC::Profiler", internal_profiler)
|
9
31
|
end
|
10
32
|
|
11
33
|
context "on initialization" do
|
@@ -54,7 +76,7 @@ describe Appsignal::GarbageCollectionProfiler do
|
|
54
76
|
|
55
77
|
2.times do
|
56
78
|
threads << Thread.new do
|
57
|
-
profiler = Appsignal::
|
79
|
+
profiler = Appsignal::GarbageCollection::Profiler.new
|
58
80
|
results << profiler.total_time
|
59
81
|
end
|
60
82
|
end
|
@@ -65,7 +87,7 @@ describe Appsignal::GarbageCollectionProfiler do
|
|
65
87
|
end
|
66
88
|
end
|
67
89
|
|
68
|
-
describe Appsignal::
|
90
|
+
describe Appsignal::GarbageCollection::NilProfiler do
|
69
91
|
let(:profiler) { described_class.new }
|
70
92
|
|
71
93
|
describe "#total_time" do
|
@@ -39,6 +39,7 @@ describe Appsignal::Probes::MriProbe do
|
|
39
39
|
let(:hostname) { nil }
|
40
40
|
before do
|
41
41
|
allow(gc_profiler_mock).to receive(:total_time)
|
42
|
+
allow(GC::Profiler).to receive(:enabled?).and_return(true)
|
42
43
|
end
|
43
44
|
|
44
45
|
it "should track vm metrics" do
|
@@ -70,6 +71,40 @@ describe Appsignal::Probes::MriProbe do
|
|
70
71
|
end
|
71
72
|
end
|
72
73
|
|
74
|
+
context "when GC profiling is disabled" do
|
75
|
+
it "does not report a gc_time metric" do
|
76
|
+
allow(GC::Profiler).to receive(:enabled?).and_return(false)
|
77
|
+
expect(gc_profiler_mock).to_not receive(:total_time)
|
78
|
+
probe.call # Normal call, create a cache
|
79
|
+
probe.call # Report delta value based on cached value
|
80
|
+
metrics = appsignal_mock.gauges.map { |(key)| key }
|
81
|
+
expect(metrics).to_not include("gc_time")
|
82
|
+
end
|
83
|
+
|
84
|
+
it "does not report a gc_time metric while temporarily disabled" do
|
85
|
+
# While enabled
|
86
|
+
allow(GC::Profiler).to receive(:enabled?).and_return(true)
|
87
|
+
expect(gc_profiler_mock).to receive(:total_time).and_return(10, 15)
|
88
|
+
probe.call # Normal call, create a cache
|
89
|
+
probe.call # Report delta value based on cached value
|
90
|
+
expect_gauges([["gc_time", 5]])
|
91
|
+
|
92
|
+
# While disabled
|
93
|
+
allow(GC::Profiler).to receive(:enabled?).and_return(false)
|
94
|
+
probe.call # Call twice to make sure any caches resets wouldn't mess up the assertion
|
95
|
+
probe.call
|
96
|
+
# Does not include any newly reported metrics
|
97
|
+
expect_gauges([["gc_time", 5]])
|
98
|
+
|
99
|
+
# When enabled after being disabled for a while, it only reports the
|
100
|
+
# newly reported time since it was renabled
|
101
|
+
allow(GC::Profiler).to receive(:enabled?).and_return(true)
|
102
|
+
expect(gc_profiler_mock).to receive(:total_time).and_return(25)
|
103
|
+
probe.call
|
104
|
+
expect_gauges([["gc_time", 5], ["gc_time", 10]])
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
73
108
|
it "tracks GC run count" do
|
74
109
|
expect(GC).to receive(:count).and_return(10, 15)
|
75
110
|
expect(GC).to receive(:stat).and_return(
|
@@ -683,43 +683,36 @@ describe Appsignal::Transaction do
|
|
683
683
|
end
|
684
684
|
|
685
685
|
describe "#sample_data" do
|
686
|
-
|
687
|
-
expect(transaction.ext).to receive(:set_sample_data).with(
|
688
|
-
"environment",
|
689
|
-
Appsignal::Utils::Data.generate(
|
690
|
-
"CONTENT_LENGTH" => "0",
|
691
|
-
"REQUEST_METHOD" => "GET",
|
692
|
-
"SERVER_NAME" => "example.org",
|
693
|
-
"SERVER_PORT" => "80",
|
694
|
-
"PATH_INFO" => "/blog"
|
695
|
-
)
|
696
|
-
).once
|
697
|
-
expect(transaction.ext).to receive(:set_sample_data).with(
|
698
|
-
"session_data",
|
699
|
-
Appsignal::Utils::Data.generate({})
|
700
|
-
).once
|
701
|
-
expect(transaction.ext).to receive(:set_sample_data).with(
|
702
|
-
"params",
|
703
|
-
Appsignal::Utils::Data.generate(
|
704
|
-
"controller" => "blog_posts",
|
705
|
-
"action" => "show",
|
706
|
-
"id" => "1"
|
707
|
-
)
|
708
|
-
).once
|
709
|
-
expect(transaction.ext).to receive(:set_sample_data).with(
|
710
|
-
"metadata",
|
711
|
-
Appsignal::Utils::Data.generate("key" => "value")
|
712
|
-
).once
|
713
|
-
expect(transaction.ext).to receive(:set_sample_data).with(
|
714
|
-
"tags",
|
715
|
-
Appsignal::Utils::Data.generate({})
|
716
|
-
).once
|
717
|
-
expect(transaction.ext).to receive(:set_sample_data).with(
|
718
|
-
"breadcrumbs",
|
719
|
-
Appsignal::Utils::Data.generate([])
|
720
|
-
).once
|
686
|
+
let(:env) { { "rack.session" => { "session" => "value" } } }
|
721
687
|
|
688
|
+
it "sets sample data" do
|
689
|
+
transaction.set_tags "tag" => "value"
|
690
|
+
transaction.add_breadcrumb "category", "action", "message", "key" => "value"
|
722
691
|
transaction.sample_data
|
692
|
+
|
693
|
+
sample_data = transaction.to_h["sample_data"]
|
694
|
+
expect(sample_data["environment"]).to include(
|
695
|
+
"CONTENT_LENGTH" => "0",
|
696
|
+
"REQUEST_METHOD" => "GET",
|
697
|
+
"SERVER_NAME" => "example.org",
|
698
|
+
"SERVER_PORT" => "80",
|
699
|
+
"PATH_INFO" => "/blog"
|
700
|
+
)
|
701
|
+
expect(sample_data["session_data"]).to eq("session" => "value")
|
702
|
+
expect(sample_data["params"]).to eq(
|
703
|
+
"controller" => "blog_posts",
|
704
|
+
"action" => "show",
|
705
|
+
"id" => "1"
|
706
|
+
)
|
707
|
+
expect(sample_data["metadata"]).to eq("key" => "value")
|
708
|
+
expect(sample_data["tags"]).to eq("tag" => "value")
|
709
|
+
expect(sample_data["breadcrumbs"]).to contain_exactly(
|
710
|
+
"action" => "action",
|
711
|
+
"category" => "category",
|
712
|
+
"message" => "message",
|
713
|
+
"metadata" => { "key" => "value" },
|
714
|
+
"time" => kind_of(Integer)
|
715
|
+
)
|
723
716
|
end
|
724
717
|
end
|
725
718
|
|
@@ -792,23 +785,6 @@ describe Appsignal::Transaction do
|
|
792
785
|
end
|
793
786
|
end
|
794
787
|
|
795
|
-
describe "#garbage_collection_profiler" do
|
796
|
-
before { Appsignal::Transaction.instance_variable_set(:@garbage_collection_profiler, nil) }
|
797
|
-
|
798
|
-
it "returns the NilGarbageCollectionProfiler" do
|
799
|
-
expect(Appsignal::Transaction.garbage_collection_profiler).to be_a(Appsignal::NilGarbageCollectionProfiler)
|
800
|
-
end
|
801
|
-
|
802
|
-
context "when gc profiling is enabled" do
|
803
|
-
before { Appsignal.config.config_hash[:enable_gc_instrumentation] = true }
|
804
|
-
after { Appsignal.config.config_hash[:enable_gc_instrumentation] = false }
|
805
|
-
|
806
|
-
it "returns the GarbageCollectionProfiler" do
|
807
|
-
expect(Appsignal::Transaction.garbage_collection_profiler).to be_a(Appsignal::GarbageCollectionProfiler)
|
808
|
-
end
|
809
|
-
end
|
810
|
-
end
|
811
|
-
|
812
788
|
describe "#start_event" do
|
813
789
|
it "starts the event in the extension" do
|
814
790
|
expect(transaction.ext).to receive(:start_event).with(0).and_call_original
|
@@ -827,11 +803,7 @@ describe Appsignal::Transaction do
|
|
827
803
|
end
|
828
804
|
|
829
805
|
describe "#finish_event" do
|
830
|
-
let(:fake_gc_time) {
|
831
|
-
before do
|
832
|
-
expect(described_class.garbage_collection_profiler)
|
833
|
-
.to receive(:total_time).at_least(:once).and_return(fake_gc_time)
|
834
|
-
end
|
806
|
+
let(:fake_gc_time) { 0 }
|
835
807
|
|
836
808
|
it "should finish the event in the extension" do
|
837
809
|
expect(transaction.ext).to receive(:finish_event).with(
|
@@ -878,11 +850,7 @@ describe Appsignal::Transaction do
|
|
878
850
|
end
|
879
851
|
|
880
852
|
describe "#record_event" do
|
881
|
-
let(:fake_gc_time) {
|
882
|
-
before do
|
883
|
-
expect(described_class.garbage_collection_profiler)
|
884
|
-
.to receive(:total_time).at_least(:once).and_return(fake_gc_time)
|
885
|
-
end
|
853
|
+
let(:fake_gc_time) { 0 }
|
886
854
|
|
887
855
|
it "should record the event in the extension" do
|
888
856
|
expect(transaction.ext).to receive(:record_event).with(
|
data/spec/lib/appsignal_spec.rb
CHANGED
@@ -54,20 +54,12 @@ describe Appsignal do
|
|
54
54
|
Appsignal.start
|
55
55
|
end
|
56
56
|
|
57
|
-
context "when allocation tracking
|
57
|
+
context "when allocation tracking has been enabled" do
|
58
58
|
before do
|
59
|
-
allow(GC::Profiler).to receive(:enable)
|
60
59
|
Appsignal.config.config_hash[:enable_allocation_tracking] = true
|
61
|
-
Appsignal.config.config_hash[:enable_gc_instrumentation] = true
|
62
60
|
capture_environment_metadata_report_calls
|
63
61
|
end
|
64
62
|
|
65
|
-
it "should enable Ruby's GC::Profiler" do
|
66
|
-
expect(GC::Profiler).to receive(:enable)
|
67
|
-
Appsignal.start
|
68
|
-
expect_environment_metadata("ruby_gc_instrumentation_enabled", "true")
|
69
|
-
end
|
70
|
-
|
71
63
|
unless DependencyHelper.running_jruby?
|
72
64
|
it "installs the allocation event hook" do
|
73
65
|
expect(Appsignal::Extension).to receive(:install_allocation_event_hook)
|
@@ -78,29 +70,17 @@ describe Appsignal do
|
|
78
70
|
end
|
79
71
|
end
|
80
72
|
|
81
|
-
context "when allocation tracking
|
73
|
+
context "when allocation tracking has been disabled" do
|
82
74
|
before do
|
83
75
|
Appsignal.config.config_hash[:enable_allocation_tracking] = false
|
84
|
-
Appsignal.config.config_hash[:enable_gc_instrumentation] = false
|
85
76
|
capture_environment_metadata_report_calls
|
86
77
|
end
|
87
78
|
|
88
|
-
it "should not enable Ruby's GC::Profiler" do
|
89
|
-
expect(GC::Profiler).not_to receive(:enable)
|
90
|
-
Appsignal.start
|
91
|
-
end
|
92
|
-
|
93
79
|
it "should not install the allocation event hook" do
|
94
|
-
expect(Appsignal::
|
80
|
+
expect(Appsignal::Extension).not_to receive(:install_allocation_event_hook)
|
95
81
|
Appsignal.start
|
96
82
|
expect_not_environment_metadata("ruby_allocation_tracking_enabled")
|
97
83
|
end
|
98
|
-
|
99
|
-
it "should not add the gc probe to minutely" do
|
100
|
-
expect(Appsignal::Minutely).not_to receive(:register_garbage_collection_probe)
|
101
|
-
Appsignal.start
|
102
|
-
expect_not_environment_metadata("ruby_gc_instrumentation_enabled")
|
103
|
-
end
|
104
84
|
end
|
105
85
|
|
106
86
|
context "when minutely metrics has been enabled" do
|
@@ -2,14 +2,14 @@ RSpec.describe "Puma plugin" do
|
|
2
2
|
include WaitForHelper
|
3
3
|
|
4
4
|
class MockPumaLauncher
|
5
|
-
def
|
6
|
-
return @
|
5
|
+
def log_writer
|
6
|
+
return @log_writer if defined?(@log_writer)
|
7
7
|
|
8
|
-
@
|
8
|
+
@log_writer = MockPumaLogWriter.new
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
class
|
12
|
+
class MockPumaLogWriter
|
13
13
|
attr_reader :logs
|
14
14
|
|
15
15
|
def initialize
|
@@ -153,7 +153,7 @@ RSpec.describe "Puma plugin" do
|
|
153
153
|
end
|
154
154
|
|
155
155
|
def logs
|
156
|
-
launcher.
|
156
|
+
launcher.log_writer.logs
|
157
157
|
end
|
158
158
|
|
159
159
|
def messages
|
@@ -281,7 +281,7 @@ RSpec.describe "Puma plugin" do
|
|
281
281
|
it "does not fetch metrics" do
|
282
282
|
run_plugin(appsignal_plugin) do
|
283
283
|
expect(logs).to_not include([:error, kind_of(String)])
|
284
|
-
expect(logs).to include([:
|
284
|
+
expect(logs).to include([:debug, "AppSignal: No Puma stats to report."])
|
285
285
|
expect(messages).to be_empty
|
286
286
|
end
|
287
287
|
end
|
@@ -296,4 +296,43 @@ RSpec.describe "Puma plugin" do
|
|
296
296
|
end
|
297
297
|
end
|
298
298
|
end
|
299
|
+
|
300
|
+
context "with Puma < 6 Events class" do
|
301
|
+
class MockPumaEvents
|
302
|
+
attr_reader :logs
|
303
|
+
|
304
|
+
def initialize
|
305
|
+
@logs = []
|
306
|
+
end
|
307
|
+
|
308
|
+
def log(message)
|
309
|
+
@logs << [:log, message]
|
310
|
+
end
|
311
|
+
|
312
|
+
def debug(message)
|
313
|
+
@logs << [:debug, message]
|
314
|
+
end
|
315
|
+
|
316
|
+
def error(message)
|
317
|
+
@logs << [:error, message]
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
let(:launcher) do
|
322
|
+
Class.new do
|
323
|
+
def events
|
324
|
+
return @events if defined?(@events)
|
325
|
+
|
326
|
+
@events = MockPumaEvents.new
|
327
|
+
end
|
328
|
+
end.new
|
329
|
+
end
|
330
|
+
let(:stats_data) { { :max_threads => 5 } }
|
331
|
+
|
332
|
+
it "logs messages to the events class" do
|
333
|
+
run_plugin(appsignal_plugin) do
|
334
|
+
expect(launcher.events.logs).to_not be_empty
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
299
338
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: appsignal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.1.
|
4
|
+
version: 3.1.5
|
5
5
|
platform: java
|
6
6
|
authors:
|
7
7
|
- Robert Beekman
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2022-
|
13
|
+
date: 2022-10-18 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rack
|
@@ -218,7 +218,7 @@ files:
|
|
218
218
|
- lib/appsignal/event_formatter/sequel/sql_formatter.rb
|
219
219
|
- lib/appsignal/extension.rb
|
220
220
|
- lib/appsignal/extension/jruby.rb
|
221
|
-
- lib/appsignal/
|
221
|
+
- lib/appsignal/garbage_collection.rb
|
222
222
|
- lib/appsignal/helpers/instrumentation.rb
|
223
223
|
- lib/appsignal/helpers/metrics.rb
|
224
224
|
- lib/appsignal/hooks.rb
|
@@ -319,7 +319,7 @@ files:
|
|
319
319
|
- spec/lib/appsignal/extension/jruby_spec.rb
|
320
320
|
- spec/lib/appsignal/extension_install_failure_spec.rb
|
321
321
|
- spec/lib/appsignal/extension_spec.rb
|
322
|
-
- spec/lib/appsignal/
|
322
|
+
- spec/lib/appsignal/garbage_collection_spec.rb
|
323
323
|
- spec/lib/appsignal/hooks/action_cable_spec.rb
|
324
324
|
- spec/lib/appsignal/hooks/action_mailer_spec.rb
|
325
325
|
- spec/lib/appsignal/hooks/active_support_notifications/finish_with_state_shared_examples.rb
|
@@ -441,7 +441,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
441
441
|
- !ruby/object:Gem::Version
|
442
442
|
version: '0'
|
443
443
|
requirements: []
|
444
|
-
rubygems_version: 3.3.
|
444
|
+
rubygems_version: 3.3.7
|
445
445
|
signing_key:
|
446
446
|
specification_version: 4
|
447
447
|
summary: Logs performance and exception data from your app to appsignal.com
|
@@ -471,7 +471,7 @@ test_files:
|
|
471
471
|
- spec/lib/appsignal/extension/jruby_spec.rb
|
472
472
|
- spec/lib/appsignal/extension_install_failure_spec.rb
|
473
473
|
- spec/lib/appsignal/extension_spec.rb
|
474
|
-
- spec/lib/appsignal/
|
474
|
+
- spec/lib/appsignal/garbage_collection_spec.rb
|
475
475
|
- spec/lib/appsignal/hooks/action_cable_spec.rb
|
476
476
|
- spec/lib/appsignal/hooks/action_mailer_spec.rb
|
477
477
|
- spec/lib/appsignal/hooks/active_support_notifications/finish_with_state_shared_examples.rb
|
@@ -1,61 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Appsignal
|
4
|
-
# {Appsignal::GarbageCollectionProfiler} wraps Ruby's `GC::Profiler` to be
|
5
|
-
# able to track garbage collection time for multiple transactions, while
|
6
|
-
# constantly clearing `GC::Profiler`'s total_time to make sure it doesn't
|
7
|
-
# leak memory by keeping garbage collection run samples in memory.
|
8
|
-
#
|
9
|
-
# @api private
|
10
|
-
class GarbageCollectionProfiler
|
11
|
-
def self.lock
|
12
|
-
@lock ||= Mutex.new
|
13
|
-
end
|
14
|
-
|
15
|
-
def initialize
|
16
|
-
@total_time = 0
|
17
|
-
end
|
18
|
-
|
19
|
-
# Whenever {#total_time} is called, the current `GC::Profiler#total_time`
|
20
|
-
# gets added to `@total_time`, after which `GC::Profiler.clear` is called
|
21
|
-
# to prevent it from leaking memory. A class-level lock is used to make
|
22
|
-
# sure garbage collection time is never counted more than once.
|
23
|
-
#
|
24
|
-
# Whenever `@total_time` gets above two billion milliseconds (about 23
|
25
|
-
# days), it's reset to make sure the result fits in a signed 32-bit
|
26
|
-
# integer.
|
27
|
-
#
|
28
|
-
# @return [Integer]
|
29
|
-
def total_time
|
30
|
-
lock.synchronize do
|
31
|
-
@total_time += (internal_profiler.total_time * 1000).round
|
32
|
-
internal_profiler.clear
|
33
|
-
end
|
34
|
-
|
35
|
-
@total_time = 0 if @total_time > 2_000_000_000
|
36
|
-
|
37
|
-
@total_time
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def internal_profiler
|
43
|
-
GC::Profiler
|
44
|
-
end
|
45
|
-
|
46
|
-
def lock
|
47
|
-
self.class.lock
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# {Appsignal::NilGarbageCollectionProfiler} is a dummy profiler
|
52
|
-
# that always returns 0 as the total time.
|
53
|
-
# Used when we don't want any profile information
|
54
|
-
#
|
55
|
-
# @api private
|
56
|
-
class NilGarbageCollectionProfiler
|
57
|
-
def total_time
|
58
|
-
0
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|