Timur Davydov has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-bts/+/42704?usp=email )
Change subject: trx: add JSON stats export for WebSDR API ......................................................................
trx: add JSON stats export for WebSDR API
Introduce stats_json.c providing JSON serialization for: - stats and counters - rate counters - BTS state - TRX/transceiver state - WebSDR runtime metrics
Add osmobts_get_stats() API to expose these via WebSDR interface and export it in Emscripten build
This enables programmatic access to data previously available via VTY (e.g. "show stats", "show bts")
Change-Id: Ifb82adfab879a65ecd222c45e06551983aa90a0f --- M src/osmo-bts-trx/Makefile.am M src/osmo-bts-trx/osmo-bts-trx-websdr.c A src/osmo-bts-trx/stats_json.c A src/osmo-bts-trx/stats_json.h 4 files changed, 752 insertions(+), 1 deletion(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-bts refs/changes/04/42704/1
diff --git a/src/osmo-bts-trx/Makefile.am b/src/osmo-bts-trx/Makefile.am index 83dd233..0092eee 100644 --- a/src/osmo-bts-trx/Makefile.am +++ b/src/osmo-bts-trx/Makefile.am @@ -103,7 +103,7 @@ -sENVIRONMENT="worker" \ -sLLD_REPORT_UNDEFINED=1 \ -sSINGLE_FILE=0 \ - -sEXPORTED_FUNCTIONS=[_osmobts_init,_osmobts_apply,_osmobts_getiqtx_short_vector,_osmobts_push_rx_short_vector,_ws_osmux_push_raw_data,_ws_ipa_push_raw_data,_on_sched_timer,_malloc,_free] \ + -sEXPORTED_FUNCTIONS=[_osmobts_init,_osmobts_apply,_osmobts_get_stats,_osmobts_getiqtx_short_vector,_osmobts_push_rx_short_vector,_ws_osmux_push_raw_data,_ws_ipa_push_raw_data,_on_sched_timer,_malloc,_free] \ -sEXPORTED_RUNTIME_METHODS=[HEAPF32,HEAPU32,HEAP32,HEAPU16,HEAP16,HEAPU8,HEAP8,ccall,cwrap,stringToAscii,AsciiToString] \ -sINITIAL_MEMORY=256MB \ -sFORCE_FILESYSTEM \ @@ -123,6 +123,7 @@
osmo_bts_trx_websdr_js_SOURCES = \ osmo-bts-trx-websdr.c \ + stats_json.c \ $(NULL)
osmo_bts_trx_websdr_js_LDADD = \ diff --git a/src/osmo-bts-trx/osmo-bts-trx-websdr.c b/src/osmo-bts-trx/osmo-bts-trx-websdr.c index 0bf6bd8..33c4be3 100644 --- a/src/osmo-bts-trx/osmo-bts-trx-websdr.c +++ b/src/osmo-bts-trx/osmo-bts-trx-websdr.c @@ -128,6 +128,25 @@ return 0; }
+int osmobts_get_stats(const char *group, char *buf, unsigned buflen) +{ + if (!strncmp(group, "stats", 5)) { + return stats_to_json(buf, buflen); + } else if (!strncmp(group, "rate-counters", 13)) { + return rate_counters_to_json(buf, buflen); + } else if (!strncmp(group, "bts", 3)) { + return bts_to_json(buf, buflen); + } else if (!strncmp(group, "trx", 3)) { + return trx_to_json(buf, buflen); + } else if (!strncmp(group, "transceiver", 11)) { + return transceiver_to_json(buf, buflen); + } else if (!strncmp(group, "websdr", 6)) { + return websdr_to_json(buf, buflen); + } + + return -EINVAL; +} + int apitrx_cmd_call(const char *command, char *response, size_t response_size) { int res = osmotrxlib_process_command(command, response, response_size); diff --git a/src/osmo-bts-trx/stats_json.c b/src/osmo-bts-trx/stats_json.c new file mode 100644 index 0000000..3452580 --- /dev/null +++ b/src/osmo-bts-trx/stats_json.c @@ -0,0 +1,689 @@ +/* JSON helpers for OsmoBTS: stats, rate-counters, bts and transceiver + * serialization API implementation + * + * Copyright (C) 2026 Timur Davydov dtv.comp@gmail.com + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdarg.h> + +#include "stats_json.h" + +#include <osmo-bts/bts.h> +#include <osmo-bts/abis.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/scheduler.h> +#include "l1_if.h" +#include "trx_if.h" + +#include <osmocom/core/counter.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/stat_item.h> + +/* g_bts is defined elsewhere */ +extern struct gsm_bts *g_bts; + +extern uint64_t sdr_send_ts; +extern uint64_t g_last_ts; +extern unsigned wptr; +extern unsigned rptr; +extern unsigned total_tx_late; +extern unsigned start_fn; + +/* JSON stats context */ +struct stats_json_ctx { + char *buf; + size_t buflen; + size_t off; + bool first; + bool first_group; + bool first_ctr; + bool first_item; + const char *cur_group_desc; +}; + +/* append a single character into ctx->buf */ +static int sj_append_char(struct stats_json_ctx *ctx, char c) +{ + if (ctx->off + 1 >= ctx->buflen) + return -ENOSPC; + + ctx->buf[ctx->off++] = c; + ctx->buf[ctx->off] = '\0'; + + return 0; +} + +/* append a formatted string into ctx->buf */ +static int sj_append_fmt(struct stats_json_ctx *ctx, const char *fmt, ...) +{ + va_list ap; + int rc; + size_t rem; + + if (ctx->off >= ctx->buflen) + return -ENOSPC; + + rem = ctx->buflen - ctx->off; + va_start(ap, fmt); + rc = vsnprintf(ctx->buf + ctx->off, rem, fmt, ap); + va_end(ap); + + if (rc < 0) + return -EIO; + if ((size_t)rc >= rem) + return -ENOSPC; + + ctx->off += rc; + return 0; +} + +/* simple JSON string escaper that appends into ctx->buf */ +static int sj_append_json_str(struct stats_json_ctx *ctx, const char *s) +{ + if (!s) s = ""; + + if (sj_append_char(ctx, '"')) + return -ENOSPC; + for (size_t j = 0; s[j] != '\0'; j++) { + char c = s[j]; + if (c == '"' || c == '\') { + int rc = sj_append_char(ctx, '\'); + if (rc) + return rc; + rc = sj_append_char(ctx, c); + if (rc) + return rc; + } else if ((unsigned char)c < 0x20) { + int rc = sj_append_char(ctx, ' '); + if (rc) + return rc; + } else { + int rc = sj_append_char(ctx, c); + if (rc) + return rc; + } + } + + return sj_append_char(ctx, '"'); +} + +/* handle a single counter */ +static int sj_counters_handler(struct osmo_counter *counter, void *ctxv) +{ + struct stats_json_ctx *ctx = ctxv; + unsigned long val = counter->value; + int rc; + + if (!ctx->first) { + rc = sj_append_char(ctx, ','); + if (rc) + return rc; + } + ctx->first = false; + + rc = sj_append_fmt(ctx, "{"name":"); + if (rc) + return rc; + + rc = sj_append_json_str(ctx, counter->name); + if (rc) + return rc; + + return sj_append_fmt(ctx, ","value":%lu}", val); +} + +/* Rate counters: produce entries with name + current + per_s + * per second doesn't work at the moment because osmo_fd_timer is not implemented + */ +static int sj_rate_ctr_counter_handler(struct rate_ctr_group *g, struct rate_ctr *ctr, + const struct rate_ctr_desc *desc, void *ctxv) +{ + struct stats_json_ctx *ctx = ctxv; + int rc; + + (void)g; + + if (!ctx->first_ctr) { + rc = sj_append_char(ctx, ','); + if (rc) + return rc; + } + ctx->first_ctr = false; + + rc = sj_append_fmt(ctx, "{"name":"); + if (rc) + return rc; + + rc = sj_append_json_str(ctx, desc->name); + if (rc) + return rc; + + return sj_append_fmt(ctx, ","current":%" PRIu64 "}", ctr->current); +#if 0 + /* Per second rate doesn't work at the moment because osmo_fd_timer is not implemented */ + return sj_append_fmt(ctx, ","current":%" PRIu64 ","per_s":%" PRIu64 "}", + ctr->current, ctr->intv[RATE_CTR_INTV_SEC].rate); +#endif +} + +/* Rate counter groups: produce entries with group + counters */ +static int sj_rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *ctxv) +{ + struct stats_json_ctx *ctx = ctxv; + int rc; + + if (!ctx->first_group) { + rc = sj_append_char(ctx, ','); + if (rc) + return rc; + } + ctx->first_group = false; + + rc = sj_append_fmt(ctx, "{"group_description":"); + if (rc) + return rc; + + rc = sj_append_json_str(ctx, ctrg->desc->group_description); + if (rc) + return rc; + + rc = sj_append_fmt(ctx, ","counters":["); + if (rc) + return rc; + + ctx->first_ctr = true; + rc = rate_ctr_for_each_counter(ctrg, sj_rate_ctr_counter_handler, ctx); + if (rc) + return rc; + + return sj_append_fmt(ctx, "]}"); +} + +/* Flat rate-counters: produce entries with group + name + current + per_s + * per second doesn't work at the moment because osmo_fd_timer is not implemented + */ +static int sj_rate_ctr_flat_counter_handler(struct rate_ctr_group *g, struct rate_ctr *ctr, + const struct rate_ctr_desc *desc, void *ctxv) +{ + struct stats_json_ctx *ctx = ctxv; + int rc; + + (void)g; + + if (!ctx->first_ctr) { + rc = sj_append_char(ctx, ','); + if (rc) + return rc; + } + ctx->first_ctr = false; + + rc = sj_append_fmt(ctx, "{"group":"); + if (rc) + return rc; + + rc = sj_append_json_str(ctx, ctx->cur_group_desc); + if (rc) + return rc; + + rc = sj_append_fmt(ctx, ","name":"); + if (rc) + return rc; + + rc = sj_append_json_str(ctx, desc->name); + if (rc) + return rc; + + return sj_append_fmt(ctx, ","current":%" PRIu64 "}", ctr->current); +#if 0 + /* Per second rate doesn't work at the moment because osmo_fd_timer is not implemented */ + return sj_append_fmt(ctx, ","current":%" PRIu64 ","per_s":%" PRIu64 "}", + ctr->current, ctr->intv[RATE_CTR_INTV_SEC].rate); +#endif +} + +/* Rate counter groups: produce entries with group + counters */ +static int sj_rate_ctr_group_flat_handler(struct rate_ctr_group *ctrg, void *ctxv) +{ + struct stats_json_ctx *ctx = ctxv; + int rc; + + ctx->cur_group_desc = ctrg->desc->group_description ? ctrg->desc->group_description : NULL; + rc = rate_ctr_for_each_counter(ctrg, sj_rate_ctr_flat_counter_handler, ctx); + + ctx->cur_group_desc = NULL; + + return rc; +} + +/* TRX / Transceiver timeslot JSON */ +static int sj_transceiver_timeslot(struct gsm_bts_trx *trx, unsigned int tn, struct stats_json_ctx *ctx) +{ + const struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + const struct l1sched_ts *l1ts = ts->priv; + int rc; + + if (!l1ts) + return 0; + + if (!ctx->first_item) { + rc = sj_append_char(ctx, ','); + if (rc) + return rc; + } + ctx->first_item = false; + + rc = sj_append_fmt(ctx, "{"tn":%u,"mf":", tn); + if (rc) + return rc; + + rc = sj_append_json_str(ctx, (const char *)trx_sched_multiframes[l1ts->mf_index].name); + if (rc) + return rc; + + return sj_append_fmt(ctx, ","pending_dl_prims":%u,"interference":%d,"unit":"dBm"}", + (unsigned)llist_count(&l1ts->dl_prims), + l1ts->chan_state[TRXC_IDLE].meas.interf_avg); +} + +/* TRX / Transceiver JSON */ +static int sj_transceiver_handler_ll(struct stats_json_ctx *ctx) +{ + struct gsm_bts_trx *trx; + bool first_trx = true; + int rc; + + if (!g_bts) + return 0; + + llist_for_each_entry(trx, &g_bts->trx_list, list) { + struct phy_instance *pinst = trx_phy_instance(trx); + struct phy_link *plink = pinst->phy_link; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + if (!first_trx) { + rc = sj_append_char(ctx, ','); + if (rc) + return rc; + } + first_trx = false; + + rc = sj_append_fmt(ctx, "{"nr":%u,"source":", trx->nr); + if (rc) + return rc; + + const char *sname = plink->u.osmotrx.trx_clk_iofd + ? osmo_iofd_get_name(plink->u.osmotrx.trx_clk_iofd) : ""; + rc = sj_append_json_str(ctx, sname); + if (rc) + return rc; + + rc = sj_append_fmt(ctx, ","poweron":%s,"phy_link_state":", + trx_if_powered(l1h) ? "true" : "false"); + if (rc) + return rc; + + rc = sj_append_json_str(ctx, phy_link_state_name(phy_link_state_get(plink))); + if (rc) + return rc; + + /* arfcn/tsc/bsic */ + if (l1h->config.arfcn_valid) { + rc = sj_append_fmt(ctx, ","arfcn":%d", l1h->config.arfcn & ~ARFCN_PCS); + } else { + rc = sj_append_fmt(ctx, ","arfcn":null"); + } + if (rc) + return rc; + + if (l1h->config.tsc_valid) { + rc = sj_append_fmt(ctx, ","tsc":%d", l1h->config.tsc); + } else { + rc = sj_append_fmt(ctx, ","tsc":null"); + } + if (rc) + return rc; + + if (l1h->config.bsic_valid) { + rc = sj_append_fmt(ctx, ","bsic":%d", l1h->config.bsic); + } else { + rc = sj_append_fmt(ctx, ","bsic":null"); + } + if (rc) + return rc; + + /* timeslots */ + rc = sj_append_fmt(ctx, ","timeslots":["); + if (rc) + return rc; + + ctx->first_item = true; + for (unsigned int tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + rc = sj_transceiver_timeslot(trx, tn, ctx); + if (rc) + return rc; + } + + rc = sj_append_fmt(ctx, "]}"); + if (rc) + return rc; + } + + return 0; +} + +/* Stat items: produce entries with name + value + unit */ +static int sj_stat_item_handler(struct osmo_stat_item_group *g, struct osmo_stat_item *item, void *ctxv) +{ + struct stats_json_ctx *ctx = ctxv; + const struct osmo_stat_item_desc *desc = osmo_stat_item_get_desc(item); + int32_t value = osmo_stat_item_get_last(item); + int rc; + + (void)g; + + if (!ctx->first_item) { + rc = sj_append_char(ctx, ','); + if (rc) + return rc; + } + ctx->first_item = false; + + rc = sj_append_fmt(ctx, "{"name":"); + if (rc) + return rc; + + rc = sj_append_json_str(ctx, desc->name); + if (rc) + return rc; + + rc = sj_append_fmt(ctx, ","value":%d,"unit":", value); + if (rc) + return rc; + + rc = sj_append_json_str(ctx, desc->unit && desc->unit != OSMO_STAT_ITEM_NO_UNIT ? desc->unit : ""); + if (rc) + return rc; + + return sj_append_char(ctx, '}'); +} + +/* Stat item groups: produce entries with group + items */ +static int sj_stat_item_group_handler(struct osmo_stat_item_group *statg, void *ctxv) +{ + struct stats_json_ctx *ctx = ctxv; + int rc; + + if (!ctx->first_group) { + rc = sj_append_char(ctx, ','); + if (rc) + return rc; + } + ctx->first_group = false; + + rc = sj_append_fmt(ctx, "{"group_description":"); + if (rc) + return rc; + + rc = sj_append_json_str(ctx, statg->desc->group_description); + if (rc) + return rc; + + rc = sj_append_fmt(ctx, ","items":["); + if (rc) + return rc; + + ctx->first_item = true; + rc = osmo_stat_item_for_each_item(statg, sj_stat_item_handler, ctx); + if (rc) + return rc; + + return sj_append_fmt(ctx, "]}"); +} + +/* Build JSON for 'show bts' by enumerating fields of g_bts and related structures */ +int bts_to_json(char *buf, size_t buflen) +{ + struct stats_json_ctx ctx = { + .buf = buf, + .buflen = buflen, + }; + int rc; + + if (!buf || buflen == 0) + return -EINVAL; + + if (g_bts) { + rc = sj_append_fmt(&ctx, + "{"bts_nr":%u,"variant":"%s","band":"%s","cell_identity":%u,"lac":%u" + ","bsic":%u,"num_trx":%u,"description":", + g_bts->nr, + btsvariant2str(g_bts->variant), + gsm_band_name(g_bts->band), + g_bts->cell_identity, + g_bts->location_area_code, + g_bts->bsic, + g_bts->num_trx); + if (rc) + return rc; + + rc = sj_append_json_str(&ctx, g_bts->description); + if (rc) + return rc; + + rc = sj_append_fmt(&ctx, + ","unit_site_id":%u,"unit_bts_id":%u,"oml_connected":%s,"pcu_version":", + g_bts->ip_access.site_id, + g_bts->ip_access.bts_id, + g_bts->oml_link ? "true" : "false"); + if (rc) + return rc; + + rc = sj_append_json_str(&ctx, g_bts->pcu_version); + if (rc) + return rc; + + rc = sj_append_fmt(&ctx, + ","paging_queue_max":%u,"paging_queue_len":%u," + ""agch_queue_max":%u,"agch_queue_len":%u,"agch_dropped":%llu,"agch_merged":%llu" + ","agch_rejected":%llu,"agch_agch_msgs":%llu,"agch_pch_msgs":%llu,"smscb_tgt":%d" + ","smscb_max":%d,"smscb_hyst":%d,"smscb_basic_len":%u,"smscb_ext_len":%u," + ""ph_rts_fn_avg":%d,"ph_rts_fn_min":%d,"ph_rts_fn_max":%d" + ","radio_link_timeout_current":%d,"radio_link_timeout_oml":%d,"c0_power_red_db":%d}", + paging_get_queue_max(g_bts->paging_state), + paging_queue_length(g_bts->paging_state), + g_bts->agch_queue.max_length, + g_bts->agch_queue.length, + (unsigned long long)g_bts->agch_queue.dropped_msgs, + (unsigned long long)g_bts->agch_queue.merged_msgs, + (unsigned long long)g_bts->agch_queue.rejected_msgs, + (unsigned long long)g_bts->agch_queue.agch_msgs, + (unsigned long long)g_bts->agch_queue.pch_msgs, + g_bts->smscb_queue_tgt_len, + g_bts->smscb_queue_max_len, + g_bts->smscb_queue_hyst, + g_bts->smscb_basic.queue_len, + g_bts->smscb_extended.queue_len, + bts_get_avg_fn_advance(g_bts), + g_bts->fn_stats.min, + g_bts->fn_stats.max, + g_bts->radio_link_timeout.current, + g_bts->radio_link_timeout.oml, + g_bts->c0_power_red_db); + if (rc) + return rc; + } + + return (int)ctx.off; +} + +/* Build JSON for 'show stats' by enumerating counters, rate_ctr groups and stat items */ +int stats_to_json(char *buf, size_t buflen) +{ + struct stats_json_ctx ctx; + int rc; + + if (!buf || buflen == 0) + return -EINVAL; + + memset(&ctx, 0, sizeof(ctx)); + ctx.buf = buf; + ctx.buflen = buflen; + ctx.first = true; + ctx.first_group = true; + ctx.first_ctr = true; + ctx.first_item = true; + + rc = sj_append_char(&ctx, '{'); + if (rc) + return rc; + + /* Ungrouped counters */ + rc = sj_append_fmt(&ctx, ""ungrouped_counters":["); + if (rc) + return rc; + + rc = osmo_counters_for_each(sj_counters_handler, &ctx); + if (rc) + return rc; + + rc = sj_append_char(&ctx, ']'); + if (rc) + return rc; + + /* Rate counter groups */ + rc = sj_append_fmt(&ctx, ","rate_ctr_groups":["); + if (rc) + return rc; + + ctx.first_group = true; + rc = rate_ctr_for_each_group(sj_rate_ctr_group_handler, &ctx); + if (rc) + return rc; + + rc = sj_append_char(&ctx, ']'); + if (rc) + return rc; + + /* Stat item groups */ + rc = sj_append_fmt(&ctx, ","stat_item_groups":["); + if (rc) + return rc; + + ctx.first_group = true; + rc = osmo_stat_item_for_each_group(sj_stat_item_group_handler, &ctx); + if (rc) + return rc; + + rc = sj_append_char(&ctx, ']'); + if (rc) + return rc; + + rc = sj_append_char(&ctx, '}'); + if (rc) + return rc; + + return (int)ctx.off; +} + +/* Build JSON for 'show rate-counters' by enumerating rate counter groups and counters */ +int rate_counters_to_json(char *buf, size_t buflen) +{ + struct stats_json_ctx ctx; + int rc; + + if (!buf || buflen == 0) + return -EINVAL; + + memset(&ctx, 0, sizeof(ctx)); + ctx.buf = buf; + ctx.buflen = buflen; + ctx.first_ctr = true; + + rc = sj_append_fmt(&ctx, "{"rate_counters":["); + if (rc) + return rc; + + rc = rate_ctr_for_each_group(sj_rate_ctr_group_flat_handler, &ctx); + if (rc) + return rc; + + rc = sj_append_fmt(&ctx, "]}"); + if (rc) + return rc; + + return (int)ctx.off; +} + +/* Build JSON for 'show transceivers' by enumerating transceivers */ +int transceiver_to_json(char *buf, size_t buflen) +{ + struct stats_json_ctx ctx; + int rc; + + if (!buf || buflen == 0) + return -EINVAL; + + memset(&ctx, 0, sizeof(ctx)); + ctx.buf = buf; + ctx.buflen = buflen; + + rc = sj_append_fmt(&ctx, "{"transceivers":["); + if (rc) + return rc; + + rc = sj_transceiver_handler_ll(&ctx); + if (rc) + return rc; + + rc = sj_append_fmt(&ctx, "]}"); + if (rc) + return rc; + + return (int)ctx.off; +} + +/* Build JSON for WebSDR runtime counters */ +int websdr_to_json(char *buf, size_t buflen) +{ + struct stats_json_ctx ctx; + int rc; + + if (!buf || buflen == 0) + return -EINVAL; + + memset(&ctx, 0, sizeof(ctx)); + ctx.buf = buf; + ctx.buflen = buflen; + + rc = sj_append_fmt(&ctx, + "{"sdr_send_ts":%" PRIu64 ","g_last_ts":%" PRIu64 + ","wptr":%u,"rptr":%u,"total_tx_late":%u,"start_fn":%u}", + sdr_send_ts, g_last_ts, wptr, rptr, total_tx_late, start_fn); + if (rc) + return rc; + + return (int)ctx.off; +} diff --git a/src/osmo-bts-trx/stats_json.h b/src/osmo-bts-trx/stats_json.h new file mode 100644 index 0000000..5e0e701 --- /dev/null +++ b/src/osmo-bts-trx/stats_json.h @@ -0,0 +1,42 @@ +/* JSON helpers for OsmoBTS: stats, rate-counters, bts and transceiver + * serialization API implementation + * + * Copyright (C) 2026 Timur Davydov dtv.comp@gmail.com + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +#ifndef STATS_JSON_H +#define STATS_JSON_H + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +int bts_to_json(char *buf, size_t buflen); +int stats_to_json(char *buf, size_t buflen); +int rate_counters_to_json(char *buf, size_t buflen); +int transceiver_to_json(char *buf, size_t buflen); +int websdr_to_json(char *buf, size_t buflen); + +#ifdef __cplusplus +} +#endif + +#endif /* STATS_JSON_H */