stats.c 32.9 KB
Newer Older
1 2 3 4 5
/* Icecast
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
6
 * Copyright 2000-2004, Jack Moffitt <jack@xiph.org,
7 8 9 10
 *                      Michael Smith <msmith@xiph.org>,
 *                      oddsock <oddsock@xiph.org>,
 *                      Karl Heyes <karl@xiph.org>
 *                      and others (see AUTHORS for details).
11
 * Copyright 2012-2014, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
12 13
 */

14 15 16 17
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

Jack Moffitt's avatar
Jack Moffitt committed
18 19 20 21
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
22
#include <ctype.h>
Jack Moffitt's avatar
Jack Moffitt committed
23

24 25 26
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
Jack Moffitt's avatar
Jack Moffitt committed
27

28 29 30 31
#include "common/thread/thread.h"
#include "common/avl/avl.h"
#include "common/httpp/httpp.h"
#include "common/net/sock.h"
Jack Moffitt's avatar
Jack Moffitt committed
32

33
#include "stats.h"
Jack Moffitt's avatar
Jack Moffitt committed
34
#include "connection.h"
35
#include "source.h"
Jack Moffitt's avatar
Jack Moffitt committed
36 37 38
#include "global.h"
#include "refbuf.h"
#include "client.h"
39
#include "admin.h"
40
#include "xslt.h"
41
#include "util.h"
42
#include "auth.h"
43 44
#define CATMODULE "stats"
#include "logging.h"
Jack Moffitt's avatar
Jack Moffitt committed
45

46
#ifdef _WIN32
47
#define atoll _atoi64
48
#define vsnprintf _vsnprintf
49
#define snprintf _snprintf
50
#endif
Jack Moffitt's avatar
Jack Moffitt committed
51

52 53 54 55
#define STATS_EVENT_SET     0
#define STATS_EVENT_INC     1
#define STATS_EVENT_DEC     2
#define STATS_EVENT_ADD     3
Karl Heyes's avatar
Karl Heyes committed
56 57 58
#define STATS_EVENT_SUB     4
#define STATS_EVENT_REMOVE  5
#define STATS_EVENT_HIDDEN  6
59

60 61 62 63 64 65 66 67
typedef struct _event_queue_tag
{
    volatile stats_event_t *head;
    volatile stats_event_t **tail;
} event_queue_t;

#define event_queue_init(qp)    { (qp)->head = NULL; (qp)->tail = &(qp)->head; }

Jack Moffitt's avatar
Jack Moffitt committed
68 69
typedef struct _event_listener_tag
{
70 71
    event_queue_t queue;
    mutex_t mutex;
Jack Moffitt's avatar
Jack Moffitt committed
72

73
    struct _event_listener_tag *next;
Jack Moffitt's avatar
Jack Moffitt committed
74 75
} event_listener_t;

76
static volatile int _stats_running = 0;
77
static thread_type *_stats_thread_id;
78
static volatile int _stats_threads = 0;
Jack Moffitt's avatar
Jack Moffitt committed
79

80 81
static stats_t _stats;
static mutex_t _stats_mutex;
Jack Moffitt's avatar
Jack Moffitt committed
82

83
static event_queue_t _global_event_queue;
Jack Moffitt's avatar
Jack Moffitt committed
84 85
mutex_t _global_event_mutex;

86
static volatile event_listener_t *_event_listeners;
Jack Moffitt's avatar
Jack Moffitt committed
87 88 89 90 91 92 93


static void *_stats_thread(void *arg);
static int _compare_stats(void *a, void *b, void *arg);
static int _compare_source_stats(void *a, void *b, void *arg);
static int _free_stats(void *key);
static int _free_source_stats(void *key);
94
static void _add_event_to_queue(stats_event_t *event, event_queue_t *queue);
95 96
static stats_node_t *_find_node(avl_tree *tree, const char *name);
static stats_source_t *_find_source(avl_tree *tree, const char *source);
Jack Moffitt's avatar
Jack Moffitt committed
97
static void _free_event(stats_event_t *event);
98
static stats_event_t *_get_event_from_queue(event_queue_t *queue);
99
static void __add_metadata(xmlNodePtr node, const char *tag);
Jack Moffitt's avatar
Jack Moffitt committed
100

101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124

/* simple helper function for creating an event */
static stats_event_t *build_event (const char *source, const char *name, const char *value)
{
    stats_event_t *event;

    event = (stats_event_t *)calloc(1, sizeof(stats_event_t));
    if (event)
    {
        if (source)
            event->source = (char *)strdup(source);
        if (name)
            event->name = (char *)strdup(name);
        if (value)
            event->value = (char *)strdup(value);
        else
            event->action = STATS_EVENT_REMOVE;
    }
    return event;
}

static void queue_global_event (stats_event_t *event)
{
    thread_mutex_lock(&_global_event_mutex);
125
    _add_event_to_queue (event, &_global_event_queue);
126 127 128
    thread_mutex_unlock(&_global_event_mutex);
}

129
void stats_initialize(void)
Jack Moffitt's avatar
Jack Moffitt committed
130
{
131
    _event_listeners = NULL;
132

133 134 135
    /* set up global struct */
    _stats.global_tree = avl_tree_new(_compare_stats, NULL);
    _stats.source_tree = avl_tree_new(_compare_source_stats, NULL);
Jack Moffitt's avatar
Jack Moffitt committed
136

137 138
    /* set up global mutex */
    thread_mutex_create(&_stats_mutex);
Jack Moffitt's avatar
Jack Moffitt committed
139

140
    /* set up stats queues */
141
    event_queue_init(&_global_event_queue);
142
    thread_mutex_create(&_global_event_mutex);
Jack Moffitt's avatar
Jack Moffitt committed
143

144 145 146
    /* fire off the stats thread */
    _stats_running = 1;
    _stats_thread_id = thread_create("Stats Thread", _stats_thread, NULL, THREAD_ATTACHED);
Jack Moffitt's avatar
Jack Moffitt committed
147 148
}

