Merge branch 'laforge/meas_vis'

I'm merging this code, as it is proven to be very useful.  The only
reason to keep it out of master was the fact that the UDP data
structures it sends are non-portable, so you can only run it reliably on
localhost or between identical systems (hardware/compiler/os).

As this hasn't been fixed in the past >= 2 years, I am merging the code
now anyway.  We can still introduce a portable protocol by increasing
the protocol  version at a later point.

There are two options:
a) we make 'struct gsm_meas_rep' portable.  This requires an ABI
   change with libosmocore, as it contains struct gsm_meas_rep_unidir :(

b) we introduce a completely separate wire format with corresponding
   encoding and decoding functions.
This commit is contained in:
Harald Welte 2015-01-01 13:23:49 +01:00
commit c83f0276b3
13 changed files with 1172 additions and 4 deletions

View File

@ -84,6 +84,10 @@ AC_HEADER_STDC
AC_CHECK_HEADERS(dahdi/user.h,,AC_MSG_WARN(DAHDI input driver will not be built))
AC_CHECK_HEADERS(dbi/dbd.h,,AC_MSG_ERROR(DBI library is not installed))
found_cdk=yes
AC_CHECK_HEADERS(cdk/cdk.h,,found_cdk=no)
AM_CONDITIONAL(HAVE_LIBCDK, test "$found_cdk" = yes)
dnl Checks for typedefs, structures and compiler characteristics

View File

@ -15,7 +15,7 @@ noinst_HEADERS = abis_nm.h abis_rsl.h db.h gsm_04_08.h gsm_data.h \
bss.h gsm_data_shared.h ipaccess.h mncc_int.h \
arfcn_range_encode.h nat_rewrite_trie.h bsc_nat_callstats.h \
osmux.h mgcp_transcode.h rtp.h gprs_utils.h \
gprs_gb_parse.h smpp.h
gprs_gb_parse.h smpp.h meas_feed.h
openbsc_HEADERS = gsm_04_08.h meas_rep.h bsc_api.h
openbscdir = $(includedir)/openbsc

View File

@ -0,0 +1,29 @@
#ifndef _OPENBSC_MEAS_FEED_H
#define _OPENBSC_MEAS_FEED_H
#include <stdint.h>
#include <openbsc/meas_rep.h>
struct meas_feed_hdr {
uint8_t msg_type;
uint8_t reserved;
uint16_t version;
};
struct meas_feed_meas {
struct meas_feed_hdr hdr;
char imsi[15+1];
char name[31+1];
char scenario[31+1];
struct gsm_meas_rep mr;
};
enum meas_feed_msgtype {
MEAS_FEED_MEAS = 0,
};
#define MEAS_FEED_VERSION 0
#endif

View File

@ -2,6 +2,8 @@ AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) \
$(LIBOSMOABIS_CFLAGS) $(COVERAGE_CFLAGS)
noinst_HEADERS = meas_feed.h
noinst_LIBRARIES = libmsc.a
libmsc_a_SOURCES = auth.c \
@ -17,9 +19,9 @@ libmsc_a_SOURCES = auth.c \
ussd.c \
vty_interface_layer3.c \
transaction.c \
osmo_msc.c ctrl_commands.c
osmo_msc.c ctrl_commands.c meas_feed.c
if BUILD_SMPP
noinst_HEADERS = smpp_smsc.h
noinst_HEADERS += smpp_smsc.h
libmsc_a_SOURCES += smpp_smsc.c smpp_openbsc.c smpp_vty.c smpp_utils.c
endif

View File

