ruby-profiler 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0e85aa4c32011018aa3931f59a3465548e32c6f5d20c1ec59b43ab567a790ac5
4
+ data.tar.gz: ce20b28ad51dac0503f8094b81e0e9e19f5906864473856265f6535df6b1366e
5
+ SHA512:
6
+ metadata.gz: 4b9f9f473810035e0bc071d22590ccc04c7897dbbf9171c8c7686511de7a5a56aea7a746c5556070acdbdf95e9759196a2b32f0104a5e7a5deabe2511d089495
7
+ data.tar.gz: eccf1caa3fb6ccef12afce709ee405b88223e1b77c3e34350dcd548211adcb592fd800de513336dc7f20aad7fd90c24ae6b3ce00005cf03b7ac02330c65af732
data/ext/extconf.rb ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Released under the MIT License.
5
+ # Copyright, 2025, by Samuel Williams.
6
+
7
+ require "mkmf"
8
+
9
+ gem_name = File.basename(__dir__)
10
+ extension_name = "Ruby_Profiler"
11
+
12
+ append_cflags(["-Wall", "-Wno-unknown-pragmas", "-std=c99"])
13
+
14
+ if ENV.key?("RUBY_DEBUG")
15
+ $stderr.puts "Enabling debug mode..."
16
+
17
+ append_cflags(["-DRUBY_DEBUG", "-O0"])
18
+ end
19
+
20
+ $srcs = ["ruby/profiler/profiler.c", "ruby/profiler/state.c"]
21
+ $VPATH << "$(srcdir)/ruby/profiler"
22
+
23
+ have_func("rb_fiber_current")
24
+ have_func("rb_ext_ractor_safe")
25
+ have_func("rb_fiber_storage_get")
26
+ have_func("rb_fiber_storage_set")
27
+
28
+ if ENV.key?("RUBY_SANITIZE")
29
+ $stderr.puts "Enabling sanitizers..."
30
+
31
+ # Add address and undefined behaviour sanitizers:
32
+ $CFLAGS << " -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer"
33
+ $LDFLAGS << " -fsanitize=address -fsanitize=undefined"
34
+ end
35
+
36
+ create_header
37
+
38
+ # Generate the makefile to compile the native binary into `lib`:
39
+ create_makefile(extension_name)
40
+
41
+
42
+
@@ -0,0 +1,66 @@
1
+ // Released under the MIT License.
2
+ // Copyright, 2025, by Samuel Williams.
3
+
4
+ #include "profiler.h"
5
+ #include "state.h"
6
+
7
+ #include <ruby/debug.h>
8
+
9
+ #ifndef HAVE_RB_FIBER_CURRENT
10
+ static ID id_current;
11
+
12
+ static VALUE Ruby_Profiler_Fiber_current(void) {
13
+ return rb_funcall(rb_cFiber, id_current, 0);
14
+ }
15
+ #else
16
+ static VALUE Ruby_Profiler_Fiber_current(void) {
17
+ return rb_fiber_current();
18
+ }
19
+ #endif
20
+
21
+ // Fiber switch callback - updates thread-local pointer based on fiber-local storage
22
+ static void Ruby_Profiler_fiber_switch_callback(rb_event_flag_t event_flag, VALUE data, VALUE self, ID id, VALUE klass) {
23
+ VALUE fiber = Ruby_Profiler_Fiber_current();
24
+
25
+ struct Ruby_Profiler_State *state = Ruby_Profiler_State_for(fiber);
26
+
27
+ // Update thread-local pointer
28
+ ruby_profiler_state = state;
29
+ }
30
+
31
+ void Init_Ruby_Profiler(void)
32
+ {
33
+ #ifdef HAVE_RB_EXT_RACTOR_SAFE
34
+ rb_ext_ractor_safe(true);
35
+ #endif
36
+
37
+ #ifndef HAVE_RB_FIBER_CURRENT
38
+ id_current = rb_intern("current");
39
+ #endif
40
+
41
+ // Get or create Ruby module:
42
+ VALUE Ruby;
43
+ if (rb_const_defined(rb_cObject, rb_intern("Ruby"))) {
44
+ Ruby = rb_const_get(rb_cObject, rb_intern("Ruby"));
45
+ } else {
46
+ Ruby = rb_define_module("Ruby");
47
+ }
48
+
49
+ VALUE Ruby_Profiler = rb_define_module_under(Ruby, "Profiler");
50
+
51
+ Init_Ruby_Profiler_State(Ruby_Profiler);
52
+
53
+ // Register fiber switch event hook automatically:
54
+ // This updates the thread-local pointer whenever a fiber switch occurs.
55
+ rb_add_event_hook(
56
+ Ruby_Profiler_fiber_switch_callback,
57
+ RUBY_EVENT_FIBER_SWITCH,
58
+ Qnil // No data needed, callback is stateless.
59
+ );
60
+
61
+ // Also update state immediately for current fiber:
62
+ VALUE fiber = Ruby_Profiler_Fiber_current();
63
+ struct Ruby_Profiler_State *state = Ruby_Profiler_State_for(fiber);
64
+ ruby_profiler_state = state;
65
+ }
66
+
@@ -0,0 +1,8 @@
1
+ // Released under the MIT License.
2
+ // Copyright, 2025, by Samuel Williams.
3
+
4
+ #pragma once
5
+
6
+ #include <ruby.h>
7
+
8
+ void Init_Ruby_Profiler(void);
@@ -0,0 +1,375 @@
1
+ // Released under the MIT License.
2
+ // Copyright, 2025, by Samuel Williams.
3
+
4
+ #include "profiler.h"
5
+ #include "state.h"
6
+
7
+ #include <ruby/internal/core/rhash.h>
8
+ #include <stdlib.h>
9
+ #include <string.h>
10
+
11
+ // Thread-local pointer to current state (public symbol for BPF access)
12
+ _Thread_local struct Ruby_Profiler_State *ruby_profiler_state = NULL;
13
+
14
+ VALUE Ruby_Profiler_State = Qnil;
15
+
16
+ // Cached ID for @ruby_profiler_state instance variable
17
+ ID id_ruby_profiler_state;
18
+
19
+ static void Ruby_Profiler_State_mark(void *ptr) {
20
+ struct Ruby_Profiler_State *state = (struct Ruby_Profiler_State*)ptr;
21
+
22
+ // Handle NULL (deferred allocation)
23
+ if (!state) {
24
+ return;
25
+ }
26
+
27
+ // Mark all VALUEs in pairs (iterate through capacity to find all non-empty slots)
28
+ for (size_t i = 0; i < state->capacity; i++) {
29
+ if (state->pairs[i].key != 0) {
30
+ rb_gc_mark_movable(state->pairs[i].value);
31
+ }
32
+ }
33
+ }
34
+
35
+ static void Ruby_Profiler_State_compact(void *ptr) {
36
+ struct Ruby_Profiler_State *state = (struct Ruby_Profiler_State*)ptr;
37
+
38
+ // Handle NULL (deferred allocation)
39
+ if (!state) {
40
+ return;
41
+ }
42
+
43
+ // Update all VALUE locations after GC compaction (iterate through capacity)
44
+ for (size_t i = 0; i < state->capacity; i++) {
45
+ if (state->pairs[i].key != 0) {
46
+ state->pairs[i].value = rb_gc_location(state->pairs[i].value);
47
+ }
48
+ }
49
+ }
50
+
51
+ static void Ruby_Profiler_State_free(void *ptr) {
52
+ struct Ruby_Profiler_State *state = (struct Ruby_Profiler_State*)ptr;
53
+
54
+ // Handle NULL (deferred allocation):
55
+ if (!state) {
56
+ return;
57
+ }
58
+
59
+ // If this state is currently active, clear the thread-local pointer
60
+ if (ruby_profiler_state == state) {
61
+ ruby_profiler_state = NULL;
62
+ }
63
+
64
+ free(state);
65
+ }
66
+
67
+ static size_t Ruby_Profiler_State_memsize(const void *ptr) {
68
+ const struct Ruby_Profiler_State *state = (const struct Ruby_Profiler_State*)ptr;
69
+
70
+ // Handle NULL (deferred allocation)
71
+ if (!state) {
72
+ return 0;
73
+ }
74
+
75
+ return sizeof(*state) + (state->capacity * sizeof(struct Ruby_Profiler_Pair));
76
+ }
77
+
78
+ const rb_data_type_t Ruby_Profiler_State_Type = {
79
+ .wrap_struct_name = "Ruby::Profiler::State",
80
+ .function = {
81
+ .dmark = Ruby_Profiler_State_mark,
82
+ .dcompact = Ruby_Profiler_State_compact,
83
+ .dfree = Ruby_Profiler_State_free,
84
+ .dsize = Ruby_Profiler_State_memsize,
85
+ },
86
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
87
+ };
88
+
89
+ struct Ruby_Profiler_State *Ruby_Profiler_State_get(VALUE self) {
90
+ struct Ruby_Profiler_State *state;
91
+ TypedData_Get_Struct(self, struct Ruby_Profiler_State, &Ruby_Profiler_State_Type, state);
92
+ return state;
93
+ }
94
+
95
+ // Round up to next power of 2
96
+ static size_t round_capacity_to_power_of_2(size_t capacity) {
97
+ if (capacity == 0) return 1;
98
+ capacity--;
99
+ capacity |= capacity >> 1;
100
+ capacity |= capacity >> 2;
101
+ capacity |= capacity >> 4;
102
+ capacity |= capacity >> 8;
103
+ capacity |= capacity >> 16;
104
+ #if SIZEOF_SIZE_T == 8
105
+ capacity |= capacity >> 32;
106
+ #endif
107
+ return capacity + 1;
108
+ }
109
+
110
+ static VALUE Ruby_Profiler_State_allocate(VALUE klass) {
111
+ // Defer allocation until initialize when we know the required capacity
112
+ return TypedData_Wrap_Struct(klass, &Ruby_Profiler_State_Type, NULL);
113
+ }
114
+
115
+ // Find a pair by key using hash table lookup with linear probing
116
+ static struct Ruby_Profiler_Pair *Ruby_Profiler_State_find_pair(struct Ruby_Profiler_State *state, ID key) {
117
+ if (key == 0 || state->capacity == 0) {
118
+ return NULL;
119
+ }
120
+
121
+ size_t mask = state->capacity - 1; // Assumes power of 2
122
+ size_t idx = (size_t)key & mask;
123
+
124
+ for (size_t i = 0; i < state->capacity; i++) {
125
+ size_t pos = (idx + i) & mask;
126
+
127
+ if (state->pairs[pos].key == key) {
128
+ return &state->pairs[pos];
129
+ }
130
+ if (state->pairs[pos].key == 0) {
131
+ return NULL; // Empty slot means not found
132
+ }
133
+ }
134
+
135
+ return NULL; // Table full, key not found
136
+ }
137
+
138
+ // Insert or update a pair using hash table with linear probing
139
+ static int Ruby_Profiler_State_insert_pair(struct Ruby_Profiler_State *state, ID key, VALUE value) {
140
+ if (key == 0) {
141
+ return 0; // Invalid key
142
+ }
143
+
144
+ size_t mask = state->capacity - 1; // Assumes power of 2
145
+ size_t idx = (size_t)key & mask;
146
+
147
+ // First, check if key already exists (update case)
148
+ for (size_t i = 0; i < state->capacity; i++) {
149
+ size_t pos = (idx + i) & mask;
150
+
151
+ if (state->pairs[pos].key == key) {
152
+ // Update existing pair (doesn't require capacity check)
153
+ state->pairs[pos].value = value;
154
+ return 1;
155
+ }
156
+ if (state->pairs[pos].key == 0) {
157
+ // Found empty slot, check capacity before inserting
158
+ if (state->size >= state->capacity) {
159
+ return 0; // Table full
160
+ }
161
+ // Insert here
162
+ state->pairs[pos].key = key;
163
+ state->pairs[pos].value = value;
164
+ state->size++;
165
+ return 1;
166
+ }
167
+ }
168
+
169
+ return 0; // Table full (no empty slot found)
170
+ }
171
+
172
+ // Callback for rb_hash_foreach to insert pairs into state
173
+ static int Ruby_Profiler_State_foreach_insert(VALUE key, VALUE value, VALUE data) {
174
+ struct Ruby_Profiler_State *state = (struct Ruby_Profiler_State*)data;
175
+
176
+ // Keys must be symbols - raise TypeError if not
177
+ if (!RB_TYPE_P(key, T_SYMBOL)) {
178
+ rb_raise(rb_eTypeError, "State keys must be symbols, got %s", rb_obj_classname(key));
179
+ }
180
+
181
+ ID id = rb_sym2id(key);
182
+
183
+ // Insert using hash table (will update if key exists, insert if new)
184
+ if (!Ruby_Profiler_State_insert_pair(state, id, value)) {
185
+ rb_raise(rb_eArgError, "State capacity exceeded (%zu pairs)!", state->capacity);
186
+ }
187
+
188
+ return ST_CONTINUE;
189
+ }
190
+
191
+ // Helper struct for counting new keys
192
+ struct Ruby_Profiler_State_CountData {
193
+ struct Ruby_Profiler_State *old_state;
194
+ size_t new_count;
195
+ };
196
+
197
+ // Callback for rb_hash_foreach to count new keys (keys not in old_state)
198
+ static int Ruby_Profiler_State_foreach_count_new(VALUE key, VALUE value, VALUE data) {
199
+ struct Ruby_Profiler_State_CountData *count_data = (struct Ruby_Profiler_State_CountData*)data;
200
+
201
+ // Keys must be symbols
202
+ if (!RB_TYPE_P(key, T_SYMBOL)) {
203
+ return ST_CONTINUE; // Skip non-symbols, will be caught during insert
204
+ }
205
+
206
+ ID id = rb_sym2id(key);
207
+
208
+ // Count if key doesn't exist in old_state
209
+ if (!count_data->old_state || !Ruby_Profiler_State_find_pair(count_data->old_state, id)) {
210
+ count_data->new_count++;
211
+ }
212
+
213
+ return ST_CONTINUE;
214
+ }
215
+
216
+ static VALUE Ruby_Profiler_State_initialize(int argc, VALUE *argv, VALUE self) {
217
+ struct Ruby_Profiler_State *state;
218
+ TypedData_Get_Struct(self, struct Ruby_Profiler_State, &Ruby_Profiler_State_Type, state);
219
+
220
+ if (state) {
221
+ rb_raise(rb_eRuntimeError, "State already initialized!");
222
+ }
223
+
224
+ VALUE options = Qnil;
225
+ rb_scan_args(argc, argv, ":", &options);
226
+
227
+ // Determine required capacity based on number of pairs
228
+ size_t required_capacity = 0;
229
+
230
+ if (!RB_NIL_P(options)) {
231
+ // Get hash size directly without allocating temporary array
232
+ size_t keys_count = RHASH_SIZE(options);
233
+
234
+ // Calculate required capacity (next power of 2)
235
+ required_capacity = round_capacity_to_power_of_2(keys_count);
236
+ } else {
237
+ return self;
238
+ }
239
+
240
+ // Allocate state with correct capacity (or reallocate if already allocated)
241
+ size_t size = sizeof(struct Ruby_Profiler_State) + (required_capacity * sizeof(struct Ruby_Profiler_Pair));
242
+ state = (struct Ruby_Profiler_State*)calloc(1, size);
243
+
244
+ if (!state) {
245
+ rb_raise(rb_eNoMemError, "Failed to allocate state!");
246
+ }
247
+
248
+ // Initialize state:
249
+ state->size = 0;
250
+ state->capacity = required_capacity;
251
+
252
+ // Update TypedData pointer:
253
+ DATA_PTR(self) = state;
254
+
255
+ // Now insert all pairs using rb_hash_foreach (more efficient than allocating keys array):
256
+ rb_hash_foreach(options, Ruby_Profiler_State_foreach_insert, (VALUE)state);
257
+
258
+ return self;
259
+ }
260
+
261
+ static VALUE Ruby_Profiler_State_apply(VALUE self) {
262
+ struct Ruby_Profiler_State *state = Ruby_Profiler_State_get(self);
263
+
264
+ // Update the thread-local pointer (NULL if state not initialized)
265
+ ruby_profiler_state = state;
266
+
267
+ // Store state in fiber-local storage using Fiber#ruby_profiler_state=
268
+ // This is fiber-local storage that persists across fiber switches
269
+ VALUE fiber;
270
+
271
+ #ifdef HAVE_RB_FIBER_CURRENT
272
+ fiber = rb_fiber_current();
273
+ #else
274
+ fiber = rb_funcall(rb_cFiber, rb_intern("current"), 0);
275
+ #endif
276
+
277
+ rb_ivar_set(fiber, id_ruby_profiler_state, self);
278
+
279
+ return self;
280
+ }
281
+
282
+ static VALUE Ruby_Profiler_State_size(VALUE self) {
283
+ struct Ruby_Profiler_State *state = Ruby_Profiler_State_get(self);
284
+
285
+ if (!state) {
286
+ return SIZET2NUM(0); // Uninitialized state has size 0
287
+ }
288
+
289
+ return SIZET2NUM(state->size);
290
+ }
291
+
292
+ static VALUE Ruby_Profiler_State_with(int argc, VALUE *argv, VALUE self) {
293
+ struct Ruby_Profiler_State *old_state;
294
+ TypedData_Get_Struct(self, struct Ruby_Profiler_State, &Ruby_Profiler_State_Type, old_state);
295
+
296
+ VALUE options = Qnil;
297
+ rb_scan_args(argc, argv, ":", &options);
298
+
299
+ if (RB_NIL_P(options)) {
300
+ // No updates, return self:
301
+ return self;
302
+ }
303
+
304
+ // Count how many keys in options are NOT in old_state (new keys)
305
+ size_t old_size = old_state ? old_state->size : 0;
306
+ struct Ruby_Profiler_State_CountData count_data = {old_state, 0};
307
+
308
+ rb_hash_foreach(options, Ruby_Profiler_State_foreach_count_new, (VALUE)&count_data);
309
+
310
+ size_t required_capacity = round_capacity_to_power_of_2(old_size + count_data.new_count);
311
+
312
+ // Allocate a new state with the required capacity
313
+ VALUE klass = rb_obj_class(self);
314
+ VALUE new_state_value = Ruby_Profiler_State_allocate(klass);
315
+
316
+ // Allocate the new state struct
317
+ size_t size = sizeof(struct Ruby_Profiler_State) + (required_capacity * sizeof(struct Ruby_Profiler_Pair));
318
+ struct Ruby_Profiler_State *new_state = (struct Ruby_Profiler_State*)calloc(1, size);
319
+
320
+ if (!new_state) {
321
+ rb_raise(rb_eNoMemError, "Failed to allocate Ruby_Profiler_State");
322
+ }
323
+
324
+ new_state->size = 0;
325
+ new_state->capacity = required_capacity;
326
+ DATA_PTR(new_state_value) = new_state;
327
+
328
+ // Copy all existing pairs from old_state to new_state (if old_state exists)
329
+ if (old_state) {
330
+ for (size_t i = 0; i < old_state->capacity; i++) {
331
+ if (old_state->pairs[i].key != 0) {
332
+ if (!Ruby_Profiler_State_insert_pair(new_state, old_state->pairs[i].key, old_state->pairs[i].value)) {
333
+ rb_raise(rb_eArgError, "State capacity exceeded while copying state");
334
+ }
335
+ }
336
+ }
337
+ }
338
+
339
+ // Apply updates from options hash using rb_hash_foreach
340
+ rb_hash_foreach(options, Ruby_Profiler_State_foreach_insert, (VALUE)new_state);
341
+
342
+ return new_state_value;
343
+ }
344
+
345
+ // Get state for fiber from fiber-local storage
346
+ struct Ruby_Profiler_State *Ruby_Profiler_State_for(VALUE fiber) {
347
+ VALUE state_value = rb_ivar_get(fiber, id_ruby_profiler_state);
348
+
349
+ if (RB_NIL_P(state_value)) {
350
+ return NULL;
351
+ }
352
+
353
+ // Extract the state pointer from the VALUE
354
+ struct Ruby_Profiler_State *state;
355
+ if (!rb_typeddata_is_kind_of(state_value, &Ruby_Profiler_State_Type)) {
356
+ return NULL;
357
+ }
358
+
359
+ TypedData_Get_Struct(state_value, struct Ruby_Profiler_State, &Ruby_Profiler_State_Type, state);
360
+ return state;
361
+ }
362
+
363
+ void Init_Ruby_Profiler_State(VALUE Ruby_Profiler) {
364
+ Ruby_Profiler_State = rb_define_class_under(Ruby_Profiler, "State", rb_cObject);
365
+ rb_define_alloc_func(Ruby_Profiler_State, Ruby_Profiler_State_allocate);
366
+
367
+ // Cache the ID for @ruby_profiler_state instance variable
368
+ id_ruby_profiler_state = rb_intern("@ruby_profiler_state");
369
+
370
+ rb_define_method(Ruby_Profiler_State, "initialize", Ruby_Profiler_State_initialize, -1);
371
+ rb_define_method(Ruby_Profiler_State, "apply!", Ruby_Profiler_State_apply, 0);
372
+ rb_define_method(Ruby_Profiler_State, "with", Ruby_Profiler_State_with, -1);
373
+ rb_define_method(Ruby_Profiler_State, "size", Ruby_Profiler_State_size, 0);
374
+ }
375
+
@@ -0,0 +1,56 @@
1
+ // Released under the MIT License.
2
+ // Copyright, 2025, by Samuel Williams.
3
+
4
+ #pragma once
5
+
6
+ #include <ruby.h>
7
+
8
+ struct Ruby_Profiler_Pair {
9
+ // ID 0 indicates empty slot:
10
+ ID key;
11
+ VALUE value;
12
+ };
13
+
14
+ // This state is considered a public interface for BPF programs to read. Therefore, we will endeavour not to change it without an extremely good reason.
15
+ struct Ruby_Profiler_State {
16
+ // Number of active pairs:
17
+ size_t size;
18
+
19
+ // Total slots (must be power of 2 for efficient hashing):
20
+ size_t capacity;
21
+
22
+ // Array of pairs:
23
+ struct Ruby_Profiler_Pair pairs[];
24
+ };
25
+
26
+ // Hash Table Design:
27
+ //
28
+ // For small hash tables (< 16 items) with integer keys like your Ruby profiler,
29
+ // hash + linear probing at 100% load factor is optimal: computing key % capacity
30
+ // (especially with power-of-2 capacity using bitwise AND) is essentially free, and
31
+ // even in the worst case where you scan all slots, you're no worse off than a pure
32
+ // linear scan from index 0, while on average you start closer to your target and
33
+ // find items faster—giving you all the benefits of hashing with zero memory overhead
34
+ // and no downside, making it strictly better than either pure linear scan or traditional
35
+ // linear probing with lower load factors.
36
+ //
37
+ // Implementation details:
38
+ // - Capacity must be a power of 2 (enforced at allocation).
39
+ // - Hash function: key & (capacity - 1) (fast bitwise AND).
40
+ // - Linear probing: (hash + i) & (capacity - 1) for i = 0, 1, 2, ...
41
+ // - Empty slots: key == 0 (ID 0 is invalid in Ruby).
42
+ // - BPF-friendly: Can enumerate by iterating capacity slots and skipping empty ones.
43
+
44
+ // Thread-local pointer to current state (public symbol for BPF access)
45
+ extern _Thread_local struct Ruby_Profiler_State *ruby_profiler_state;
46
+
47
+ // Typed data type (defined in state.c)
48
+ extern const rb_data_type_t Ruby_Profiler_State_Type;
49
+
50
+ // Cached ID for @ruby_profiler_state instance variable (defined in state.c)
51
+ extern ID id_ruby_profiler_state;
52
+
53
+ // Get state for fiber from fiber-local storage
54
+ struct Ruby_Profiler_State *Ruby_Profiler_State_for(VALUE fiber);
55
+
56
+ void Init_Ruby_Profiler_State(VALUE Ruby_Profiler);
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require "Ruby_Profiler"
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ # @namespace
7
+ module Ruby
8
+ # @namespace
9
+ module Profiler
10
+ VERSION = "0.1.0"
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require_relative "profiler/version"
7
+ require_relative "profiler/native"
8
+
9
+ # Define fiber-local accessor for profiler state:
10
+ Fiber.attr_accessor :ruby_profiler_state
data/license.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright, 2025, by Samuel Williams.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/readme.md ADDED
@@ -0,0 +1,37 @@
1
+ # Ruby Profiler
2
+
3
+ A profiler state manager for Ruby fibers, designed for BPF/eBPF integration.
4
+
5
+ [![Development Status](https://github.com/socketry/ruby-profiler/workflows/Test/badge.svg)](https://github.com/socketry/ruby-profiler/actions?workflow=Test)
6
+
7
+ ## Usage
8
+
9
+ Please see the [project documentation](https://socketry.github.io/ruby-profiler/) for more details.
10
+
11
+ - [Getting Started](https://socketry.github.io/ruby-profiler/guides/getting-started/index) - This guide explains how to get started with `ruby-profiler`, a profiler state manager for Ruby fibers designed for BPF/eBPF integration.
12
+
13
+ - [BPF Integration Guide](https://socketry.github.io/ruby-profiler/guides/bpf-integration/index) - This guide explains how to integrate `ruby-profiler` with BPF/eBPF programs to read profiler state from Ruby fibers.
14
+
15
+ ## Releases
16
+
17
+ Please see the [project releases](https://socketry.github.io/ruby-profiler/releases/index) for all releases.
18
+
19
+ ### v0.1.0
20
+
21
+ ## Contributing
22
+
23
+ We welcome contributions to this project.
24
+
25
+ 1. Fork it.
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`).
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`).
28
+ 4. Push to the branch (`git push origin my-new-feature`).
29
+ 5. Create new Pull Request.
30
+
31
+ ### Developer Certificate of Origin
32
+
33
+ In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
34
+
35
+ ### Community Guidelines
36
+
37
+ This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
data/releases.md ADDED
@@ -0,0 +1,3 @@
1
+ # Releases
2
+
3
+ ## v0.1.0
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-profiler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Williams
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ executables: []
13
+ extensions:
14
+ - ext/extconf.rb
15
+ extra_rdoc_files: []
16
+ files:
17
+ - ext/extconf.rb
18
+ - ext/ruby/profiler/profiler.c
19
+ - ext/ruby/profiler/profiler.h
20
+ - ext/ruby/profiler/state.c
21
+ - ext/ruby/profiler/state.h
22
+ - lib/ruby/profiler.rb
23
+ - lib/ruby/profiler/native.rb
24
+ - lib/ruby/profiler/version.rb
25
+ - license.md
26
+ - readme.md
27
+ - releases.md
28
+ homepage: https://github.com/socketry/ruby-profiler
29
+ licenses:
30
+ - MIT
31
+ metadata:
32
+ documentation_uri: https://socketry.github.io/ruby-profiler/
33
+ source_code_uri: https://github.com/socketry/ruby-profiler.git
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '3.2'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubygems_version: 3.6.9
49
+ specification_version: 4
50
+ summary: A profiler state manager for Ruby fibers.
51
+ test_files: []