149
void stats_shutdown(void)
Jack Moffitt's avatar
Jack Moffitt committed
150
{
151
    int n;
Jack Moffitt's avatar
Jack Moffitt committed
152

153
    if (!_stats_running) /* We can't shutdown if we're not running. */
154 155
        return;

156
    /* wait for thread to exit */
157
    thread_mutex_lock(&_stats_mutex);
158
    _stats_running = 0;
159
    thread_mutex_unlock(&_stats_mutex);
160
    thread_join(_stats_thread_id);
Jack Moffitt's avatar
Jack Moffitt committed
161

162 163 164 165 166 167 168
    /* wait for other threads to shut down */
    do {
        thread_sleep(300000);
        thread_mutex_lock(&_stats_mutex);
        n = _stats_threads;
        thread_mutex_unlock(&_stats_mutex);
    } while (n > 0);
169
    ICECAST_LOG_INFO("stats thread finished");
170

171
    /* free the queues */
Jack Moffitt's avatar
Jack Moffitt committed
172

173 174
    /* destroy the queue mutexes */
    thread_mutex_destroy(&_global_event_mutex);
Jack Moffitt's avatar
Jack Moffitt committed
175

176 177 178
    thread_mutex_destroy(&_stats_mutex);
    avl_tree_free(_stats.source_tree, _free_source_stats);
    avl_tree_free(_stats.global_tree, _free_stats);
179

180 181 182 183
    while (1)
    {
        stats_event_t *event = _get_event_from_queue (&_global_event_queue);
        if (event == NULL) break;
184 185 186 187
        if(event->source)
            free(event->source);
        if(event->value)
            free(event->value);
188 189
        if(event->name)
            free(event->name);
190 191
        free(event);
    }
Jack Moffitt's avatar
Jack Moffitt committed
192 193
}

194
stats_t *stats_get_stats(void)
Jack Moffitt's avatar
Jack Moffitt committed
195
{
196
    /* lock global stats
197

198
     copy stats
Jack Moffitt's avatar
Jack Moffitt committed
199

200
     unlock global stats
Jack Moffitt's avatar
Jack Moffitt committed
201

202
     return copied stats */
Jack Moffitt's avatar
Jack Moffitt committed
203

204
    return NULL;
Jack Moffitt's avatar
Jack Moffitt committed
205 206
}

207 208
/* simple name=tag stat create/update */
void stats_event(const char *source, const char *name, const char *value)
Jack Moffitt's avatar
Jack Moffitt committed
209
{
210 211
    stats_event_t *event;

212 213
    if (value && xmlCheckUTF8 ((unsigned char *)value) == 0)
    {
214
        ICECAST_LOG_WARN("seen non-UTF8 data, probably incorrect metadata (%s, %s)", name, value);
215 216
        return;
    }
217
    event = build_event(source, name, value);
218
    if (event)
219
        queue_global_event(event);
Jack Moffitt's avatar
Jack Moffitt committed
220 221
}

222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242

/* wrapper for stats_event, this takes a charset to convert from */
void stats_event_conv(const char *mount, const char *name, const char *value, const char *charset)
{
    const char *metadata = value;
    xmlBufferPtr conv = xmlBufferCreate ();

    if (charset)
    {
        xmlCharEncodingHandlerPtr handle = xmlFindCharEncodingHandler (charset);

        if (handle)
        {
            xmlBufferPtr raw = xmlBufferCreate ();
            xmlBufferAdd (raw, (const xmlChar *)value, strlen (value));
            if (xmlCharEncInFunc (handle, conv, raw) > 0)
                metadata = (char *)xmlBufferContent (conv);
            xmlBufferFree (raw);
            xmlCharEncCloseFunc (handle);
        }
        else
243
            ICECAST_LOG_WARN("No charset found for \"%s\"", charset);
244 245 246 247 248 249
    }

    stats_event (mount, name, metadata);
    xmlBufferFree (conv);
}

250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
/* make stat hidden (non-zero). name can be NULL if it applies to a whole
 * source stats tree. */
void stats_event_hidden (const char *source, const char *name, int hidden)
{
    stats_event_t *event;
    const char *str = NULL;

    if (hidden)
        str = "";
    event = build_event (source, name, str);
    if (event)
    {
        event->action = STATS_EVENT_HIDDEN;
        queue_global_event (event);
    }
}
266 267 268

/* printf style formatting for stat create/update */
void stats_event_args(const char *source, char *name, char *format, ...)
Jack Moffitt's avatar
Jack Moffitt committed
269
{
270 271
    char buf[1024];
    va_list val;
272 273 274 275
    int ret;

    if (name == NULL)
        return;
276
    va_start(val, format);
277
    ret = vsnprintf(buf, sizeof(buf), format, val);
278
    va_end(val);
Jack Moffitt's avatar
Jack Moffitt committed
279

280 281
    if (ret < 0 || (unsigned int)ret >= sizeof (buf))
    {
282
        ICECAST_LOG_WARN("problem with formatting %s stat %s",
283 284 285
                source==NULL ? "global" : source, name);
        return;
    }
286
    stats_event(source, name, buf);
Jack Moffitt's avatar
Jack Moffitt committed
287 288
}

