diff --git a/src/signal-handling.c b/src/signal-handling.c index e941d0fd9548b..43782bf4070f2 100644 --- a/src/signal-handling.c +++ b/src/signal-handling.c @@ -29,6 +29,52 @@ static const uint64_t GIGA = 1000000000ULL; JL_DLLEXPORT void jl_profile_stop_timer(void); JL_DLLEXPORT int jl_profile_start_timer(void); +/////////////////////// +// Utility functions // +/////////////////////// +JL_DLLEXPORT int jl_profile_init(size_t maxsize, uint64_t delay_nsec) +{ + bt_size_max = maxsize; + nsecprof = delay_nsec; + if (bt_data_prof != NULL) + free((void*)bt_data_prof); + bt_data_prof = (jl_bt_element_t*) calloc(maxsize, sizeof(jl_bt_element_t)); + if (bt_data_prof == NULL && maxsize > 0) + return -1; + bt_size_cur = 0; + return 0; +} + +JL_DLLEXPORT uint8_t *jl_profile_get_data(void) +{ + return (uint8_t*) bt_data_prof; +} + +JL_DLLEXPORT size_t jl_profile_len_data(void) +{ + return bt_size_cur; +} + +JL_DLLEXPORT size_t jl_profile_maxlen_data(void) +{ + return bt_size_max; +} + +JL_DLLEXPORT uint64_t jl_profile_delay_nsec(void) +{ + return nsecprof; +} + +JL_DLLEXPORT void jl_profile_clear_data(void) +{ + bt_size_cur = 0; +} + +JL_DLLEXPORT int jl_profile_is_running(void) +{ + return running; +} + // Any function that acquires this lock must be either a unmanaged thread // or in the GC safe region and must NOT allocate anything through the GC // while holding this lock. @@ -426,52 +472,6 @@ void jl_critical_error(int sig, bt_context_t *context, jl_task_t *ct) jl_gc_debug_critical_error(); } -/////////////////////// -// Utility functions // -/////////////////////// -JL_DLLEXPORT int jl_profile_init(size_t maxsize, uint64_t delay_nsec) -{ - bt_size_max = maxsize; - nsecprof = delay_nsec; - if (bt_data_prof != NULL) - free((void*)bt_data_prof); - bt_data_prof = (jl_bt_element_t*) calloc(maxsize, sizeof(jl_bt_element_t)); - if (bt_data_prof == NULL && maxsize > 0) - return -1; - bt_size_cur = 0; - return 0; -} - -JL_DLLEXPORT uint8_t *jl_profile_get_data(void) -{ - return (uint8_t*) bt_data_prof; -} - -JL_DLLEXPORT size_t jl_profile_len_data(void) -{ - return bt_size_cur; -} - -JL_DLLEXPORT size_t jl_profile_maxlen_data(void) -{ - return bt_size_max; -} - -JL_DLLEXPORT uint64_t jl_profile_delay_nsec(void) -{ - return nsecprof; -} - -JL_DLLEXPORT void jl_profile_clear_data(void) -{ - bt_size_cur = 0; -} - -JL_DLLEXPORT int jl_profile_is_running(void) -{ - return running; -} - #ifdef __cplusplus } #endif diff --git a/src/signals-unix.c b/src/signals-unix.c index 34a77fc6fad6e..dadbd15de0832 100644 --- a/src/signals-unix.c +++ b/src/signals-unix.c @@ -698,6 +698,14 @@ void trigger_profile_peek(void) jl_safe_printf("\n======================================================================================\n"); jl_safe_printf("Information request received. A stacktrace will print followed by a %.1f second profile\n", profile_peek_duration); jl_safe_printf("======================================================================================\n"); + if (bt_size_max == 0){ + // If the buffer hasn't been initialized, initialize with default size + // Keep these values synchronized with Profile.default_init() + if (jl_profile_init(10000000 * jl_n_threads, 1000000) == -1){ + jl_safe_printf("ERROR: could not initialize the profile buffer"); + return; + } + } bt_size_cur = 0; // clear profile buffer if (jl_profile_start_timer() < 0) jl_safe_printf("ERROR: Could not start profile timer\n"); diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index 12a44b9acda9a..f260be3ae4dfe 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -23,10 +23,7 @@ appended to an internal buffer of backtraces. macro profile(ex) return quote try - status = start_timer() - if status < 0 - error(error_codes[status]) - end + start_timer() $(esc(ex)) finally stop_timer() @@ -98,6 +95,11 @@ using keywords or in the order `(n, delay)`. """ function init(; n::Union{Nothing,Integer} = nothing, delay::Union{Nothing,Real} = nothing, limitwarn::Bool = true) n_cur = ccall(:jl_profile_maxlen_data, Csize_t, ()) + if n_cur == 0 && isnothing(n) && isnothing(delay) + # indicates that the buffer hasn't been initialized at all, so set the default + default_init() + n_cur = ccall(:jl_profile_maxlen_data, Csize_t, ()) + end delay_cur = ccall(:jl_profile_delay_nsec, UInt64, ())/10^9 if n === nothing && delay === nothing 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) end end -function __init__() +function default_init() # init with default values # Use a max size of 10M profile samples, and fire timer every 1ms # (that should typically give around 100 seconds of record) @@ -136,10 +138,25 @@ function __init__() n = 1_000_000 delay = 0.01 else + # Keep these values synchronized with trigger_profile_peek n = 10_000_000 delay = 0.001 end init(n, delay, limitwarn = false) +end + +# Checks whether the profile buffer has been initialized. If not, initializes it with the default size. +function check_init() + buffer_size = @ccall jl_profile_maxlen_data()::Int + if buffer_size == 0 + default_init() + end +end + +function __init__() + # Note: The profile buffer is no longer initialized during __init__ because Profile is in the sysimage, + # thus __init__ is called every startup. The buffer is lazily initialized the first time `@profile` is + # used, if not manually initialized before that. @static if !Sys.iswindows() # triggering a profile via signals is not implemented on windows PROFILE_PRINT_COND[] = Base.AsyncCondition() @@ -567,7 +584,14 @@ Julia, and examine the resulting `*.mem` files. clear_malloc_data() = ccall(:jl_clear_malloc_data, Cvoid, ()) # C wrappers -start_timer() = ccall(:jl_profile_start_timer, Cint, ()) +function start_timer() + check_init() # if the profile buffer hasn't been initialized, initialize with default size + status = ccall(:jl_profile_start_timer, Cint, ()) + if status < 0 + error(error_codes[status]) + end +end + stop_timer() = ccall(:jl_profile_stop_timer, Cvoid, ()) @@ -599,6 +623,9 @@ By default metadata such as threadid and taskid is included. Set `include_meta` """ function fetch(;include_meta = true, limitwarn = true) maxlen = maxlen_data() + if maxlen == 0 + error("The profiling data buffer is not initialized. A profile has not been requested this session.") + end len = len_data() if limitwarn && is_buffer_full() @warn """The profile data buffer is full; profiling probably terminated diff --git a/stdlib/Profile/test/runtests.jl b/stdlib/Profile/test/runtests.jl index edd291f22e4a6..86c391d573e50 100644 --- a/stdlib/Profile/test/runtests.jl +++ b/stdlib/Profile/test/runtests.jl @@ -3,6 +3,8 @@ using Test, Profile, Serialization, Logging using Base.StackTraces: StackFrame +@test_throws "The profiling data buffer is not initialized. A profile has not been requested this session." Profile.print() + Profile.clear() Profile.init()