8de9a8047df7596e1428c896ac050109fc5d952b
[oweals/gnunet.git] / src / statistics / gnunet-service-statistics.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      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      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file statistics/gnunet-service-statistics.c
23  * @brief program that tracks statistics
24  * @author Christian Grothoff
25  * 
26  * TODO:
27  * - use BIO for IO operations
28  */
29 #include "platform.h"
30 #include "gnunet_disk_lib.h"
31 #include "gnunet_getopt_lib.h"
32 #include "gnunet_protocols.h"
33 #include "gnunet_service_lib.h"
34 #include "gnunet_statistics_service.h"
35 #include "gnunet_strings_lib.h"
36 #include "gnunet_time_lib.h"
37 #include "statistics.h"
38
39 /**
40  * Entry in the statistics list.
41  */
42 struct StatsEntry
43 {
44   /**
45    * This is a linked list.
46    */
47   struct StatsEntry *next;
48
49   /**
50    * Name of the service, points into the
51    * middle of msg.
52    */
53   const char *service;
54
55   /**
56    * Name for the value, points into
57    * the middle of msg.
58    */
59   const char *name;
60
61   /**
62    * Message that can be used to set this value,
63    * stored at the end of the memory used by
64    * this struct.
65    */
66   struct GNUNET_STATISTICS_SetMessage *msg;
67
68   /**
69    * Our value.
70    */
71   uint64_t value;
72
73   /**
74    * Unique ID.
75    */
76   uint32_t uid;
77
78   /**
79    * Is this value persistent?
80    */
81   int persistent;
82
83 };
84
85 /**
86  * Our configuration.
87  */
88 static const struct GNUNET_CONFIGURATION_Handle *cfg;
89
90 /**
91  * Linked list of our active statistics.
92  */
93 static struct StatsEntry *start;
94
95 /**
96  * Counter used to generate unique values.
97  */
98 static uint32_t uidgen;
99
100 /**
101  * Load persistent values from disk.  Disk format is
102  * exactly the same format that we also use for
103  * setting the values over the network.
104  *
105  * @param server handle to the server context
106  */
107 static void
108 load (struct GNUNET_SERVER_Handle *server)
109 {
110   char *fn;
111   struct GNUNET_DISK_FileHandle *fh;
112   struct GNUNET_DISK_MapHandle *mh;
113   struct stat sb;
114   char *buf;
115   size_t off;
116   const struct GNUNET_MessageHeader *msg;
117
118   fn = GNUNET_DISK_get_home_filename (cfg,
119                                       "statistics", "statistics.data", NULL);
120   if (fn == NULL)
121     return;
122   if ((0 != stat (fn, &sb)) || (sb.st_size == 0))
123     {
124       GNUNET_free (fn);
125       return;
126     }
127   fh = GNUNET_DISK_file_open (fn, GNUNET_DISK_OPEN_READ,
128                               GNUNET_DISK_PERM_NONE);
129   if (!fh)
130     {
131       GNUNET_free (fn);
132       return;
133     }
134   buf = GNUNET_DISK_file_map (fh, &mh, GNUNET_DISK_MAP_TYPE_READ, sb.st_size);
135   if (NULL == buf)
136     {
137       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "mmap", fn);
138       GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
139       GNUNET_free (fn);
140       return;
141     }
142   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
143               _("Loading %llu bytes of statistics from `%s'\n"),
144               (unsigned long long) sb.st_size, fn);
145   off = 0;
146   while (off + sizeof (struct GNUNET_MessageHeader) < sb.st_size)
147     {
148       msg = (const struct GNUNET_MessageHeader *) &buf[off];
149       if ((ntohs (msg->size) + off > sb.st_size) ||
150           (GNUNET_OK != GNUNET_SERVER_inject (server, NULL, msg)))
151         {
152           GNUNET_break (0);
153           break;
154         }
155       off += ntohs (msg->size);
156     }
157   GNUNET_break (GNUNET_OK == GNUNET_DISK_file_unmap (mh));
158   GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
159   GNUNET_free (fn);
160 }
161
162 /**
163  * Write persistent statistics to disk.
164  */
165 static void
166 save ()       
167 {
168   struct StatsEntry *pos;
169   char *fn;
170   struct GNUNET_DISK_FileHandle *fh;
171   uint16_t size;
172   unsigned long long total;
173
174   fh = NULL;
175   fn = GNUNET_DISK_get_home_filename (cfg,
176                                       "statistics", "statistics.data", NULL);
177   if (fn != NULL)
178     fh = GNUNET_DISK_file_open (fn, GNUNET_DISK_OPEN_WRITE
179         | GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_TRUNCATE,
180         GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE);
181   total = 0;
182   while (NULL != (pos = start))
183     {
184       start = pos->next;
185       if ((pos->persistent) && (NULL != fh))
186         {
187           size = htons (pos->msg->header.size);
188           if (size != GNUNET_DISK_file_write (fh, pos->msg, size))
189             {
190               GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
191                                         "write", fn);
192               GNUNET_DISK_file_close (fh);
193               fh = NULL;
194             }
195           else
196             total += size;
197         }
198       GNUNET_free (pos);
199     }
200   if (NULL != fh)
201     {
202       GNUNET_DISK_file_close (fh);
203       if (total == 0)
204         GNUNET_break (0 == UNLINK (fn));
205       else
206         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
207                     _("Wrote %llu bytes of statistics to `%s'\n"), total, fn);
208     }
209   GNUNET_free_non_null (fn);
210 }
211
212
213 /**
214  * Transmit the given stats value.
215  */
216 static void
217 transmit (struct GNUNET_SERVER_TransmitContext *tc,
218           const struct StatsEntry *e)
219 {
220   struct GNUNET_STATISTICS_ReplyMessage *m;
221   struct GNUNET_MessageHeader *h;
222   size_t size;
223   uint16_t msize;
224
225   size =
226     sizeof (struct GNUNET_STATISTICS_ReplyMessage) + strlen (e->service) + 1 +
227     strlen (e->name) + 1;
228   GNUNET_assert (size < GNUNET_SERVER_MAX_MESSAGE_SIZE);
229   msize = size - sizeof (struct GNUNET_MessageHeader);
230   m = GNUNET_malloc (size);
231   m->uid = htonl (e->uid);
232   if (e->persistent)
233     m->uid |= htonl (GNUNET_STATISTICS_PERSIST_BIT);
234   m->value = GNUNET_htonll (e->value);
235   size -= sizeof (struct GNUNET_STATISTICS_ReplyMessage);
236   GNUNET_assert (size == GNUNET_STRINGS_buffer_fill ((char *) &m[1],
237                                                      size,
238                                                      2, e->service, e->name));
239 #if DEBUG_STATISTICS
240   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
241               "Transmitting value for `%s:%s': %llu\n",
242               e->service, e->name, e->value);
243 #endif
244   h = &m->header;
245   GNUNET_SERVER_transmit_context_append (tc,
246                                          &h[1],
247                                          msize,
248                                          GNUNET_MESSAGE_TYPE_STATISTICS_VALUE);
249   GNUNET_free (m);
250 }
251
252
253 /**
254  * Does this entry match the request?
255  */
256 static int
257 matches (const struct StatsEntry *e, const char *service, const char *name)
258 {
259   return ((0 == strlen (service)) ||
260           (0 == strcmp (service, e->service)))
261     && ((0 == strlen (name)) || (0 == strcmp (name, e->name)));
262 }
263
264
265 /**
266  * Handle GET-message.
267  *
268  * @param cls closure
269  * @param client identification of the client
270  * @param message the actual message
271  * @return GNUNET_OK to keep the connection open,
272  *         GNUNET_SYSERR to close it (signal serious error)
273  */
274 static void
275 handle_get (void *cls,
276             struct GNUNET_SERVER_Client *client,
277             const struct GNUNET_MessageHeader *message)
278 {
279   char *service;
280   char *name;
281   struct StatsEntry *pos;
282   struct GNUNET_SERVER_TransmitContext *tc;
283   size_t size;
284
285   size = ntohs (message->size) - sizeof (struct GNUNET_MessageHeader);
286   if (size != GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
287                                               size, 2, &service, &name))
288     {
289       GNUNET_break (0);
290       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
291       return;
292     }
293 #if DEBUG_STATISTICS
294   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
295               "Received request for statistics on `%s:%s'\n",
296               strlen (service) ? service : "*", strlen (name) ? name : "*");
297 #endif
298   tc = GNUNET_SERVER_transmit_context_create (client);
299   pos = start;
300   while (pos != NULL)
301     {
302       if (matches (pos, service, name))
303         transmit (tc, pos);
304       pos = pos->next;
305     }
306   GNUNET_SERVER_transmit_context_append (tc, NULL, 0,
307                                          GNUNET_MESSAGE_TYPE_STATISTICS_END);
308   GNUNET_SERVER_transmit_context_run (tc, GNUNET_TIME_UNIT_FOREVER_REL);
309 }
310
311
312 /**
313  * Handle SET-message.
314  *
315  * @param cls closure
316  * @param client identification of the client
317  * @param message the actual message
318  */
319 static void
320 handle_set (void *cls,
321             struct GNUNET_SERVER_Client *client,
322             const struct GNUNET_MessageHeader *message)
323 {
324   char *service;
325   char *name;
326   uint16_t msize;
327   uint16_t size;
328   const struct GNUNET_STATISTICS_SetMessage *msg;
329   struct StatsEntry *pos;
330   struct StatsEntry *prev;
331   uint32_t flags;
332   uint64_t value;
333   int64_t delta;
334
335   msize = ntohs (message->size);
336   if (msize < sizeof (struct GNUNET_STATISTICS_SetMessage))
337     {
338       GNUNET_break (0);
339       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
340       return;
341     }
342   size = msize - sizeof (struct GNUNET_STATISTICS_SetMessage);
343   msg = (const struct GNUNET_STATISTICS_SetMessage *) message;
344
345   if (size != GNUNET_STRINGS_buffer_tokenize ((const char *) &msg[1],
346                                               size, 2, &service, &name))
347     {
348       GNUNET_break (0);
349       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
350       return;
351     }
352 #if DEBUG_STATISTICS
353   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
354               "Received request to update statistic on `%s:%s'\n",
355               service, name);
356 #endif
357   flags = ntohl (msg->flags);
358   value = GNUNET_ntohll (msg->value);
359   pos = start;
360   prev = NULL;
361   while (pos != NULL)
362     {
363       if (matches (pos, service, name))
364         {
365           if ((flags & GNUNET_STATISTICS_SETFLAG_RELATIVE) == 0)
366             {
367               pos->value = value;
368             }
369           else
370             {
371               delta = (int64_t) value;
372               if ((delta < 0) && (pos->value < -delta))
373                 {
374                   pos->value = 0;
375                 }
376               else
377                 {
378                   GNUNET_break ((delta <= 0) ||
379                                 (pos->value + delta > pos->value));
380                   pos->value += delta;
381                 }
382             }
383           pos->msg->value = GNUNET_htonll (pos->value);
384           pos->msg->flags = msg->flags;
385           pos->persistent =
386             (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
387           if (prev != NULL)
388             {
389               /* move to front for faster setting next time! */
390               prev->next = pos->next;
391               pos->next = start;
392               start = pos;
393             }
394 #if DEBUG_STATISTICS
395           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
396                       "Statistic `%s:%s' updated to value %llu.\n",
397                       service, name, pos->value);
398 #endif
399           GNUNET_SERVER_receive_done (client, GNUNET_OK);
400           return;
401         }
402       prev = pos;
403       pos = pos->next;
404     }
405   pos = GNUNET_malloc (sizeof (struct StatsEntry) + msize);
406   pos->next = start;
407   if (((flags & GNUNET_STATISTICS_SETFLAG_RELATIVE) == 0) ||
408       (0 < (int64_t) GNUNET_ntohll (msg->value)))
409     pos->value = GNUNET_ntohll (msg->value);
410   pos->uid = uidgen++;
411   pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
412   pos->msg = (void *) &pos[1];
413   memcpy (pos->msg, message, ntohs (message->size));
414   pos->service = (const char *) &pos->msg[1];
415   pos->name = &pos->service[strlen (pos->service) + 1];
416
417   start = pos;
418 #if DEBUG_STATISTICS
419   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
420               "New statistic on `%s:%s' with value %llu created.\n",
421               service, name, pos->value);
422 #endif
423   GNUNET_SERVER_receive_done (client, GNUNET_OK);
424 }
425
426
427 /**
428  * List of handlers for the messages understood by this
429  * service.
430  */
431 static struct GNUNET_SERVER_MessageHandler handlers[] = {
432   {&handle_set, NULL, GNUNET_MESSAGE_TYPE_STATISTICS_SET, 0},
433   {&handle_get, NULL, GNUNET_MESSAGE_TYPE_STATISTICS_GET, 0},
434   {NULL, NULL, 0, 0}
435 };
436
437
438 /**
439  * Task run during shutdown.
440  *
441  * @param cls unused
442  * @param tc unused
443  */
444 static void
445 shutdown_task (void *cls,
446                const struct GNUNET_SCHEDULER_TaskContext *tc)
447 {
448   save ();
449 }
450
451
452 /**
453  * Process statistics requests.
454  *
455  * @param cls closure
456  * @param sched scheduler to use
457  * @param server the initialized server
458  * @param c configuration to use
459  */
460 static void
461 run (void *cls,
462      struct GNUNET_SCHEDULER_Handle *sched,
463      struct GNUNET_SERVER_Handle *server,
464      const struct GNUNET_CONFIGURATION_Handle *c)
465 {
466   cfg = c;
467   GNUNET_SERVER_add_handlers (server, handlers);
468   load (server);
469   GNUNET_SCHEDULER_add_delayed (sched,
470                                 GNUNET_TIME_UNIT_FOREVER_REL,
471                                 &shutdown_task,
472                                 NULL);
473 }
474
475
476 /**
477  * The main function for the statistics service.
478  *
479  * @param argc number of arguments from the command line
480  * @param argv command line arguments
481  * @return 0 ok, 1 on error
482  */
483 int
484 main (int argc, char *const *argv)
485 {
486   return (GNUNET_OK ==
487           GNUNET_SERVICE_run (argc,
488                               argv,
489                               "statistics", &run, NULL)) ? 0 : 1;
490 }
491
492 /* end of gnunet-service-statistics.c */