289
static char *_get_stats(const char *source, const char *name)
Jack Moffitt's avatar
Jack Moffitt committed
290
{
291 292 293
    stats_node_t *stats = NULL;
    stats_source_t *src = NULL;
    char *value = NULL;
Jack Moffitt's avatar
Jack Moffitt committed
294

295
    thread_mutex_lock(&_stats_mutex);
Jack Moffitt's avatar
Jack Moffitt committed
296

297 298 299 300 301 302 303 304
    if (source == NULL) {
        stats = _find_node(_stats.global_tree, name);
    } else {
        src = _find_source(_stats.source_tree, source);
        if (src) {
            stats = _find_node(src->stats_tree, name);
        }
    }
Jack Moffitt's avatar
Jack Moffitt committed
305

306
    if (stats) value = (char *)strdup(stats->value);
Jack Moffitt's avatar
Jack Moffitt committed
307

308
    thread_mutex_unlock(&_stats_mutex);
Jack Moffitt's avatar
Jack Moffitt committed
309

310
    return value;
Jack Moffitt's avatar
Jack Moffitt committed
311 312
}

313
char *stats_get_value(const char *source, const char *name)
314
{
315
    return(_get_stats(source, name));
316
}
317 318 319

/* increase the value in the provided stat by 1 */
void stats_event_inc(const char *source, const char *name)
Jack Moffitt's avatar
Jack Moffitt committed
320
{
321
    stats_event_t *event = build_event (source, name, NULL);
322
    /* ICECAST_LOG_DEBUG("%s on %s", name, source==NULL?"global":source); */
323 324 325 326
    if (event)
    {
        event->action = STATS_EVENT_INC;
        queue_global_event (event);
327
    }
Jack Moffitt's avatar
Jack Moffitt committed
328 329
}

330
void stats_event_add(const char *source, const char *name, unsigned long value)
Jack Moffitt's avatar
Jack Moffitt committed
331
{
332
    stats_event_t *event = build_event (source, name, NULL);
333
    /* ICECAST_LOG_DEBUG("%s on %s", name, source==NULL?"global":source); */
334 335 336
    if (event)
    {
        event->value = malloc (16);
337
        snprintf(event->value, 16, "%lu", value);
338 339
        event->action = STATS_EVENT_ADD;
        queue_global_event (event);
340
    }
Jack Moffitt's avatar
Jack Moffitt committed
341 342
}

Karl Heyes's avatar
Karl Heyes committed
343 344 345 346 347 348
void stats_event_sub(const char *source, const char *name, unsigned long value)
{
    stats_event_t *event = build_event (source, name, NULL);
    if (event)
    {
        event->value = malloc (16);
349
        snprintf(event->value, 16, "%lu", value);
Karl Heyes's avatar
Karl Heyes committed
350 351 352 353 354
        event->action = STATS_EVENT_SUB;
        queue_global_event (event);
    }
}

355 356
/* decrease the value in the provided stat by 1 */
void stats_event_dec(const char *source, const char *name)
Jack Moffitt's avatar
Jack Moffitt committed
357
{
358
    /* ICECAST_LOG_DEBUG("%s on %s", name, source==NULL?"global":source); */
359 360 361 362 363
    stats_event_t *event = build_event (source, name, NULL);
    if (event)
    {
        event->action = STATS_EVENT_DEC;
        queue_global_event (event);
364
    }
Jack Moffitt's avatar
Jack Moffitt committed
365 366 367 368 369
}

/* note: you must call this function only when you have exclusive access
** to the avl_tree
*/
370
static stats_node_t *_find_node(avl_tree *stats_tree, const char *name)
Jack Moffitt's avatar
Jack Moffitt committed
371
{
372 373 374 375 376 377
    stats_node_t *stats;
    avl_node *node;
    int cmp;

    /* get the root node */
    node = stats_tree->root->right;
378

379
    while (node) {
380
        stats = (stats_node_t *) node->key;
381
        cmp = strcmp(name, stats->name);
382
        if (cmp < 0)
383 384 385 386 387 388
            node = node->left;
        else if (cmp > 0)
            node = node->right;
        else
            return stats;
    }
389

390 391
    /* didn't find it */
    return NULL;
Jack Moffitt's avatar
Jack Moffitt committed
392 393 394 395 396
}

/* note: you must call this function only when you have exclusive access
** to the avl_tree
*/
397
static stats_source_t *_find_source(avl_tree *source_tree, const char *source)
Jack Moffitt's avatar
Jack Moffitt committed
398
{
399 400 401 402 403 404 405
    stats_source_t *stats;
    avl_node *node;
    int cmp;

    /* get the root node */
    node = source_tree->root->right;
    while (node) {
406
        stats = (stats_source_t *) node->key;
407 408 409 410 411 412 413 414
        cmp = strcmp(source, stats->source);
        if (cmp < 0)
            node = node->left;
        else if (cmp > 0)
            node = node->right;
        else
            return stats;
    }
Jack Moffitt's avatar
Jack Moffitt committed
415

416 417
    /* didn't find it */
    return NULL;
Jack Moffitt's avatar
Jack Moffitt committed
418 419 420 421
}

static stats_event_t *_copy_event(stats_event_t *event)
{
422
    stats_event_t *copy = (stats_event_t *)calloc(1, sizeof(stats_event_t));
423
    if (event->source)
424 425 426
        copy->source = (char *)strdup(event->source);
    else
        copy->source = NULL;
427 428
    if (event->name)
        copy->name = (char *)strdup(event->name);
429 430 431 432
    if (event->value)
        copy->value = (char *)strdup(event->value);
    else
        copy->value = NULL;
433
    copy->hidden = event->hidden;
434
    copy->next = NULL;
Jack Moffitt's avatar
Jack Moffitt committed
435

436
    return copy;
Jack Moffitt's avatar
Jack Moffitt committed
437 438
}

439 440

/* helper to apply specialised changes to a stats node */
441
static void modify_node_event(stats_node_t *node, stats_event_t *event)
442 443 444
{
    char *str;

445 446 447 448 449 450 451 452
    if (event->action == STATS_EVENT_HIDDEN)
    {
        if (event->value)
            node->hidden = 1;
        else
            node->hidden = 0;
        return;
    }
453 454
    if (event->action != STATS_EVENT_SET)
    {
455
        int64_t value = 0;
456 457 458 459 460 461 462 463 464 465 466 467

        switch (event->action)
        {
            case STATS_EVENT_INC:
                value = atoi (node->value)+1;
                break;
            case STATS_EVENT_DEC:
                value = atoi (node->value)-1;
                break;
            case STATS_EVENT_ADD:
                value = atoi (node->value)+atoi (event->value);
                break;
468 469 470
            case STATS_EVENT_SUB:
                value = atoll (node->value) - atoll (event->value);
                break;
471
            default:
472
                ICECAST_LOG_WARN("unhandled event (%d) for %s", event->action, event->source);
473 474 475
                break;
        }
        str = malloc (16);
476
        snprintf (str, 16, "%" PRId64, value);
477 478
        if (event->value == NULL)
            event->value = strdup (str);
479 480 481 482 483
    }
    else
        str = (char *)strdup (event->value);
    free (node->value);
    node->value = str;
484
    if (event->source)
485
        ICECAST_LOG_DEBUG("update \"%s\" %s (%s)", event->source, node->name, node->value);
486
    else
487
        ICECAST_LOG_DEBUG("update global %s (%s)", node->name, node->value);
488 489 490 491 492 493 494
}


static void process_global_event (stats_event_t *event)
{
    stats_node_t *node;

495
    /* ICECAST_LOG_DEBUG("global event %s %s %d", event->name, event->value, event->action); */
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530
    if (event->action == STATS_EVENT_REMOVE)
    {
        /* we're deleting */
        node = _find_node(_stats.global_tree, event->name);
        if (node != NULL)
            avl_delete(_stats.global_tree, (void *)node, _free_stats);
        return;
    }
    node = _find_node(_stats.global_tree, event->name);
    if (node)
    {
        modify_node_event (node, event);
    }
    else
    {
        /* add node */
        node = (stats_node_t *)calloc(1, sizeof(stats_node_t));
        node->name = (char *)strdup(event->name);
        node->value = (char *)strdup(event->value);

        avl_insert(_stats.global_tree, (void *)node);
    }
}


static void process_source_event (stats_event_t *event)
{
    stats_source_t *snode = _find_source(_stats.source_tree, event->source);
    if (snode == NULL)
    {
        if (event->action == STATS_EVENT_REMOVE)
            return;
        snode = (stats_source_t *)calloc(1,sizeof(stats_source_t));
        if (snode == NULL)
            return;
531
        ICECAST_LOG_DEBUG("new source stat %s", event->source);
532 533
        snode->source = (char *)strdup(event->source);
        snode->stats_tree = avl_tree_new(_compare_stats, NULL);
534 535 536 537
        if (event->action == STATS_EVENT_HIDDEN)
            snode->hidden = 1;
        else
            snode->hidden = 0;
538

539
        avl_insert(_stats.source_tree, (void *) snode);
540 541 542 543 544 545 546 547 548 549 550
    }
    if (event->name)
    {
        stats_node_t *node = _find_node(snode->stats_tree, event->name);
        if (node == NULL)
        {
            if (event->action == STATS_EVENT_REMOVE)
                return;
            /* adding node */
            if (event->value)
            {
551
                ICECAST_LOG_DEBUG("new node %s (%s)", event->name, event->value);
552 553 554
                node = (stats_node_t *)calloc(1,sizeof(stats_node_t));
                node->name = (char *)strdup(event->name);
                node->value = (char *)strdup(event->value);
555
                node->hidden = snode->hidden;
556 557 558 559 560 561 562

                avl_insert(snode->stats_tree, (void *)node);
            }
            return;
        }
        if (event->action == STATS_EVENT_REMOVE)
        {
563
            ICECAST_LOG_DEBUG("delete node %s", event->name);
564 565 566 567 568 569
            avl_delete(snode->stats_tree, (void *)node, _free_stats);
            return;
        }
        modify_node_event (node, event);
        return;
    }
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
    if (event->action == STATS_EVENT_HIDDEN)
    {
        avl_node *node = avl_get_first (snode->stats_tree);

        if (event->value)
            snode->hidden = 1;
        else
            snode->hidden = 0;
        while (node)
        {
            stats_node_t *stats = (stats_node_t*)node->key;
            stats->hidden = snode->hidden;
            node = avl_get_next (node);
        }
        return;
    }
586 587
    if (event->action == STATS_EVENT_REMOVE)
    {
588
        ICECAST_LOG_DEBUG("delete source node %s", event->source);
589 590 591 592
        avl_delete(_stats.source_tree, (void *)snode, _free_source_stats);
    }
}