@ -0,0 +1,158 @@
/* UDP-Feed of measurement reports */
#include <unistd.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/core/talloc.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/vty.h>
#include <openbsc/meas_rep.h>
#include <openbsc/signal.h>
#include <openbsc/gsm_subscriber.h>
#include <openbsc/meas_feed.h>
#include <openbsc/vty.h>
#include "meas_feed.h"
struct meas_feed_state {
struct osmo_wqueue wqueue;
char scenario[31+1];
char *dst_host;
uint16_t dst_port;
};
static struct meas_feed_state g_mfs;
static int process_meas_rep(struct gsm_meas_rep *mr)
{
struct msgb *msg;
struct meas_feed_meas *mfm;
struct gsm_subscriber *subscr;
/* ignore measurements as long as we don't know who it is */
if (!mr->lchan || !mr->lchan->conn || !mr->lchan->conn->subscr)
return 0;
subscr = mr->lchan->conn->subscr;
msg = msgb_alloc(sizeof(struct meas_feed_meas), "Meas. Feed");
if (!msg)
return 0;
/* fill in the header */
mfm = (struct meas_feed_meas *) msgb_put(msg, sizeof(*mfm));
mfm->hdr.msg_type = MEAS_FEED_MEAS;
mfm->hdr.version = MEAS_FEED_VERSION;
/* fill in MEAS_FEED_MEAS specific header */
strncpy(mfm->imsi, subscr->imsi, sizeof(mfm->imsi)-1);
mfm->imsi[sizeof(mfm->imsi)-1] = '\0';
strncpy(mfm->name, subscr->name, sizeof(mfm->name)-1);
mfm->name[sizeof(mfm->name)-1] = '\0';
strncpy(mfm->scenario, g_mfs.scenario, sizeof(mfm->scenario));
mfm->scenario[sizeof(mfm->scenario)-1] = '\0';
/* copy the entire measurement report */
memcpy(&mfm->mr, mr, sizeof(mfm->mr));
/* and send it to the socket */
osmo_wqueue_enqueue(&g_mfs.wqueue, msg);
return 0;
}
static int meas_feed_sig_cb(unsigned int subsys, unsigned int signal,
void *handler_data, void *signal_data)
{
struct lchan_signal_data *sdata = signal_data;
if (subsys != SS_LCHAN)
return 0;
if (signal == S_LCHAN_MEAS_REP)
process_meas_rep(sdata->mr);
return 0;
}
static int feed_write_cb(struct osmo_fd *ofd, struct msgb *msg)
{
return write(ofd->fd, msgb_data(msg), msgb_length(msg));
}
static int feed_read_cb(struct osmo_fd *ofd)
{
int rc;
char buf[256];
rc = read(ofd->fd, buf, sizeof(buf));
ofd->fd &= ~BSC_FD_READ;
return rc;
}
int meas_feed_cfg_set(const char *dst_host, uint16_t dst_port)
{
int rc;
int already_initialized = 0;
if (g_mfs.wqueue.bfd.fd)
already_initialized = 1;
if (already_initialized &&
!strcmp(dst_host, g_mfs.dst_host) &&
dst_port == g_mfs.dst_port)
return 0;
if (!already_initialized) {
osmo_wqueue_init(&g_mfs.wqueue, 10);
g_mfs.wqueue.write_cb = feed_write_cb;
g_mfs.wqueue.read_cb = feed_read_cb;
osmo_signal_register_handler(SS_LCHAN, meas_feed_sig_cb, NULL);
}
if (already_initialized) {
osmo_wqueue_clear(&g_mfs.wqueue);
osmo_fd_unregister(&g_mfs.wqueue.bfd);
close(g_mfs.wqueue.bfd.fd);
/* don't set to zero, as that would mean 'not yet initialized' */
g_mfs.wqueue.bfd.fd = -1;
}
rc = osmo_sock_init_ofd(&g_mfs.wqueue.bfd, AF_UNSPEC, SOCK_DGRAM,
IPPROTO_UDP, dst_host, dst_port,
OSMO_SOCK_F_CONNECT);
if (rc < 0)
return rc;
g_mfs.wqueue.bfd.when &= ~BSC_FD_READ;
if (g_mfs.dst_host)
talloc_free(g_mfs.dst_host);
g_mfs.dst_host = talloc_strdup(NULL, dst_host);
g_mfs.dst_port = dst_port;
return 0;
}
void meas_feed_cfg_get(char **host, uint16_t *port)
{
*port = g_mfs.dst_port;
*host = g_mfs.dst_host;
}
void meas_feed_scenario_set(const char *name)
{
strncpy(g_mfs.scenario, name, sizeof(g_mfs.scenario)-1);
g_mfs.scenario[sizeof(g_mfs.scenario)-1] = '\0';
}
const char *meas_feed_scenario_get(void)
{
return g_mfs.scenario;
}

View File

@ -0,0 +1,12 @@
#ifndef _INT_MEAS_FEED_H
#define _INT_MEAS_FEED_H
#include <stdint.h>
int meas_feed_cfg_set(const char *dst_host, uint16_t dst_port);
void meas_feed_cfg_get(char **host, uint16_t *port);
void meas_feed_scenario_set(const char *name);
const char *meas_feed_scenario_get(void);
#endif /* _INT_MEAS_FEED_H */

View File

