diff --git a/openbsc/include/openbsc/Makefile.am b/openbsc/include/openbsc/Makefile.am index 27c88b770..b4760252e 100644 --- a/openbsc/include/openbsc/Makefile.am +++ b/openbsc/include/openbsc/Makefile.am @@ -5,4 +5,4 @@ noinst_HEADERS = abis_nm.h abis_rsl.h debug.h db.h gsm_04_08.h gsm_data.h \ gsm_utils.h ipaccess.h rs232.h openbscdefines.h rtp_proxy.h \ bsc_rll.h mncc.h talloc.h transaction.h ussd.h gsm_04_80.h \ silent_call.h mgcp.h meas_rep.h bitvec.h rest_octets.h \ - system_information.h handover.h + system_information.h handover.h statistics.h diff --git a/openbsc/include/openbsc/db.h b/openbsc/include/openbsc/db.h index 07135937b..fca736493 100644 --- a/openbsc/include/openbsc/db.h +++ b/openbsc/include/openbsc/db.h @@ -53,4 +53,8 @@ int db_sms_mark_sent(struct gsm_sms *sms); int db_apdu_blob_store(struct gsm_subscriber *subscr, u_int8_t apdu_id_flags, u_int8_t len, u_int8_t *apdu); + +/* Statistics counter storage */ +int db_store_counter(struct counter *ctr); + #endif /* _DB_H */ diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h index 74e193832..c2eec4156 100644 --- a/openbsc/include/openbsc/gsm_data.h +++ b/openbsc/include/openbsc/gsm_data.h @@ -61,6 +61,7 @@ enum gsm_chreq_reason_t { #include #include #include +#include #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) @@ -460,43 +461,43 @@ struct gsm_bts { /* Some statistics of our network */ struct gsmnet_stats { struct { - unsigned long total; - unsigned long no_channel; + struct counter *total; + struct counter *no_channel; } chreq; struct { - unsigned long attempted; - unsigned long no_channel; /* no channel available */ - unsigned long timeout; /* T3103 timeout */ - unsigned long completed; /* HO COMPL received */ - unsigned long failed; /* HO FAIL received */ + struct counter *attempted; + struct counter *no_channel; /* no channel available */ + struct counter *timeout; /* T3103 timeout */ + struct counter *completed; /* HO COMPL received */ + struct counter *failed; /* HO FAIL received */ } handover; struct { - unsigned long attach; - unsigned long normal; - unsigned long periodic; - unsigned long detach; + struct counter *attach; + struct counter *normal; + struct counter *periodic; + struct counter *detach; } loc_upd_type; struct { - unsigned long reject; - unsigned long accept; + struct counter *reject; + struct counter *accept; } loc_upd_resp; struct { - unsigned long attempted; - unsigned long detached; - unsigned long completed; - unsigned long expired; + struct counter *attempted; + struct counter *detached; + struct counter *completed; + struct counter *expired; } paging; struct { - unsigned long submitted; /* MO SMS submissions */ - unsigned long no_receiver; - unsigned long delivered; /* MT SMS deliveries */ - unsigned long rp_err_mem; - unsigned long rp_err_other; + struct counter *submitted; /* MO SMS submissions */ + struct counter *no_receiver; + struct counter *delivered; /* MT SMS deliveries */ + struct counter *rp_err_mem; + struct counter *rp_err_other; } sms; struct { - unsigned long dialled; /* total number of dialled calls */ - unsigned long alerted; /* we alerted the other end */ - unsigned long connected;/* how many calls were accepted */ + struct counter *dialled; /* total number of dialled calls */ + struct counter *alerted; /* we alerted the other end */ + struct counter *connected;/* how many calls were accepted */ } call; }; diff --git a/openbsc/include/openbsc/statistics.h b/openbsc/include/openbsc/statistics.h new file mode 100644 index 000000000..9291a8c25 --- /dev/null +++ b/openbsc/include/openbsc/statistics.h @@ -0,0 +1,30 @@ +#ifndef _STATISTICS_H +#define _STATISTICS_H + +struct counter { + struct llist_head list; + const char *name; + const char *description; + unsigned long value; +}; + +static inline void counter_inc(struct counter *ctr) +{ + ctr->value++; +} + +static inline unsigned long counter_get(struct counter *ctr) +{ + return ctr->value; +} + +static inline void counter_reset(struct counter *ctr) +{ + ctr->value = 0; +} + +struct counter *counter_alloc(const char *name); +void counter_free(struct counter *ctr); +int counters_store_db(void); + +#endif /* _STATISTICS_H */ diff --git a/openbsc/src/Makefile.am b/openbsc/src/Makefile.am index 7abec6953..de413d5ed 100644 --- a/openbsc/src/Makefile.am +++ b/openbsc/src/Makefile.am @@ -12,7 +12,7 @@ libbsc_a_SOURCES = abis_rsl.c abis_nm.c gsm_data.c gsm_04_08_utils.c \ trau_frame.c trau_mux.c paging.c e1_config.c e1_input.c tlv_parser.c \ input/misdn.c input/ipaccess.c signal.c gsm_utils.c talloc.c \ talloc_ctx.c system_information.c bitvec.c rest_octets.c \ - rtp_proxy.c + rtp_proxy.c statistics.c libmsc_a_SOURCES = gsm_subscriber.c db.c telnet_interface.c \ mncc.c gsm_04_08.c gsm_04_11.c transaction.c \ diff --git a/openbsc/src/abis_rsl.c b/openbsc/src/abis_rsl.c index ae1d6af5d..787a8038b 100644 --- a/openbsc/src/abis_rsl.c +++ b/openbsc/src/abis_rsl.c @@ -1261,14 +1261,14 @@ static int rsl_rx_chan_rqd(struct msgb *msg) lctype = get_ctype_by_chreq(bts, rqd_ref->ra, bts->network->neci); chreq_reason = get_reason_by_chreq(bts, rqd_ref->ra, bts->network->neci); - bts->network->stats.chreq.total++; + counter_inc(bts->network->stats.chreq.total); /* check availability / allocate channel */ lchan = lchan_alloc(bts, lctype); if (!lchan) { DEBUGP(DRSL, "CHAN RQD: no resources for %s 0x%x\n", gsm_lchan_name(lctype), rqd_ref->ra); - bts->network->stats.chreq.no_channel++; + counter_inc(bts->network->stats.chreq.no_channel); /* FIXME: send some kind of reject ?!? */ return -ENOMEM; } diff --git a/openbsc/src/bs11_config.c b/openbsc/src/bs11_config.c index 3e8bf88a3..2a80a49ad 100644 --- a/openbsc/src/bs11_config.c +++ b/openbsc/src/bs11_config.c @@ -71,6 +71,11 @@ static const char *trx1_password = "1111111111"; static const u_int8_t too_fast[] = { 0x12, 0x80, 0x00, 0x00, 0x02, 0x02 }; +/* dummy function to keep gsm_data.c happy */ +struct counter *counter_alloc(const char *name) +{ + return NULL; +} int handle_serial_msg(struct msgb *rx_msg); diff --git a/openbsc/src/db.c b/openbsc/src/db.c index 707d877bb..916527de6 100644 --- a/openbsc/src/db.c +++ b/openbsc/src/db.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -117,6 +118,12 @@ static char *create_stmts[] = { "subscriber_id INTEGER NOT NULL, " "apdu BLOB " ")", + "CREATE TABLE IF NOT EXISTS Counters (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "timestamp TIMESTAMP NOT NULL, " + "value INTEGER NOT NULL " + "name TEXT NOT NULL, " + ")", }; void db_error_func(dbi_conn conn, void* data) { @@ -902,3 +909,24 @@ int db_apdu_blob_store(struct gsm_subscriber *subscr, dbi_result_free(result); return 0; } + +int db_store_counter(struct counter *ctr) +{ + dbi_result result; + char *q_name; + + dbi_conn_quote_string_copy(conn, ctr->name, &q_name); + + result = dbi_conn_queryf(conn, + "INSERT INTO Counters " + "(timestamp,name,value) VALUES " + "(datetime('now'),%s,%lu)", q_name, ctr->value); + + free(q_name); + + if (!result) + return -EIO; + + dbi_result_free(result); + return 0; +} diff --git a/openbsc/src/gsm_04_08.c b/openbsc/src/gsm_04_08.c index f89d8c5a2..2b22122fb 100644 --- a/openbsc/src/gsm_04_08.c +++ b/openbsc/src/gsm_04_08.c @@ -831,7 +831,7 @@ int gsm0408_loc_upd_rej(struct gsm_lchan *lchan, u_int8_t cause) DEBUGP(DMM, "-> LOCATION UPDATING REJECT on channel: %d\n", lchan->nr); - bts->network->stats.loc_upd_resp.reject++; + counter_inc(bts->network->stats.loc_upd_resp.reject); return gsm48_sendmsg(msg, NULL); } @@ -860,7 +860,7 @@ int gsm0408_loc_upd_acc(struct gsm_lchan *lchan, u_int32_t tmsi) DEBUGP(DMM, "-> LOCATION UPDATE ACCEPT\n"); - bts->network->stats.loc_upd_resp.accept++; + counter_inc(bts->network->stats.loc_upd_resp.accept); return gsm48_sendmsg(msg, NULL); } @@ -982,13 +982,13 @@ static int mm_rx_loc_upd_req(struct msgb *msg) switch (lu->type) { case GSM48_LUPD_NORMAL: - bts->network->stats.loc_upd_type.normal++; + counter_inc(bts->network->stats.loc_upd_type.normal); break; case GSM48_LUPD_IMSI_ATT: - bts->network->stats.loc_upd_type.attach++; + counter_inc(bts->network->stats.loc_upd_type.attach); break; case GSM48_LUPD_PERIODIC: - bts->network->stats.loc_upd_type.periodic++; + counter_inc(bts->network->stats.loc_upd_type.periodic); break; } @@ -1318,7 +1318,7 @@ static int gsm48_rx_mm_imsi_detach_ind(struct msgb *msg) DEBUGP(DMM, "IMSI DETACH INDICATION: mi_type=0x%02x MI(%s): ", mi_type, mi_string); - bts->network->stats.loc_upd_type.detach++; + counter_inc(bts->network->stats.loc_upd_type.detach); switch (mi_type) { case GSM_MI_TYPE_TMSI: diff --git a/openbsc/src/gsm_04_11.c b/openbsc/src/gsm_04_11.c index a5f64f170..d5b011609 100644 --- a/openbsc/src/gsm_04_11.c +++ b/openbsc/src/gsm_04_11.c @@ -517,7 +517,7 @@ static int gsm340_rx_tpdu(struct msgb *msg) u_int8_t address_lv[12]; /* according to 03.40 / 9.1.2.5 */ int rc = 0; - bts->network->stats.sms.submitted++; + counter_inc(bts->network->stats.sms.submitted); gsms = sms_alloc(); if (!gsms) @@ -607,7 +607,7 @@ static int gsm340_rx_tpdu(struct msgb *msg) gsms->receiver = subscr_get_by_extension(bts->network, gsms->dest_addr); if (!gsms->receiver) { rc = 1; /* cause 1: unknown subscriber */ - bts->network->stats.sms.no_receiver++; + counter_inc(bts->network->stats.sms.no_receiver); goto out; } @@ -761,6 +761,7 @@ static int gsm411_rx_rp_ack(struct msgb *msg, struct gsm_trans *trans, static int gsm411_rx_rp_error(struct msgb *msg, struct gsm_trans *trans, struct gsm411_rp_hdr *rph) { + struct gsm_network *net = trans->lchan->ts->trx->bts->network; struct gsm_sms *sms = trans->sms.sms; u_int8_t cause_len = rph->data[0]; u_int8_t cause = rph->data[1]; @@ -794,9 +795,9 @@ static int gsm411_rx_rp_error(struct msgb *msg, struct gsm_trans *trans, * to store this in our database and wati for a SMMA message */ /* FIXME */ dispatch_signal(SS_SMS, S_SMS_MEM_EXCEEDED, trans->subscr); - trans->lchan->ts->trx->bts->network->stats.sms.rp_err_mem++; + counter_inc(net->stats.sms.rp_err_mem); } else - trans->lchan->ts->trx->bts->network->stats.sms.rp_err_other++; + counter_inc(net->stats.sms.rp_err_other); sms_free(sms); trans->sms.sms = NULL; @@ -1073,7 +1074,7 @@ int gsm411_send_sms_lchan(struct gsm_lchan *lchan, struct gsm_sms *sms) DEBUGP(DSMS, "TX: SMS DELIVER\n"); - lchan->ts->trx->bts->network->stats.sms.delivered++; + counter_inc(lchan->ts->trx->bts->network->stats.sms.delivered); return gsm411_rp_sendmsg(msg, trans, GSM411_MT_RP_DATA_MT, msg_ref); /* FIXME: enter 'wait for RP-ACK' state, start TR1N */ diff --git a/openbsc/src/gsm_data.c b/openbsc/src/gsm_data.c index d6ebd9224..e2f56d539 100644 --- a/openbsc/src/gsm_data.c +++ b/openbsc/src/gsm_data.c @@ -28,6 +28,7 @@ #include #include #include +#include void *tall_bsc_ctx; @@ -226,6 +227,32 @@ struct gsm_network *gsm_network_init(u_int16_t country_code, u_int16_t network_c INIT_LLIST_HEAD(&net->upqueue); INIT_LLIST_HEAD(&net->bts_list); + net->stats.chreq.total = counter_alloc("net.chreq.total"); + net->stats.chreq.no_channel = counter_alloc("net.chreq.no_channel"); + net->stats.handover.attempted = counter_alloc("net.handover.attempted"); + net->stats.handover.no_channel = counter_alloc("net.handover.no_channel"); + net->stats.handover.timeout = counter_alloc("net.handover.timeout"); + net->stats.handover.completed = counter_alloc("net.handover.completed"); + net->stats.handover.failed = counter_alloc("net.handover.failed"); + net->stats.loc_upd_type.attach = counter_alloc("net.loc_upd_type.attach"); + net->stats.loc_upd_type.normal = counter_alloc("net.loc_upd_type.normal"); + net->stats.loc_upd_type.periodic = counter_alloc("net.loc_upd_type.periodic"); + net->stats.loc_upd_type.detach = counter_alloc("net.imsi_detach.count"); + net->stats.loc_upd_resp.reject = counter_alloc("net.loc_upd_resp.reject"); + net->stats.loc_upd_resp.accept = counter_alloc("net.loc_upd_resp.accept"); + net->stats.paging.attempted = counter_alloc("net.paging.attempted"); + net->stats.paging.detached = counter_alloc("net.paging.detached"); + net->stats.paging.completed = counter_alloc("net.paging.completed"); + net->stats.paging.expired = counter_alloc("net.paging.expired"); + net->stats.sms.submitted = counter_alloc("net.sms.submitted"); + net->stats.sms.no_receiver = counter_alloc("net.sms.no_receiver"); + net->stats.sms.delivered = counter_alloc("net.sms.delivered"); + net->stats.sms.rp_err_mem = counter_alloc("net.sms.rp_err_mem"); + net->stats.sms.rp_err_other = counter_alloc("net.sms.rp_err_other"); + net->stats.call.dialled = counter_alloc("net.call.dialled"); + net->stats.call.alerted = counter_alloc("net.call.alerted"); + net->stats.call.connected = counter_alloc("net.call.connected"); + net->mncc_recv = mncc_recv; return net; diff --git a/openbsc/src/handover_logic.c b/openbsc/src/handover_logic.c index 393b0f2ad..5297ab6ca 100644 --- a/openbsc/src/handover_logic.c +++ b/openbsc/src/handover_logic.c @@ -97,12 +97,12 @@ int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts) DEBUGP(DHO, "(old_lchan on BTS %u, new BTS %u)\n", old_lchan->ts->trx->bts->nr, bts->nr); - bts->network->stats.handover.attempted++; + counter_inc(bts->network->stats.handover.attempted); new_lchan = lchan_alloc(bts, old_lchan->type); if (!new_lchan) { LOGP(DHO, LOGL_NOTICE, "No free channel\n"); - bts->network->stats.handover.no_channel++; + counter_inc(bts->network->stats.handover.no_channel); return -ENOSPC; } @@ -144,9 +144,10 @@ int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts) static void ho_T3103_cb(void *_ho) { struct bsc_handover *ho = _ho; + struct gsm_network *net = ho->new_lchan->ts->trx->bts->network; DEBUGP(DHO, "HO T3103 expired\n"); - ho->new_lchan->ts->trx->bts->network->stats.handover.timeout++; + counter_inc(net->stats.handover.timeout); lchan_free(ho->new_lchan); llist_del(&ho->list); @@ -207,6 +208,7 @@ static int ho_chan_activ_nack(struct gsm_lchan *new_lchan) /* GSM 04.08 HANDOVER COMPLETE has been received on new channel */ static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan) { + struct gsm_network *net = new_lchan->ts->trx->bts->network; struct bsc_handover *ho; ho = bsc_ho_by_new_lchan(new_lchan); @@ -215,7 +217,7 @@ static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan) return -ENODEV; } - new_lchan->ts->trx->bts->network->stats.handover.completed++; + counter_inc(net->stats.handover.completed); bsc_del_timer(&ho->T3103); @@ -236,6 +238,7 @@ static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan) /* GSM 04.08 HANDOVER FAIL has been received */ static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan) { + struct gsm_network *net = old_lchan->ts->trx->bts->network; struct bsc_handover *ho; ho = bsc_ho_by_old_lchan(old_lchan); @@ -244,7 +247,7 @@ static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan) return -ENODEV; } - old_lchan->ts->trx->bts->network->stats.handover.failed++; + counter_inc(net->stats.handover.failed); bsc_del_timer(&ho->T3103); llist_del(&ho->list); diff --git a/openbsc/src/paging.c b/openbsc/src/paging.c index 538e0a8ec..820773a9e 100644 --- a/openbsc/src/paging.c +++ b/openbsc/src/paging.c @@ -212,7 +212,7 @@ static void paging_T3113_expired(void *data) cbfn = req->cbfn; paging_remove_request(&req->bts->paging, req); - req->bts->network->stats.paging.expired++; + counter_inc(req->bts->network->stats.paging.expired); dispatch_signal(SS_PAGING, S_PAGING_COMPLETED, &sig_data); if (cbfn) @@ -256,7 +256,7 @@ int paging_request(struct gsm_network *network, struct gsm_subscriber *subscr, struct gsm_bts *bts = NULL; int num_pages = 0; - network->stats.paging.attempted++; + counter_inc(network->stats.paging.attempted); /* start paging subscriber on all BTS within Location Area */ do { @@ -274,7 +274,7 @@ int paging_request(struct gsm_network *network, struct gsm_subscriber *subscr, } while (1); if (num_pages == 0) - network->stats.paging.detached++; + counter_inc(network->stats.paging.detached); return num_pages; } diff --git a/openbsc/src/statistics.c b/openbsc/src/statistics.c new file mode 100644 index 000000000..3429d6ec6 --- /dev/null +++ b/openbsc/src/statistics.c @@ -0,0 +1,85 @@ +/* utility routines for keeping some statistics */ + +/* (C) 2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + + +#include + +#include +#include +#include +#include +#include +#include +#include + +static LLIST_HEAD(counters); + +static struct timer_list db_sync_timer; + +#define DB_SYNC_INTERVAL 60, 0 + +struct counter *counter_alloc(const char *name) +{ + struct counter *ctr = talloc_zero(tall_bsc_ctx, struct counter); + + if (!ctr) + return NULL; + + ctr->name = name; + llist_add_tail(&ctr->list, &counters); + + return ctr; +} + +void counter_free(struct counter *ctr) +{ + llist_del(&ctr->list); + talloc_free(ctr); +} + +int counters_store_db(void) +{ + struct counter *ctr; + int rc = 0; + + llist_for_each_entry(ctr, &counters, list) { + rc = db_store_counter(ctr); + if (rc < 0) + return rc; + } + + return rc; +} + +static void db_sync_timer_cb(void *data) +{ + /* store counters to database and re-schedule */ + counters_store_db(); + bsc_schedule_timer(&db_sync_timer, DB_SYNC_INTERVAL); +} + +static __attribute__((constructor)) void on_dso_load_stat(void) +{ + db_sync_timer.cb = db_sync_timer_cb; + db_sync_timer.data = NULL; + bsc_schedule_timer(&db_sync_timer, DB_SYNC_INTERVAL); +} diff --git a/openbsc/src/vty_interface.c b/openbsc/src/vty_interface.c index 808db3fb0..75fe54c1d 100644 --- a/openbsc/src/vty_interface.c +++ b/openbsc/src/vty_interface.c @@ -811,30 +811,35 @@ DEFUN(show_stats, struct gsm_network *net = gsmnet; vty_out(vty, "Channel Requests: %lu total, %lu no channel%s", - net->stats.chreq.total, net->stats.chreq.no_channel, - VTY_NEWLINE); + counter_get(net->stats.chreq.total), + counter_get(net->stats.chreq.no_channel), VTY_NEWLINE); vty_out(vty, "Location Update: %lu attach, %lu normal, %lu periodic%s", - net->stats.loc_upd_type.attach, net->stats.loc_upd_type.normal, - net->stats.loc_upd_type.periodic, VTY_NEWLINE); - vty_out(vty, "IMSI Detach Indications: %lu%s\n", - net->stats.loc_upd_type.detach, VTY_NEWLINE); + counter_get(net->stats.loc_upd_type.attach), + counter_get(net->stats.loc_upd_type.normal), + counter_get(net->stats.loc_upd_type.periodic), VTY_NEWLINE); + vty_out(vty, "IMSI Detach Indications: %lu%s", + counter_get(net->stats.loc_upd_type.detach), VTY_NEWLINE); vty_out(vty, "Location Update Response: %lu accept, %lu reject%s", - net->stats.loc_upd_resp.accept, - net->stats.loc_upd_resp.reject, VTY_NEWLINE); + counter_get(net->stats.loc_upd_resp.accept), + counter_get(net->stats.loc_upd_resp.reject), VTY_NEWLINE); vty_out(vty, "Paging: %lu attempted, %lu complete, %lu expired%s", - net->stats.paging.attempted, net->stats.paging.completed, - net->stats.paging.expired, VTY_NEWLINE); + counter_get(net->stats.paging.attempted), + counter_get(net->stats.paging.completed), + counter_get(net->stats.paging.expired), VTY_NEWLINE); vty_out(vty, "Handover: %lu attempted, %lu no_channel, %lu timeout, " - "%lu completed, %lu failed%s", net->stats.handover.attempted, - net->stats.handover.no_channel, net->stats.handover.timeout, - net->stats.handover.completed, net->stats.handover.failed, - VTY_NEWLINE); + "%lu completed, %lu failed%s", + counter_get(net->stats.handover.attempted), + counter_get(net->stats.handover.no_channel), + counter_get(net->stats.handover.timeout), + counter_get(net->stats.handover.completed), + counter_get(net->stats.handover.failed), VTY_NEWLINE); vty_out(vty, "SMS MO: %lu submitted, %lu no receiver%s", - net->stats.sms.submitted, net->stats.sms.no_receiver, - VTY_NEWLINE); + counter_get(net->stats.sms.submitted), + counter_get(net->stats.sms.no_receiver), VTY_NEWLINE); vty_out(vty, "SMS MT: %lu delivered, %lu no memory, %lu other error%s", - net->stats.sms.delivered, net->stats.sms.rp_err_mem, - net->stats.sms.rp_err_other, VTY_NEWLINE); + counter_get(net->stats.sms.delivered), + counter_get(net->stats.sms.rp_err_mem), + counter_get(net->stats.sms.rp_err_other), VTY_NEWLINE); return CMD_SUCCESS; }