593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
/* NOTE: implicit %z is added to format string. */
static inline void __format_time(char * buffer, size_t len, const char * format) {
    time_t now = time(NULL);
    struct tm local;
    char tzbuffer[32];
    char timebuffer[128];
#ifdef _WIN32
    struct tm *thetime;
    int time_days, time_hours, time_tz;
    int tempnum1, tempnum2;
    char sign;
#endif

    localtime_r (&now, &local);
#ifndef _WIN32
    strftime (tzbuffer, sizeof(tzbuffer), "%z", &local);
#else
    thetime = gmtime (&now);
    time_days = local.tm_yday - thetime->tm_yday;

    if (time_days < -1) {
        tempnum1 = 24;
    } else {
        tempnum1 = 1;
    }

    if (tempnum1 < time_days) {
        tempnum2 = -24;
    } else {
        tempnum2 = time_days*24;
    }

    time_hours = (tempnum2 + local.tm_hour - thetime->tm_hour);
    time_tz = time_hours * 60 + local.tm_min - thetime->tm_min;

    if (time_tz < 0) {
        sign = '-';
        time_tz = -time_tz;
    } else {
        sign = '+';
    }

    snprintf(tzbuffer, sizeof(tzbuffer), "%c%.2d%.2d", sign, time_tz / 60, time_tz % 60);
#endif
    strftime (timebuffer, sizeof(timebuffer), format, &local);

    snprintf(buffer, len, "%s%s", timebuffer, tzbuffer);
}
641