@ -49,6 +49,8 @@
#include <openbsc/mncc_int.h>
#include <openbsc/handover.h>
#include "meas_feed.h"
extern struct gsm_network *gsmnet_from_vty(struct vty *v);
static void subscr_dump_full_vty(struct vty *vty, struct gsm_subscriber *subscr, int pending)
@ -947,6 +949,11 @@ static const struct value_string tchh_codec_names[] = {
static int config_write_mncc_int(struct vty *vty)
{
uint16_t meas_port;
char *meas_host;
meas_feed_cfg_get(&meas_host, &meas_port);
vty_out(vty, "mncc-int%s", VTY_NEWLINE);
vty_out(vty, " default-codec tch-f %s%s",
get_value_string(tchf_codec_names, mncc_int.def_codec[0]),
@ -954,6 +961,10 @@ static int config_write_mncc_int(struct vty *vty)
vty_out(vty, " default-codec tch-h %s%s",
get_value_string(tchh_codec_names, mncc_int.def_codec[1]),
VTY_NEWLINE);
if (meas_port)
vty_out(vty, " meas-feed destination %s %u%s",
meas_host, meas_port, VTY_NEWLINE);
return CMD_SUCCESS;
}
@ -994,6 +1005,28 @@ DEFUN_DEPRECATED(log_level_sms, log_level_sms_cmd,
return CMD_SUCCESS;
}
DEFUN(mnccint_meas_feed, mnccint_meas_feed_cmd,
"meas-feed destination ADDR <0-65535>",
"FIXME")
{
int rc;
rc = meas_feed_cfg_set(argv[0], atoi(argv[1]));
if (rc < 0)
return CMD_WARNING;
return CMD_SUCCESS;
}
DEFUN(meas_feed_scenario, meas_feed_scenario_cmd,
"meas-feed scenario NAME",
"FIXME")
{
meas_feed_scenario_set(argv[0]);
return CMD_SUCCESS;
}
int bsc_vty_init_extra(void)
{
osmo_signal_register_handler(SS_SCALL, scall_cbfn, NULL);
@ -1028,12 +1061,14 @@ int bsc_vty_init_extra(void)
install_element(ENABLE_NODE, &smsqueue_clear_cmd);
install_element(ENABLE_NODE, &smsqueue_fail_cmd);
install_element(ENABLE_NODE, &subscriber_send_pending_sms_cmd);
install_element(ENABLE_NODE, &meas_feed_scenario_cmd);
install_element(CONFIG_NODE, &cfg_mncc_int_cmd);
install_node(&mncc_int_node, config_write_mncc_int);
vty_install_default(MNCC_INT_NODE);
install_element(MNCC_INT_NODE, &mnccint_def_codec_f_cmd);
install_element(MNCC_INT_NODE, &mnccint_def_codec_h_cmd);
install_element(MNCC_INT_NODE, &mnccint_meas_feed_cmd);
install_element(CFG_LOG_NODE, &log_level_sms_cmd);

View File

@ -2,7 +2,13 @@ AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(COVERAGE_CFLAGS)
AM_LDFLAGS = $(COVERAGE_LDFLAGS)
bin_PROGRAMS = bs11_config isdnsync
noinst_HEADERS = meas_db.h
if HAVE_LIBCDK
bin_PROGRAMS = bs11_config isdnsync osmo-meas-pcap2db osmo-meas-udp2db meas_vis
else
bin_PROGRAMS = bs11_config isdnsync osmo-meas-pcap2db osmo-meas-udp2db
endif
if BUILD_SMPP
noinst_PROGRAMS = smpp_mirror
@ -19,3 +25,15 @@ isdnsync_SOURCES = isdnsync.c
smpp_mirror_SOURCES = smpp_mirror.c
smpp_mirror_LDADD = $(top_builddir)/src/libcommon/libcommon.a \
$(LIBOSMOCORE_LIBS) $(LIBSMPP34_LIBS)
meas_vis_SOURCES = meas_vis.c
meas_vis_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) -lcdk -lncurses
meas_vis_CFLAGS = $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS)
osmo_meas_pcap2db_SOURCES = meas_pcap2db.c meas_db.c
osmo_meas_pcap2db_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) -lpcap -lsqlite3
osmo_meas_pcap2db_CFLAGS = $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS)
osmo_meas_udp2db_SOURCES = meas_udp2db.c meas_db.c
osmo_meas_udp2db_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) -lsqlite3
osmo_meas_udp2db_CFLAGS = $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS)

323
openbsc/src/utils/meas_db.c Normal file
View File

