vernier 0.1.1 → 0.2.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: 9d826cc1605b081bf6784ff46e1e49b09958f3c2626637d3d994b967bc52b391
4
- data.tar.gz: 743a02e32629c961795ab0a4b62f839e5bf905e3a4f339f6534157cd257f2e7a
3
+ metadata.gz: da6d8fafb453c25b5f1127a33c53401150cb91bf604a37db971c952ec490d900
4
+ data.tar.gz: 97cc5b3435335fe97c531122d5692a3af3264c5abeb34eafb1a1808743b017fe
5
5
  SHA512:
6
- metadata.gz: ac4dc6ea1e3f9c32ba223e96aa7eae35c62f4a4e3d503efdee643b44984f5d03638b42a4d2e25fe991e8bc49240f75541da0c0791b525782f827969f1926d50c
7
- data.tar.gz: 4321e93946c0c2155f79326f9bf29f00bf0fab24785c1c7238b8fdf46570706d8906bf70e80241a7dadd1d1531f8c44e12b4b47584b6024cf7d47fa9fa0a432d
6
+ metadata.gz: 92529d7331c8a015c900227f1d28b093322dfb386ce99213c4a22dea168d433f9e0d9ae221c3171ea7e3c91fd67753a9da668140af97f62e0edec08341cb4f63
7
+ data.tar.gz: bdd0baaf24abdbed97f603da42e827fa10442f4f702f15a5ee2f01e2b25eb893465d8b313bcd3dec84a402d76554aa58d28547e2bad401db2c4dbb24797a2414
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vernier
2
2
 
3
- Experimental Ruby profiling tool
3
+ Experimental next-generation Ruby profiler.
4
4
 
5
5
  ## Installation
6
6
 
@@ -10,16 +10,23 @@ gem 'vernier'
10
10
 
11
11
  ## Usage
12
12
 
13
- Record a flamegraph of all **retained** allocations from requiring `irb`.
13
+ ### Retained memory
14
+
15
+ Record a flamegraph of all **retained** allocations from loading `irb`.
14
16
 
15
17
  ```
16
- ruby -r vernier -e 'Vernier.trace_retained(out: "irb.stackcollapse") { require "irb" }'
18
+ ruby -r vernier -e 'Vernier.trace_retained(out: "irb_profile.json") { require "irb" }'
17
19
  ```
18
20
 
