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 +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +55 -16
- data/ext/majo/allocation_info.c +10 -0
- data/ext/majo/allocation_info.h +1 -0
- data/ext/majo/attached_object.c +26 -0
- data/ext/majo/attached_object.h +13 -0
- data/ext/majo/majo.c +25 -9
- data/ext/majo/majo.h +2 -0
- data/ext/majo/result.c +32 -1
- 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 +6 -2
    
        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/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 | 
| 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
    
    | @@ -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);
         | 
    
        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,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 | 
            -
               | 
| 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 | 
            -
                 | 
| 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,  | 
| 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
    
    
    
        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,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: majo
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version:  | 
| 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- | 
| 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:
         |