vernier 0.0.0 → 0.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
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
- }