@ -0,0 +1,323 @@
/* Routines for storing measurement reports in SQLite3 database */
/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
*
* 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 <stdint.h>
#include <errno.h>
#include <string.h>
#include <sqlite3.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <openbsc/meas_rep.h>
#include "meas_db.h"
#define INS_MR "INSERT INTO meas_rep (time, imsi, name, scenario, nr, bs_power, ms_timing_offset, fpc, ms_l1_pwr, ms_l1_ta) VALUES (?,?,?,?,?,?,?,?,?,?)"
#define INS_UD "INSERT INTO meas_rep_unidir (meas_id, rx_lev_full, rx_lev_sub, rx_qual_full, rx_qual_sub, dtx, uplink) VALUES (?,?,?,?,?,?,?)"
#define UPD_MR "UPDATE meas_rep SET ul_unidir=?, dl_unidir=? WHERE id=?"
struct meas_db_state {
sqlite3 *db;
sqlite3_stmt *stmt_ins_ud;
sqlite3_stmt *stmt_ins_mr;
sqlite3_stmt *stmt_upd_mr;
};
/* macros to check for SQLite3 result codes */
#define _SCK_OK(db, call, exp) \
do { \
int rc = call; \
if (rc != exp) { \
fprintf(stderr,"SQL Error in line %u: %s\n", \
__LINE__, sqlite3_errmsg(db)); \
goto err_io; \
} \
} while (0)
#define SCK_OK(db, call) _SCK_OK(db, call, SQLITE_OK)
#define SCK_DONE(db, call) _SCK_OK(db, call, SQLITE_DONE)
static int _insert_ud(struct meas_db_state *st, unsigned long meas_id, int dtx,
int uplink, const struct gsm_meas_rep_unidir *ud)
{
unsigned long rowid;
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 1, meas_id));
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 2,
rxlev2dbm(ud->full.rx_lev)));
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 3,
rxlev2dbm(ud->sub.rx_lev)));
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 4, ud->full.rx_qual));
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 5, ud->sub.rx_qual));
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 6, dtx));
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 7, uplink));
SCK_DONE(st->db, sqlite3_step(st->stmt_ins_ud));
SCK_OK(st->db, sqlite3_reset(st->stmt_ins_ud));
return sqlite3_last_insert_rowid(st->db);
err_io:
exit(1);
}
/* insert a measurement report into the database */
int meas_db_insert(struct meas_db_state *st, const char *imsi,
const char *name, unsigned long timestamp,
const char *scenario,
const struct gsm_meas_rep *mr)
{
int rc;
sqlite3_int64 rowid, ul_rowid, dl_rowid;
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 1, timestamp));
if (imsi)
SCK_OK(st->db, sqlite3_bind_text(st->stmt_ins_mr, 2,
imsi, -1, SQLITE_STATIC));
else
SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 2));
if (name)
SCK_OK(st->db, sqlite3_bind_text(st->stmt_ins_mr, 3,
name, -1, SQLITE_STATIC));
else
SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 3));
if (scenario)
SCK_OK(st->db, sqlite3_bind_text(st->stmt_ins_mr, 4,
scenario, -1, SQLITE_STATIC));
else
SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 4));
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 5, mr->nr));
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 6, mr->bs_power));
if (mr->flags & MEAS_REP_F_MS_TO)
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 7,
mr->ms_timing_offset));
else
SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 7));
if (mr->flags & MEAS_REP_F_FPC)
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 8, 1));
else
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 8, 0));
if (mr->flags & MEAS_REP_F_MS_L1) {
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 9,
mr->ms_l1.pwr));
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 10,
mr->ms_l1.ta));
}
SCK_DONE(st->db, sqlite3_step(st->stmt_ins_mr));
SCK_OK(st->db, sqlite3_reset(st->stmt_ins_mr));
rowid = sqlite3_last_insert_rowid(st->db);
/* insert uplink measurement */
ul_rowid = _insert_ud(st, rowid, mr->flags & MEAS_REP_F_UL_DTX,
1, &mr->ul);
SCK_OK(st->db, sqlite3_bind_int(st->stmt_upd_mr, 1, ul_rowid));
/* insert downlink measurement, if present */
if (mr->flags & MEAS_REP_F_DL_VALID) {
dl_rowid = _insert_ud(st, rowid, mr->flags & MEAS_REP_F_DL_DTX,
0, &mr->dl);
SCK_OK(st->db, sqlite3_bind_int(st->stmt_upd_mr, 2, dl_rowid));
} else
SCK_OK(st->db, sqlite3_bind_null(st->stmt_upd_mr, 2));
/* update meas_rep with the id's of the unidirectional
* measurements */
SCK_OK(st->db, sqlite3_bind_int(st->stmt_upd_mr, 3, rowid));
SCK_DONE(st->db, sqlite3_step(st->stmt_upd_mr));
SCK_OK(st->db, sqlite3_reset(st->stmt_upd_mr));
return 0;
err_io:
return -EIO;
}
int meas_db_begin(struct meas_db_state *st)
{
SCK_OK(st->db, sqlite3_exec(st->db, "BEGIN", NULL, NULL, NULL));
return 0;
err_io:
return -EIO;
}
int meas_db_commit(struct meas_db_state *st)
{
SCK_OK(st->db, sqlite3_exec(st->db, "COMMIT", NULL, NULL, NULL));
return 0;
err_io:
return -EIO;
}
static const char *create_stmts[] = {
"CREATE TABLE IF NOT EXISTS meas_rep ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"time TIMESTAMP,"
"imsi TEXT,"
"name TEXT,"
"scenario TEXT,"
"nr INTEGER,"
"bs_power INTEGER NOT NULL,"
"ms_timing_offset INTEGER,"
"fpc INTEGER NOT NULL DEFAULT 0,"
"ul_unidir INTEGER REFERENCES meas_rep_unidir(id),"
"dl_unidir INTEGER REFERENCES meas_rep_unidir(id),"
"ms_l1_pwr INTEGER,"
"ms_l1_ta INTEGER"
")",
"CREATE TABLE IF NOT EXISTS meas_rep_unidir ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"meas_id INTEGER NOT NULL REFERENCES meas_rep(id),"
"rx_lev_full INTEGER NOT NULL,"
"rx_lev_sub INTEGER NOT NULL,"
"rx_qual_full INTEGER NOT NULL,"
"rx_qual_sub INTEGER NOT NULL,"
"dtx BOOLEAN NOT NULL DEFAULT 0,"
"uplink BOOLEAN NOT NULL"
")",
"CREATE VIEW IF NOT EXISTS path_loss AS "
"SELECT "
"meas_rep.id, "
"datetime(time,'unixepoch') AS timestamp, "
"imsi, "
"name, "
"scenario, "
"ms_timing_offset, "
"ms_l1_ta, "
"fpc, "
"ms_l1_pwr, "
"ud_ul.rx_lev_full AS ul_rx_lev_full, "
"ms_l1_pwr-ud_ul.rx_lev_full AS ul_path_loss_full, "
"ud_ul.rx_lev_sub ul_rx_lev_sub, "
"ms_l1_pwr-ud_ul.rx_lev_sub AS ul_path_loss_sub, "
"ud_ul.rx_qual_full AS ul_rx_qual_full, "
"ud_ul.rx_qual_sub AS ul_rx_qual_sub, "
"bs_power, "
"ud_dl.rx_lev_full AS dl_rx_lev_full, "
"bs_power-ud_dl.rx_lev_full AS dl_path_loss_full, "
"ud_dl.rx_lev_sub AS dl_rx_lev_sub, "
"bs_power-ud_dl.rx_lev_sub AS dl_path_loss_sub, "
"ud_dl.rx_qual_full AS dl_rx_qual_full, "
"ud_dl.rx_qual_sub AS dl_rx_qual_sub "
"FROM "
"meas_rep, "
"meas_rep_unidir AS ud_dl, "
"meas_rep_unidir AS ud_ul "
"WHERE "
"ud_ul.id = meas_rep.ul_unidir AND "
"ud_dl.id = meas_rep.dl_unidir",
"CREATE VIEW IF NOT EXISTS overview AS "
"SELECT "
"id,"
"timestamp,"
"imsi,"
"name,"
"scenario,"
"ms_l1_pwr,"
"ul_rx_lev_full,"
"ul_path_loss_full,"
"ul_rx_qual_full,"
"bs_power,"
"dl_rx_lev_full,"
"dl_path_loss_full,"
"dl_rx_qual_full "
"FROM path_loss",
};
static int check_create_tbl(struct meas_db_state *st)
{
int i, rc;
for (i = 0; i < ARRAY_SIZE(create_stmts); i++) {
SCK_OK(st->db, sqlite3_exec(st->db, create_stmts[i],
NULL, NULL, NULL));
}
return 0;
err_io:
return -EIO;
}
#define PREP_CHK(db, stmt, ptr) \
do { \
int rc; \
rc = sqlite3_prepare_v2(db, stmt, strlen(stmt)+1, \
ptr, NULL); \
if (rc != SQLITE_OK) { \
fprintf(stderr, "Error during prepare of '%s': %s\n", \
stmt, sqlite3_errmsg(db)); \
goto err_io; \
} \
} while (0)
struct meas_db_state *meas_db_open(void *ctx, const char *fname)
{
int rc;
struct meas_db_state *st = talloc_zero(ctx, struct meas_db_state);
if (!st)
return NULL;
rc = sqlite3_open_v2(fname, &st->db,
SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE,
NULL);
if (rc != SQLITE_OK) {
fprintf(stderr, "Unable to open DB: %s\n",
sqlite3_errmsg(st->db));
goto err_io;
}
rc = check_create_tbl(st);
PREP_CHK(st->db, INS_MR, &st->stmt_ins_mr);
PREP_CHK(st->db, INS_UD, &st->stmt_ins_ud);
PREP_CHK(st->db, UPD_MR, &st->stmt_upd_mr);
return st;
err_io:
talloc_free(st);
return NULL;
}
void meas_db_close(struct meas_db_state *st)
{
sqlite3_finalize(st->stmt_ins_mr);
sqlite3_finalize(st->stmt_ins_ud);
sqlite3_finalize(st->stmt_upd_mr);
sqlite3_close_v2(st->db);
talloc_free(st);
}

View File

@ -0,0 +1,17 @@
#ifndef OPENBSC_MEAS_DB_H
#define OPENBSC_MEAS_DB_H
struct meas_db_state;
struct meas_db_state *meas_db_open(void *ctx, const char *fname);
void meas_db_close(struct meas_db_state *st);
int meas_db_begin(struct meas_db_state *st);
int meas_db_commit(struct meas_db_state *st);
int meas_db_insert(struct meas_db_state *st, const char *imsi,
const char *name, unsigned long timestamp,
const char *scenario,
const struct gsm_meas_rep *mr);
#endif

View File

@ -0,0 +1,141 @@
/* read PCAP file with meas_feed data and write it to sqlite3 database */
/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
*
* 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 <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <cdk/cdk.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/select.h>
#include <osmocom/core/talloc.h>
#include <osmocom/gsm/gsm_utils.h>
#include <openbsc/meas_feed.h>
#include <pcap/pcap.h>
#include "meas_db.h"
static struct meas_db_state *db;
static void handle_mfm(const struct pcap_pkthdr *h,
const struct meas_feed_meas *mfm)
{
const char *scenario;
if (strlen(mfm->scenario))
scenario = mfm->scenario;
else
scenario = NULL;
meas_db_insert(db, mfm->imsi, mfm->name, h->ts.tv_sec,
scenario, &mfm->mr);
}
static void pcap_cb(u_char *user, const struct pcap_pkthdr *h,
const u_char *bytes)
{
const char *cur = bytes;
const struct iphdr *ip;
const struct udphdr *udp;
const struct meas_feed_meas *mfm;
uint16_t udplen;
if (h->caplen < 14+20+8)
return;
/* Check if there is IPv4 in the Ethernet */
if (cur[12] != 0x08 || cur[13] != 0x00)
return;
cur += 14; /* ethernet header */
ip = (struct iphdr *) cur;
if (ip->version != 4)
return;
cur += ip->ihl * 4;
if (ip->protocol != IPPROTO_UDP)
return;
udp = (struct udphdr *) cur;
if (udp->dest != htons(8888))
return;
udplen = ntohs(udp->len);
if (udplen != sizeof(*udp) + sizeof(*mfm))
return;
cur += sizeof(*udp);
mfm = (const struct meas_feed_meas *) cur;
handle_mfm(h, mfm);
}
int main(int argc, char **argv)
{
char errbuf[PCAP_ERRBUF_SIZE+1];
char *pcap_fname, *db_fname;
pcap_t *pc;
int rc;
if (argc < 3) {
fprintf(stderr, "You need to specify PCAP and database file\n");
exit(2);
}
pcap_fname = argv[1];
db_fname = argv[2];
pc = pcap_open_offline(pcap_fname, errbuf);
if (!pc) {
fprintf(stderr, "Cannot open %s: %s\n", pcap_fname, errbuf);
exit(1);
}
db = meas_db_open(NULL, db_fname);
if (!db)
exit(0);
rc = meas_db_begin(db);
if (rc < 0) {
fprintf(stderr, "Error during BEGIN\n");
exit(1);
}
pcap_loop(pc, 0 , pcap_cb, NULL);
meas_db_commit(db);
exit(0);
}

View File

@ -0,0 +1,123 @@
/* liesten to meas_feed on UDP and write it to sqlite3 database */
/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
*
* 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 <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <netinet/in.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/select.h>
#include <osmocom/core/talloc.h>
#include <osmocom/gsm/gsm_utils.h>
#include <openbsc/meas_feed.h>
#include "meas_db.h"
static struct osmo_fd udp_ofd;
static struct meas_db_state *db;
static int handle_msg(struct msgb *msg)
{
struct meas_feed_hdr *mfh = (struct meas_feed_hdr *) msgb_data(msg);
struct meas_feed_meas *mfm = (struct meas_feed_meas *) msgb_data(msg);
const char *scenario;
time_t now = time(NULL);
if (mfh->version != MEAS_FEED_VERSION)
return -EINVAL;
if (mfh->msg_type != MEAS_FEED_MEAS)
return -EINVAL;
if (strlen(mfm->scenario))
scenario = mfm->scenario;
else
scenario = NULL;
meas_db_insert(db, mfm->imsi, mfm->name, now,
scenario, &mfm->mr);
return 0;
}
static int udp_fd_cb(struct osmo_fd *ofd, unsigned int what)
{
int rc;
if (what & BSC_FD_READ) {
struct msgb *msg = msgb_alloc(1024, "UDP Rx");
rc = read(ofd->fd, msgb_data(msg), msgb_tailroom(msg));
if (rc < 0)
return rc;
msgb_put(msg, rc);
handle_msg(msg);
msgb_free(msg);
}
return 0;
}
int main(int argc, char **argv)
{
char *db_fname;
int rc;
if (argc < 2) {
fprintf(stderr, "You have to specify the database file name\n");
exit(2);
}
db_fname = argv[1];
udp_ofd.cb = udp_fd_cb;
rc = osmo_sock_init_ofd(&udp_ofd, AF_INET, SOCK_DGRAM,
IPPROTO_UDP, NULL, 8888, OSMO_SOCK_F_BIND);
if (rc < 0) {
fprintf(stderr, "Unable to create UDP listen socket\n");
exit(1);
}
db = meas_db_open(NULL, db_fname);
if (!db) {
fprintf(stderr, "Unable to open database\n");
exit(1);
}
/* FIXME: timer-based BEGIN/COMMIT */
while (1) {
osmo_select_main(0);
};
meas_db_close(db);
exit(0);
}

