majo 0.1.0 → 1.0.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: 84aa655c3585c95f2e639690246b2946114208ce08b979d00d46e6a93efef60c
4
- data.tar.gz: 0073efd2fd643f97219ca17be43259960cfb514ab864cbb6f0b0f9378d3f7a9f
3
+ metadata.gz: 5db0056e26ba9974d00512d31930aad34d35af8e5ed9f880eda7665bdb9c2f59
4
+ data.tar.gz: 7489e069356907943bdf68de696cd3c9071aa4c2918743a5c05ba3b4ce286bf4
5
5
  SHA512:
6
- metadata.gz: 7d0cb81a092b6c0db17c5db53d2a2404fc554c1545bf8ab7b6546679ba7bd9a64e603af30e2fe7d1d5f90fe0848aebba490918d5101a179b6ac309bf21e3b878
7
- data.tar.gz: 630052b3e6c3558c7ce2628a17528518e3770188fca26b26ff1f51a69c6d3d14e339b03814a93173ed134e6565d4e6f1aa1a07249961a70360c8903949943889
6
+ metadata.gz: 3ab8e2fa890a01d0ac7a36c7be6e2adbe492fbd58319888636e6c733459a0f76dbf3d8aedc08cbe68d488f3a11ff54be758f661b0866afa31b2b4c6ce75abae0
7
+ data.tar.gz: 42ce9c56b603b4f50ff1504a47bf8f1616d384c4e135034be6b7f9d13c9afa6b5f278fde5683b2011ae9a3b024c9dc2f2b87da955b9dfdaad8fd44bb6fd08e1b
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
@@ -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,,_parse_signature,/path/to/gems/rbs-3.5.1/lib/rbs/parser_aux.rb,20,20,22,160,3
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,,_parse_signature,/path/to/gems/rbs-3.5.1/lib/rbs/parser_aux.rb,20,21,23,160,1
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
- | `Path` | The file path of the method allocating the object |
127
- | `Line` | The line number of the method allocating the object |
128
- | `Alloc generation` | The GC generation number when the object is allocated |
129
- | `Free generation` | The GC generation number when the object is freed |
130
- | `Memsize` | The memory size of the object in bytes |
131
- | `Count` | Number of objects allocated with the same conditions |
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
 
@@ -84,6 +84,12 @@ allocation_info_method_id(VALUE self) {
84
84
  return info->mid;
85
85
  }
86
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
+
87
93
  static VALUE
