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 +4 -4
- data/.github/workflows/ruby.yml +34 -0
- data/Gemfile +2 -2
- data/README.md +85 -6
- data/ext/calleree/calleree.c +55 -16
- data/lib/calleree/version.rb +1 -1
- data/lib/calleree.rb +18 -13
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: efe0a24448e4190f50b98f29915cce53ab119f58d47794ef096389c36283b9a2
|
4
|
+
data.tar.gz: a17ca6a2f922d7bed7930bca47e52aa8ca32556c66d3f41e4a69472ae11cb452
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
[](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
|
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",
|
47
|
-
[["test.rb",
|
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
|
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.
|
data/ext/calleree/calleree.c
CHANGED
@@ -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 -> [
|
10
|
-
struct st_table* caller_callee; //
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
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,
|
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
|
-
|
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
|
}
|
data/lib/calleree/version.rb
CHANGED
data/lib/calleree.rb
CHANGED
@@ -20,19 +20,24 @@ module Calleree
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.result clear: false
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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.
|
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:
|
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.
|
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.
|