aboutsummaryrefslogtreecommitdiff
path: root/src/service/statistics
diff options
context:
space:
mode:
Diffstat (limited to 'src/service/statistics')
-rw-r--r--src/service/statistics/.gitignore7
-rw-r--r--src/service/statistics/Makefile.am88
-rw-r--r--src/service/statistics/gnunet-service-statistics.c1059
-rw-r--r--src/service/statistics/meson.build35
-rw-r--r--src/service/statistics/statistics.conf.in21
-rw-r--r--src/service/statistics/statistics.h149
-rw-r--r--src/service/statistics/statistics_api.c1342
-rw-r--r--src/service/statistics/test_gnunet_statistics.py.in171
-rw-r--r--src/service/statistics/test_statistics_api.c253
-rw-r--r--src/service/statistics/test_statistics_api_data.conf5
-rw-r--r--src/service/statistics/test_statistics_api_loop.c123
-rw-r--r--src/service/statistics/test_statistics_api_watch.c156
-rw-r--r--src/service/statistics/test_statistics_api_watch_zero_value.c197
13 files changed, 3606 insertions, 0 deletions
diff --git a/src/service/statistics/.gitignore b/src/service/statistics/.gitignore
new file mode 100644
index 000000000..f1f567149
--- /dev/null
+++ b/src/service/statistics/.gitignore
@@ -0,0 +1,7 @@
1gnunet-statistics
2gnunet-service-statistics
3test_gnunet_statistics.py
4test_statistics_api
5test_statistics_api_loop
6test_statistics_api_watch
7test_statistics_api_watch_zero_value
diff --git a/src/service/statistics/Makefile.am b/src/service/statistics/Makefile.am
new file mode 100644
index 000000000..42c010c54
--- /dev/null
+++ b/src/service/statistics/Makefile.am
@@ -0,0 +1,88 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4if USE_COVERAGE
5 AM_CFLAGS = --coverage -O0
6 XLIB = -lgcov
7endif
8
9pkgcfgdir= $(pkgdatadir)/config.d/
10
11libexecdir= $(pkglibdir)/libexec/
12
13pkgcfg_DATA = \
14 statistics.conf
15
16lib_LTLIBRARIES = libgnunetstatistics.la
17
18libgnunetstatistics_la_SOURCES = \
19 statistics_api.c statistics.h
20libgnunetstatistics_la_LIBADD = \
21 $(top_builddir)/src/lib/util/libgnunetutil.la \
22 $(GN_LIBINTL) $(XLIB)
23libgnunetstatistics_la_LDFLAGS = \
24 $(GN_LIB_LDFLAGS) \
25 -version-info 2:0:0
26
27libexec_PROGRAMS = \
28 gnunet-service-statistics
29
30gnunet_service_statistics_SOURCES = \
31 gnunet-service-statistics.c
32gnunet_service_statistics_LDADD = \
33 libgnunetstatistics.la \
34 $(top_builddir)/src/lib/util/libgnunetutil.la \
35 $(GN_LIBINTL)
36
37check_PROGRAMS = \
38 test_statistics_api \
39 test_statistics_api_loop \
40 test_statistics_api_watch \
41 test_statistics_api_watch_zero_value
42
43if ENABLE_TEST_RUN
44AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@};export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME;
45TESTS = $(check_PROGRAMS) $(check_SCRIPTS)
46endif
47
48test_statistics_api_SOURCES = \
49 test_statistics_api.c
50test_statistics_api_LDADD = \
51 libgnunetstatistics.la \
52 $(top_builddir)/src/lib/util/libgnunetutil.la
53
54test_statistics_api_loop_SOURCES = \
55 test_statistics_api_loop.c
56test_statistics_api_loop_LDADD = \
57 libgnunetstatistics.la \
58 $(top_builddir)/src/lib/util/libgnunetutil.la
59
60test_statistics_api_watch_SOURCES = \
61 test_statistics_api_watch.c
62test_statistics_api_watch_LDADD = \
63 libgnunetstatistics.la \
64 $(top_builddir)/src/lib/util/libgnunetutil.la
65
66test_statistics_api_watch_zero_value_SOURCES = \
67 test_statistics_api_watch_zero_value.c
68test_statistics_api_watch_zero_value_LDADD = \
69 libgnunetstatistics.la \
70 $(top_builddir)/src/lib/util/libgnunetutil.la
71
72# Needs to be done with CLI
73#if HAVE_PYTHON
74#check_SCRIPTS = \
75# test_gnunet_statistics.py
76#endif
77#
78#SUFFIXES = .py.in .py
79#.py.in.py:
80# $(AWK) -v bdir="$(bindir)" -v py="$(PYTHON)" -v awkay="$(AWK_BINARY)" -v pfx="$(prefix)" -v prl="$(PERL)" -v sysconfdirectory="$(sysconfdir)" -v pkgdatadirectory="$(pkgdatadir)" -f $(top_srcdir)/scripts/dosubst.awk < $(srcdir)/$< > $@
81# chmod +x $@
82#
83#test_gnunet_statistics.py: test_gnunet_statistics.py.in Makefile
84# $(AWK) -v bdir="$(bindir)" -v py="$(PYTHON)" -v awkay="$(AWK_BINARY)" -v pfx="$(prefix)" -v prl="$(PERL)" -v sysconfdirectory="$(sysconfdir)" -v pkgdatadirectory="$(pkgdatadir)" -f $(top_srcdir)/scripts/dosubst.awk < $(srcdir)/test_gnunet_statistics.py.in > test_gnunet_statistics.py
85# chmod +x test_gnunet_statistics.py
86#
87EXTRA_DIST = \
88 test_statistics_api_data.conf
diff --git a/src/service/statistics/gnunet-service-statistics.c b/src/service/statistics/gnunet-service-statistics.c
new file mode 100644
index 000000000..a6c897a79
--- /dev/null
+++ b/src/service/statistics/gnunet-service-statistics.c
@@ -0,0 +1,1059 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2009, 2010, 2012, 2014, 2016 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file statistics/gnunet-service-statistics.c
23 * @brief program that tracks statistics
24 * @author Christian Grothoff
25 */
26#include "platform.h"
27#include "gnunet_util_lib.h"
28#include "gnunet_protocols.h"
29#include "gnunet_statistics_service.h"
30#include "gnunet_time_lib.h"
31#include "statistics.h"
32
33/**
34 * Watch entry.
35 */
36struct WatchEntry
37{
38 /**
39 * Watch entries are kept in a linked list.
40 */
41 struct WatchEntry *next;
42
43 /**
44 * Watch entries are kept in a linked list.
45 */
46 struct WatchEntry *prev;
47
48 /**
49 * For which client is this watch entry?
50 */
51 struct ClientEntry *ce;
52
53 /**
54 * Last value we communicated to the client for this watch entry.
55 */
56 uint64_t last_value;
57
58 /**
59 * Unique watch number for this client and this watched value.
60 */
61 uint32_t wid;
62
63 /**
64 * Is last_value valid
65 * #GNUNET_NO : last_value is n/a, #GNUNET_YES: last_value is valid
66 */
67 int last_value_set;
68};
69
70
71/**
72 * We keep the statistics organized by subsystem for faster
73 * lookup during SET operations.
74 */
75struct SubsystemEntry;
76
77
78/**
79 * Entry in the statistics list.
80 */
81struct StatsEntry
82{
83 /**
84 * This is a linked list.
85 */
86 struct StatsEntry *next;
87
88 /**
89 * This is a linked list.
90 */
91 struct StatsEntry *prev;
92
93 /**
94 * Subsystem this entry belongs to.
95 */
96 struct SubsystemEntry *subsystem;
97
98 /**
99 * Name for the value stored by this entry, allocated at the end of
100 * this struct.
101 */
102 const char *name;
103
104 /**
105 * Watch context for changes to this value, or NULL for none.
106 */
107 struct WatchEntry *we_head;
108
109 /**
110 * Watch context for changes to this value, or NULL for none.
111 */
112 struct WatchEntry *we_tail;
113
114 /**
115 * Our value.
116 */
117 uint64_t value;
118
119 /**
120 * Unique ID.
121 */
122 uint32_t uid;
123
124 /**
125 * Is this value persistent?
126 */
127 int persistent;
128
129 /**
130 * Is this value set?
131 * #GNUNET_NO: value is n/a, #GNUNET_YES: value is valid
132 */
133 int set;
134};
135
136
137/**
138 * We keep the statistics organized by subsystem for faster
139 * lookup during SET operations.
140 */
141struct SubsystemEntry
142{
143 /**
144 * Subsystems are kept in a DLL.
145 */
146 struct SubsystemEntry *next;
147
148 /**
149 * Subsystems are kept in a DLL.
150 */
151 struct SubsystemEntry *prev;
152
153 /**
154 * Head of list of values kept for this subsystem.
155 */
156 struct StatsEntry *stat_head;
157
158 /**
159 * Tail of list of values kept for this subsystem.
160 */
161 struct StatsEntry *stat_tail;
162
163 /**
164 * Name of the subsystem this entry is for, allocated at
165 * the end of this struct, do not free().
166 */
167 const char *service;
168};
169
170
171/**
172 * Client entry.
173 */
174struct ClientEntry
175{
176 /**
177 * Corresponding server handle.
178 */
179 struct GNUNET_SERVICE_Client *client;
180
181 /**
182 * Corresponding message queue.
183 */
184 struct GNUNET_MQ_Handle *mq;
185
186 /**
187 * Which subsystem is this client writing to (SET/UPDATE)?
188 */
189 struct SubsystemEntry *subsystem;
190
191 /**
192 * Maximum watch ID used by this client so far.
193 */
194 uint32_t max_wid;
195};
196
197
198/**
199 * Our configuration.
200 */
201static const struct GNUNET_CONFIGURATION_Handle *cfg;
202
203/**
204 * Head of linked list of subsystems with active statistics.
205 */
206static struct SubsystemEntry *sub_head;
207
208/**
209 * Tail of linked list of subsystems with active statistics.
210 */
211static struct SubsystemEntry *sub_tail;
212
213/**
214 * Number of connected clients.
215 */
216static unsigned int client_count;
217
218/**
219 * Our notification context.
220 */
221static struct GNUNET_NotificationContext *nc;
222
223/**
224 * Counter used to generate unique values.
225 */
226static uint32_t uidgen;
227
228/**
229 * Set to #GNUNET_YES if we are shutting down as soon as possible.
230 */
231static int in_shutdown;
232
233
234/**
235 * Write persistent statistics to disk.
236 */
237static void
238save ()
239{
240 struct SubsystemEntry *se;
241 struct StatsEntry *pos;
242 char *fn;
243 struct GNUNET_BIO_WriteHandle *wh;
244 uint16_t size;
245 unsigned long long total;
246 size_t nlen;
247 size_t slen;
248 struct GNUNET_STATISTICS_SetMessage *msg;
249
250 if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg,
251 "STATISTICS",
252 "DATABASE",
253 &fn))
254 {
255 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
256 "STATISTICS",
257 "DATABASE");
258 return;
259 }
260 (void) GNUNET_DISK_directory_create_for_file (fn);
261 wh = GNUNET_BIO_write_open_file (fn);
262 total = 0;
263 while (NULL != (se = sub_head))
264 {
265 GNUNET_CONTAINER_DLL_remove (sub_head, sub_tail, se);
266 slen = strlen (se->service) + 1;
267 while (NULL != (pos = se->stat_head))
268 {
269 GNUNET_CONTAINER_DLL_remove (se->stat_head, se->stat_tail, pos);
270 if ((pos->persistent) && (NULL != wh))
271 {
272 nlen = strlen (pos->name) + 1;
273 size = sizeof(struct GNUNET_STATISTICS_SetMessage) + nlen + slen;
274 GNUNET_assert (size < UINT16_MAX);
275 msg = GNUNET_malloc (size);
276
277 msg->header.size = htons ((uint16_t) size);
278 msg->header.type = htons (GNUNET_MESSAGE_TYPE_STATISTICS_SET);
279 GNUNET_assert (nlen + slen ==
280 GNUNET_STRINGS_buffer_fill ((char *) &msg[1],
281 nlen + slen,
282 2,
283 se->service,
284 pos->name));
285 msg->flags =
286 htonl (pos->persistent ? GNUNET_STATISTICS_SETFLAG_PERSISTENT : 0);
287 msg->value = GNUNET_htonll (pos->value);
288 if (GNUNET_OK != GNUNET_BIO_write (wh, "statistics-save-msg", msg,
289 size))
290 {
291 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "write", fn);
292 if (GNUNET_OK != GNUNET_BIO_write_close (wh, NULL))
293 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "close", fn);
294 wh = NULL;
295 }
296 else
297 {
298 total += size;
299 }
300 GNUNET_free (msg);
301 }
302 GNUNET_free (pos);
303 }
304 GNUNET_free (se);
305 }
306 if (NULL != wh)
307 {
308 if (GNUNET_OK != GNUNET_BIO_write_close (wh, NULL))
309 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "close", fn);
310 if (0 == total)
311 GNUNET_break (0 == unlink (fn));
312 else
313 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
314 _ ("Wrote %llu bytes of statistics to `%s'\n"),
315 total,
316 fn);
317 }
318 GNUNET_free (fn);
319}
320
321
322/**
323 * Transmit the given stats value.
324 *
325 * @param ce receiver of the value
326 * @param e value to transmit
327 */
328static void
329transmit (struct ClientEntry *ce, const struct StatsEntry *e)
330{
331 struct GNUNET_MQ_Envelope *env;
332 struct GNUNET_STATISTICS_ReplyMessage *m;
333 size_t size;
334
335 size = strlen (e->subsystem->service) + 1 + strlen (e->name) + 1;
336 GNUNET_assert (size < GNUNET_MAX_MESSAGE_SIZE);
337 env = GNUNET_MQ_msg_extra (m, size, GNUNET_MESSAGE_TYPE_STATISTICS_VALUE);
338 m->uid = htonl (e->uid);
339 if (e->persistent)
340 m->uid |= htonl (GNUNET_STATISTICS_PERSIST_BIT);
341 m->value = GNUNET_htonll (e->value);
342 GNUNET_assert (size == GNUNET_STRINGS_buffer_fill ((char *) &m[1],
343 size,
344 2,
345 e->subsystem->service,
346 e->name));
347 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
348 "Transmitting value for `%s:%s' (%d): %llu\n",
349 e->subsystem->service,
350 e->name,
351 e->persistent,
352 (unsigned long long) e->value);
353 GNUNET_MQ_send (ce->mq, env);
354}
355
356
357/**
358 * Callback called when a client connects to the service.
359 *
360 * @param cls closure for the service
361 * @param c the new client that connected to the service
362 * @param mq the message queue used to send messages to the client
363 * @return @a c
364 */
365static void *
366client_connect_cb (void *cls,
367 struct GNUNET_SERVICE_Client *c,
368 struct GNUNET_MQ_Handle *mq)
369{
370 struct ClientEntry *ce;
371
372 ce = GNUNET_new (struct ClientEntry);
373 ce->client = c;
374 ce->mq = mq;
375 client_count++;
376 GNUNET_notification_context_add (nc, mq);
377 return ce;
378}
379
380
381/**
382 * Check integrity of GET-message.
383 *
384 * @param cls identification of the client
385 * @param message the actual message
386 * @return #GNUNET_OK if @a message is well-formed
387 */
388static int
389check_get (void *cls, const struct GNUNET_MessageHeader *message)
390{
391 const char *service;
392 const char *name;
393 size_t size;
394
395 size = ntohs (message->size) - sizeof(struct GNUNET_MessageHeader);
396 if (size != GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
397 size,
398 2,
399 &service,
400 &name))
401 {
402 GNUNET_break (0);
403 return GNUNET_SYSERR;
404 }
405 return GNUNET_OK;
406}
407
408
409/**
410 * Handle GET-message.
411 *
412 * @param cls identification of the client
413 * @param message the actual message
414 */
415static void
416handle_get (void *cls, const struct GNUNET_MessageHeader *message)
417{
418 struct ClientEntry *ce = cls;
419 struct GNUNET_MQ_Envelope *env;
420 struct GNUNET_MessageHeader *end;
421 const char *service;
422 const char *name;
423 size_t slen;
424 size_t nlen;
425 struct SubsystemEntry *se;
426 struct StatsEntry *pos;
427 size_t size;
428
429 size = ntohs (message->size) - sizeof(struct GNUNET_MessageHeader);
430 GNUNET_assert (size ==
431 GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
432 size,
433 2,
434 &service,
435 &name));
436 slen = strlen (service);
437 nlen = strlen (name);
438 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
439 "Received request for statistics on `%s:%s'\n",
440 slen ? service : "*",
441 nlen ? name : "*");
442 for (se = sub_head; NULL != se; se = se->next)
443 {
444 if (! ((0 == slen) || (0 == strcmp (service, se->service))))
445 continue;
446 for (pos = se->stat_head; NULL != pos; pos = pos->next)
447 {
448 if (! ((0 == nlen) || (0 == strcmp (name, pos->name))))
449 continue;
450 transmit (ce, pos);
451 }
452 }
453 env = GNUNET_MQ_msg (end, GNUNET_MESSAGE_TYPE_STATISTICS_END);
454 GNUNET_MQ_send (ce->mq, env);
455 GNUNET_SERVICE_client_continue (ce->client);
456}
457
458
459/**
460 * Notify all clients listening about a change to a value.
461 *
462 * @param se value that changed
463 */
464static void
465notify_change (struct StatsEntry *se)
466{
467 struct GNUNET_MQ_Envelope *env;
468 struct GNUNET_STATISTICS_WatchValueMessage *wvm;
469 struct WatchEntry *pos;
470
471 for (pos = se->we_head; NULL != pos; pos = pos->next)
472 {
473 if (GNUNET_YES == pos->last_value_set)
474 {
475 if (pos->last_value == se->value)
476 continue;
477 }
478 else
479 {
480 pos->last_value_set = GNUNET_YES;
481 }
482 env = GNUNET_MQ_msg (wvm, GNUNET_MESSAGE_TYPE_STATISTICS_WATCH_VALUE);
483 wvm->flags =
484 htonl (se->persistent ? GNUNET_STATISTICS_SETFLAG_PERSISTENT : 0);
485 wvm->wid = htonl (pos->wid);
486 wvm->reserved = htonl (0);
487 wvm->value = GNUNET_htonll (se->value);
488 GNUNET_MQ_send (pos->ce->mq, env);
489 pos->last_value = se->value;
490 }
491}
492
493
494/**
495 * Find the subsystem entry of the given name for the specified client.
496 *
497 * @param ce client looking for the subsystem, may contain a hint
498 * to find the entry faster, can be NULL
499 * @param service name of the subsystem to look for
500 * @return subsystem entry, never NULL (subsystem entry is created if necessary)
501 */
502static struct SubsystemEntry *
503find_subsystem_entry (struct ClientEntry *ce, const char *service)
504{
505 size_t slen;
506 struct SubsystemEntry *se;
507
508 if (NULL != ce)
509 se = ce->subsystem;
510 else
511 se = NULL;
512 if ((NULL == se) || (0 != strcmp (service, se->service)))
513 {
514 for (se = sub_head; NULL != se; se = se->next)
515 if (0 == strcmp (service, se->service))
516 break;
517 if (NULL != ce)
518 ce->subsystem = se;
519 }
520 if (NULL != se)
521 return se;
522 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
523 "Allocating new subsystem entry `%s'\n",
524 service);
525 slen = strlen (service) + 1;
526 se = GNUNET_malloc (sizeof(struct SubsystemEntry) + slen);
527 GNUNET_memcpy (&se[1], service, slen);
528 se->service = (const char *) &se[1];
529 GNUNET_CONTAINER_DLL_insert (sub_head, sub_tail, se);
530 if (NULL != ce)
531 ce->subsystem = se;
532 return se;
533}
534
535
536/**
537 * Find the statistics entry of the given subsystem.
538 *
539 * @param se subsystem to look in
540 * @param name name of the entry to look for
541 * @return statistics entry, or NULL if not found
542 */
543static struct StatsEntry *
544find_stat_entry (struct SubsystemEntry *se, const char *name)
545{
546 struct StatsEntry *pos;
547
548 for (pos = se->stat_head; NULL != pos; pos = pos->next)
549 if (0 == strcmp (name, pos->name))
550 return pos;
551 return NULL;
552}
553
554
555/**
556 * Check format of SET-message.
557 *
558 * @param cls the `struct ClientEntry`
559 * @param msg the actual message
560 * @return #GNUNET_OK if message is well-formed
561 */
562static int
563check_set (void *cls, const struct GNUNET_STATISTICS_SetMessage *msg)
564{
565 const char *service;
566 const char *name;
567 size_t msize;
568
569 msize = ntohs (msg->header.size) - sizeof(*msg);
570 if (msize != GNUNET_STRINGS_buffer_tokenize ((const char *) &msg[1],
571 msize,
572 2,
573 &service,
574 &name))
575 {
576 GNUNET_break (0);
577 return GNUNET_SYSERR;
578 }
579 return GNUNET_OK;
580}
581
582
583/**
584 * Handle SET-message.
585 *
586 * @param cls the `struct ClientEntry`
587 * @param msg the actual message
588 */
589static void
590handle_set (void *cls, const struct GNUNET_STATISTICS_SetMessage *msg)
591{
592 struct ClientEntry *ce = cls;
593 const char *service;
594 const char *name;
595 size_t nlen;
596 uint16_t msize;
597 uint16_t size;
598 struct SubsystemEntry *se;
599 struct StatsEntry *pos;
600 uint32_t flags;
601 uint64_t value;
602 int64_t delta;
603 int changed;
604 int initial_set;
605
606 msize = ntohs (msg->header.size);
607 size = msize - sizeof(struct GNUNET_STATISTICS_SetMessage);
608 GNUNET_assert (size == GNUNET_STRINGS_buffer_tokenize ((const char *) &msg[1],
609 size,
610 2,
611 &service,
612 &name));
613 se = find_subsystem_entry (ce, service);
614 flags = ntohl (msg->flags);
615 value = GNUNET_ntohll (msg->value);
616 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
617 "Received request to update statistic on `%s:%s' (%u) to/by %llu\n",
618 service,
619 name,
620 (unsigned int) flags,
621 (unsigned long long) value);
622 pos = find_stat_entry (se, name);
623 if (NULL != pos)
624 {
625 initial_set = 0;
626 if (0 == (flags & GNUNET_STATISTICS_SETFLAG_RELATIVE))
627 {
628 changed = (pos->value != value);
629 pos->value = value;
630 }
631 else
632 {
633 delta = (int64_t) value;
634 if ((delta < 0) && (pos->value < -delta))
635 {
636 changed = (0 != pos->value);
637 pos->value = 0;
638 }
639 else
640 {
641 changed = (0 != delta);
642 GNUNET_break ((delta <= 0) || (pos->value + delta > pos->value));
643 pos->value += delta;
644 }
645 }
646 if (GNUNET_NO == pos->set)
647 {
648 pos->set = GNUNET_YES;
649 initial_set = 1;
650 }
651 pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
652 if (pos != se->stat_head)
653 {
654 /* move to front for faster setting next time! */
655 GNUNET_CONTAINER_DLL_remove (se->stat_head, se->stat_tail, pos);
656 GNUNET_CONTAINER_DLL_insert (se->stat_head, se->stat_tail, pos);
657 }
658 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
659 "Statistic `%s:%s' updated to value %llu (%d).\n",
660 service,
661 name,
662 (unsigned long long) pos->value,
663 pos->persistent);
664 if ((changed) || (1 == initial_set))
665 notify_change (pos);
666 GNUNET_SERVICE_client_continue (ce->client);
667 return;
668 }
669 /* not found, create a new entry */
670 nlen = strlen (name) + 1;
671 pos = GNUNET_malloc (sizeof(struct StatsEntry) + nlen);
672 GNUNET_memcpy (&pos[1], name, nlen);
673 pos->name = (const char *) &pos[1];
674 pos->subsystem = se;
675 if ((0 == (flags & GNUNET_STATISTICS_SETFLAG_RELATIVE)) ||
676 (0 < (int64_t) GNUNET_ntohll (msg->value)))
677 {
678 pos->value = GNUNET_ntohll (msg->value);
679 pos->set = GNUNET_YES;
680 }
681 else
682 {
683 pos->set = GNUNET_NO;
684 }
685 pos->uid = uidgen++;
686 pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
687 GNUNET_CONTAINER_DLL_insert (se->stat_head, se->stat_tail, pos);
688 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
689 "New statistic on `%s:%s' with value %llu created.\n",
690 service,
691 name,
692 (unsigned long long) pos->value);
693 if (NULL != ce)
694 GNUNET_SERVICE_client_continue (ce->client);
695}
696
697
698/**
699 * Check integrity of WATCH-message.
700 *
701 * @param cls the `struct ClientEntry *`
702 * @param message the actual message
703 * @return #GNUNET_OK if message is well-formed
704 */
705static int
706check_watch (void *cls, const struct GNUNET_MessageHeader *message)
707{
708 size_t size;
709 const char *service;
710 const char *name;
711
712 size = ntohs (message->size) - sizeof(struct GNUNET_MessageHeader);
713 if (size != GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
714 size,
715 2,
716 &service,
717 &name))
718 {
719 GNUNET_break (0);
720 return GNUNET_SYSERR;
721 }
722 return GNUNET_OK;
723}
724
725
726/**
727 * Handle WATCH-message.
728 *
729 * @param cls the `struct ClientEntry *`
730 * @param message the actual message
731 */
732static void
733handle_watch (void *cls, const struct GNUNET_MessageHeader *message)
734{
735 struct ClientEntry *ce = cls;
736 const char *service;
737 const char *name;
738 uint16_t msize;
739 uint16_t size;
740 struct SubsystemEntry *se;
741 struct StatsEntry *pos;
742 struct WatchEntry *we;
743 size_t nlen;
744
745 if (NULL == nc)
746 {
747 GNUNET_SERVICE_client_drop (ce->client);
748 return;
749 }
750 GNUNET_SERVICE_client_mark_monitor (ce->client);
751 msize = ntohs (message->size);
752 size = msize - sizeof(struct GNUNET_MessageHeader);
753 GNUNET_assert (size ==
754 GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
755 size,
756 2,
757 &service,
758 &name));
759 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
760 "Received request to watch statistic on `%s:%s'\n",
761 service,
762 name);
763 se = find_subsystem_entry (ce, service);
764 pos = find_stat_entry (se, name);
765 if (NULL == pos)
766 {
767 nlen = strlen (name) + 1;
768 pos = GNUNET_malloc (sizeof(struct StatsEntry) + nlen);
769 GNUNET_memcpy (&pos[1], name, nlen);
770 pos->name = (const char *) &pos[1];
771 pos->subsystem = se;
772 GNUNET_CONTAINER_DLL_insert (se->stat_head, se->stat_tail, pos);
773 pos->uid = uidgen++;
774 pos->set = GNUNET_NO;
775 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
776 "New statistic on `%s:%s' with value %llu created.\n",
777 service,
778 name,
779 (unsigned long long) pos->value);
780 }
781 we = GNUNET_new (struct WatchEntry);
782 we->ce = ce;
783 we->last_value_set = GNUNET_NO;
784 we->wid = ce->max_wid++;
785 GNUNET_CONTAINER_DLL_insert (pos->we_head, pos->we_tail, we);
786 if (0 != pos->value)
787 notify_change (pos);
788 GNUNET_SERVICE_client_continue (ce->client);
789}
790
791
792/**
793 * Handle DISCONNECT-message. Sync to disk and send
794 * back a #GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT_CONFIRM
795 * message.
796 *
797 * @param cls the `struct ClientEntry *`
798 * @param message the actual message
799 */
800static void
801handle_disconnect (void *cls, const struct GNUNET_MessageHeader *message)
802{
803 struct ClientEntry *ce = cls;
804 struct GNUNET_MQ_Envelope *env;
805 struct GNUNET_MessageHeader *msg;
806
807 env = GNUNET_MQ_msg (msg, GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT_CONFIRM);
808 GNUNET_MQ_send (ce->mq, env);
809 GNUNET_SERVICE_client_continue (ce->client);
810}
811
812
813/**
814 * Actually perform the shutdown.
815 */
816static void
817do_shutdown ()
818{
819 struct WatchEntry *we;
820 struct StatsEntry *pos;
821 struct SubsystemEntry *se;
822
823 if (NULL == nc)
824 return;
825 save ();
826 GNUNET_notification_context_destroy (nc);
827 nc = NULL;
828 GNUNET_assert (0 == client_count);
829 while (NULL != (se = sub_head))
830 {
831 GNUNET_CONTAINER_DLL_remove (sub_head, sub_tail, se);
832 while (NULL != (pos = se->stat_head))
833 {
834 GNUNET_CONTAINER_DLL_remove (se->stat_head, se->stat_tail, pos);
835 while (NULL != (we = pos->we_head))
836 {
837 GNUNET_break (0);
838 GNUNET_CONTAINER_DLL_remove (pos->we_head, pos->we_tail, we);
839 GNUNET_free (we);
840 }
841 GNUNET_free (pos);
842 }
843 GNUNET_free (se);
844 }
845}
846
847
848/**
849 * Task run during shutdown.
850 *
851 * @param cls unused
852 */
853static void
854shutdown_task (void *cls)
855{
856 in_shutdown = GNUNET_YES;
857 if (0 != client_count)
858 return;
859 do_shutdown ();
860}
861
862
863/**
864 * A client disconnected. Remove all of its data structure entries.
865 *
866 * @param cls closure, NULL
867 * @param client identification of the client
868 * @param app_cls the `struct ClientEntry *`
869 */
870static void
871client_disconnect_cb (void *cls,
872 struct GNUNET_SERVICE_Client *client,
873 void *app_cls)
874{
875 struct ClientEntry *ce = app_cls;
876 struct WatchEntry *we;
877 struct WatchEntry *wen;
878 struct StatsEntry *pos;
879 struct SubsystemEntry *se;
880
881 client_count--;
882 for (se = sub_head; NULL != se; se = se->next)
883 {
884 for (pos = se->stat_head; NULL != pos; pos = pos->next)
885 {
886 wen = pos->we_head;
887 while (NULL != (we = wen))
888 {
889 wen = we->next;
890 if (we->ce != ce)
891 continue;
892 GNUNET_CONTAINER_DLL_remove (pos->we_head, pos->we_tail, we);
893 GNUNET_free (we);
894 }
895 }
896 }
897 GNUNET_free (ce);
898 if ((0 == client_count) && (GNUNET_YES == in_shutdown))
899 do_shutdown ();
900}
901
902
903/**
904 * We've read a `struct GNUNET_STATISTICS_SetMessage *` from
905 * disk. Check that it is well-formed, and if so pass it to
906 * the handler for set messages.
907 *
908 * @param cls NULL
909 * @param message the message found on disk
910 * @return #GNUNET_OK on success,
911 * #GNUNET_NO to stop further processing (no error)
912 * #GNUNET_SYSERR to stop further processing with error
913 */
914static int
915inject_message (void *cls, const struct GNUNET_MessageHeader *message)
916{
917 uint16_t msize = ntohs (message->size);
918 const struct GNUNET_STATISTICS_SetMessage *sm;
919
920 sm = (const struct GNUNET_STATISTICS_SetMessage *) message;
921 if ((sizeof(struct GNUNET_STATISTICS_SetMessage) > msize) ||
922 (GNUNET_OK != check_set (NULL, sm)))
923 {
924 GNUNET_break (0);
925 return GNUNET_SYSERR;
926 }
927 handle_set (NULL, sm);
928 return GNUNET_OK;
929}
930
931
932/**
933 * Load persistent values from disk. Disk format is exactly the same
934 * format that we also use for setting the values over the network.
935 */
936static void
937load ()
938{
939 char *fn;
940 struct GNUNET_BIO_ReadHandle *rh;
941 uint64_t fsize;
942 char *buf;
943 struct GNUNET_MessageStreamTokenizer *mst;
944
945 if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg,
946 "STATISTICS",
947 "DATABASE",
948 &fn))
949 {
950 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
951 "STATISTICS",
952 "DATABASE");
953 return;
954 }
955 if ((GNUNET_OK !=
956 GNUNET_DISK_file_size (fn, &fsize, GNUNET_NO, GNUNET_YES)) ||
957 (0 == fsize))
958 {
959 GNUNET_free (fn);
960 return;
961 }
962 buf = GNUNET_malloc (fsize);
963 rh = GNUNET_BIO_read_open_file (fn);
964 if (! rh)
965 {
966 GNUNET_free (buf);
967 GNUNET_free (fn);
968 return;
969 }
970 if (GNUNET_OK != GNUNET_BIO_read (rh, fn, buf, fsize))
971 {
972 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "read", fn);
973 GNUNET_break (GNUNET_OK == GNUNET_BIO_read_close (rh, NULL));
974 GNUNET_free (buf);
975 GNUNET_free (fn);
976 return;
977 }
978 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
979 _ ("Loading %llu bytes of statistics from `%s'\n"),
980 (unsigned long long) fsize,
981 fn);
982 mst = GNUNET_MST_create (&inject_message, NULL);
983 GNUNET_break (
984 GNUNET_OK ==
985 GNUNET_MST_from_buffer (mst, buf, (size_t) fsize, GNUNET_YES, GNUNET_NO));
986 GNUNET_MST_destroy (mst);
987 GNUNET_free (buf);
988 GNUNET_break (GNUNET_OK == GNUNET_BIO_read_close (rh, NULL));
989 GNUNET_free (fn);
990}
991
992
993/**
994 * Process statistics requests.
995 *
996 * @param cls closure
997 * @param c configuration to use
998 * @param service the initialized service
999 */
1000static void
1001run (void *cls,
1002 const struct GNUNET_CONFIGURATION_Handle *c,
1003 struct GNUNET_SERVICE_Handle *service)
1004{
1005 cfg = c;
1006 nc = GNUNET_notification_context_create (16);
1007 load ();
1008 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
1009}
1010
1011
1012/**
1013 * Define "main" method using service macro.
1014 */
1015GNUNET_SERVICE_MAIN (
1016 "statistics",
1017 GNUNET_SERVICE_OPTION_SOFT_SHUTDOWN,
1018 &run,
1019 &client_connect_cb,
1020 &client_disconnect_cb,
1021 NULL,
1022 GNUNET_MQ_hd_var_size (set,
1023 GNUNET_MESSAGE_TYPE_STATISTICS_SET,
1024 struct GNUNET_STATISTICS_SetMessage,
1025 NULL),
1026 GNUNET_MQ_hd_var_size (get,
1027 GNUNET_MESSAGE_TYPE_STATISTICS_GET,
1028 struct GNUNET_MessageHeader,
1029 NULL),
1030 GNUNET_MQ_hd_var_size (watch,
1031 GNUNET_MESSAGE_TYPE_STATISTICS_WATCH,
1032 struct GNUNET_MessageHeader,
1033 NULL),
1034 GNUNET_MQ_hd_fixed_size (disconnect,
1035 GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT,
1036 struct GNUNET_MessageHeader,
1037 NULL),
1038 GNUNET_MQ_handler_end ());
1039
1040
1041#if defined(__linux__) && defined(__GLIBC__)
1042#include <malloc.h>
1043
1044/**
1045 * MINIMIZE heap size (way below 128k) since this process doesn't need much.
1046 */
1047void __attribute__ ((constructor))
1048GNUNET_STATISTICS_memory_init ()
1049{
1050 mallopt (M_TRIM_THRESHOLD, 4 * 1024);
1051 mallopt (M_TOP_PAD, 1 * 1024);
1052 malloc_trim (0);
1053}
1054
1055
1056#endif
1057
1058
1059/* end of gnunet-service-statistics.c */
diff --git a/src/service/statistics/meson.build b/src/service/statistics/meson.build
new file mode 100644
index 000000000..9c4d8c649
--- /dev/null
+++ b/src/service/statistics/meson.build
@@ -0,0 +1,35 @@
1libgnunetstatistics_src = ['statistics_api.c']
2
3gnunetservicestatistics_src = ['gnunet-service-statistics.c']
4
5configure_file(input : 'statistics.conf.in',
6 output : 'statistics.conf',
7 configuration : cdata,
8 install: true,
9 install_dir: pkgcfgdir)
10
11
12if get_option('monolith')
13 foreach p : libgnunetstatistics_src + gnunetservicestatistics_src
14 gnunet_src += 'statistics/' + p
15 endforeach
16endif
17
18libgnunetstatistics = library('gnunetstatistics',
19 libgnunetstatistics_src,
20 soversion: '2',
21 version: '2.0.0',
22 dependencies: libgnunetutil_dep,
23 include_directories: [incdir, configuration_inc],
24 install: true,
25 install_dir: get_option('libdir'))
26libgnunetstatistics_dep = declare_dependency(link_with : libgnunetstatistics)
27pkg.generate(libgnunetstatistics, url: 'https://www.gnunet.org',
28 description : 'Provides API of GNUnet statistics service')
29
30executable ('gnunet-service-statistics',
31 gnunetservicestatistics_src,
32 dependencies: [libgnunetstatistics_dep, libgnunetutil_dep],
33 include_directories: [incdir, configuration_inc],
34 install: true,
35 install_dir: get_option('libdir') / 'gnunet' / 'libexec')
diff --git a/src/service/statistics/statistics.conf.in b/src/service/statistics/statistics.conf.in
new file mode 100644
index 000000000..36aca538f
--- /dev/null
+++ b/src/service/statistics/statistics.conf.in
@@ -0,0 +1,21 @@
1[statistics]
2START_ON_DEMAND = @START_ON_DEMAND@
3@JAVAPORT@PORT = 2088
4HOSTNAME = localhost
5BINARY = gnunet-service-statistics
6ACCEPT_FROM = 127.0.0.1;
7ACCEPT_FROM6 = ::1;
8UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-statistics.sock
9UNIX_MATCH_UID = NO
10UNIX_MATCH_GID = YES
11DATABASE = $GNUNET_DATA_HOME/statistics.dat
12# DISABLE_SOCKET_FORWARDING = NO
13# USERNAME =
14# MAXBUF =
15# TIMEOUT =
16# DISABLEV6 =
17# BINDTO =
18# REJECT_FROM =
19# REJECT_FROM6 =
20# PREFIX =
21
diff --git a/src/service/statistics/statistics.h b/src/service/statistics/statistics.h
new file mode 100644
index 000000000..6eb75cc99
--- /dev/null
+++ b/src/service/statistics/statistics.h
@@ -0,0 +1,149 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2001-2014 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @author Christian Grothoff
23 * @file statistics/statistics.h
24 */
25#ifndef STATISTICS_H
26#define STATISTICS_H
27
28#include "gnunet_common.h"
29
30
31GNUNET_NETWORK_STRUCT_BEGIN
32
33/**
34 * Statistics message. Contains how long the system is up
35 * and one value.
36 *
37 * The struct is be followed by the service name and
38 * name of the statistic, both 0-terminated.
39 */
40struct GNUNET_STATISTICS_ReplyMessage
41{
42 /**
43 * Type: GNUNET_MESSAGE_TYPE_STATISTICS_VALUE
44 */
45 struct GNUNET_MessageHeader header;
46
47 /**
48 * Unique numerical identifier for the value (will
49 * not change during the same client-session). Highest
50 * bit will be set for persistent values (see
51 * #GNUNET_STATISTICS_PERSIST_BIT).
52 */
53 uint32_t uid GNUNET_PACKED;
54
55 /**
56 * The value.
57 */
58 uint64_t value GNUNET_PACKED;
59};
60
61/**
62 * Flag for the `struct GNUNET_STATISTICS_ReplyMessage` UID only.
63 * Note that other messages use #GNUNET_STATISTICS_SETFLAG_PERSISTENT.
64 */
65#define GNUNET_STATISTICS_PERSIST_BIT ((uint32_t) (1LLU << 31))
66
67/**
68 * The value being set is an absolute change.
69 */
70#define GNUNET_STATISTICS_SETFLAG_ABSOLUTE 0
71
72/**
73 * The value being set is a relative change.
74 */
75#define GNUNET_STATISTICS_SETFLAG_RELATIVE 1
76
77/**
78 * The value being set is to be persistent (note that
79 * this bit can be combined with #GNUNET_STATISTICS_SETFLAG_RELATIVE).
80 * This value must not be used for the `uid` member of
81 * `struct GNUNET_STATISTICS_ReplyMessage`!
82 */
83#define GNUNET_STATISTICS_SETFLAG_PERSISTENT 2
84
85
86/**
87 * Message to set a statistic. Followed
88 * by the subsystem name and the name of
89 * the statistic (each 0-terminated).
90 */
91struct GNUNET_STATISTICS_SetMessage
92{
93 /**
94 * Type: #GNUNET_MESSAGE_TYPE_STATISTICS_SET
95 */
96 struct GNUNET_MessageHeader header;
97
98 /**
99 * 0 for absolute value, 1 for relative value; 2 to make persistent
100 * (see GNUNET_STATISTICS_SETFLAG_*).
101 */
102 uint32_t flags GNUNET_PACKED;
103
104 /**
105 * Value. Note that if this is a relative value, it will
106 * be signed even though the type given here is unsigned.
107 */
108 uint64_t value GNUNET_PACKED;
109};
110
111
112/**
113 * Message transmitted if a watched value changes.
114 */
115struct GNUNET_STATISTICS_WatchValueMessage
116{
117 /**
118 * Type: #GNUNET_MESSAGE_TYPE_STATISTICS_WATCH_VALUE
119 */
120 struct GNUNET_MessageHeader header;
121
122 /**
123 * 0 for absolute value, 1 for relative value; 2 to make persistent
124 * (see GNUNET_STATISTICS_SETFLAG_*).
125 */
126 uint32_t flags GNUNET_PACKED;
127
128 /**
129 * Unique watch identification number (watch
130 * requests are enumerated in the order they
131 * are received, the first request having
132 * a wid of zero).
133 */
134 uint32_t wid GNUNET_PACKED;
135
136 /**
137 * Reserved (always 0).
138 */
139 uint32_t reserved GNUNET_PACKED;
140
141 /**
142 * Value. Note that if this is a relative value, it will
143 * be signed even though the type given here is unsigned.
144 */
145 uint64_t value GNUNET_PACKED;
146};
147GNUNET_NETWORK_STRUCT_END
148
149#endif
diff --git a/src/service/statistics/statistics_api.c b/src/service/statistics/statistics_api.c
new file mode 100644
index 000000000..88f127da8
--- /dev/null
+++ b/src/service/statistics/statistics_api.c
@@ -0,0 +1,1342 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2009, 2010, 2011, 2016 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file statistics/statistics_api.c
23 * @brief API of the statistics service
24 * @author Christian Grothoff
25 */
26#include "platform.h"
27#include "gnunet_util_lib.h"
28#include "gnunet_constants.h"
29#include "gnunet_protocols.h"
30#include "gnunet_statistics_service.h"
31#include "statistics.h"
32
33/**
34 * How long do we wait until a statistics request for setting
35 * a value times out? (The update will be lost if the
36 * service does not react within this timeframe).
37 */
38#define SET_TRANSMIT_TIMEOUT GNUNET_TIME_relative_multiply ( \
39 GNUNET_TIME_UNIT_SECONDS, 2)
40
41#define LOG(kind, ...) GNUNET_log_from (kind, "statistics-api", __VA_ARGS__)
42
43/**
44 * Types of actions.
45 */
46enum ActionType
47{
48 /**
49 * Get a value.
50 */
51 ACTION_GET,
52
53 /**
54 * Set a value.
55 */
56 ACTION_SET,
57
58 /**
59 * Update a value.
60 */
61 ACTION_UPDATE,
62
63 /**
64 * Watch a value.
65 */
66 ACTION_WATCH
67};
68
69
70/**
71 * Entry kept for each value we are watching.
72 */
73struct GNUNET_STATISTICS_WatchEntry
74{
75 /**
76 * What subsystem is this action about? (never NULL)
77 */
78 char *subsystem;
79
80 /**
81 * What value is this action about? (never NULL)
82 */
83 char *name;
84
85 /**
86 * Function to call
87 */
88 GNUNET_STATISTICS_Iterator proc;
89
90 /**
91 * Closure for @e proc
92 */
93 void *proc_cls;
94};
95
96
97/**
98 * Linked list of things we still need to do.
99 */
100struct GNUNET_STATISTICS_GetHandle
101{
102 /**
103 * This is a doubly linked list.
104 */
105 struct GNUNET_STATISTICS_GetHandle *next;
106
107 /**
108 * This is a doubly linked list.
109 */
110 struct GNUNET_STATISTICS_GetHandle *prev;
111
112 /**
113 * Main statistics handle.
114 */
115 struct GNUNET_STATISTICS_Handle *sh;
116
117 /**
118 * What subsystem is this action about? (can be NULL)
119 */
120 char *subsystem;
121
122 /**
123 * What value is this action about? (can be NULL)
124 */
125 char *name;
126
127 /**
128 * Continuation to call once action is complete.
129 */
130 GNUNET_STATISTICS_Callback cont;
131
132 /**
133 * Function to call (for GET actions only).
134 */
135 GNUNET_STATISTICS_Iterator proc;
136
137 /**
138 * Closure for @e proc and @e cont.
139 */
140 void *cls;
141
142 /**
143 * Timeout for this action.
144 */
145 struct GNUNET_TIME_Absolute timeout;
146
147 /**
148 * Associated value.
149 */
150 uint64_t value;
151
152 /**
153 * Flag for SET/UPDATE actions.
154 */
155 int make_persistent;
156
157 /**
158 * Has the current iteration been aborted; for GET actions.
159 */
160 int aborted;
161
162 /**
163 * Is this a #ACTION_GET, #ACTION_SET, #ACTION_UPDATE or #ACTION_WATCH?
164 */
165 enum ActionType type;
166
167 /**
168 * Size of the message that we will be transmitting.
169 */
170 uint16_t msize;
171};
172
173
174/**
175 * Handle for the service.
176 */
177struct GNUNET_STATISTICS_Handle
178{
179 /**
180 * Name of our subsystem.
181 */
182 char *subsystem;
183
184 /**
185 * Configuration to use.
186 */
187 const struct GNUNET_CONFIGURATION_Handle *cfg;
188
189 /**
190 * Message queue to the service.
191 */
192 struct GNUNET_MQ_Handle *mq;
193
194 /**
195 * Head of the linked list of pending actions (first action
196 * to be performed).
197 */
198 struct GNUNET_STATISTICS_GetHandle *action_head;
199
200 /**
201 * Tail of the linked list of actions (for fast append).
202 */
203 struct GNUNET_STATISTICS_GetHandle *action_tail;
204
205 /**
206 * Action we are currently busy with (action request has been
207 * transmitted, we're now receiving the response from the
208 * service).
209 */
210 struct GNUNET_STATISTICS_GetHandle *current;
211
212 /**
213 * Array of watch entries.
214 */
215 struct GNUNET_STATISTICS_WatchEntry **watches;
216
217 /**
218 * Task doing exponential back-off trying to reconnect.
219 */
220 struct GNUNET_SCHEDULER_Task *backoff_task;
221
222 /**
223 * Task for running #do_destroy().
224 */
225 struct GNUNET_SCHEDULER_Task *destroy_task;
226
227 /**
228 * Time for next connect retry.
229 */
230 struct GNUNET_TIME_Relative backoff;
231
232 /**
233 * Maximum heap size observed so far (if available).
234 */
235 uint64_t peak_heap_size;
236
237 /**
238 * Maximum resident set side observed so far (if available).
239 */
240 uint64_t peak_rss;
241
242 /**
243 * Size of the @e watches array.
244 */
245 unsigned int watches_size;
246
247 /**
248 * Should this handle auto-destruct once all actions have
249 * been processed?
250 */
251 int do_destroy;
252
253 /**
254 * Are we currently receiving from the service?
255 */
256 int receiving;
257};
258
259
260/**
261 * Obtain statistics about this process's memory consumption and
262 * report those as well (if they changed).
263 */
264static void
265update_memory_statistics (struct GNUNET_STATISTICS_Handle *h)
266{
267#if ENABLE_HEAP_STATISTICS
268 uint64_t current_heap_size = 0;
269 uint64_t current_rss = 0;
270
271 if (GNUNET_NO != h->do_destroy)
272 return;
273#if HAVE_MALLINFO2
274 {
275 struct mallinfo2 mi;
276
277 mi = mallinfo2 ();
278 current_heap_size = mi.uordblks + mi.fordblks;
279 }
280#endif
281#if HAVE_GETRUSAGE
282 {
283 struct rusage ru;
284
285 if (0 == getrusage (RUSAGE_SELF, &ru))
286 {
287 current_rss = 1024LL * ru.ru_maxrss;
288 }
289 }
290#endif
291 if (current_heap_size > h->peak_heap_size)
292 {
293 h->peak_heap_size = current_heap_size;
294 GNUNET_STATISTICS_set (h,
295 "# peak heap size",
296 current_heap_size,
297 GNUNET_NO);
298 }
299 if (current_rss > h->peak_rss)
300 {
301 h->peak_rss = current_rss;
302 GNUNET_STATISTICS_set (h,
303 "# peak resident set size",
304 current_rss,
305 GNUNET_NO);
306 }
307#endif
308}
309
310
311/**
312 * Reconnect at a later time, respecting back-off.
313 *
314 * @param h statistics handle
315 */
316static void
317reconnect_later (struct GNUNET_STATISTICS_Handle *h);
318
319
320/**
321 * Schedule the next action to be performed.
322 *
323 * @param cls statistics handle to reconnect
324 */
325static void
326schedule_action (void *cls);
327
328
329/**
330 * Transmit request to service that we want to watch
331 * the development of a particular value.
332 *
333 * @param h statistics handle
334 * @param watch watch entry of the value to watch
335 */
336static void
337schedule_watch_request (struct GNUNET_STATISTICS_Handle *h,
338 struct GNUNET_STATISTICS_WatchEntry *watch)
339{
340 struct GNUNET_STATISTICS_GetHandle *ai;
341 size_t slen;
342 size_t nlen;
343 size_t nsize;
344
345 slen = strlen (watch->subsystem) + 1;
346 nlen = strlen (watch->name) + 1;
347 nsize = sizeof(struct GNUNET_MessageHeader) + slen + nlen;
348 if (nsize >= GNUNET_MAX_MESSAGE_SIZE)
349 {
350 GNUNET_break (0);
351 return;
352 }
353 ai = GNUNET_new (struct GNUNET_STATISTICS_GetHandle);
354 ai->sh = h;
355 ai->subsystem = GNUNET_strdup (watch->subsystem);
356 ai->name = GNUNET_strdup (watch->name);
357 ai->timeout = GNUNET_TIME_UNIT_FOREVER_ABS;
358 ai->msize = nsize;
359 ai->type = ACTION_WATCH;
360 ai->proc = watch->proc;
361 ai->cls = watch->proc_cls;
362 GNUNET_CONTAINER_DLL_insert_tail (h->action_head,
363 h->action_tail,
364 ai);
365 schedule_action (h);
366}
367
368
369/**
370 * Free memory associated with the given action item.
371 *
372 * @param gh action item to free
373 */
374static void
375free_action_item (struct GNUNET_STATISTICS_GetHandle *gh)
376{
377 GNUNET_free (gh->subsystem);
378 GNUNET_free (gh->name);
379 GNUNET_free (gh);
380}
381
382
383/**
384 * Disconnect from the statistics service.
385 *
386 * @param h statistics handle to disconnect from
387 */
388static void
389do_disconnect (struct GNUNET_STATISTICS_Handle *h)
390{
391 struct GNUNET_STATISTICS_GetHandle *c;
392
393 h->receiving = GNUNET_NO;
394 if (NULL != (c = h->current))
395 {
396 h->current = NULL;
397 if ((NULL != c->cont) &&
398 (GNUNET_YES != c->aborted))
399 {
400 c->cont (c->cls,
401 GNUNET_SYSERR);
402 c->cont = NULL;
403 }
404 free_action_item (c);
405 }
406 if (NULL != h->mq)
407 {
408 GNUNET_MQ_destroy (h->mq);
409 h->mq = NULL;
410 }
411}
412
413
414/**
415 * Process a #GNUNET_MESSAGE_TYPE_STATISTICS_VALUE message.
416 *
417 * @param cls statistics handle
418 * @param smsg message received from the service, never NULL
419 * @return #GNUNET_OK if the message was well-formed
420 */
421static int
422check_statistics_value (void *cls,
423 const struct GNUNET_STATISTICS_ReplyMessage *smsg)
424{
425 const char *service;
426 const char *name;
427 uint16_t size;
428
429 size = ntohs (smsg->header.size);
430 size -= sizeof(struct GNUNET_STATISTICS_ReplyMessage);
431 if (size !=
432 GNUNET_STRINGS_buffer_tokenize ((const char *) &smsg[1],
433 size,
434 2,
435 &service,
436 &name))
437 {
438 GNUNET_break (0);
439 return GNUNET_SYSERR;
440 }
441 return GNUNET_OK;
442}
443
444
445/**
446 * Process a #GNUNET_MESSAGE_TYPE_STATISTICS_VALUE message.
447 *
448 * @param cls statistics handle
449 * @param smsg message received from the service, never NULL
450 * @return #GNUNET_OK if the message was well-formed
451 */
452static void
453handle_statistics_value (void *cls,
454 const struct GNUNET_STATISTICS_ReplyMessage *smsg)
455{
456 struct GNUNET_STATISTICS_Handle *h = cls;
457 const char *service;
458 const char *name;
459 uint16_t size;
460
461 if (h->current->aborted)
462 return; /* iteration aborted, don't bother */
463
464 size = ntohs (smsg->header.size);
465 size -= sizeof(struct GNUNET_STATISTICS_ReplyMessage);
466 GNUNET_assert (size ==
467 GNUNET_STRINGS_buffer_tokenize ((const char *) &smsg[1],
468 size,
469 2,
470 &service,
471 &name));
472 LOG (GNUNET_ERROR_TYPE_DEBUG,
473 "Received valid statistic on `%s:%s': %llu\n",
474 service, name,
475 (unsigned long long) GNUNET_ntohll (smsg->value));
476 if (GNUNET_OK !=
477 h->current->proc (h->current->cls,
478 service,
479 name,
480 GNUNET_ntohll (smsg->value),
481 (0 !=
482 (ntohl (smsg->uid) & GNUNET_STATISTICS_PERSIST_BIT)) ))
483 {
484 LOG (GNUNET_ERROR_TYPE_DEBUG,
485 "Processing of remaining statistics aborted by client.\n");
486 h->current->aborted = GNUNET_YES;
487 }
488}
489
490
491/**
492 * We have received a watch value from the service. Process it.
493 *
494 * @param cls statistics handle
495 * @param wvm the watch value message
496 */
497static void
498handle_statistics_watch_value (void *cls,
499 const struct
500 GNUNET_STATISTICS_WatchValueMessage *wvm)
501{
502 struct GNUNET_STATISTICS_Handle *h = cls;
503 struct GNUNET_STATISTICS_WatchEntry *w;
504 uint32_t wid;
505
506 GNUNET_break (0 == ntohl (wvm->reserved));
507 wid = ntohl (wvm->wid);
508 if (wid >= h->watches_size)
509 {
510 do_disconnect (h);
511 reconnect_later (h);
512 return;
513 }
514 w = h->watches[wid];
515 if (NULL == w)
516 return;
517 (void) w->proc (w->proc_cls,
518 w->subsystem,
519 w->name,
520 GNUNET_ntohll (wvm->value),
521 0 != (ntohl (wvm->flags) & GNUNET_STATISTICS_PERSIST_BIT));
522}
523
524
525/**
526 * Generic error handler, called with the appropriate error code and
527 * the same closure specified at the creation of the message queue.
528 * Not every message queue implementation supports an error handler.
529 *
530 * @param cls closure with the `struct GNUNET_STATISTICS_Handle *`
531 * @param error error code
532 */
533static void
534mq_error_handler (void *cls,
535 enum GNUNET_MQ_Error error)
536{
537 struct GNUNET_STATISTICS_Handle *h = cls;
538
539 if (GNUNET_NO != h->do_destroy)
540 {
541 h->do_destroy = GNUNET_NO;
542 if (NULL != h->destroy_task)
543 {
544 GNUNET_SCHEDULER_cancel (h->destroy_task);
545 h->destroy_task = NULL;
546 }
547 GNUNET_STATISTICS_destroy (h,
548 GNUNET_NO);
549 return;
550 }
551 do_disconnect (h);
552 reconnect_later (h);
553}
554
555
556/**
557 * Task used to destroy the statistics handle.
558 *
559 * @param cls the `struct GNUNET_STATISTICS_Handle`
560 */
561static void
562do_destroy (void *cls)
563{
564 struct GNUNET_STATISTICS_Handle *h = cls;
565
566 h->destroy_task = NULL;
567 h->do_destroy = GNUNET_NO;
568 LOG (GNUNET_ERROR_TYPE_DEBUG,
569 "Running final destruction\n");
570 GNUNET_STATISTICS_destroy (h,
571 GNUNET_NO);
572}
573
574
575/**
576 * Handle a #GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT_CONFIRM
577 * message. We receive this message at the end of the shutdown when
578 * the service confirms that all data has been written to disk.
579 *
580 * @param cls our `struct GNUNET_STATISTICS_Handle *`
581 * @param msg the message
582 */
583static void
584handle_disconnect_confirm (void *cls,
585 const struct GNUNET_MessageHeader *msg)
586{
587 struct GNUNET_STATISTICS_Handle *h = cls;
588
589 if (GNUNET_SYSERR != h->do_destroy)
590 {
591 /* not in shutdown, why do we get 'TEST'? */
592 GNUNET_break (0);
593 do_disconnect (h);
594 reconnect_later (h);
595 return;
596 }
597 LOG (GNUNET_ERROR_TYPE_DEBUG,
598 "Received DISCONNNECT_CONFIRM message from statistics, can complete disconnect\n");
599 if (NULL != h->destroy_task)
600 GNUNET_SCHEDULER_cancel (h->destroy_task);
601 h->destroy_task = GNUNET_SCHEDULER_add_now (&do_destroy,
602 h);
603}
604
605
606/**
607 * Handle a #GNUNET_MESSAGE_TYPE_STATISTICS_END message. We receive
608 * this message in response to a query to indicate that there are no
609 * further matching results.
610 *
611 * @param cls our `struct GNUNET_STATISTICS_Handle *`
612 * @param msg the message
613 */
614static void
615handle_statistics_end (void *cls,
616 const struct GNUNET_MessageHeader *msg)
617{
618 struct GNUNET_STATISTICS_Handle *h = cls;
619 struct GNUNET_STATISTICS_GetHandle *c;
620
621 LOG (GNUNET_ERROR_TYPE_DEBUG,
622 "Received end of statistics marker\n");
623 if (NULL == (c = h->current))
624 {
625 GNUNET_break (0);
626 do_disconnect (h);
627 reconnect_later (h);
628 return;
629 }
630 h->backoff = GNUNET_TIME_UNIT_MILLISECONDS;
631 h->current = NULL;
632 schedule_action (h);
633 if (NULL != c->cont)
634 {
635 c->cont (c->cls,
636 GNUNET_OK);
637 c->cont = NULL;
638 }
639 free_action_item (c);
640}
641
642
643/**
644 * Try to (re)connect to the statistics service.
645 *
646 * @param h statistics handle to reconnect
647 * @return #GNUNET_YES on success, #GNUNET_NO on failure.
648 */
649static int
650try_connect (struct GNUNET_STATISTICS_Handle *h)
651{
652 struct GNUNET_MQ_MessageHandler handlers[] = {
653 GNUNET_MQ_hd_fixed_size (disconnect_confirm,
654 GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT_CONFIRM,
655 struct GNUNET_MessageHeader,
656 h),
657 GNUNET_MQ_hd_fixed_size (statistics_end,
658 GNUNET_MESSAGE_TYPE_STATISTICS_END,
659 struct GNUNET_MessageHeader,
660 h),
661 GNUNET_MQ_hd_var_size (statistics_value,
662 GNUNET_MESSAGE_TYPE_STATISTICS_VALUE,
663 struct GNUNET_STATISTICS_ReplyMessage,
664 h),
665 GNUNET_MQ_hd_fixed_size (statistics_watch_value,
666 GNUNET_MESSAGE_TYPE_STATISTICS_WATCH_VALUE,
667 struct GNUNET_STATISTICS_WatchValueMessage,
668 h),
669 GNUNET_MQ_handler_end ()
670 };
671 struct GNUNET_STATISTICS_GetHandle *gh;
672 struct GNUNET_STATISTICS_GetHandle *gn;
673
674 if (NULL != h->backoff_task)
675 return GNUNET_NO;
676 if (NULL != h->mq)
677 return GNUNET_YES;
678 h->mq = GNUNET_CLIENT_connect (h->cfg,
679 "statistics",
680 handlers,
681 &mq_error_handler,
682 h);
683 if (NULL == h->mq)
684 {
685 LOG (GNUNET_ERROR_TYPE_DEBUG,
686 "Failed to connect to statistics service!\n");
687 return GNUNET_NO;
688 }
689 gn = h->action_head;
690 while (NULL != (gh = gn))
691 {
692 gn = gh->next;
693 if (gh->type == ACTION_WATCH)
694 {
695 GNUNET_CONTAINER_DLL_remove (h->action_head,
696 h->action_tail,
697 gh);
698 free_action_item (gh);
699 }
700 }
701 for (unsigned int i = 0; i < h->watches_size; i++)
702 if (NULL != h->watches[i])
703 schedule_watch_request (h,
704 h->watches[i]);
705 return GNUNET_YES;
706}
707
708
709/**
710 * We've waited long enough, reconnect now.
711 *
712 * @param cls the `struct GNUNET_STATISTICS_Handle` to reconnect
713 */
714static void
715reconnect_task (void *cls)
716{
717 struct GNUNET_STATISTICS_Handle *h = cls;
718
719 h->backoff_task = NULL;
720 schedule_action (h);
721}
722
723
724/**
725 * Reconnect at a later time, respecting back-off.
726 *
727 * @param h statistics handle
728 */
729static void
730reconnect_later (struct GNUNET_STATISTICS_Handle *h)
731{
732 int loss;
733 struct GNUNET_STATISTICS_GetHandle *gh;
734
735 GNUNET_assert (NULL == h->backoff_task);
736 if (GNUNET_YES == h->do_destroy)
737 {
738 /* So we are shutting down and the service is not reachable.
739 * Chances are that it's down for good and we are not going to connect to
740 * it anymore.
741 * Give up and don't sync the rest of the data.
742 */loss = GNUNET_NO;
743 for (gh = h->action_head; NULL != gh; gh = gh->next)
744 if ((gh->make_persistent) &&
745 (ACTION_SET == gh->type))
746 loss = GNUNET_YES;
747 if (GNUNET_YES == loss)
748 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
749 _ ("Could not save some persistent statistics\n"));
750 if (NULL != h->destroy_task)
751 GNUNET_SCHEDULER_cancel (h->destroy_task);
752 h->destroy_task = GNUNET_SCHEDULER_add_now (&do_destroy,
753 h);
754 return;
755 }
756 h->backoff_task
757 = GNUNET_SCHEDULER_add_delayed (h->backoff,
758 &reconnect_task,
759 h);
760 h->backoff = GNUNET_TIME_STD_BACKOFF (h->backoff);
761}
762
763
764/**
765 * Transmit a GET request (and if successful, start to receive
766 * the response).
767 *
768 * @param handle statistics handle
769 */
770static void
771transmit_get (struct GNUNET_STATISTICS_Handle *handle)
772{
773 struct GNUNET_STATISTICS_GetHandle *c;
774 struct GNUNET_MessageHeader *hdr;
775 struct GNUNET_MQ_Envelope *env;
776 size_t slen1;
777 size_t slen2;
778
779 GNUNET_assert (NULL != (c = handle->current));
780 slen1 = strlen (c->subsystem) + 1;
781 slen2 = strlen (c->name) + 1;
782 env = GNUNET_MQ_msg_extra (hdr,
783 slen1 + slen2,
784 GNUNET_MESSAGE_TYPE_STATISTICS_GET);
785 GNUNET_assert (slen1 + slen2 ==
786 GNUNET_STRINGS_buffer_fill ((char *) &hdr[1],
787 slen1 + slen2,
788 2,
789 c->subsystem,
790 c->name));
791 GNUNET_MQ_notify_sent (env,
792 &schedule_action,
793 handle);
794 GNUNET_MQ_send (handle->mq,
795 env);
796}
797
798
799/**
800 * Transmit a WATCH request (and if successful, start to receive
801 * the response).
802 *
803 * @param handle statistics handle
804 */
805static void
806transmit_watch (struct GNUNET_STATISTICS_Handle *handle)
807{
808 struct GNUNET_MessageHeader *hdr;
809 struct GNUNET_MQ_Envelope *env;
810 size_t slen1;
811 size_t slen2;
812
813 LOG (GNUNET_ERROR_TYPE_DEBUG,
814 "Transmitting watch request for `%s'\n",
815 handle->current->name);
816 slen1 = strlen (handle->current->subsystem) + 1;
817 slen2 = strlen (handle->current->name) + 1;
818 env = GNUNET_MQ_msg_extra (hdr,
819 slen1 + slen2,
820 GNUNET_MESSAGE_TYPE_STATISTICS_WATCH);
821 GNUNET_assert (slen1 + slen2 ==
822 GNUNET_STRINGS_buffer_fill ((char *) &hdr[1],
823 slen1 + slen2,
824 2,
825 handle->current->subsystem,
826 handle->current->name));
827 GNUNET_MQ_notify_sent (env,
828 &schedule_action,
829 handle);
830 GNUNET_MQ_send (handle->mq,
831 env);
832 GNUNET_assert (NULL == handle->current->cont);
833 free_action_item (handle->current);
834 handle->current = NULL;
835 schedule_action (handle);
836}
837
838
839/**
840 * Transmit a SET/UPDATE request.
841 *
842 * @param handle statistics handle
843 */
844static void
845transmit_set (struct GNUNET_STATISTICS_Handle *handle)
846{
847 struct GNUNET_STATISTICS_SetMessage *r;
848 struct GNUNET_MQ_Envelope *env;
849 size_t slen;
850 size_t nlen;
851
852 slen = strlen (handle->current->subsystem) + 1;
853 nlen = strlen (handle->current->name) + 1;
854 env = GNUNET_MQ_msg_extra (r,
855 slen + nlen,
856 GNUNET_MESSAGE_TYPE_STATISTICS_SET);
857 r->flags = 0;
858 r->value = GNUNET_htonll (handle->current->value);
859 if (handle->current->make_persistent)
860 r->flags |= htonl (GNUNET_STATISTICS_SETFLAG_PERSISTENT);
861 if (handle->current->type == ACTION_UPDATE)
862 r->flags |= htonl (GNUNET_STATISTICS_SETFLAG_RELATIVE);
863 GNUNET_assert (slen + nlen ==
864 GNUNET_STRINGS_buffer_fill ((char *) &r[1],
865 slen + nlen,
866 2,
867 handle->current->subsystem,
868 handle->current->name));
869 GNUNET_assert (NULL == handle->current->cont);
870 free_action_item (handle->current);
871 handle->current = NULL;
872 update_memory_statistics (handle);
873 GNUNET_MQ_notify_sent (env,
874 &schedule_action,
875 handle);
876 GNUNET_MQ_send (handle->mq,
877 env);
878}
879
880
881/**
882 * Get handle for the statistics service.
883 *
884 * @param subsystem name of subsystem using the service
885 * @param cfg services configuration in use
886 * @return handle to use
887 */
888struct GNUNET_STATISTICS_Handle *
889GNUNET_STATISTICS_create (const char *subsystem,
890 const struct GNUNET_CONFIGURATION_Handle *cfg)
891{
892 struct GNUNET_STATISTICS_Handle *h;
893
894 if (GNUNET_YES ==
895 GNUNET_CONFIGURATION_get_value_yesno (cfg,
896 "statistics",
897 "DISABLE"))
898 return NULL;
899 h = GNUNET_new (struct GNUNET_STATISTICS_Handle);
900 h->cfg = cfg;
901 h->subsystem = GNUNET_strdup (subsystem);
902 h->backoff = GNUNET_TIME_UNIT_MILLISECONDS;
903 return h;
904}
905
906
907/**
908 * Destroy a handle (free all state associated with
909 * it).
910 *
911 * @param h statistics handle to destroy
912 * @param sync_first set to #GNUNET_YES if pending SET requests should
913 * be completed
914 */
915void
916GNUNET_STATISTICS_destroy (struct GNUNET_STATISTICS_Handle *h,
917 int sync_first)
918{
919 struct GNUNET_STATISTICS_GetHandle *pos;
920 struct GNUNET_STATISTICS_GetHandle *next;
921
922 if (NULL == h)
923 return;
924 GNUNET_assert (GNUNET_NO == h->do_destroy); /* Don't call twice. */
925 if ((sync_first) &&
926 (NULL != h->mq) &&
927 (0 != GNUNET_MQ_get_length (h->mq)))
928 {
929 if ((NULL != h->current) &&
930 (ACTION_GET == h->current->type))
931 h->current->aborted = GNUNET_YES;
932 next = h->action_head;
933 while (NULL != (pos = next))
934 {
935 next = pos->next;
936 if ((ACTION_GET == pos->type) ||
937 (ACTION_WATCH == pos->type))
938 {
939 GNUNET_CONTAINER_DLL_remove (h->action_head,
940 h->action_tail,
941 pos);
942 free_action_item (pos);
943 }
944 }
945 h->do_destroy = GNUNET_YES;
946 schedule_action (h);
947 GNUNET_assert (NULL == h->destroy_task);
948 h->destroy_task
949 = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (h->backoff,
950 5),
951 &do_destroy,
952 h);
953 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
954 "Deferring destruction\n");
955 return; /* do not finish destruction just yet */
956 }
957 /* do clean up all */
958 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
959 "Cleaning all up\n");
960 while (NULL != (pos = h->action_head))
961 {
962 GNUNET_CONTAINER_DLL_remove (h->action_head,
963 h->action_tail,
964 pos);
965 free_action_item (pos);
966 }
967 do_disconnect (h);
968 if (NULL != h->backoff_task)
969 {
970 GNUNET_SCHEDULER_cancel (h->backoff_task);
971 h->backoff_task = NULL;
972 }
973 if (NULL != h->destroy_task)
974 {
975 GNUNET_break (0);
976 GNUNET_SCHEDULER_cancel (h->destroy_task);
977 h->destroy_task = NULL;
978 }
979 for (unsigned int i = 0; i < h->watches_size; i++)
980 {
981 if (NULL == h->watches[i])
982 continue;
983 GNUNET_free (h->watches[i]->subsystem);
984 GNUNET_free (h->watches[i]->name);
985 GNUNET_free (h->watches[i]);
986 }
987 GNUNET_array_grow (h->watches,
988 h->watches_size,
989 0);
990 GNUNET_free (h->subsystem);
991 GNUNET_free (h);
992}
993
994
995/**
996 * Schedule the next action to be performed.
997 *
998 * @param cls statistics handle
999 */
1000static void
1001schedule_action (void *cls)
1002{
1003 struct GNUNET_STATISTICS_Handle *h = cls;
1004
1005 if (NULL != h->backoff_task)
1006 return; /* action already pending */
1007 if (GNUNET_YES != try_connect (h))
1008 {
1009 reconnect_later (h);
1010 return;
1011 }
1012 if (0 < GNUNET_MQ_get_length (h->mq))
1013 return; /* Wait for queue to be reduced more */
1014 /* schedule next action */
1015 while (NULL == h->current)
1016 {
1017 h->current = h->action_head;
1018 if (NULL == h->current)
1019 {
1020 struct GNUNET_MessageHeader *hdr;
1021 struct GNUNET_MQ_Envelope *env;
1022
1023 if (GNUNET_YES != h->do_destroy)
1024 return; /* nothing to do */
1025 /* let service know that we're done */
1026 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1027 "Notifying service that we are done\n");
1028 h->do_destroy = GNUNET_SYSERR; /* in 'TEST' mode */
1029 env = GNUNET_MQ_msg (hdr,
1030 GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT);
1031 GNUNET_MQ_notify_sent (env,
1032 &schedule_action,
1033 h);
1034 GNUNET_MQ_send (h->mq,
1035 env);
1036 return;
1037 }
1038 GNUNET_CONTAINER_DLL_remove (h->action_head,
1039 h->action_tail,
1040 h->current);
1041 switch (h->current->type)
1042 {
1043 case ACTION_GET:
1044 transmit_get (h);
1045 break;
1046
1047 case ACTION_SET:
1048 case ACTION_UPDATE:
1049 transmit_set (h);
1050 break;
1051
1052 case ACTION_WATCH:
1053 transmit_watch (h);
1054 break;
1055
1056 default:
1057 GNUNET_assert (0);
1058 break;
1059 }
1060 }
1061}
1062
1063
1064struct GNUNET_STATISTICS_GetHandle *
1065GNUNET_STATISTICS_get (struct GNUNET_STATISTICS_Handle *handle,
1066 const char *subsystem,
1067 const char *name,
1068 GNUNET_STATISTICS_Callback cont,
1069 GNUNET_STATISTICS_Iterator proc,
1070 void *cls)
1071{
1072 size_t slen1;
1073 size_t slen2;
1074 struct GNUNET_STATISTICS_GetHandle *ai;
1075
1076 if (NULL == handle)
1077 return NULL;
1078 GNUNET_assert (NULL != proc);
1079 GNUNET_assert (GNUNET_NO == handle->do_destroy);
1080 if (NULL == subsystem)
1081 subsystem = "";
1082 if (NULL == name)
1083 name = "";
1084 slen1 = strlen (subsystem) + 1;
1085 slen2 = strlen (name) + 1;
1086 GNUNET_assert (slen1 + slen2 + sizeof(struct GNUNET_MessageHeader) <
1087 GNUNET_MAX_MESSAGE_SIZE);
1088 ai = GNUNET_new (struct GNUNET_STATISTICS_GetHandle);
1089 ai->sh = handle;
1090 ai->subsystem = GNUNET_strdup (subsystem);
1091 ai->name = GNUNET_strdup (name);
1092 ai->cont = cont;
1093 ai->proc = proc;
1094 ai->cls = cls;
1095 ai->type = ACTION_GET;
1096 ai->msize = slen1 + slen2 + sizeof(struct GNUNET_MessageHeader);
1097 GNUNET_CONTAINER_DLL_insert_tail (handle->action_head,
1098 handle->action_tail,
1099 ai);
1100 schedule_action (handle);
1101 return ai;
1102}
1103
1104
1105void
1106GNUNET_STATISTICS_get_cancel (struct GNUNET_STATISTICS_GetHandle *gh)
1107{
1108 if (NULL == gh)
1109 return;
1110 gh->cont = NULL;
1111 if (gh->sh->current == gh)
1112 {
1113 gh->aborted = GNUNET_YES;
1114 return;
1115 }
1116 GNUNET_CONTAINER_DLL_remove (gh->sh->action_head,
1117 gh->sh->action_tail,
1118 gh);
1119 GNUNET_free (gh->name);
1120 GNUNET_free (gh->subsystem);
1121 GNUNET_free (gh);
1122}
1123
1124
1125/**
1126 * Watch statistics from the peer (be notified whenever they change).
1127 *
1128 * @param handle identification of the statistics service
1129 * @param subsystem limit to the specified subsystem, never NULL
1130 * @param name name of the statistic value, never NULL
1131 * @param proc function to call on each value
1132 * @param proc_cls closure for @a proc
1133 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
1134 */
1135int
1136GNUNET_STATISTICS_watch (struct GNUNET_STATISTICS_Handle *handle,
1137 const char *subsystem,
1138 const char *name,
1139 GNUNET_STATISTICS_Iterator proc,
1140 void *proc_cls)
1141{
1142 struct GNUNET_STATISTICS_WatchEntry *w;
1143
1144 if (NULL == handle)
1145 return GNUNET_SYSERR;
1146 w = GNUNET_new (struct GNUNET_STATISTICS_WatchEntry);
1147 w->subsystem = GNUNET_strdup (subsystem);
1148 w->name = GNUNET_strdup (name);
1149 w->proc = proc;
1150 w->proc_cls = proc_cls;
1151 GNUNET_array_append (handle->watches,
1152 handle->watches_size,
1153 w);
1154 schedule_watch_request (handle,
1155 w);
1156 return GNUNET_OK;
1157}
1158
1159
1160/**
1161 * Stop watching statistics from the peer.
1162 *
1163 * @param handle identification of the statistics service
1164 * @param subsystem limit to the specified subsystem, never NULL
1165 * @param name name of the statistic value, never NULL
1166 * @param proc function to call on each value
1167 * @param proc_cls closure for @a proc
1168 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error (no such watch)
1169 */
1170int
1171GNUNET_STATISTICS_watch_cancel (struct GNUNET_STATISTICS_Handle *handle,
1172 const char *subsystem,
1173 const char *name,
1174 GNUNET_STATISTICS_Iterator proc,
1175 void *proc_cls)
1176{
1177 struct GNUNET_STATISTICS_WatchEntry *w;
1178
1179 if (NULL == handle)
1180 return GNUNET_SYSERR;
1181 for (unsigned int i = 0; i < handle->watches_size; i++)
1182 {
1183 w = handle->watches[i];
1184 if (NULL == w)
1185 continue;
1186 if ((w->proc == proc) &&
1187 (w->proc_cls == proc_cls) &&
1188 (0 == strcmp (w->name,
1189 name)) &&
1190 (0 == strcmp (w->subsystem,
1191 subsystem)))
1192 {
1193 GNUNET_free (w->name);
1194 GNUNET_free (w->subsystem);
1195 GNUNET_free (w);
1196 handle->watches[i] = NULL;
1197 return GNUNET_OK;
1198 }
1199 }
1200 return GNUNET_SYSERR;
1201}
1202
1203
1204/**
1205 * Queue a request to change a statistic.
1206 *
1207 * @param h statistics handle
1208 * @param name name of the value
1209 * @param make_persistent should the value be kept across restarts?
1210 * @param value new value or change
1211 * @param type type of the action (#ACTION_SET or #ACTION_UPDATE)
1212 */
1213static void
1214add_setter_action (struct GNUNET_STATISTICS_Handle *h,
1215 const char *name,
1216 int make_persistent,
1217 uint64_t value,
1218 enum ActionType type)
1219{
1220 struct GNUNET_STATISTICS_GetHandle *ai;
1221 size_t slen;
1222 size_t nlen;
1223 size_t nsize;
1224 int64_t delta;
1225
1226 slen = strlen (h->subsystem) + 1;
1227 nlen = strlen (name) + 1;
1228 nsize = sizeof(struct GNUNET_STATISTICS_SetMessage) + slen + nlen;
1229 if (nsize >= GNUNET_MAX_MESSAGE_SIZE)
1230 {
1231 GNUNET_break (0);
1232 return;
1233 }
1234 for (ai = h->action_head; NULL != ai; ai = ai->next)
1235 {
1236 if (! ((0 == strcmp (ai->subsystem,
1237 h->subsystem)) &&
1238 (0 == strcmp (ai->name,
1239 name)) &&
1240 ((ACTION_UPDATE == ai->type) ||
1241 (ACTION_SET == ai->type))))
1242 continue;
1243 if (ACTION_SET == ai->type)
1244 {
1245 if (ACTION_UPDATE == type)
1246 {
1247 delta = (int64_t) value;
1248 if (delta > 0)
1249 {
1250 /* update old set by new delta */
1251 ai->value += delta;
1252 }
1253 else
1254 {
1255 /* update old set by new delta, but never go negative */
1256 if (ai->value < -delta)
1257 ai->value = 0;
1258 else
1259 ai->value += delta;
1260 }
1261 }
1262 else
1263 {
1264 /* new set overrides old set */
1265 ai->value = value;
1266 }
1267 }
1268 else
1269 {
1270 if (ACTION_UPDATE == type)
1271 {
1272 /* make delta cumulative */
1273 delta = (int64_t) value;
1274 ai->value += delta;
1275 }
1276 else
1277 {
1278 /* drop old 'update', use new 'set' instead */
1279 ai->value = value;
1280 ai->type = type;
1281 }
1282 }
1283 ai->timeout
1284 = GNUNET_TIME_relative_to_absolute (SET_TRANSMIT_TIMEOUT);
1285 ai->make_persistent
1286 = make_persistent;
1287 return;
1288 }
1289 /* no existing entry matches, create a fresh one */
1290 ai = GNUNET_new (struct GNUNET_STATISTICS_GetHandle);
1291 ai->sh = h;
1292 ai->subsystem = GNUNET_strdup (h->subsystem);
1293 ai->name = GNUNET_strdup (name);
1294 ai->timeout = GNUNET_TIME_relative_to_absolute (SET_TRANSMIT_TIMEOUT);
1295 ai->make_persistent = make_persistent;
1296 ai->msize = nsize;
1297 ai->value = value;
1298 ai->type = type;
1299 GNUNET_CONTAINER_DLL_insert_tail (h->action_head,
1300 h->action_tail,
1301 ai);
1302 schedule_action (h);
1303}
1304
1305
1306void
1307GNUNET_STATISTICS_set (struct GNUNET_STATISTICS_Handle *handle,
1308 const char *name,
1309 uint64_t value,
1310 int make_persistent)
1311{
1312 if (NULL == handle)
1313 return;
1314 GNUNET_assert (GNUNET_NO == handle->do_destroy);
1315 add_setter_action (handle,
1316 name,
1317 make_persistent,
1318 value,
1319 ACTION_SET);
1320}
1321
1322
1323void
1324GNUNET_STATISTICS_update (struct GNUNET_STATISTICS_Handle *handle,
1325 const char *name,
1326 int64_t delta,
1327 int make_persistent)
1328{
1329 if (NULL == handle)
1330 return;
1331 if (0 == delta)
1332 return;
1333 GNUNET_assert (GNUNET_NO == handle->do_destroy);
1334 add_setter_action (handle,
1335 name,
1336 make_persistent,
1337 (uint64_t) delta,
1338 ACTION_UPDATE);
1339}
1340
1341
1342/* end of statistics_api.c */
diff --git a/src/service/statistics/test_gnunet_statistics.py.in b/src/service/statistics/test_gnunet_statistics.py.in
new file mode 100644
index 000000000..bf6ba6ef2
--- /dev/null
+++ b/src/service/statistics/test_gnunet_statistics.py.in
@@ -0,0 +1,171 @@
1#!@PYTHONEXE@
2
3import os
4import sys
5import shutil
6import re
7import subprocess
8import time
9
10raw_tmp = True
11if os.name == "nt":
12 tmp = os.getenv("TEMP")
13elif None != os.environ.get("TMPDIR"):
14 tmp = os.getenv("TMPDIR")
15elif None != os.environ.get("TMP"):
16 tmp = os.getenv("TMP")
17else:
18 raw_tmp = False
19 tmp = subprocess.check_output(["gnunet-config", "-f", "-s", "PATHS", "-o", "GNUNET_TMP"],
20 text=True)
21
22if os.name == 'nt':
23 st = './gnunet-statistics.exe'
24 arm = 'gnunet-arm.exe'
25else:
26 st = './gnunet-statistics'
27 arm = 'gnunet-arm'
28
29run_st = [st, '-c', 'test_statistics_api_data.conf']
30run_arm = [arm, '-c', 'test_statistics_api_data.conf']
31debug = os.getenv('DEBUG')
32if debug:
33 run_arm += [debug.split(' ')]
34
35
36if raw_tmp:
37 cleanup_path = "gnunet/test-gnunet-statistics"
38else:
39 cleanup_path = "test-gnunet-statistics"
40
41
42def cleanup():
43 shutil.rmtree(os.path.join(tmp, cleanup_path), True)
44
45
46def sub_run(args, want_stdo=True, want_stde=False, nofail=False):
47 if want_stdo:
48 stdo = subprocess.PIPE
49 else:
50 stdo = None
51 if want_stde:
52 stde = subprocess.PIPE
53 else:
54 stde = None
55 p = subprocess.Popen(args, stdout=stdo, stderr=stde)
56 stdo, stde = p.communicate()
57 if not nofail:
58 if p.returncode != 0:
59 sys.exit(p.returncode)
60 return (p.returncode, stdo, stde)
61
62
63def fail(result):
64 print(result)
65 r_arm(['-e'], want_stdo=False)
66 sys.exit(1)
67
68
69def r_arm(extra_args, **kw):
70 rc, stdo, stde = sub_run(run_arm + extra_args, **kw)
71 if rc != 0:
72 fail("FAIL: error running {}".format(run_arm))
73 return (rc, stdo, stde)
74
75
76def r_st(extra_args, normal=True, **kw):
77 rc, stdo, stde = sub_run(run_st + extra_args, **kw)
78 if normal:
79 if rc != 0:
80 fail("FAIL: error running {}".format(run_st))
81 else:
82 if rc == 0:
83 fail("FAIL: expected error while running {}".format(run_st))
84 return (rc, stdo, stde)
85
86
87def restart():
88 print("Restarting service...")
89 t = r_arm(['-k', 'statistics'])
90 time.sleep(1)
91 t = r_arm(['-i', 'statistics'])
92 time.sleep(1)
93
94
95cleanup()
96
97print("Preparing: Starting service...")
98t = r_arm(['-s'], want_stdo=False)
99time.sleep(1)
100t = r_arm(['-i', 'statistics'], want_stdo=False)
101time.sleep(1)
102
103print("TEST: Bad argument checking...", end='')
104r_st(['-x'], normal=False, nofail=True, want_stdo=False, want_stde=True)
105print("PASS")
106
107print("TEST: Set value...", end='')
108r_st(['-n', 'test', '-s', 'subsystem', b'42'], nofail=True, want_stdo=False)
109print("PASS")
110
111print("TEST: Set another value...", end='')
112r_st(['-n', 'other', '-s', 'osystem', b'43'], nofail=True, want_stdo=False)
113print("PASS")
114
115#print("TEST: Viewing all stats...", end='')
116#rc, stdo, stde = r_st([], nofail=True, want_stdo=True)
117#if len(stdo.splitlines()) != 2:
118# fail("FAIL: unexpected output:\n{}".format(stdo))
119#print("PASS")
120
121print("TEST: Viewing stats by name...", end='')
122rc, stdo, stde = r_st(['-n', 'other'], nofail=True, want_stdo=True)
123if len([x for x in stdo.splitlines() if re.search(b'43', x)]) != 1:
124 fail("FAIL: unexpected output:\n{}".format(stdo))
125print("PASS")
126
127print("TEST: Viewing stats by subsystem...", end='')
128rc, stdo, stde = r_st(['-s', 'subsystem'], nofail=True, want_stdo=True)
129if len([x for x in stdo.splitlines() if re.search(b'42', x)]) != 1:
130 fail("FAIL: unexpected output:\n{}".format(stdo))
131print("PASS")
132
133print("TEST: Set persistent value...", end='')
134rc, stdo, stde = r_st(['-n', 'lasting', '-s', 'subsystem', '40', '-p'],
135 nofail=True,
136 want_stdo=False)
137rc, stdo, stde = r_st([], nofail=True, want_stdo=True)
138if len([x for x in stdo.splitlines() if re.search(b'40', x)]) != 1:
139 fail("FAIL: unexpected output:\n{}".format(stdo))
140print("PASS")
141
142restart()
143
144print("TEST: Checking persistence...", end='')
145rc, stdo, stde = r_st([], nofail=True, want_stdo=True)
146if len([x for x in stdo.splitlines() if re.search(b'40', x)]) != 1:
147 fail("FAIL: unexpected output:\n{}".format(stdo))
148print("PASS")
149
150print("TEST: Removing persistence...", end='')
151rc, stdo, stde = r_st(['-n', 'lasting', '-s', 'subsystem', '40'],
152 nofail=True,
153 want_stdo=False)
154rc, stdo, stde = r_st([], nofail=True, want_stdo=True)
155if len([x for x in stdo.splitlines() if re.search(b'!', x)]) != 0:
156 fail("FAIL: unexpected output:\n{}".format(stdo))
157print("PASS")
158
159restart()
160
161print("TEST: Checking removed persistence...", end='')
162rc, stdo, stde = r_st([], nofail=True, want_stdo=True)
163if len([x for x in stdo.splitlines() if re.search(b'40', x)]) != 0:
164 fail("FAIL: unexpected output:\n{}".format(stdo))
165print("PASS")
166
167print("Stopping service...")
168t = r_arm(['-e'], want_stdo=False)
169time.sleep(1)
170
171cleanup()
diff --git a/src/service/statistics/test_statistics_api.c b/src/service/statistics/test_statistics_api.c
new file mode 100644
index 000000000..c9e568870
--- /dev/null
+++ b/src/service/statistics/test_statistics_api.c
@@ -0,0 +1,253 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2009, 2012, 2016 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/**
21 * @file statistics/test_statistics_api.c
22 * @brief testcase for statistics_api.c
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include "gnunet_util_lib.h"
27#include "gnunet_statistics_service.h"
28
29
30static struct GNUNET_STATISTICS_Handle *h;
31
32static struct GNUNET_STATISTICS_GetHandle *g;
33
34
35static void
36do_shutdown ()
37{
38 if (NULL != g)
39 {
40 GNUNET_STATISTICS_get_cancel (g);
41 g = NULL;
42 }
43 GNUNET_STATISTICS_destroy (h, GNUNET_NO);
44 h = NULL;
45}
46
47
48static int
49check_1 (void *cls,
50 const char *subsystem,
51 const char *name,
52 uint64_t value,
53 int is_persistent)
54{
55 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
56 "Received value %llu for `%s:%s\n",
57 (unsigned long long) value,
58 subsystem,
59 name);
60 GNUNET_assert (0 == strcmp (name, "test-1"));
61 GNUNET_assert (0 == strcmp (subsystem, "test-statistics-api"));
62 GNUNET_assert (value == 1);
63 GNUNET_assert (is_persistent == GNUNET_NO);
64 return GNUNET_OK;
65}
66
67
68static int
69check_2 (void *cls,
70 const char *subsystem,
71 const char *name,
72 uint64_t value,
73 int is_persistent)
74{
75 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
76 "Received value %llu for `%s:%s\n",
77 (unsigned long long) value,
78 subsystem,
79 name);
80 GNUNET_assert (0 == strcmp (name, "test-2"));
81 GNUNET_assert (0 == strcmp (subsystem, "test-statistics-api"));
82 GNUNET_assert (value == 2);
83 GNUNET_assert (is_persistent == GNUNET_NO);
84 return GNUNET_OK;
85}
86
87
88static int
89check_3 (void *cls,
90 const char *subsystem,
91 const char *name,
92 uint64_t value,
93 int is_persistent)
94{
95 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
96 "Received value %llu for `%s:%s\n",
97 (unsigned long long) value,
98 subsystem,
99 name);
100 GNUNET_assert (0 == strcmp (name, "test-3"));
101 GNUNET_assert (0 == strcmp (subsystem, "test-statistics-api"));
102 GNUNET_assert (value == 3);
103 GNUNET_assert (is_persistent == GNUNET_YES);
104 return GNUNET_OK;
105}
106
107
108static void
109next_fin (void *cls,
110 int success)
111{
112 int *ok = cls;
113
114 g = NULL;
115 GNUNET_SCHEDULER_shutdown ();
116 GNUNET_assert (success == GNUNET_OK);
117 *ok = 0;
118}
119
120
121static void
122next (void *cls, int success)
123{
124 g = NULL;
125 GNUNET_assert (success == GNUNET_OK);
126 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
127 "Issuing GET request\n");
128 GNUNET_break (NULL !=
129 GNUNET_STATISTICS_get (h, NULL, "test-2",
130 &next_fin,
131 &check_2, cls));
132}
133
134
135static void
136run (void *cls,
137 char *const *args,
138 const char *cfgfile,
139 const struct GNUNET_CONFIGURATION_Handle *cfg)
140{
141 h = GNUNET_STATISTICS_create ("test-statistics-api", cfg);
142 if (NULL == h)
143 {
144 GNUNET_break (0);
145 return;
146 }
147 GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
148 NULL);
149 GNUNET_STATISTICS_set (h, "test-1", 1, GNUNET_NO);
150 GNUNET_STATISTICS_set (h, "test-2", 2, GNUNET_NO);
151 GNUNET_STATISTICS_set (h, "test-3", 2, GNUNET_NO);
152 GNUNET_STATISTICS_update (h, "test-3", 1, GNUNET_YES);
153 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
154 "Issuing GET request\n");
155 GNUNET_break (NULL !=
156 (g = GNUNET_STATISTICS_get (h, NULL, "test-1",
157 &next,
158 &check_1, cls)));
159}
160
161
162static void
163run_more (void *cls,
164 char *const *args,
165 const char *cfgfile,
166 const struct GNUNET_CONFIGURATION_Handle *cfg)
167{
168 h = GNUNET_STATISTICS_create ("test-statistics-api",
169 cfg);
170 GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
171 NULL);
172 GNUNET_break (NULL !=
173 (g = GNUNET_STATISTICS_get (h, NULL,
174 "test-3",
175 &next_fin,
176 &check_3, cls)));
177}
178
179
180int
181main (int argc, char *argv_ign[])
182{
183 int ok = 1;
184 char *const argv[] = { "test-statistics-api",
185 "-c",
186 "test_statistics_api_data.conf",
187 "-L", "WARNING",
188 NULL };
189 struct GNUNET_GETOPT_CommandLineOption options[] = {
190 GNUNET_GETOPT_OPTION_END
191 };
192 struct GNUNET_OS_Process *proc;
193 char *binary;
194
195 GNUNET_log_setup ("test_statistics_api",
196 "WARNING",
197 NULL);
198 binary = GNUNET_OS_get_libexec_binary_path ("gnunet-service-statistics");
199 proc =
200 GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_OUT_AND_ERR
201 | GNUNET_OS_USE_PIPE_CONTROL,
202 NULL, NULL, NULL,
203 binary,
204 "gnunet-service-statistics",
205 "-c", "test_statistics_api_data.conf", NULL);
206 GNUNET_assert (NULL != proc);
207 GNUNET_PROGRAM_run (5, argv,
208 "test-statistics-api", "nohelp",
209 options, &run,
210 &ok);
211 if (0 != GNUNET_OS_process_kill (proc,
212 GNUNET_TERM_SIG))
213 {
214 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
215 ok = 1;
216 }
217 GNUNET_OS_process_wait (proc);
218 GNUNET_OS_process_destroy (proc);
219 proc = NULL;
220 if (ok != 0)
221 {
222 GNUNET_free (binary);
223 return ok;
224 }
225 ok = 1;
226 /* restart to check persistence! */
227 proc =
228 GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_OUT_AND_ERR
229 | GNUNET_OS_USE_PIPE_CONTROL,
230 NULL, NULL, NULL,
231 binary,
232 "gnunet-service-statistics",
233 "-c", "test_statistics_api_data.conf",
234 NULL);
235 GNUNET_PROGRAM_run (5, argv,
236 "test-statistics-api", "nohelp",
237 options,
238 &run_more, &ok);
239 if (0 != GNUNET_OS_process_kill (proc,
240 GNUNET_TERM_SIG))
241 {
242 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
243 ok = 1;
244 }
245 GNUNET_OS_process_wait (proc);
246 GNUNET_OS_process_destroy (proc);
247 proc = NULL;
248 GNUNET_free (binary);
249 return ok;
250}
251
252
253/* end of test_statistics_api.c */
diff --git a/src/service/statistics/test_statistics_api_data.conf b/src/service/statistics/test_statistics_api_data.conf
new file mode 100644
index 000000000..f7884f368
--- /dev/null
+++ b/src/service/statistics/test_statistics_api_data.conf
@@ -0,0 +1,5 @@
1@INLINE@ ../../../contrib/conf/gnunet/no_forcestart.conf
2@INLINE@ ../../../contrib/conf/gnunet/no_autostart_above_core.conf
3
4[PATHS]
5GNUNET_TEST_HOME = $GNUNET_TMP/test-gnunet-statistics/
diff --git a/src/service/statistics/test_statistics_api_loop.c b/src/service/statistics/test_statistics_api_loop.c
new file mode 100644
index 000000000..ad273287d
--- /dev/null
+++ b/src/service/statistics/test_statistics_api_loop.c
@@ -0,0 +1,123 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2009 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/**
21 * @file statistics/test_statistics_api_loop.c
22 * @brief testcase for statistics_api.c
23 */
24#include "platform.h"
25#include "gnunet_util_lib.h"
26#include "gnunet_statistics_service.h"
27
28#define ROUNDS (1024 * 1024)
29
30static struct GNUNET_STATISTICS_Handle *h;
31
32
33static int
34check_1 (void *cls,
35 const char *subsystem,
36 const char *name,
37 uint64_t value,
38 int is_persistent)
39{
40 GNUNET_assert (0 == strcmp (name, "test-0"));
41 GNUNET_assert (0 == strcmp (subsystem, "test-statistics-api-loop"));
42 GNUNET_assert (is_persistent == GNUNET_NO);
43 return GNUNET_OK;
44}
45
46
47static void
48next (void *cls,
49 int success)
50{
51 int *ok = cls;
52
53 GNUNET_STATISTICS_destroy (h, GNUNET_NO);
54 GNUNET_assert (success == GNUNET_OK);
55 *ok = 0;
56}
57
58
59static void
60run (void *cls,
61 char *const *args,
62 const char *cfgfile,
63 const struct GNUNET_CONFIGURATION_Handle *cfg)
64{
65 unsigned int i;
66 char name[128];
67
68 h = GNUNET_STATISTICS_create ("test-statistics-api-loop", cfg);
69 for (i = 0; i < ROUNDS; i++)
70 {
71 GNUNET_snprintf (name, sizeof(name), "test-%d", i % 32);
72 GNUNET_STATISTICS_set (h, name, i, GNUNET_NO);
73 GNUNET_snprintf (name, sizeof(name), "test-%d", i % 16);
74 GNUNET_STATISTICS_update (h, name, 1, GNUNET_NO);
75 }
76 i = 0;
77 GNUNET_break (NULL !=
78 GNUNET_STATISTICS_get (h, NULL, "test-0",
79 &next,
80 &check_1, cls));
81}
82
83
84int
85main (int argc, char *argv_ign[])
86{
87 int ok = 1;
88
89 char *const argv[] = { "test-statistics-api",
90 "-c",
91 "test_statistics_api_data.conf",
92 NULL };
93 struct GNUNET_GETOPT_CommandLineOption options[] = {
94 GNUNET_GETOPT_OPTION_END
95 };
96 struct GNUNET_OS_Process *proc;
97 char *binary;
98
99 binary = GNUNET_OS_get_libexec_binary_path ("gnunet-service-statistics");
100 proc =
101 GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_OUT_AND_ERR
102 | GNUNET_OS_USE_PIPE_CONTROL,
103 NULL, NULL, NULL,
104 binary,
105 "gnunet-service-statistics",
106 "-c", "test_statistics_api_data.conf", NULL);
107 GNUNET_assert (NULL != proc);
108 GNUNET_PROGRAM_run (3, argv, "test-statistics-api", "nohelp", options, &run,
109 &ok);
110 if (0 != GNUNET_OS_process_kill (proc, GNUNET_TERM_SIG))
111 {
112 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
113 ok = 1;
114 }
115 GNUNET_OS_process_wait (proc);
116 GNUNET_OS_process_destroy (proc);
117 proc = NULL;
118 GNUNET_free (binary);
119 return ok;
120}
121
122
123/* end of test_statistics_api_loop.c */
diff --git a/src/service/statistics/test_statistics_api_watch.c b/src/service/statistics/test_statistics_api_watch.c
new file mode 100644
index 000000000..2d9d08305
--- /dev/null
+++ b/src/service/statistics/test_statistics_api_watch.c
@@ -0,0 +1,156 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2009, 2011, 2012 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/**
21 * @file statistics/test_statistics_api_watch.c
22 * @brief testcase for statistics_api.c watch functions
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include "gnunet_util_lib.h"
27#include "gnunet_statistics_service.h"
28
29
30static int ok;
31
32static struct GNUNET_STATISTICS_Handle *h;
33
34static struct GNUNET_STATISTICS_Handle *h2;
35
36static struct GNUNET_SCHEDULER_Task *shutdown_task;
37
38
39static void
40force_shutdown (void *cls)
41{
42 fprintf (stderr, "Timeout, failed to receive notifications: %d\n", ok);
43 GNUNET_STATISTICS_destroy (h, GNUNET_NO);
44 GNUNET_STATISTICS_destroy (h2, GNUNET_NO);
45 ok = 7;
46}
47
48
49static void
50normal_shutdown (void *cls)
51{
52 GNUNET_STATISTICS_destroy (h, GNUNET_NO);
53 GNUNET_STATISTICS_destroy (h2, GNUNET_NO);
54}
55
56
57static int
58watch_1 (void *cls,
59 const char *subsystem,
60 const char *name,
61 uint64_t value,
62 int is_persistent)
63{
64 GNUNET_assert (value == 42);
65 GNUNET_assert (0 == strcmp (name, "test-1"));
66 ok &= ~1;
67 if (0 == ok)
68 {
69 GNUNET_SCHEDULER_cancel (shutdown_task);
70 GNUNET_SCHEDULER_add_now (&normal_shutdown, NULL);
71 }
72 return GNUNET_OK;
73}
74
75
76static int
77watch_2 (void *cls,
78 const char *subsystem,
79 const char *name,
80 uint64_t value,
81 int is_persistent)
82{
83 GNUNET_assert (value == 43);
84 GNUNET_assert (0 == strcmp (name, "test-2"));
85 ok &= ~2;
86 if (0 == ok)
87 {
88 GNUNET_SCHEDULER_cancel (shutdown_task);
89 GNUNET_SCHEDULER_add_now (&normal_shutdown, NULL);
90 }
91 return GNUNET_OK;
92}
93
94
95static void
96run (void *cls,
97 char *const *args,
98 const char *cfgfile,
99 const struct GNUNET_CONFIGURATION_Handle *cfg)
100{
101 h = GNUNET_STATISTICS_create ("dummy", cfg);
102 GNUNET_assert (GNUNET_OK ==
103 GNUNET_STATISTICS_watch (h, "test-statistics-api-watch",
104 "test-1", &watch_1, NULL));
105 GNUNET_assert (GNUNET_OK ==
106 GNUNET_STATISTICS_watch (h, "test-statistics-api-watch",
107 "test-2", &watch_2, NULL));
108 h2 = GNUNET_STATISTICS_create ("test-statistics-api-watch", cfg);
109 GNUNET_STATISTICS_set (h2, "test-1", 42, GNUNET_NO);
110 GNUNET_STATISTICS_set (h2, "test-2", 43, GNUNET_NO);
111 shutdown_task =
112 GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES,
113 &force_shutdown,
114 NULL);
115}
116
117
118int
119main (int argc, char *argv_ign[])
120{
121 char *const argv[] = { "test-statistics-api",
122 "-c",
123 "test_statistics_api_data.conf",
124 NULL };
125 struct GNUNET_GETOPT_CommandLineOption options[] = {
126 GNUNET_GETOPT_OPTION_END
127 };
128 struct GNUNET_OS_Process *proc;
129 char *binary;
130
131 binary = GNUNET_OS_get_libexec_binary_path ("gnunet-service-statistics");
132 proc =
133 GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_OUT_AND_ERR
134 | GNUNET_OS_USE_PIPE_CONTROL,
135 NULL, NULL, NULL,
136 binary,
137 "gnunet-service-statistics",
138 "-c", "test_statistics_api_data.conf", NULL);
139 GNUNET_assert (NULL != proc);
140 ok = 3;
141 GNUNET_PROGRAM_run (3, argv, "test-statistics-api", "nohelp", options, &run,
142 NULL);
143 if (0 != GNUNET_OS_process_kill (proc, GNUNET_TERM_SIG))
144 {
145 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
146 ok = 1;
147 }
148 GNUNET_OS_process_wait (proc);
149 GNUNET_OS_process_destroy (proc);
150 proc = NULL;
151 GNUNET_free (binary);
152 return ok;
153}
154
155
156/* end of test_statistics_api_watch.c */
diff --git a/src/service/statistics/test_statistics_api_watch_zero_value.c b/src/service/statistics/test_statistics_api_watch_zero_value.c
new file mode 100644
index 000000000..cb2694f8f
--- /dev/null
+++ b/src/service/statistics/test_statistics_api_watch_zero_value.c
@@ -0,0 +1,197 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2009, 2011, 2012 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/**
21 * @file statistics/test_statistics_api_watch_zero_value.c
22 * @brief testcase for statistics_api.c watch functions with initial 0 value
23 */
24#include "platform.h"
25#include "gnunet_util_lib.h"
26#include "gnunet_statistics_service.h"
27
28static int ok;
29
30static int ok2;
31
32static struct GNUNET_STATISTICS_Handle *h;
33
34static struct GNUNET_STATISTICS_Handle *h2;
35
36static struct GNUNET_SCHEDULER_Task *shutdown_task;
37
38
39static void
40force_shutdown (void *cls)
41{
42 fprintf (stderr, "Timeout, failed to receive notifications: %d\n", ok);
43 GNUNET_STATISTICS_destroy (h, GNUNET_NO);
44 GNUNET_STATISTICS_destroy (h2, GNUNET_NO);
45 ok = 7;
46}
47
48
49static void
50normal_shutdown (void *cls)
51{
52 GNUNET_STATISTICS_destroy (h, GNUNET_NO);
53 GNUNET_STATISTICS_destroy (h2, GNUNET_NO);
54}
55
56
57static int
58watch_1 (void *cls, const char *subsystem, const char *name, uint64_t value,
59 int is_persistent)
60{
61 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
62 "Received value `%s' `%s' %llu\n",
63 subsystem,
64 name,
65 (unsigned long long) value);
66 GNUNET_assert (0 == strcmp (name, "test-1"));
67 if ((0 == value) && (3 == ok))
68 {
69 ok--;
70 GNUNET_STATISTICS_set (h, "test-1", 42, GNUNET_NO);
71 }
72
73 if ((42 == value) && (2 == ok))
74 {
75 ok--;
76 GNUNET_STATISTICS_set (h, "test-1", 0, GNUNET_NO);
77 }
78
79 if ((0 == value) && (1 == ok))
80 {
81 ok--;
82 }
83 if ((0 == ok) && (0 == ok2))
84 {
85 GNUNET_SCHEDULER_cancel (shutdown_task);
86 GNUNET_SCHEDULER_add_now (&normal_shutdown, NULL);
87 }
88
89 return GNUNET_OK;
90}
91
92
93static int
94watch_2 (void *cls,
95 const char *subsystem,
96 const char *name,
97 uint64_t value,
98 int is_persistent)
99{
100 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
101 "Received value `%s' `%s' %llu\n",
102 subsystem,
103 name,
104 (unsigned long long) value);
105
106 GNUNET_assert (0 == strcmp (name, "test-2"));
107 if ((42 == value) && (1 == ok2))
108 {
109 ok2 = 0;
110 if (0 == ok)
111 {
112 GNUNET_SCHEDULER_cancel (shutdown_task);
113 GNUNET_SCHEDULER_add_now (&normal_shutdown, NULL);
114 }
115 }
116 else
117 {
118 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
119 "Received unexpected value %llu\n",
120 (unsigned long long) value);
121
122 GNUNET_break (0);
123 GNUNET_SCHEDULER_cancel (shutdown_task);
124 GNUNET_SCHEDULER_add_now (&normal_shutdown, NULL);
125 }
126
127 return GNUNET_OK;
128}
129
130
131static void
132run (void *cls, char *const *args, const char *cfgfile,
133 const struct GNUNET_CONFIGURATION_Handle *cfg)
134{
135 h = GNUNET_STATISTICS_create ("dummy", cfg);
136 h2 = GNUNET_STATISTICS_create ("dummy-2", cfg);
137 GNUNET_assert (GNUNET_OK ==
138 GNUNET_STATISTICS_watch (h, "dummy",
139 "test-1", &watch_1, NULL));
140
141 GNUNET_assert (GNUNET_OK ==
142 GNUNET_STATISTICS_watch (h2, "dummy-2",
143 "test-2", &watch_2, NULL));
144
145 /* Set initial value to 0 */
146 GNUNET_STATISTICS_set (h, "test-1", 0, GNUNET_NO);
147 GNUNET_STATISTICS_set (h2, "test-2", 42, GNUNET_NO);
148
149 shutdown_task =
150 GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES,
151 &force_shutdown,
152 NULL);
153}
154
155
156int
157main (int argc, char *argv_ign[])
158{
159 char *const argv[] = { "test-statistics-api",
160 "-c",
161 "test_statistics_api_data.conf",
162 NULL };
163 struct GNUNET_GETOPT_CommandLineOption options[] = {
164 GNUNET_GETOPT_OPTION_END
165 };
166 struct GNUNET_OS_Process *proc;
167 char *binary;
168
169 binary = GNUNET_OS_get_libexec_binary_path ("gnunet-service-statistics");
170 proc =
171 GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_OUT_AND_ERR
172 | GNUNET_OS_USE_PIPE_CONTROL,
173 NULL, NULL, NULL,
174 binary,
175 "gnunet-service-statistics",
176 "-c", "test_statistics_api_data.conf", NULL);
177 GNUNET_assert (NULL != proc);
178 ok = 3;
179 ok2 = 1;
180 GNUNET_PROGRAM_run (3, argv, "test-statistics-api", "nohelp", options, &run,
181 NULL);
182 if (0 != GNUNET_OS_process_kill (proc, GNUNET_TERM_SIG))
183 {
184 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
185 ok = 1;
186 }
187 GNUNET_OS_process_wait (proc);
188 GNUNET_OS_process_destroy (proc);
189 proc = NULL;
190 GNUNET_free (binary);
191 if ((0 == ok) && (0 == ok2))
192 return 0;
193 return 1;
194}
195
196
197/* end of test_statistics_api_watch_zero_value.c */