majo 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 30b12103910ff82b8073d3882debca86928bc05d03e318b8c640e0c3b6f4c6ee
4
+ data.tar.gz: 851e43d87d7ba4020b5eb04487ae9859522bba5bd7f7a6765d02432efe2b8d74
5
+ SHA512:
6
+ metadata.gz: ee86c8d4f321e54646ec82ee79168a8071ff641be47f9fd006ab70c9f254fbd958e6667c1e9dde36b00e911eee17d970a09a785dd53912f9bc4383101e649957
7
+ data.tar.gz: 3b707eee86c906409bd26f9b15e0e35aa1e6516e566b7d0f7e526b4fc76d74478110f75aa77b23041133d8b966999882c46d60515617121d7eec9cd41d9c3ac1
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,11 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+
4
+ Style:
5
+ Enabled: false
6
+
7
+ Layout/LineLength:
8
+ Enabled: false
9
+
10
+ Metrics:
11
+ Enabled: false
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2024, Masataka Pocke Kuwabara
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Majo
2
+
3
+ TODO: Delete this and the text below, and describe your gem
4
+
5
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/majo`. To experiment with that code, run `bin/console` for an interactive prompt.
6
+
7
+ ## Installation
8
+
9
+ TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
+
11
+ Install the gem and add to the application's Gemfile by executing:
12
+
13
+ $ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
14
+
15
+ If bundler is not being used to manage dependencies, install the gem by executing:
16
+
17
+ $ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Development
24
+
25
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
26
+
27
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
28
+
29
+ ## Contributing
30
+
31
+ Bug reports and pull requests are welcome on GitHub at https://github.com/pocke/majo.
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+
5
+ require "rubocop/rake_task"
6
+
7
+ RuboCop::RakeTask.new
8
+
9
+ require 'rake/testtask'
10
+
11
+ Rake::TestTask.new do |test|
12
+ test.libs << 'test'
13
+ test.test_files = Dir['test/**/*_test.rb']
14
+ test.verbose = true
15
+ end
16
+
17
+ require "rake/extensiontask"
18
+
19
+ Rake::ExtensionTask.new("majo")
20
+
21
+ CLEAN.add("{ext,lib}/**/*.{o,so,bundle}", "pkg")
22
+
23
+ task test: :compile
24
+ task default: %i[rubocop test]
@@ -0,0 +1,119 @@
1
+ #include "majo.h"
2
+
3
+ static void majo_allocation_info_mark(void *ptr)
4
+ {
5
+ // TODO
6
+ majo_allocation_info *info = (majo_allocation_info*)ptr;
7
+ }
8
+
9
+ static void majo_allocation_info_free(majo_allocation_info *info) {
10
+ // TODO
11
+ ruby_xfree(info);
12
+ }
13
+
14
+ static size_t majo_allocation_info_memsize(const void *ptr) {
15
+ return sizeof(majo_allocation_info);
16
+ }
17
+
18
+ static rb_data_type_t allocation_info_type = {
19
+ "Majo::AllocationInfo",
20
+ {majo_allocation_info_mark, (RUBY_DATA_FUNC)majo_allocation_info_free, majo_allocation_info_memsize},
21
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
22
+ };
23
+
24
+
25
+ static VALUE
26
+ allocation_info_alloc(VALUE klass) {
27
+ majo_allocation_info *info;
28
+ VALUE obj = TypedData_Make_Struct(klass, majo_allocation_info, &allocation_info_type, info);
29
+ return obj;
30
+ }
31
+
32
+ VALUE
33
+ majo_new_allocation_info(majo_allocation_info *info) {
34
+ majo_allocation_info *new_info;
35
+ VALUE obj = TypedData_Make_Struct(rb_cMajo_AllocationInfo, majo_allocation_info, &allocation_info_type, new_info);
36
+ *new_info = *info;
37
+ return obj;
38
+ }
39
+
40
+ majo_allocation_info *
41
+ majo_check_allocation_info(VALUE obj) {
42
+ return rb_check_typeddata(obj, &allocation_info_type);
43
+ }
44
+
45
+ static VALUE
46
+ allocation_info_path(VALUE self) {
47
+ majo_allocation_info *info = majo_check_allocation_info(self);
48
+ if (info->path) {
49
+ return rb_str_new2(info->path);
50
+ } else {
51
+ return Qnil;
52
+ }
53
+ }
54
+
55
+ static VALUE
56
+ allocation_info_class_path(VALUE self) {
57
+ majo_allocation_info *info = majo_check_allocation_info(self);
58
+ if (info->class_path) {
59
+ return rb_str_new2(info->class_path);
60
+ } else {
61
+ return Qnil;
62
+ }
63
+ }
64
+
65
+ static VALUE
66
+ allocation_info_object_class_path(VALUE self) {
67
+ majo_allocation_info *info = majo_check_allocation_info(self);
68
+ if (info->object_class_path) {
69
+ return rb_str_new2(info->object_class_path);
70
+ } else {
71
+ return Qnil;
72
+ }
73
+ }
74
+
75
+ static VALUE
76
+ allocation_info_line(VALUE self) {
77
+ majo_allocation_info *info = majo_check_allocation_info(self);
78
+ return LONG2NUM(info->line);
79
+ }
80
+
81
+
82
+ static VALUE
83
+ allocation_info_method_id(VALUE self) {
84
+ majo_allocation_info *info = majo_check_allocation_info(self);
85
+ return info->mid;
86
+ }
87
+
88
+ static VALUE
89
+ allocation_info_alloc_generation(VALUE self) {
90
+ majo_allocation_info *info = majo_check_allocation_info(self);
91
+ return SIZET2NUM(info->alloc_generation);
92
+ }
93
+
94
+ static VALUE
95
+ allocation_info_free_generation(VALUE self) {
96
+ majo_allocation_info *info = majo_check_allocation_info(self);
97
+ return SIZET2NUM(info->free_generation);
98
+ }
99
+
100
+ static VALUE
101
+ allocation_info_memsize(VALUE self) {
102
+ majo_allocation_info *info = majo_check_allocation_info(self);
103
+ return SIZET2NUM(info->memsize);
104
+ }
105
+
106
+ void
107
+ majo_init_allocation_info() {
108
+ rb_cMajo_AllocationInfo = rb_define_class_under(rb_mMajo, "AllocationInfo", rb_cObject);
109
+ rb_define_alloc_func(rb_cMajo_AllocationInfo, allocation_info_alloc);
110
+
111
+ rb_define_method(rb_cMajo_AllocationInfo, "path", allocation_info_path, 0);
112
+ rb_define_method(rb_cMajo_AllocationInfo, "class_path", allocation_info_class_path, 0);
113
+ rb_define_method(rb_cMajo_AllocationInfo, "method_id", allocation_info_method_id, 0);
114
+ rb_define_method(rb_cMajo_AllocationInfo, "line", allocation_info_line, 0);
115
+ rb_define_method(rb_cMajo_AllocationInfo, "object_class_path", allocation_info_object_class_path, 0);
116
+ rb_define_method(rb_cMajo_AllocationInfo, "alloc_generation", allocation_info_alloc_generation, 0);
117
+ rb_define_method(rb_cMajo_AllocationInfo, "free_generation", allocation_info_free_generation, 0);
118
+ rb_define_method(rb_cMajo_AllocationInfo, "memsize", allocation_info_memsize, 0);
119
+ }
@@ -0,0 +1,22 @@
1
+ #ifndef MAJO_ALLOCATION_INFO_H
2
+ #define MAJO_ALLOCATION_INFO_H
3
+
4
+ typedef struct {
5
+ const char *path;
6
+ const char *class_path;
7
+ const char *object_class_path;
8
+ unsigned long line;
9
+ VALUE mid;
10
+
11
+ size_t alloc_generation;
12
+ size_t free_generation;
13
+ size_t memsize;
14
+ } majo_allocation_info;
15
+
16
+ VALUE
17
+ majo_new_allocation_info(majo_allocation_info *info);
18
+
19
+ void
20
+ majo_init_allocation_info();
21
+
22
+ #endif
data/ext/majo/darray.h ADDED
@@ -0,0 +1,218 @@
1
+ // This file is copied from https://github.com/ruby/ruby and modified for this project.
2
+ //
3
+ // ----
4
+ //
5
+ // Copyright (C) 1993-2013 Yukihiro Matsumoto.
6
+ // Copyright (C) 2024 Masataka Kuwabara.
7
+ // All rights reserved.
8
+ //
9
+ // Redistribution and use in source and binary forms, with or without
10
+ // modification, are permitted provided that the following conditions
11
+ // are met:
12
+ // 1. Redistributions of source code must retain the above copyright
13
+ // notice, this list of conditions and the following disclaimer.
14
+ // 2. Redistributions in binary form must reproduce the above copyright
15
+ // notice, this list of conditions and the following disclaimer in the
16
+ // documentation and/or other materials provided with the distribution.
17
+ //
18
+ // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19
+ // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22
+ // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24
+ // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25
+ // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26
+ // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27
+ // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28
+ // SUCH DAMAGE.
29
+
30
+ #ifndef RUBY_DARRAY_H
31
+ #define RUBY_DARRAY_H
32
+
33
+ #include <stdint.h>
34
+ #include <stddef.h>
35
+ #include <stdlib.h>
36
+
37
+ // Type for a dynamic array. Use to declare a dynamic array.
38
+ // It is a pointer so it fits in st_table nicely. Designed
39
+ // to be fairly type-safe.
40
+ //
41
+ // NULL is a valid empty dynamic array.
42
+ //
43
+ // Example:
44
+ // rb_darray(char) char_array = NULL;
45
+ // rb_darray_append(&char_array, 'e');
46
+ // printf("pushed %c\n", *rb_darray_ref(char_array, 0));
47
+ // rb_darray_free(char_array);
48
+ //
49
+ #define rb_darray(T) struct { rb_darray_meta_t meta; T data[]; } *
50
+
51
+ // Copy an element out of the array. Warning: not bounds checked.
52
+ //
53
+ // T rb_darray_get(rb_darray(T) ary, size_t idx);
54
+ //
55
+ #define rb_darray_get(ary, idx) ((ary)->data[(idx)])
56
+
57
+ // Assign to an element. Warning: not bounds checked.
58
+ //
59
+ // void rb_darray_set(rb_darray(T) ary, size_t idx, T element);
60
+ //
61
+ #define rb_darray_set(ary, idx, element) ((ary)->data[(idx)] = (element))
62
+
63
+ // Get a pointer to an element. Warning: not bounds checked.
64
+ //
65
+ // T *rb_darray_ref(rb_darray(T) ary, size_t idx);
66
+ //
67
+ #define rb_darray_ref(ary, idx) (&((ary)->data[(idx)]))
68
+
69
+ /* Copy a new element into the array. ptr_to_ary is evaluated multiple times.
70
+ *
71
+ * void rb_darray_append(rb_darray(T) *ptr_to_ary, T element);
72
+ */
73
+ #define rb_darray_append(ptr_to_ary, element) \
74
+ rb_darray_append_impl(ptr_to_ary, element)
75
+
76
+ #define rb_darray_append_impl(ptr_to_ary, element) do { \
77
+ rb_darray_ensure_space((ptr_to_ary), \
78
+ sizeof(**(ptr_to_ary)), \
79
+ sizeof((*(ptr_to_ary))->data[0])); \
80
+ rb_darray_set(*(ptr_to_ary), \
81
+ (*(ptr_to_ary))->meta.size, \
82
+ (element)); \
83
+ (*(ptr_to_ary))->meta.size++; \
84
+ } while (0)
85
+
86
+ // Iterate over items of the array in a for loop
87
+ //
88
+ #define rb_darray_foreach(ary, idx_name, elem_ptr_var) \
89
+ for (size_t idx_name = 0; idx_name < rb_darray_size(ary) && ((elem_ptr_var) = rb_darray_ref(ary, idx_name)); ++idx_name)
90
+
91
+ // Iterate over valid indices in the array in a for loop
92
+ //
93
+ #define rb_darray_for(ary, idx_name) \
94
+ for (size_t idx_name = 0; idx_name < rb_darray_size(ary); ++idx_name)
95
+
96
+ /* Make a dynamic array of a certain size. All bytes backing the elements are set to zero.
97
+ * Return 1 on success and 0 on failure.
98
+ *
99
+ * Note that NULL is a valid empty dynamic array.
100
+ *
101
+ * void rb_darray_make(rb_darray(T) *ptr_to_ary, size_t size);
102
+ */
103
+ #define rb_darray_make(ptr_to_ary, size) \
104
+ rb_darray_make_impl((ptr_to_ary), size, sizeof(**(ptr_to_ary)), sizeof((*(ptr_to_ary))->data[0]))
105
+
106
+ /* Resize the darray to a new capacity. The new capacity must be greater than
107
+ * or equal to the size of the darray.
108
+ *
109
+ * void rb_darray_resize_capa(rb_darray(T) *ptr_to_ary, size_t capa);
110
+ */
111
+ #define rb_darray_resize_capa(ptr_to_ary, capa) \
112
+ rb_darray_resize_capa_impl((ptr_to_ary), capa, sizeof(**(ptr_to_ary)), sizeof((*(ptr_to_ary))->data[0]))
113
+
114
+ #define rb_darray_data_ptr(ary) ((ary)->data)
115
+
116
+ typedef struct rb_darray_meta {
117
+ size_t size;
118
+ size_t capa;
119
+ } rb_darray_meta_t;
120
+
121
+ /* Set the size of the array to zero without freeing the backing memory.
122
+ * Allows reusing the same array. */
123
+ static inline void
124
+ rb_darray_clear(void *ary)
125
+ {
126
+ rb_darray_meta_t *meta = ary;
127
+ if (meta) {
128
+ meta->size = 0;
129
+ }
130
+ }
131
+
132
+ // Get the size of the dynamic array.
133
+ //
134
+ static inline size_t
135
+ rb_darray_size(const void *ary)
136
+ {
137
+ const rb_darray_meta_t *meta = ary;
138
+ return meta ? meta->size : 0;
139
+ }
140
+
141
+ // Get the capacity of the dynamic array.
142
+ //
143
+ static inline size_t
144
+ rb_darray_capa(const void *ary)
145
+ {
146
+ const rb_darray_meta_t *meta = ary;
147
+ return meta ? meta->capa : 0;
148
+ }
149
+
150
+ /* Free the dynamic array. */
151
+ static inline void
152
+ rb_darray_free(void *ary)
153
+ {
154
+ free(ary);
155
+ }
156
+
157
+ /* Internal function. Resizes the capacity of a darray. The new capacity must
158
+ * be greater than or equal to the size of the darray. */
159
+ static inline void
160
+ rb_darray_resize_capa_impl(void *ptr_to_ary, size_t new_capa, size_t header_size, size_t element_size)
161
+ {
162
+ rb_darray_meta_t **ptr_to_ptr_to_meta = ptr_to_ary;
163
+ rb_darray_meta_t *meta = *ptr_to_ptr_to_meta;
164
+
165
+ rb_darray_meta_t *new_ary = realloc(meta, new_capa * element_size + header_size);
166
+
167
+ if (meta == NULL) {
168
+ /* First allocation. Initialize size. On subsequence allocations
169
+ * realloc takes care of carrying over the size. */
170
+ new_ary->size = 0;
171
+ }
172
+
173
+ RUBY_ASSERT(new_ary->size <= new_capa);
174
+
175
+ new_ary->capa = new_capa;
176
+
177
+ // We don't have access to the type of the dynamic array in function context.
178
+ // Write out result with memcpy to avoid strict aliasing issue.
179
+ memcpy(ptr_to_ary, &new_ary, sizeof(new_ary));
180
+ }
181
+
182
+ // Internal function
183
+ // Ensure there is space for one more element.
184
+ // Note: header_size can be bigger than sizeof(rb_darray_meta_t) when T is __int128_t, for example.
185
+ static inline void
186
+ rb_darray_ensure_space(void *ptr_to_ary, size_t header_size, size_t element_size)
187
+ {
188
+ rb_darray_meta_t **ptr_to_ptr_to_meta = ptr_to_ary;
189
+ rb_darray_meta_t *meta = *ptr_to_ptr_to_meta;
190
+ size_t current_capa = rb_darray_capa(meta);
191
+ if (rb_darray_size(meta) < current_capa) return;
192
+
193
+ // Double the capacity
194
+ size_t new_capa = current_capa == 0 ? 1 : current_capa * 2;
195
+
196
+ rb_darray_resize_capa_impl(ptr_to_ary, new_capa, header_size, element_size);
197
+ }
198
+
199
+ static inline void
200
+ rb_darray_make_impl(void *ptr_to_ary, size_t array_size, size_t header_size, size_t element_size)
201
+ {
202
+ rb_darray_meta_t **ptr_to_ptr_to_meta = ptr_to_ary;
203
+ if (array_size == 0) {
204
+ *ptr_to_ptr_to_meta = NULL;
205
+ return;
206
+ }
207
+
208
+ rb_darray_meta_t *meta = calloc(array_size * element_size + header_size, 1);
209
+
210
+ meta->size = array_size;
211
+ meta->capa = array_size;
212
+
213
+ // We don't have access to the type of the dynamic array in function context.
214
+ // Write out result with memcpy to avoid strict aliasing issue.
215
+ memcpy(ptr_to_ary, &meta, sizeof(meta));
216
+ }
217
+
218
+ #endif /* RUBY_DARRAY_H */
@@ -0,0 +1,3 @@
1
+ require "mkmf"
2
+
3
+ create_makefile("majo")
data/ext/majo/majo.c ADDED
@@ -0,0 +1,148 @@
1
+ #include "majo.h"
2
+
3
+ VALUE rb_mMajo;
4
+ VALUE rb_cMajo_Result;
5
+ VALUE rb_cMajo_AllocationInfo;
6
+
7
+ ID running_tracer_stack;
8
+
9
+ static bool
10
+ internal_object_p(VALUE obj)
11
+ {
12
+ switch (TYPE(obj)) {
13
+ case T_OBJECT:
14
+ case T_CLASS:
15
+ case T_MODULE:
16
+ case T_FLOAT:
17
+ case T_STRING:
18
+ case T_REGEXP:
19
+ case T_ARRAY:
20
+ case T_HASH:
21
+ case T_STRUCT:
22
+ case T_BIGNUM:
23
+ case T_FILE:
24
+ case T_DATA:
25
+ case T_MATCH:
26
+ case T_COMPLEX:
27
+ case T_RATIONAL:
28
+ case T_NIL:
29
+ case T_TRUE:
30
+ case T_FALSE:
31
+ case T_SYMBOL:
32
+ case T_FIXNUM:
33
+ return false;
34
+ default:
35
+ return true;
36
+ }
37
+ }
38
+
39
+ static void
40
+ newobj_i(VALUE tpval, void *data)
41
+ {
42
+ majo_result *arg = (majo_result *)data;
43
+
44
+ rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
45
+ VALUE obj = rb_tracearg_object(tparg);
46
+
47
+ if (internal_object_p(obj)) {
48
+ return;
49
+ }
50
+
51
+ VALUE path = rb_tracearg_path(tparg);
52
+ VALUE line = rb_tracearg_lineno(tparg);
53
+ VALUE mid = rb_tracearg_method_id(tparg);
54
+ VALUE klass = rb_tracearg_defined_class(tparg);
55
+
56
+ // TODO: when the st already has an entry for the value
57
+ majo_allocation_info *info = (majo_allocation_info *)malloc(sizeof(majo_allocation_info));
58
+
59
+ 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;
63
+
64
+ 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
+
68
+
69
+ info->path = path_cstr;
70
+ info->line = NUM2INT(line);
71
+ info->mid = mid;
72
+ info->object_class_path = obj_class_path_cstr;
73
+
74
+ info->class_path = class_path_cstr;
75
+ info->alloc_generation = rb_gc_count();
76
+ st_insert(arg->object_table, (st_data_t)obj, (st_data_t)info);
77
+ }
78
+
79
+ static void
80
+ freeobj_i(VALUE tpval, void *data)
81
+ {
82
+ majo_result *arg = (majo_result *)data;
83
+ rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
84
+ st_data_t obj = (st_data_t)rb_tracearg_object(tparg);
85
+ st_data_t v;
86
+
87
+ // TODO refcount of the strings
88
+ if (st_delete(arg->object_table, &obj, &v)) {
89
+ majo_allocation_info *info = (majo_allocation_info *)v;
90
+ size_t gc_count = rb_gc_count();
91
+ // Reject it for majo
92
+ if (info->alloc_generation < gc_count-1) {
93
+ info->memsize = rb_obj_memsize_of((VALUE)obj);
94
+ info->free_generation = gc_count;
95
+
96
+ VALUE obj = rb_tracearg_object(tparg);
97
+ if (!internal_object_p(obj)) {
98
+ majo_result_append_info(arg, *info);
99
+ }
100
+ }
101
+ free(info);
102
+ }
103
+ }
104
+
105
+ static VALUE
106
+ start(VALUE self) {
107
+ VALUE res = majo_new_result();
108
+ majo_result *arg = majo_check_result(res);
109
+
110
+ VALUE stack = rb_ivar_get(rb_mMajo, running_tracer_stack);
111
+ rb_ary_push(stack, res);
112
+
113
+ arg->newobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, arg);
114
+ arg->freeobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, arg);
115
+
116
+ rb_tracepoint_enable(arg->newobj_trace);
117
+ rb_tracepoint_enable(arg->freeobj_trace);
118
+
119
+ return res;
120
+ }
121
+
122
+ static VALUE
123
+ stop(VALUE self)
124
+ {
125
+ VALUE stack = rb_ivar_get(rb_mMajo, running_tracer_stack);
126
+ VALUE res = rb_ary_pop(stack);
127
+ majo_result *arg = majo_check_result(res);
128
+
129
+ rb_tracepoint_disable(arg->newobj_trace);
130
+ rb_tracepoint_disable(arg->freeobj_trace);
131
+
132
+ return res;
133
+ }
134
+
135
+ void
136
+ Init_majo(void)
137
+ {
138
+ rb_mMajo = rb_define_module("Majo");
139
+
140
+ rb_define_module_function(rb_mMajo, "__start", start, 0);
141
+ rb_define_module_function(rb_mMajo, "__stop", stop, 0);
142
+
143
+ running_tracer_stack = rb_intern("running_tracer_stack");
144
+ rb_ivar_set(rb_mMajo, running_tracer_stack, rb_ary_new());
145
+
146
+ majo_init_result();
147
+ majo_init_allocation_info();
148
+ }
data/ext/majo/majo.h ADDED
@@ -0,0 +1,23 @@
1
+ #ifndef MAJO_H
2
+ #define MAJO_H 1
3
+
4
+ #include <stdbool.h>
5
+
6
+ #include "ruby.h"
7
+ #include "ruby/debug.h"
8
+ #include "ruby/internal/gc.h"
9
+
10
+ #include "darray.h"
11
+
12
+ #include "allocation_info.h"
13
+ #include "result.h"
14
+ #include "unique_str.h"
15
+
16
+ extern VALUE rb_mMajo;
17
+ extern VALUE rb_cMajo_Result;
18
+ extern VALUE rb_cMajo_AllocationInfo;
19
+
20
+ // Exposing the internal functions
21
+ size_t rb_obj_memsize_of(VALUE);
22
+
23
+ #endif
data/ext/majo/result.c ADDED
@@ -0,0 +1,71 @@
1
+ #include "majo.h"
2
+
3
+ static void majo_result_mark(void *ptr)
4
+ {
5
+ majo_result *arg = (majo_result*)ptr;
6
+ rb_gc_mark(arg->newobj_trace);
7
+ rb_gc_mark(arg->freeobj_trace);
8
+ }
9
+
10
+ static void majo_result_free(majo_result *arg) {
11
+ // TODO
12
+ ruby_xfree(arg);
13
+ }
14
+
15
+ static size_t majo_result_memsize(const void *ptr) {
16
+ // TODO
17
+ return sizeof(majo_result);
18
+ }
19
+
20
+ static rb_data_type_t result_type = {
21
+ "Majo::Result",
22
+ {majo_result_mark, (RUBY_DATA_FUNC)majo_result_free, majo_result_memsize},
23
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
24
+ };
25
+
26
+ static VALUE result_alloc(VALUE klass) {
27
+ majo_result *arg;
28
+ VALUE obj = TypedData_Make_Struct(klass, majo_result, &result_type, arg);
29
+ arg->object_table = st_init_numtable();
30
+ arg->str_table = st_init_strtable();
31
+ arg->olds = NULL;
32
+
33
+ return obj;
34
+ }
35
+
36
+ VALUE
37
+ majo_new_result() {
38
+ return rb_class_new_instance(0, NULL, rb_cMajo_Result);
39
+ }
40
+
41
+ majo_result *
42
+ majo_check_result(VALUE obj) {
43
+ return rb_check_typeddata(obj, &result_type);
44
+ }
45
+
46
+ void
47
+ majo_result_append_info(majo_result *res, majo_allocation_info info) {
48
+ rb_darray_append(&res->olds, info);
49
+ }
50
+
51
+ VALUE
52
+ majo_result_allocations(VALUE self) {
53
+ majo_result *res = majo_check_result(self);
54
+ VALUE ary = rb_ary_new_capa(rb_darray_size(res->olds));
55
+
56
+ majo_allocation_info *info_ptr;
57
+ rb_darray_foreach(res->olds, i, info_ptr) {
58
+ VALUE v = majo_new_allocation_info(info_ptr);
59
+ rb_ary_push(ary, v);
60
+ }
61
+
62
+ return ary;
63
+ }
64
+
65
+ void
66
+ majo_init_result() {
67
+ rb_cMajo_Result = rb_define_class_under(rb_mMajo, "Result", rb_cObject);
68
+ rb_define_alloc_func(rb_cMajo_Result, result_alloc);
69
+
70
+ rb_define_method(rb_cMajo_Result, "allocations", majo_result_allocations, 0);
71
+ }
data/ext/majo/result.h ADDED
@@ -0,0 +1,24 @@
1
+ #ifndef MAJO_RESULT_H
2
+ #define MAJO_RESULT_H
3
+
4
+ typedef struct {
5
+ st_table *object_table; /* obj (VALUE) -> allocation_info */
6
+ st_table *str_table; /* cstr -> refcount */
7
+ rb_darray(majo_allocation_info) olds;
8
+ VALUE newobj_trace;
9
+ VALUE freeobj_trace;
10
+ } majo_result;
11
+
12
+ VALUE
13
+ majo_new_result();
14
+
15
+ majo_result *
16
+ majo_check_result(VALUE obj);
17
+
18
+ void
19
+ majo_result_append_info(majo_result *res, majo_allocation_info info);
20
+
21
+ void
22
+ majo_init_result();
23
+
24
+ #endif
@@ -0,0 +1,73 @@
1
+ // This file is copied from https://github.com/ruby/ruby and modified for this project.
2
+ //
3
+ // ----
4
+ //
5
+ // Copyright (C) 1993-2013 Yukihiro Matsumoto.
6
+ // Copyright (C) 2024 Masataka Kuwabara.
7
+ // All rights reserved.
8
+ //
9
+ // Redistribution and use in source and binary forms, with or without
10
+ // modification, are permitted provided that the following conditions
11
+ // are met:
12
+ // 1. Redistributions of source code must retain the above copyright
13
+ // notice, this list of conditions and the following disclaimer.
14
+ // 2. Redistributions in binary form must reproduce the above copyright
15
+ // notice, this list of conditions and the following disclaimer in the
16
+ // documentation and/or other materials provided with the distribution.
17
+ //
18
+ // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19
+ // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22
+ // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24
+ // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25
+ // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26
+ // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27
+ // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28
+ // SUCH DAMAGE.
29
+
30
+ #include "majo.h"
31
+
32
+ const char *
33
+ majo_make_unique_str(st_table *tbl, const char *str, long len)
34
+ {
35
+ if (!str) {
36
+ return NULL;
37
+ }
38
+ else {
39
+ st_data_t n;
40
+ char *result;
41
+
42
+ if (st_lookup(tbl, (st_data_t)str, &n)) {
43
+ st_insert(tbl, (st_data_t)str, n+1);
44
+ st_get_key(tbl, (st_data_t)str, &n);
45
+ result = (char *)n;
46
+ }
47
+ else {
48
+ result = (char *)malloc(len+1);
49
+ strncpy(result, str, len);
50
+ result[len] = 0;
51
+ st_add_direct(tbl, (st_data_t)result, 1);
52
+ }
53
+ return result;
54
+ }
55
+ }
56
+
57
+ void
58
+ majo_delete_unique_str(st_table *tbl, const char *str)
59
+ {
60
+ if (str) {
61
+ st_data_t n;
62
+
63
+ st_lookup(tbl, (st_data_t)str, &n);
64
+ if (n == 1) {
65
+ n = (st_data_t)str;
66
+ st_delete(tbl, &n, 0);
67
+ free((char *)n);
68
+ }
69
+ else {
70
+ st_insert(tbl, (st_data_t)str, n-1);
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,10 @@
1
+ #ifndef MAJO_UNIQUE_STR_H
2
+ #define MAJO_UNIQUE_STR_H
3
+
4
+ const char *
5
+ majo_make_unique_str(st_table *tbl, const char *str, long len);
6
+
7
+ void
8
+ majo_delete_unique_str(st_table *tbl, const char *str);
9
+
10
+ #endif
@@ -0,0 +1,23 @@
1
+ module Majo
2
+ class AllocationInfo
3
+ def inspect
4
+ "#<Majo::AllocationInfo #{object_class_path} #{class_path}##{method_id} #{path}:#{line} gen#{alloc_generation}..#{free_generation}>"
5
+ end
6
+
7
+ def hash
8
+ path.hash ^ class_path.hash ^ method_id.hash ^ line.hash ^ object_class_path.hash ^ alloc_generation.hash ^ free_generation.hash ^ memsize.hash
9
+ end
10
+
11
+ def eql?(other)
12
+ other.is_a?(AllocationInfo) &&
13
+ path == other.path &&
14
+ class_path == other.class_path &&
15
+ method_id == other.method_id &&
16
+ line == other.line &&
17
+ object_class_path == other.object_class_path &&
18
+ alloc_generation == other.alloc_generation &&
19
+ free_generation == other.free_generation &&
20
+ memsize == other.memsize
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,92 @@
1
+ module Majo
2
+ module Formatter
3
+ class Color
4
+ def initialize(result)
5
+ @result = result
6
+ end
7
+
8
+ def call
9
+ <<~RESULT
10
+ Total #{total_memory} bytes (#{total_objects} objects)
11
+
12
+ Memory by file
13
+ #{format_two_columns(memory_by_file)}
14
+
15
+ Memory by location
16
+ #{format_two_columns(memory_by_location)}
17
+
18
+ Memory by class
19
+ #{format_two_columns(memory_by_class)}
20
+
21
+ Objects by file
22
+ #{format_two_columns(objects_by_file)}
23
+
24
+ Objects by location
25
+ #{format_two_columns(objects_by_location)}
26
+
27
+ Objects by class
28
+ #{format_two_columns(objects_by_class)}
29
+ RESULT
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :result
35
+
36
+ def total_objects
37
+ allocs.size
38
+ end
39
+
40
+ def total_memory
41
+ allocs.sum(&:memsize)
42
+ end
43
+
44
+ def memory_by_file
45
+ allocs.group_by(&:path).map do |path, allocations|
46
+ [allocations.sum(&:memsize), path]
47
+ end.sort_by(&:first).reverse
48
+ end
49
+
50
+ def memory_by_location
51
+ allocs.group_by { |a| "#{a.path}:#{a.line}" }.map do |location, allocations|
52
+ [allocations.sum(&:memsize), location]
53
+ end.sort_by(&:first).reverse
54
+ end
55
+
56
+ def memory_by_class
57
+ allocs.group_by(&:object_class_path).map do |class_path, allocations|
58
+ [allocations.sum(&:memsize), class_path]
59
+ end.sort_by(&:first).reverse
60
+ end
61
+
62
+ def objects_by_file
63
+ allocs.group_by(&:path).map do |path, allocations|
64
+ [allocations.size, path]
65
+ end.sort_by(&:first).reverse
66
+ end
67
+
68
+ def objects_by_location
69
+ allocs.group_by { |a| "#{a.path}:#{a.line}" }.map do |location, allocations|
70
+ [allocations.size, location]
71
+ end.sort_by(&:first).reverse
72
+ end
73
+
74
+ def objects_by_class
75
+ allocs.group_by(&:object_class_path).map do |class_path, allocations|
76
+ [allocations.size, class_path]
77
+ end.sort_by(&:first).reverse
78
+ end
79
+
80
+ def format_two_columns(data)
81
+ return "" if data.empty?
82
+
83
+ max_length = data.max_by { |row| row[0].to_s.size }[0].size
84
+ data.map { |row| "#{row[0].to_s.ljust(max_length)} #{row[1]}" }.join("\n")
85
+ end
86
+
87
+ def allocs
88
+ @allocs ||= result.allocations
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,24 @@
1
+ require 'csv'
2
+
3
+ module Majo
4
+ module Formatter
5
+ class CSV
6
+ def initialize(result)
7
+ @result = result
8
+ end
9
+
10
+ def call
11
+ ::CSV.generate do |csv|
12
+ csv << ['Object class path', 'Class path', 'Method ID', 'Path', 'Line', 'Alloc generation', 'Free generation', 'Memsize', "Count"]
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]
15
+ end
16
+ end
17
+ end
18
+
19
+ def groups
20
+ @result.allocations.tally
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ module Majo
2
+ module Formatter
3
+ autoload :Color, 'majo/formatter/color'
4
+ autoload :CSV, 'majo/formatter/csv'
5
+ end
6
+ end
@@ -0,0 +1,34 @@
1
+ module Majo
2
+ class Result
3
+ def report(out: $stdout, formatter: nil)
4
+ fmt =
5
+ case formatter
6
+ when nil, :color
7
+ Formatter::Color
8
+ when :csv
9
+ Formatter::CSV
10
+ else
11
+ raise "unknown formatter: #{formatter.inspect}"
12
+ end
13
+
14
+ with_output(out) do |io|
15
+ io.puts fmt.new(self).call
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def with_output(out)
22
+ case
23
+ when out.is_a?(IO)
24
+ yield out
25
+ when out.is_a?(String)
26
+ File.open(out, "w") { |io| yield io }
27
+ when out.respond_to?(:to_path)
28
+ File.open(out.to_path, "w") { |io| yield io }
29
+ else
30
+ raise ArgumentError, "out must be an IO or a String"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Majo
4
+ VERSION = "0.0.3"
5
+ end
data/lib/majo.rb ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'majo.so'
4
+ require_relative "majo/version"
5
+ require_relative 'majo/allocation_info'
6
+ require_relative 'majo/result'
7
+ require_relative 'majo/formatter'
8
+
9
+ module Majo
10
+ class Error < StandardError; end
11
+
12
+ def self.start
13
+ GC.start
14
+ GC.start
15
+ GC.start
16
+ __start
17
+ end
18
+
19
+ def self.stop
20
+ GC.start
21
+ GC.start
22
+ GC.start
23
+ __stop
24
+ end
25
+
26
+ def self.run
27
+ r = start
28
+ yield
29
+ r
30
+ ensure
31
+ stop
32
+ end
33
+ end
data/sig/majo.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Majo
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: majo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Masataka Pocke Kuwabara
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2024-07-22 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: ''
13
+ email:
14
+ - kuwabara@pocke.me
15
+ executables: []
16
+ extensions:
17
+ - ext/majo/extconf.rb
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".rspec"
21
+ - ".rubocop.yml"
22
+ - LICENSE
23
+ - README.md
24
+ - Rakefile
25
+ - ext/majo/allocation_info.c
26
+ - ext/majo/allocation_info.h
27
+ - ext/majo/darray.h
28
+ - ext/majo/extconf.rb
29
+ - ext/majo/majo.c
30
+ - ext/majo/majo.h
31
+ - ext/majo/result.c
32
+ - ext/majo/result.h
33
+ - ext/majo/unique_str.c
34
+ - ext/majo/unique_str.h
35
+ - lib/majo.rb
36
+ - lib/majo/allocation_info.rb
37
+ - lib/majo/formatter.rb
38
+ - lib/majo/formatter/color.rb
39
+ - lib/majo/formatter/csv.rb
40
+ - lib/majo/result.rb
41
+ - lib/majo/version.rb
42
+ - sig/majo.rbs
43
+ homepage: https://github.com/pocke/majo
44
+ licenses:
45
+ - BSD-2-Clause
46
+ metadata:
47
+ homepage_uri: https://github.com/pocke/majo
48
+ source_code_uri: https://github.com/pocke/majo
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 3.1.0
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubygems_version: 3.6.0.dev
64
+ specification_version: 4
65
+ summary: ''
66
+ test_files: []