/* npu_monitor.c — minimal libqcperf NPU integration example */
#define _POSIX_C_SOURCE 200809L
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "qcperf.h"
#include "qcperf_common.h"
/* ── Shared state ─────────────────────────────────────────────────────────── */
static volatile sig_atomic_t g_running = 1;
/* Deep copy of backend info so the callback can resolve metric names. */
static struct QcPerfBackendInfo *g_info = NULL;
/* ── Signal handler ───────────────────────────────────────────────────────── */
static void on_signal(int sig) {
(void)sig;
g_running = 0;
}
/* ── Data callback ────────────────────────────────────────────────────────── */
/*
* Called by the libqcperf background thread once per streaming interval
* (1000 ms in this example). data->metric_response holds all samples
* collected during that window; we print only the most recent value for
* each metric_id.
*/
static enum QcPerfReturnCode on_data(struct QcPerfData *data) {
uint32_t idx = 0;
uint64_t written = 0;
if (NULL == data) {
return QC_PERF_RETURN_CODE_FAILED;
}
printf("--- NPU snapshot ---\n");
for (idx = data->metric_response_len; idx > 0; idx--) {
uint16_t mid = data->metric_response[idx - 1].metric_id;
uint64_t bit = (mid < 64) ? ((uint64_t)1 << mid) : 0;
if (0 == bit || 0 != (written & bit)) {
continue; /* skip: out of range or already printed */
}
written |= bit;
/* Resolve the metric name from the deep-copied backend info. */
const char *name = NULL;
if (NULL != g_info && NULL != g_info->capabilities_list) {
uint8_t cap = data->capabilityId;
if (cap < g_info->capabilities_list_length) {
struct QcPerfCapabilityInfo *ci = &g_info->capabilities_list[cap];
for (uint8_t m = 0; m < ci->metric_ids_list_len; m++) {
if (ci->metric_ids_list[m].metric_id == mid) {
name = ci->metric_ids_list[m].metric_name;
break;
}
}
}
}
if (NULL == name) {
printf(" metric_%u: ", (unsigned)mid);
} else {
printf(" %-20s ", name);
}
struct QcPerfGenericType *v = &data->metric_response[idx - 1].metric_value;
switch (v->data_type) {
case QC_PERF_DATA_TYPE_DOUBLE: printf("%.2f\n", v->double_value); break;
case QC_PERF_DATA_TYPE_UINT64: printf("%llu\n", (unsigned long long)v->uint64_value); break;
case QC_PERF_DATA_TYPE_INT64: printf("%lld\n", (long long)v->int64_value); break;
case QC_PERF_DATA_TYPE_BOOL: printf("%s\n", v->bool_value ? "true" : "false"); break;
default: printf("(unknown type)\n"); break;
}
}
return QC_PERF_RETURN_CODE_SUCCESS;
}
/* ── Message callback ─────────────────────────────────────────────────────── */
static enum QcPerfReturnCode on_message(struct QcPerfMessage *msg) {
if (NULL == msg || NULL == msg->message) {
return QC_PERF_RETURN_CODE_FAILED;
}
if (msg->message_level != QC_PERF_MESSAGE_LEVEL_DEBUG) {
fprintf(stderr, "[backend] %s\n", msg->message);
}
return QC_PERF_RETURN_CODE_SUCCESS;
}
/* ── main ─────────────────────────────────────────────────────────────────── */
int main(void) {
enum QcPerfReturnCode rc = QC_PERF_RETURN_CODE_FAILED;
struct QcPerfBackendInfo *info = NULL;
struct QcPerfRequest *req = NULL;
int exit_code = 0;
/* Install signal handlers for clean shutdown. */
struct sigaction sa = {0};
sa.sa_handler = on_signal;
sigemptyset(&sa.sa_mask);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
/* Step 1: Initialise the library. */
rc = qcperf_init();
if (QC_PERF_RETURN_CODE_SUCCESS != rc) {
fprintf(stderr, "qcperf_init failed (%d)\n", (int)rc);
return 1;
}
/* Step 2: Connect to the NPU backend, registering the message callback. */
rc = qcperf_connect_backend(QC_PERF_BACKEND_DSP_NPU, &on_message);
if (QC_PERF_RETURN_CODE_SUCCESS != rc) {
fprintf(stderr, "qcperf_connect_backend failed (%d)\n", (int)rc);
exit_code = 1;
goto deinit;
}
/* Step 3: Query capabilities and deep-copy for use in the callback. */
info = (struct QcPerfBackendInfo *)calloc(1, sizeof(struct QcPerfBackendInfo));
if (NULL == info) {
fprintf(stderr, "calloc failed\n");
exit_code = 1;
goto disconnect;
}
rc = qcperf_get_capabilities_info(QC_PERF_BACKEND_DSP_NPU, info);
if (QC_PERF_RETURN_CODE_SUCCESS != rc) {
fprintf(stderr, "qcperf_get_capabilities_info failed (%d)\n", (int)rc);
exit_code = 1;
goto disconnect;
}
/*
* Deep-copy into g_info so the data callback (called from a background
* thread) can safely look up metric names without touching the stack-local
* `info` pointer.
*/
g_info = (struct QcPerfBackendInfo *)calloc(1, sizeof(struct QcPerfBackendInfo));
if (NULL != g_info) {
g_info->backend_id = info->backend_id;
g_info->capabilities_list_length = info->capabilities_list_length;
g_info->capabilities_list = (struct QcPerfCapabilityInfo *)calloc(
info->capabilities_list_length, sizeof(struct QcPerfCapabilityInfo));
if (NULL != g_info->capabilities_list) {
for (uint8_t c = 0; c < info->capabilities_list_length; c++) {
g_info->capabilities_list[c] = info->capabilities_list[c];
uint8_t mlen = info->capabilities_list[c].metric_ids_list_len;
g_info->capabilities_list[c].metric_ids_list =
(struct QcPerfMetricInfo *)calloc(mlen, sizeof(struct QcPerfMetricInfo));
if (NULL != g_info->capabilities_list[c].metric_ids_list) {
for (uint8_t m = 0; m < mlen; m++) {
g_info->capabilities_list[c].metric_ids_list[m] =
info->capabilities_list[c].metric_ids_list[m];
}
}
}
}
}
/* Step 4: Register the data callback. */
rc = qcperf_set_data_callback(QC_PERF_BACKEND_DSP_NPU, &on_data);
if (QC_PERF_RETURN_CODE_SUCCESS != rc) {
fprintf(stderr, "qcperf_set_data_callback failed (%d)\n", (int)rc);
exit_code = 1;
goto disconnect;
}
/* Step 5: Build the request and start monitoring. */
req = (struct QcPerfRequest *)calloc(1, sizeof(struct QcPerfRequest));
if (NULL == req) {
fprintf(stderr, "calloc failed\n");
exit_code = 1;
goto disconnect;
}
req->capability_id = info->capabilities_list[0].capability_id;
req->sampling_rate = 100; /* poll CDSP every 100 ms */
req->streaming_rate = 1000; /* deliver callback every 1000 ms */
rc = qcperf_start(QC_PERF_BACKEND_DSP_NPU, req);
if (QC_PERF_RETURN_CODE_SUCCESS != rc) {
fprintf(stderr, "qcperf_start failed (%d)\n", (int)rc);
exit_code = 1;
goto disconnect;
}
fprintf(stderr, "Streaming NPU metrics — press Ctrl-C to stop\n");
/* Step 6: Run until signalled. */
while (0 != g_running) {
sleep(1);
}
/* Step 7: Stop monitoring. */
qcperf_stop(QC_PERF_BACKEND_DSP_NPU, req);
free(req);
req = NULL;
disconnect:
/* Step 8: Disconnect the backend. */
free(req);
req = NULL;
qcperf_disconnect_backend(QC_PERF_BACKEND_DSP_NPU);
deinit:
/* Step 9: Deinitialise the library. */
qcperf_deinit();
/* Free caller-owned memory. */
if (NULL != info) {
free(info);
}
if (NULL != g_info) {
if (NULL != g_info->capabilities_list) {
for (uint8_t c = 0; c < g_info->capabilities_list_length; c++) {
free(g_info->capabilities_list[c].metric_ids_list);
}
free(g_info->capabilities_list);
}
free(g_info);
}
return exit_code;
}