123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182 |
- #include <inttypes.h>
- #include "profiler.h"
- #include "darray.h"
- #include "dstr.h"
- #include "platform.h"
- #include "threading.h"
- #include <math.h>
- #include <zlib.h>
- //#define TRACK_OVERHEAD
- struct profiler_snapshot {
- DARRAY(profiler_snapshot_entry_t) roots;
- };
- struct profiler_snapshot_entry {
- const char *name;
- profiler_time_entries_t times;
- uint64_t min_time;
- uint64_t max_time;
- uint64_t overall_count;
- profiler_time_entries_t times_between_calls;
- uint64_t expected_time_between_calls;
- uint64_t min_time_between_calls;
- uint64_t max_time_between_calls;
- uint64_t overall_between_calls_count;
- DARRAY(profiler_snapshot_entry_t) children;
- };
- typedef struct profiler_time_entry profiler_time_entry;
- typedef struct profile_call profile_call;
- struct profile_call {
- const char *name;
- #ifdef TRACK_OVERHEAD
- uint64_t overhead_start;
- #endif
- uint64_t start_time;
- uint64_t end_time;
- #ifdef TRACK_OVERHEAD
- uint64_t overhead_end;
- #endif
- uint64_t expected_time_between_calls;
- DARRAY(profile_call) children;
- profile_call *parent;
- };
- typedef struct profile_times_table_entry profile_times_table_entry;
- struct profile_times_table_entry {
- size_t probes;
- profiler_time_entry entry;
- };
- typedef struct profile_times_table profile_times_table;
- struct profile_times_table {
- size_t size;
- size_t occupied;
- size_t max_probe_count;
- profile_times_table_entry *entries;
- size_t old_start_index;
- size_t old_occupied;
- profile_times_table_entry *old_entries;
- };
- typedef struct profile_entry profile_entry;
- struct profile_entry {
- const char *name;
- profile_times_table times;
- #ifdef TRACK_OVERHEAD
- profile_times_table overhead;
- #endif
- uint64_t expected_time_between_calls;
- profile_times_table times_between_calls;
- DARRAY(profile_entry) children;
- };
- typedef struct profile_root_entry profile_root_entry;
- struct profile_root_entry {
- pthread_mutex_t *mutex;
- const char *name;
- profile_entry *entry;
- profile_call *prev_call;
- };
- static inline uint64_t diff_ns_to_usec(uint64_t prev, uint64_t next)
- {
- return (next - prev + 500) / 1000;
- }
- static inline void update_max_probes(profile_times_table *map, size_t val)
- {
- map->max_probe_count =
- map->max_probe_count < val ? val : map->max_probe_count;
- }
- static void migrate_old_entries(profile_times_table *map, bool limit_items);
- static void grow_hashmap(profile_times_table *map, uint64_t usec,
- uint64_t count);
- static void add_hashmap_entry(profile_times_table *map, uint64_t usec,
- uint64_t count)
- {
- size_t probes = 1;
- size_t start = usec % map->size;
- for (;; probes += 1) {
- size_t idx = (start + probes) % map->size;
- profile_times_table_entry *entry = &map->entries[idx];
- if (!entry->probes) {
- entry->probes = probes;
- entry->entry.time_delta = usec;
- entry->entry.count = count;
- map->occupied += 1;
- update_max_probes(map, probes);
- return;
- }
- if (entry->entry.time_delta == usec) {
- entry->entry.count += count;
- return;
- }
- if (entry->probes >= probes)
- continue;
- if (map->occupied / (double)map->size > 0.7) {
- grow_hashmap(map, usec, count);
- return;
- }
- size_t old_probes = entry->probes;
- uint64_t old_count = entry->entry.count;
- uint64_t old_usec = entry->entry.time_delta;
- entry->probes = probes;
- entry->entry.count = count;
- entry->entry.time_delta = usec;
- update_max_probes(map, probes);
- probes = old_probes;
- count = old_count;
- usec = old_usec;
- start = usec % map->size;
- }
- }
- static void init_hashmap(profile_times_table *map, size_t size)
- {
- map->size = size;
- map->occupied = 0;
- map->max_probe_count = 0;
- map->entries = bzalloc(sizeof(profile_times_table_entry) * size);
- map->old_start_index = 0;
- map->old_occupied = 0;
- map->old_entries = NULL;
- }
- static void migrate_old_entries(profile_times_table *map, bool limit_items)
- {
- if (!map->old_entries)
- return;
- if (!map->old_occupied) {
- bfree(map->old_entries);
- map->old_entries = NULL;
- return;
- }
- for (size_t i = 0; !limit_items || i < 8; i++, map->old_start_index++) {
- if (!map->old_occupied)
- return;
- profile_times_table_entry *entry =
- &map->old_entries[map->old_start_index];
- if (!entry->probes)
- continue;
- add_hashmap_entry(map, entry->entry.time_delta,
- entry->entry.count);
- map->old_occupied -= 1;
- }
- }
- static void grow_hashmap(profile_times_table *map, uint64_t usec,
- uint64_t count)
- {
- migrate_old_entries(map, false);
- size_t old_size = map->size;
- size_t old_occupied = map->occupied;
- profile_times_table_entry *entries = map->entries;
- init_hashmap(map, (old_size * 2 < 16) ? 16 : (old_size * 2));
- map->old_occupied = old_occupied;
- map->old_entries = entries;
- add_hashmap_entry(map, usec, count);
- }
- static profile_entry *init_entry(profile_entry *entry, const char *name)
- {
- entry->name = name;
- init_hashmap(&entry->times, 1);
- #ifdef TRACK_OVERHEAD
- init_hashmap(&entry->overhead, 1);
- #endif
- entry->expected_time_between_calls = 0;
- init_hashmap(&entry->times_between_calls, 1);
- return entry;
- }
- static profile_entry *get_child(profile_entry *parent, const char *name)
- {
- const size_t num = parent->children.num;
- for (size_t i = 0; i < num; i++) {
- profile_entry *child = &parent->children.array[i];
- if (child->name == name)
- return child;
- }
- return init_entry(da_push_back_new(parent->children), name);
- }
- static void merge_call(profile_entry *entry, profile_call *call,
- profile_call *prev_call)
- {
- const size_t num = call->children.num;
- for (size_t i = 0; i < num; i++) {
- profile_call *child = &call->children.array[i];
- merge_call(get_child(entry, child->name), child, NULL);
- }
- if (entry->expected_time_between_calls != 0 && prev_call) {
- migrate_old_entries(&entry->times_between_calls, true);
- uint64_t usec = diff_ns_to_usec(prev_call->start_time,
- call->start_time);
- add_hashmap_entry(&entry->times_between_calls, usec, 1);
- }
- migrate_old_entries(&entry->times, true);
- uint64_t usec = diff_ns_to_usec(call->start_time, call->end_time);
- add_hashmap_entry(&entry->times, usec, 1);
- #ifdef TRACK_OVERHEAD
- migrate_old_entries(&entry->overhead, true);
- usec = diff_ns_to_usec(call->overhead_start, call->start_time);
- usec += diff_ns_to_usec(call->end_time, call->overhead_end);
- add_hashmap_entry(&entry->overhead, usec, 1);
- #endif
- }
- static bool enabled = false;
- static pthread_mutex_t root_mutex = PTHREAD_MUTEX_INITIALIZER;
- static DARRAY(profile_root_entry) root_entries;
- static THREAD_LOCAL profile_call *thread_context = NULL;
- static THREAD_LOCAL bool thread_enabled = true;
- void profiler_start(void)
- {
- pthread_mutex_lock(&root_mutex);
- enabled = true;
- pthread_mutex_unlock(&root_mutex);
- }
- void profiler_stop(void)
- {
- pthread_mutex_lock(&root_mutex);
- enabled = false;
- pthread_mutex_unlock(&root_mutex);
- }
- void profile_reenable_thread(void)
- {
- if (thread_enabled)
- return;
- pthread_mutex_lock(&root_mutex);
- thread_enabled = enabled;
- pthread_mutex_unlock(&root_mutex);
- }
- static bool lock_root(void)
- {
- pthread_mutex_lock(&root_mutex);
- if (!enabled) {
- pthread_mutex_unlock(&root_mutex);
- thread_enabled = false;
- return false;
- }
- return true;
- }
- static profile_root_entry *get_root_entry(const char *name)
- {
- profile_root_entry *r_entry = NULL;
- for (size_t i = 0; i < root_entries.num; i++) {
- if (root_entries.array[i].name == name) {
- r_entry = &root_entries.array[i];
- break;
- }
- }
- if (!r_entry) {
- r_entry = da_push_back_new(root_entries);
- r_entry->mutex = bmalloc(sizeof(pthread_mutex_t));
- pthread_mutex_init(r_entry->mutex, NULL);
- r_entry->name = name;
- r_entry->entry = bzalloc(sizeof(profile_entry));
- init_entry(r_entry->entry, name);
- }
- return r_entry;
- }
- void profile_register_root(const char *name,
- uint64_t expected_time_between_calls)
- {
- if (!lock_root())
- return;
- get_root_entry(name)->entry->expected_time_between_calls =
- (expected_time_between_calls + 500) / 1000;
- pthread_mutex_unlock(&root_mutex);
- }
- static void free_call_context(profile_call *context);
- static void merge_context(profile_call *context)
- {
- pthread_mutex_t *mutex = NULL;
- profile_entry *entry = NULL;
- profile_call *prev_call = NULL;
- if (!lock_root()) {
- free_call_context(context);
- return;
- }
- profile_root_entry *r_entry = get_root_entry(context->name);
- mutex = r_entry->mutex;
- entry = r_entry->entry;
- prev_call = r_entry->prev_call;
- r_entry->prev_call = context;
- pthread_mutex_lock(mutex);
- pthread_mutex_unlock(&root_mutex);
- merge_call(entry, context, prev_call);
- pthread_mutex_unlock(mutex);
- free_call_context(prev_call);
- }
- void profile_start(const char *name)
- {
- if (!thread_enabled)
- return;
- profile_call new_call = {
- .name = name,
- #ifdef TRACK_OVERHEAD
- .overhead_start = os_gettime_ns(),
- #endif
- .parent = thread_context,
- };
- profile_call *call = NULL;
- if (new_call.parent) {
- size_t idx = da_push_back(new_call.parent->children, &new_call);
- call = &new_call.parent->children.array[idx];
- } else {
- call = bmalloc(sizeof(profile_call));
- memcpy(call, &new_call, sizeof(profile_call));
- }
- thread_context = call;
- call->start_time = os_gettime_ns();
- }
- void profile_end(const char *name)
- {
- uint64_t end = os_gettime_ns();
- if (!thread_enabled)
- return;
- profile_call *call = thread_context;
- if (!call) {
- blog(LOG_ERROR, "Called profile end with no active profile");
- return;
- }
- if (!call->name)
- call->name = name;
- if (call->name != name) {
- blog(LOG_ERROR,
- "Called profile end with mismatching name: "
- "start(\"%s\"[%p]) <-> end(\"%s\"[%p])",
- call->name, call->name, name, name);
- profile_call *parent = call->parent;
- while (parent && parent->parent && parent->name != name)
- parent = parent->parent;
- if (!parent || parent->name != name)
- return;
- while (call->name != name) {
- profile_end(call->name);
- call = call->parent;
- }
- }
- thread_context = call->parent;
- call->end_time = end;
- #ifdef TRACK_OVERHEAD
- call->overhead_end = os_gettime_ns();
- #endif
- if (call->parent)
- return;
- merge_context(call);
- }
- static int profiler_time_entry_compare(const void *first, const void *second)
- {
- int64_t diff = ((profiler_time_entry *)second)->time_delta -
- ((profiler_time_entry *)first)->time_delta;
- return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
- }
- static uint64_t copy_map_to_array(profile_times_table *map,
- profiler_time_entries_t *entry_buffer,
- uint64_t *min_, uint64_t *max_)
- {
- migrate_old_entries(map, false);
- da_reserve((*entry_buffer), map->occupied);
- da_resize((*entry_buffer), 0);
- uint64_t min__ = ~(uint64_t)0;
- uint64_t max__ = 0;
- uint64_t calls = 0;
- for (size_t i = 0; i < map->size; i++) {
- if (!map->entries[i].probes)
- continue;
- profiler_time_entry *entry = &map->entries[i].entry;
- da_push_back((*entry_buffer), entry);
- calls += entry->count;
- min__ = (min__ < entry->time_delta) ? min__ : entry->time_delta;
- max__ = (max__ > entry->time_delta) ? max__ : entry->time_delta;
- }
- if (min_)
- *min_ = min__;
- if (max_)
- *max_ = max__;
- return calls;
- }
- typedef void (*profile_entry_print_func)(profiler_snapshot_entry_t *entry,
- struct dstr *indent_buffer,
- struct dstr *output_buffer,
- unsigned indent, uint64_t active,
- uint64_t parent_calls);
- /* UTF-8 characters */
- #define VPIPE_RIGHT " \xe2\x94\xa3"
- #define VPIPE " \xe2\x94\x83"
- #define DOWN_RIGHT " \xe2\x94\x97"
- static void make_indent_string(struct dstr *indent_buffer, unsigned indent,
- uint64_t active)
- {
- indent_buffer->len = 0;
- if (!indent) {
- dstr_cat_ch(indent_buffer, 0);
- return;
- }
- for (size_t i = 0; i < indent; i++) {
- const char *fragment = "";
- bool last = i + 1 == indent;
- if (active & ((uint64_t)1 << i))
- fragment = last ? VPIPE_RIGHT : VPIPE;
- else
- fragment = last ? DOWN_RIGHT : " ";
- dstr_cat(indent_buffer, fragment);
- }
- }
- static void gather_stats(uint64_t expected_time_between_calls,
- profiler_time_entries_t *entries, uint64_t calls,
- uint64_t *percentile99, uint64_t *median,
- double *percent_within_bounds)
- {
- if (!entries->num) {
- *percentile99 = 0;
- *median = 0;
- *percent_within_bounds = 0.;
- return;
- }
- /*if (entry_buffer->num > 2)
- blog(LOG_INFO, "buffer-size %lu, overall count %llu\n"
- "map-size %lu, occupied %lu, probes %lu",
- entry_buffer->num, calls,
- map->size, map->occupied,
- map->max_probe_count);*/
- uint64_t accu = 0;
- for (size_t i = 0; i < entries->num; i++) {
- uint64_t old_accu = accu;
- accu += entries->array[i].count;
- if (old_accu < calls * 0.01 && accu >= calls * 0.01)
- *percentile99 = entries->array[i].time_delta;
- else if (old_accu < calls * 0.5 && accu >= calls * 0.5) {
- *median = entries->array[i].time_delta;
- break;
- }
- }
- *percent_within_bounds = 0.;
- if (!expected_time_between_calls)
- return;
- accu = 0;
- for (size_t i = 0; i < entries->num; i++) {
- profiler_time_entry *entry = &entries->array[i];
- if (entry->time_delta < expected_time_between_calls)
- break;
- accu += entry->count;
- }
- *percent_within_bounds = (1. - (double)accu / calls) * 100;
- }
- #define G_MS "g\xC2\xA0ms"
- static void profile_print_entry(profiler_snapshot_entry_t *entry,
- struct dstr *indent_buffer,
- struct dstr *output_buffer, unsigned indent,
- uint64_t active, uint64_t parent_calls)
- {
- uint64_t calls = entry->overall_count;
- uint64_t min_ = entry->min_time;
- uint64_t max_ = entry->max_time;
- uint64_t percentile99 = 0;
- uint64_t median = 0;
- double percent_within_bounds = 0.;
- gather_stats(entry->expected_time_between_calls, &entry->times, calls,
- &percentile99, &median, &percent_within_bounds);
- make_indent_string(indent_buffer, indent, active);
- if (min_ == max_) {
- dstr_printf(output_buffer, "%s%s: %" G_MS, indent_buffer->array,
- entry->name, min_ / 1000.);
- } else {
- dstr_printf(output_buffer,
- "%s%s: min=%" G_MS ", median=%" G_MS ", "
- "max=%" G_MS ", 99th percentile=%" G_MS,
- indent_buffer->array, entry->name, min_ / 1000.,
- median / 1000., max_ / 1000., percentile99 / 1000.);
- if (entry->expected_time_between_calls) {
- double expected_ms =
- entry->expected_time_between_calls / 1000.;
- dstr_catf(output_buffer, ", %g%% below %" G_MS,
- percent_within_bounds, expected_ms);
- }
- }
- if (parent_calls && calls != parent_calls) {
- double calls_per_parent = (double)calls / parent_calls;
- if (lround(calls_per_parent * 10) != 10)
- dstr_catf(output_buffer, ", %g calls per parent call",
- calls_per_parent);
- }
- blog(LOG_INFO, "%s", output_buffer->array);
- active |= (uint64_t)1 << indent;
- for (size_t i = 0; i < entry->children.num; i++) {
- if ((i + 1) == entry->children.num)
- active &= (1 << indent) - 1;
- profile_print_entry(&entry->children.array[i], indent_buffer,
- output_buffer, indent + 1, active, calls);
- }
- }
- static void gather_stats_between(profiler_time_entries_t *entries,
- uint64_t calls, uint64_t lower_bound,
- uint64_t upper_bound, uint64_t min_,
- uint64_t max_, uint64_t *median,
- double *percent, double *lower, double *higher)
- {
- *median = 0;
- *percent = 0.;
- *lower = 0.;
- *higher = 0.;
- if (!entries->num)
- return;
- uint64_t accu = 0;
- for (size_t i = 0; i < entries->num; i++) {
- accu += entries->array[i].count;
- if (accu < calls * 0.5)
- continue;
- *median = entries->array[i].time_delta;
- break;
- }
- bool found_upper_bound = max_ <= upper_bound;
- bool found_lower_bound = false;
- if (min_ >= upper_bound) {
- *higher = 100.;
- return;
- }
- if (found_upper_bound && min_ >= lower_bound) {
- *percent = 100.;
- return;
- }
- accu = 0;
- for (size_t i = 0; i < entries->num; i++) {
- uint64_t delta = entries->array[i].time_delta;
- if (!found_upper_bound && delta <= upper_bound) {
- *higher = (double)accu / calls * 100;
- accu = 0;
- found_upper_bound = true;
- }
- if (!found_lower_bound && delta < lower_bound) {
- *percent = (double)accu / calls * 100;
- accu = 0;
- found_lower_bound = true;
- }
- accu += entries->array[i].count;
- }
- if (!found_upper_bound) {
- *higher = 100.;
- } else if (!found_lower_bound) {
- *percent = (double)accu / calls * 100;
- } else {
- *lower = (double)accu / calls * 100;
- }
- }
- static void profile_print_entry_expected(profiler_snapshot_entry_t *entry,
- struct dstr *indent_buffer,
- struct dstr *output_buffer,
- unsigned indent, uint64_t active,
- uint64_t parent_calls)
- {
- UNUSED_PARAMETER(parent_calls);
- if (!entry->expected_time_between_calls)
- return;
- uint64_t expected_time = entry->expected_time_between_calls;
- uint64_t min_ = entry->min_time_between_calls;
- uint64_t max_ = entry->max_time_between_calls;
- uint64_t median = 0;
- double percent = 0.;
- double lower = 0.;
- double higher = 0.;
- gather_stats_between(&entry->times_between_calls,
- entry->overall_between_calls_count,
- (uint64_t)(expected_time * 0.98),
- (uint64_t)(expected_time * 1.02 + 0.5), min_, max_,
- &median, &percent, &lower, &higher);
- make_indent_string(indent_buffer, indent, active);
- blog(LOG_INFO,
- "%s%s: min=%" G_MS ", median=%" G_MS ", max=%" G_MS ", %g%% "
- "within ±2%% of %" G_MS " (%g%% lower, %g%% higher)",
- indent_buffer->array, entry->name, min_ / 1000., median / 1000.,
- max_ / 1000., percent, expected_time / 1000., lower, higher);
- active |= (uint64_t)1 << indent;
- for (size_t i = 0; i < entry->children.num; i++) {
- if ((i + 1) == entry->children.num)
- active &= (1 << indent) - 1;
- profile_print_entry_expected(&entry->children.array[i],
- indent_buffer, output_buffer,
- indent + 1, active, 0);
- }
- }
- void profile_print_func(const char *intro, profile_entry_print_func print,
- profiler_snapshot_t *snap)
- {
- struct dstr indent_buffer = {0};
- struct dstr output_buffer = {0};
- bool free_snapshot = !snap;
- if (!snap)
- snap = profile_snapshot_create();
- blog(LOG_INFO, "%s", intro);
- for (size_t i = 0; i < snap->roots.num; i++) {
- print(&snap->roots.array[i], &indent_buffer, &output_buffer, 0,
- 0, 0);
- }
- blog(LOG_INFO, "=================================================");
- if (free_snapshot)
- profile_snapshot_free(snap);
- dstr_free(&output_buffer);
- dstr_free(&indent_buffer);
- }
- void profiler_print(profiler_snapshot_t *snap)
- {
- profile_print_func("== Profiler Results =============================",
- profile_print_entry, snap);
- }
- void profiler_print_time_between_calls(profiler_snapshot_t *snap)
- {
- profile_print_func("== Profiler Time Between Calls ==================",
- profile_print_entry_expected, snap);
- }
- static void free_call_children(profile_call *call)
- {
- if (!call)
- return;
- const size_t num = call->children.num;
- for (size_t i = 0; i < num; i++)
- free_call_children(&call->children.array[i]);
- da_free(call->children);
- }
- static void free_call_context(profile_call *context)
- {
- free_call_children(context);
- bfree(context);
- }
- static void free_hashmap(profile_times_table *map)
- {
- map->size = 0;
- bfree(map->entries);
- map->entries = NULL;
- bfree(map->old_entries);
- map->old_entries = NULL;
- }
- static void free_profile_entry(profile_entry *entry)
- {
- for (size_t i = 0; i < entry->children.num; i++)
- free_profile_entry(&entry->children.array[i]);
- free_hashmap(&entry->times);
- #ifdef TRACK_OVERHEAD
- free_hashmap(&entry->overhead);
- #endif
- free_hashmap(&entry->times_between_calls);
- da_free(entry->children);
- }
- void profiler_free(void)
- {
- DARRAY(profile_root_entry) old_root_entries = {0};
- pthread_mutex_lock(&root_mutex);
- enabled = false;
- da_move(old_root_entries, root_entries);
- pthread_mutex_unlock(&root_mutex);
- for (size_t i = 0; i < old_root_entries.num; i++) {
- profile_root_entry *entry = &old_root_entries.array[i];
- pthread_mutex_lock(entry->mutex);
- pthread_mutex_unlock(entry->mutex);
- pthread_mutex_destroy(entry->mutex);
- bfree(entry->mutex);
- entry->mutex = NULL;
- free_call_context(entry->prev_call);
- free_profile_entry(entry->entry);
- bfree(entry->entry);
- }
- da_free(old_root_entries);
- pthread_mutex_destroy(&root_mutex);
- }
- /* ------------------------------------------------------------------------- */
- /* Profiler name storage */
- struct profiler_name_store {
- pthread_mutex_t mutex;
- DARRAY(char *) names;
- };
- profiler_name_store_t *profiler_name_store_create(void)
- {
- profiler_name_store_t *store = bzalloc(sizeof(profiler_name_store_t));
- if (pthread_mutex_init(&store->mutex, NULL))
- goto error;
- return store;
- error:
- bfree(store);
- return NULL;
- }
- void profiler_name_store_free(profiler_name_store_t *store)
- {
- if (!store)
- return;
- for (size_t i = 0; i < store->names.num; i++)
- bfree(store->names.array[i]);
- da_free(store->names);
- pthread_mutex_destroy(&store->mutex);
- bfree(store);
- }
- const char *profile_store_name(profiler_name_store_t *store, const char *format,
- ...)
- {
- va_list args;
- va_start(args, format);
- struct dstr str = {0};
- dstr_vprintf(&str, format, args);
- va_end(args);
- const char *result = NULL;
- pthread_mutex_lock(&store->mutex);
- size_t idx = da_push_back(store->names, &str.array);
- result = store->names.array[idx];
- pthread_mutex_unlock(&store->mutex);
- return result;
- }
- /* ------------------------------------------------------------------------- */
- /* Profiler data access */
- static void add_entry_to_snapshot(profile_entry *entry,
- profiler_snapshot_entry_t *s_entry)
- {
- s_entry->name = entry->name;
- s_entry->overall_count =
- copy_map_to_array(&entry->times, &s_entry->times,
- &s_entry->min_time, &s_entry->max_time);
- if ((s_entry->expected_time_between_calls =
- entry->expected_time_between_calls))
- s_entry->overall_between_calls_count =
- copy_map_to_array(&entry->times_between_calls,
- &s_entry->times_between_calls,
- &s_entry->min_time_between_calls,
- &s_entry->max_time_between_calls);
- da_reserve(s_entry->children, entry->children.num);
- for (size_t i = 0; i < entry->children.num; i++)
- add_entry_to_snapshot(&entry->children.array[i],
- da_push_back_new(s_entry->children));
- }
- static void sort_snapshot_entry(profiler_snapshot_entry_t *entry)
- {
- qsort(entry->times.array, entry->times.num, sizeof(profiler_time_entry),
- profiler_time_entry_compare);
- if (entry->expected_time_between_calls)
- qsort(entry->times_between_calls.array,
- entry->times_between_calls.num,
- sizeof(profiler_time_entry), profiler_time_entry_compare);
- for (size_t i = 0; i < entry->children.num; i++)
- sort_snapshot_entry(&entry->children.array[i]);
- }
- profiler_snapshot_t *profile_snapshot_create(void)
- {
- profiler_snapshot_t *snap = bzalloc(sizeof(profiler_snapshot_t));
- pthread_mutex_lock(&root_mutex);
- da_reserve(snap->roots, root_entries.num);
- for (size_t i = 0; i < root_entries.num; i++) {
- pthread_mutex_lock(root_entries.array[i].mutex);
- add_entry_to_snapshot(root_entries.array[i].entry,
- da_push_back_new(snap->roots));
- pthread_mutex_unlock(root_entries.array[i].mutex);
- }
- pthread_mutex_unlock(&root_mutex);
- for (size_t i = 0; i < snap->roots.num; i++)
- sort_snapshot_entry(&snap->roots.array[i]);
- return snap;
- }
- static void free_snapshot_entry(profiler_snapshot_entry_t *entry)
- {
- for (size_t i = 0; i < entry->children.num; i++)
- free_snapshot_entry(&entry->children.array[i]);
- da_free(entry->children);
- da_free(entry->times_between_calls);
- da_free(entry->times);
- }
- void profile_snapshot_free(profiler_snapshot_t *snap)
- {
- if (!snap)
- return;
- for (size_t i = 0; i < snap->roots.num; i++)
- free_snapshot_entry(&snap->roots.array[i]);
- da_free(snap->roots);
- bfree(snap);
- }
- typedef void (*dump_csv_func)(void *data, struct dstr *buffer);
- static void entry_dump_csv(struct dstr *buffer,
- const profiler_snapshot_entry_t *parent,
- const profiler_snapshot_entry_t *entry,
- dump_csv_func func, void *data)
- {
- const char *parent_name = parent ? parent->name : NULL;
- for (size_t i = 0; i < entry->times.num; i++) {
- dstr_printf(buffer,
- "%p,%p,%p,%p,%s,0,"
- "%" PRIu64 ",%" PRIu64 "\n",
- entry, parent, entry->name, parent_name,
- entry->name, entry->times.array[i].time_delta,
- entry->times.array[i].count);
- func(data, buffer);
- }
- for (size_t i = 0; i < entry->times_between_calls.num; i++) {
- dstr_printf(buffer,
- "%p,%p,%p,%p,%s,"
- "%" PRIu64 ",%" PRIu64 ",%" PRIu64 "\n",
- entry, parent, entry->name, parent_name,
- entry->name, entry->expected_time_between_calls,
- entry->times_between_calls.array[i].time_delta,
- entry->times_between_calls.array[i].count);
- func(data, buffer);
- }
- for (size_t i = 0; i < entry->children.num; i++)
- entry_dump_csv(buffer, entry, &entry->children.array[i], func,
- data);
- }
- static void profiler_snapshot_dump(const profiler_snapshot_t *snap,
- dump_csv_func func, void *data)
- {
- struct dstr buffer = {0};
- dstr_init_copy(&buffer, "id,parent_id,name_id,parent_name_id,name,"
- "time_between_calls,time_delta_µs,count\n");
- func(data, &buffer);
- for (size_t i = 0; i < snap->roots.num; i++)
- entry_dump_csv(&buffer, NULL, &snap->roots.array[i], func,
- data);
- dstr_free(&buffer);
- }
- static void dump_csv_fwrite(void *data, struct dstr *buffer)
- {
- fwrite(buffer->array, 1, buffer->len, data);
- }
- bool profiler_snapshot_dump_csv(const profiler_snapshot_t *snap,
- const char *filename)
- {
- FILE *f = os_fopen(filename, "wb+");
- if (!f)
- return false;
- profiler_snapshot_dump(snap, dump_csv_fwrite, f);
- fclose(f);
- return true;
- }
- static void dump_csv_gzwrite(void *data, struct dstr *buffer)
- {
- gzwrite(data, buffer->array, (unsigned)buffer->len);
- }
- bool profiler_snapshot_dump_csv_gz(const profiler_snapshot_t *snap,
- const char *filename)
- {
- gzFile gz;
- #ifdef _WIN32
- wchar_t *filename_w = NULL;
- os_utf8_to_wcs_ptr(filename, 0, &filename_w);
- if (!filename_w)
- return false;
- gz = gzopen_w(filename_w, "wb");
- bfree(filename_w);
- #else
- gz = gzopen(filename, "wb");
- #endif
- if (!gz)
- return false;
- profiler_snapshot_dump(snap, dump_csv_gzwrite, gz);
- #ifdef _WIN32
- gzclose_w(gz);
- #else
- gzclose(gz);
- #endif
- return true;
- }
- size_t profiler_snapshot_num_roots(profiler_snapshot_t *snap)
- {
- return snap ? snap->roots.num : 0;
- }
- void profiler_snapshot_enumerate_roots(profiler_snapshot_t *snap,
- profiler_entry_enum_func func,
- void *context)
- {
- if (!snap)
- return;
- for (size_t i = 0; i < snap->roots.num; i++)
- if (!func(context, &snap->roots.array[i]))
- break;
- }
- void profiler_snapshot_filter_roots(profiler_snapshot_t *snap,
- profiler_name_filter_func func, void *data)
- {
- for (size_t i = 0; i < snap->roots.num;) {
- bool remove = false;
- bool res = func(data, snap->roots.array[i].name, &remove);
- if (remove) {
- free_snapshot_entry(&snap->roots.array[i]);
- da_erase(snap->roots, i);
- }
- if (!res)
- break;
- if (!remove)
- i += 1;
- }
- }
- size_t profiler_snapshot_num_children(profiler_snapshot_entry_t *entry)
- {
- return entry ? entry->children.num : 0;
- }
- void profiler_snapshot_enumerate_children(profiler_snapshot_entry_t *entry,
- profiler_entry_enum_func func,
- void *context)
- {
- if (!entry)
- return;
- for (size_t i = 0; i < entry->children.num; i++)
- if (!func(context, &entry->children.array[i]))
- break;
- }
- const char *profiler_snapshot_entry_name(profiler_snapshot_entry_t *entry)
- {
- return entry ? entry->name : NULL;
- }
- profiler_time_entries_t *
- profiler_snapshot_entry_times(profiler_snapshot_entry_t *entry)
- {
- return entry ? &entry->times : NULL;
- }
- uint64_t profiler_snapshot_entry_overall_count(profiler_snapshot_entry_t *entry)
- {
- return entry ? entry->overall_count : 0;
- }
- uint64_t profiler_snapshot_entry_min_time(profiler_snapshot_entry_t *entry)
- {
- return entry ? entry->min_time : 0;
- }
- uint64_t profiler_snapshot_entry_max_time(profiler_snapshot_entry_t *entry)
- {
- return entry ? entry->max_time : 0;
- }
- profiler_time_entries_t *
- profiler_snapshot_entry_times_between_calls(profiler_snapshot_entry_t *entry)
- {
- return entry ? &entry->times_between_calls : NULL;
- }
- uint64_t profiler_snapshot_entry_expected_time_between_calls(
- profiler_snapshot_entry_t *entry)
- {
- return entry ? entry->expected_time_between_calls : 0;
- }
- uint64_t
- profiler_snapshot_entry_min_time_between_calls(profiler_snapshot_entry_t *entry)
- {
- return entry ? entry->min_time_between_calls : 0;
- }
- uint64_t
- profiler_snapshot_entry_max_time_between_calls(profiler_snapshot_entry_t *entry)
- {
- return entry ? entry->max_time_between_calls : 0;
- }
- uint64_t profiler_snapshot_entry_overall_between_calls_count(
- profiler_snapshot_entry_t *entry)
- {
- return entry ? entry->overall_between_calls_count : 0;
- }
|