* SPDX-License-Identifier: MIT
* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
* Description: umq perf
* Create: 2025-10-29
*/
#include <stdarg.h>
#include "umq_errno.h"
#include "umq_vlog.h"
#include "urpc_thread_closure.h"
#include "urpc_util.h"
#include "urpc_bitmap.h"
#include "umq_perf_hdr.h"
#include "umq_thread_local.h"
#include "perf.h"
#define UMQ_PERF_IO_DIRECTION_ALL_OFFSET (0)
#define UMQ_PERF_IO_DIRECTION_TX_OFFSET (1)
#define UMQ_PERF_IO_DIRECTION_RX_OFFSET (2)
#define UMQ_PERF_INTERRUPT_DIRECTION_TX_OFFSET (0)
#define UMQ_PERF_INTERRUPT_DIRECTION_RX_OFFSET (1)
typedef struct umq_perf_record {
struct {
umq_perf_record_type_t type;
uint64_t accumulation;
uint64_t min;
uint64_t max;
uint64_t cnt;
umq_perf_hdr_t *hdr;
} type_record[UMQ_PERF_RECORD_TYPE_MAX];
volatile bool inited;
} umq_perf_record_t;
typedef struct umq_perf_record_ctx {
umq_perf_record_t perf_record_table[UMQ_THREAD_ID_MAX];
} umq_perf_record_ctx_t;
static const double g_umq_perf_quantile[UMQ_PERF_QUANTILE_CNT] = {
50.0,
90.0,
99.0,
99.99
};
static bool g_umq_perf_record_enable = false;
static umq_perf_record_ctx_t *g_umq_perf_record_ctx;
static inline uint64_t umq_perf_hdr_max_cycles_get(void)
{
return (uint64_t)UMQ_PERF_HDR_DEFAULT_MAX_MS * urpc_get_cpu_hz() / MS_PER_SEC;
}
static void umq_perf_destroy_all_hdrs(uint32_t idx)
{
umq_perf_record_t *rec = &g_umq_perf_record_ctx->perf_record_table[idx];
for (int type = 0; type < UMQ_PERF_RECORD_TYPE_MAX; ++type) {
if (rec->type_record[type].hdr != NULL) {
umq_perf_hdr_destroy(rec->type_record[type].hdr);
rec->type_record[type].hdr = NULL;
}
}
rec->inited = false;
}
static void umq_perf_record_closure(uint64_t idx)
{
if (g_umq_perf_record_ctx == NULL) {
return;
}
umq_perf_record_t *cur_record = &g_umq_perf_record_ctx->perf_record_table[idx];
cur_record->inited = false;
return;
}
static int umq_perf_type_record_init(uint32_t idx)
{
int type;
umq_perf_record_t *rec = &g_umq_perf_record_ctx->perf_record_table[idx];
for (type = 0; type < UMQ_PERF_RECORD_TYPE_MAX; ++type) {
rec->type_record[type].accumulation = 0;
rec->type_record[type].min = UINT64_MAX;
rec->type_record[type].max = 0;
rec->type_record[type].cnt = 0;
if (rec->type_record[type].hdr != NULL) {
umq_perf_hdr_reset(rec->type_record[type].hdr);
} else {
rec->type_record[type].hdr = umq_perf_hdr_create(umq_perf_hdr_max_cycles_get());
if (rec->type_record[type].hdr == NULL) {
UMQ_LIMIT_VLOG_ERR(VLOG_UMQ, "umq_perf_hdr_create failed, quantile data of type %d missing\n", type);
goto ERROR;
}
}
}
rec->inited = true;
return UMQ_SUCCESS;
ERROR:
umq_perf_destroy_all_hdrs(idx);
return -UMQ_ERR_ENOMEM;
}
int umq_perf_init(void)
{
int ret = UMQ_SUCCESS;
if (g_umq_perf_record_ctx != NULL) {
UMQ_VLOG_ERR(VLOG_UMQ, "umq perf has been inited\n");
return -UMQ_ERR_EEXIST;
}
g_umq_perf_record_ctx = (umq_perf_record_ctx_t *)calloc(1, sizeof(umq_perf_record_ctx_t));
if (g_umq_perf_record_ctx == NULL) {
UMQ_VLOG_ERR(VLOG_UMQ, "calloc for umq_perf_record failed\n");
return -UMQ_ERR_ENOMEM;
}
for (uint32_t i = 0; i < UMQ_THREAD_ID_RANGE_DEFAULT; i++) {
ret = umq_perf_type_record_init(i);
if (ret != UMQ_SUCCESS) {
for (uint32_t j = 0; j < i; j++) {
umq_perf_destroy_all_hdrs(j);
}
goto FREE_CTX;
}
urpc_thread_closure_register(THREAD_CLOSURE_UMQ_PERF, i, umq_perf_record_closure);
}
return UMQ_SUCCESS;
FREE_CTX:
free(g_umq_perf_record_ctx);
g_umq_perf_record_ctx = NULL;
return ret;
}
void umq_perf_uninit(void)
{
if (g_umq_perf_record_ctx == NULL) {
return;
}
g_umq_perf_record_enable = false;
for (uint32_t i = 0; i < UMQ_THREAD_ID_MAX; i++) {
umq_perf_destroy_all_hdrs(i);
}
free(g_umq_perf_record_ctx);
g_umq_perf_record_ctx = NULL;
}
static void umq_clear_perf_record_item(uint32_t record_idx)
{
if (g_umq_perf_record_ctx == NULL) {
return;
}
umq_perf_record_t *cur_record = &g_umq_perf_record_ctx->perf_record_table[record_idx];
for (int type = 0; type < UMQ_PERF_RECORD_TYPE_MAX; ++type) {
cur_record->type_record[type].accumulation = 0;
cur_record->type_record[type].min = UINT64_MAX;
cur_record->type_record[type].max = 0;
cur_record->type_record[type].cnt = 0;
if (cur_record->type_record[type].hdr != NULL) {
umq_perf_hdr_reset(cur_record->type_record[type].hdr);
}
}
cur_record->inited = false;
}
uint64_t umq_perf_get_start_timestamp(void)
{
if (!g_umq_perf_record_enable) {
return 0;
}
uint32_t thead_id = umq_thread_id_get();
if (thead_id < UMQ_THREAD_ID_MAX && !g_umq_perf_record_ctx->perf_record_table[thead_id].inited) {
umq_perf_type_record_init(thead_id);
urpc_thread_closure_register(THREAD_CLOSURE_UMQ_PERF, thead_id, umq_perf_record_closure);
}
return urpc_get_cpu_cycles();
}
static umq_perf_hdr_t *umq_perf_ensure_hdr(umq_perf_record_t *rec, umq_perf_record_type_t type)
{
umq_perf_hdr_t *h = rec->type_record[type].hdr;
if (h != NULL) {
return h;
}
h = umq_perf_hdr_create(umq_perf_hdr_max_cycles_get());
if (h == NULL) {
UMQ_LIMIT_VLOG_ERR(VLOG_UMQ, "umq_perf_hdr_create failed, quantile data of type %d missing\n", type);
return NULL;
}
rec->type_record[type].hdr = h;
return h;
}
static void umq_perf_fill_perf_record(umq_perf_record_type_t type, uint64_t start)
{
uint64_t delta = urpc_get_cpu_cycles() - start;
uint32_t thead_id = umq_thread_id_get();
if (thead_id >= UMQ_THREAD_ID_MAX) {
return;
}
umq_perf_record_t *cur_rec = &g_umq_perf_record_ctx->perf_record_table[thead_id];
cur_rec->type_record[type].accumulation += delta;
(delta < cur_rec->type_record[type].min) ? cur_rec->type_record[type].min = delta : 0;
(delta > cur_rec->type_record[type].max) ? cur_rec->type_record[type].max = delta : 0;
++cur_rec->type_record[type].cnt;
umq_perf_hdr_t *h = umq_perf_ensure_hdr(cur_rec, type);
if (h != NULL) {
umq_perf_hdr_record(h, delta);
}
}
void umq_perf_record_write(umq_perf_record_type_t type, uint64_t start)
{
if (!g_umq_perf_record_enable || start == 0) {
return;
}
umq_perf_fill_perf_record(type, start);
}
void umq_perf_record_write_with_direction(umq_perf_record_type_t type, uint64_t start, umq_io_direction_t direction)
{
if (!g_umq_perf_record_enable || start == 0 || direction >= UMQ_IO_MAX) {
return;
}
static const umq_perf_record_type_t perf_record_type_map[UMQ_IO_MAX] = {
[UMQ_IO_ALL] = UMQ_PERF_IO_DIRECTION_ALL_OFFSET,
[UMQ_IO_TX] = UMQ_PERF_IO_DIRECTION_TX_OFFSET,
[UMQ_IO_RX] = UMQ_PERF_IO_DIRECTION_RX_OFFSET,
};
umq_perf_fill_perf_record(type + perf_record_type_map[direction], start);
}
void umq_perf_record_write_interrupt_with_direction(
umq_perf_record_type_t type, uint64_t start, umq_io_direction_t direction)
{
if (!g_umq_perf_record_enable || start == 0 || direction >= UMQ_IO_MAX || direction == UMQ_IO_ALL) {
return;
}
static const umq_perf_record_type_t perf_record_type_map[UMQ_IO_MAX] = {
[UMQ_IO_TX] = UMQ_PERF_INTERRUPT_DIRECTION_TX_OFFSET,
[UMQ_IO_RX] = UMQ_PERF_INTERRUPT_DIRECTION_RX_OFFSET,
};
umq_perf_fill_perf_record(type + perf_record_type_map[direction], start);
}
int umq_perf_start(void)
{
if (g_umq_perf_record_ctx == NULL) {
int ret = umq_perf_init();
if (ret != UMQ_SUCCESS) {
UMQ_VLOG_ERR(VLOG_UMQ, "umq perf init failed\n");
return ret;
}
}
if (g_umq_perf_record_enable) {
UMQ_VLOG_ERR(VLOG_UMQ, "invalid parameter\n");
return -UMQ_ERR_EINVAL;
}
g_umq_perf_record_enable = true;
return UMQ_SUCCESS;
}
int umq_perf_reset(umq_perf_stats_cfg_t *perf_stats_cfg)
{
if (g_umq_perf_record_ctx == NULL || perf_stats_cfg == NULL) {
UMQ_VLOG_ERR(VLOG_UMQ, "invalid parameter\n");
return -UMQ_ERR_EINVAL;
}
for (uint32_t i = 0; i < UMQ_THREAD_ID_MAX; ++i) {
umq_clear_perf_record_item(i);
}
return UMQ_SUCCESS;
}
int umq_perf_stop(void)
{
if (g_umq_perf_record_ctx == NULL || !g_umq_perf_record_enable) {
UMQ_VLOG_ERR(VLOG_UMQ, "invalid parameter\n");
return -UMQ_ERR_EINVAL;
}
g_umq_perf_record_enable = false;
return UMQ_SUCCESS;
}
static inline uint64_t cpu_cycles_to_ns(uint64_t cycles)
{
if (cycles != 0 && UINT64_MAX / cycles <= NS_PER_SEC) {
return cycles / urpc_get_cpu_hz() * NS_PER_SEC;
} else {
return cycles * NS_PER_SEC / urpc_get_cpu_hz();
}
}
static inline void umq_perf_convert_cycles_to_ns(umq_perf_record_t *perf_rec)
{
for (int type = 0; type < UMQ_PERF_RECORD_TYPE_MAX; ++type) {
perf_rec->type_record[type].accumulation = cpu_cycles_to_ns(perf_rec->type_record[type].accumulation);
perf_rec->type_record[type].min =
perf_rec->type_record[type].min == UINT64_MAX ? 0 : cpu_cycles_to_ns(perf_rec->type_record[type].min);
perf_rec->type_record[type].max = cpu_cycles_to_ns(perf_rec->type_record[type].max);
}
}
static ALWAYS_INLINE void umq_perf_record_add(umq_perf_record_t *total_perf_record, umq_perf_record_t *perf_record)
{
for (uint32_t i = 0; i < UMQ_PERF_RECORD_TYPE_MAX; i++) {
if (perf_record->type_record[i].cnt == 0) {
continue;
}
total_perf_record->type_record[i].accumulation += perf_record->type_record[i].accumulation;
total_perf_record->type_record[i].min =
(total_perf_record->type_record[i].min != 0 &&
total_perf_record->type_record[i].min < perf_record->type_record[i].min) ?
total_perf_record->type_record[i].min : perf_record->type_record[i].min;
total_perf_record->type_record[i].max =
total_perf_record->type_record[i].max > perf_record->type_record[i].max ?
total_perf_record->type_record[i].max : perf_record->type_record[i].max;
total_perf_record->type_record[i].cnt += perf_record->type_record[i].cnt;
if (perf_record->type_record[i].hdr != NULL) {
if (total_perf_record->type_record[i].hdr == NULL) {
total_perf_record->type_record[i].hdr = umq_perf_hdr_create(umq_perf_hdr_max_cycles_get());
}
if (total_perf_record->type_record[i].hdr == NULL) {
UMQ_LIMIT_VLOG_ERR(VLOG_UMQ,
"umq_perf_hdr_create failed, quantile data of type %u missing\n", i);
continue;
}
umq_perf_hdr_merge(total_perf_record->type_record[i].hdr, perf_record->type_record[i].hdr);
}
}
}
int umq_perf_info_get(umq_perf_stats_t *perf_info)
{
if (g_umq_perf_record_ctx == NULL || perf_info == NULL) {
UMQ_VLOG_ERR(VLOG_UMQ, "invalid parameter\n");
return -UMQ_ERR_EINVAL;
}
umq_perf_record_t total_perf_record = {0};
for (uint32_t i = 0; i < UMQ_THREAD_ID_MAX; ++i) {
umq_perf_record_add(&total_perf_record, &g_umq_perf_record_ctx->perf_record_table[i]);
}
umq_perf_convert_cycles_to_ns(&total_perf_record);
for (uint32_t i = 0; i < UMQ_PERF_RECORD_TYPE_MAX; i++) {
uint64_t cnt = total_perf_record.type_record[i].cnt;
perf_info->type_record[i].type = i;
perf_info->type_record[i].sample_num = cnt;
perf_info->type_record[i].average = cnt != 0 ?
(total_perf_record.type_record[i].accumulation / cnt) : 0;
perf_info->type_record[i].maxinum = total_perf_record.type_record[i].max;
perf_info->type_record[i].mininum = total_perf_record.type_record[i].min;
umq_perf_hdr_t *h = total_perf_record.type_record[i].hdr;
for (uint32_t j = 0; j < UMQ_PERF_QUANTILE_CNT; j++) {
if (cnt > 0 && h != NULL) {
perf_info->type_record[i].quantile[j] = cpu_cycles_to_ns(
umq_perf_hdr_value_at_quantile(h, g_umq_perf_quantile[j]));
} else {
perf_info->type_record[i].quantile[j] = 0;
}
}
if (h != NULL) {
umq_perf_hdr_destroy(h);
}
}
return 0;
}