From: Daniel Wagner <[email protected]>

Currently the statistic information is
stored into the profile file. This
results in rewriting the whole file
if connann_stats_save is called. This
results in too many disk I/Os for low
power devices.

Furthermore, only the current value is
stored. There is no way to find out
how many bytes have been transfered
in the last month.

First, each service statistic is
stored into a separate files under
/var/lib/connman having a *.data
extension. This file contains fixed sized
records of stats counters and will be mmap
into memory. It is used like a ring buffer.

If the buffer is full or after a certain
period (e.g. a month), the raw data
will be summerized into the *.info file.
---
 Makefile.am   |    2 +-
 configure.ac  |   11 ++
 src/connman.h |   24 ++++
 src/main.c    |    2 +
 src/service.c |  131 +++-------------------
 src/stats.c   |  345 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 401 insertions(+), 114 deletions(-)
 create mode 100644 src/stats.c

diff --git a/Makefile.am b/Makefile.am
index 1bf7207..6a6fe6d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -63,7 +63,7 @@ src_connmand_SOURCES = $(gdbus_sources) $(gdhcp_sources) 
$(gresolv_sources) \
                        src/wifi.c src/storage.c src/dbus.c src/config.c \
                        src/technology.c src/counter.c src/location.c \
                        src/session.c src/tethering.c src/ondemand.c \
-                       src/wpad.c
+                       src/wpad.c src/stats.c
 
 if UDEV
 src_connmand_SOURCES += src/udev.c
diff --git a/configure.ac b/configure.ac
index 6923aa1..17a66c7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -258,6 +258,17 @@ fi
 AM_CONDITIONAL(NTPD, test "${enable_ntpd}" != "no")
 AM_CONDITIONAL(NTPD_BUILTIN, test "${enable_ntpd}" = "builtin")
 
+AC_ARG_WITH(stats-max-file-size, 
AC_HELP_STRING([--with-stats-max-file-size=SIZE],
+                                [Maximal size of a statistics round robin 
file]),
+                                [stats_max_file_size=${withval}])
+
+if (test -z "${stats_max_file_size}"); then
+   # default size is 512 kByte
+   stats_max_file_size="512 * 8 * 128"
+fi
+
+AC_DEFINE_UNQUOTED([STATS_MAX_FILE_SIZE], (${stats_max_file_size}), [Maximal 
size of a statistics round robin file])
+
 PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.16, dummy=yes,
                                AC_MSG_ERROR(GLib >= 2.16 is required))
 AC_SUBST(GLIB_CFLAGS)
diff --git a/src/connman.h b/src/connman.h
index 7c723ba..af0e73e 100644
--- a/src/connman.h
+++ b/src/connman.h
@@ -455,6 +455,7 @@ void __connman_service_create_ipconfig(struct 
connman_service *service,
                                                                int index);
 struct connman_ipconfig *__connman_service_get_ipconfig(
                                struct connman_service *service);
+const char *__connman_service_get_ident(struct connman_service *service);
 const char *__connman_service_get_path(struct connman_service *service);
 unsigned int __connman_service_get_order(struct connman_service *service);
 struct connman_network *__connman_service_get_network(struct connman_service 
*service);
@@ -561,3 +562,26 @@ void __connman_session_cleanup(void);
 
 int __connman_ondemand_init(void);
 void __connman_ondemand_cleanup(void);
+
+struct connman_stats_data {
+       unsigned int rx_packets;
+       unsigned int tx_packets;
+       unsigned int rx_bytes;
+       unsigned int tx_bytes;
+       unsigned int rx_errors;
+       unsigned int tx_errors;
+       unsigned int rx_dropped;
+       unsigned int tx_dropped;
+       unsigned int time;
+};
+
+int __connman_stats_init(void);
+void __connman_stats_cleanup(void);
+int __connman_stats_service_register(struct connman_service *service);
+void __connman_stats_service_unregister(struct connman_service *service);
+int  __connman_stats_update(struct connman_service *service,
+                               connman_bool_t roaming,
+                               struct connman_stats_data *data);
+int __connman_stats_get(struct connman_service *service,
+                               connman_bool_t roaming,
+                               struct connman_stats_data *data);
diff --git a/src/main.c b/src/main.c
index aa63225..28e72ba 100644
--- a/src/main.c
+++ b/src/main.c
@@ -225,6 +225,7 @@ int main(int argc, char *argv[])
        __connman_manager_init(option_compat);
        __connman_profile_init();
        __connman_config_init();
+       __connman_stats_init();
 
        __connman_resolver_init();
        __connman_ipconfig_init();
@@ -262,6 +263,7 @@ int main(int argc, char *argv[])
        __connman_ipconfig_cleanup();
        __connman_resolver_cleanup();
 
+       __connman_stats_cleanup();
        __connman_config_cleanup();
        __connman_profile_cleanup();
        __connman_manager_cleanup();
diff --git a/src/service.c b/src/service.c
index 1af2e08..64d97d1 100644
--- a/src/service.c
+++ b/src/service.c
@@ -37,18 +37,6 @@ static GSequence *service_list = NULL;
 static GHashTable *service_hash = NULL;
 static GSList *counter_list = NULL;
 
-struct connman_stats_data {
-       unsigned int rx_packets;
-       unsigned int tx_packets;
-       unsigned int rx_bytes;
-       unsigned int tx_bytes;
-       unsigned int rx_errors;
-       unsigned int tx_errors;
-       unsigned int rx_dropped;
-       unsigned int tx_dropped;
-       unsigned int time;
-};
-
 struct connman_stats {
        connman_bool_t valid;
        connman_bool_t enabled;
@@ -486,104 +474,6 @@ static void stats_stop(struct connman_service *service)
        stats->enabled = FALSE;
 }
 
