missing function reference
[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   size_t size;
222
223   size =
224     sizeof (struct GNUNET_STATISTICS_ReplyMessage) + strlen (e->service) + 1 +
225     strlen (e->name) + 1;
226   GNUNET_assert (size < GNUNET_SERVER_MAX_MESSAGE_SIZE);
227   m = GNUNET_malloc (size);
228   m->header.type = htons (GNUNET_MESSAGE_TYPE_STATISTICS_VALUE);
229   m->header.size = htons (size);
230   m->uid = htonl (e->uid);
231   if (e->persistent)
232     m->uid |= htonl (GNUNET_STATISTICS_PERSIST_BIT);
233   m->value = GNUNET_htonll (e->value);
234   size -= sizeof (struct GNUNET_STATISTICS_ReplyMessage);
235   GNUNET_assert (size == GNUNET_STRINGS_buffer_fill ((char *) &m[1],
236                                                      size,
237                                                      2, e->service, e->name));
238 #if DEBUG_STATISTICS
239   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
240               "Transmitting value for `%s:%s': %llu\n",
241               e->service, e->name, e->value);
242 #endif
243   GNUNET_SERVER_transmit_context_append_message (tc, &m->header);
244   GNUNET_free (m);
245 }
246
247
248 /**
249  * Does this entry match the request?
250  */
251 static int
252 matches (const struct StatsEntry *e, const char *service, const char *name)
253 {
254   return ((0 == strlen (service)) ||
255           (0 == strcmp (service, e->service)))
256     && ((0 == strlen (name)) || (0 == strcmp (name, e->name)));
257 }
258
259
260 /**
261  * Handle GET-message.
262  *
263  * @param cls closure
264  * @param client identification of the client
265  * @param message the actual message
266  * @return GNUNET_OK to keep the connection open,
267  *         GNUNET_SYSERR to close it (signal serious error)
268  */
269 static void
270 handle_get (void *cls,
271             struct GNUNET_SERVER_Client *client,
272             const struct GNUNET_MessageHeader *message)
273 {
274   char *service;
275   char *name;
276   struct StatsEntry *pos;
277   struct GNUNET_SERVER_TransmitContext *tc;
278   size_t size;
279
280   size = ntohs (message->size) - sizeof (struct GNUNET_MessageHeader);
281   if (size != GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
282                                               size, 2, &service, &name))
283     {
284       GNUNET_break (0);
285       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
286       return;
287     }
288 #if DEBUG_STATISTICS
289   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
290               "Received request for statistics on `%s:%s'\n",
291               strlen (service) ? service : "*", strlen (name) ? name : "*");
292 #endif
293   tc = GNUNET_SERVER_transmit_context_create (client);
294   pos = start;
295   while (pos != NULL)
296     {
297       if (matches (pos, service, name))
298         transmit (tc, pos);
299       pos = pos->next;
300     }
301   GNUNET_SERVER_transmit_context_append_data (tc, NULL, 0,
302                                               GNUNET_MESSAGE_TYPE_STATISTICS_END);
303   GNUNET_SERVER_transmit_context_run (tc, GNUNET_TIME_UNIT_FOREVER_REL);
304 }
305
306
307 /**
308  * Handle SET-message.
309  *
310  * @param cls closure
311  * @param client identification of the client
312  * @param message the actual message
313  */
314 static void
315 handle_set (void *cls,
316             struct GNUNET_SERVER_Client *client,
317             const struct GNUNET_MessageHeader *message)
318 {
319   char *service;
320   char *name;
321   uint16_t msize;
322   uint16_t size;
323   const struct GNUNET_STATISTICS_SetMessage *msg;
324   struct StatsEntry *pos;
325   struct StatsEntry *prev;
326   uint32_t flags;
327   uint64_t value;
328   int64_t delta;
329
330   msize = ntohs (message->size);
331   if (msize < sizeof (struct GNUNET_STATISTICS_SetMessage))
332     {
333       GNUNET_break (0);
334       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
335       return;
336     }
337   size = msize - sizeof (struct GNUNET_STATISTICS_SetMessage);
338   msg = (const struct GNUNET_STATISTICS_SetMessage *) message;
339
340   if (size != GNUNET_STRINGS_buffer_tokenize ((const char *) &msg[1],
341                                               size, 2, &service, &name))
342     {
343       GNUNET_break (0);
344       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
345       return;
346     }
347 #if DEBUG_STATISTICS
348   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
349               "Received request to update statistic on `%s:%s'\n",
350               service, name);
351 #endif
352   flags = ntohl (msg->flags);
353   value = GNUNET_ntohll (msg->value);
354   pos = start;
355   prev = NULL;
356   while (pos != NULL)
357     {
358       if (matches (pos, service, name))
359         {
360           if ((flags & GNUNET_STATISTICS_SETFLAG_RELATIVE) == 0)
361             {
362               pos->value = value;
363             }
364           else
365             {
366               delta = (int64_t) value;
367               if ((delta < 0) && (pos->value < -delta))
368                 {
369                   pos->value = 0;
370                 }
371               else
372                 {
373                   GNUNET_break ((delta <= 0) ||
374                                 (pos->value + delta > pos->value));
375                   pos->value += delta;
376                 }
377             }
378           pos->msg->value = GNUNET_htonll (pos->value);
379           pos->msg->flags = msg->flags;
380           pos->persistent =
381             (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
382           if (prev != NULL)
383             {
384               /* move to front for faster setting next time! */
385               prev->next = pos->next;
386               pos->next = start;
387               start = pos;
388             }
389 #if DEBUG_STATISTICS
390           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
391                       "Statistic `%s:%s' updated to value %llu.\n",
392                       service, name, pos->value);
393 #endif
394           GNUNET_SERVER_receive_done (client, GNUNET_OK);
395           return;
396         }
397       prev = pos;
398       pos = pos->next;
399     }
400   pos = GNUNET_malloc (sizeof (struct StatsEntry) + msize);
401   pos->next = start;
402   if (((flags & GNUNET_STATISTICS_SETFLAG_RELATIVE) == 0) ||
403       (0 < (int64_t) GNUNET_ntohll (msg->value)))
404     pos->value = GNUNET_ntohll (msg->value);
405   pos->uid = uidgen++;
406   pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
407   pos->msg = (void *) &pos[1];
408   memcpy (pos->msg, message, ntohs (message->size));
409   pos->service = (const char *) &pos->msg[1];
410   pos->name = &pos->service[strlen (pos->service) + 1];
411
412   start = pos;
413 #if DEBUG_STATISTICS
414   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
415               "New statistic on `%s:%s' with value %llu created.\n",
416               service, name, pos->value);
417 #endif
418   GNUNET_SERVER_receive_done (client, GNUNET_OK);
419 }
420
421
422 /**
423  * List of handlers for the messages understood by this
424  * service.
425  */
426 static struct GNUNET_SERVER_MessageHandler handlers[] = {
427   {&handle_set, NULL, GNUNET_MESSAGE_TYPE_STATISTICS_SET, 0},
428   {&handle_get, NULL, GNUNET_MESSAGE_TYPE_STATISTICS_GET, 0},
429   {NULL, NULL, 0, 0}
430 };
431
432
433 /**
434  * Task run during shutdown.
435  *
436  * @param cls unused
437  * @param tc unused
438  */
439 static void
440 shutdown_task (void *cls,
441                const struct GNUNET_SCHEDULER_TaskContext *tc)
442 {
443   save ();
444 }
445
446
447 /**
448  * Process statistics requests.
449  *
450  * @param cls closure
451  * @param sched scheduler to use
452  * @param server the initialized server
453  * @param c configuration to use
454  */
455 static void
456 run (void *cls,
457      struct GNUNET_SCHEDULER_Handle *sched,
458      struct GNUNET_SERVER_Handle *server,
459      const struct GNUNET_CONFIGURATION_Handle *c)
460 {
461   cfg = c;
462   GNUNET_SERVER_add_handlers (server, handlers);
463   load (server);
464   GNUNET_SCHEDULER_add_delayed (sched,
465                                 GNUNET_TIME_UNIT_FOREVER_REL,
466                                 &shutdown_task,
467                                 NULL);
468 }
469
470
471 /**
472  * The main function for the statistics service.
473  *
474  * @param argc number of arguments from the command line
475  * @param argv command line arguments
476  * @return 0 ok, 1 on error
477  */
478 int
479 main (int argc, char *const *argv)
480 {
481   return (GNUNET_OK ==
482           GNUNET_SERVICE_run (argc,
483                               argv,
484                               "statistics",
485                               GNUNET_SERVICE_OPTION_NONE,
486                               &run, NULL)) ? 0 : 1;
487 }
488
489 /* end of gnunet-service-statistics.c */