majo 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: