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

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

Marvin Scholz's avatar
Marvin Scholz committed
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);
}


Karl Heyes's avatar
Karl Heyes committed
660
661
void stats_global (ice_config_t *config)
{
662
    stats_event (NULL, "server_id", config->server_id);
Karl Heyes's avatar
Karl Heyes committed
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);