vernier 0.0.0 → 0.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
2
  SHA256:
3
- metadata.gz: bb417258dead5051e93582f97ab76914610d5b76a401fca448726e030a0a87ea
4
- data.tar.gz: 44e4270b2bbd665a464094b6484d7c3a3be41eebfab288c4258ab01038f73aab
3
+ metadata.gz: 97eadc619870ae964e5bf61e925791d4399c0480775aadc5af31597535ae0374
4
+ data.tar.gz: 5327fd25cfac20752905f89126e8f6a4422e8cc90c134bab98a47b7132f7909d
5
5
  SHA512:
6
- metadata.gz: 56c65be290cfbb04be65159dc166e54902d9b4ecb1b2870247c60c7e8c8a4bd9a107ef824ae29dd0279001765e9b24ae0acc4df1991375312dec92d477a5ce64
7
- data.tar.gz: e4bebe5150e4cd3c8e67ee67907f061e404be84d3f6c202e3466b33e267861b8d5a2e6e573dbdc05801a500bf003182dd542063506e91986805de8c76856094f
6
+ metadata.gz: f0fd5a598a03f41a0d5984d4652f3f85dece310ca83c015b554b85271cda220ca2535c198d4d62dad39532861ff38c858cb1048e46c29a43ce25c4fcb318ba7f
7
+ data.tar.gz: d7e0462723a56f1e7a1cef5c96f2a86958df9b129442defa7814c2b5dc8d2844a1507abf0ea486d7a0458257347e9cf243d6f369568258bbdde0fa374a9d7268
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- vernier (0.0.0)
4
+ vernier (0.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,28 +1,25 @@
1
1
  # Vernier
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/vernier`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ Experimental Ruby profiling tool
6
4
 
7
5
  ## Installation
8
6
 
9
- Add this line to your application's Gemfile:
10
-
11
7
  ```ruby
12
8
  gem 'vernier'
13
9
  ```
14
10
 
15
- And then execute:
11
+ ## Usage
16
12
 
17
- $ bundle install
13
+ Record a flamegraph of all **retained** allocations from requiring `irb`.
18
14
 
19
- Or install it yourself as:
15
+ ```
16
+ ruby -r vernier -e 'Vernier.trace_retained(out: "irb.stackcollapse") { require "irb" }'
17
+ ```
20
18
 
21
- $ gem install vernier
19
+ The output can then be viewed in speedscope or other flamegraph tools
22
20
 
23
- ## Usage
21
+ <img width="1082" alt="Screen Shot 2022-04-26 at 8 11 19 PM" src="https://user-images.githubusercontent.com/131752/165440422-3a11f5cc-3018-4455-8918-887c2afa6d6e.png">
24
22
 
25
- TODO: Write usage instructions here
26
23
 
27
24
  ## Development
28
25
 
@@ -2,4 +2,7 @@
2
2
 
3
3
  require "mkmf"
4
4
 
5
+ $CXXFLAGS += " -std=c++14 "
6
+ $CXXFLAGS += " -ggdb3 -Og "
7
+
5
8
  create_makefile("vernier/vernier")
@@ -0,0 +1,78 @@
1
+ #pragma once
2
+
3
+ #include "ruby/debug.h"
4
+
5
+ #include <iostream>
6
+ #include <vector>
7
+ #include <memory>
8
+ #include <algorithm>
9
+
10
+ struct Frame {
11
+ VALUE frame;
12
+ int line;
13
+
14
+ VALUE full_label() const {
15
+ return rb_profile_frame_full_label(frame);
16
+ }
17
+
18
+ VALUE absolute_path() const {
19
+ return rb_profile_frame_absolute_path(frame);
20
+ }
21
+
22
+ VALUE path() const {
23
+ return rb_profile_frame_path(frame);
24
+ }
25
+
26
+ VALUE file() const {
27
+ VALUE file = absolute_path();
28
+ return NIL_P(file) ? path() : file;
29
+ }
30
+
31
+ VALUE first_lineno() const {
32
+ return rb_profile_frame_first_lineno(frame);
33
+ }
34
+ };
35
+
36
+ struct Stack {
37
+ std::unique_ptr<VALUE[]> frames;
38
+ std::unique_ptr<int[]> lines;
39
+ int _size = 0;
40
+
41
+ int size() const {
42
+ return _size;
43
+ }
44
+
45
+ Stack(const VALUE *_frames, const int *_lines, int size) :
46
+ _size(size),
47
+ frames(std::make_unique<VALUE[]>(size)),
48
+ lines(std::make_unique<int[]>(size))
49
+ {
50
+ std::copy_n(_frames, size, &frames[0]);
51
+ std::copy_n(_lines, size, &lines[0]);
52
+ }
53
+
54
+ Frame frame(int i) const {
55
+ if (i >= size()) throw std::out_of_range("nope");
56
+ return Frame{frames[i], lines[i]};
57
+ }
58
+ };
59
+
60
+ std::ostream& operator<<(std::ostream& os, const Frame& frame)
61
+ {
62
+ VALUE label = frame.full_label();
63
+ VALUE file = frame.absolute_path();
64
+ const char *file_cstr = NIL_P(file) ? "" : StringValueCStr(file);
65
+ os << file_cstr << ":" << frame.line << ":in `" << StringValueCStr(label) << "'";
66
+ return os;
67
+ }
68
+
69
+ std::ostream& operator<<(std::ostream& os, const Stack& stack)
70
+ {
71
+ for (int i = 0; i < stack.size(); i++) {
72
+ Frame frame = stack.frame(i);
73
+ os << frame << "\n";
74
+ }
75
+
76
+ return os;
77
+ }
78
+
@@ -0,0 +1,188 @@
1
+ #include <iostream>
2
+ #include <vector>
3
+ #include <memory>
4
+ #include <algorithm>
5
+ #include <sstream>
6
+ #include <unordered_map>
7
+
8
+ #include "vernier.hh"
9
+ #include "stack.hh"
10
+ #include "ruby/debug.h"
11
+
12
+ using namespace std;
13
+
14
+ #define numberof(array) ((int)(sizeof(array) / sizeof((array)[0])))
15
+
16
+ static VALUE rb_mVernier;
17
+
18
+ struct retained_collector {
19
+ int allocated_objects = 0;
20
+ int freed_objects = 0;
21
+
22
+ std::unordered_map<VALUE, std::unique_ptr<Stack>> object_frames;
23
+ };
24
+
25
+ struct TraceArg {
26
+ rb_trace_arg_t *tparg;
27
+ VALUE obj;
28
+ VALUE path;
29
+ VALUE line;
30
+ VALUE mid;
31
+ VALUE klass;
32
+
33
+ TraceArg(VALUE tpval) {
34
+ tparg = rb_tracearg_from_tracepoint(tpval);
35
+ obj = rb_tracearg_object(tparg);
36
+ path = rb_tracearg_path(tparg);
37
+ line = rb_tracearg_lineno(tparg);
38
+ mid = rb_tracearg_method_id(tparg);
39
+ klass = rb_tracearg_defined_class(tparg);
40
+ }
41
+ };
42
+
43
+ static retained_collector _collector;
44
+
45
+ static VALUE tp_newobj;
46
+ static VALUE tp_freeobj;
47
+ static void
48
+ newobj_i(VALUE tpval, void *data) {
49
+ retained_collector *collector = static_cast<retained_collector *>(data);
50
+ TraceArg tp(tpval);
51
+ collector->allocated_objects++;
52
+
53
+ VALUE frames_buffer[2048];
54
+ int lines_buffer[2048];
55
+ int n = rb_profile_frames(0, 2048, frames_buffer, lines_buffer);
56
+
57
+ collector->object_frames.emplace(
58
+ tp.obj,
59
+ make_unique<Stack>(frames_buffer, lines_buffer, n)
60
+ );
61
+ }
62
+
63
+ static void
64
+ freeobj_i(VALUE tpval, void *data) {
65
+ retained_collector *collector = static_cast<retained_collector *>(data);
66
+ TraceArg tp(tpval);
67
+ collector->freed_objects++;
68
+
69
+ collector->object_frames.erase(tp.obj);
70
+ }
71
+
72
+
73
+ static VALUE
74
+ trace_retained_start(VALUE self) {
75
+ retained_collector *collector = &_collector;
76
+
77
+ tp_newobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, collector);
78
+ tp_freeobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, collector);
79
+
80
+ rb_tracepoint_enable(tp_newobj);
81
+ rb_tracepoint_enable(tp_freeobj);
82
+
83
+ return Qtrue;
84
+ }
85
+
86
+ #define sym(name) ID2SYM(rb_intern_const(name))
87
+
88
+ // HACK: This isn't public, but the objspace ext uses it
89
+ extern "C" size_t rb_obj_memsize_of(VALUE);
90
+
91
+ static const char *
92
+ ruby_object_type_name(VALUE obj) {
93
+ enum ruby_value_type type = rb_type(obj);
94
+
95
+ #define TYPE_CASE(x) case (x): return (#x)
96
+
97
+ // Many of these are impossible, but it's easier to just include them
98
+ switch (type) {
99
+ TYPE_CASE(T_OBJECT);
100
+ TYPE_CASE(T_CLASS);
101
+ TYPE_CASE(T_MODULE);
102
+ TYPE_CASE(T_FLOAT);
103
+ TYPE_CASE(T_STRING);
104
+ TYPE_CASE(T_REGEXP);
105
+ TYPE_CASE(T_ARRAY);
106
+ TYPE_CASE(T_HASH);
107
+ TYPE_CASE(T_STRUCT);
108
+ TYPE_CASE(T_BIGNUM);
109
+ TYPE_CASE(T_FILE);
110
+ TYPE_CASE(T_DATA);
111
+ TYPE_CASE(T_MATCH);
112
+ TYPE_CASE(T_COMPLEX);
113
+ TYPE_CASE(T_RATIONAL);
114
+
115
+ TYPE_CASE(T_NIL);
116
+ TYPE_CASE(T_TRUE);
117
+ TYPE_CASE(T_FALSE);
118
+ TYPE_CASE(T_SYMBOL);
119
+ TYPE_CASE(T_FIXNUM);
120
+ TYPE_CASE(T_UNDEF);
121
+
122
+ TYPE_CASE(T_IMEMO);
123
+ TYPE_CASE(T_NODE);
124
+ TYPE_CASE(T_ICLASS);
125
+ TYPE_CASE(T_ZOMBIE);
126
+ TYPE_CASE(T_MOVED);
127
+
128
+ default:
129
+ return "unknown type";
130
+ }
131
+ #undef TYPE_CASE
132
+ }
133
+
134
+ static VALUE
135
+ trace_retained_stop(VALUE self) {
136
+ rb_tracepoint_disable(tp_newobj);
137
+ rb_tracepoint_disable(tp_freeobj);
138
+
139
+ retained_collector *collector = &_collector;
140
+
141
+ std::stringstream ss;
142
+
143
+ for (auto& it: collector->object_frames) {
144
+ VALUE obj = it.first;
145
+ const Stack &stack = *it.second;
146
+
147
+ for (int i = stack.size() - 1; i >= 0; i--) {
148
+ const Frame &frame = stack.frame(i);
149
+ ss << frame;
150
+ if (i > 0) ss << ";";
151
+ }
152
+ ss << ";" << ruby_object_type_name(obj);
153
+ ss << " " << rb_obj_memsize_of(obj) << endl;
154
+ }
155
+
156
+ std::string s = ss.str();
157
+ VALUE str = rb_str_new(s.c_str(), s.size());
158
+
159
+ return str;
160
+ }
161
+
162
+ static void
163
+ retained_collector_mark(void *data) {
164
+ retained_collector *collector = static_cast<retained_collector *>(data);
165
+
166
+ // We don't mark the objects, but we MUST mark the frames, otherwise they
167
+ // can be garbage collected.
168
+ // This may lead to method entries being unnecessarily retained.
169
+ for (auto& it: collector->object_frames) {
170
+ const Stack &stack = *it.second;
171
+
172
+ for (int i = 0; i < stack.size(); i++) {
173
+ rb_gc_mark(stack.frames[i]);
174
+ }
175
+ }
176
+ }
177
+
178
+ extern "C" void
179
+ Init_vernier(void)
180
+ {
181
+ rb_mVernier = rb_define_module("Vernier");
182
+
183
+ rb_define_module_function(rb_mVernier, "trace_retained_start", trace_retained_start, 0);
184
+ rb_define_module_function(rb_mVernier, "trace_retained_stop", trace_retained_stop, 0);
185
+
186
+ static VALUE gc_hook = Data_Wrap_Struct(rb_cObject, retained_collector_mark, NULL, &_collector);
187
+ rb_global_variable(&gc_hook);
188
+ }
File without changes
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vernier
4
- VERSION = "0.0.0"
4
+ VERSION = "0.1.0"
5
5
  end
