fiber-profiler 0.2.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/ext/Fiber_Profiler.bundle +0 -0
- data/ext/Makefile +270 -0
- data/ext/capture.o +0 -0
- data/ext/extconf.h +5 -0
- data/ext/fiber/profiler/capture.c +248 -194
- data/ext/fiber/profiler/capture.h +0 -4
- data/ext/fiber.o +0 -0
- data/ext/profiler.o +0 -0
- data/ext/time.o +0 -0
- data/lib/fiber/profiler/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +14 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: babeb0fbb4a600a487a2f90ad86f7f98a393978055a3409d8c67084e5f649a26
|
4
|
+
data.tar.gz: 62ab31b197bfa49f84fbf5c03d2f5fd59321e84bffab755b5c3b5387a9d27eea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 13a477d15ba50e6c858b63a0250493c48eb6ea26256340ea46de57effbdc6ac87ced1e3a51c0f7b3c8c74b8538b59c29730146e22a532fb913f79a5ff5c03531
|
7
|
+
data.tar.gz: d9d93e687484c38033cbe68778c6e018b323e9b1281480a35cf3cf6dbc2809d75712faf6318527482242373c2af01dc2ec9cfcc64a612c8725bfdb5890cf0017
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
Binary file
|
data/ext/Makefile
ADDED
@@ -0,0 +1,270 @@
|
|
1
|
+
|
2
|
+
SHELL = /bin/sh
|
3
|
+
|
4
|
+
# V=0 quiet, V=1 verbose. other values don't work.
|
5
|
+
V = 0
|
6
|
+
V0 = $(V:0=)
|
7
|
+
Q1 = $(V:1=)
|
8
|
+
Q = $(Q1:0=@)
|
9
|
+
ECHO1 = $(V:1=@ :)
|
10
|
+
ECHO = $(ECHO1:0=@ echo)
|
11
|
+
NULLCMD = :
|
12
|
+
|
13
|
+
#### Start of system configuration section. ####
|
14
|
+
|
15
|
+
srcdir = .
|
16
|
+
topdir = /Users/samuel/.rubies/ruby-3.3.5/include/ruby-3.3.0
|
17
|
+
hdrdir = $(topdir)
|
18
|
+
arch_hdrdir = /Users/samuel/.rubies/ruby-3.3.5/include/ruby-3.3.0/arm64-darwin24
|
19
|
+
PATH_SEPARATOR = :
|
20
|
+
VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby:$(srcdir)/fiber/profiler
|
21
|
+
prefix = $(DESTDIR)/Users/samuel/.rubies/ruby-3.3.5
|
22
|
+
rubysitearchprefix = $(rubylibprefix)/$(sitearch)
|
23
|
+
rubyarchprefix = $(rubylibprefix)/$(arch)
|
24
|
+
rubylibprefix = $(libdir)/$(RUBY_BASE_NAME)
|
25
|
+
exec_prefix = $(prefix)
|
26
|
+
vendorarchhdrdir = $(vendorhdrdir)/$(sitearch)
|
27
|
+
sitearchhdrdir = $(sitehdrdir)/$(sitearch)
|
28
|
+
rubyarchhdrdir = $(rubyhdrdir)/$(arch)
|
29
|
+
vendorhdrdir = $(rubyhdrdir)/vendor_ruby
|
30
|
+
sitehdrdir = $(rubyhdrdir)/site_ruby
|
31
|
+
rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME)
|
32
|
+
vendorarchdir = $(vendorlibdir)/$(sitearch)
|
33
|
+
vendorlibdir = $(vendordir)/$(ruby_version)
|
34
|
+
vendordir = $(rubylibprefix)/vendor_ruby
|
35
|
+
sitearchdir = $(sitelibdir)/$(sitearch)
|
36
|
+
sitelibdir = $(sitedir)/$(ruby_version)
|
37
|
+
sitedir = $(rubylibprefix)/site_ruby
|
38
|
+
rubyarchdir = $(rubylibdir)/$(arch)
|
39
|
+
rubylibdir = $(rubylibprefix)/$(ruby_version)
|
40
|
+
sitearchincludedir = $(includedir)/$(sitearch)
|
41
|
+
archincludedir = $(includedir)/$(arch)
|
42
|
+
sitearchlibdir = $(libdir)/$(sitearch)
|
43
|
+
archlibdir = $(libdir)/$(arch)
|
44
|
+
ridir = $(datarootdir)/$(RI_BASE_NAME)
|
45
|
+
mandir = $(datarootdir)/man
|
46
|
+
localedir = $(datarootdir)/locale
|
47
|
+
libdir = $(exec_prefix)/lib
|
48
|
+
psdir = $(docdir)
|
49
|
+
pdfdir = $(docdir)
|
50
|
+
dvidir = $(docdir)
|
51
|
+
htmldir = $(docdir)
|
52
|
+
infodir = $(datarootdir)/info
|
53
|
+
docdir = $(datarootdir)/doc/$(PACKAGE)
|
54
|
+
oldincludedir = $(DESTDIR)/usr/include
|
55
|
+
includedir = $(SDKROOT)$(prefix)/include
|
56
|
+
runstatedir = $(localstatedir)/run
|
57
|
+
localstatedir = $(prefix)/var
|
58
|
+
sharedstatedir = $(prefix)/com
|
59
|
+
sysconfdir = $(prefix)/etc
|
60
|
+
datadir = $(datarootdir)
|
61
|
+
datarootdir = $(prefix)/share
|
62
|
+
libexecdir = $(exec_prefix)/libexec
|
63
|
+
sbindir = $(exec_prefix)/sbin
|
64
|
+
bindir = $(exec_prefix)/bin
|
65
|
+
archdir = $(rubyarchdir)
|
66
|
+
|
67
|
+
|
68
|
+
CC_WRAPPER =
|
69
|
+
CC = clang
|
70
|
+
CXX = clang++
|
71
|
+
LIBRUBY = $(LIBRUBY_A)
|
72
|
+
LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
|
73
|
+
LIBRUBYARG_SHARED =
|
74
|
+
LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static -framework CoreFoundation $(MAINLIBS)
|
75
|
+
empty =
|
76
|
+
OUTFLAG = -o $(empty)
|
77
|
+
COUTFLAG = -o $(empty)
|
78
|
+
CSRCFLAG = $(empty)
|
79
|
+
|
80
|
+
RUBY_EXTCONF_H = extconf.h
|
81
|
+
cflags = -fdeclspec $(optflags) $(debugflags) $(warnflags)
|
82
|
+
cxxflags =
|
83
|
+
optflags = -O3 -fno-fast-math
|
84
|
+
debugflags = -ggdb3
|
85
|
+
warnflags = -Wall -Wextra -Wextra-tokens -Wdeprecated-declarations -Wdivision-by-zero -Wdiv-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wold-style-definition -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wmisleading-indentation -Wundef
|
86
|
+
cppflags =
|
87
|
+
CCDLFLAGS = -fno-common
|
88
|
+
CFLAGS = $(CCDLFLAGS) $(cflags) -pipe -Wall -Wno-unknown-pragmas -std=c99 $(ARCH_FLAG)
|
89
|
+
INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir)
|
90
|
+
DEFS =
|
91
|
+
CPPFLAGS = -DRUBY_EXTCONF_H=\"$(RUBY_EXTCONF_H)\" -I/opt/local/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT $(DEFS) $(cppflags)
|
92
|
+
CXXFLAGS = $(CCDLFLAGS) -fdeclspec $(ARCH_FLAG)
|
93
|
+
ldflags = -L. -fstack-protector-strong -L/opt/local/lib
|
94
|
+
dldflags = -L/opt/local/lib -Wl,-undefined,dynamic_lookup -bundle_loader '$(BUILTRUBY)'
|
95
|
+
ARCH_FLAG = -arch arm64
|
96
|
+
DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG)
|
97
|
+
LDSHARED = $(CC) -dynamic -bundle
|
98
|
+
LDSHAREDXX = $(CXX) -dynamic -bundle
|
99
|
+
AR = ar
|
100
|
+
EXEEXT =
|
101
|
+
|
102
|
+
RUBY_INSTALL_NAME = $(RUBY_BASE_NAME)
|
103
|
+
RUBY_SO_NAME = ruby.3.3
|
104
|
+
RUBYW_INSTALL_NAME =
|
105
|
+
RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version)
|
106
|
+
RUBYW_BASE_NAME = rubyw
|
107
|
+
RUBY_BASE_NAME = ruby
|
108
|
+
|
109
|
+
arch = arm64-darwin24
|
110
|
+
sitearch = $(arch)
|
111
|
+
ruby_version = 3.3.0
|
112
|
+
ruby = $(bindir)/$(RUBY_BASE_NAME)
|
113
|
+
RUBY = $(ruby)
|
114
|
+
BUILTRUBY = $(bindir)/$(RUBY_BASE_NAME)
|
115
|
+
ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h $(RUBY_EXTCONF_H)
|
116
|
+
|
117
|
+
RM = rm -f
|
118
|
+
RM_RF = rm -fr
|
119
|
+
RMDIRS = rmdir -p
|
120
|
+
MAKEDIRS = /opt/local/bin/gmkdir -p
|
121
|
+
INSTALL = /opt/local/bin/ginstall -c
|
122
|
+
INSTALL_PROG = $(INSTALL) -m 0755
|
123
|
+
INSTALL_DATA = $(INSTALL) -m 644
|
124
|
+
COPY = cp
|
125
|
+
TOUCH = exit >
|
126
|
+
|
127
|
+
#### End of system configuration section. ####
|
128
|
+
|
129
|
+
preload =
|
130
|
+
libpath = . $(libdir) /opt/local/lib
|
131
|
+
LIBPATH = -L. -L$(libdir) -L/opt/local/lib
|
132
|
+
DEFFILE =
|
133
|
+
|
134
|
+
CLEANFILES = mkmf.log
|
135
|
+
DISTCLEANFILES =
|
136
|
+
DISTCLEANDIRS =
|
137
|
+
|
138
|
+
extout =
|
139
|
+
extout_prefix =
|
140
|
+
target_prefix =
|
141
|
+
LOCAL_LIBS =
|
142
|
+
LIBS = -lpthread
|
143
|
+
ORIG_SRCS =
|
144
|
+
SRCS = $(ORIG_SRCS) profiler.c time.c fiber.c capture.c
|
145
|
+
OBJS = profiler.o time.o fiber.o capture.o
|
146
|
+
HDRS = $(srcdir)/extconf.h
|
147
|
+
LOCAL_HDRS =
|
148
|
+
TARGET = Fiber_Profiler
|
149
|
+
TARGET_NAME = Fiber_Profiler
|
150
|
+
TARGET_ENTRY = Init_$(TARGET_NAME)
|
151
|
+
DLLIB = $(TARGET).bundle
|
152
|
+
EXTSTATIC =
|
153
|
+
STATIC_LIB =
|
154
|
+
|
155
|
+
TIMESTAMP_DIR = .
|
156
|
+
BINDIR = $(bindir)
|
157
|
+
RUBYCOMMONDIR = $(sitedir)$(target_prefix)
|
158
|
+
RUBYLIBDIR = $(sitelibdir)$(target_prefix)
|
159
|
+
RUBYARCHDIR = $(sitearchdir)$(target_prefix)
|
160
|
+
HDRDIR = $(sitehdrdir)$(target_prefix)
|
161
|
+
ARCHHDRDIR = $(sitearchhdrdir)$(target_prefix)
|
162
|
+
TARGET_SO_DIR =
|
163
|
+
TARGET_SO = $(TARGET_SO_DIR)$(DLLIB)
|
164
|
+
CLEANLIBS = $(TARGET_SO) $(TARGET_SO).dSYM
|
165
|
+
CLEANOBJS = $(OBJS) *.bak
|
166
|
+
TARGET_SO_DIR_TIMESTAMP = $(TIMESTAMP_DIR)/.sitearchdir.time
|
167
|
+
|
168
|
+
all: $(DLLIB)
|
169
|
+
static: $(STATIC_LIB)
|
170
|
+
.PHONY: all install static install-so install-rb
|
171
|
+
.PHONY: clean clean-so clean-static clean-rb
|
172
|
+
|
173
|
+
clean-static::
|
174
|
+
clean-rb-default::
|
175
|
+
clean-rb::
|
176
|
+
clean-so::
|
177
|
+
clean: clean-so clean-static clean-rb-default clean-rb
|
178
|
+
-$(Q)$(RM_RF) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time
|
179
|
+
|
180
|
+
distclean-rb-default::
|
181
|
+
distclean-rb::
|
182
|
+
distclean-so::
|
183
|
+
distclean-static::
|
184
|
+
distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb
|
185
|
+
-$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
|
186
|
+
-$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
|
187
|
+
-$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true
|
188
|
+
|
189
|
+
realclean: distclean
|
190
|
+
install: install-so install-rb
|
191
|
+
|
192
|
+
install-so: $(DLLIB) $(TARGET_SO_DIR_TIMESTAMP)
|
193
|
+
$(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
|
194
|
+
clean-static::
|
195
|
+
-$(Q)$(RM) $(STATIC_LIB)
|
196
|
+
install-rb: pre-install-rb do-install-rb install-rb-default
|
197
|
+
install-rb-default: pre-install-rb-default do-install-rb-default
|
198
|
+
pre-install-rb: Makefile
|
199
|
+
pre-install-rb-default: Makefile
|
200
|
+
do-install-rb:
|
201
|
+
do-install-rb-default:
|
202
|
+
pre-install-rb-default:
|
203
|
+
@$(NULLCMD)
|
204
|
+
$(TARGET_SO_DIR_TIMESTAMP):
|
205
|
+
$(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR)
|
206
|
+
$(Q) $(TOUCH) $@
|
207
|
+
|
208
|
+
site-install: site-install-so site-install-rb
|
209
|
+
site-install-so: install-so
|
210
|
+
site-install-rb: install-rb
|
211
|
+
|
212
|
+
.SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S
|
213
|
+
|
214
|
+
.cc.o:
|
215
|
+
$(ECHO) compiling $(<)
|
216
|
+
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
|
217
|
+
|
218
|
+
.cc.S:
|
219
|
+
$(ECHO) translating $(<)
|
220
|
+
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
|
221
|
+
|
222
|
+
.mm.o:
|
223
|
+
$(ECHO) compiling $(<)
|
224
|
+
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
|
225
|
+
|
226
|
+
.mm.S:
|
227
|
+
$(ECHO) translating $(<)
|
228
|
+
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
|
229
|
+
|
230
|
+
.cxx.o:
|
231
|
+
$(ECHO) compiling $(<)
|
232
|
+
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
|
233
|
+
|
234
|
+
.cxx.S:
|
235
|
+
$(ECHO) translating $(<)
|
236
|
+
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
|
237
|
+
|
238
|
+
.cpp.o:
|
239
|
+
$(ECHO) compiling $(<)
|
240
|
+
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
|
241
|
+
|
242
|
+
.cpp.S:
|
243
|
+
$(ECHO) translating $(<)
|
244
|
+
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
|
245
|
+
|
246
|
+
.c.o:
|
247
|
+
$(ECHO) compiling $(<)
|
248
|
+
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
|
249
|
+
|
250
|
+
.c.S:
|
251
|
+
$(ECHO) translating $(<)
|
252
|
+
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
|
253
|
+
|
254
|
+
.m.o:
|
255
|
+
$(ECHO) compiling $(<)
|
256
|
+
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
|
257
|
+
|
258
|
+
.m.S:
|
259
|
+
$(ECHO) translating $(<)
|
260
|
+
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
|
261
|
+
|
262
|
+
$(TARGET_SO): $(OBJS) Makefile
|
263
|
+
$(ECHO) linking shared-object $(DLLIB)
|
264
|
+
-$(Q)$(RM) $(@)
|
265
|
+
$(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)
|
266
|
+
$(Q) $(POSTLINK)
|
267
|
+
|
268
|
+
|
269
|
+
|
270
|
+
$(OBJS): $(HDRS) $(ruby_headers)
|
data/ext/capture.o
ADDED
Binary file
|
data/ext/extconf.h
ADDED
@@ -19,6 +19,7 @@ enum {
|
|
19
19
|
|
20
20
|
int Fiber_Profiler_capture_p = 0;
|
21
21
|
double Fiber_Profiler_Capture_stall_threshold = 0.01;
|
22
|
+
double Fiber_Profiler_Capture_filter_threshold = 0.001;
|
22
23
|
int Fiber_Profiler_Capture_track_calls = 1;
|
23
24
|
double Fiber_Profiler_Capture_sample_rate = 1;
|
24
25
|
|
@@ -65,7 +66,7 @@ void Fiber_Profiler_Stream_free(struct Fiber_Profiler_Stream *stream) {
|
|
65
66
|
}
|
66
67
|
|
67
68
|
struct Fiber_Profiler_Capture;
|
68
|
-
typedef void(*Fiber_Profiler_Stream_Print)(struct Fiber_Profiler_Capture*, FILE* restrict);
|
69
|
+
typedef void(*Fiber_Profiler_Stream_Print)(struct Fiber_Profiler_Capture*, FILE* restrict, double duration);
|
69
70
|
|
70
71
|
struct Fiber_Profiler_Capture {
|
71
72
|
// The threshold in seconds, which determines when a fiber is considered to have stalled the event loop.
|
@@ -74,7 +75,7 @@ struct Fiber_Profiler_Capture {
|
|
74
75
|
// Whether or not to track calls.
|
75
76
|
int track_calls;
|
76
77
|
|
77
|
-
// The sample rate of the
|
78
|
+
// The sample rate of the capture, as a fraction of 1.0, which controls how often the profiler will sample between fiber context switches.
|
78
79
|
double sample_rate;
|
79
80
|
|
80
81
|
// Calls that are shorter than this filter threshold will be ignored.
|
@@ -89,6 +90,15 @@ struct Fiber_Profiler_Capture {
|
|
89
90
|
// The stream buffer used for printing.
|
90
91
|
struct Fiber_Profiler_Stream stream;
|
91
92
|
|
93
|
+
// How many fiber context switches have been encountered. Not all of them will be sampled, based on the sample rate.
|
94
|
+
size_t switches;
|
95
|
+
|
96
|
+
// How many samples have been taken, not all of them will be stalls, based on the stall threshold.
|
97
|
+
size_t samples;
|
98
|
+
|
99
|
+
// The number of stalls encountered and printed.
|
100
|
+
size_t stalls;
|
101
|
+
|
92
102
|
// Whether or not the profiler is currently running.
|
93
103
|
int running;
|
94
104
|
|
@@ -98,14 +108,11 @@ struct Fiber_Profiler_Capture {
|
|
98
108
|
// Whether or not to capture call data.
|
99
109
|
int capture;
|
100
110
|
|
101
|
-
// The number of stalls encountered.
|
102
|
-
size_t stalls;
|
103
|
-
|
104
111
|
// The start time of the profile.
|
105
112
|
struct timespec start_time;
|
106
113
|
|
107
|
-
// The
|
108
|
-
struct timespec
|
114
|
+
// The time of the last fiber switch that was sampled.
|
115
|
+
struct timespec switch_time;
|
109
116
|
|
110
117
|
// The depth of the call stack (can be negative).
|
111
118
|
int nesting;
|
@@ -120,13 +127,6 @@ struct Fiber_Profiler_Capture {
|
|
120
127
|
struct Fiber_Profiler_Deque calls;
|
121
128
|
};
|
122
129
|
|
123
|
-
void Fiber_Profiler_Capture_reset(struct Fiber_Profiler_Capture *profiler) {
|
124
|
-
profiler->nesting = 0;
|
125
|
-
profiler->nesting_minimum = 0;
|
126
|
-
profiler->current = NULL;
|
127
|
-
Fiber_Profiler_Deque_truncate(&profiler->calls);
|
128
|
-
}
|
129
|
-
|
130
130
|
void Fiber_Profiler_Capture_Call_initialize(void *element) {
|
131
131
|
struct Fiber_Profiler_Capture_Call *call = element;
|
132
132
|
|
@@ -155,43 +155,43 @@ void Fiber_Profiler_Capture_Call_free(void *element) {
|
|
155
155
|
}
|
156
156
|
|
157
157
|
static void Fiber_Profiler_Capture_mark(void *ptr) {
|
158
|
-
struct Fiber_Profiler_Capture *
|
158
|
+
struct Fiber_Profiler_Capture *capture = (struct Fiber_Profiler_Capture*)ptr;
|
159
159
|
|
160
|
-
rb_gc_mark_movable(
|
161
|
-
rb_gc_mark_movable(
|
160
|
+
rb_gc_mark_movable(capture->thread);
|
161
|
+
rb_gc_mark_movable(capture->output);
|
162
162
|
|
163
163
|
// If `klass` is stored as a VALUE in calls, we need to mark them here:
|
164
|
-
Fiber_Profiler_Deque_each(&
|
164
|
+
Fiber_Profiler_Deque_each(&capture->calls, struct Fiber_Profiler_Capture_Call, call) {
|
165
165
|
rb_gc_mark_movable(call->klass);
|
166
166
|
}
|
167
167
|
}
|
168
168
|
|
169
169
|
static void Fiber_Profiler_Capture_compact(void *ptr) {
|
170
|
-
struct Fiber_Profiler_Capture *
|
170
|
+
struct Fiber_Profiler_Capture *capture = (struct Fiber_Profiler_Capture*)ptr;
|
171
171
|
|
172
|
-
|
173
|
-
|
172
|
+
capture->thread = rb_gc_location(capture->thread);
|
173
|
+
capture->output = rb_gc_location(capture->output);
|
174
174
|
|
175
175
|
// If `klass` is stored as a VALUE in calls, we need to update their locations here:
|
176
|
-
Fiber_Profiler_Deque_each(&
|
176
|
+
Fiber_Profiler_Deque_each(&capture->calls, struct Fiber_Profiler_Capture_Call, call) {
|
177
177
|
call->klass = rb_gc_location(call->klass);
|
178
178
|
}
|
179
179
|
}
|
180
180
|
|
181
181
|
static void Fiber_Profiler_Capture_free(void *ptr) {
|
182
|
-
struct Fiber_Profiler_Capture *
|
182
|
+
struct Fiber_Profiler_Capture *capture = (struct Fiber_Profiler_Capture*)ptr;
|
183
183
|
|
184
|
-
RUBY_ASSERT(
|
184
|
+
RUBY_ASSERT(capture->running == 0);
|
185
185
|
|
186
|
-
Fiber_Profiler_Stream_free(&
|
187
|
-
Fiber_Profiler_Deque_free(&
|
186
|
+
Fiber_Profiler_Stream_free(&capture->stream);
|
187
|
+
Fiber_Profiler_Deque_free(&capture->calls);
|
188
188
|
|
189
|
-
free(
|
189
|
+
free(capture);
|
190
190
|
}
|
191
191
|
|
192
192
|
static size_t Fiber_Profiler_Capture_memsize(const void *ptr) {
|
193
|
-
const struct Fiber_Profiler_Capture *
|
194
|
-
return sizeof(*
|
193
|
+
const struct Fiber_Profiler_Capture *capture = (const struct Fiber_Profiler_Capture*)ptr;
|
194
|
+
return sizeof(*capture) + Fiber_Profiler_Deque_memory_size(&capture->calls);
|
195
195
|
}
|
196
196
|
|
197
197
|
const rb_data_type_t Fiber_Profiler_Capture_Type = {
|
@@ -206,9 +206,9 @@ const rb_data_type_t Fiber_Profiler_Capture_Type = {
|
|
206
206
|
};
|
207
207
|
|
208
208
|
struct Fiber_Profiler_Capture *Fiber_Profiler_Capture_get(VALUE self) {
|
209
|
-
struct Fiber_Profiler_Capture *
|
210
|
-
TypedData_Get_Struct(self, struct Fiber_Profiler_Capture, &Fiber_Profiler_Capture_Type,
|
211
|
-
return
|
209
|
+
struct Fiber_Profiler_Capture *capture;
|
210
|
+
TypedData_Get_Struct(self, struct Fiber_Profiler_Capture, &Fiber_Profiler_Capture_Type, capture);
|
211
|
+
return capture;
|
212
212
|
}
|
213
213
|
|
214
214
|
int IO_istty(VALUE io) {
|
@@ -220,78 +220,83 @@ int IO_istty(VALUE io) {
|
|
220
220
|
return 0;
|
221
221
|
}
|
222
222
|
|
223
|
-
void Fiber_Profiler_Capture_print_tty(struct Fiber_Profiler_Capture *
|
224
|
-
void Fiber_Profiler_Capture_print_json(struct Fiber_Profiler_Capture *
|
223
|
+
void Fiber_Profiler_Capture_print_tty(struct Fiber_Profiler_Capture *capture, FILE *restrict stream, double duration);
|
224
|
+
void Fiber_Profiler_Capture_print_json(struct Fiber_Profiler_Capture *capture, FILE *restrict stream, double duration);
|
225
225
|
|
226
|
-
static void Fiber_Profiler_Capture_output_set(struct Fiber_Profiler_Capture *
|
227
|
-
|
226
|
+
static void Fiber_Profiler_Capture_output_set(struct Fiber_Profiler_Capture *capture, VALUE output) {
|
227
|
+
capture->output = output;
|
228
228
|
|
229
|
-
if (IO_istty(
|
230
|
-
|
229
|
+
if (IO_istty(capture->output)) {
|
230
|
+
capture->print = &Fiber_Profiler_Capture_print_tty;
|
231
231
|
} else {
|
232
|
-
|
232
|
+
capture->print = &Fiber_Profiler_Capture_print_json;
|
233
233
|
}
|
234
234
|
}
|
235
235
|
|
236
236
|
VALUE Fiber_Profiler_Capture_allocate(VALUE klass) {
|
237
|
-
struct Fiber_Profiler_Capture *
|
237
|
+
struct Fiber_Profiler_Capture *capture = ALLOC(struct Fiber_Profiler_Capture);
|
238
238
|
|
239
239
|
// Initialize the profiler state:
|
240
|
-
Fiber_Profiler_Stream_initialize(&
|
241
|
-
|
240
|
+
Fiber_Profiler_Stream_initialize(&capture->stream);
|
241
|
+
capture->output = Qnil;
|
242
242
|
|
243
|
-
|
244
|
-
|
243
|
+
capture->switches = 0;
|
244
|
+
capture->samples = 0;
|
245
|
+
capture->stalls = 0;
|
245
246
|
|
246
|
-
|
247
|
-
|
248
|
-
profiler->nesting = 0;
|
249
|
-
profiler->nesting_minimum = 0;
|
250
|
-
profiler->current = NULL;
|
247
|
+
capture->running = 0;
|
248
|
+
capture->thread = Qnil;
|
251
249
|
|
252
|
-
|
253
|
-
|
254
|
-
|
250
|
+
capture->capture = 0;
|
251
|
+
capture->nesting = 0;
|
252
|
+
capture->nesting_minimum = 0;
|
253
|
+
capture->current = NULL;
|
255
254
|
|
256
|
-
|
257
|
-
|
255
|
+
capture->stall_threshold = Fiber_Profiler_Capture_stall_threshold;
|
256
|
+
capture->filter_threshold = Fiber_Profiler_Capture_filter_threshold;
|
257
|
+
capture->track_calls = Fiber_Profiler_Capture_track_calls;
|
258
|
+
capture->sample_rate = Fiber_Profiler_Capture_sample_rate;
|
258
259
|
|
259
|
-
|
260
|
-
|
260
|
+
capture->calls.element_initialize = (void (*)(void*))Fiber_Profiler_Capture_Call_initialize;
|
261
|
+
capture->calls.element_free = (void (*)(void*))Fiber_Profiler_Capture_Call_free;
|
261
262
|
|
262
|
-
Fiber_Profiler_Deque_initialize(&
|
263
|
-
Fiber_Profiler_Deque_reserve_default(&
|
263
|
+
Fiber_Profiler_Deque_initialize(&capture->calls, sizeof(struct Fiber_Profiler_Capture_Call));
|
264
|
+
Fiber_Profiler_Deque_reserve_default(&capture->calls);
|
264
265
|
|
265
|
-
return TypedData_Wrap_Struct(klass, &Fiber_Profiler_Capture_Type,
|
266
|
+
return TypedData_Wrap_Struct(klass, &Fiber_Profiler_Capture_Type, capture);
|
266
267
|
}
|
267
268
|
|
268
|
-
ID Fiber_Profiler_Capture_initialize_options[
|
269
|
+
ID Fiber_Profiler_Capture_initialize_options[5];
|
269
270
|
|
270
271
|
VALUE Fiber_Profiler_Capture_initialize(int argc, VALUE *argv, VALUE self) {
|
271
|
-
struct Fiber_Profiler_Capture *
|
272
|
+
struct Fiber_Profiler_Capture *capture = Fiber_Profiler_Capture_get(self);
|
272
273
|
|
273
|
-
VALUE arguments[
|
274
|
+
VALUE arguments[5] = {0};
|
274
275
|
VALUE options = Qnil;
|
275
276
|
rb_scan_args(argc, argv, ":", &options);
|
276
|
-
rb_get_kwargs(options, Fiber_Profiler_Capture_initialize_options, 0,
|
277
|
+
rb_get_kwargs(options, Fiber_Profiler_Capture_initialize_options, 0, 5, arguments);
|
277
278
|
|
278
279
|
if (arguments[0] != Qundef) {
|
279
|
-
|
280
|
+
capture->stall_threshold = NUM2DBL(arguments[0]);
|
280
281
|
}
|
281
282
|
|
282
283
|
if (arguments[1] != Qundef) {
|
283
|
-
|
284
|
+
capture->filter_threshold = NUM2DBL(arguments[1]);
|
284
285
|
}
|
285
286
|
|
286
287
|
if (arguments[2] != Qundef) {
|
287
|
-
|
288
|
+
capture->track_calls = RB_TEST(arguments[2]);
|
288
289
|
}
|
289
290
|
|
290
291
|
if (arguments[3] != Qundef) {
|
291
|
-
|
292
|
+
capture->sample_rate = NUM2DBL(arguments[3]);
|
293
|
+
}
|
294
|
+
|
295
|
+
if (arguments[4] != Qundef) {
|
296
|
+
Fiber_Profiler_Capture_output_set(capture, arguments[4]);
|
292
297
|
} else {
|
293
298
|
// Initialize the profiler output - we dup `rb_stderr` because the profiler may otherwise run into synchronization issues with other uses of `rb_stderr`:
|
294
|
-
Fiber_Profiler_Capture_output_set(
|
299
|
+
Fiber_Profiler_Capture_output_set(capture, rb_obj_dup(rb_stderr));
|
295
300
|
}
|
296
301
|
|
297
302
|
return self;
|
@@ -302,10 +307,10 @@ VALUE Fiber_Profiler_Capture_default(VALUE klass) {
|
|
302
307
|
return Qnil;
|
303
308
|
}
|
304
309
|
|
305
|
-
VALUE
|
306
|
-
Fiber_Profiler_Capture_initialize(0, NULL,
|
310
|
+
VALUE capture = Fiber_Profiler_Capture_allocate(klass);
|
311
|
+
Fiber_Profiler_Capture_initialize(0, NULL, capture);
|
307
312
|
|
308
|
-
return
|
313
|
+
return capture;
|
309
314
|
}
|
310
315
|
|
311
316
|
int event_flag_call_p(rb_event_flag_t event_flags) {
|
@@ -332,19 +337,19 @@ const char *event_flag_name(rb_event_flag_t event_flag) {
|
|
332
337
|
}
|
333
338
|
}
|
334
339
|
|
335
|
-
static struct Fiber_Profiler_Capture_Call* Fiber_Profiler_Capture_Call_new(struct Fiber_Profiler_Capture *
|
336
|
-
struct Fiber_Profiler_Capture_Call *call = Fiber_Profiler_Deque_push(&
|
340
|
+
static struct Fiber_Profiler_Capture_Call* Fiber_Profiler_Capture_Call_new(struct Fiber_Profiler_Capture *capture, rb_event_flag_t event_flag, ID id, VALUE klass) {
|
341
|
+
struct Fiber_Profiler_Capture_Call *call = Fiber_Profiler_Deque_push(&capture->calls);
|
337
342
|
|
338
343
|
call->event_flag = event_flag;
|
339
344
|
|
340
|
-
call->parent =
|
345
|
+
call->parent = capture->current;
|
341
346
|
if (call->parent) {
|
342
347
|
call->parent->children += 1;
|
343
348
|
}
|
344
349
|
|
345
|
-
|
350
|
+
capture->current = call;
|
346
351
|
|
347
|
-
call->nesting =
|
352
|
+
call->nesting = capture->nesting;
|
348
353
|
|
349
354
|
if (id) {
|
350
355
|
call->id = id;
|
@@ -363,15 +368,20 @@ static struct Fiber_Profiler_Capture_Call* Fiber_Profiler_Capture_Call_new(struc
|
|
363
368
|
}
|
364
369
|
|
365
370
|
// Finish the call by calculating the duration and filtering it if necessary.
|
366
|
-
int Fiber_Profiler_Capture_Call_finish(struct Fiber_Profiler_Capture *
|
371
|
+
int Fiber_Profiler_Capture_Call_finish(struct Fiber_Profiler_Capture *capture, struct Fiber_Profiler_Capture_Call *call) {
|
367
372
|
// Don't filter calls if we're not running:
|
368
373
|
if (DEBUG_FILTERED) return 0;
|
369
374
|
|
370
|
-
if (call->
|
375
|
+
if (event_flag_return_p(call->event_flag)) {
|
376
|
+
// We don't filter return statements, as they are always part of the call stack:
|
377
|
+
return 0;
|
378
|
+
}
|
379
|
+
|
380
|
+
if (call->duration < capture->filter_threshold) {
|
371
381
|
// We can only remove calls from the end of the deque, otherwise they might be referenced by other calls:
|
372
|
-
if (call == Fiber_Profiler_Deque_last(&
|
373
|
-
if (
|
374
|
-
|
382
|
+
if (call == Fiber_Profiler_Deque_last(&capture->calls)) {
|
383
|
+
if (capture->current == call) {
|
384
|
+
capture->current = call->parent;
|
375
385
|
}
|
376
386
|
|
377
387
|
if (call->parent) {
|
@@ -380,7 +390,7 @@ int Fiber_Profiler_Capture_Call_finish(struct Fiber_Profiler_Capture *profiler,
|
|
380
390
|
call->parent = NULL;
|
381
391
|
}
|
382
392
|
|
383
|
-
Fiber_Profiler_Deque_pop(&
|
393
|
+
Fiber_Profiler_Deque_pop(&capture->calls);
|
384
394
|
|
385
395
|
return 1;
|
386
396
|
}
|
@@ -389,10 +399,11 @@ int Fiber_Profiler_Capture_Call_finish(struct Fiber_Profiler_Capture *profiler,
|
|
389
399
|
return 0;
|
390
400
|
}
|
391
401
|
|
402
|
+
// Whether to highlight a call as expensive. This is purely cosmetic.
|
392
403
|
static const double Fiber_Profiler_Capture_Call_EXPENSIVE_THRESHOLD = 0.2;
|
393
404
|
|
394
|
-
int Fiber_Profiler_Capture_Call_expensive_p(struct Fiber_Profiler_Capture_Call *call, double
|
395
|
-
if (call->duration >
|
405
|
+
int Fiber_Profiler_Capture_Call_expensive_p(struct Fiber_Profiler_Capture *capture, struct Fiber_Profiler_Capture_Call *call, double duration) {
|
406
|
+
if (call->duration > duration * Fiber_Profiler_Capture_Call_EXPENSIVE_THRESHOLD) {
|
396
407
|
return 1;
|
397
408
|
}
|
398
409
|
|
@@ -400,57 +411,63 @@ int Fiber_Profiler_Capture_Call_expensive_p(struct Fiber_Profiler_Capture_Call *
|
|
400
411
|
}
|
401
412
|
|
402
413
|
static void Fiber_Profiler_Capture_callback(rb_event_flag_t event_flag, VALUE data, VALUE self, ID id, VALUE klass) {
|
403
|
-
struct Fiber_Profiler_Capture *
|
414
|
+
struct Fiber_Profiler_Capture *capture = Fiber_Profiler_Capture_get(data);
|
404
415
|
|
405
416
|
// We don't want to capture data if we're not running:
|
406
|
-
if (!
|
417
|
+
if (!capture->capture) return;
|
407
418
|
|
408
419
|
if (event_flag_call_p(event_flag)) {
|
409
|
-
struct Fiber_Profiler_Capture_Call *call = Fiber_Profiler_Capture_Call_new(
|
420
|
+
struct Fiber_Profiler_Capture_Call *call = Fiber_Profiler_Capture_Call_new(capture, event_flag, id, klass);
|
410
421
|
|
411
|
-
|
422
|
+
capture->nesting += 1;
|
412
423
|
|
413
424
|
Fiber_Profiler_Time_current(&call->enter_time);
|
414
425
|
}
|
415
426
|
|
416
427
|
else if (event_flag_return_p(event_flag)) {
|
417
|
-
struct Fiber_Profiler_Capture_Call *call =
|
428
|
+
struct Fiber_Profiler_Capture_Call *call = capture->current;
|
418
429
|
|
419
430
|
// We may encounter returns without a preceeding call. This isn't an error, but we should pretend like the call started at the beginning of the profiling session:
|
420
431
|
if (call == NULL) {
|
421
|
-
struct Fiber_Profiler_Capture_Call *last_call = Fiber_Profiler_Deque_last(&
|
422
|
-
call = Fiber_Profiler_Capture_Call_new(
|
432
|
+
struct Fiber_Profiler_Capture_Call *last_call = Fiber_Profiler_Deque_last(&capture->calls);
|
433
|
+
call = Fiber_Profiler_Capture_Call_new(capture, event_flag, id, klass);
|
434
|
+
|
435
|
+
struct timespec call_time;
|
423
436
|
|
424
437
|
if (last_call) {
|
425
|
-
|
438
|
+
call_time = last_call->enter_time;
|
426
439
|
} else {
|
427
|
-
|
440
|
+
call_time = capture->switch_time;
|
428
441
|
}
|
442
|
+
|
443
|
+
// For return statements, we record the current time as the enter time:
|
444
|
+
Fiber_Profiler_Time_current(&call->enter_time);
|
445
|
+
call->duration = Fiber_Profiler_Time_delta(&call_time, &call->enter_time);
|
446
|
+
} else {
|
447
|
+
call->duration = Fiber_Profiler_Time_delta_current(&call->enter_time);
|
429
448
|
}
|
430
449
|
|
431
|
-
|
432
|
-
|
433
|
-
profiler->current = call->parent;
|
450
|
+
capture->current = call->parent;
|
434
451
|
|
435
452
|
// We may encounter returns without a preceeding call.
|
436
|
-
|
453
|
+
capture->nesting -= 1;
|
437
454
|
|
438
455
|
// We need to keep track of how deep the call stack goes:
|
439
|
-
if (
|
440
|
-
|
456
|
+
if (capture->nesting < capture->nesting_minimum) {
|
457
|
+
capture->nesting_minimum = capture->nesting;
|
441
458
|
}
|
442
459
|
|
443
|
-
Fiber_Profiler_Capture_Call_finish(
|
460
|
+
Fiber_Profiler_Capture_Call_finish(capture, call);
|
444
461
|
}
|
445
462
|
|
446
463
|
else {
|
447
|
-
struct Fiber_Profiler_Capture_Call *last_call = Fiber_Profiler_Deque_last(&
|
448
|
-
struct Fiber_Profiler_Capture_Call *call = Fiber_Profiler_Capture_Call_new(
|
464
|
+
struct Fiber_Profiler_Capture_Call *last_call = Fiber_Profiler_Deque_last(&capture->calls);
|
465
|
+
struct Fiber_Profiler_Capture_Call *call = Fiber_Profiler_Capture_Call_new(capture, event_flag, id, klass);
|
449
466
|
|
450
467
|
if (last_call) {
|
451
468
|
call->enter_time = last_call->enter_time;
|
452
469
|
} else {
|
453
|
-
call->enter_time =
|
470
|
+
call->enter_time = capture->switch_time;
|
454
471
|
}
|
455
472
|
|
456
473
|
call->duration = Fiber_Profiler_Time_delta_current(&call->enter_time);
|
@@ -458,35 +475,35 @@ static void Fiber_Profiler_Capture_callback(rb_event_flag_t event_flag, VALUE da
|
|
458
475
|
}
|
459
476
|
|
460
477
|
void Fiber_Profiler_Capture_pause(VALUE self) {
|
461
|
-
struct Fiber_Profiler_Capture *
|
478
|
+
struct Fiber_Profiler_Capture *capture = Fiber_Profiler_Capture_get(self);
|
462
479
|
|
463
|
-
if (!
|
480
|
+
if (!capture->capture) return;
|
481
|
+
capture->capture = 0;
|
464
482
|
|
465
|
-
|
466
|
-
|
467
|
-
|
483
|
+
if (capture->track_calls) {
|
484
|
+
rb_thread_remove_event_hook_with_data(capture->thread, Fiber_Profiler_Capture_callback, self);
|
485
|
+
}
|
468
486
|
}
|
469
487
|
|
470
488
|
void Fiber_Profiler_Capture_resume(VALUE self) {
|
471
|
-
struct Fiber_Profiler_Capture *
|
472
|
-
|
473
|
-
if (profiler->capture) return;
|
489
|
+
struct Fiber_Profiler_Capture *capture = Fiber_Profiler_Capture_get(self);
|
474
490
|
|
475
|
-
|
491
|
+
if (capture->capture) return;
|
492
|
+
capture->capture = 1;
|
493
|
+
capture->samples += 1;
|
476
494
|
|
477
|
-
|
478
|
-
|
479
|
-
if (profiler->track_calls) {
|
480
|
-
// event_flags |= RUBY_EVENT_LINE;
|
495
|
+
if (capture->track_calls) {
|
496
|
+
rb_event_flag_t event_flags = 0;
|
481
497
|
|
498
|
+
// event_flags |= RUBY_EVENT_LINE;
|
482
499
|
event_flags |= RUBY_EVENT_CALL | RUBY_EVENT_RETURN;
|
483
500
|
event_flags |= RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN;
|
484
501
|
event_flags |= RUBY_EVENT_B_CALL | RUBY_EVENT_B_RETURN;
|
502
|
+
|
503
|
+
// CRuby will raise an exception if you try to add "INTERNAL_EVENT" hooks at the same time as other hooks, so we do it in two calls:
|
504
|
+
rb_thread_add_event_hook(capture->thread, Fiber_Profiler_Capture_callback, event_flags, self);
|
505
|
+
rb_thread_add_event_hook(capture->thread, Fiber_Profiler_Capture_callback, RUBY_INTERNAL_EVENT_GC_START | RUBY_INTERNAL_EVENT_GC_END_SWEEP, self);
|
485
506
|
}
|
486
|
-
|
487
|
-
// CRuby will raise an exception if you try to add "INTERNAL_EVENT" hooks at the same time as other hooks, so we do it in two calls:
|
488
|
-
rb_thread_add_event_hook(profiler->thread, Fiber_Profiler_Capture_callback, event_flags, self);
|
489
|
-
rb_thread_add_event_hook(profiler->thread, Fiber_Profiler_Capture_callback, RUBY_INTERNAL_EVENT_GC_START | RUBY_INTERNAL_EVENT_GC_END_SWEEP, self);
|
490
507
|
}
|
491
508
|
|
492
509
|
void Fiber_Profiler_Capture_fiber_switch(VALUE self);
|
@@ -495,68 +512,70 @@ void Fiber_Profiler_Capture_fiber_switch_callback(rb_event_flag_t event_flag, VA
|
|
495
512
|
Fiber_Profiler_Capture_fiber_switch(data);
|
496
513
|
}
|
497
514
|
|
515
|
+
// Reset the sample state, and truncate the call log.
|
516
|
+
void Fiber_Profiler_Capture_reset(struct Fiber_Profiler_Capture *capture) {
|
517
|
+
capture->nesting = 0;
|
518
|
+
capture->nesting_minimum = 0;
|
519
|
+
capture->current = NULL;
|
520
|
+
Fiber_Profiler_Deque_truncate(&capture->calls);
|
521
|
+
}
|
522
|
+
|
498
523
|
VALUE Fiber_Profiler_Capture_start(VALUE self) {
|
499
|
-
struct Fiber_Profiler_Capture *
|
524
|
+
struct Fiber_Profiler_Capture *capture = Fiber_Profiler_Capture_get(self);
|
500
525
|
|
501
|
-
if (
|
526
|
+
if (capture->running) return Qfalse;
|
502
527
|
|
503
|
-
|
504
|
-
|
528
|
+
capture->running = 1;
|
529
|
+
capture->thread = rb_thread_current();
|
505
530
|
|
506
|
-
Fiber_Profiler_Capture_reset(
|
507
|
-
Fiber_Profiler_Time_current(&
|
531
|
+
Fiber_Profiler_Capture_reset(capture);
|
532
|
+
Fiber_Profiler_Time_current(&capture->start_time);
|
508
533
|
|
509
|
-
rb_thread_add_event_hook(
|
534
|
+
rb_thread_add_event_hook(capture->thread, Fiber_Profiler_Capture_fiber_switch_callback, RUBY_EVENT_FIBER_SWITCH, self);
|
510
535
|
|
511
536
|
return self;
|
512
537
|
}
|
513
538
|
|
514
539
|
VALUE Fiber_Profiler_Capture_stop(VALUE self) {
|
515
|
-
struct Fiber_Profiler_Capture *
|
540
|
+
struct Fiber_Profiler_Capture *capture = Fiber_Profiler_Capture_get(self);
|
516
541
|
|
517
|
-
if (!
|
542
|
+
if (!capture->running) return Qfalse;
|
518
543
|
|
519
544
|
Fiber_Profiler_Capture_pause(self);
|
520
545
|
|
521
|
-
rb_thread_remove_event_hook_with_data(
|
546
|
+
rb_thread_remove_event_hook_with_data(capture->thread, Fiber_Profiler_Capture_fiber_switch_callback, self);
|
522
547
|
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
Fiber_Profiler_Capture_reset(profiler);
|
548
|
+
capture->running = 0;
|
549
|
+
capture->thread = Qnil;
|
550
|
+
|
551
|
+
Fiber_Profiler_Capture_reset(capture);
|
528
552
|
|
529
553
|
return self;
|
530
554
|
}
|
531
555
|
|
532
|
-
void Fiber_Profiler_Capture_finish(struct Fiber_Profiler_Capture *
|
533
|
-
|
534
|
-
|
535
|
-
struct timespec stop_time;
|
536
|
-
Fiber_Profiler_Time_current(&stop_time);
|
537
|
-
|
538
|
-
struct Fiber_Profiler_Capture_Call *current = profiler->current;
|
556
|
+
void Fiber_Profiler_Capture_finish(struct Fiber_Profiler_Capture *capture, struct timespec switch_time) {
|
557
|
+
struct Fiber_Profiler_Capture_Call *current = capture->current;
|
539
558
|
while (current) {
|
540
559
|
struct Fiber_Profiler_Capture_Call *parent = current->parent;
|
541
560
|
|
542
|
-
current->duration = Fiber_Profiler_Time_delta(¤t->enter_time, &
|
561
|
+
current->duration = Fiber_Profiler_Time_delta(¤t->enter_time, &switch_time);
|
543
562
|
|
544
|
-
Fiber_Profiler_Capture_Call_finish(
|
563
|
+
Fiber_Profiler_Capture_Call_finish(capture, current);
|
545
564
|
|
546
565
|
current = parent;
|
547
566
|
}
|
548
567
|
}
|
549
568
|
|
550
|
-
void Fiber_Profiler_Capture_print(struct Fiber_Profiler_Capture *
|
569
|
+
void Fiber_Profiler_Capture_print(struct Fiber_Profiler_Capture *capture, double duration);
|
551
570
|
|
552
|
-
int Fiber_Profiler_Capture_sample(struct Fiber_Profiler_Capture *
|
571
|
+
int Fiber_Profiler_Capture_sample(struct Fiber_Profiler_Capture *capture) {
|
553
572
|
VALUE fiber = Fiber_Profiler_Fiber_current();
|
554
573
|
|
555
574
|
// We don't want to capture data from blocking fibers:
|
556
575
|
if (Fiber_Profiler_Fiber_blocking(fiber)) return 0;
|
557
576
|
|
558
|
-
if (
|
559
|
-
return rand() < (RAND_MAX *
|
577
|
+
if (capture->sample_rate < 1) {
|
578
|
+
return rand() < (RAND_MAX * capture->sample_rate);
|
560
579
|
} else {
|
561
580
|
return 1;
|
562
581
|
}
|
@@ -564,47 +583,58 @@ int Fiber_Profiler_Capture_sample(struct Fiber_Profiler_Capture *profiler) {
|
|
564
583
|
|
565
584
|
void Fiber_Profiler_Capture_fiber_switch(VALUE self)
|
566
585
|
{
|
567
|
-
struct Fiber_Profiler_Capture *
|
568
|
-
|
569
|
-
double duration = Fiber_Profiler_Time_delta(&profiler->start_time, &profiler->stop_time);
|
586
|
+
struct Fiber_Profiler_Capture *capture = Fiber_Profiler_Capture_get(self);
|
587
|
+
capture->switches += 1;
|
570
588
|
|
571
|
-
if (
|
572
|
-
|
589
|
+
if (capture->capture) {
|
590
|
+
// The time of the switch (end):
|
591
|
+
struct timespec switch_time;
|
592
|
+
Fiber_Profiler_Time_current(&switch_time);
|
593
|
+
|
594
|
+
// The duration of the sample:
|
595
|
+
double duration = Fiber_Profiler_Time_delta(&capture->switch_time, &switch_time);
|
573
596
|
|
574
|
-
|
597
|
+
// Finish the current sample:
|
598
|
+
Fiber_Profiler_Capture_pause(self);
|
599
|
+
Fiber_Profiler_Capture_finish(capture, switch_time);
|
575
600
|
|
576
|
-
|
577
|
-
|
578
|
-
|
601
|
+
// If the duration of the sample is greater than the stall threshold, we consider it a stall:
|
602
|
+
if (duration > capture->stall_threshold) {
|
603
|
+
capture->stalls += 1;
|
604
|
+
|
605
|
+
// Print the sample:
|
606
|
+
Fiber_Profiler_Capture_print(capture, duration);
|
579
607
|
}
|
580
608
|
|
581
|
-
|
609
|
+
// Reset the capture state:
|
610
|
+
Fiber_Profiler_Capture_reset(capture);
|
582
611
|
}
|
583
612
|
|
584
|
-
if (Fiber_Profiler_Capture_sample(
|
585
|
-
//
|
586
|
-
Fiber_Profiler_Time_current(&
|
613
|
+
if (Fiber_Profiler_Capture_sample(capture)) {
|
614
|
+
// Capture the time of the switch (start):
|
615
|
+
Fiber_Profiler_Time_current(&capture->switch_time);
|
587
616
|
|
617
|
+
// Start capturing data again:
|
588
618
|
Fiber_Profiler_Capture_resume(self);
|
589
619
|
}
|
590
620
|
}
|
591
621
|
|
592
622
|
// When sampling a fiber, we may encounter returns without a preceeding call. This isn't an error, and we should correctly visualize the call stack. We track both the relative nesting (which can be negative) and the minimum nesting level encountered during the profiling session, and use that to determine the absolute nesting level of each call when printing the call stack.
|
593
|
-
static size_t Fiber_Profiler_Capture_absolute_nesting(struct Fiber_Profiler_Capture *
|
594
|
-
return call->nesting -
|
623
|
+
static size_t Fiber_Profiler_Capture_absolute_nesting(struct Fiber_Profiler_Capture *capture, struct Fiber_Profiler_Capture_Call *call) {
|
624
|
+
return call->nesting - capture->nesting_minimum;
|
595
625
|
}
|
596
626
|
|
597
627
|
// If a call is within this threshold of the parent call, it will be skipped when printing the call stack - it's considered inconsequential to the performance of the parent call.
|
598
628
|
static const double Fiber_Profiler_Capture_SKIP_THRESHOLD = 0.98;
|
599
629
|
|
600
|
-
void Fiber_Profiler_Capture_print_tty(struct Fiber_Profiler_Capture *
|
601
|
-
double
|
630
|
+
void Fiber_Profiler_Capture_print_tty(struct Fiber_Profiler_Capture *capture, FILE *restrict stream, double duration) {
|
631
|
+
double start_time = Fiber_Profiler_Time_delta(&capture->start_time, &capture->switch_time);
|
602
632
|
|
603
|
-
fprintf(stderr, "## Fiber stalled for %.3f seconds
|
633
|
+
fprintf(stderr, "## Fiber stalled for %.3f seconds (switches=%zu, samples=%zu, stalls=%zu, T+%0.3fs)\n", duration, capture->switches, capture->samples, capture->stalls, start_time);
|
604
634
|
|
605
635
|
size_t skipped = 0;
|
606
636
|
|
607
|
-
Fiber_Profiler_Deque_each(&
|
637
|
+
Fiber_Profiler_Deque_each(&capture->calls, struct Fiber_Profiler_Capture_Call, call) {
|
608
638
|
if (call->children) {
|
609
639
|
if (call->parent && call->parent->children == 1) {
|
610
640
|
if (call->duration > call->parent->duration * Fiber_Profiler_Capture_SKIP_THRESHOLD) {
|
@@ -627,7 +657,7 @@ void Fiber_Profiler_Capture_print_tty(struct Fiber_Profiler_Capture *profiler, F
|
|
627
657
|
if (skipped) {
|
628
658
|
fprintf(stream, "\e[2m");
|
629
659
|
|
630
|
-
size_t nesting = Fiber_Profiler_Capture_absolute_nesting(
|
660
|
+
size_t nesting = Fiber_Profiler_Capture_absolute_nesting(capture, call);
|
631
661
|
for (size_t i = 0; i < nesting; i += 1) {
|
632
662
|
fputc('\t', stream);
|
633
663
|
}
|
@@ -638,12 +668,12 @@ void Fiber_Profiler_Capture_print_tty(struct Fiber_Profiler_Capture *profiler, F
|
|
638
668
|
call->nesting += 1;
|
639
669
|
}
|
640
670
|
|
641
|
-
size_t nesting = Fiber_Profiler_Capture_absolute_nesting(
|
671
|
+
size_t nesting = Fiber_Profiler_Capture_absolute_nesting(capture, call);
|
642
672
|
for (size_t i = 0; i < nesting; i += 1) {
|
643
673
|
fputc('\t', stream);
|
644
674
|
}
|
645
675
|
|
646
|
-
if (Fiber_Profiler_Capture_Call_expensive_p(call,
|
676
|
+
if (Fiber_Profiler_Capture_Call_expensive_p(capture, call, duration)) {
|
647
677
|
fprintf(stream, "\e[31m");
|
648
678
|
}
|
649
679
|
|
@@ -651,7 +681,7 @@ void Fiber_Profiler_Capture_print_tty(struct Fiber_Profiler_Capture *profiler, F
|
|
651
681
|
const char *name = rb_id2name(call->id);
|
652
682
|
|
653
683
|
struct timespec offset;
|
654
|
-
Fiber_Profiler_Time_elapsed(&
|
684
|
+
Fiber_Profiler_Time_elapsed(&capture->switch_time, &call->enter_time, &offset);
|
655
685
|
|
656
686
|
fprintf(stream, "%s:%d in %s '%s#%s' (%0.4fs, T+" Fiber_Profiler_TIME_PRINTF_TIMESPEC ")\n", call->path, call->line, event_flag_name(call->event_flag), RSTRING_PTR(class_inspect), name, call->duration, Fiber_Profiler_TIME_PRINTF_TIMESPEC_ARGUMENTS(offset));
|
657
687
|
|
@@ -667,21 +697,25 @@ void Fiber_Profiler_Capture_print_tty(struct Fiber_Profiler_Capture *profiler, F
|
|
667
697
|
fprintf(stream, "... filtered %zu direct calls ...\e[0m\n", call->filtered);
|
668
698
|
}
|
669
699
|
}
|
700
|
+
|
701
|
+
if (skipped) {
|
702
|
+
fprintf(stream, "\e[2m... skipped %zu calls ...\e[0m\n", skipped);
|
703
|
+
}
|
670
704
|
}
|
671
705
|
|
672
|
-
void Fiber_Profiler_Capture_print_json(struct Fiber_Profiler_Capture *
|
673
|
-
double
|
706
|
+
void Fiber_Profiler_Capture_print_json(struct Fiber_Profiler_Capture *capture, FILE *restrict stream, double duration) {
|
707
|
+
double start_time = Fiber_Profiler_Time_delta(&capture->start_time, &capture->switch_time);
|
674
708
|
|
675
709
|
fputc('{', stream);
|
676
710
|
|
677
|
-
fprintf(stream, "\"duration\":%0.6f",
|
711
|
+
fprintf(stream, "\"start_time\":%0.3f,\"duration\":%0.6f", start_time, duration);
|
678
712
|
|
679
713
|
size_t skipped = 0;
|
680
714
|
|
681
715
|
fprintf(stream, ",\"calls\":[");
|
682
716
|
int first = 1;
|
683
717
|
|
684
|
-
Fiber_Profiler_Deque_each(&
|
718
|
+
Fiber_Profiler_Deque_each(&capture->calls, struct Fiber_Profiler_Capture_Call, call) {
|
685
719
|
if (call->children) {
|
686
720
|
if (call->parent && call->parent->children == 1) {
|
687
721
|
if (call->duration > call->parent->duration * Fiber_Profiler_Capture_SKIP_THRESHOLD) {
|
@@ -700,10 +734,10 @@ void Fiber_Profiler_Capture_print_json(struct Fiber_Profiler_Capture *profiler,
|
|
700
734
|
VALUE class_inspect = rb_inspect(call->klass);
|
701
735
|
const char *name = rb_id2name(call->id);
|
702
736
|
|
703
|
-
size_t nesting = Fiber_Profiler_Capture_absolute_nesting(
|
737
|
+
size_t nesting = Fiber_Profiler_Capture_absolute_nesting(capture, call);
|
704
738
|
|
705
739
|
struct timespec offset;
|
706
|
-
Fiber_Profiler_Time_elapsed(&
|
740
|
+
Fiber_Profiler_Time_elapsed(&capture->switch_time, &call->enter_time, &offset);
|
707
741
|
|
708
742
|
fprintf(stream, "%s{\"path\":\"%s\",\"line\":%d,\"class\":\"%s\",\"method\":\"%s\",\"duration\":%0.6f,\"offset\":" Fiber_Profiler_TIME_PRINTF_TIMESPEC ",\"nesting\":%zu,\"skipped\":%zu,\"filtered\":%zu}", first ? "" : ",", call->path, call->line, RSTRING_PTR(class_inspect), name, call->duration, Fiber_Profiler_TIME_PRINTF_TIMESPEC_ARGUMENTS(offset), nesting, skipped, call->filtered);
|
709
743
|
|
@@ -717,17 +751,17 @@ void Fiber_Profiler_Capture_print_json(struct Fiber_Profiler_Capture *profiler,
|
|
717
751
|
fprintf(stream, ",\"skipped\":%zu", skipped);
|
718
752
|
}
|
719
753
|
|
720
|
-
fprintf(stream, "}\n");
|
754
|
+
fprintf(stream, ",\"switches\":%zu,\"samples\":%zu,\"stalls\":%zu}\n", capture->switches, capture->samples, capture->stalls);
|
721
755
|
}
|
722
756
|
|
723
|
-
void Fiber_Profiler_Capture_print(struct Fiber_Profiler_Capture *
|
724
|
-
if (
|
757
|
+
void Fiber_Profiler_Capture_print(struct Fiber_Profiler_Capture *capture, double duration) {
|
758
|
+
if (capture->output == Qnil) return;
|
725
759
|
|
726
|
-
FILE *stream =
|
727
|
-
|
760
|
+
FILE *stream = capture->stream.file;
|
761
|
+
capture->print(capture, stream, duration);
|
728
762
|
fflush(stream);
|
729
763
|
|
730
|
-
rb_io_write(
|
764
|
+
rb_io_write(capture->output, rb_str_new_static(capture->stream.buffer, capture->stream.size));
|
731
765
|
|
732
766
|
fseek(stream, 0, SEEK_SET);
|
733
767
|
}
|
@@ -735,27 +769,33 @@ void Fiber_Profiler_Capture_print(struct Fiber_Profiler_Capture *profiler) {
|
|
735
769
|
#pragma mark - Accessors
|
736
770
|
|
737
771
|
static VALUE Fiber_Profiler_Capture_stall_threshold_get(VALUE self) {
|
738
|
-
struct Fiber_Profiler_Capture *
|
772
|
+
struct Fiber_Profiler_Capture *capture = Fiber_Profiler_Capture_get(self);
|
773
|
+
|
774
|
+
return DBL2NUM(capture->stall_threshold);
|
775
|
+
}
|
776
|
+
|
777
|
+
static VALUE Fiber_Profiler_Capture_filter_threshold_get(VALUE self) {
|
778
|
+
struct Fiber_Profiler_Capture *capture = Fiber_Profiler_Capture_get(self);
|
739
779
|
|
740
|
-
return DBL2NUM(
|
780
|
+
return DBL2NUM(capture->filter_threshold);
|
741
781
|
}
|
742
782
|
|
743
783
|
static VALUE Fiber_Profiler_Capture_track_calls_get(VALUE self) {
|
744
|
-
struct Fiber_Profiler_Capture *
|
784
|
+
struct Fiber_Profiler_Capture *capture = Fiber_Profiler_Capture_get(self);
|
745
785
|
|
746
|
-
return
|
786
|
+
return capture->track_calls ? Qtrue : Qfalse;
|
747
787
|
}
|
748
788
|
|
749
789
|
static VALUE Fiber_Profiler_Capture_stalls_get(VALUE self) {
|
750
|
-
struct Fiber_Profiler_Capture *
|
790
|
+
struct Fiber_Profiler_Capture *capture = Fiber_Profiler_Capture_get(self);
|
751
791
|
|
752
|
-
return SIZET2NUM(
|
792
|
+
return SIZET2NUM(capture->stalls);
|
753
793
|
}
|
754
794
|
|
755
795
|
static VALUE Fiber_Profiler_Capture_sample_rate_get(VALUE self) {
|
756
|
-
struct Fiber_Profiler_Capture *
|
796
|
+
struct Fiber_Profiler_Capture *capture = Fiber_Profiler_Capture_get(self);
|
757
797
|
|
758
|
-
return DBL2NUM(
|
798
|
+
return DBL2NUM(capture->sample_rate);
|
759
799
|
}
|
760
800
|
|
761
801
|
#pragma mark - Environment Variables
|
@@ -800,18 +840,31 @@ static double FIBER_PROFILER_CAPTURE_SAMPLE_RATE(void) {
|
|
800
840
|
}
|
801
841
|
}
|
802
842
|
|
843
|
+
static double FIBER_PROFILER_CAPTURE_FILTER_THRESHOLD(void) {
|
844
|
+
const char *value = getenv("FIBER_PROFILER_CAPTURE_FILTER_THRESHOLD");
|
845
|
+
|
846
|
+
if (value) {
|
847
|
+
return atof(value);
|
848
|
+
} else {
|
849
|
+
// We use 10% of the stall threshold as the default filter threshold:
|
850
|
+
return Fiber_Profiler_Capture_stall_threshold * 0.1;
|
851
|
+
}
|
852
|
+
}
|
853
|
+
|
803
854
|
#pragma mark - Initialization
|
804
855
|
|
805
856
|
void Init_Fiber_Profiler_Capture(VALUE Fiber_Profiler) {
|
806
857
|
Fiber_Profiler_capture_p = FIBER_PROFILER_CAPTURE();
|
807
858
|
Fiber_Profiler_Capture_stall_threshold = FIBER_PROFILER_CAPTURE_STALL_THRESHOLD();
|
859
|
+
Fiber_Profiler_Capture_filter_threshold = FIBER_PROFILER_CAPTURE_FILTER_THRESHOLD();
|
808
860
|
Fiber_Profiler_Capture_track_calls = FIBER_PROFILER_CAPTURE_TRACK_CALLS();
|
809
861
|
Fiber_Profiler_Capture_sample_rate = FIBER_PROFILER_CAPTURE_SAMPLE_RATE();
|
810
862
|
|
811
863
|
Fiber_Profiler_Capture_initialize_options[0] = rb_intern("stall_threshold");
|
812
|
-
Fiber_Profiler_Capture_initialize_options[1] = rb_intern("
|
813
|
-
Fiber_Profiler_Capture_initialize_options[2] = rb_intern("
|
814
|
-
Fiber_Profiler_Capture_initialize_options[3] = rb_intern("
|
864
|
+
Fiber_Profiler_Capture_initialize_options[1] = rb_intern("filter_threshold");
|
865
|
+
Fiber_Profiler_Capture_initialize_options[2] = rb_intern("track_calls");
|
866
|
+
Fiber_Profiler_Capture_initialize_options[3] = rb_intern("sample_rate");
|
867
|
+
Fiber_Profiler_Capture_initialize_options[4] = rb_intern("output");
|
815
868
|
|
816
869
|
Fiber_Profiler_Capture = rb_define_class_under(Fiber_Profiler, "Capture", rb_cObject);
|
817
870
|
rb_define_alloc_func(Fiber_Profiler_Capture, Fiber_Profiler_Capture_allocate);
|
@@ -824,6 +877,7 @@ void Init_Fiber_Profiler_Capture(VALUE Fiber_Profiler) {
|
|
824
877
|
rb_define_method(Fiber_Profiler_Capture, "stop", Fiber_Profiler_Capture_stop, 0);
|
825
878
|
|
826
879
|
rb_define_method(Fiber_Profiler_Capture, "stall_threshold", Fiber_Profiler_Capture_stall_threshold_get, 0);
|
880
|
+
rb_define_method(Fiber_Profiler_Capture, "filter_threshold", Fiber_Profiler_Capture_filter_threshold_get, 0);
|
827
881
|
rb_define_method(Fiber_Profiler_Capture, "track_calls", Fiber_Profiler_Capture_track_calls_get, 0);
|
828
882
|
rb_define_method(Fiber_Profiler_Capture, "sample_rate", Fiber_Profiler_Capture_sample_rate_get, 0);
|
829
883
|
|
data/ext/fiber.o
ADDED
Binary file
|
data/ext/profiler.o
ADDED
Binary file
|
data/ext/time.o
ADDED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fiber-profiler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
|
+
autorequire:
|
8
9
|
bindir: bin
|
9
10
|
cert_chain:
|
10
11
|
- |
|
@@ -38,12 +39,19 @@ cert_chain:
|
|
38
39
|
-----END CERTIFICATE-----
|
39
40
|
date: 2025-02-15 00:00:00.000000000 Z
|
40
41
|
dependencies: []
|
42
|
+
description:
|
43
|
+
email:
|
41
44
|
executables: []
|
42
45
|
extensions:
|
43
46
|
- ext/extconf.rb
|
44
47
|
extra_rdoc_files: []
|
45
48
|
files:
|
49
|
+
- ext/Fiber_Profiler.bundle
|
50
|
+
- ext/Makefile
|
51
|
+
- ext/capture.o
|
52
|
+
- ext/extconf.h
|
46
53
|
- ext/extconf.rb
|
54
|
+
- ext/fiber.o
|
47
55
|
- ext/fiber/profiler/capture.c
|
48
56
|
- ext/fiber/profiler/capture.h
|
49
57
|
- ext/fiber/profiler/deque.h
|
@@ -53,6 +61,8 @@ files:
|
|
53
61
|
- ext/fiber/profiler/profiler.h
|
54
62
|
- ext/fiber/profiler/time.c
|
55
63
|
- ext/fiber/profiler/time.h
|
64
|
+
- ext/profiler.o
|
65
|
+
- ext/time.o
|
56
66
|
- lib/fiber/profiler.rb
|
57
67
|
- lib/fiber/profiler/capture.rb
|
58
68
|
- lib/fiber/profiler/native.rb
|
@@ -66,6 +76,7 @@ licenses:
|
|
66
76
|
metadata:
|
67
77
|
documentation_uri: https://socketry.github.io/fiber-profiler/
|
68
78
|
source_code_uri: https://github.com/socketry/fiber-profiler.git
|
79
|
+
post_install_message:
|
69
80
|
rdoc_options: []
|
70
81
|
require_paths:
|
71
82
|
- lib
|
@@ -80,7 +91,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
91
|
- !ruby/object:Gem::Version
|
81
92
|
version: '0'
|
82
93
|
requirements: []
|
83
|
-
rubygems_version: 3.
|
94
|
+
rubygems_version: 3.5.16
|
95
|
+
signing_key:
|
84
96
|
specification_version: 4
|
85
97
|
summary: A fiber stall profiler.
|
86
98
|
test_files: []
|
metadata.gz.sig
CHANGED
Binary file
|