View File

@ -0,0 +1,306 @@
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <netinet/in.h>
#include <cdk/cdk.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/select.h>
#include <osmocom/core/talloc.h>
#include <osmocom/gsm/gsm_utils.h>
#include <openbsc/meas_feed.h>
struct ms_state_uni {
CDKSLIDER *cdk;
CDKLABEL *cdk_label;
time_t last_update;
char label[32];
char *_lbl[1];
};
struct ms_state {
struct llist_head list;
char name[31+1];
char imsi[15+1];
struct gsm_meas_rep mr;
struct ms_state_uni ul;
struct ms_state_uni dl;
};
struct state {
struct osmo_fd udp_ofd;
struct llist_head ms_list;
CDKSCREEN *cdkscreen;
WINDOW *curses_win;
CDKLABEL *cdk_title;
char *title;
CDKLABEL *cdk_header;
char header[256];
};
static struct state g_st;
struct ms_state *find_ms(const char *imsi)
{
struct ms_state *ms;
llist_for_each_entry(ms, &g_st.ms_list, list) {
if (!strcmp(ms->imsi, imsi))
return ms;
}
return NULL;
}
static struct ms_state *find_alloc_ms(const char *imsi)
{
struct ms_state *ms;
ms = find_ms(imsi);
if (!ms) {
ms = talloc_zero(NULL, struct ms_state);
strncpy(ms->imsi, imsi, sizeof(ms->imsi)-1);
ms->ul._lbl[0] = ms->ul.label;
ms->dl._lbl[0] = ms->dl.label;
llist_add_tail(&ms->list, &g_st.ms_list);
}
return ms;
}
static int handle_meas(struct msgb *msg)
{
struct meas_feed_meas *mfm = (struct meas_feed_meas *) msgb_data(msg);
struct ms_state *ms = find_alloc_ms(mfm->imsi);
time_t now = time(NULL);
strncpy(ms->name, mfm->name, sizeof(ms->imsi)-1);
memcpy(&ms->mr, &mfm->mr, sizeof(ms->mr));
ms->ul.last_update = now;
if (ms->mr.flags & MEAS_REP_F_DL_VALID)
ms->dl.last_update = now;
/* move to head of list */
llist_del(&ms->list);
llist_add(&ms->list, &g_st.ms_list);
return 0;
}
static int handle_msg(struct msgb *msg)
{
struct meas_feed_hdr *mfh = (struct meas_feed_hdr *) msgb_data(msg);
if (mfh->version != MEAS_FEED_VERSION)
return -EINVAL;
switch (mfh->msg_type) {
case MEAS_FEED_MEAS:
handle_meas(msg);
break;
default:
break;
}
}
static int udp_fd_cb(struct osmo_fd *ofd, unsigned int what)
{
int rc;
if (what & BSC_FD_READ) {
struct msgb *msg = msgb_alloc(1024, "UDP Rx");
rc = read(ofd->fd, msgb_data(msg), msgb_tailroom(msg));
if (rc < 0)
return rc;
msgb_put(msg, rc);
handle_msg(msg);
msgb_free(msg);
}
return 0;
}
static void destroy_dir(struct ms_state_uni *uni)
{
if (uni->cdk) {
destroyCDKSlider(uni->cdk);
uni->cdk = NULL;
}
if (uni->cdk_label) {
destroyCDKLabel(uni->cdk_label);
uni->cdk_label = NULL;
}
}
#define DIR_UL 0
#define DIR_DL 1
static const char *dir_str[2] = {
[DIR_UL] = "UL",
[DIR_DL] = "DL",
};
static int colpair_by_qual(uint8_t rx_qual)
{
if (rx_qual == 0)
return 24;
else if (rx_qual <= 4)
return 32;
else
return 16;
}
static int colpair_by_lev(int rx_lev)
{
if (rx_lev < -95)
return 16;
else if (rx_lev < -80)
return 32;
else
return 24;
}
void write_uni(struct ms_state *ms, struct ms_state_uni *msu,
struct gsm_rx_lev_qual *lq, int dir, int row)
{
char label[128];
time_t now = time(NULL);
int qual_col = colpair_by_qual(lq->rx_qual);
int lev_col = colpair_by_lev(rxlev2dbm(lq->rx_lev));
int color, pwr;
if (dir == DIR_UL) {
pwr = ms->mr.ms_l1.pwr;
} else {
pwr = ms->mr.bs_power;
}
color = A_REVERSE | COLOR_PAIR(lev_col) | ' ';
snprintf(label, sizeof(label), "%s %s ", ms->imsi, dir_str[dir]);
msu->cdk = newCDKSlider(g_st.cdkscreen, 0, row, NULL, label, color,
COLS-40, rxlev2dbm(lq->rx_lev), -110, -47,
1, 2, FALSE, FALSE);
//IsVisibleObj(ms->ul.cdk) = FALSE;
snprintf(msu->label, sizeof(msu->label), "</%d>%1d<!%d> %3d %2u %2u %4u",
qual_col, lq->rx_qual, qual_col, pwr,
ms->mr.ms_l1.ta, ms->mr.ms_timing_offset,
now - msu->last_update);
msu->cdk_label = newCDKLabel(g_st.cdkscreen, RIGHT, row,
msu->_lbl, 1, FALSE, FALSE);
}
static void update_sliders(void)
{
int num_vis_sliders = 0;
struct ms_state *ms;
#define HEADER_LINES 2
/* remove all sliders */
llist_for_each_entry(ms, &g_st.ms_list, list) {
destroy_dir(&ms->ul);
destroy_dir(&ms->dl);
}
llist_for_each_entry(ms, &g_st.ms_list, list) {
struct gsm_rx_lev_qual *lq;
unsigned int row = HEADER_LINES + num_vis_sliders*3;
if (ms->mr.flags & MEAS_REP_F_UL_DTX)
lq = &ms->mr.ul.sub;
else
lq = &ms->mr.ul.full;
write_uni(ms, &ms->ul, lq, DIR_UL, row);
if (ms->mr.flags & MEAS_REP_F_DL_DTX)
lq = &ms->mr.dl.sub;
else
lq = &ms->mr.dl.full;
write_uni(ms, &ms->dl, lq, DIR_DL, row+1);
num_vis_sliders++;
if (num_vis_sliders >= LINES/3)
break;
}
refreshCDKScreen(g_st.cdkscreen);
}
const struct value_string col_strs[] = {
{ COLOR_WHITE, "white" },
{ COLOR_RED, "red" },
{ COLOR_GREEN, "green" },
{ COLOR_YELLOW, "yellow" },
{ COLOR_BLUE, "blue" },
{ COLOR_MAGENTA,"magenta" },
{ COLOR_CYAN, "cyan" },
{ COLOR_BLACK, "black" },
{ 0, NULL }
};
int main(int argc, char **argv)
{
int rc;
char *header[1];
char *title[1];
printf("sizeof(gsm_meas_rep)=%u\n", sizeof(struct gsm_meas_rep));
printf("sizeof(meas_feed_meas)=%u\n", sizeof(struct meas_feed_meas));
INIT_LLIST_HEAD(&g_st.ms_list);
g_st.curses_win = initscr();
g_st.cdkscreen = initCDKScreen(g_st.curses_win);
initCDKColor();
g_st.title = "OpenBSC link quality monitor";
title[0] = g_st.title;
g_st.cdk_title = newCDKLabel(g_st.cdkscreen, CENTER, 0, title, 1, FALSE, FALSE);
snprintf(g_st.header, sizeof(g_st.header), "Q Pwr TA TO Time");
header[0] = g_st.header;
g_st.cdk_header = newCDKLabel(g_st.cdkscreen, RIGHT, 1, header, 1, FALSE, FALSE);
#if 0
int i;
for (i = 0; i < 64; i++) {
short f, b;
pair_content(i, &f, &b);
attron(COLOR_PAIR(i));
printw("%u: %u (%s) ", i, f, get_value_string(col_strs, f));
printw("%u (%s)\n\r", b, get_value_string(col_strs, b));
}
refresh();
getch();
exit(0);
#endif
g_st.udp_ofd.cb = udp_fd_cb;
rc = osmo_sock_init_ofd(&g_st.udp_ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 8888, OSMO_SOCK_F_BIND);
if (rc < 0)
exit(1);
while (1) {
osmo_select_main(0);
update_sliders();
};
exit(0);
}