calleree 0.1.0 → 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: fbb6acebd17bc68a5547ccd29c14e41a359aec189a08dbdeca7ab19088a00a8e
4
- data.tar.gz: 7e364534c22b4d4d6184fba2dc472939bb578b379fe508d1292e538f27391383
3
+ metadata.gz: efe0a24448e4190f50b98f29915cce53ab119f58d47794ef096389c36283b9a2
4
+ data.tar.gz: a17ca6a2f922d7bed7930bca47e52aa8ca32556c66d3f41e4a69472ae11cb452
5
5
  SHA512:
6
- metadata.gz: 0a1be7bce4337c46c1bb6e45c1dc62d8801379a07167b0784dfa5c36e0595e5fd2bbcf939b27ca7e6ba41f006f6a6b3a31807f52a5d67861fb9ad314903ec209
7
- data.tar.gz: c5c6695fe51447599341bb541a903964380e1b6f1579856101739d14a6e6443fb04f4accd7859ef7d12ca2b8146989507475984ae7bd428cc27d05427b65a4fb
6
+ metadata.gz: 6bc549831417fcf325e61da2f4cbd9f5bf9cfa17a8f7921281bc104689e8e8ad40e294e5e29b1670cb0a66e4f036de45dcd64b875c6609dd7069917f0c434e04
7
+ data.tar.gz: a84916439baa9a7aa05f673709562d203b9eeae1632421bf1196ee16a19e848ba97ce1c53cd50d9e2c90ad177629d8e5e491aa64d28aa7f12cd29fa8a7ae441a
@@ -0,0 +1,34 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ branches: [ master ]
15
+
16
+ jobs:
17
+ test:
18
+
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ matrix:
22
+ ruby-version: ['2.6', '2.7', '3.0', 'head', 'debug']
23
+
24
+ steps:
25
+ - uses: actions/checkout@v2
26
+ - name: Set up Ruby
27
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
28
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
29
+ uses: ruby/setup-ruby@v1
30
+ with:
31
+ ruby-version: ${{ matrix.ruby-version }}
32
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
33
+ - name: Run tests
34
+ run: bundle exec rake
data/Gemfile CHANGED
@@ -3,6 +3,6 @@ source "https://rubygems.org"
3
3
  # Specify your gem's dependencies in calleree.gemspec
4
4
  gemspec
5
5
 
6
- gem "rake", "~> 12.0"
6
+ gem "rake"
7
7
  gem "rake-compiler"
8
- gem "minitest", "~> 5.0"
8
+ gem "minitest"
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Ruby](https://github.com/ko1/calleree/actions/workflows/ruby.yml/badge.svg)](https://github.com/ko1/calleree/actions/workflows/ruby.yml)
2
+
1
3
  # Calleree
2
4
 
3
5
  Calleree helps to analyze Ruby's caller-callee relationships.
@@ -35,7 +37,7 @@ Or install it yourself as:
35
37
  11
36
38
  12 Calleree.start
37
39
  13
38
- 14 foo
40
+ 14 bar
39
41
  15
40
42
  16 pp Calleree.result
41
43
  ```
@@ -43,18 +45,95 @@ Or install it yourself as:
43
45
  And this program shows:
44
46
 
45
47
  ```
46
- [[["test.rb", 14], ["test.rb", 5], 1],
47
- [["test.rb", 16], ["/mnt/c/ko1/src/rb/calleree/lib/calleree.rb", 24], 1]]
48
- ...
48
+ [[["test.rb", 14], ["test.rb", 9], 1],
49
+ [["test.rb", 9], ["test.rb", 5], 1],
50
+ [["test.rb", 16], ["/mnt/c/ko1/src/rb/calleree/lib/calleree.rb", 23], 1]]
51
+ ```
49
52
 
50
53
  The `Calleree.result` method returns an array of arrays which contains `[[caller_path, caller_line], [callee_path, callee_line], called_caount]`.
51
54
 
52
- In this case, `foo` at `["test.rb", 5]` is called at `["test.rb", 14]` only once.
55
+ In this case:
56
+
57
+ * A method `bar` at `["test.rb", 9]` is called from `["test.rb", 14]` only once.
58
+ * A method `foo` at `["test.rb", 5]` is called from `bar` at `["test.rb", 9]` once.
59
+ * A method `Calleree.result` at `["...calleree.rb", 23]` is called from `["test.rb", 16]`.
60
+
61
+ Additional usage:
53
62
 
54
- * You can stop the analisys with `Calleree.stop`. `Calleree.start` will continue the analysis.
55
63
  * You can use block with `Calleree.start do ... end`.
64
+ * You can stop the analisys with `Calleree.stop`. `Calleree.start` will continue the analysis.
56
65
  * You can clear the result if `Calleree.result(clear: true)` is passed.
57
66
 
67
+ ## Performance
68
+
69
+ The feature of this library can be implemented by Ruby (see `manual` method in the following code).
70
+ The difference is the performance.
71
+
72
+ ```ruby
73
+ require 'calleree'
74
+
75
+ def foo
76
+ :foo
77
+ end
78
+
79
+ def bar
80
+ foo
81
+ end
82
+
83
+ def demo
84
+ 100_000.times{
85
+ bar
86
+ }
87
+ end
88
+
89
+ def manual
90
+ result = Hash.new(0)
91
+ TracePoint.new(:call){
92
+ callee, caller = caller_locations(1, 2).map{|loc| [loc.path, loc.lineno]}
93
+ result[[caller, callee]] += 1
94
+ }.enable{
95
+ yield
96
+ }
97
+ result
98
+ end
99
+
100
+ require 'benchmark'
101
+
102
+ Benchmark.bm(10){|x|
103
+ x.report('none'){ demo }
104
+ x.report('manual'){ manual{ demo } }
105
+ x.report('calleree'){ Calleree.start{ demo } }
106
+ }
107
+ ```
108
+
109
+ The result of above benchmark is:
110
+
111
+ ```
112
+ user system total real
113
+ none 0.004860 0.000972 0.005832 ( 0.005828)
114
+ manual 0.864746 0.000000 0.864746 ( 0.864833) # about x175
115
+ calleree 0.031616 0.000000 0.031616 ( 0.031620) # about x6
116
+ ```
117
+
118
+ The called method `foo` and `bar` don't do anything so the above result shows the almost worst case.
119
+
120
+ For example if we introduce the allocation by changing the definition of `foo` with:
121
+
122
+ ```ruby
123
+ def foo
124
+ :foo.to_s
125
+ end
126
+ ```
127
+
128
+ The result becomes more moderated.
129
+
130
+ ```
131
+ user system total real
132
+ none 0.012348 0.001953 0.014301 ( 0.014328)
133
+ manual 0.858473 0.000650 0.859123 ( 0.859259) # about x61
134
+ calleree 0.038204 0.000000 0.038204 ( 0.038214) # about x2.7
135
+ ```
136
+
58
137
  ## Development
59
138
 
60
139
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -2,16 +2,19 @@
2
2
  #include "ruby/debug.h"
3
3
  #include "ruby/st.h"
4
4
 
5
+ #define USE_LOCINDEX 0 // Ruby 3.1 feature trial
6
+
5
7
  VALUE rb_mCalleree;
6
8
 
7
9
  struct eree_data {
8
10
  VALUE tp; // TracePoint.new(:call)
9
- VALUE files; // name -> [ {[fname, 1], ... } ]
10
- struct st_table* caller_callee; // [posnum | posnum] -> cnt
11
+ VALUE files; // name -> [ nil, posnum1, posnum,2 ... ]
12
+ struct st_table* caller_callee; // { (posnumX | posnumY) => cnt, ... }
11
13
  int last_posnum;
12
14
  } eree_data;
13
15
 
14
- static int
16
+ #if !USE_LOCINDEX
17
+ static unsigned int
15
18
  posnum(struct eree_data *data, VALUE path, int line)
16
19
  {
17
20
  VALUE lines, posnumv;
@@ -20,10 +23,13 @@ posnum(struct eree_data *data, VALUE path, int line)
20
23
  rb_hash_aset(data->files, path, lines = rb_ary_new());
21
24
  }
22
25
  if (NIL_P(posnumv = rb_ary_entry(lines, line))) {
23
- rb_ary_store(lines, line, posnumv = INT2FIX(data->last_posnum++));
26
+ int posnum = ++data->last_posnum;
27
+ if (RB_UNLIKELY(posnum == 0)) rb_raise(rb_eRuntimeError, "posnum overflow");
28
+ rb_ary_store(lines, line, posnumv = INT2FIX(posnum));
24
29
  }
25
- return FIX2INT(posnumv);
30
+ return (unsigned int)FIX2INT(posnumv);
26
31
  }
32
+ #endif
27
33
 
28
34
  static int
29
35
  increment_i(st_data_t *key, st_data_t *value, st_data_t arg, int existing)
@@ -33,26 +39,41 @@ increment_i(st_data_t *key, st_data_t *value, st_data_t arg, int existing)
33
39
  }
34
40
 
35
41
  static void
36
- erree_called(VALUE tpval, void *ptr)
42
+ eree_called(VALUE tpval, void *ptr)
37
43
  {
38
44
  struct eree_data *data = ptr;
45
+
46
+ #if !USE_LOCINDEX
39
47
  VALUE frames[2];
40
48
  int lines[2];
41
49
  // int start, int limit, VALUE *buff, int *lines);
42
- rb_profile_frames(1, 2, frames, lines);
43
- int callee_posnum = posnum(data, rb_profile_frame_path(frames[0]), lines[0]);
44
- int caller_posnum = posnum(data, rb_profile_frame_path(frames[1]), lines[1]);
45
- // rb_p(rb_profile_frame_path(frames[1]));
46
- st_data_t eree_pair = (((VALUE)caller_posnum << 32) | ((VALUE)callee_posnum));
50
+ int r = rb_profile_frames(1, 2, frames, lines);
51
+
52
+ if (r >= 2) {
53
+ unsigned int callee_posnum = posnum(data, rb_profile_frame_absolute_path(frames[0]), lines[0]);
54
+ unsigned int caller_posnum = posnum(data, rb_profile_frame_absolute_path(frames[1]), lines[1]);
47
55
 
48
- st_update(data->caller_callee, eree_pair, increment_i, 0);
56
+ st_data_t eree_pair = (((VALUE)caller_posnum << 32) | ((VALUE)callee_posnum));
57
+ st_update(data->caller_callee, eree_pair, increment_i, 0);
58
+ }
59
+ #else
60
+ unsigned int locs[2];
61
+ int r = rb_profile_locindex(0, 2, locs, 0);
62
+ if (r >= 2) {
63
+ unsigned int callee_posnum = locs[0];// posnum(data, rb_profile_frame_absolute_path (frames[0]), lines[0]);
64
+ unsigned int caller_posnum = locs[1];// posnum(data, rb_profile_frame_absolute_path (frames[1]), lines[1]);
65
+ st_data_t eree_pair = (((VALUE)caller_posnum << 32) | ((VALUE)callee_posnum));
66
+ st_update(data->caller_callee, eree_pair, increment_i, 0);
67
+ }
68
+ #endif
49
69
  }
50
70
 
51
71
  static VALUE
52
72
  eree_start(VALUE self)
53
73
  {
54
74
  if (!eree_data.tp) {
55
- eree_data.tp = rb_tracepoint_new(0, RUBY_EVENT_CALL, erree_called, &eree_data);
75
+ eree_data.tp = rb_tracepoint_new(0, RUBY_EVENT_CALL, eree_called, &eree_data);
76
+ rb_gc_register_mark_object(eree_data.tp);
56
77
  eree_data.caller_callee = st_init_numtable();
57
78
  }
58
79
  rb_tracepoint_enable(eree_data.tp);
@@ -72,9 +93,21 @@ static int
72
93
  raw_result_i(st_data_t key, st_data_t val, st_data_t data)
73
94
  {
74
95
  VALUE ary = (VALUE)data;
75
- int callee_posnum = (int)(key & 0xffffffff);
76
- int caller_posnum = (int)(key >> 32);
77
- rb_ary_push(ary, rb_ary_new_from_args(3, INT2FIX(caller_posnum), INT2FIX(callee_posnum), INT2FIX((int)val)));
96
+ unsigned int callee_posnum = (unsigned int)(key & 0xffffffff);
97
+ unsigned int caller_posnum = (unsigned int)(key >> 32);
98
+ #if !USE_LOCINDEX
99
+ rb_ary_push(ary, rb_ary_new_from_args(3, UINT2NUM(caller_posnum), UINT2NUM(callee_posnum), INT2FIX((int)val)));
100
+ #else
101
+ VALUE er_path, ee_path;
102
+ int er_line, ee_line;
103
+ rb_locindex_resolve(callee_posnum, &ee_path, &ee_line);
104
+ rb_locindex_resolve(caller_posnum, &er_path, &er_line);
105
+
106
+ rb_ary_push(ary, rb_ary_new_from_args(3,
107
+ rb_ary_new_from_args(2, er_path, INT2FIX(er_line)),
108
+ rb_ary_new_from_args(2, ee_path, INT2FIX(ee_line)),
109
+ INT2FIX((int)val)));
110
+ #endif
78
111
  return ST_CONTINUE;
79
112
  }
80
113
 
@@ -92,21 +125,27 @@ eree_raw_result(VALUE self, VALUE clear_p)
92
125
  return ary;
93
126
  }
94
127
 
128
+ #if !USE_LOCINDEX
95
129
  static VALUE
96
130
  eree_raw_posmap(VALUE self)
97
131
  {
98
132
  return eree_data.files;
99
133
  }
134
+ #endif
100
135
 
101
136
  void
102
137
  Init_calleree(void)
103
138
  {
104
139
  eree_data.files = rb_hash_new();
140
+ rb_funcall(eree_data.files, rb_intern("compare_by_identity"), 0);
105
141
  rb_gc_register_mark_object(eree_data.files);
106
142
 
107
143
  rb_mCalleree = rb_define_module("Calleree");
108
144
  rb_define_singleton_method(rb_mCalleree, "_start", eree_start, 0);
109
145
  rb_define_singleton_method(rb_mCalleree, "_stop", eree_stop, 0);
110
146
  rb_define_singleton_method(rb_mCalleree, "raw_result", eree_raw_result, 1);
147
+
148
+ #if !USE_LOCINDEX
111
149
  rb_define_singleton_method(rb_mCalleree, "raw_posmap", eree_raw_posmap, 0);
150
+ #endif
112
151
  }
@@ -1,3 +1,3 @@
1
1
  module Calleree
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/calleree.rb CHANGED
@@ -20,19 +20,24 @@ module Calleree
20
20
  end
21
21
 
22
22
  def self.result clear: false
23
- posnum_set = []
24
- Calleree.raw_posmap.each{|file, lines|
25
- lines.each_with_index{|posnum, line|
26
- posnum_set[posnum] = [file, line] if posnum
23
+ if respond_to? :raw_posmap
24
+ posnum_set = []
25
+ Calleree.raw_posmap.each{|file, lines|
26
+ lines.each_with_index{|posnum, line|
27
+ posnum_set[posnum] = [file, line] if posnum
28
+ }
27
29
  }
28
- }
29
- # pp posnum_set
30
- rs = []
31
- Calleree.raw_result(clear).each{|i, j, cnt|
32
- er = posnum_set[i]
33
- ee = posnum_set[j]
34
- rs << [er, ee, cnt] if er && ee
35
- }
36
- rs
30
+ # pp posnum_set
31
+ rs = []
32
+ Calleree.raw_result(clear).each{|i, j, cnt|
33
+ er = posnum_set[i]
34
+ ee = posnum_set[j]
35
+ rs << [er, ee, cnt] if er && ee
36
+ }
37
+ rs
38
+ else
39
+ # USE_LOCINDEX version
40
+ Calleree.raw_result(clear)
41
+ end
37
42
  end
38
43
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: calleree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koichi Sasada
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-17 00:00:00.000000000 Z
11
+ date: 2022-10-03 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Calleree helps to analyze Ruby's caller-callee relationships.
14
14
  email:
@@ -18,6 +18,7 @@ extensions:
18
18
  - ext/calleree/extconf.rb
19
19
  extra_rdoc_files: []
20
20
  files:
21
+ - ".github/workflows/ruby.yml"
21
22
  - ".gitignore"
22
23
  - ".travis.yml"
23
24
  - Gemfile
@@ -53,7 +54,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
53
54
  - !ruby/object:Gem::Version
54
55
  version: '0'
55
56
  requirements: []
56
- rubygems_version: 3.1.6
57
+ rubygems_version: 3.3.3
57
58
  signing_key:
58
59
  specification_version: 4
59
60
  summary: Calleree helps to analyze Ruby's caller-callee relationships.