Skip to content

Commit 50bbad7

Browse files
Lazily initialize Profile buffer (#46239)
(cherry picked from commit 72473ae)
1 parent 3e4646d commit 50bbad7

File tree

4 files changed

+111
-74
lines changed

4 files changed

+111
-74
lines changed

src/signal-handling.c

Lines changed: 68 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,74 @@ void jl_lock_profile(void);
3535
void jl_unlock_profile(void);
3636
void jl_shuffle_int_array_inplace(volatile uint64_t *carray, size_t size, uint64_t *seed);
3737

38+
///////////////////////
39+
// Utility functions //
40+
///////////////////////
41+
JL_DLLEXPORT int jl_profile_init(size_t maxsize, uint64_t delay_nsec)
42+
{
43+
bt_size_max = maxsize;
44+
nsecprof = delay_nsec;
45+
if (bt_data_prof != NULL)
46+
free((void*)bt_data_prof);
47+
if (profile_round_robin_thread_order == NULL) {
48+
// NOTE: We currently only allocate this once, since jl_n_threads cannot change
49+
// during execution of a julia process. If/when this invariant changes in the
50+
// future, this will have to be adjusted.
51+
profile_round_robin_thread_order = (uint64_t*) calloc(jl_n_threads, sizeof(uint64_t));
52+
for (int i = 0; i < jl_n_threads; i++) {
53+
profile_round_robin_thread_order[i] = i;
54+
}
55+
}
56+
profile_cong_rng_seed = jl_rand();
57+
unbias_cong(jl_n_threads, &profile_cong_rng_unbias);
58+
bt_data_prof = (jl_bt_element_t*) calloc(maxsize, sizeof(jl_bt_element_t));
59+
if (bt_data_prof == NULL && maxsize > 0)
60+
return -1;
61+
bt_size_cur = 0;
62+
return 0;
63+
}
64+
65+
void jl_shuffle_int_array_inplace(volatile uint64_t *carray, size_t size, uint64_t *seed) {
66+
// The "modern Fisher–Yates shuffle" - O(n) algorithm
67+
// https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
68+
for (size_t i = size - 1; i >= 1; --i) {
69+
size_t j = cong(i, profile_cong_rng_unbias, seed);
70+
uint64_t tmp = carray[j];
71+
carray[j] = carray[i];
72+
carray[i] = tmp;
73+
}
74+
}
75+
76+
JL_DLLEXPORT uint8_t *jl_profile_get_data(void)
77+
{
78+
return (uint8_t*) bt_data_prof;
79+
}
80+
81+
JL_DLLEXPORT size_t jl_profile_len_data(void)
82+
{
83+
return bt_size_cur;
84+
}
85+
86+
JL_DLLEXPORT size_t jl_profile_maxlen_data(void)
87+
{
88+
return bt_size_max;
89+
}
90+
91+
JL_DLLEXPORT uint64_t jl_profile_delay_nsec(void)
92+
{
93+
return nsecprof;
94+
}
95+
96+
JL_DLLEXPORT void jl_profile_clear_data(void)
97+
{
98+
bt_size_cur = 0;
99+
}
100+
101+
JL_DLLEXPORT int jl_profile_is_running(void)
102+
{
103+
return running;
104+
}
105+
38106
JL_DLLEXPORT int jl_profile_is_buffer_full(void)
39107
{
40108
// declare buffer full if there isn't enough room to take samples across all threads
@@ -323,74 +391,6 @@ void jl_critical_error(int sig, bt_context_t *context, jl_task_t *ct)
323391
jl_gc_debug_critical_error();
324392
}
325393

326-
///////////////////////
327-
// Utility functions //
328-
///////////////////////
329-
JL_DLLEXPORT int jl_profile_init(size_t maxsize, uint64_t delay_nsec)
330-
{
331-
bt_size_max = maxsize;
332-
nsecprof = delay_nsec;
333-
if (bt_data_prof != NULL)
334-
free((void*)bt_data_prof);
335-
if (profile_round_robin_thread_order == NULL) {
336-
// NOTE: We currently only allocate this once, since jl_n_threads cannot change
337-
// during execution of a julia process. If/when this invariant changes in the
338-
// future, this will have to be adjusted.
339-
profile_round_robin_thread_order = (uint64_t*) calloc(jl_n_threads, sizeof(uint64_t));
340-
for (int i = 0; i < jl_n_threads; i++) {
341-
profile_round_robin_thread_order[i] = i;
342-
}
343-
}
344-
profile_cong_rng_seed = jl_rand();
345-
unbias_cong(jl_n_threads, &profile_cong_rng_unbias);
346-
bt_data_prof = (jl_bt_element_t*) calloc(maxsize, sizeof(jl_bt_element_t));
347-
if (bt_data_prof == NULL && maxsize > 0)
348-
return -1;
349-
bt_size_cur = 0;
350-
return 0;
351-
}
352-
353-
void jl_shuffle_int_array_inplace(volatile uint64_t *carray, size_t size, uint64_t *seed) {
354-
// The "modern Fisher–Yates shuffle" - O(n) algorithm
355-
// https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
356-
for (size_t i = size - 1; i >= 1; --i) {
357-
size_t j = cong(i, profile_cong_rng_unbias, seed);
358-
uint64_t tmp = carray[j];
359-
carray[j] = carray[i];
360-
carray[i] = tmp;
361-
}
362-
}
363-
364-
JL_DLLEXPORT uint8_t *jl_profile_get_data(void)
365-
{
366-
return (uint8_t*) bt_data_prof;
367-
}
368-
369-
JL_DLLEXPORT size_t jl_profile_len_data(void)
370-
{
371-
return bt_size_cur;
372-
}
373-
374-
JL_DLLEXPORT size_t jl_profile_maxlen_data(void)
375-
{
376-
return bt_size_max;
377-
}
378-
379-
JL_DLLEXPORT uint64_t jl_profile_delay_nsec(void)
380-
{
381-
return nsecprof;
382-
}
383-
384-
JL_DLLEXPORT void jl_profile_clear_data(void)
385-
{
386-
bt_size_cur = 0;
387-
}
388-
389-
JL_DLLEXPORT int jl_profile_is_running(void)
390-
{
391-
return running;
392-
}
393-
394394
#ifdef __cplusplus
395395
}
396396
#endif