-static int stats_load(struct connman_service *service, GKeyFile *keyfile)
-{
-       struct connman_stats_data *data;
-
-       /* home */
-       data = &service->stats.data;
-       data->rx_packets = g_key_file_get_integer(keyfile,
-                       service->identifier, "Home.rx_packets", NULL);
-       data->tx_packets = g_key_file_get_integer(keyfile,
-                       service->identifier, "Home.tx_packets", NULL);
-       data->rx_bytes = g_key_file_get_integer(keyfile,
-                       service->identifier, "Home.rx_bytes", NULL);
-       data->tx_bytes = g_key_file_get_integer(keyfile,
-                       service->identifier, "Home.tx_bytes", NULL);
-       data->rx_errors = g_key_file_get_integer(keyfile,
-                       service->identifier, "Home.rx_errors", NULL);
-       data->tx_errors = g_key_file_get_integer(keyfile,
-                       service->identifier, "Home.tx_errors", NULL);
-       data->rx_dropped = g_key_file_get_integer(keyfile,
-                       service->identifier, "Home.rx_dropped", NULL);
-       data->tx_dropped = g_key_file_get_integer(keyfile,
-                       service->identifier, "Home.tx_dropped", NULL);
-       data->time = g_key_file_get_integer(keyfile,
-                       service->identifier, "Home.time", NULL);
-
-       /* roaming */
-       data = &service->stats_roaming.data;
-       data->rx_packets = g_key_file_get_integer(keyfile,
-                       service->identifier, "Roaming.rx_packets", NULL);
-       data->tx_packets = g_key_file_get_integer(keyfile,
-                       service->identifier, "Roaming.tx_packets", NULL);
-       data->rx_bytes = g_key_file_get_integer(keyfile,
-                       service->identifier, "Roaming.rx_bytes", NULL);
-       data->tx_bytes = g_key_file_get_integer(keyfile,
-                       service->identifier, "Roaming.tx_bytes", NULL);
-       data->rx_errors = g_key_file_get_integer(keyfile,
-                       service->identifier, "Roaming.rx_errors", NULL);
-       data->tx_errors = g_key_file_get_integer(keyfile,
-                       service->identifier, "Roaming.tx_errors", NULL);
-       data->rx_dropped = g_key_file_get_integer(keyfile,
-                       service->identifier, "Roaming.rx_dropped", NULL);
-       data->tx_dropped = g_key_file_get_integer(keyfile,
-                       service->identifier, "Roaming.tx_dropped", NULL);
-       data->time = g_key_file_get_integer(keyfile,
-                       service->identifier, "Roaming.time", NULL);
-
-       return 0;
-}
-
-static int stats_save(struct connman_service *service, GKeyFile *keyfile)
-{
-       struct connman_stats_data *data;
-
-       /* home */
-       data = &service->stats.data;
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Home.rx_packets", data->rx_packets);
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Home.tx_packets", data->tx_packets);
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Home.rx_bytes", data->rx_bytes);
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Home.tx_bytes", data->tx_bytes);
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Home.rx_errors", data->rx_errors);
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Home.tx_errors", data->tx_errors);
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Home.rx_dropped", data->rx_dropped);
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Home.tx_dropped", data->tx_dropped);
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Home.time", data->time);
-
-       /* roaming */
-       data = &service->stats_roaming.data;
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Roaming.rx_packets", data->rx_packets);
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Roaming.tx_packets", data->tx_packets);
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Roaming.rx_bytes", data->rx_bytes);
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Roaming.tx_bytes", data->tx_bytes);
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Roaming.rx_errors", data->rx_errors);
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Roaming.tx_errors", data->tx_errors);
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Roaming.rx_dropped", data->rx_dropped);
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Roaming.tx_dropped", data->tx_dropped);
-       g_key_file_set_integer(keyfile, service->identifier,
-               "Roaming.time", data->time);
-
-       return 0;
-}
-
 static void reset_stats(struct connman_service *service)
 {
        DBG("service %p", service);
@@ -1186,6 +1076,8 @@ void __connman_service_notify(struct connman_ipconfig 
*ipconfig,
        gpointer key, value;
        const char *counter;
        struct connman_stats_counter *counters;
+       struct connman_stats_data *data;
+       int err;
 
        service = connman_ipconfig_get_data(ipconfig);
        if (service == NULL)
@@ -1200,6 +1092,12 @@ void __connman_service_notify(struct connman_ipconfig 
*ipconfig,
                rx_errors, tx_errors,
                rx_dropped, tx_dropped);
 
+       data = &stats_get(service)->data;
+       err = __connman_stats_update(service, service->roaming, data);
+       if (err < 0)
+               connman_error("Failed to store statistics for %s",
+                               service->identifier);
+
        g_hash_table_iter_init(&iter, service->counter_table);
        while (g_hash_table_iter_next(&iter, &key, &value)) {
                counter = key;
@@ -2267,6 +2165,7 @@ static void service_free(gpointer user_data)
 
        stats_stop(service);
        __connman_storage_save_service(service);
+       __connman_stats_service_unregister(service);
 
        service->path = NULL;
 
@@ -3293,6 +3192,10 @@ static int service_register(struct connman_service 
*service)
 
        __connman_storage_load_service(service);
 
+       __connman_stats_service_register(service);
+       __connman_stats_get(service, FALSE, &service->stats.data);
+       __connman_stats_get(service, TRUE, &service->stats_roaming.data);
+
        g_dbus_register_interface(connection, service->path,
                                        CONNMAN_SERVICE_INTERFACE,
                                        service_methods, service_signals,
@@ -3464,6 +3367,11 @@ struct connman_service 
*__connman_service_lookup_from_index(int index)
        return NULL;
 }
 
+const char *__connman_service_get_ident(struct connman_service *service)
+{
+       return service->identifier;
+}
+
 const char *__connman_service_get_path(struct connman_service *service)
 {
        return service->path;
@@ -4025,7 +3933,6 @@ static int service_load(struct connman_service *service)
                service->domains = NULL;
        }
 
-       stats_load(service, keyfile);
 done:
        g_key_file_free(keyfile);
 
@@ -4187,8 +4094,6 @@ update:
                g_key_file_remove_key(keyfile, service->identifier,
                                                        "Domains", NULL);
 
-       stats_save(service, keyfile);
-
        data = g_key_file_to_data(keyfile, &length, NULL);
 
        if (g_file_set_contents(pathname, data, length, NULL) == FALSE)
diff --git a/src/stats.c b/src/stats.c
new file mode 100644
index 0000000..3e8cbf3
--- /dev/null
+++ b/src/stats.c
@@ -0,0 +1,345 @@
+/*
+ *
+ *  Connection Manager
+ *
+ *  Copyright (C) 2010  BMW Car IT GmbH. 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 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.
+ *
+ *  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 St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "connman.h"
+
+#define MAGIC 0xFA00B905
+
+struct stats_file_header {
+       unsigned int magic;
+       unsigned int cur;
+       unsigned int next;
+};
+
+struct stats_record {
+       struct timeval tv;
+       struct connman_stats_data data;
+};
+
+struct stats_file {
+       int fd;
+       unsigned int blksize;
+       size_t size;
+       char *addr_base;
+       char *addr_end;
+
+       struct stats_file_header *hdr;
+       struct stats_record *cur;  /* NULL means invalid */
+       struct stats_record *next; /* next free slot to store a record */
+};
+
+struct stats_handle {
+       struct stats_file home;
+       struct stats_file roaming;
+};
+
+GHashTable *stats_hash = NULL;
+
+static struct stats_file *stats_file_get(struct stats_handle *handle,
+                                               connman_bool_t roaming)
+{
+       struct stats_file *file;
+
+       if (roaming == FALSE)
+               file = &handle->home;
+       else
+               file = &handle->roaming;
+
+       return file;
+}
+
+static connman_bool_t stats_file_full(struct stats_file *file)
+{
+       const char *next_end = (char *)file->next + sizeof(struct stats_record);
+
+       return  next_end >= file->addr_end;
+}
+
+static connman_bool_t stats_file_grow(struct stats_file *file)
+{
+       const char *next_end = (char *)file->next + sizeof(struct stats_record);
+
+       if (file->size < STATS_MAX_FILE_SIZE &&
+                       next_end >= file->addr_end)
+               return TRUE;
+       else
+               return FALSE;
+}
+
+static void stats_close_file(struct stats_file *file)
+{
+       file->hdr->cur = (char *)file->cur - file->addr_base;
+       file->hdr->next = (char *)file->next - file->addr_base;
+
+       msync(file->addr_base, file->size, MS_SYNC);
+
+       file->cur = NULL;
+       file->next = NULL;
+
+       munmap(file->addr_base, STATS_MAX_FILE_SIZE);
+       file->addr_base = NULL;
+
+       close(file->fd);
+       file->fd = -1;
+}
+
+static void stats_free(gpointer user_data)
+{
+       struct stats_handle *handle = user_data;
+
+       stats_close_file(&handle->home);
+       stats_close_file(&handle->roaming);
+       g_free(handle);
+}
+
+static int stats_create(struct connman_service *service)
+{
+       struct stats_handle *handle;
+
+       handle = g_try_new0(struct stats_handle, 1);
+       if (handle == NULL)
+               return -ENOMEM;
+
+       g_hash_table_insert(stats_hash, service, handle);
+
+       return 0;
+}
+
+static int stats_increase(struct stats_file *file)
+{
+       int err;
+
+       err = lseek(file->fd, file->size + file->blksize - 1, SEEK_SET);
+       if (err < 0)
+               return -errno;
+
+       err = write(file->fd, "", 1);
+       if (err < 0)
+               return -errno;
+
+       file->size += file->blksize;
+       file->addr_end = file->addr_base + file->size;
+
+       return 0;
+}
+
+static int stats_open_file(struct connman_service *service,
+                               struct stats_file *file,
+                               connman_bool_t roaming)
+{
+       struct stat stat;
+       gchar *filename;
+       int err;
+
+       if (roaming == FALSE) {
+               filename = g_strdup_printf("%s/%s.data", STORAGEDIR,
+                                       __connman_service_get_ident(service));
+       } else {
+               filename = g_strdup_printf("%s/%s-roaming.data", STORAGEDIR,
+                                       __connman_service_get_ident(service));
+       }
+
+       file->fd = open(filename, O_RDWR | O_CREAT, 0644);
+
+       g_free(filename);
+
+       if (file->fd == -1)
+               return -errno;
+
+       err = fstat(file->fd, &stat);
+       if (err < 0)
+               return -errno;
+
+       file->blksize = stat.st_blksize;
+       file->size = stat.st_blocks * 512;
+
+       if (file->size == 0) {
+               /* Allways have space allocated for the file header */
+               err = stats_increase(file);
+               if (err < 0)
+                       return err;
+       }
+
+       /* Note we might ask for a larger address space then the file size */
+       file->addr_base = mmap(NULL, STATS_MAX_FILE_SIZE, PROT_READ | 
PROT_WRITE,
+                               MAP_SHARED, file->fd, 0);
+       if (file->addr_base == MAP_FAILED)
+               return -errno;
+
+       file->addr_end = file->addr_base + file->size;
+
+       file->hdr = (struct stats_file_header *)file->addr_base;
+
+       if (file->hdr->magic != MAGIC ||
+                       file->hdr->cur < sizeof(struct stats_file_header) ||
+                       file->hdr->next < sizeof(struct stats_file_header) ||
+                       file->hdr->cur > file->size ||
+                       file->hdr->next > file->size) {
+               /* invalid header, reset */
+               file->hdr->magic = MAGIC;
+               file->cur = NULL;
+               file->next = (struct stats_record *)
+                       (file->addr_base + sizeof(struct stats_file_header));
+       } else {
+               /* valid header */
+               file->cur = (struct stats_record *)
+                       (file->addr_base + file->hdr->cur);
+               file->next = (struct stats_record *)
+                       (file->addr_base + file->hdr->next);
+       }
+
+       return 0;
+}
+
+static int stats_open(struct connman_service *service,
+                       struct stats_handle *handle)
+{
+       int err;
+
+       err = stats_open_file(service, &handle->home, FALSE);
+       if (err < 0)
+               return err;
+
+       err = stats_open_file(service, &handle->roaming, TRUE);
+       if (err < 0)
+               return err;
+
+       return 0;
+}
+
+int __connman_stats_service_register(struct connman_service *service)
+{
+       struct stats_handle *handle;
+       int err;
+
+       DBG("service %p", service);
+
+       handle = g_hash_table_lookup(stats_hash, service);
+       if (handle == NULL) {
+               err = stats_create(service);
+
+               if (err < 0)
+                       return err;
+
+               handle = g_hash_table_lookup(stats_hash, service);
+       }
+
+       err = stats_open(service, handle);
+       if (err < 0)
+               g_hash_table_remove(stats_hash, service);
+
+       return err;
+}
+
+void __connman_stats_service_unregister(struct connman_service *service)
+{
+       DBG("service %p", service);
+
+       g_hash_table_remove(stats_hash, service);
+}
+
+int  __connman_stats_update(struct connman_service *service,
+                               connman_bool_t roaming,
+                               struct connman_stats_data *data)
+{
+       struct stats_handle *handle;
+       struct stats_file *file;
+       int err;
+
+       handle = g_hash_table_lookup(stats_hash, service);
+       if (handle == NULL)
+               return -EEXIST;
+
+       file = stats_file_get(handle, roaming);
+
+       err = gettimeofday(&file->next->tv, NULL);
+       if (err < 0)
+               return -errno;
+
+       memcpy(&file->next->data, data, sizeof(struct connman_stats_data));
+
+       file->cur = file->next;
+       file->next++;
+
+       if (stats_file_grow(file) == TRUE)
+               err = stats_increase(file);
+
+       if (stats_file_full(file) == TRUE) {
+               /* wrap */
+               file->next = (struct stats_record *)
+                       (file->addr_base + sizeof(struct stats_file_header));
+       }
+
+
+       return err;
+}
+
+int __connman_stats_get(struct connman_service *service,
+                               connman_bool_t roaming,
+                               struct connman_stats_data *data)
+{
+       struct stats_handle *handle;
+       struct stats_file *file;
+
+       handle = g_hash_table_lookup(stats_hash, service);
+       if (handle == NULL)
+               return -EEXIST;
+
+       file = stats_file_get(handle, roaming);
+
+       if (file->cur == NULL)
+               bzero(data, sizeof(struct connman_stats_data));
+       else
+               memcpy(data, &file->cur->data,
+                       sizeof(struct connman_stats_data));
+
+       return 0;
+}
+
+int __connman_stats_init(void)
+{
+       DBG("");
+
+       stats_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+                                                       NULL, stats_free);
+
+       return 0;
+}
+
+void __connman_stats_cleanup(void)
+{
+       DBG("");
+
+       g_hash_table_destroy(stats_hash);
+       stats_hash = NULL;
+}
-- 
1.7.2.2

_______________________________________________
connman mailing list
[email protected]
http://lists.connman.net/listinfo/connman

Reply via email to