Commit ddfd9fa0 authored by Slava Monich's avatar Slava Monich

[connman] Added new counters and history. JB#36051, JB#43750

New and improved counters with history and support for automatic cut-off
and periodic reset.
parent 3717c42d
......@@ -139,7 +139,7 @@ src_connmand_SOURCES = $(gdhcp_sources) $(gweb_sources) $(backtrace_sources) \
src/ippool.c src/bridge.c src/nat.c src/ipaddress.c \
src/inotify.c src/firewall.c src/ipv6pd.c src/peer.c \
src/peer_service.c src/machine.c src/util.c \
src/wakeup_timer.c src/jolla-stats.c src/fsid.c \
src/wakeup_timer.c src/fsid.c \
src/access.c vpn/vpn-settings.c \
src/sailfish_iptables_ext.c src/iptables-validate.c
......@@ -154,6 +154,21 @@ src_connmand_wait_online_SOURCES = src/connmand-wait-online.c
src_connmand_wait_online_LDADD = gdbus/libgdbus-internal.la \
@GLIB_LIBS@ @DBUS_LIBS@
if SAILFISH_COUNTERS
src_connmand_SOURCES += src/sailfish_datacounter.c \
src/sailfish_datacounter_dbus.c \
src/sailfish_datacounters.c \
src/sailfish_datacounters_dbus.c \
src/sailfish_datacounters_dbus_util.c \
src/sailfish_datacounters_util.c \
src/sailfish_datahistory.c \
src/sailfish_datahistory_dbus.c \
src/sailfish_datahistory_file.c \
src/sailfish_datahistory_memory.c \
src/sailfish_stats.c
else
src_connmand_SOURCES += src/jolla-stats.c
endif
if VPN
vpn_plugin_LTLIBRARIES =
......
......@@ -447,6 +447,15 @@ if (test "${enable_sailfish_wifi}" = "yes"); then
need_glibutil=yes
fi
AC_ARG_ENABLE(sailfish-counters,
AC_HELP_STRING([--enable-sailfish-counters], [enable Sailfish counters]),
[enable_sailfish_counters=${enableval}], [enable_sailfish_counters="no"])
AM_CONDITIONAL(SAILFISH_COUNTERS, test "${enable_sailfish_counters}" != "no")
if (test "${enable_sailfish_counters}" = "yes"); then
need_glibutil=yes
fi
AC_ARG_ENABLE(sailfish-ofono,
AC_HELP_STRING([--enable-sailfish-ofono], [enable Sailfish oFono plugin]),
[enable_sailfish_ofono=${enableval}], [enable_sailfish_ofono="no"])
......
This diff is collapsed.
This diff is collapsed.
/*
* Connection Manager
*
* Copyright (C) 2016-2018 Jolla Ltd. All rights reserved.
* Copyright (C) 2016-2018 Slava Monich <slava.monich@jolla.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*/
#include "sailfish_datacounters.h"
#include "connman.h"
#include <gutil_misc.h>
typedef struct datacounters DataCounters;
struct datacounters_priv {
char *ident;
GSList *list;
const char **counters;
};
typedef GObjectClass DataCountersClass;
G_DEFINE_TYPE(DataCounters, datacounters, G_TYPE_OBJECT)
#define PARENT_CLASS datacounters_parent_class
#define DATACOUNTERS_TYPE (datacounters_get_type())
#define DATACOUNTERS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \
DATACOUNTERS_TYPE, DataCounters))
enum datacounter_signal {
SIGNAL_COUNTERS,
SIGNAL_COUNT
};
#define SIGNAL_COUNTERS_NAME "datacounters-counters"
static guint datacounters_signal[SIGNAL_COUNT];
/* Weak references to the instances of DataCounters */
static GHashTable *datacounters_table = NULL;
/*==========================================================================*
* Implementation
*==========================================================================*/
static struct datacounter *datacounters_find(struct datacounters_priv *priv,
const char *name)
{
GSList *l = priv->list;
while (l) {
struct datacounter *dc = l->data;
if (!g_strcmp0(dc->name, name)) {
return dc;
}
l = l->next;
}
return NULL;
}
static const char **datacounters_counter_names(struct datacounters_priv *priv)
{
guint i;
GSList *l;
const guint n = g_slist_length(priv->list);
const char **counters = g_new0(const char*, n+1);
for (l = priv->list, i = 0; l && i < n; l = l->next, i++) {
/*
* Assume that datacounter never reallocates its name and
* just store the pointer (could be an over-optimization?)
*/
struct datacounter *counter = l->data;
counters[i] = counter->name;
}
counters[i] = NULL;
return counters;
}
static void datacounters_destroyed(gpointer key, GObject *obj)
{
DBG("%s", (char*)key);
g_hash_table_remove(datacounters_table, key);
if (g_hash_table_size(datacounters_table) == 0) {
/* Delete the hashtable when we no longer need it */
g_hash_table_unref(datacounters_table);
datacounters_table = NULL;
}
}
static void datacounters_counters_modified(struct datacounters *self)
{
struct datacounters_priv *priv = self->priv;
g_free(priv->counters);
self->counters = priv->counters = datacounters_counter_names(priv);
g_signal_emit(self, datacounters_signal[SIGNAL_COUNTERS], 0);
}
static void datacounters_counter_destroyed(gpointer arg, GObject *counter)
{
struct datacounters *self = DATACOUNTERS(arg);
struct datacounters_priv *priv = self->priv;
priv->list = g_slist_remove(priv->list, counter);
datacounters_counters_modified(self);
datacounters_unref(self);
}
static void datacounters_append_counter(struct datacounters *self,
struct datacounter *dc)
{
struct datacounters_priv *priv = self->priv;
priv->list = g_slist_append(priv->list, dc);
datacounters_counters_modified(self);
}
static struct datacounters *datacounters_create(const char *ident)
{
struct datacounters *self = g_object_new(DATACOUNTERS_TYPE, NULL);
struct datacounters_priv *priv = self->priv;
DBG("%s", ident);
self->ident = priv->ident = g_strdup(ident);
return self;
}
/*==========================================================================*
* API
*==========================================================================*/
struct datacounters *datacounters_new(const char *ident)
{
struct datacounters *self = NULL;
if (G_LIKELY(ident)) {
if (datacounters_table) {
self = g_hash_table_lookup(datacounters_table, ident);
} else {
datacounters_table = g_hash_table_new_full(g_str_hash,
g_str_equal, g_free, NULL);
}
if (self) {
datacounters_ref(self);
} else {
char *key = g_strdup(ident);
self = datacounters_create(ident);
g_hash_table_insert(datacounters_table, key, self);
g_object_weak_ref(G_OBJECT(self),
datacounters_destroyed, key);
}
}
return self;
}
struct datacounters *datacounters_ref(struct datacounters *self)
{
if (G_LIKELY(self)) {
g_object_ref(DATACOUNTERS(self));
}
return self;
}
void datacounters_unref(struct datacounters *self)
{
if (G_LIKELY(self)) {
g_object_unref(DATACOUNTERS(self));
}
}
struct datacounter *datacounters_get_counter(struct datacounters *self,
const char *name)
{
if (G_LIKELY(self) && G_LIKELY(name)) {
struct datacounters_priv *priv = self->priv;
struct datacounter *dc = datacounters_find(priv, name);
if (dc) {
datacounter_ref(dc);
} else {
/*
* Each datacounter created this way, implicitely
* holds a reference to datacounters. The reference
* gets released when datacounter is destroyed.
*/
dc = datacounter_new(self->ident, name);
g_object_weak_ref(G_OBJECT(dc),
datacounters_counter_destroyed,
datacounters_ref(self));
datacounters_append_counter(self, dc);
}
return dc;
}
return NULL;
}
void datacounters_reset_all_counters(struct datacounters *self)
{
if (G_LIKELY(self)) {
GSList *l;
struct datacounters_priv *priv = self->priv;
for (l = priv->list; l; l = l->next) {
datacounter_reset(l->data);
}
}
}
gulong datacounters_add_counters_handler(struct datacounters *self,
datacounters_cb_t cb, void *arg)
{
return (G_LIKELY(self) && G_LIKELY(cb)) ? g_signal_connect(self,
SIGNAL_COUNTERS_NAME, G_CALLBACK(cb), arg) : 0;
}
void datacounters_remove_handler(struct datacounters *self, gulong id)
{
if (G_LIKELY(self) && G_LIKELY(id)) {
g_signal_handler_disconnect(self, id);
}
}
void datacounters_remove_handlers(struct datacounters *self,
gulong *ids, guint count)
{
gutil_disconnect_handlers(self, ids, count);
}
/*==========================================================================*
* Internals
*==========================================================================*/
static void datacounters_init(struct datacounters *self)
{
struct datacounters_priv *priv = G_TYPE_INSTANCE_GET_PRIVATE(self,
DATACOUNTERS_TYPE, struct datacounters_priv);
priv->counters = g_new0(const char*, 1);
self->priv = priv;
self->counters = priv->counters;
}
static void datacounters_finalize(GObject *object)
{
struct datacounters *self = DATACOUNTERS(object);
struct datacounters_priv *priv = self->priv;
DBG("%s", priv->ident);
g_free(priv->ident);
g_free(priv->counters);
G_OBJECT_CLASS(PARENT_CLASS)->finalize(object);
}
static void datacounters_class_init(DataCountersClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->finalize = datacounters_finalize;
g_type_class_add_private(klass, sizeof(struct datacounters_priv));
datacounters_signal[SIGNAL_COUNTERS] =
g_signal_new(SIGNAL_COUNTERS_NAME, G_OBJECT_CLASS_TYPE(klass),
G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 0);
}
/*
* Local Variables:
* mode: C
* c-basic-offset: 8
* indent-tabs-mode: t
* End:
*/
This diff is collapsed.
/*
* Connection Manager
*
* Copyright (C) 2016-2018 Jolla Ltd. All rights reserved.
* Copyright (C) 2016-2018 Slava Monich <slava.monich@jolla.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*/
#include "sailfish_datacounters.h"
#include "sailfish_datacounters_dbus_util.h"
#include <connman/dbus.h>
#include <connman/log.h>
struct datacounters_dbus {
gint ref_count;
struct datacounters *counters;
DBusConnection *conn;
char *path;
gulong handler_id;
};
typedef void (*datacounters_dbus_append_fn)(DBusMessageIter *it,
struct datacounters_dbus *dbus);
#define COUNTERS_DBUS_PATH_PREFIX CONNMAN_PATH "/service/"
#define COUNTERS_DBUS_INTERFACE_VERSION (1)
#define COUNTERS_DBUS_SIGNAL_CREATED "Created"
#define COUNTERS_DBUS_SIGNAL_DELETED "Deleted"
#define COUNTERS_DBUS_SIGNAL_COUNTERS_CHANGED "CountersChanged"
/* Active instances of datacounters_dbus */
static GHashTable *datacounters_dbus_table = NULL;
static DBusMessage *datacounters_dbus_reply(DBusMessage *msg,
struct datacounters_dbus *dbus, datacounters_dbus_append_fn append)
{
DBusMessage *reply = dbus_message_new_method_return(msg);
DBusMessageIter it;
dbus_message_iter_init_append(reply, &it);
append(&it, dbus);
return reply;
}
static void datacounters_dbus_signal(struct datacounters_dbus *dbus,
const char *name, datacounters_dbus_append_fn append)
{
DBusMessage *signal = dbus_message_new_signal(dbus->path,
COUNTERS_DBUS_INTERFACE, name);
if (append) {
DBusMessageIter it;
dbus_message_iter_init_append(signal, &it);
append(&it, dbus);
}
g_dbus_send_message(dbus->conn, signal);
}
static void datacounters_dbus_append_version(DBusMessageIter *it,
struct datacounters_dbus *dbus)
{
datacounters_dbus_append_int32(it, COUNTERS_DBUS_INTERFACE_VERSION);
}
static void datacounters_dbus_append_counters(DBusMessageIter *it,
struct datacounters_dbus *dbus)
{
DBusMessageIter array;
const char *ident = dbus->counters->ident;
const char *const *ptr = dbus->counters->counters;
dbus_message_iter_open_container(it, DBUS_TYPE_ARRAY,
DBUS_TYPE_OBJECT_PATH_AS_STRING, &array);
while (*ptr) {
const char *name = *ptr++;
char *path = datacounter_dbus_path(ident, name);
dbus_message_iter_append_basic(&array, DBUS_TYPE_OBJECT_PATH,
&path);
g_free(path);
}
dbus_message_iter_close_container(it, &array);
}
static void datacounters_dbus_append_all(DBusMessageIter *it,
struct datacounters_dbus *dbus)
{
datacounters_dbus_append_version(it, dbus);
datacounters_dbus_append_counters(it, dbus);
}
static DBusMessage *datacounters_dbus_get_all(DBusConnection *conn,
DBusMessage *msg, void *data)
{
return datacounters_dbus_reply(msg, (struct datacounters_dbus *)data,
datacounters_dbus_append_all);
}
static DBusMessage *datacounters_dbus_get_version(DBusConnection *conn,
DBusMessage *msg, void *data)
{
return datacounters_dbus_reply(msg, (struct datacounters_dbus *)data,
datacounters_dbus_append_version);
}
static DBusMessage *datacounters_dbus_get_counters(DBusConnection *conn,
DBusMessage *msg, void *data)
{
return datacounters_dbus_reply(msg, (struct datacounters_dbus *)data,
datacounters_dbus_append_counters);
}
static DBusMessage *datacounters_dbus_reset_all(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct datacounters_dbus *dbus = data;
datacounters_reset_all_counters(dbus->counters);
return dbus_message_new_method_return(msg);
}
#define COUNTERS_DBUS_GET_ALL_ARGS \
{"version", "i" }, \
{"counters", "ao" }
static const GDBusMethodTable datacounters_dbus_methods[] = {
{ GDBUS_METHOD("GetAll",
NULL, GDBUS_ARGS(COUNTERS_DBUS_GET_ALL_ARGS),
datacounters_dbus_get_all) },
{ GDBUS_METHOD("GetInterfaceVersion",
NULL, GDBUS_ARGS({ "version", "i" }),
datacounters_dbus_get_version) },
{ GDBUS_METHOD("GetCounters",
NULL, GDBUS_ARGS({ "counters", "ao" }),
datacounters_dbus_get_counters) },
{ GDBUS_METHOD("ResetAll",
NULL, NULL,
datacounters_dbus_reset_all) },
{ }
};
static const GDBusSignalTable datacounters_dbus_signals[] = {
{ GDBUS_SIGNAL(COUNTERS_DBUS_SIGNAL_CREATED, NULL) },
{ GDBUS_SIGNAL(COUNTERS_DBUS_SIGNAL_DELETED, NULL) },
{ GDBUS_SIGNAL(COUNTERS_DBUS_SIGNAL_COUNTERS_CHANGED,
GDBUS_ARGS({ "counters", "ao" })) },
{ }
};
static void datacounters_dbus_counters_changed(struct datacounters *counters,
void *arg)
{
struct datacounters_dbus *dbus = arg;
datacounters_dbus_signal(dbus, COUNTERS_DBUS_SIGNAL_COUNTERS_CHANGED,
datacounters_dbus_append_counters);
}
static void datacounters_dbus_really_free(struct datacounters_dbus *dbus)
{
/* Remove it from the table */
g_hash_table_remove(datacounters_dbus_table, dbus->counters->ident);
if (g_hash_table_size(datacounters_dbus_table) == 0) {
/* Delete the hashtable when we no longer need it */
g_hash_table_unref(datacounters_dbus_table);
datacounters_dbus_table = NULL;
}
datacounters_dbus_signal(dbus, COUNTERS_DBUS_SIGNAL_DELETED, NULL);
g_dbus_unregister_interface(dbus->conn, dbus->path,
COUNTERS_DBUS_INTERFACE);
datacounters_remove_handler(dbus->counters, dbus->handler_id);
datacounters_unref(dbus->counters);
dbus_connection_unref(dbus->conn);
g_free(dbus->path);
g_free(dbus);
}
static struct datacounters_dbus *datacounters_dbus_create(
struct datacounters *counters)
{
struct datacounters_dbus *dbus = g_new0(struct datacounters_dbus, 1);
g_atomic_int_set(&dbus->ref_count, 1);
dbus->conn = dbus_connection_ref(connman_dbus_get_connection());
dbus->counters = datacounters_ref(counters);
dbus->path = g_strconcat(COUNTERS_DBUS_PATH_PREFIX,
counters->ident, NULL);
dbus->handler_id = datacounters_add_counters_handler(counters,
datacounters_dbus_counters_changed, dbus);
if (g_dbus_register_interface(dbus->conn, dbus->path,
COUNTERS_DBUS_INTERFACE, datacounters_dbus_methods,
datacounters_dbus_signals, NULL, dbus, NULL)) {
datacounters_dbus_signal(dbus, COUNTERS_DBUS_SIGNAL_CREATED,
NULL);
return dbus;
} else {
connman_error("Counters D-Bus registeration failed");
datacounters_dbus_free(dbus);
}
return NULL;
}
struct datacounters_dbus *datacounters_dbus_new(struct datacounters *counters)
{
struct datacounters_dbus *dbus = NULL;
if (G_LIKELY(counters)) {
if (datacounters_dbus_table) {
dbus = g_hash_table_lookup(datacounters_dbus_table,
counters->ident);
} else {
datacounters_dbus_table = g_hash_table_new(g_str_hash,
g_str_equal);
}
if (dbus) {
g_atomic_int_inc(&dbus->ref_count);
} else {
dbus = datacounters_dbus_create(counters);
g_hash_table_insert(datacounters_dbus_table,
(gpointer)counters->ident, dbus);
}
}
return dbus;
}
void datacounters_dbus_free(struct datacounters_dbus *dbus)
{
if (G_LIKELY(dbus)) {
if (g_atomic_int_dec_and_test(&dbus->ref_count)) {
datacounters_dbus_really_free(dbus);
}
}
}
/*
* Local Variables:
* mode: C
* c-basic-offset: 8
* indent-tabs-mode: t
* End:
*/
/*
* Connection Manager
*
* Copyright (C) 2016-2018 Jolla Ltd. All rights reserved.
* Copyright (C) 2016-2018 Slava Monich <slava.monich@jolla.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*/
#include "sailfish_datacounters_dbus_util.h"
#include "connman.h"
struct datacounters_dbus_client {
struct datacounters_dbus_updates *up;
char *peer;
guint watch_id;
DBusConnection *conn;
GHashTable *updates;
guint flags;
};
struct datacounters_dbus_client_update {
guint flags;
guint interval;
};
static void datacounters_dbus_client_update_free(gpointer data)
{
struct datacounters_dbus_client_update *update = data;
__connman_rtnl_update_interval_remove(update->interval);
g_slice_free(struct datacounters_dbus_client_update, update);
}
gboolean datacounters_dbus_get_args(DBusMessageIter *it, int first_type, ...)
{
gboolean ok = TRUE;
int type = first_type;
va_list va;
va_start(va, first_type);
while (type != DBUS_TYPE_INVALID) {
if (dbus_type_is_basic(type) &&
dbus_message_iter_get_arg_type(it) == type) {
DBusBasicValue *ptr = va_arg(va, DBusBasicValue*);
dbus_message_iter_get_basic(it, ptr);
dbus_message_iter_next(it);
} else {
ok = FALSE;
break;
}
type = va_arg(va, int);
}
va_end(va);
return ok;
}
static guint datacounter_dbus_updates_client_flags(
struct datacounters_dbus_client *client)
{
client->flags = 0;
if (g_hash_table_size(client->updates)) {
gpointer value;
GHashTableIter it;
g_hash_table_iter_init(&it, client->updates);
while (g_hash_table_iter_next(&it, NULL, &value)) {
struct datacounters_dbus_client_update *update = value;
client->flags |= update->flags;
}
}
return client->flags;
}
static void datacounter_dbus_updates_refresh_flags(
struct datacounters_dbus_updates *up)
{
guint flags = 0;
if (up->clients && g_hash_table_size(up->clients)) {
gpointer value;
GHashTableIter it;
g_hash_table_iter_init(&it, up->clients);
while (g_hash_table_iter_next(&it, NULL, &value)) {
flags |= datacounter_dbus_updates_client_flags(value);
}
}
if (up->flags != flags) {
up->flags = flags;
DBG("%s flags => 0x%x", up->name, up->flags);
}
}
static void datacounters_dbus_client_free(gpointer data)
{
struct datacounters_dbus_client *client = data;
g_dbus_remove_watch(client->conn, client->watch_id);
dbus_connection_unref(client->conn);
g_hash_table_destroy(client->updates);
g_free(client->peer);
g_slice_free(struct datacounters_dbus_client, client);
}
static void datacounters_dbus_client_remove(
struct datacounters_dbus_client *client)
{
struct datacounters_dbus_updates *up = client->up;
g_hash_table_remove(up->clients, client->peer);
if (!g_hash_table_size(up->clients)) {
g_hash_table_destroy(up->clients);
up->clients = NULL;
}
datacounter_dbus_updates_refresh_flags(up);
}
static void datacounters_dbus_client_disconnected(DBusConnection *conn,
void *data)
{
datacounters_dbus_client_remove(data);
}
static struct datacounters_dbus_client *datacounters_dbus_client_new(
struct datacounters_dbus_updates *up,
DBusConnection *conn, const char *peer)
{