data/lib/vernier.rb CHANGED
@@ -5,5 +5,15 @@ require_relative "vernier/vernier"
5
5
 
6
6
  module Vernier
7
7
  class Error < StandardError; end
8
- # Your code goes here...
8
+
9
+ def self.trace_retained(out: nil, gc: true)
10
+ 3.times { GC.start } if gc
11
+ Vernier.trace_retained_start
12
+ yield
13
+ 3.times { GC.start } if gc
14
+ result = Vernier.trace_retained_stop
15
+
16
+ File.write(out, result) if out
17
+ result
18
+ end
9
19
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vernier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Hawthorn
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-26 00:00:00.000000000 Z
11
+ date: 2022-04-27 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: An experimental profiler
14
14
  email:
@@ -27,8 +27,9 @@ files:
27
27
  - bin/console
28
28
  - bin/setup
29
29
  - ext/vernier/extconf.rb
30
- - ext/vernier/vernier.c
31
- - ext/vernier/vernier.h
30
+ - ext/vernier/stack.hh
31
+ - ext/vernier/vernier.cc
32
+ - ext/vernier/vernier.hh
32
33
  - lib/vernier.rb
33
34
  - lib/vernier/version.rb
34
35
  - sig/vernier.rbs
@@ -1,9 +0,0 @@
1
- #include "vernier.h"
2
-
3
- VALUE rb_mVernier;
4
-
5
- void
6
- Init_vernier(void)
7
- {
8
- rb_mVernier = rb_define_module("Vernier");
9
- }