Karl Heyes's avatar
Karl Heyes committed
642 643 644 645
void stats_event_time (const char *mount, const char *name)
{
    char buffer[100];

646
    __format_time(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S ");
Karl Heyes's avatar
Karl Heyes committed
647 648 649 650
    stats_event (mount, name, buffer);
}


651 652 653 654
void stats_event_time_iso8601 (const char *mount, const char *name)
{
    char buffer[100];

655
    __format_time(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S");
656 657 658 659
    stats_event (mount, name, buffer);
}


660 661
void stats_global (ice_config_t *config)
{
662
    stats_event (NULL, "server_id", config->server_id);
663 664 665 666 667 668
    stats_event (NULL, "host", config->hostname);
    stats_event (NULL, "location", config->location);
    stats_event (NULL, "admin", config->admin);
}


Jack Moffitt's avatar
Jack Moffitt committed
669 670
static void *_stats_thread(void *arg)
{
671 672 673 674
    stats_event_t *event;
    stats_event_t *copy;
    event_listener_t *listener;

675 676
    (void)arg;

Karl Heyes's avatar
Karl Heyes committed
677
    stats_event_time (NULL, "server_start");
678
    stats_event_time_iso8601 (NULL, "server_start_iso8601");
679 680 681 682 683 684

    /* global currently active stats */
    stats_event (NULL, "clients", "0");
    stats_event (NULL, "connections", "0");
    stats_event (NULL, "sources", "0");
    stats_event (NULL, "stats", "0");
Karl Heyes's avatar
Karl Heyes committed
685
    stats_event (NULL, "listeners", "0");
686 687 688 689 690 691 692

    /* global accumulating stats */
    stats_event (NULL, "client_connections", "0");
    stats_event (NULL, "source_client_connections", "0");
    stats_event (NULL, "source_relay_connections", "0");
    stats_event (NULL, "source_total_connections", "0");
    stats_event (NULL, "stats_connections", "0");
693
    stats_event (NULL, "listener_connections", "0");
694

695
    ICECAST_LOG_INFO("stats thread started");
696 697 698 699 700 701 702 703
    while (1) {
        thread_mutex_lock(&_stats_mutex);
        if (!_stats_running) {
            thread_mutex_unlock(&_stats_mutex);
            break;
        }
        thread_mutex_unlock(&_stats_mutex);

704
        thread_mutex_lock(&_global_event_mutex);
705
        if (_global_event_queue.head != NULL) {
706
            /* grab the next event from the queue */
707
            event = _get_event_from_queue (&_global_event_queue);
708 709
            thread_mutex_unlock(&_global_event_mutex);

710 711
            if (event == NULL)
                continue;
712 713 714
            event->next = NULL;

            thread_mutex_lock(&_stats_mutex);
715 716 717 718 719 720

            /* check if we are dealing with a global or source event */
            if (event->source == NULL)
                process_global_event (event);
            else
                process_source_event (event);
721

722 723
            /* now we have an event that's been processed into the running stats */
            /* this event should get copied to event listeners' queues */
724
            listener = (event_listener_t *)_event_listeners;
725 726
            while (listener) {
                copy = _copy_event(event);
727 728 729
                thread_mutex_lock (&listener->mutex);
                _add_event_to_queue (copy, &listener->queue);
                thread_mutex_unlock (&listener->mutex);
730 731 732 733 734 735 736 737

                listener = listener->next;
            }

            /* now we need to destroy the event */
            _free_event(event);

            thread_mutex_unlock(&_stats_mutex);
738
            continue;
739
        }
740 741 742 743
        else
        {
            thread_mutex_unlock(&_global_event_mutex);
        }
744

745
        thread_sleep(300000);
746 747 748
    }

    return NULL;
Jack Moffitt's avatar
Jack Moffitt committed
749 750
}

751
/* you must have the _stats_mutex locked here */
752
static void _unregister_listener(event_listener_t *listener)
753 754 755 756 757
{
    event_listener_t **prev = (event_listener_t **)&_event_listeners,
                     *current = *prev;
    while (current)
    {
758
        if (current == listener)
759 760 761 762 763 764 765 766 767 768
        {
            *prev = current->next;
            break;
        }
        prev = &current->next;
        current = *prev;
    }
}


Jack Moffitt's avatar
Jack Moffitt committed
769 770
static stats_event_t *_make_event_from_node(stats_node_t *node, char *source)
{
771
    stats_event_t *event = (stats_event_t *)malloc(sizeof(stats_event_t));
772

773 774 775 776 777 778
    if (source != NULL)
        event->source = (char *)strdup(source);
    else
        event->source = NULL;
    event->name = (char *)strdup(node->name);
    event->value = (char *)strdup(node->value);
779
    event->hidden = node->hidden;
780
    event->action = STATS_EVENT_SET;
781
    event->next = NULL;
Jack Moffitt's avatar
Jack Moffitt committed
782

783
    return event;
Jack Moffitt's avatar
Jack Moffitt committed
784 785 786
}


787 788 789 790
static void _add_event_to_queue(stats_event_t *event, event_queue_t *queue)
{
    *queue->tail = event;
    queue->tail = (volatile stats_event_t **)&event->next;
Jack Moffitt's avatar
Jack Moffitt committed
791 792 793
}


794 795 796
static stats_event_t *_get_event_from_queue (event_queue_t *queue)
{
    stats_event_t *event = NULL;
Jack Moffitt's avatar
Jack Moffitt committed
797

798 799 800 801 802 803 804
    if (queue && queue->head)
    {
        event = (stats_event_t *)queue->head;
        queue->head = event->next;
        if (queue->head == NULL)
            queue->tail = &queue->head;
    }
Jack Moffitt's avatar
Jack Moffitt committed
805

806
    return event;
Jack Moffitt's avatar
Jack Moffitt committed
807 808
}

809
static int _send_event_to_client(stats_event_t *event, client_t *client)
Jack Moffitt's avatar
Jack Moffitt committed
810
{
811
    int len;
812
    char buf [200];
Jack Moffitt's avatar
Jack Moffitt committed
813

814
    /* send data to the client!!!! */
815
    len = snprintf (buf, sizeof (buf), "EVENT %s %s %s\n",
816 817 818
            (event->source != NULL) ? event->source : "global",
            event->name ? event->name : "null",
            event->value ? event->value : "null");
819
    if (len > 0 && len < (int)sizeof (buf))
820 821 822 823 824 825
    {
        client_send_bytes (client, buf, len);
        if (client->con->error)
            return -1;
    }
    return 0;
Jack Moffitt's avatar
Jack Moffitt committed
826 827
}

828 829 830
static inline void __add_authstack (auth_stack_t *stack, xmlNodePtr parent) {
    xmlNodePtr authentication;
    authentication = xmlNewTextChild(parent, NULL, XMLSTR("authentication"), NULL);
831

832 833 834 835 836 837 838 839 840
    auth_stack_addref(stack);

    while (stack) {
        auth_t *auth = auth_stack_get(stack);
        admin_add_role_to_authentication(auth, authentication);
        auth_release(auth);
        auth_stack_next(&stack);
   }
}
841
static xmlNodePtr _dump_stats_to_doc (xmlNodePtr root, const char *show_mount, int hidden, client_t *client) {
842 843
    avl_node *avlnode;
    xmlNodePtr ret = NULL;
844
    ice_config_t *config;
845

846 847 848 849
    config = config_get_config();
    __add_authstack(config->authstack, root);
    config_release_config();

850
    thread_mutex_lock(&_stats_mutex);
851 852
    /* general stats first */
    avlnode = avl_get_first(_stats.global_tree);
853

854
    while (avlnode) {
855 856 857 858 859 860 861
        stats_node_t *stat = avlnode->key;
        if (stat->hidden <=  hidden)
            xmlNewTextChild (root, NULL, XMLSTR(stat->name), XMLSTR(stat->value));
        avlnode = avl_get_next (avlnode);
    }
    /* now per mount stats */
    avlnode = avl_get_first(_stats.source_tree);
862

863
    while (avlnode) {
864
        stats_source_t *source = (stats_source_t *)avlnode->key;
865

866 867 868
        if (source->hidden <= hidden &&
                (show_mount == NULL || strcmp (show_mount, source->source) == 0))
        {
869
            xmlNodePtr metadata, history;
870
            source_t *source_real;
871
            mount_proxy *mountproxy;
872 873
            int i;

874 875
            avl_node *avlnode2 = avl_get_first (source->stats_tree);
            xmlNodePtr xmlnode = xmlNewTextChild (root, NULL, XMLSTR("source"), NULL);
876

877 878 879 880 881 882
            xmlSetProp (xmlnode, XMLSTR("mount"), XMLSTR(source->source));
            if (ret == NULL)
                ret = xmlnode;
            while (avlnode2)
            {
                stats_node_t *stat = avlnode2->key;
883 884 885 886 887 888 889
                if (client && strcmp(stat->name, "listenurl") == 0) {
                    char buf[512];
                    client_get_baseurl(client, NULL, buf, sizeof(buf), NULL, NULL, NULL, source->source, NULL);
                    xmlNewTextChild (xmlnode, NULL, XMLSTR(stat->name), XMLSTR(buf));
                } else {
                    xmlNewTextChild (xmlnode, NULL, XMLSTR(stat->name), XMLSTR(stat->value));
                }
890 891
                avlnode2 = avl_get_next (avlnode2);
            }
892 893 894 895


            avl_tree_rlock(global.source_tree);
            source_real = source_find_mount_raw(source->source);
896 897 898 899
            history = playlist_render_xspf(source_real->history);
            if (history)
                xmlAddChild(xmlnode, history);
            metadata = xmlNewTextChild(xmlnode, NULL, XMLSTR("metadata"), NULL);
900 901 902 903
            if (source_real->format) {
                for (i = 0; i < source_real->format->vc.comments; i++)
                    __add_metadata(metadata, source_real->format->vc.user_comments[i]);
            }
904
            avl_tree_unlock(global.source_tree);
905 906 907

            config = config_get_config();
            mountproxy = config_find_mount(config, source->source, MOUNT_TYPE_NORMAL);
908
            __add_authstack(mountproxy->authstack, xmlnode);
909
            config_release_config();
910
        }
911
        avlnode = avl_get_next (avlnode);
912 913
    }
    thread_mutex_unlock(&_stats_mutex);
914
    return ret;
Jack Moffitt's avatar
Jack Moffitt committed
915 916
}

917

918
/* factoring out code for stats loops
919
** this function copies all stats to queue, and registers
920 921 922
** the queue for all new events atomically.
** note: mutex must already be created!
*/
923
static void _register_listener (event_listener_t *listener)
Jack Moffitt's avatar
Jack Moffitt committed
924
{
925 926 927 928
    avl_node *node;
    avl_node *node2;
    stats_event_t *event;
    stats_source_t *source;
Jack Moffitt's avatar
Jack Moffitt committed
929

930
    thread_mutex_lock(&_stats_mutex);
Jack Moffitt's avatar
Jack Moffitt committed
931

932
    /* first we fill our queue with the current stats */
933

934 935 936
    /* start with the global stats */
    node = avl_get_first(_stats.global_tree);
    while (node) {
937 938
        event = _make_event_from_node((stats_node_t *) node->key, NULL);
        _add_event_to_queue(event, &listener->queue);
Jack Moffitt's avatar
Jack Moffitt committed
939

940 941
        node = avl_get_next(node);
    }
Jack Moffitt's avatar
Jack Moffitt committed
942

943 944 945 946 947 948 949
    /* now the stats for each source */
    node = avl_get_first(_stats.source_tree);
    while (node) {
        source = (stats_source_t *)node->key;
        node2 = avl_get_first(source->stats_tree);
        while (node2) {
            event = _make_event_from_node((stats_node_t *)node2->key, source->source);
950
            _add_event_to_queue (event, &listener->queue);
951 952 953

            node2 = avl_get_next(node2);
        }
954

955 956
        node = avl_get_next(node);
    }
Jack Moffitt's avatar
Jack Moffitt committed
957

958
    /* now we register to receive future event notices */
959 960
    listener->next = (event_listener_t *)_event_listeners;
    _event_listeners = listener;
Jack Moffitt's avatar
Jack Moffitt committed
961

962
    thread_mutex_unlock(&_stats_mutex);
963 964 965 966
}

void *stats_connection(void *arg)
{
967
    client_t *client = (client_t *)arg;
968
    stats_event_t *event;
969
    event_listener_t listener;
970

971
    ICECAST_LOG_INFO("stats client starting");
972

973
    event_queue_init (&listener.queue);
974 975 976
    /* increment the thread count */
    thread_mutex_lock(&_stats_mutex);
    _stats_threads++;
977
    stats_event_args (NULL, "stats", "%d", _stats_threads);
978 979
    thread_mutex_unlock(&_stats_mutex);

980
    thread_mutex_create (&(listener.mutex));
981

982
    _register_listener (&listener);
983

984 985 986 987 988 989 990 991
    while (1) {
        thread_mutex_lock(&_stats_mutex);
        if (!_stats_running) {
            thread_mutex_unlock(&_stats_mutex);
            break;
        }
        thread_mutex_unlock(&_stats_mutex);

992 993 994
        thread_mutex_lock (&listener.mutex);
        event = _get_event_from_queue (&listener.queue);
        thread_mutex_unlock (&listener.mutex);
995
        if (event != NULL) {
996
            if (_send_event_to_client(event, client) < 0) {
997 998 999 1000 1001 1002
                _free_event(event);
                break;
            }
            _free_event(event);
            continue;
        }
1003
        thread_sleep (500000);
1004 1005 1006
    }

    thread_mutex_lock(&_stats_mutex);
1007
    _unregister_listener (&listener);
1008
    _stats_threads--;
1009
    stats_event_args (NULL, "stats", "%d", _stats_threads);
1010 1011
    thread_mutex_unlock(&_stats_mutex);

1012
    thread_mutex_destroy (&listener.mutex);
1013
    client_destroy (client);
1014
    ICECAST_LOG_INFO("stats client finished");
1015

1016
    return NULL;
Jack Moffitt's avatar
Jack Moffitt committed
1017 1018
}

1019 1020 1021

void stats_callback (client_t *client, void *notused)
{
1022 1023
    (void)notused;

1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
    if (client->con->error)
    {
        client_destroy (client);
        return;
    }
    client_set_queue (client, NULL);
    thread_create("Stats Connection", stats_connection, (void *)client, THREAD_DETACHED);
}


Jack Moffitt's avatar
Jack Moffitt committed
1034
typedef struct _source_xml_tag {
1035 1036
    char *mount;
    xmlNodePtr node;
Jack Moffitt's avatar
Jack Moffitt committed
1037

1038
    struct _source_xml_tag *next;
Jack Moffitt's avatar
Jack Moffitt committed
1039 1040 1041
} source_xml_t;


1042
void stats_transform_xslt(client_t *client)
1043 1044
{
    xmlDocPtr doc;
1045
    char *xslpath = util_get_path_from_normalised_uri(client->uri);
1046
    const char *mount = httpp_get_param(client->parser, "mount");
1047

1048
    doc = stats_get_xml(0, mount, client);
1049

1050
    xslt_transform(doc, xslpath, client, 200, NULL);
1051 1052

    xmlFreeDoc(doc);
1053
    free(xslpath);
1054 1055
}

1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078
static void __add_metadata(xmlNodePtr node, const char *tag) {
    const char *value = strstr(tag, "=");
    char *name = NULL;
    size_t namelen = value - tag + 1;
    size_t i;

    if (!value)
        return;

    name = malloc(namelen);
    if (!name)
        return;

    for (i = 0; i < (namelen - 1); i++)
        name[i] = tolower(tag[i]);

    name[namelen-1] = 0;

    xmlNewTextChild(node, NULL, XMLSTR(name), XMLSTR(value+1));

    free(name);
}

1079
xmlDocPtr stats_get_xml(int show_hidden, const char *show_mount, client_t *client)
Jack Moffitt's avatar
Jack Moffitt committed
1080
{
1081
    xmlDocPtr doc;
1082
    xmlNodePtr node;
1083
    xmlNodePtr modules;
1084
    source_t * source;
1085

1086 1087
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode (doc, NULL, XMLSTR("icestats"), NULL);
1088 1089
    xmlDocSetRootElement(doc, node);

1090 1091 1092
    modules = module_container_get_modulelist_as_xml(global.modulecontainer);
    xmlAddChild(node, modules);

1093
    node = _dump_stats_to_doc(node, show_mount, show_hidden, client);
1094

1095 1096 1097
    if (show_mount && node) {
        avl_tree_rlock(global.source_tree);
        source = source_find_mount_raw(show_mount);
1098
        admin_add_listeners_to_mount(source, node, client->mode);
1099 1100 1101
        avl_tree_unlock(global.source_tree);
    }

1102
    return doc;
Jack Moffitt's avatar
Jack Moffitt committed
1103 1104
}

1105

Jack Moffitt's avatar
Jack Moffitt committed
1106 1107
static int _compare_stats(void *arg, void *a, void *b)
{
1108 1109
    stats_node_t *nodea = (stats_node_t *)a;
    stats_node_t *nodeb = (stats_node_t *)b;
Jack Moffitt's avatar
Jack Moffitt committed
1110

1111 1112
    (void)arg;

1113
    return strcmp(nodea->name, nodeb->name);
Jack Moffitt's avatar
Jack Moffitt committed
1114 1115 1116 1117
}

static int _compare_source_stats(void *arg, void *a, void *b)
{
1118 1119
    stats_source_t *nodea = (stats_source_t *)a;
    stats_source_t *nodeb = (stats_source_t *)b;
Jack Moffitt's avatar
Jack Moffitt committed
1120

1121 1122
    (void)arg;

1123
    return strcmp(nodea->source, nodeb->source);
Jack Moffitt's avatar
Jack Moffitt committed
1124 1125 1126 1127
}

static int _free_stats(void *key)
{
1128 1129 1130 1131
    stats_node_t *node = (stats_node_t *)key;
    free(node->value);
    free(node->name);
    free(node);
1132

1133
    return 1;
Jack Moffitt's avatar
Jack Moffitt committed
1134 1135 1136 1137
}

static int _free_source_stats(void *key)
{
1138 1139 1140
    stats_source_t *node = (stats_source_t *)key;
    avl_tree_free(node->stats_tree, _free_stats);
    free(node->source);
1141
    free(node);
Jack Moffitt's avatar
Jack Moffitt committed
1142

1143
    return 1;
Jack Moffitt's avatar
Jack Moffitt committed
1144 1145 1146 1147
}

static void _free_event(stats_event_t *event)
{
1148 1149 1150 1151
    if (event->source) free(event->source);
    if (event->name) free(event->name);
    if (event->value) free(event->value);
    free(event);
Jack Moffitt's avatar
Jack Moffitt committed
1152
}
1153 1154


1155
refbuf_t *stats_get_streams (void)
1156
{
1157
#define STREAMLIST_BLKSIZE  4096
1158
    avl_node *node;
1159
    unsigned int remaining = STREAMLIST_BLKSIZE;
1160 1161
    refbuf_t *start = refbuf_new (remaining), *cur = start;
    char *buffer = cur->data;
1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172

    /* now the stats for each source */
    thread_mutex_lock (&_stats_mutex);
    node = avl_get_first(_stats.source_tree);
    while (node)
    {
        int ret;
        stats_source_t *source = (stats_source_t *)node->key;

        if (source->hidden == 0)
        {
1173
            if (remaining <= strlen (source->source) + 3)
1174
            {
1175 1176 1177 1178 1179
                cur->len = STREAMLIST_BLKSIZE - remaining;
                cur->next = refbuf_new (STREAMLIST_BLKSIZE);
                remaining = STREAMLIST_BLKSIZE;
                cur = cur->next;
                buffer = cur->data;
1180 1181 1182 1183 1184 1185 1186 1187 1188 1189
            }
            ret = snprintf (buffer, remaining, "%s\r\n", source->source);
            if (ret > 0)
            {
                buffer += ret;
                remaining -= ret;
            }
        }
        node = avl_get_next(node);
    }
1190
    thread_mutex_unlock(&_stats_mutex);
1191 1192
    cur->len = STREAMLIST_BLKSIZE - remaining;
    return start;
1193 1194
}

1195 1196


1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215
/* This removes any source stats from virtual mountpoints, ie mountpoints
 * where no source_t exists. This function requires the global sources lock
 * to be held before calling.
 */
void stats_clear_virtual_mounts (void)
{
    avl_node *snode;

    thread_mutex_lock (&_stats_mutex);
    snode = avl_get_first(_stats.source_tree);
    while (snode)
    {
        stats_source_t *src = (stats_source_t *)snode->key;
        source_t *source = source_find_mount_raw (src->source);

        if (source == NULL)
        {
            /* no source_t is reserved so remove them now */
            snode = avl_get_next (snode);
1216
            ICECAST_LOG_DEBUG("releasing %s stats", src->source);
1217 1218 1219 1220 1221 1222 1223 1224 1225
            avl_delete (_stats.source_tree, src, _free_source_stats);
            continue;
        }

        snode = avl_get_next (snode);
    }
    thread_mutex_unlock (&_stats_mutex);
}