majo 0.0.4 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ARCHITECTURE.md +25 -0
- data/CHANGELOG.md +13 -0
- data/README.md +59 -20
- data/ext/majo/allocation_info.c +12 -3
- data/ext/majo/allocation_info.h +3 -0
- data/ext/majo/attached_object.c +26 -0
- data/ext/majo/attached_object.h +13 -0
- data/ext/majo/majo.c +29 -14
- data/ext/majo/majo.h +2 -0
- data/ext/majo/result.c +57 -5
- data/ext/majo/result.h +4 -0
- data/lib/majo/formatter/color.rb +43 -19
- data/lib/majo/formatter/csv.rb +3 -3
- data/lib/majo/result.rb +1 -1
- data/lib/majo/version.rb +1 -1
- data/lib/majo.rb +10 -5
- metadata +11 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5db0056e26ba9974d00512d31930aad34d35af8e5ed9f880eda7665bdb9c2f59
|
4
|
+
data.tar.gz: 7489e069356907943bdf68de696cd3c9071aa4c2918743a5c05ba3b4ce286bf4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ab8e2fa890a01d0ac7a36c7be6e2adbe492fbd58319888636e6c733459a0f76dbf3d8aedc08cbe68d488f3a11ff54be758f661b0866afa31b2b4c6ce75abae0
|
7
|
+
data.tar.gz: 42ce9c56b603b4f50ff1504a47bf8f1616d384c4e135034be6b7f9d13c9afa6b5f278fde5683b2011ae9a3b024c9dc2f2b87da955b9dfdaad8fd44bb6fd08e1b
|
data/ARCHITECTURE.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# Architecture of Majo
|
2
|
+
|
3
|
+
Majo uses internal TracePoint hooks, `RUBY_INTERNAL_EVENT_NEWOBJ` and `RUBY_INTERNAL_EVENT_FREEOBJ`, to trace object allocation and free events.
|
4
|
+
We can use `ObjectSpace.trace_object_allocations_start` method in the Ruby-level, but this API is not suitable for Majo because it does not provide information about freed objects. Therefore, Majo uses these hooks in the C-level.
|
5
|
+
|
6
|
+
## On allocation event
|
7
|
+
|
8
|
+
When an object is allocated, Majo records the object into a st table with the path, line number, and so on.
|
9
|
+
|
10
|
+
See `newobj_i` function in `majo.c`.
|
11
|
+
|
12
|
+
## On free event
|
13
|
+
|
14
|
+
When an object is freed, Majo moves the allocation information to an array from the st table if it's a long-lived object.
|
15
|
+
It determines whether the object is long-lived by checking the GC count. If the object is not swept by a GC sweeping phase, it's a long-lived object.
|
16
|
+
|
17
|
+
See `freeobj_i` function in `majo.c`.
|
18
|
+
|
19
|
+
## Output format
|
20
|
+
|
21
|
+
Majo provides three output formats: `color`, `monochrome`, and `csv`.
|
22
|
+
|
23
|
+
`color` and `monochrome` are for human-readable output. They provide overview information about long-lived objects.
|
24
|
+
|
25
|
+
`csv` is for machine-readable output. It provides the raw information. You can use this format to analyze the result with other tools.
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# CHANGELOG
|
2
|
+
|
3
|
+
## v1.0.0 - 2024-07-26
|
4
|
+
|
5
|
+
The first stable release of Majo!
|
6
|
+
|
7
|
+
* Report retained objects [#32](https://github.com/pocke/majo/pull/32)
|
8
|
+
* Add options for upper and lower threshold of lifetime [#34](https://github.com/pocke/majo/pull/34)
|
9
|
+
* Display class path for singleton method call [#37](https://github.com/pocke/majo/pull/37)
|
10
|
+
|
11
|
+
## v0.1.0 - 2024-07-25
|
12
|
+
|
13
|
+
* Initial release
|
data/README.md
CHANGED
@@ -37,10 +37,10 @@ result.report
|
|
37
37
|
|
38
38
|
You can pass the following options to `report` method.
|
39
39
|
|
40
|
-
| Name | Description | Default
|
41
|
-
| ----------- | ------------------------ |
|
42
|
-
| `out` | Output file or IO | `$stdout`
|
43
|
-
| `formatter` | The format of the result | `:color`
|
40
|
+
| Name | Description | Default | Type |
|
41
|
+
| ----------- | ------------------------ | ------------------------ | ------------------------------------------------------------------------ |
|
42
|
+
| `out` | Output file or IO | `$stdout` | `IO`, `String` or an object having `to_path` method (such as `Pathname`) |
|
43
|
+
| `formatter` | The format of the result | `:color` or `monochrome` | `:color`, `:monochrome`, or `:csv` |
|
44
44
|
|
45
45
|
For example:
|
46
46
|
|
@@ -54,7 +54,7 @@ result.report(out: "majo-result.csv", formatter: :csv)
|
|
54
54
|
|
55
55
|
### Result Example
|
56
56
|
|
57
|
-
The result contains only long-lived objects, which are collected by the major GC.
|
57
|
+
The result contains only long-lived objects, which are collected by the major GC or retained.
|
58
58
|
|
59
59
|
The example is as follows:
|
60
60
|
|
@@ -102,15 +102,53 @@ Objects by class
|
|
102
102
|
22435 RBS::Location
|
103
103
|
11144 RBS::TypeName
|
104
104
|
(snip)
|
105
|
+
|
106
|
+
Retained Memory by file
|
107
|
+
-----------------------------------
|
108
|
+
7040 /path/to/gems/3.3.0/gems/rbs-3.5.1/lib/rbs/parser_aux.rb
|
109
|
+
256 /path/to/gems/3.3.0/gems/rbs-3.5.1/lib/rbs/resolver/type_name_resolver.rb
|
110
|
+
(snip)
|
111
|
+
|
112
|
+
Retained Memory by location
|
113
|
+
-----------------------------------
|
114
|
+
7040 /path/to/gems/3.3.0/gems/rbs-3.5.1/lib/rbs/parser_aux.rb:20
|
115
|
+
256 /path/to/gems/3.3.0/gems/rbs-3.5.1/lib/rbs/resolver/type_name_resolver.rb:22
|
116
|
+
(snip)
|
117
|
+
|
118
|
+
Retained Memory by class
|
119
|
+
-----------------------------------
|
120
|
+
6920 String
|
121
|
+
256 Hash
|
122
|
+
120 Symbol
|
123
|
+
(snip)
|
124
|
+
|
125
|
+
Retained Objects by file
|
126
|
+
-----------------------------------
|
127
|
+
160 /path/to/gems/3.3.0/gems/rbs-3.5.1/lib/rbs/parser_aux.rb
|
128
|
+
1 /path/to/gems/3.3.0/gems/rbs-3.5.1/lib/rbs/resolver/type_name_resolver.rb
|
129
|
+
(snip)
|
130
|
+
|
131
|
+
Retained Objects by location
|
132
|
+
-----------------------------------
|
133
|
+
160 /path/to/gems/3.3.0/gems/rbs-3.5.1/lib/rbs/parser_aux.rb:20
|
134
|
+
1 /path/to/gems/3.3.0/gems/rbs-3.5.1/lib/rbs/resolver/type_name_resolver.rb:22
|
135
|
+
(snip)
|
136
|
+
|
137
|
+
Retained Objects by class
|
138
|
+
-----------------------------------
|
139
|
+
157 String
|
140
|
+
3 Symbol
|
141
|
+
1 Hash
|
142
|
+
(snip)
|
105
143
|
```
|
106
144
|
|
107
145
|
The CSV format is as follows:
|
108
146
|
|
109
147
|
```csv
|
110
|
-
Object class path,Class path,Method ID,Path,Line,Alloc generation,Free generation,Memsize,Count
|
111
|
-
Hash
|
112
|
-
Hash,RBS::EnvironmentLoader,each_signature,/path/to/gems/rbs-3.5.1/lib/rbs/environment_loader.rb,159,20,22,160,1
|
113
|
-
Hash
|
148
|
+
Object class path,Class path,Method ID,Singleton method,Path,Line,Alloc generation,Free generation,Memsize,Count
|
149
|
+
Hash,RBS::Parser,_parse_signature,true,/path/to/gems/rbs-3.5.1/lib/rbs/parser_aux.rb,20,20,22,160,3
|
150
|
+
Hash,RBS::EnvironmentLoader,each_signature,false,/path/to/gems/rbs-3.5.1/lib/rbs/environment_loader.rb,159,20,22,160,1
|
151
|
+
Hash,RBS::Parser,_parse_signature,true,/path/to/gems/rbs-3.5.1/lib/rbs/parser_aux.rb,20,21,23,160,1
|
114
152
|
(snip)
|
115
153
|
```
|
116
154
|
|
@@ -118,17 +156,18 @@ You can find the raw data in the CSV format. It is useful for further analysis.
|
|
118
156
|
|
119
157
|
The columns are as follows:
|
120
158
|
|
121
|
-
| Column name | Description
|
122
|
-
| ------------------- |
|
123
|
-
| `Object class path` | The class name of the allocated object
|
124
|
-
| `Class path` | The class name of the receiver of the method allocating the object
|
125
|
-
| `Method ID` | The method name allocating the object
|
126
|
-
| `
|
127
|
-
| `
|
128
|
-
| `
|
129
|
-
| `
|
130
|
-
| `
|
131
|
-
| `
|
159
|
+
| Column name | Description |
|
160
|
+
| ------------------- | ----------------------------------------------------------------------------------- |
|
161
|
+
| `Object class path` | The class name of the allocated object |
|
162
|
+
| `Class path` | The class name of the receiver of the method allocating the object |
|
163
|
+
| `Method ID` | The method name allocating the object |
|
164
|
+
| `Singleton method` | `true` if the method call is for a singleton method |
|
165
|
+
| `Path` | The file path of the method allocating the object |
|
166
|
+
| `Line` | The line number of the method allocating the object |
|
167
|
+
| `Alloc generation` | The GC generation number when the object is allocated |
|
168
|
+
| `Free generation` | The GC generation number when the object is freed. It's empty for retained objects. |
|
169
|
+
| `Memsize` | The memory size of the object in bytes |
|
170
|
+
| `Count` | Number of objects allocated with the same conditions |
|
132
171
|
|
133
172
|
## Name
|
134
173
|
|
data/ext/majo/allocation_info.c
CHANGED
@@ -2,13 +2,12 @@
|
|
2
2
|
|
3
3
|
static void majo_allocation_info_mark(void *ptr)
|
4
4
|
{
|
5
|
-
// TODO
|
6
5
|
majo_allocation_info *info = (majo_allocation_info*)ptr;
|
6
|
+
rb_gc_mark(info->result);
|
7
7
|
}
|
8
8
|
|
9
9
|
static void majo_allocation_info_free(majo_allocation_info *info) {
|
10
|
-
|
11
|
-
ruby_xfree(info);
|
10
|
+
free(info);
|
12
11
|
}
|
13
12
|
|
14
13
|
static size_t majo_allocation_info_memsize(const void *ptr) {
|
@@ -85,6 +84,12 @@ allocation_info_method_id(VALUE self) {
|
|
85
84
|
return info->mid;
|
86
85
|
}
|
87
86
|
|
87
|
+
static VALUE
|
88
|
+
allocation_info_singleton_p(VALUE self) {
|
89
|
+
majo_allocation_info *info = majo_check_allocation_info(self);
|
90
|
+
return info->singleton_p ? Qtrue : Qfalse;
|
91
|
+
}
|
92
|
+
|
88
93
|
static VALUE
|
89
94
|
allocation_info_alloc_generation(VALUE self) {
|
90
95
|
majo_allocation_info *info = majo_check_allocation_info(self);
|
@@ -94,6 +99,9 @@ allocation_info_alloc_generation(VALUE self) {
|
|
94
99
|
static VALUE
|
95
100
|
allocation_info_free_generation(VALUE self) {
|
96
101
|
majo_allocation_info *info = majo_check_allocation_info(self);
|
102
|
+
if (info->free_generation == 0) {
|
103
|
+
return Qnil;
|
104
|
+
}
|
97
105
|
return SIZET2NUM(info->free_generation);
|
98
106
|
}
|
99
107
|
|
@@ -111,6 +119,7 @@ majo_init_allocation_info() {
|
|
111
119
|
rb_define_method(rb_cMajo_AllocationInfo, "path", allocation_info_path, 0);
|
112
120
|
rb_define_method(rb_cMajo_AllocationInfo, "class_path", allocation_info_class_path, 0);
|
113
121
|
rb_define_method(rb_cMajo_AllocationInfo, "method_id", allocation_info_method_id, 0);
|
122
|
+
rb_define_method(rb_cMajo_AllocationInfo, "singleton?", allocation_info_singleton_p, 0);
|
114
123
|
rb_define_method(rb_cMajo_AllocationInfo, "line", allocation_info_line, 0);
|
115
124
|
rb_define_method(rb_cMajo_AllocationInfo, "object_class_path", allocation_info_object_class_path, 0);
|
116
125
|
rb_define_method(rb_cMajo_AllocationInfo, "alloc_generation", allocation_info_alloc_generation, 0);
|
data/ext/majo/allocation_info.h
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
#include "majo.h"
|
2
|
+
|
3
|
+
#if RUBY_API_VERSION_MAJOR == 3 && RUBY_API_VERSION_MINOR == 1
|
4
|
+
|
5
|
+
static ID id_attached;
|
6
|
+
|
7
|
+
VALUE
|
8
|
+
majo_attached_object(VALUE klass) {
|
9
|
+
if (!FL_TEST(klass, FL_SINGLETON)) {
|
10
|
+
rb_raise(rb_eTypeError, "`%"PRIsVALUE"' is not a singleton class", klass);
|
11
|
+
}
|
12
|
+
|
13
|
+
return rb_attr_get(klass, id_attached);
|
14
|
+
}
|
15
|
+
|
16
|
+
void
|
17
|
+
majo_init_attached_object() {
|
18
|
+
id_attached = rb_intern_const("__attached__");
|
19
|
+
}
|
20
|
+
|
21
|
+
#else
|
22
|
+
|
23
|
+
void
|
24
|
+
majo_init_attached_object() {}
|
25
|
+
|
26
|
+
#endif
|
data/ext/majo/majo.c
CHANGED
@@ -36,10 +36,23 @@ internal_object_p(VALUE obj)
|
|
36
36
|
}
|
37
37
|
}
|
38
38
|
|
39
|
+
static const char*
|
40
|
+
to_class_path(VALUE klass, st_table *str_table)
|
41
|
+
{
|
42
|
+
if (FL_TEST(klass, FL_SINGLETON)) {
|
43
|
+
return to_class_path(majo_attached_object(klass), str_table);
|
44
|
+
}
|
45
|
+
|
46
|
+
VALUE class_path = (RTEST(klass) && !OBJ_FROZEN(klass)) ? rb_class_path_cached(klass) : Qnil;
|
47
|
+
const char *class_path_cstr = RTEST(class_path) ? majo_make_unique_str(str_table, RSTRING_PTR(class_path), RSTRING_LEN(class_path)) : 0;
|
48
|
+
return class_path_cstr;
|
49
|
+
}
|
50
|
+
|
39
51
|
static void
|
40
52
|
newobj_i(VALUE tpval, void *data)
|
41
53
|
{
|
42
|
-
|
54
|
+
VALUE res = (VALUE)data;
|
55
|
+
majo_result *arg = majo_check_result(res);
|
43
56
|
|
44
57
|
rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
|
45
58
|
VALUE obj = rb_tracearg_object(tparg);
|
@@ -53,22 +66,19 @@ newobj_i(VALUE tpval, void *data)
|
|
53
66
|
VALUE mid = rb_tracearg_method_id(tparg);
|
54
67
|
VALUE klass = rb_tracearg_defined_class(tparg);
|
55
68
|
|
56
|
-
// TODO: when the st already has an entry for the value
|
57
69
|
majo_allocation_info *info = (majo_allocation_info *)malloc(sizeof(majo_allocation_info));
|
58
70
|
|
59
71
|
const char *path_cstr = RTEST(path) ? majo_make_unique_str(arg->str_table, RSTRING_PTR(path), RSTRING_LEN(path)) : 0;
|
60
|
-
|
61
|
-
VALUE class_path = (RTEST(klass) && !OBJ_FROZEN(klass)) ? rb_class_path_cached(klass) : Qnil;
|
62
|
-
const char *class_path_cstr = RTEST(class_path) ? majo_make_unique_str(arg->str_table, RSTRING_PTR(class_path), RSTRING_LEN(class_path)) : 0;
|
72
|
+
const char *class_path_cstr = to_class_path(klass, arg->str_table);
|
63
73
|
|
64
74
|
VALUE obj_class = rb_obj_class(obj);
|
65
|
-
|
66
|
-
const char *obj_class_path_cstr = RTEST(obj_class_path) ? majo_make_unique_str(arg->str_table, RSTRING_PTR(obj_class_path), RSTRING_LEN(obj_class_path)) : 0;
|
67
|
-
|
75
|
+
const char *obj_class_path_cstr = to_class_path(obj_class, arg->str_table);
|
68
76
|
|
77
|
+
info->result = res;
|
69
78
|
info->path = path_cstr;
|
70
79
|
info->line = NUM2INT(line);
|
71
80
|
info->mid = mid;
|
81
|
+
info->singleton_p = FL_TEST(klass, FL_SINGLETON);
|
72
82
|
info->object_class_path = obj_class_path_cstr;
|
73
83
|
|
74
84
|
info->class_path = class_path_cstr;
|
@@ -84,12 +94,14 @@ freeobj_i(VALUE tpval, void *data)
|
|
84
94
|
st_data_t obj = (st_data_t)rb_tracearg_object(tparg);
|
85
95
|
st_data_t v;
|
86
96
|
|
87
|
-
// TODO refcount of the strings
|
88
97
|
if (st_delete(arg->object_table, &obj, &v)) {
|
89
98
|
majo_allocation_info *info = (majo_allocation_info *)v;
|
90
99
|
size_t gc_count = rb_gc_count();
|
91
|
-
|
92
|
-
if (
|
100
|
+
int lifetime = (int)(gc_count - info->alloc_generation - 1);
|
101
|
+
if (
|
102
|
+
(NIL_P(arg->upper_lifetime) || lifetime <= NUM2INT(arg->upper_lifetime)) &&
|
103
|
+
(NIL_P(arg->lower_lifetime) || NUM2INT(arg->lower_lifetime) <= lifetime)
|
104
|
+
) {
|
93
105
|
info->memsize = rb_obj_memsize_of((VALUE)obj);
|
94
106
|
info->free_generation = gc_count;
|
95
107
|
|
@@ -103,14 +115,16 @@ freeobj_i(VALUE tpval, void *data)
|
|
103
115
|
}
|
104
116
|
|
105
117
|
static VALUE
|
106
|
-
start(VALUE self) {
|
118
|
+
start(VALUE self, VALUE upper_lifetime, VALUE lower_lifetime) {
|
107
119
|
VALUE res = majo_new_result();
|
108
120
|
majo_result *arg = majo_check_result(res);
|
121
|
+
arg->upper_lifetime = upper_lifetime;
|
122
|
+
arg->lower_lifetime = lower_lifetime;
|
109
123
|
|
110
124
|
VALUE stack = rb_ivar_get(rb_mMajo, running_tracer_stack);
|
111
125
|
rb_ary_push(stack, res);
|
112
126
|
|
113
|
-
arg->newobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i,
|
127
|
+
arg->newobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, (void *)res);
|
114
128
|
arg->freeobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, arg);
|
115
129
|
|
116
130
|
rb_tracepoint_enable(arg->newobj_trace);
|
@@ -137,7 +151,7 @@ Init_majo(void)
|
|
137
151
|
{
|
138
152
|
rb_mMajo = rb_define_module("Majo");
|
139
153
|
|
140
|
-
rb_define_module_function(rb_mMajo, "__start", start,
|
154
|
+
rb_define_module_function(rb_mMajo, "__start", start, 2);
|
141
155
|
rb_define_module_function(rb_mMajo, "__stop", stop, 0);
|
142
156
|
|
143
157
|
running_tracer_stack = rb_intern("running_tracer_stack");
|
@@ -145,4 +159,5 @@ Init_majo(void)
|
|
145
159
|
|
146
160
|
majo_init_result();
|
147
161
|
majo_init_allocation_info();
|
162
|
+
majo_init_attached_object();
|
148
163
|
}
|
data/ext/majo/majo.h
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
#include <stdbool.h>
|
5
5
|
|
6
6
|
#include "ruby.h"
|
7
|
+
#include "ruby/version.h"
|
7
8
|
#include "ruby/debug.h"
|
8
9
|
#include "ruby/internal/gc.h"
|
9
10
|
|
@@ -12,6 +13,7 @@
|
|
12
13
|
#include "allocation_info.h"
|
13
14
|
#include "result.h"
|
14
15
|
#include "unique_str.h"
|
16
|
+
#include "attached_object.h"
|
15
17
|
|
16
18
|
extern VALUE rb_mMajo;
|
17
19
|
extern VALUE rb_cMajo_Result;
|
data/ext/majo/result.c
CHANGED
@@ -5,16 +5,40 @@ static void majo_result_mark(void *ptr)
|
|
5
5
|
majo_result *arg = (majo_result*)ptr;
|
6
6
|
rb_gc_mark(arg->newobj_trace);
|
7
7
|
rb_gc_mark(arg->freeobj_trace);
|
8
|
+
rb_gc_mark(arg->retained);
|
9
|
+
rb_gc_mark(arg->upper_lifetime);
|
10
|
+
rb_gc_mark(arg->lower_lifetime);
|
11
|
+
}
|
12
|
+
|
13
|
+
static int
|
14
|
+
free_st_key(st_data_t key, st_data_t value, st_data_t data)
|
15
|
+
{
|
16
|
+
free((void *)key);
|
17
|
+
return ST_CONTINUE;
|
18
|
+
}
|
19
|
+
|
20
|
+
static int
|
21
|
+
free_st_value(st_data_t key, st_data_t value, st_data_t data)
|
22
|
+
{
|
23
|
+
free((void *)value);
|
24
|
+
return ST_CONTINUE;
|
8
25
|
}
|
9
26
|
|
10
27
|
static void majo_result_free(majo_result *arg) {
|
11
|
-
|
12
|
-
|
28
|
+
st_foreach(arg->object_table, free_st_value, 0);
|
29
|
+
st_free_table(arg->object_table);
|
30
|
+
|
31
|
+
st_foreach(arg->str_table, free_st_key, 0);
|
32
|
+
st_free_table(arg->str_table);
|
33
|
+
|
34
|
+
rb_darray_free(arg->olds);
|
35
|
+
|
36
|
+
free(arg);
|
13
37
|
}
|
14
38
|
|
15
39
|
static size_t majo_result_memsize(const void *ptr) {
|
16
|
-
|
17
|
-
return sizeof(majo_result);
|
40
|
+
majo_result *res = (majo_result*)ptr;
|
41
|
+
return sizeof(majo_result) + st_memsize(res->object_table) + st_memsize(res->str_table) + rb_darray_capa(res->olds) * sizeof(majo_allocation_info);
|
18
42
|
}
|
19
43
|
|
20
44
|
static rb_data_type_t result_type = {
|
@@ -29,6 +53,7 @@ static VALUE result_alloc(VALUE klass) {
|
|
29
53
|
arg->object_table = st_init_numtable();
|
30
54
|
arg->str_table = st_init_strtable();
|
31
55
|
arg->olds = NULL;
|
56
|
+
arg->retained = rb_ary_new();
|
32
57
|
|
33
58
|
return obj;
|
34
59
|
}
|
@@ -48,7 +73,7 @@ majo_result_append_info(majo_result *res, majo_allocation_info info) {
|
|
48
73
|
rb_darray_append(&res->olds, info);
|
49
74
|
}
|
50
75
|
|
51
|
-
VALUE
|
76
|
+
static VALUE
|
52
77
|
majo_result_allocations(VALUE self) {
|
53
78
|
majo_result *res = majo_check_result(self);
|
54
79
|
VALUE ary = rb_ary_new_capa(rb_darray_size(res->olds));
|
@@ -62,10 +87,37 @@ majo_result_allocations(VALUE self) {
|
|
62
87
|
return ary;
|
63
88
|
}
|
64
89
|
|
90
|
+
static VALUE
|
91
|
+
majo_result_retained(VALUE self) {
|
92
|
+
majo_result *res = majo_check_result(self);
|
93
|
+
return res->retained;
|
94
|
+
}
|
95
|
+
|
96
|
+
static VALUE
|
97
|
+
majo_result_store_retained_object(VALUE self, VALUE obj) {
|
98
|
+
majo_result *res = majo_check_result(self);
|
99
|
+
|
100
|
+
st_data_t value;
|
101
|
+
if (st_lookup(res->object_table, (st_data_t)obj, &value)) {
|
102
|
+
majo_allocation_info *info = (majo_allocation_info *)value;
|
103
|
+
int lifetime = (int)(rb_gc_count() - info->alloc_generation - 1);
|
104
|
+
if (NIL_P(res->upper_lifetime) || lifetime <= NUM2INT(res->upper_lifetime)) {
|
105
|
+
info->memsize = rb_obj_memsize_of(obj);
|
106
|
+
|
107
|
+
VALUE info_r = majo_new_allocation_info(info);
|
108
|
+
rb_ary_push(res->retained, info_r);
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
return Qnil;
|
113
|
+
}
|
114
|
+
|
65
115
|
void
|
66
116
|
majo_init_result() {
|
67
117
|
rb_cMajo_Result = rb_define_class_under(rb_mMajo, "Result", rb_cObject);
|
68
118
|
rb_define_alloc_func(rb_cMajo_Result, result_alloc);
|
69
119
|
|
70
120
|
rb_define_method(rb_cMajo_Result, "allocations", majo_result_allocations, 0);
|
121
|
+
rb_define_method(rb_cMajo_Result, "retained", majo_result_retained, 0);
|
122
|
+
rb_define_method(rb_cMajo_Result, "store_retained_object", majo_result_store_retained_object, 1);
|
71
123
|
}
|
data/ext/majo/result.h
CHANGED
data/lib/majo/formatter/color.rb
CHANGED
@@ -8,32 +8,60 @@ module Majo
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def call
|
11
|
+
allocs = result.allocations
|
12
|
+
retained = result.retained
|
13
|
+
|
11
14
|
<<~RESULT
|
12
|
-
Total #{total_memory} bytes (#{total_objects} objects)
|
15
|
+
Total #{total_memory(allocs)} bytes (#{total_objects(allocs)} objects)
|
16
|
+
Total retained #{total_memory(retained)} bytes (#{total_objects(retained)} objects)
|
13
17
|
|
14
18
|
Memory by file
|
15
19
|
#{bar}
|
16
|
-
#{format_two_columns(memory_by_file)}
|
20
|
+
#{format_two_columns(memory_by_file(allocs))}
|
17
21
|
|
18
22
|
Memory by location
|
19
23
|
#{bar}
|
20
|
-
#{format_two_columns(memory_by_location)}
|
24
|
+
#{format_two_columns(memory_by_location(allocs))}
|
21
25
|
|
22
26
|
Memory by class
|
23
27
|
#{bar}
|
24
|
-
#{format_two_columns(memory_by_class)}
|
28
|
+
#{format_two_columns(memory_by_class(allocs))}
|
25
29
|
|
26
30
|
Objects by file
|
27
31
|
#{bar}
|
28
|
-
#{format_two_columns(objects_by_file)}
|
32
|
+
#{format_two_columns(objects_by_file(allocs))}
|
29
33
|
|
30
34
|
Objects by location
|
31
35
|
#{bar}
|
32
|
-
#{format_two_columns(objects_by_location)}
|
36
|
+
#{format_two_columns(objects_by_location(allocs))}
|
33
37
|
|
34
38
|
Objects by class
|
35
39
|
#{bar}
|
36
|
-
#{format_two_columns(objects_by_class)}
|
40
|
+
#{format_two_columns(objects_by_class(allocs))}
|
41
|
+
|
42
|
+
Retained Memory by file
|
43
|
+
#{bar}
|
44
|
+
#{format_two_columns(memory_by_file(retained))}
|
45
|
+
|
46
|
+
Retained Memory by location
|
47
|
+
#{bar}
|
48
|
+
#{format_two_columns(memory_by_location(retained))}
|
49
|
+
|
50
|
+
Retained Memory by class
|
51
|
+
#{bar}
|
52
|
+
#{format_two_columns(memory_by_class(retained))}
|
53
|
+
|
54
|
+
Retained Objects by file
|
55
|
+
#{bar}
|
56
|
+
#{format_two_columns(objects_by_file(retained))}
|
57
|
+
|
58
|
+
Retained Objects by location
|
59
|
+
#{bar}
|
60
|
+
#{format_two_columns(objects_by_location(retained))}
|
61
|
+
|
62
|
+
Retained Objects by class
|
63
|
+
#{bar}
|
64
|
+
#{format_two_columns(objects_by_class(retained))}
|
37
65
|
RESULT
|
38
66
|
end
|
39
67
|
|
@@ -45,45 +73,45 @@ module Majo
|
|
45
73
|
cyan '-----------------------------------'
|
46
74
|
end
|
47
75
|
|
48
|
-
def total_objects
|
76
|
+
def total_objects(allocs)
|
49
77
|
allocs.size
|
50
78
|
end
|
51
79
|
|
52
|
-
def total_memory
|
80
|
+
def total_memory(allocs)
|
53
81
|
allocs.sum(&:memsize)
|
54
82
|
end
|
55
83
|
|
56
|
-
def memory_by_file
|
84
|
+
def memory_by_file(allocs)
|
57
85
|
allocs.group_by(&:path).map do |path, allocations|
|
58
86
|
[allocations.sum(&:memsize), path]
|
59
87
|
end.sort_by(&:first).reverse
|
60
88
|
end
|
61
89
|
|
62
|
-
def memory_by_location
|
90
|
+
def memory_by_location(allocs)
|
63
91
|
allocs.group_by { |a| "#{a.path}:#{a.line}" }.map do |location, allocations|
|
64
92
|
[allocations.sum(&:memsize), location]
|
65
93
|
end.sort_by(&:first).reverse
|
66
94
|
end
|
67
95
|
|
68
|
-
def memory_by_class
|
96
|
+
def memory_by_class(allocs)
|
69
97
|
allocs.group_by(&:object_class_path).map do |class_path, allocations|
|
70
98
|
[allocations.sum(&:memsize), class_path]
|
71
99
|
end.sort_by(&:first).reverse
|
72
100
|
end
|
73
101
|
|
74
|
-
def objects_by_file
|
102
|
+
def objects_by_file(allocs)
|
75
103
|
allocs.group_by(&:path).map do |path, allocations|
|
76
104
|
[allocations.size, path]
|
77
105
|
end.sort_by(&:first).reverse
|
78
106
|
end
|
79
107
|
|
80
|
-
def objects_by_location
|
108
|
+
def objects_by_location(allocs)
|
81
109
|
allocs.group_by { |a| "#{a.path}:#{a.line}" }.map do |location, allocations|
|
82
110
|
[allocations.size, location]
|
83
111
|
end.sort_by(&:first).reverse
|
84
112
|
end
|
85
113
|
|
86
|
-
def objects_by_class
|
114
|
+
def objects_by_class(allocs)
|
87
115
|
allocs.group_by(&:object_class_path).map do |class_path, allocations|
|
88
116
|
[allocations.size, class_path]
|
89
117
|
end.sort_by(&:first).reverse
|
@@ -95,10 +123,6 @@ module Majo
|
|
95
123
|
max_length = data.max_by { |row| row[0].to_s.size }[0].to_s.size
|
96
124
|
data.map { |row| "#{blue(row[0].to_s.rjust(max_length))} #{row[1]}" }.join("\n")
|
97
125
|
end
|
98
|
-
|
99
|
-
def allocs
|
100
|
-
@allocs ||= result.allocations
|
101
|
-
end
|
102
126
|
end
|
103
127
|
end
|
104
128
|
end
|
data/lib/majo/formatter/csv.rb
CHANGED
@@ -9,15 +9,15 @@ module Majo
|
|
9
9
|
|
10
10
|
def call
|
11
11
|
::CSV.generate do |csv|
|
12
|
-
csv << ['Object class path', 'Class path', 'Method ID', 'Path', 'Line', 'Alloc generation', 'Free generation', 'Memsize', "Count"]
|
12
|
+
csv << ['Object class path', 'Class path', 'Method ID', 'Singleton method', 'Path', 'Line', 'Alloc generation', 'Free generation', 'Memsize', "Count"]
|
13
13
|
groups.each do |row, count|
|
14
|
-
csv << [row.object_class_path, row.class_path, row.method_id, row.path, row.line, row.alloc_generation, row.free_generation, row.memsize, count]
|
14
|
+
csv << [row.object_class_path, row.class_path, row.method_id, row.singleton?, row.path, row.line, row.alloc_generation, row.free_generation, row.memsize, count]
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
def groups
|
20
|
-
|
20
|
+
[*@result.allocations, *@result.retained].tally
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
data/lib/majo/result.rb
CHANGED
data/lib/majo/version.rb
CHANGED
data/lib/majo.rb
CHANGED
@@ -10,22 +10,27 @@ require_relative 'majo/formatter'
|
|
10
10
|
module Majo
|
11
11
|
class Error < StandardError; end
|
12
12
|
|
13
|
-
def self.start
|
13
|
+
def self.start(upper_lifetime: nil, lower_lifetime: 1)
|
14
14
|
GC.start
|
15
15
|
GC.start
|
16
16
|
GC.start
|
17
|
-
__start
|
17
|
+
__start(upper_lifetime, lower_lifetime)
|
18
18
|
end
|
19
19
|
|
20
20
|
def self.stop
|
21
21
|
GC.start
|
22
22
|
GC.start
|
23
23
|
GC.start
|
24
|
-
__stop
|
24
|
+
res = __stop
|
25
|
+
|
26
|
+
# Collect retained objcects
|
27
|
+
ObjectSpace.each_object do |obj|
|
28
|
+
res.store_retained_object(obj)
|
29
|
+
end
|
25
30
|
end
|
26
31
|
|
27
|
-
def self.run
|
28
|
-
r = start
|
32
|
+
def self.run(upper_lifetime: nil, lower_lifetime: 1)
|
33
|
+
r = start(upper_lifetime:, lower_lifetime:)
|
29
34
|
yield
|
30
35
|
r
|
31
36
|
ensure
|
metadata
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: majo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Masataka Pocke Kuwabara
|
8
|
+
autorequire:
|
8
9
|
bindir: exe
|
9
10
|
cert_chain: []
|
10
|
-
date: 2024-07-
|
11
|
+
date: 2024-07-26 00:00:00.000000000 Z
|
11
12
|
dependencies: []
|
12
13
|
description: A memory profiler focusing on long-lived objects.
|
13
14
|
email:
|
@@ -19,11 +20,15 @@ extra_rdoc_files: []
|
|
19
20
|
files:
|
20
21
|
- ".rspec"
|
21
22
|
- ".rubocop.yml"
|
23
|
+
- ARCHITECTURE.md
|
24
|
+
- CHANGELOG.md
|
22
25
|
- LICENSE
|
23
26
|
- README.md
|
24
27
|
- Rakefile
|
25
28
|
- ext/majo/allocation_info.c
|
26
29
|
- ext/majo/allocation_info.h
|
30
|
+
- ext/majo/attached_object.c
|
31
|
+
- ext/majo/attached_object.h
|
27
32
|
- ext/majo/darray.h
|
28
33
|
- ext/majo/extconf.rb
|
29
34
|
- ext/majo/majo.c
|
@@ -48,6 +53,8 @@ licenses:
|
|
48
53
|
metadata:
|
49
54
|
homepage_uri: https://github.com/pocke/majo
|
50
55
|
source_code_uri: https://github.com/pocke/majo
|
56
|
+
changelog_uri: https://github.com/pocke/majo/blob/master/CHANGELOG.md
|
57
|
+
post_install_message:
|
51
58
|
rdoc_options: []
|
52
59
|
require_paths:
|
53
60
|
- lib
|
@@ -62,7 +69,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
62
69
|
- !ruby/object:Gem::Version
|
63
70
|
version: '0'
|
64
71
|
requirements: []
|
65
|
-
rubygems_version: 3.
|
72
|
+
rubygems_version: 3.5.11
|
73
|
+
signing_key:
|
66
74
|
specification_version: 4
|
67
75
|
summary: A memory profiler focusing on long-lived objects.
|
68
76
|
test_files: []
|