88
94
  allocation_info_alloc_generation(VALUE self) {
89
95
  majo_allocation_info *info = majo_check_allocation_info(self);
@@ -93,6 +99,9 @@ allocation_info_alloc_generation(VALUE self) {
93
99
  static VALUE
94
100
  allocation_info_free_generation(VALUE self) {
95
101
  majo_allocation_info *info = majo_check_allocation_info(self);
102
+ if (info->free_generation == 0) {
103
+ return Qnil;
104
+ }
96
105
  return SIZET2NUM(info->free_generation);
97
106
  }
98
107
 
@@ -110,6 +119,7 @@ majo_init_allocation_info() {
110
119
  rb_define_method(rb_cMajo_AllocationInfo, "path", allocation_info_path, 0);
111
120
  rb_define_method(rb_cMajo_AllocationInfo, "class_path", allocation_info_class_path, 0);
112
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);
113
123
  rb_define_method(rb_cMajo_AllocationInfo, "line", allocation_info_line, 0);
114
124
  rb_define_method(rb_cMajo_AllocationInfo, "object_class_path", allocation_info_object_class_path, 0);
115
125
  rb_define_method(rb_cMajo_AllocationInfo, "alloc_generation", allocation_info_alloc_generation, 0);
@@ -7,6 +7,7 @@ typedef struct {
7
7
  const char *object_class_path;
8
8
  unsigned long line;
9
9
  VALUE mid;
10
+ bool singleton_p;
10
11
 
11
12
  size_t alloc_generation;
12
13
  size_t free_generation;
@@ -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
@@ -0,0 +1,13 @@
1
+ #if RUBY_API_VERSION_MAJOR == 3 && RUBY_API_VERSION_MINOR == 1
2
+
3
+ VALUE
4
+ majo_attached_object(VALUE klass);
5
+
6
+ #else
7
+
8
+ #define majo_attached_object(klass) rb_class_attached_object(klass)
9
+
10
+ #endif
11
+
12
+ void
13
+ majo_init_attached_object();
data/ext/majo/majo.c CHANGED
@@ -36,6 +36,18 @@ 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
  {
@@ -57,19 +69,16 @@ newobj_i(VALUE tpval, void *data)
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
- VALUE obj_class_path = (RTEST(obj_class) && !OBJ_FROZEN(obj_class)) ? rb_class_path_cached(obj_class) : Qnil;
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
 
69
77
  info->result = res;
70
78
  info->path = path_cstr;
71
79
  info->line = NUM2INT(line);
72
80
  info->mid = mid;
81
+ info->singleton_p = FL_TEST(klass, FL_SINGLETON);
73
82
  info->object_class_path = obj_class_path_cstr;
74
83
 
75
84
  info->class_path = class_path_cstr;
@@ -88,7 +97,11 @@ freeobj_i(VALUE tpval, void *data)
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
- if (info->alloc_generation < gc_count-1) {
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
+ ) {
92
105
  info->memsize = rb_obj_memsize_of((VALUE)obj);
93
106
  info->free_generation = gc_count;
94
107
 
@@ -102,9 +115,11 @@ freeobj_i(VALUE tpval, void *data)
102
115
  }
103
116
 
104
117
  static VALUE
105
- start(VALUE self) {
118
+ start(VALUE self, VALUE upper_lifetime, VALUE lower_lifetime) {
106
119
  VALUE res = majo_new_result();
107
120
  majo_result *arg = majo_check_result(res);
121
+ arg->upper_lifetime = upper_lifetime;
122
+ arg->lower_lifetime = lower_lifetime;
108
123
 
109
124
  VALUE stack = rb_ivar_get(rb_mMajo, running_tracer_stack);
110
125
  rb_ary_push(stack, res);
@@ -136,7 +151,7 @@ Init_majo(void)
136
151
  {
137
152
  rb_mMajo = rb_define_module("Majo");
138
153
 
139
- rb_define_module_function(rb_mMajo, "__start", start, 0);
154
+ rb_define_module_function(rb_mMajo, "__start", start, 2);
140
155
  rb_define_module_function(rb_mMajo, "__stop", stop, 0);
141
156
 
142
157
  running_tracer_stack = rb_intern("running_tracer_stack");
@@ -144,4 +159,5 @@ Init_majo(void)
144
159
 
145
160
  majo_init_result();
146
161
  majo_init_allocation_info();
162
+ majo_init_attached_object();
147
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,6 +5,9 @@ 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);
8
11
  }
9
12
 
10
13
  static int
@@ -50,6 +53,7 @@ static VALUE result_alloc(VALUE klass) {
50
53
  arg->object_table = st_init_numtable();
51
54
  arg->str_table = st_init_strtable();
52
55
  arg->olds = NULL;
56
+ arg->retained = rb_ary_new();
53
57
 
54
58
  return obj;
55
59
  }
@@ -69,7 +73,7 @@ majo_result_append_info(majo_result *res, majo_allocation_info info) {
69
73
  rb_darray_append(&res->olds, info);
70
74
  }
71
75
 
72
- VALUE
76
+ static VALUE
73
77
  majo_result_allocations(VALUE self) {
74
78
  majo_result *res = majo_check_result(self);
75
79
  VALUE ary = rb_ary_new_capa(rb_darray_size(res->olds));
@@ -83,10 +87,37 @@ majo_result_allocations(VALUE self) {
83
87
  return ary;
84
88
  }
85
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
+
86
115
  void
87
116
  majo_init_result() {
88
117
  rb_cMajo_Result = rb_define_class_under(rb_mMajo, "Result", rb_cObject);
89
118
  rb_define_alloc_func(rb_cMajo_Result, result_alloc);
90
119
 
91
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);
92
123
  }
data/ext/majo/result.h CHANGED
@@ -7,6 +7,10 @@ typedef struct {
7
7
  rb_darray(majo_allocation_info) olds;
8
8
  VALUE newobj_trace;
9
9
  VALUE freeobj_trace;
10
+ VALUE retained;
11
+
12
+ VALUE upper_lifetime;
13
+ VALUE lower_lifetime;
10
14
  } majo_result;
11
15
 
12
16
  VALUE
@@ -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
@@ -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
- @result.allocations.tally
20
+ [*@result.allocations, *@result.retained].tally
21
21
  end
22
22
  end
23
23
  end
data/lib/majo/result.rb CHANGED
@@ -28,7 +28,7 @@ module Majo
28
28
 
29
29
  def with_output(out)
30
30
  case
31
- when out.is_a?(IO)
31
+ when out.is_a?(IO) || (defined?(StringIO) && out.is_a?(StringIO))
32
32
  yield out
33
33
  when out.is_a?(String)
34
34
  File.open(out, "w") { |io| yield io }
data/lib/majo/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Majo
4
- VERSION = "0.1.0"
4
+ VERSION = "1.0.0"
5
5
  end
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,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: majo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masataka Pocke Kuwabara
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-07-25 00:00:00.000000000 Z
11
+ date: 2024-07-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A memory profiler focusing on long-lived objects.
14
14
  email:
@@ -21,11 +21,14 @@ files:
21
21
  - ".rspec"
22
22
  - ".rubocop.yml"
23
23
  - ARCHITECTURE.md
24
+ - CHANGELOG.md
24
25
  - LICENSE
25
26
  - README.md
26
27
  - Rakefile
27
28
  - ext/majo/allocation_info.c
28
29
  - ext/majo/allocation_info.h
30
+ - ext/majo/attached_object.c
31
+ - ext/majo/attached_object.h
29
32
  - ext/majo/darray.h
30
33
  - ext/majo/extconf.rb
31
34
  - ext/majo/majo.c
@@ -50,6 +53,7 @@ licenses:
50
53
  metadata:
51
54
  homepage_uri: https://github.com/pocke/majo
52
55
  source_code_uri: https://github.com/pocke/majo
56
+ changelog_uri: https://github.com/pocke/majo/blob/master/CHANGELOG.md
53
57
  post_install_message:
54
58
  rdoc_options: []
55
59
  require_paths: