jolla-stats.c 13.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
/*
 *
 *  Connection Manager
 *
 *  Copyright (C) 2014 Jolla Ltd. 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

#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/stat.h>

#include "connman.h"

/*
 * Simplified version of stats.c
 */

#define STATS_DIR_MODE      (0755)
#define STATS_FILE_MODE     (0644)
#define STATS_FILE_VERSION  (0x01)
#define STATS_FILE_HOME     "stats.home"
#define STATS_FILE_ROAMING  "stats.roaming"

#define stats_file(roaming) ((roaming) ? STATS_FILE_ROAMING : STATS_FILE_HOME)

47 48 49 50 51 52 53 54 55 56 57
/*
 * To reduce the number of writes, we don't overwrite the stats files more
 * often than once in STATS_SHORT_WRITE_PERIOD_SEC seconds. If the changes are
 * insignificant (less than STATS_SIGNIFICANT_CHANGE bytes) we overwrite the
 * file after STATS_LONG_WRITE_PERIOD_SEC. If there are no changes, we don't
 * overwrite it at all, except when stats get reset or rebased.
 */
#define STATS_SIGNIFICANT_CHANGE        (1024)
#define STATS_SHORT_WRITE_PERIOD_SEC    (2)
#define STATS_LONG_WRITE_PERIOD_SEC     (30)

58 59 60 61 62 63 64 65 66 67 68 69
/* Unused files that may have been created by earlier versions of connman */
static const char* stats_obsolete[] = { "data", "history" };

struct stats_file_contents {
	uint32_t version;
	uint32_t reserved;
	struct connman_stats_data total;
} __attribute__((packed));

struct connman_stats {
	char *path;
	char *name;
70 71 72 73 74
	gboolean modified;
	uint64_t bytes_change;
	guint short_write_timeout_id;
	guint long_write_timeout_id;
	struct stats_file_contents contents;
75 76 77
	struct connman_stats_data last;
};

78 79
#define llu_(x) ((long long unsigned int)(x))

80 81 82 83
static void stats_save(struct connman_stats *stats);

static gboolean stats_file_read(const char *path,
					struct stats_file_contents *contents)
84
{
85 86 87 88 89 90 91 92 93
	gboolean ok = false;
	int fd = open(path, O_RDONLY);
	if (fd >= 0) {
		struct stats_file_contents buf;
		ssize_t nbytes = read(fd, &buf, sizeof(buf));
		if (nbytes == sizeof(buf)) {
			if (buf.version == STATS_FILE_VERSION) {
				DBG("%s", path);
				DBG("[RX] %llu packets %llu bytes",
94 95
					llu_(buf.total.rx_packets),
					llu_(buf.total.rx_bytes));
96
				DBG("[TX] %llu packets %llu bytes",
97 98
					llu_(buf.total.tx_packets),
					llu_(buf.total.tx_bytes));
99 100 101 102 103 104 105 106 107 108 109 110 111
				*contents = buf;
				ok = true;
			} else {
				connman_error("%s: unexpected version (%u)",
					path, buf.version);
			}
		} else if (nbytes >= 0) {
			connman_error("%s: failed to read (%u bytes)",
				path, (unsigned int) nbytes);
		} else {
			connman_error("%s: %s", path, strerror(errno));
		}
		close(fd);
112
	}
113
	return ok;
114 115
}

116 117
static gboolean stats_file_write(const char *path,
				const struct stats_file_contents *contents)
118
{
119 120
	gboolean ok = false;
	int fd = open(path, O_RDWR | O_CREAT, STATS_FILE_MODE);
121
	if (fd >= 0) {
122
		int err = ftruncate(fd, sizeof(*contents));
123
		if (err >= 0) {
124 125
			ssize_t nbytes = write(fd, contents, sizeof(*contents));
			if (nbytes == sizeof(*contents)) {
126
				DBG("%s", path);
127 128
				ok = true;
			} else if (nbytes >= 0) {
129
				DBG("%s: failed to write (%u bytes)",
130 131
					path, (unsigned int) nbytes);
			} else {
132
				DBG("%s: %s", path, strerror(errno));
133 134
			}
		} else {
135
			DBG("%s: %s", path, strerror(errno));
136 137
		}
		close(fd);
138 139
	} else {
		DBG("%s: %s", path, strerror(errno));
140
	}
141 142 143 144 145 146 147 148 149 150 151 152
	return ok;
}

