Skip to content

Commit a9ad00a

Browse files
committed
Add support for experimental profiler.
Use `IO_EVENT_SELECTOR_STALL_LOG_THRESHOLD=true` to enable.
1 parent b0947e6 commit a9ad00a

File tree

21 files changed

+418
-323
lines changed

21 files changed

+418
-323
lines changed

ext/extconf.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
$CFLAGS << " -DRUBY_DEBUG -O0"
2323
end
2424

25-
$srcs = ["io/event/event.c", "io/event/selector/selector.c"]
25+
$srcs = ["io/event/event.c", "io/event/selector/selector.c", "io/event/time.c", "io/event/profile.c"]
2626
$VPATH << "$(srcdir)/io/event"
2727
$VPATH << "$(srcdir)/io/event/selector"
2828

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ struct IO_Event_Array {
2828
void (*element_free)(void*);
2929
};
3030

31-
inline static int IO_Event_Array_allocate(struct IO_Event_Array *array, size_t count, size_t element_size)
31+
inline static int IO_Event_Array_initialize(struct IO_Event_Array *array, size_t count, size_t element_size)
3232
{
3333
array->limit = 0;
3434
array->element_size = element_size;
@@ -153,6 +153,12 @@ inline static void* IO_Event_Array_lookup(struct IO_Event_Array *array, size_t i
153153
return *element;
154154
}
155155

156+
inline static void* IO_Event_Array_last(struct IO_Event_Array *array)
157+
{
158+
if (array->limit == 0) return NULL;
159+
else return array->base[array->limit - 1];
160+
}
161+
156162
// Push a new element onto the end of the array.
157163
inline static void* IO_Event_Array_push(struct IO_Event_Array *array)
158164
{

ext/io/event/event.c

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,5 @@
1-
// Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2-
//
3-
// Permission is hereby granted, free of charge, to any person obtaining a copy
4-
// of this software and associated documentation files (the "Software"), to deal
5-
// in the Software without restriction, including without limitation the rights
6-
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7-
// copies of the Software, and to permit persons to whom the Software is
8-
// furnished to do so, subject to the following conditions:
9-
//
10-
// The above copyright notice and this permission notice shall be included in
11-
// all copies or substantial portions of the Software.
12-
//
13-
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14-
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15-
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16-
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17-
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18-
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19-
// THE SOFTWARE.
1+
// Released under the MIT License.
2+
// Copyright, 2021-2025, by Samuel Williams.
203

214
#include "event.h"
225
#include "selector/selector.h"

ext/io/event/event.h

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,5 @@
1-
// Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2-
//
3-
// Permission is hereby granted, free of charge, to any person obtaining a copy
4-
// of this software and associated documentation files (the "Software"), to deal
5-
// in the Software without restriction, including without limitation the rights
6-
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7-
// copies of the Software, and to permit persons to whom the Software is
8-
// furnished to do so, subject to the following conditions:
9-
//
10-
// The above copyright notice and this permission notice shall be included in
11-
// all copies or substantial portions of the Software.
12-
//
13-
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14-
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15-
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16-
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17-
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18-
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19-
// THE SOFTWARE.
1+
// Released under the MIT License.
2+
// Copyright, 2021-2025, by Samuel Williams.
203

214
#pragma once
225

ext/io/event/interrupt.c

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,5 @@
1-
// Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2-
//
3-
// Permission is hereby granted, free of charge, to any person obtaining a copy
4-
// of this software and associated documentation files (the "Software"), to deal
5-
// in the Software without restriction, including without limitation the rights
6-
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7-
// copies of the Software, and to permit persons to whom the Software is
8-
// furnished to do so, subject to the following conditions:
9-
//
10-
// The above copyright notice and this permission notice shall be included in
11-
// all copies or substantial portions of the Software.
12-
//
13-
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14-
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15-
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16-
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17-
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18-
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19-
// THE SOFTWARE.
20-
21-
// static const int DEBUG = 0;
1+
// Released under the MIT License.
2+
// Copyright, 2021-2025, by Samuel Williams.
223

234
#include "interrupt.h"
245
#include <unistd.h>

ext/io/event/interrupt.h

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,5 @@
1-
// Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2-
//
3-
// Permission is hereby granted, free of charge, to any person obtaining a copy
4-
// of this software and associated documentation files (the "Software"), to deal
5-
// in the Software without restriction, including without limitation the rights
6-
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7-
// copies of the Software, and to permit persons to whom the Software is
8-
// furnished to do so, subject to the following conditions:
9-
//
10-
// The above copyright notice and this permission notice shall be included in
11-
// all copies or substantial portions of the Software.
12-
//
13-
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14-
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15-
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16-
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17-
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18-
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19-
// THE SOFTWARE.
1+
// Released under the MIT License.
2+
// Copyright, 2021-2025, by Samuel Williams.
203

214
#pragma once
225

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Released under the MIT License.
2-
// Copyright, 2023, by Samuel Williams.
2+
// Copyright, 2023-2025, by Samuel Williams.
33

44
#include <ruby.h>
55
#include <stdio.h>

ext/io/event/profile.c

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Released under the MIT License.
2+
// Copyright, 2025, by Samuel Williams.
3+
4+
#include "profile.h"
5+
#include "time.h"
6+
7+
#include <ruby/debug.h>
8+
9+
#include <stdio.h>
10+
11+
void IO_Event_Profile_Event_initialize(struct IO_Event_Profile_Event *event) {
12+
event->time.tv_sec = 0;
13+
event->time.tv_nsec = 0;
14+
event->nesting = 0;
15+
16+
event->event_flag = 0;
17+
event->id = 0;
18+
19+
event->path = NULL;
20+
event->line = 0;
21+
}
22+
23+
void IO_Event_Profile_Event_free(struct IO_Event_Profile_Event *event) {
24+
if (event->path) {
25+
free((void*)event->path);
26+
}
27+
}
28+
29+
static const char *event_flag_name(rb_event_flag_t event_flag) {
30+
switch (event_flag) {
31+
case RUBY_EVENT_LINE:
32+
return "line";
33+
case RUBY_EVENT_CALL:
34+
case RUBY_EVENT_C_CALL:
35+
return "call";
36+
case RUBY_EVENT_RETURN:
37+
case RUBY_EVENT_C_RETURN:
38+
return "return";
39+
default:
40+
return "unknown";
41+
}
42+
}
43+
44+
int event_flag_call_p(rb_event_flag_t event_flags) {
45+
return event_flags & (RUBY_EVENT_CALL | RUBY_EVENT_C_CALL);
46+
}
47+
48+
int event_flag_return_p(rb_event_flag_t event_flags) {
49+
return event_flags & (RUBY_EVENT_RETURN | RUBY_EVENT_C_RETURN);
50+
}
51+
52+
static void profile_event_callback(rb_event_flag_t event_flag, VALUE data, VALUE self, ID id, VALUE klass) {
53+
struct IO_Event_Profile *profile = (struct IO_Event_Profile*)data;
54+
struct IO_Event_Profile_Event *event = IO_Event_Array_push(&profile->events);
55+
56+
IO_Event_Time_current(&event->time);
57+
58+
event->event_flag = event_flag;
59+
60+
if (event_flag_call_p(event_flag)) {
61+
event->parent = profile->current;
62+
profile->current = event;
63+
64+
event->nesting = profile->nesting;
65+
profile->nesting += 1;
66+
67+
if (id) {
68+
event->id = id;
69+
event->klass = klass;
70+
} else {
71+
rb_frame_method_id_and_class(&event->id, &event->klass);
72+
}
73+
74+
const char *path = rb_sourcefile();
75+
if (path) {
76+
event->path = strdup(path);
77+
}
78+
event->line = rb_sourceline();
79+
} else if (event_flag_return_p(event_flag)) {
80+
// Set up the call/return pair:
81+
profile->current->pair = event;
82+
event->pair = profile->current;
83+
84+
profile->current = profile->current->parent;
85+
event->parent = profile->current;
86+
87+
profile->nesting -= 1;
88+
event->nesting = profile->nesting;
89+
}
90+
}
91+
92+
void IO_Event_Profile_initialize(struct IO_Event_Profile *profile, VALUE fiber) {
93+
profile->fiber = fiber;
94+
95+
profile->events.element_initialize = (void (*)(void*))IO_Event_Profile_Event_initialize;
96+
profile->events.element_free = (void (*)(void*))IO_Event_Profile_Event_free;
97+
98+
IO_Event_Array_initialize(&profile->events, 0, sizeof(struct IO_Event_Profile_Event));
99+
}
100+
101+
void IO_Event_Profile_start(struct IO_Event_Profile *profile) {
102+
IO_Event_Time_current(&profile->start_time);
103+
profile->nesting = 0;
104+
profile->current = NULL;
105+
106+
// Since fibers are currently limited to a single thread, we use this in the hope that it's a little more efficient:
107+
VALUE thread = rb_thread_current();
108+
rb_thread_add_event_hook(thread, profile_event_callback, RUBY_EVENT_CALL | RUBY_EVENT_C_CALL | RUBY_EVENT_RETURN | RUBY_EVENT_C_RETURN, (VALUE)profile);
109+
}
110+
111+
void IO_Event_Profile_stop(struct IO_Event_Profile *profile) {
112+
IO_Event_Time_current(&profile->stop_time);
113+
114+
VALUE thread = rb_thread_current();
115+
rb_thread_remove_event_hook_with_data(thread, profile_event_callback, (VALUE)profile);
116+
}
117+
118+
void IO_Event_Profile_free(struct IO_Event_Profile *profile) {
119+
IO_Event_Array_free(&profile->events);
120+
}
121+
122+
static const float IO_EVENT_PROFILE_PRINT_MINIMUM_PROPORTION = 0.01;
123+
124+
void IO_Event_Profile_print(FILE *restrict stream, struct IO_Event_Profile *profile) {
125+
struct timespec total_duration = {};
126+
IO_Event_Time_elapsed(&profile->start_time, &profile->stop_time, &total_duration);
127+
128+
size_t skipped = 0;
129+
130+
for (size_t i = 0; i < profile->events.limit; i += 1) {
131+
struct IO_Event_Profile_Event *event = profile->events.base[i];
132+
133+
if (event_flag_call_p(event->event_flag)) {
134+
struct timespec duration = {};
135+
136+
if (event->pair) {
137+
IO_Event_Time_elapsed(&event->time, &event->pair->time, &duration);
138+
139+
// Skip events that are too short to be meaningful:
140+
if (IO_Event_Time_proportion(&duration, &total_duration) < IO_EVENT_PROFILE_PRINT_MINIMUM_PROPORTION) {
141+
skipped += 1;
142+
continue;
143+
}
144+
}
145+
146+
for (size_t i = 0; i < event->nesting; i += 1) {
147+
fputc('\t', stream);
148+
}
149+
150+
const char *name = rb_id2name(event->id);
151+
fprintf(stream, "\t%s:%d in '%s#%s' (" IO_EVENT_TIME_PRINTF_TIMESPEC "s)\n", event->path, event->line, RSTRING_PTR(rb_inspect(event->klass)), name, IO_EVENT_TIME_PRINTF_TIMESPEC_ARGUMENTS(duration));
152+
}
153+
}
154+
}

ext/io/event/profile.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Released under the MIT License.
2+
// Copyright, 2025, by Samuel Williams.
3+
4+
#pragma once
5+
6+
#include <ruby.h>
7+
#include "array.h"
8+
#include "time.h"
9+
10+
struct IO_Event_Profile_Event {
11+
struct timespec time;
12+
size_t nesting;
13+
14+
rb_event_flag_t event_flag;
15+
ID id;
16+
17+
VALUE klass;
18+
const char *path;
19+
int line;
20+
21+
struct IO_Event_Profile_Event *parent;
22+
struct IO_Event_Profile_Event *pair;
23+
};
24+
25+
struct IO_Event_Profile {
26+
VALUE fiber;
27+
28+
struct timespec start_time;
29+
struct timespec stop_time;
30+
31+
// The depth of the call stack:
32+
size_t nesting;
33+
34+
// The current call frame:
35+
struct IO_Event_Profile_Event *current;
36+
37+
struct IO_Event_Array events;
38+
};
39+
40+
void IO_Event_Profile_initialize(struct IO_Event_Profile *profile, VALUE fiber);
41+
42+
void IO_Event_Profile_start(struct IO_Event_Profile *profile);
43+
void IO_Event_Profile_stop(struct IO_Event_Profile *profile);
44+
45+
void IO_Event_Profile_free(struct IO_Event_Profile *profile);
46+
void IO_Event_Profile_print(FILE *restrict stream, struct IO_Event_Profile *profile);
47+
48+
static inline float IO_Event_Profile_duration(struct IO_Event_Profile *profile) {
49+
struct timespec duration;
50+
51+
IO_Event_Time_elapsed(&profile->start_time, &profile->stop_time, &duration);
52+
53+
return IO_Event_Time_duration(&duration);
54+
}

0 commit comments

Comments
 (0)