vernier 1.8.1 → 1.9.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/.ruby-version +1 -1
- data/Gemfile +1 -0
- data/README.md +65 -0
- data/examples/custom_hook.rb +37 -0
- data/ext/vernier/heap_tracker.cc +277 -0
- data/ext/vernier/memory.cc +1 -1
- data/ext/vernier/stack_table.cc +290 -0
- data/ext/vernier/stack_table.hh +314 -0
- data/ext/vernier/vernier.cc +67 -791
- data/ext/vernier/vernier.hh +7 -0
- data/lib/vernier/collector.rb +112 -2
- data/lib/vernier/heap_tracker.rb +47 -0
- data/lib/vernier/memory_leak_detector.rb +40 -0
- data/lib/vernier/result.rb +37 -22
- data/lib/vernier/stack_table_helpers.rb +24 -10
- data/lib/vernier/version.rb +1 -1
- data/lib/vernier.rb +2 -6
- data/vernier.gemspec +39 -0
- metadata +9 -2
data/ext/vernier/vernier.hh
CHANGED
|
@@ -3,8 +3,15 @@
|
|
|
3
3
|
|
|
4
4
|
#include "ruby.h"
|
|
5
5
|
|
|
6
|
+
// HACK: This isn't public, but the objspace ext uses it
|
|
7
|
+
extern "C" size_t rb_obj_memsize_of(VALUE);
|
|
8
|
+
|
|
9
|
+
#define sym(name) ID2SYM(rb_intern_const(name))
|
|
10
|
+
|
|
6
11
|
extern VALUE rb_mVernier;
|
|
7
12
|
|
|
8
13
|
void Init_memory();
|
|
14
|
+
void Init_stack_table();
|
|
15
|
+
void Init_heap_tracker();
|
|
9
16
|
|
|
10
17
|
#endif /* VERNIER_H */
|
data/lib/vernier/collector.rb
CHANGED
|
@@ -5,6 +5,110 @@ require_relative "thread_names"
|
|
|
5
5
|
|
|
6
6
|
module Vernier
|
|
7
7
|
class Collector
|
|
8
|
+
class CustomCollector < Collector
|
|
9
|
+
def initialize(mode, options)
|
|
10
|
+
@stack_table = StackTable.new
|
|
11
|
+
|
|
12
|
+
@samples = []
|
|
13
|
+
@timestamps = []
|
|
14
|
+
|
|
15
|
+
@started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
|
16
|
+
super
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def sample
|
|
20
|
+
@samples << @stack_table.current_stack
|
|
21
|
+
@timestamps << Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def start
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def finish
|
|
28
|
+
result = Result.new
|
|
29
|
+
result.instance_variable_set(:@threads, {
|
|
30
|
+
0 => {
|
|
31
|
+
tid: 0,
|
|
32
|
+
name: "custom",
|
|
33
|
+
started_at: @started_at,
|
|
34
|
+
samples: @samples,
|
|
35
|
+
weights: [1] * @samples.size,
|
|
36
|
+
timestamps: @timestamps,
|
|
37
|
+
sample_categories: [0] * @samples.size,
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
result.instance_variable_set(:@meta, {
|
|
41
|
+
started_at: @started_at
|
|
42
|
+
})
|
|
43
|
+
result
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class RetainedCollector < Collector
|
|
48
|
+
def initialize(mode, options)
|
|
49
|
+
@stack_table = StackTable.new
|
|
50
|
+
@heap_tracker = HeapTracker.new(@stack_table)
|
|
51
|
+
|
|
52
|
+
@started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
|
53
|
+
super
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def start
|
|
57
|
+
@heap_tracker.collect
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def drain
|
|
61
|
+
@heap_tracker.drain
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def finish
|
|
65
|
+
@heap_tracker.drain
|
|
66
|
+
|
|
67
|
+
GC.start
|
|
68
|
+
|
|
69
|
+
@stack_table.finalize
|
|
70
|
+
|
|
71
|
+
GC.start
|
|
72
|
+
|
|
73
|
+
@heap_tracker.lock
|
|
74
|
+
tracker_data = @heap_tracker.data
|
|
75
|
+
|
|
76
|
+
samples = tracker_data.fetch(:samples)
|
|
77
|
+
weights = tracker_data.fetch(:weights)
|
|
78
|
+
|
|
79
|
+
result = Result.new
|
|
80
|
+
result.instance_variable_set(:@threads, {
|
|
81
|
+
0 => {
|
|
82
|
+
tid: 0,
|
|
83
|
+
name: "retained memory",
|
|
84
|
+
started_at: @started_at,
|
|
85
|
+
samples: samples,
|
|
86
|
+
weights: weights,
|
|
87
|
+
sample_categories: [0] * samples.size,
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
result.instance_variable_set(:@meta, {
|
|
91
|
+
started_at: @started_at
|
|
92
|
+
})
|
|
93
|
+
result
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.new(mode, options = {})
|
|
98
|
+
return super unless Collector.equal?(self)
|
|
99
|
+
|
|
100
|
+
case mode
|
|
101
|
+
when :wall
|
|
102
|
+
TimeCollector.new(mode, options)
|
|
103
|
+
when :custom
|
|
104
|
+
CustomCollector.new(mode, options)
|
|
105
|
+
when :retained
|
|
106
|
+
RetainedCollector.new(mode, options)
|
|
107
|
+
else
|
|
108
|
+
raise ArgumentError, "invalid mode: #{mode.inspect}"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
8
112
|
def initialize(mode, options = {})
|
|
9
113
|
@gc = options.fetch(:gc, true) && (mode == :retained)
|
|
10
114
|
GC.start if @gc
|
|
@@ -30,14 +134,20 @@ module Vernier
|
|
|
30
134
|
@user_metadata = options[:metadata] || {}
|
|
31
135
|
end
|
|
32
136
|
|
|
137
|
+
attr_reader :stack_table
|
|
138
|
+
|
|
33
139
|
private def add_hook(hook)
|
|
34
|
-
case hook.to_sym
|
|
140
|
+
case hook.to_s.to_sym
|
|
35
141
|
when :rails, :activesupport
|
|
36
142
|
@hooks << Vernier::Hooks::ActiveSupport.new(self)
|
|
37
143
|
when :memory_usage
|
|
38
144
|
@hooks << Vernier::Hooks::MemoryUsage.new(self)
|
|
39
145
|
else
|
|
40
|
-
|
|
146
|
+
if hook.respond_to?(:new)
|
|
147
|
+
@hooks << hook.new(self)
|
|
148
|
+
else
|
|
149
|
+
warn "unknown hook: #{hook.inspect}"
|
|
150
|
+
end
|
|
41
151
|
end
|
|
42
152
|
end
|
|
43
153
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Vernier
|
|
4
|
+
# Plan: The heap tracker can be in a few states:
|
|
5
|
+
# * Idle
|
|
6
|
+
# * Collecting
|
|
7
|
+
# * Watching for new objects
|
|
8
|
+
# * Watching for freed objects
|
|
9
|
+
# * Draining
|
|
10
|
+
# * Ignoring new objects
|
|
11
|
+
# * Watching for freed objects
|
|
12
|
+
# * Locked
|
|
13
|
+
# * Ignoring new objects
|
|
14
|
+
# * Ignoring freed objects
|
|
15
|
+
# * Marking all existing objects (not yet implemented)
|
|
16
|
+
# * N.B. This prevents any objects which the tracker has seen from being GC'd
|
|
17
|
+
class HeapTracker
|
|
18
|
+
attr_reader :stack_table
|
|
19
|
+
|
|
20
|
+
def self.new(stack_table = StackTable.new)
|
|
21
|
+
_new(stack_table)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def inspect
|
|
25
|
+
"#<#{self.class} allocated_objects=#{allocated_objects} freed_objects=#{freed_objects} stack_table=#{stack_table.inspect}>"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.track(&block)
|
|
29
|
+
tracker = new
|
|
30
|
+
tracker.track(&block)
|
|
31
|
+
tracker
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def track
|
|
35
|
+
collect
|
|
36
|
+
yield self
|
|
37
|
+
ensure
|
|
38
|
+
lock
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def stack(obj)
|
|
42
|
+
idx = stack_idx(obj)
|
|
43
|
+
return nil unless idx
|
|
44
|
+
stack_table.stack(idx)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Vernier
|
|
4
|
+
class MemoryLeakDetector
|
|
5
|
+
def self.start_thread(...)
|
|
6
|
+
detector = new(...)
|
|
7
|
+
detector.start_thread
|
|
8
|
+
detector
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(idle_time: 0, collect_time:, drain_time: 0, **collector_options)
|
|
12
|
+
@idle_time = idle_time
|
|
13
|
+
@collect_time = collect_time
|
|
14
|
+
@drain_time = drain_time
|
|
15
|
+
@collector_options = collector_options
|
|
16
|
+
@thread = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def start_thread
|
|
20
|
+
@thread = Thread.new do
|
|
21
|
+
sleep @idle_time
|
|
22
|
+
|
|
23
|
+
collector = Collector.new(:retained, @collector_options)
|
|
24
|
+
collector.start
|
|
25
|
+
|
|
26
|
+
sleep @collect_time
|
|
27
|
+
|
|
28
|
+
collector.drain
|
|
29
|
+
|
|
30
|
+
sleep @drain_time
|
|
31
|
+
|
|
32
|
+
collector.stop
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def result
|
|
37
|
+
@thread&.value
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/vernier/result.rb
CHANGED
|
@@ -1,26 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Vernier
|
|
2
4
|
class Result
|
|
3
5
|
attr_accessor :stack_table
|
|
4
6
|
alias _stack_table stack_table
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
attr_accessor :hooks
|
|
8
|
+
attr_accessor :hooks, :pid, :end_time
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
attr_accessor :threads
|
|
12
|
-
attr_accessor :meta
|
|
13
|
-
attr_accessor :mode
|
|
10
|
+
attr_reader :meta, :threads, :gc_markers
|
|
14
11
|
|
|
15
12
|
def main_thread
|
|
16
13
|
threads.values.detect {|x| x[:is_main] }
|
|
17
14
|
end
|
|
18
15
|
|
|
19
|
-
# TODO: remove these
|
|
20
|
-
def weights; threads.values.flat_map { _1[:weights] }; end
|
|
21
|
-
def samples; threads.values.flat_map { _1[:samples] }; end
|
|
22
|
-
def sample_categories; threads.values.flat_map { _1[:sample_categories] }; end
|
|
23
|
-
|
|
24
16
|
# Realtime in nanoseconds since the unix epoch
|
|
25
17
|
def started_at
|
|
26
18
|
started_at_mono_ns = meta[:started_at]
|
|
@@ -41,10 +33,17 @@ module Vernier
|
|
|
41
33
|
def write(out:, format: "firefox")
|
|
42
34
|
case format
|
|
43
35
|
when "cpuprofile"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
36
|
+
if out.respond_to?(:write)
|
|
37
|
+
out.write(to_cpuprofile)
|
|
38
|
+
else
|
|
39
|
+
File.binwrite(out, to_cpuprofile)
|
|
40
|
+
end
|
|
41
|
+
when "firefox", nil
|
|
42
|
+
if out.respond_to?(:write)
|
|
43
|
+
out.write(to_firefox)
|
|
44
|
+
else
|
|
45
|
+
File.binwrite(out, to_firefox(gzip: out.end_with?(".gz")))
|
|
46
|
+
end
|
|
48
47
|
else
|
|
49
48
|
raise ArgumentError, "unknown format: #{format}"
|
|
50
49
|
end
|
|
@@ -55,15 +54,15 @@ module Vernier
|
|
|
55
54
|
end
|
|
56
55
|
|
|
57
56
|
def inspect
|
|
58
|
-
"#<#{self.class} #{elapsed_seconds} seconds, #{threads.count} threads, #{
|
|
57
|
+
"#<#{self.class} #{elapsed_seconds rescue "?"} seconds, #{threads.count} threads, #{total_samples} samples, #{total_unique_samples} unique>"
|
|
59
58
|
end
|
|
60
59
|
|
|
61
60
|
def each_sample
|
|
62
61
|
return enum_for(__method__) unless block_given?
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
threads.values.each do |thread|
|
|
63
|
+
thread[:samples].zip(thread[:weights]) do |stack_idx, weight|
|
|
64
|
+
yield stack(stack_idx), weight
|
|
65
|
+
end
|
|
67
66
|
end
|
|
68
67
|
end
|
|
69
68
|
|
|
@@ -71,8 +70,24 @@ module Vernier
|
|
|
71
70
|
stack_table.stack(idx)
|
|
72
71
|
end
|
|
73
72
|
|
|
73
|
+
def total_weights
|
|
74
|
+
threads.values.sum { _1[:weights].sum }
|
|
75
|
+
end
|
|
76
|
+
|
|
74
77
|
def total_bytes
|
|
75
|
-
|
|
78
|
+
unless meta[:mode] == :retained
|
|
79
|
+
raise NotImplementedError, "total_bytes is only implemented for retained mode"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
total_weights
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def total_samples
|
|
86
|
+
threads.values.sum { _1[:samples].count }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def total_unique_samples
|
|
90
|
+
threads.values.flat_map { _1[:samples] }.uniq.count
|
|
76
91
|
end
|
|
77
92
|
end
|
|
78
93
|
end
|
|
@@ -23,15 +23,21 @@ module Vernier
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def backtrace(stack_idx)
|
|
26
|
-
|
|
26
|
+
last_filename = nil
|
|
27
|
+
last_lineno = nil
|
|
28
|
+
full_stack(stack_idx).reverse.map do |stack_idx|
|
|
27
29
|
frame_idx = stack_frame_idx(stack_idx)
|
|
28
30
|
func_idx = frame_func_idx(frame_idx)
|
|
29
31
|
line = frame_line_no(frame_idx)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
line = last_lineno if line == 0
|
|
33
|
+
last_lineno = line
|
|
34
|
+
name = func_name(func_idx)
|
|
35
|
+
filename = func_path(func_idx)
|
|
36
|
+
filename = last_filename if filename.empty?
|
|
37
|
+
last_filename = filename
|
|
32
38
|
|
|
33
39
|
"#{filename}:#{line}:in '#{name}'"
|
|
34
|
-
end
|
|
40
|
+
end.reverse
|
|
35
41
|
end
|
|
36
42
|
|
|
37
43
|
def full_stack(stack_idx)
|
|
@@ -83,9 +89,14 @@ module Vernier
|
|
|
83
89
|
def line
|
|
84
90
|
stack_table.frame_line_no(idx)
|
|
85
91
|
end
|
|
92
|
+
alias lineno line
|
|
86
93
|
|
|
87
94
|
def to_s
|
|
88
|
-
|
|
95
|
+
if (line = self.line) == 0
|
|
96
|
+
func.to_s
|
|
97
|
+
else
|
|
98
|
+
"#{func}:#{line}"
|
|
99
|
+
end
|
|
89
100
|
end
|
|
90
101
|
end
|
|
91
102
|
|
|
@@ -102,14 +113,16 @@ module Vernier
|
|
|
102
113
|
end
|
|
103
114
|
alias each_frame each
|
|
104
115
|
|
|
105
|
-
def [](
|
|
106
|
-
raise RangeError if
|
|
116
|
+
def [](offset)
|
|
117
|
+
raise RangeError if offset < 0
|
|
107
118
|
stack_idx = idx
|
|
108
|
-
while
|
|
119
|
+
while stack_idx && offset > 0
|
|
109
120
|
stack_idx = stack_table.stack_parent_idx(stack_idx)
|
|
110
|
-
|
|
121
|
+
offset -= 1
|
|
122
|
+
end
|
|
123
|
+
if stack_idx && offset == 0
|
|
124
|
+
Frame.new(stack_table, stack_table.stack_frame_idx(stack_idx))
|
|
111
125
|
end
|
|
112
|
-
Frame.new(stack_table, stack_table.stack_frame_idx(stack_idx))
|
|
113
126
|
end
|
|
114
127
|
|
|
115
128
|
def leaf_frame_idx
|
|
@@ -134,6 +147,7 @@ module Vernier
|
|
|
134
147
|
end
|
|
135
148
|
|
|
136
149
|
def stack(idx)
|
|
150
|
+
raise ArgumentError, "invalid index" unless idx
|
|
137
151
|
Stack.new(self, idx)
|
|
138
152
|
end
|
|
139
153
|
end
|
data/lib/vernier/version.rb
CHANGED
data/lib/vernier.rb
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require_relative "vernier/version"
|
|
4
4
|
require_relative "vernier/collector"
|
|
5
5
|
require_relative "vernier/stack_table"
|
|
6
|
+
require_relative "vernier/heap_tracker"
|
|
7
|
+
require_relative "vernier/memory_leak_detector"
|
|
6
8
|
require_relative "vernier/parsed_profile"
|
|
7
9
|
require_relative "vernier/result"
|
|
8
10
|
require_relative "vernier/hooks"
|
|
@@ -63,10 +65,4 @@ module Vernier
|
|
|
63
65
|
def self.trace_retained(**profile_options, &block)
|
|
64
66
|
profile(**profile_options.merge(mode: :retained), &block)
|
|
65
67
|
end
|
|
66
|
-
|
|
67
|
-
class Collector
|
|
68
|
-
def self.new(mode, options = {})
|
|
69
|
-
_new(mode, options)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
68
|
end
|
data/vernier.gemspec
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/vernier/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "vernier"
|
|
7
|
+
spec.version = Vernier::VERSION
|
|
8
|
+
spec.authors = ["John Hawthorn"]
|
|
9
|
+
spec.email = ["john@hawthorn.email"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "A next generation CRuby profiler"
|
|
12
|
+
spec.description = "Next-generation Ruby 3.2.1+ sampling profiler. Tracks multiple threads, GVL activity, GC pauses, idle time, and more."
|
|
13
|
+
spec.homepage = "https://github.com/jhawthorn/vernier"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
unless ENV["IGNORE_REQUIRED_RUBY_VERSION"]
|
|
17
|
+
spec.required_ruby_version = ">= 3.2.1"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
21
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
22
|
+
spec.metadata["changelog_uri"] = spec.homepage
|
|
23
|
+
|
|
24
|
+
# Specify which files should be added to the gem when it is released.
|
|
25
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
26
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
27
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
28
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
spec.bindir = "exe"
|
|
32
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
33
|
+
spec.require_paths = ["lib"]
|
|
34
|
+
spec.extensions = ["ext/vernier/extconf.rb"]
|
|
35
|
+
|
|
36
|
+
spec.add_development_dependency "activesupport"
|
|
37
|
+
spec.add_development_dependency "gvltest"
|
|
38
|
+
spec.add_development_dependency "rack"
|
|
39
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vernier
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- John Hawthorn
|
|
@@ -70,6 +70,7 @@ files:
|
|
|
70
70
|
- bin/console
|
|
71
71
|
- bin/setup
|
|
72
72
|
- bin/vernier
|
|
73
|
+
- examples/custom_hook.rb
|
|
73
74
|
- examples/fiber_stalls.rb
|
|
74
75
|
- examples/gvl_sleep.rb
|
|
75
76
|
- examples/measure_overhead.rb
|
|
@@ -79,20 +80,25 @@ files:
|
|
|
79
80
|
- examples/threaded_http_requests.rb
|
|
80
81
|
- exe/vernier
|
|
81
82
|
- ext/vernier/extconf.rb
|
|
83
|
+
- ext/vernier/heap_tracker.cc
|
|
82
84
|
- ext/vernier/memory.cc
|
|
83
85
|
- ext/vernier/periodic_thread.hh
|
|
84
86
|
- ext/vernier/ruby_type_names.h
|
|
85
87
|
- ext/vernier/signal_safe_semaphore.hh
|
|
88
|
+
- ext/vernier/stack_table.cc
|
|
89
|
+
- ext/vernier/stack_table.hh
|
|
86
90
|
- ext/vernier/timestamp.hh
|
|
87
91
|
- ext/vernier/vernier.cc
|
|
88
92
|
- ext/vernier/vernier.hh
|
|
89
93
|
- lib/vernier.rb
|
|
90
94
|
- lib/vernier/autorun.rb
|
|
91
95
|
- lib/vernier/collector.rb
|
|
96
|
+
- lib/vernier/heap_tracker.rb
|
|
92
97
|
- lib/vernier/hooks.rb
|
|
93
98
|
- lib/vernier/hooks/active_support.rb
|
|
94
99
|
- lib/vernier/hooks/memory_usage.rb
|
|
95
100
|
- lib/vernier/marker.rb
|
|
101
|
+
- lib/vernier/memory_leak_detector.rb
|
|
96
102
|
- lib/vernier/middleware.rb
|
|
97
103
|
- lib/vernier/output/cpuprofile.rb
|
|
98
104
|
- lib/vernier/output/file_listing.rb
|
|
@@ -105,6 +111,7 @@ files:
|
|
|
105
111
|
- lib/vernier/stack_table_helpers.rb
|
|
106
112
|
- lib/vernier/thread_names.rb
|
|
107
113
|
- lib/vernier/version.rb
|
|
114
|
+
- vernier.gemspec
|
|
108
115
|
homepage: https://github.com/jhawthorn/vernier
|
|
109
116
|
licenses:
|
|
110
117
|
- MIT
|
|
@@ -126,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
126
133
|
- !ruby/object:Gem::Version
|
|
127
134
|
version: '0'
|
|
128
135
|
requirements: []
|
|
129
|
-
rubygems_version: 3.
|
|
136
|
+
rubygems_version: 3.6.9
|
|
130
137
|
specification_version: 4
|
|
131
138
|
summary: A next generation CRuby profiler
|
|
132
139
|
test_files: []
|