19
- The output can then be viewed in speedscope or other flamegraph tools
21
+ The output can then be viewed in the [Firefox Profiler (demo)](https://share.firefox.dev/3DhLsFa)
22
+
23
+ ![Screenshot 2023-07-16 at 21-06-19 Ruby_Vernier – 1970-01-01 12 00 00 a m UTC – Firefox Profiler](https://github.com/jhawthorn/vernier/assets/131752/9ca0b593-70fb-4c8b-aed9-cb33e0e0bc06)
20
24
 
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">
25
+ ### Time
22
26
 
27
+ ```
28
+ Vernier.trace(out: "time_profile.json") { some_slow_method }
29
+ ```
23
30
 
24
31
  ## Development
25
32
 
@@ -31,6 +38,13 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
31
38
 
32
39
  Bug reports and pull requests are welcome on GitHub at https://github.com/jhawthorn/vernier. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/jhawthorn/vernier/blob/main/CODE_OF_CONDUCT.md).
33
40
 
41
+ ### Resources
42
+
43
+ * https://profiler.firefox.com/docs/#/
44
+ * https://github.com/firefox-devtools/profiler/tree/main/docs-developer
45
+ * https://github.com/tmm1/stackprof
46
+ * https://github.com/ruby/ruby/pull/5500
47
+
34
48
  ## License
35
49
 
36
50
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -9,6 +9,10 @@ Rake::TestTask.new(:test) do |t|
9
9
  t.test_files = FileList["test/**/test_*.rb"]
10
10
  end
11
11
 
12
+ task :console => :compile do
13
+ sh "irb -r vernier"
14
+ end
15
+
12
16
  require "rake/extensiontask"
13
17
 
14
18
  task build: :compile
@@ -0,0 +1,38 @@
1
+ require "vernier"
2
+
3
+ Vernier.trace(out: "http_requests.json") do
4
+
5
+ require "net/http"
6
+ require "uri"
7
+ require "openssl"
8
+
9
+ urls = Queue.new
10
+ received = Queue.new
11
+
12
+ threads = 2.times.map do
13
+ Thread.new do
14
+ while url = urls.pop
15
+ uri = URI.parse(url)
16
+ response = Net::HTTP.get_response(uri)
17
+
18
+ received << [url, response.code]
19
+ end
20
+ end
21
+ end
22
+
23
+ Thread.new do
24
+ threads.each(&:join)
25
+ received.close
26
+ end
27
+
28
+ urls << "http://example.com"
29
+ urls << "https://www.johnhawthorn.com"
30
+ urls << "https://tenderlovemaking.com/"
31
+ urls.close
32
+
33
+ while x = received.pop
34
+ url, code = *x
35
+ puts "#{url} #{code}"
36
+ end
37
+
38
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "mkmf"
4
4
 
5
- $CXXFLAGS += " -std=c++14 "
5
+ $CXXFLAGS += " -std=c++17 "
6
6
  $CXXFLAGS += " -ggdb3 -Og "
7
7
 
8
8
  create_makefile("vernier/vernier")
@@ -0,0 +1,44 @@
1
+ #pragma once
2
+
3
+ inline const char *
4
+ ruby_object_type_name(VALUE obj) {
5
+ enum ruby_value_type type = rb_type(obj);
6
+
7
+ #define TYPE_CASE(x) case (x): return (#x)
8
+
9
+ // Many of these are impossible, but it's easier to just include them
10
+ switch (type) {
11
+ TYPE_CASE(T_OBJECT);
12
+ TYPE_CASE(T_CLASS);
13
+ TYPE_CASE(T_MODULE);
14
+ TYPE_CASE(T_FLOAT);
15
+ TYPE_CASE(T_STRING);
16
+ TYPE_CASE(T_REGEXP);
17
+ TYPE_CASE(T_ARRAY);
18
+ TYPE_CASE(T_HASH);
19
+ TYPE_CASE(T_STRUCT);
20
+ TYPE_CASE(T_BIGNUM);
21
+ TYPE_CASE(T_FILE);
22
+ TYPE_CASE(T_DATA);
23
+ TYPE_CASE(T_MATCH);
24
+ TYPE_CASE(T_COMPLEX);
25
+ TYPE_CASE(T_RATIONAL);
26
+
27
+ TYPE_CASE(T_NIL);
28
+ TYPE_CASE(T_TRUE);
29
+ TYPE_CASE(T_FALSE);
30
+ TYPE_CASE(T_SYMBOL);
31
+ TYPE_CASE(T_FIXNUM);
32
+ TYPE_CASE(T_UNDEF);
33
+
34
+ TYPE_CASE(T_IMEMO);
35
+ TYPE_CASE(T_NODE);
36
+ TYPE_CASE(T_ICLASS);
37
+ TYPE_CASE(T_ZOMBIE);
38
+ TYPE_CASE(T_MOVED);
39
+
40
+ default:
41
+ return "unknown type";
42
+ }
43
+ #undef TYPE_CASE
44
+ }
data/ext/vernier/stack.hh CHANGED
@@ -7,33 +7,87 @@
7
7
  #include <memory>
8
8
  #include <algorithm>
9
9
 
10
- struct Frame {
11
- VALUE frame;
12
- int line;
10
+ struct FrameInfo {
11
+ static const char *label_cstr(VALUE frame) {
12
+ VALUE label = rb_profile_frame_full_label(frame);
13
+ return StringValueCStr(label);
14
+ }
13
15
 
14
- VALUE full_label() const {
15
- return rb_profile_frame_full_label(frame);
16
+ static const char *file_cstr(VALUE frame) {
17
+ VALUE file = rb_profile_frame_absolute_path(frame);
18
+ if (NIL_P(file))
19
+ file = rb_profile_frame_path(frame);
20
+ if (NIL_P(file)) {
21
+ return "";
22
+ } else {
23
+ return StringValueCStr(file);
24
+ }
16
25
  }
17
26
 
18
- VALUE absolute_path() const {
19
- return rb_profile_frame_absolute_path(frame);
27
+ static int first_lineno_int(VALUE frame) {
28
+ VALUE first_lineno = rb_profile_frame_first_lineno(frame);
29
+ return NIL_P(first_lineno) ? 0 : FIX2INT(first_lineno);
20
30
  }
21
31
 
22
- VALUE path() const {
23
- return rb_profile_frame_path(frame);
32
+ FrameInfo(VALUE frame) :
33
+ label(label_cstr(frame)),
34
+ file(file_cstr(frame)),
35
+ first_lineno(first_lineno_int(frame)) { }
36
+
37
+ std::string label;
38
+ std::string file;
39
+ int first_lineno;
40
+ };
41
+
42
+ bool operator==(const FrameInfo& lhs, const FrameInfo& rhs) noexcept {
43
+ return
44
+ lhs.label == rhs.label &&
45
+ lhs.file == rhs.file &&
46
+ lhs.first_lineno == rhs.first_lineno;
47
+ }
48
+
49
+ template<>
50
+ struct std::hash<FrameInfo>
51
+ {
52
+ std::size_t operator()(FrameInfo const& f) const noexcept
53
+ {
54
+ return
55
+ std::hash<std::string>{}(f.label) ^
56
+ std::hash<std::string>{}(f.file) ^
57
+ f.first_lineno;
24
58
  }
59
+ };
25
60
 
26
- VALUE file() const {
27
- VALUE file = absolute_path();
28
- return NIL_P(file) ? path() : file;
61
+
62
+ struct Frame {
63
+ VALUE frame;
64
+ int line;
65
+
66
+ FrameInfo info() const {
67
+ return FrameInfo(frame);
29
68
  }
69
+ };
30
70
 
31
- VALUE first_lineno() const {
32
- return rb_profile_frame_first_lineno(frame);
71
+ bool operator==(const Frame& lhs, const Frame& rhs) noexcept {
72
+ return lhs.frame == rhs.frame && lhs.line == rhs.line;
73
+ }
74
+
75
+ template<>
76
+ struct std::hash<Frame>
77
+ {
78
+ std::size_t operator()(Frame const& s) const noexcept
79
+ {
80
+ return s.frame ^ s.line;
33
81
  }
34
82
  };
35
83
 
36
- struct Stack {
84
+ struct BaseStack {
85
+ virtual ~BaseStack() {};
86
+
87
+ virtual int size() const = 0;
88
+ };
89
+
90
+ struct Stack : public BaseStack {
37
91
  std::unique_ptr<VALUE[]> frames;
38
92
  std::unique_ptr<int[]> lines;
39
93
  int _size = 0;
@@ -51,28 +105,51 @@ struct Stack {
51
105
  std::copy_n(_lines, size, &lines[0]);
52
106
  }
53
107
 
108
+ Stack(const Stack &s) :
109
+ _size(s.size()),
110
+ frames(std::make_unique<VALUE[]>(s.size())),
111
+ lines(std::make_unique<int[]>(s.size()))
112
+ {
113
+ std::copy_n(&s.frames[0], s.size(), &frames[0]);
114
+ std::copy_n(&s.lines[0], s.size(), &lines[0]);
115
+ }
116
+
54
117
  Frame frame(int i) const {
55
118
  if (i >= size()) throw std::out_of_range("nope");
56
119
  return Frame{frames[i], lines[i]};
57
120
  }
58
121
  };
59
122
 
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;
123
+ bool operator==(const Stack& lhs, const Stack& rhs) noexcept {
124
+ return lhs.size() == rhs.size() &&
125
+ std::equal(&lhs.frames[0], &lhs.frames[lhs.size()], &rhs.frames[0]) &&
126
+ std::equal(&lhs.lines[0], &lhs.lines[lhs.size()], &rhs.lines[0]);
67
127
  }
68
128
 
69
- std::ostream& operator<<(std::ostream& os, const Stack& stack)
129
+ // https://xoshiro.di.unimi.it/splitmix64.c
130
+ // https://nullprogram.com/blog/2018/07/31/
131
+ uint64_t
132
+ hash64(uint64_t x)
70
133
  {
71
- for (int i = 0; i < stack.size(); i++) {
72
- Frame frame = stack.frame(i);
73
- os << frame << "\n";
74
- }
75
-
76
- return os;
134
+ x ^= x >> 16;
135
+ x *= 0x7feb352dU;
136
+ x ^= x >> 15;
137
+ x *= 0x846ca68bU;
138
+ x ^= x >> 16;
139
+ return x;
77
140
  }
78
141
 
142
+ template<>
143
+ struct std::hash<Stack>
144
+ {
145
+ std::size_t operator()(Stack const& s) const noexcept
146
+ {
147
+ size_t hash = 0;
148
+ for (int i = 0; i < s.size(); i++) {
149
+ VALUE frame = s.frames[i];
150
+ hash ^= frame;
151
+ hash = hash64(hash);
152
+ }
153
+ return hash;
154
+ }
155
+ };