src/signals-unix.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,14 @@ void trigger_profile_peek(void)
673673
jl_safe_printf("\n======================================================================================\n");
674674
jl_safe_printf("Information request received. A stacktrace will print followed by a %.1f second profile\n", profile_peek_duration);
675675
jl_safe_printf("======================================================================================\n");
676+
if (bt_size_max == 0){
677+
// If the buffer hasn't been initialized, initialize with default size
678+
// Keep these values synchronized with Profile.default_init()
679+
if (jl_profile_init(10000000 * jl_n_threads, 1000000) == -1){
680+
jl_safe_printf("ERROR: could not initialize the profile buffer");
681+
return;
682+
}
683+
}
676684
bt_size_cur = 0; // clear profile buffer
677685
if (jl_profile_start_timer() < 0)
678686
jl_safe_printf("ERROR: Could not start profile timer\n");

stdlib/Profile/src/Profile.jl

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@ appended to an internal buffer of backtraces.
2323
macro profile(ex)
2424
return quote
2525
try
26-
status = start_timer()
27-
if status < 0
28-
error(error_codes[status])
29-
end
26+
start_timer()
3027
$(esc(ex))
3128
finally
3229
stop_timer()
@@ -98,6 +95,11 @@ using keywords or in the order `(n, delay)`.
9895
"""
9996
function init(; n::Union{Nothing,Integer} = nothing, delay::Union{Nothing,Real} = nothing, limitwarn::Bool = true)
10097
n_cur = ccall(:jl_profile_maxlen_data, Csize_t, ())
98+
if n_cur == 0 && isnothing(n) && isnothing(delay)
99+
# indicates that the buffer hasn't been initialized at all, so set the default
100+
default_init()
101+
n_cur = ccall(:jl_profile_maxlen_data, Csize_t, ())
102+
end
101103
delay_cur = ccall(:jl_profile_delay_nsec, UInt64, ())/10^9
102104
if n === nothing && delay === nothing
103105
nthreads = Sys.iswindows() ? 1 : Threads.nthreads() # windows only profiles the main thread
@@ -126,7 +128,7 @@ function init(n::Integer, delay::Real; limitwarn::Bool = true)
126128
end
127129
end
128130

129-
function __init__()
131+
function default_init()
130132
# init with default values
131133
# Use a max size of 10M profile samples, and fire timer every 1ms
132134
# (that should typically give around 100 seconds of record)
@@ -136,10 +138,25 @@ function __init__()
136138
n = 1_000_000
137139
delay = 0.01
138140
else
141+
# Keep these values synchronized with trigger_profile_peek
139142
n = 10_000_000
140143
delay = 0.001
141144
end
142145
init(n, delay, limitwarn = false)
146+
end
147+
148+
# Checks whether the profile buffer has been initialized. If not, initializes it with the default size.
149+
function check_init()
150+
buffer_size = @ccall jl_profile_maxlen_data()::Int
151+
if buffer_size == 0
152+
default_init()
153+
end
154+
end
155+
156+
function __init__()
157+
# Note: The profile buffer is no longer initialized during __init__ because Profile is in the sysimage,
158+
# thus __init__ is called every startup. The buffer is lazily initialized the first time `@profile` is
159+
# used, if not manually initialized before that.
143160
@static if !Sys.iswindows()
144161
# triggering a profile via signals is not implemented on windows
145162
PROFILE_PRINT_COND[] = Base.AsyncCondition()
@@ -567,7 +584,14 @@ Julia, and examine the resulting `*.mem` files.
567584
clear_malloc_data() = ccall(:jl_clear_malloc_data, Cvoid, ())
568585

569586
# C wrappers
570-
start_timer() = ccall(:jl_profile_start_timer, Cint, ())
587+
function start_timer()
588+
check_init() # if the profile buffer hasn't been initialized, initialize with default size
589+
status = ccall(:jl_profile_start_timer, Cint, ())
590+
if status < 0
591+
error(error_codes[status])
592+
end
593+
end
594+
571595

572596
stop_timer() = ccall(:jl_profile_stop_timer, Cvoid, ())
573597

@@ -599,6 +623,9 @@ By default metadata such as threadid and taskid is included. Set `include_meta`
599623
"""
600624
function fetch(;include_meta = true, limitwarn = true)
601625
maxlen = maxlen_data()
626+
if maxlen == 0
627+
error("The profiling data buffer is not initialized. A profile has not been requested this session.")
628+
end
602629
len = len_data()
603630
if limitwarn && is_buffer_full()
604631
@warn """The profile data buffer is full; profiling probably terminated

stdlib/Profile/test/runtests.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using Test, Profile, Serialization, Logging
44
using Base.StackTraces: StackFrame
55

6+
@test_throws "The profiling data buffer is not initialized. A profile has not been requested this session." Profile.print()
7+
68
Profile.clear()
79
Profile.init()
810

0 commit comments

Comments
 (0)