Branch data Line data Source code
1 : : // This file is a part of Julia. License is MIT: https://julialang.org/license 2 : : 3 : : #include "gc-alloc-profiler.h" 4 : : 5 : : #include "julia_internal.h" 6 : : #include "gc.h" 7 : : 8 : : #include <string> 9 : : #include <vector> 10 : : 11 : : using std::string; 12 : : using std::vector; 13 : : 14 : : struct jl_raw_backtrace_t { 15 : : jl_bt_element_t *data; 16 : : size_t size; 17 : : }; 18 : : 19 : : struct jl_raw_alloc_t { 20 : : jl_datatype_t *type_address; 21 : : jl_raw_backtrace_t backtrace; 22 : : size_t size; 23 : : void *task; 24 : : uint64_t timestamp; 25 : : }; 26 : : 27 : : // == These structs define the global singleton profile buffer that will be used by 28 : : // callbacks to store profile results. == 29 : : struct jl_per_thread_alloc_profile_t { 30 : : vector<jl_raw_alloc_t> allocs; 31 : : }; 32 : : 33 : : struct jl_alloc_profile_t { 34 : : double sample_rate; 35 : : 36 : : vector<jl_per_thread_alloc_profile_t> per_thread_profiles; 37 : : }; 38 : : 39 : : struct jl_combined_results { 40 : : vector<jl_raw_alloc_t> combined_allocs; 41 : : }; 42 : : 43 : : // == Global variables manipulated by callbacks == 44 : : 45 : : jl_alloc_profile_t g_alloc_profile; 46 : : int g_alloc_profile_enabled = false; 47 : : jl_combined_results g_combined_results; // Will live forever. 48 : : 49 : : // === stack stuff === 50 : : 51 : 0 : jl_raw_backtrace_t get_raw_backtrace() JL_NOTSAFEPOINT { 52 : : // We first record the backtrace onto a MAX-sized buffer, so that we don't have to 53 : : // allocate the buffer until we know the size. To ensure thread-safety, we use a 54 : : // per-thread backtrace buffer. 55 : 0 : jl_ptls_t ptls = jl_current_task->ptls; 56 : 0 : jl_bt_element_t *shared_bt_data_buffer = ptls->profiling_bt_buffer; 57 [ # # ]: 0 : if (shared_bt_data_buffer == NULL) { 58 : 0 : size_t size = sizeof(jl_bt_element_t) * (JL_MAX_BT_SIZE + 1); 59 : 0 : shared_bt_data_buffer = (jl_bt_element_t*) malloc_s(size); 60 : 0 : ptls->profiling_bt_buffer = shared_bt_data_buffer; 61 : : } 62 : : 63 : 0 : size_t bt_size = rec_backtrace(shared_bt_data_buffer, JL_MAX_BT_SIZE, 2); 64 : : 65 : : // Then we copy only the needed bytes out of the buffer into our profile. 66 : 0 : size_t bt_bytes = bt_size * sizeof(jl_bt_element_t); 67 : 0 : jl_bt_element_t *bt_data = (jl_bt_element_t*) malloc_s(bt_bytes); 68 : 0 : memcpy(bt_data, shared_bt_data_buffer, bt_bytes); 69 : : 70 : : 71 : : return jl_raw_backtrace_t{ 72 : : bt_data, 73 : : bt_size 74 : 0 : }; 75 : : } 76 : : 77 : : // == exported interface == 78 : : 79 : : extern "C" { // Needed since these functions doesn't take any arguments. 80 : : 81 : 0 : JL_DLLEXPORT void jl_start_alloc_profile(double sample_rate) { 82 : : // We only need to do this once, the first time this is called. 83 [ # # ]: 0 : while (g_alloc_profile.per_thread_profiles.size() < (size_t)jl_n_threads) { 84 : 0 : g_alloc_profile.per_thread_profiles.push_back(jl_per_thread_alloc_profile_t{}); 85 : : } 86 : : 87 : 0 : g_alloc_profile.sample_rate = sample_rate; 88 : 0 : g_alloc_profile_enabled = true; 89 : 0 : } 90 : : 91 : 0 : JL_DLLEXPORT jl_profile_allocs_raw_results_t jl_fetch_alloc_profile() { 92 : : // combine allocs 93 : : // TODO: interleave to preserve ordering 94 [ # # ]: 0 : for (auto& profile : g_alloc_profile.per_thread_profiles) { 95 [ # # ]: 0 : for (const auto& alloc : profile.allocs) { 96 : 0 : g_combined_results.combined_allocs.push_back(alloc); 97 : : } 98 : : 99 : 0 : profile.allocs.clear(); 100 : : } 101 : : 102 : : return jl_profile_allocs_raw_results_t{ 103 : 0 : g_combined_results.combined_allocs.data(), 104 : 0 : g_combined_results.combined_allocs.size(), 105 : 0 : }; 106 : : } 107 : : 108 : 0 : JL_DLLEXPORT void jl_stop_alloc_profile() { 109 : 0 : g_alloc_profile_enabled = false; 110 : 0 : } 111 : : 112 : 0 : JL_DLLEXPORT void jl_free_alloc_profile() { 113 : : // Free any allocs that remain in the per-thread profiles, that haven't 114 : : // been combined yet (which happens in fetch_alloc_profiles()). 115 [ # # ]: 0 : for (auto& profile : g_alloc_profile.per_thread_profiles) { 116 [ # # ]: 0 : for (auto alloc : profile.allocs) { 117 : 0 : free(alloc.backtrace.data); 118 : : } 119 : 0 : profile.allocs.clear(); 120 : : } 121 : : 122 : : // Free the allocs that have been already combined into the combined results object. 123 [ # # ]: 0 : for (auto alloc : g_combined_results.combined_allocs) { 124 : 0 : free(alloc.backtrace.data); 125 : : } 126 : : 127 : 0 : g_combined_results.combined_allocs.clear(); 128 : 0 : } 129 : : 130 : : // == callback called into by the outside == 131 : : 132 : 0 : void _maybe_record_alloc_to_profile(jl_value_t *val, size_t size, jl_datatype_t *type) JL_NOTSAFEPOINT { 133 : 0 : auto& global_profile = g_alloc_profile; 134 : 0 : auto thread_id = jl_atomic_load_relaxed(&jl_current_task->tid); 135 : 0 : auto& profile = global_profile.per_thread_profiles[thread_id]; 136 : : 137 : 0 : auto sample_val = double(rand()) / double(RAND_MAX); 138 : 0 : auto should_record = sample_val <= global_profile.sample_rate; 139 [ # # ]: 0 : if (!should_record) { 140 : 0 : return; 141 : : } 142 : : 143 : 0 : profile.allocs.emplace_back(jl_raw_alloc_t{ 144 : : type, 145 : 0 : get_raw_backtrace(), 146 : : size, 147 : 0 : (void *)jl_current_task, 148 : 0 : cycleclock() 149 : : }); 150 : : } 151 : : 152 : : } // extern "C"