static struct connman_stats *stats_new(const char *id, const char *dir,
							const char *file)
{
	struct connman_stats *stats = g_new0(struct connman_stats, 1);

	stats->contents.version = STATS_FILE_VERSION;
	stats->path = g_strconcat(dir, "/", file, NULL);
	stats->name = g_strconcat(id, "/", file, NULL);
	return stats;
153 154 155 156 157
}

/** Deletes the leftovers from the old connman */
static void stats_delete_obsolete_files(const char* dir)
{
158
	guint i;
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
	for (i=0; i<G_N_ELEMENTS(stats_obsolete); i++) {
		char* path = g_strconcat(dir, "/", stats_obsolete[i], NULL);
		if (unlink(path) < 0) {
			if (errno != ENOENT) {
				connman_error("error deleting %s: %s",
						path, strerror(errno));
			}
		} else {
			DBG("deleted %s", path);
		}
		g_free(path);
	}
}

/** Creates file if it doesn't exist */
174 175
struct connman_stats *__connman_stats_new(struct connman_service *service,
							gboolean roaming)
176 177 178
{
	int err = 0;
	struct connman_stats *stats = NULL;
179
	const char *ident = connman_service_get_identifier(service);
180 181 182 183 184 185 186 187 188 189 190 191 192 193
	char *dir = g_strconcat(STORAGEDIR, "/", ident, NULL);

	DBG("%s %d", ident, roaming);

	/* If the dir doesn't exist, create it */
	if (!g_file_test(dir, G_FILE_TEST_IS_DIR)) {
		if (mkdir(dir, STATS_DIR_MODE) < 0) {
			if (errno != EEXIST) {
				err = -errno;
			}
		}
	}

	if (!err) {
194 195
		stats = stats_new(ident, dir, stats_file(roaming));
		stats_file_read(stats->path, &stats->contents);
196 197 198 199 200 201 202 203 204 205
		stats_delete_obsolete_files(dir);
	} else {
		connman_error("failed to create %s: %s", dir, strerror(errno));
	}

	g_free(dir);
	return stats;
}

/** Returns NULL if the file doesn't exist */
206 207
struct connman_stats *__connman_stats_new_existing(
			struct connman_service *service, gboolean roaming)
208
{
209 210 211
	struct connman_stats *stats = NULL;
	struct stats_file_contents contents;
	const char* file = stats_file(roaming);
212 213
	const char *ident = connman_service_get_identifier(service);
	char *dir = g_strconcat(STORAGEDIR, "/", ident, NULL);
214 215 216
	char *path = g_strconcat(dir, "/", file, NULL);

	if (stats_file_read(path, &contents)) {
217
		stats = stats_new(ident, dir, file);
218 219
		stats->contents = contents;
	}
220 221

	g_free(dir);
222
	g_free(path);
223 224 225
	return stats;
}

226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
static char *stats_path(const char *identifier, gboolean roaming)
{
	return g_strconcat(STORAGEDIR, G_DIR_SEPARATOR_S, identifier,
			G_DIR_SEPARATOR_S, stats_file(roaming), NULL);
}

void __connman_stats_read(const char *identifier, gboolean roaming,
				struct connman_stats_data *data)
{
	char *path = stats_path(identifier, roaming);
	struct stats_file_contents contents;
	if (stats_file_read(path, &contents)) {
		*data = contents.total;
	} else {
		memset(data, 0, sizeof(*data));
	}
	g_free(path);
}

void __connman_stats_clear(const char *identifier, gboolean roaming)
{
	char *path = stats_path(identifier, roaming);
	struct stats_file_contents contents;
	if (stats_file_read(path, &contents)) {
		memset(&contents.total, 0, sizeof(contents.total));
		stats_file_write(path, &contents);
	}
	g_free(path);
}

