Skip to content

File syn_profiler.c

File List > debug > syn_profiler.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_PROFILER) || SYN_USE_PROFILER

#include "../util/syn_assert.h"
#include "../util/syn_fmt.h"
#include "syn_profiler.h"

#include <string.h>

void syn_profiler_init(SYN_Profiler *prof, SYN_ProfileEntry *entries, uint8_t capacity)
{
    SYN_ASSERT(prof != NULL);
    SYN_ASSERT(entries != NULL);
    SYN_ASSERT(capacity > 0);

    memset(entries, 0, sizeof(*entries) * capacity);
    prof->entries = entries;
    prof->capacity = capacity;
    prof->period_start = syn_port_get_tick_ms();
    prof->period_ms = 0;
    prof->enabled = true;
}

void syn_profiler_register(SYN_Profiler *prof, uint8_t index, const char *name)
{
    SYN_ASSERT(prof != NULL);
    SYN_ASSERT(index < prof->capacity);
    prof->entries[index].name = name;
}

void syn_profiler_task_begin(SYN_Profiler *prof, uint8_t index)
{
    SYN_ASSERT(prof != NULL);
    if (!prof->enabled)
        return;
    if (index >= prof->capacity)
        return;

    prof->entries[index]._start_tick = syn_port_get_tick_ms();
}

void syn_profiler_task_end(SYN_Profiler *prof, uint8_t index)
{
    SYN_ASSERT(prof != NULL);
    if (!prof->enabled)
        return;
    if (index >= prof->capacity)
        return;

    SYN_ProfileEntry *e = &prof->entries[index];
    uint32_t elapsed = syn_port_get_tick_ms() - e->_start_tick;

    /* We work in ms (our tick resolution) but report as µs for display.
     * On platforms with µs ticks, this is exact; with ms ticks it's ×1000. */
    uint32_t elapsed_us = elapsed * 1000;

    e->total_us += elapsed_us;
    if (elapsed_us > e->peak_us) {
        e->peak_us = elapsed_us;
    }
    e->run_count++;
}

void syn_profiler_update(SYN_Profiler *prof)
{
    SYN_ASSERT(prof != NULL);

    uint32_t now = syn_port_get_tick_ms();
    uint32_t period = now - prof->period_start;
    prof->period_ms = period;

    if (period == 0)
        period = 1; /* avoid divide by zero */

    /* Calculate CPU percentages (×10 for 0.1% resolution) */
    for (uint8_t i = 0; i < prof->capacity; i++) {
        SYN_ProfileEntry *e = &prof->entries[i];
        if (e->name == NULL)
            continue;

        /* total_us is in microseconds, period is in ms */
        /* cpu% = (total_us / 1000) / period * 100
         *      = total_us / (period * 10)
         * cpu% × 10 = total_us / period                    */
        e->cpu_percent_x10 = (uint16_t)(e->total_us / period);
    }

    /* Reset for next period (keep peak) */
    for (uint8_t i = 0; i < prof->capacity; i++) {
        prof->entries[i].total_us = 0;
        prof->entries[i].run_count = 0;
        /* peak_us is NOT reset — it's an all-time high watermark */
    }
    prof->period_start = now;
}

void syn_profiler_dump(const SYN_Profiler *prof, SYN_ProfilerPrintFunc print)
{
    SYN_ASSERT(prof != NULL);
    if (print == NULL)
        return;

    char buf[80];
    char num[12];

    /* Header */
    const char *h1[] = {"--- Task Profiler (period=", NULL, "ms) ---\n"};
    syn_fmt_uint(num, sizeof(num), (uint32_t)prof->period_ms);
    h1[1] = num;
    syn_fmt_concat(buf, sizeof(buf), h1, 3);
    print(buf);

    print("Task          CPU%     Peak(us)     Runs\n");

    for (uint8_t i = 0; i < prof->capacity; i++) {
        const SYN_ProfileEntry *e = &prof->entries[i];
        if (e->name == NULL)
            continue;

        /* Build line: "name          XX.X%   peak     runs\n" */
        size_t pos = 0;

        /* Name (pad to 14 chars) */
        size_t nlen = strlen(e->name);
        if (nlen > 13)
            nlen = 13;
        memcpy(buf + pos, e->name, nlen);
        pos += nlen;
        while (pos < 14)
            buf[pos++] = ' ';

        /* CPU% as X.X% */
        syn_fmt_uint(num, sizeof(num), e->cpu_percent_x10 / 10);
        size_t nl = strlen(num);
        memcpy(buf + pos, num, nl);
        pos += nl;
        buf[pos++] = '.';
        buf[pos++] = (char)('0' + (e->cpu_percent_x10 % 10));
        buf[pos++] = '%';
        while (pos < 22)
            buf[pos++] = ' ';

        /* Peak (us) */
        syn_fmt_uint(num, sizeof(num), (uint32_t)e->peak_us);
        nl = strlen(num);
        size_t pad = (8 > nl) ? 8 - nl : 0;
        while (pad-- > 0)
            buf[pos++] = ' ';
        memcpy(buf + pos, num, nl);
        pos += nl;
        while (pos < 35)
            buf[pos++] = ' ';

        /* Runs */
        syn_fmt_uint(num, sizeof(num), (uint32_t)e->run_count);
        nl = strlen(num);
        pad = (8 > nl) ? 8 - nl : 0;
        while (pad-- > 0)
            buf[pos++] = ' ';
        memcpy(buf + pos, num, nl);
        pos += nl;

        buf[pos++] = '\n';
        buf[pos] = '\0';
        print(buf);
    }
}

void syn_profiler_enable(SYN_Profiler *prof, bool enable)
{
    SYN_ASSERT(prof != NULL);
    prof->enabled = enable;
}

#endif /* SYN_USE_PROFILER */