256 257 258 259
void __connman_stats_free(struct connman_stats *stats)
{
	if (stats) {
		DBG("%s", stats->name);
260 261 262 263 264 265 266 267 268 269

		if (stats->modified)
			stats_file_write(stats->path, &stats->contents);

                if (stats->short_write_timeout_id)
                    g_source_remove(stats->short_write_timeout_id);

                if (stats->long_write_timeout_id)
                    g_source_remove(stats->long_write_timeout_id);

270 271 272 273 274 275
		g_free(stats->path);
		g_free(stats->name);
		g_free(stats);
	}
}

276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
static inline gboolean stats_significantly_changed(
					const struct connman_stats *stats)
{
	return stats->bytes_change >= STATS_SIGNIFICANT_CHANGE;
}

static gboolean stats_short_save_timeout(gpointer data)
{
	struct connman_stats *stats = data;

	DBG("%s", stats->name);
	stats->short_write_timeout_id = 0;
	if (stats_significantly_changed(stats))
		stats_save(stats);

	return FALSE;
}

static gboolean stats_long_save_timeout(gpointer data)
{
	struct connman_stats *stats = data;

	DBG("%s", stats->name);
	stats->long_write_timeout_id = 0;
	if (stats->modified)
		stats_save(stats);

	return FALSE;
}

static void stats_save(struct connman_stats *stats)
{
	if (stats_file_write(stats->path, &stats->contents)) {
		stats->bytes_change = 0;
		stats->modified = false;
	}

	/* Reset the timeouts */
	if (stats->short_write_timeout_id)
		g_source_remove(stats->short_write_timeout_id);

	if (stats->long_write_timeout_id)
		g_source_remove(stats->long_write_timeout_id);

	stats->short_write_timeout_id = g_timeout_add_seconds(
		STATS_SHORT_WRITE_PERIOD_SEC, stats_short_save_timeout, stats);
	stats->long_write_timeout_id = g_timeout_add_seconds(
		STATS_LONG_WRITE_PERIOD_SEC, stats_long_save_timeout, stats);
}

326 327 328 329 330 331 332 333 334 335 336 337 338 339
/* Protection against counters getting wrapped at 32-bit boundary */
#define STATS_UPPER_BITS_SHIFT (32)
#define STATS_UPPER_BITS (~((1ull << STATS_UPPER_BITS_SHIFT) - 1))
#define stats_32bit(value) (((value) & STATS_UPPER_BITS) == 0)

static inline void stats_fix32(uint64_t *newval, uint64_t oldval)
{
	if (*newval < oldval) {
		uint64_t prev = *newval;
		*newval |= (oldval & STATS_UPPER_BITS);

		if (G_UNLIKELY(*newval < oldval))
			*newval += (1ull << STATS_UPPER_BITS_SHIFT);

340
		DBG("0x%08llx -> 0x%llx", llu_(prev), llu_(*newval));
341 342 343 344 345 346 347 348 349 350 351 352 353
	}
}

void __connman_stats_update(struct connman_stats *stats,
				const struct connman_stats_data *data)
{
	struct connman_stats_data *last, *total;
	struct connman_stats_data fixed;

	if (!stats)
		return;

	last = &stats->last;
354
	total = &stats->contents.total;
355

356
	/* If nothing has changed, don't do anything */
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
	if (!memcmp(last, data, sizeof(*last)))
		return;

	if ((data->rx_packets < last->rx_packets) ||
	    (data->tx_packets < last->tx_packets) ||
	    (data->rx_bytes   < last->rx_bytes  ) ||
	    (data->tx_bytes   < last->tx_bytes  ) ||
	    (data->rx_errors  < last->rx_errors ) ||
	    (data->tx_errors  < last->tx_errors ) ||
	    (data->rx_dropped < last->rx_dropped) ||
	    (data->tx_dropped < last->tx_dropped)) {

		/*
		 * This can happen if the counter wasn't rebased after
		 * switching the network interface. However most likely
		 * it's the result of 32-bit wrap-around that occurs in
		 * (at least some versions of) 32-bit kernels. Double
		 * check that all the upper 32-bits in all counters are
		 * indeed zero.
		 */

		if (G_UNLIKELY(!stats_32bit(data->rx_packets)) ||
		    G_UNLIKELY(!stats_32bit(data->tx_packets)) ||
		    G_UNLIKELY(!stats_32bit(data->rx_bytes  )) ||
		    G_UNLIKELY(!stats_32bit(data->tx_bytes  )) ||
		    G_UNLIKELY(!stats_32bit(data->rx_errors )) ||
		    G_UNLIKELY(!stats_32bit(data->tx_errors )) ||
		    G_UNLIKELY(!stats_32bit(data->rx_dropped)) ||
		    G_UNLIKELY(!stats_32bit(data->tx_dropped))) {
			DBG("%s screwed up", stats->name);
			return;
		}

		fixed = *data;
		data = &fixed;

		stats_fix32(&fixed.rx_packets, last->rx_packets);
		stats_fix32(&fixed.tx_packets, last->tx_packets);
		stats_fix32(&fixed.rx_bytes,   last->rx_bytes  );
		stats_fix32(&fixed.tx_bytes,   last->tx_bytes  );
		stats_fix32(&fixed.rx_errors,  last->rx_errors );
		stats_fix32(&fixed.tx_errors,  last->tx_errors );
		stats_fix32(&fixed.rx_dropped, last->rx_dropped);
		stats_fix32(&fixed.tx_dropped, last->tx_dropped);
	}

	DBG("%s [RX] %llu packets %llu bytes", stats->name,
404
			llu_(data->rx_packets), llu_(data->rx_bytes));
405
	DBG("%s [TX] %llu packets %llu bytes", stats->name,
406
			llu_(data->tx_packets), llu_(data->tx_bytes));
407 408 409 410 411 412 413 414 415 416 417

	/* Update the total counters */
	total->rx_packets += (data->rx_packets - last->rx_packets);
	total->tx_packets += (data->tx_packets - last->tx_packets);
	total->rx_bytes   += (data->rx_bytes   - last->rx_bytes  );
	total->tx_bytes   += (data->tx_bytes   - last->tx_bytes  );
	total->rx_errors  += (data->rx_errors  - last->rx_errors );
	total->tx_errors  += (data->tx_errors  - last->tx_errors );
	total->rx_dropped += (data->rx_dropped - last->rx_dropped);
	total->tx_dropped += (data->tx_dropped - last->tx_dropped);

418 419 420 421 422 423
	/* Accumulate the changes */
	stats->modified = true;
	stats->bytes_change +=
		(data->rx_bytes - last->rx_bytes) +
		(data->tx_bytes - last->tx_bytes);

424 425
	/* Store the last values */
	*last = *data;
426 427 428 429 430 431 432 433 434 435 436

	/* Check if the changes need to be saved right away */
	if (stats_significantly_changed(stats)) {
		/* short_write_timeout_id prohibits any saves */
		if (!stats->short_write_timeout_id)
			stats_save(stats);
	} else {
		/* long_write_timeout_id prohibits insignificant saves */
		if (!stats->long_write_timeout_id)
			stats_save(stats);
	}
437 438 439 440 441
}

void __connman_stats_reset(struct connman_stats *stats)
{
	if (stats) {
442
		struct connman_stats_data* total = &stats->contents.total;
443 444 445

		DBG("%s", stats->name);
		memset(total, 0, sizeof(*total));
446
		stats_save(stats);
447 448 449 450 451 452 453 454 455 456 457
	}
}

void __connman_stats_rebase(struct connman_stats *stats,
				const struct connman_stats_data *data)
{
	if (stats) {
		struct connman_stats_data* last = &stats->last;

		if (data) {
			DBG("%s [RX] %llu packets %llu bytes", stats->name,
458
				llu_(data->rx_packets), llu_(data->rx_bytes));
459
			DBG("%s [TX] %llu packets %llu bytes", stats->name,
460
				llu_(data->tx_packets), llu_(data->tx_bytes));
461 462 463 464 465
			*last = *data;
		} else {
			DBG("%s", stats->name);
			memset(last, 0, sizeof(*last));
		}
466 467

		stats_save(stats);
468 469 470 471 472 473 474
	}
}

void __connman_stats_get(struct connman_stats *stats,
				struct connman_stats_data *data)
{
	if (stats) {
475
		*data = stats->contents.total;
476
	} else {
477
		memset(data, 0, sizeof(*data));
478 479 480 481 482 483 484 485 486 487 488
	}
}

int __connman_stats_init(void)
{
	return 0;
}

void __connman_stats_cleanup(void)
{
}