20dd105866141f4a832b1abe2a526eb3bda57139
[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  * Linked list of our active statistics.
87  */
88 static struct StatsEntry *start;
89
90 /**
91  * Counter used to generate unique values.
92  */
93 static uint32_t uidgen;
94
95 /**
96  * Load persistent values from disk.  Disk format is
97  * exactly the same format that we also use for
98  * setting the values over the network.
99  */
100 static void
101 load (struct GNUNET_SERVER_Handle *server,
102       const struct GNUNET_CONFIGURATION_Handle *cfg)
103 {
104   char *fn;
105   struct GNUNET_DISK_FileHandle *fh;
106   struct GNUNET_DISK_MapHandle *mh;
107   struct stat sb;
108   char *buf;
109   size_t off;
110   const struct GNUNET_MessageHeader *msg;
111
112   fn = GNUNET_DISK_get_home_filename (cfg,
113                                       "statistics", "statistics.data", NULL);
114   if (fn == NULL)
115     return;
116   if ((0 != stat (fn, &sb)) || (sb.st_size == 0))
117     {
118       GNUNET_free (fn);
119       return;
120     }
121   fh = GNUNET_DISK_file_open (fn, GNUNET_DISK_OPEN_READ);
122   if (!fh)
123     {
124       GNUNET_free (fn);
125       return;
126     }
127   buf = GNUNET_DISK_file_map (fh, &mh, GNUNET_DISK_MAP_TYPE_READ, sb.st_size);
128   if (NULL == buf)
129     {
130       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "mmap", fn);
131       GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
132       GNUNET_free (fn);
133       return;
134     }
135   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
136               _("Loading %llu bytes of statistics from `%s'\n"),
137               (unsigned long long) sb.st_size, fn);
138   off = 0;
139   while (off + sizeof (struct GNUNET_MessageHeader) < sb.st_size)
140     {
141       msg = (const struct GNUNET_MessageHeader *) &buf[off];
142       if ((ntohs (msg->size) + off > sb.st_size) ||
143           (GNUNET_OK != GNUNET_SERVER_inject (server, NULL, msg)))
144         {
145           GNUNET_break (0);
146           break;
147         }
148       off += ntohs (msg->size);
149     }
150   GNUNET_break (GNUNET_OK == GNUNET_DISK_file_unmap (mh));
151   GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
152   GNUNET_free (fn);
153 }
154
155
156 /**
157  * Write persistent statistics to disk.
158  *
159  * @param cls closure
160  * @param cfg configuration to use
161  */
162 static void
163 save (void *cls, 
164       const struct GNUNET_CONFIGURATION_Handle *cfg)
165 {
166   struct StatsEntry *pos;
167   char *fn;
168   struct GNUNET_DISK_FileHandle *fh;
169   uint16_t size;
170   unsigned long long total;
171
172   fh = NULL;
173   fn = GNUNET_DISK_get_home_filename (cfg,
174                                       "statistics", "statistics.data", NULL);
175   if (fn != NULL)
176     fh = GNUNET_DISK_file_open (fn, GNUNET_DISK_OPEN_WRITE
177         | GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_TRUNCATE,
178         GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE);
179   total = 0;
180   while (NULL != (pos = start))
181     {
182       start = pos->next;
183       if ((pos->persistent) && (NULL != fh))
184         {
185           size = htons (pos->msg->header.size);
186           if (size != GNUNET_DISK_file_write (fh, pos->msg, size))
187             {
188               GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
189                                         "write", fn);
190               GNUNET_DISK_file_close (fh);
191               fh = NULL;
192             }
193           else
194             total += size;
195         }
196       GNUNET_free (pos);
197     }
198   if (NULL != fh)
199     {
200       GNUNET_DISK_file_close (fh);
201       if (total == 0)
202         GNUNET_break (0 == UNLINK (fn));
203       else
204         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
205                     _("Wrote %llu bytes of statistics to `%s'\n"), total, fn);
206     }
207   GNUNET_free_non_null (fn);
208 }
209
210
211 /**
212  * Transmit the given stats value.
213  */
214 static void
215 transmit (struct GNUNET_SERVER_TransmitContext *tc,
216           const struct StatsEntry *e)
217 {
218   struct GNUNET_STATISTICS_ReplyMessage *m;
219   struct GNUNET_MessageHeader *h;
220   size_t size;
221   uint16_t msize;
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   msize = size - sizeof (struct GNUNET_MessageHeader);
228   m = GNUNET_malloc (size);
229   m->uid = htonl (e->uid);
230   if (e->persistent)
231     m->uid |= htonl (GNUNET_STATISTICS_PERSIST_BIT);
232   m->value = GNUNET_htonll (e->value);
233   size -= sizeof (struct GNUNET_STATISTICS_ReplyMessage);
234   GNUNET_assert (size == GNUNET_STRINGS_buffer_fill ((char *) &m[1],
235                                                      size,
236                                                      2, e->service, e->name));
237 #if DEBUG_STATISTICS
238   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
239               "Transmitting value for `%s:%s': %llu\n",
240               e->service, e->name, e->value);
241 #endif
242   h = &m->header;
243   GNUNET_SERVER_transmit_context_append (tc,
244                                          &h[1],
245                                          msize,
246                                          GNUNET_MESSAGE_TYPE_STATISTICS_VALUE);
247   GNUNET_free (m);
248 }
249
250
251 /**
252  * Does this entry match the request?
253  */
254 static int
255 matches (const struct StatsEntry *e, const char *service, const char *name)
256 {
257   return ((0 == strlen (service)) ||
258           (0 == strcmp (service, e->service)))
259     && ((0 == strlen (name)) || (0 == strcmp (name, e->name)));
260 }
261
262
263 /**
264  * Handle GET-message.
265  *
266  * @param cls closure
267  * @param client identification of the client
268  * @param message the actual message
269  * @return GNUNET_OK to keep the connection open,
270  *         GNUNET_SYSERR to close it (signal serious error)
271  */
272 static void
273 handle_get (void *cls,
274             struct GNUNET_SERVER_Client *client,
275             const struct GNUNET_MessageHeader *message)
276 {
277   char *service;
278   char *name;
279   struct StatsEntry *pos;
280   struct GNUNET_SERVER_TransmitContext *tc;
281   size_t size;
282
283   size = ntohs (message->size) - sizeof (struct GNUNET_MessageHeader);
284   if (size != GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
285                                               size, 2, &service, &name))
286     {
287       GNUNET_break (0);
288       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
289       return;
290     }
291 #if DEBUG_STATISTICS
292   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
293               "Received request for statistics on `%s:%s'\n",
294               strlen (service) ? service : "*", strlen (name) ? name : "*");
295 #endif
296   tc = GNUNET_SERVER_transmit_context_create (client);
297   pos = start;
298   while (pos != NULL)
299     {
300       if (matches (pos, service, name))
301         transmit (tc, pos);
302       pos = pos->next;
303     }
304   GNUNET_SERVER_transmit_context_append (tc, NULL, 0,
305                                          GNUNET_MESSAGE_TYPE_STATISTICS_END);
306   GNUNET_SERVER_transmit_context_run (tc, GNUNET_TIME_UNIT_FOREVER_REL);
307 }
308
309
310 /**
311  * Handle SET-message.
312  *
313  * @param cls closure
314  * @param client identification of the client
315  * @param message the actual message
316  */
317 static void
318 handle_set (void *cls,
319             struct GNUNET_SERVER_Client *client,
320             const struct GNUNET_MessageHeader *message)
321 {
322   char *service;
323   char *name;
324   uint16_t msize;
325   uint16_t size;
326   const struct GNUNET_STATISTICS_SetMessage *msg;
327   struct StatsEntry *pos;
328   struct StatsEntry *prev;
329   uint32_t flags;
330   uint64_t value;
331   int64_t delta;
332
333   msize = ntohs (message->size);
334   if (msize < sizeof (struct GNUNET_STATISTICS_SetMessage))
335     {
336       GNUNET_break (0);
337       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
338       return;
339     }
340   size = msize - sizeof (struct GNUNET_STATISTICS_SetMessage);
341   msg = (const struct GNUNET_STATISTICS_SetMessage *) message;
342
343   if (size != GNUNET_STRINGS_buffer_tokenize ((const char *) &msg[1],
344                                               size, 2, &service, &name))
345     {
346       GNUNET_break (0);
347       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
348       return;
349     }
350 #if DEBUG_STATISTICS
351   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
352               "Received request to update statistic on `%s:%s'\n",
353               service, name);
354 #endif
355   flags = ntohl (msg->flags);
356   value = GNUNET_ntohll (msg->value);
357   pos = start;
358   prev = NULL;
359   while (pos != NULL)
360     {
361       if (matches (pos, service, name))
362         {
363           if ((flags & GNUNET_STATISTICS_SETFLAG_RELATIVE) == 0)
364             {
365               pos->value = value;
366             }
367           else
368             {
369               delta = (int64_t) value;
370               if ((delta < 0) && (pos->value < -delta))
371                 {
372                   pos->value = 0;
373                 }
374               else
375                 {
376                   GNUNET_break ((delta <= 0) ||
377                                 (pos->value + delta > pos->value));
378                   pos->value += delta;
379                 }
380             }
381           pos->msg->value = GNUNET_htonll (pos->value);
382           pos->msg->flags = msg->flags;
383           pos->persistent =
384             (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
385           if (prev != NULL)
386             {
387               /* move to front for faster setting next time! */
388               prev->next = pos->next;
389               pos->next = start;
390               start = pos;
391             }
392 #if DEBUG_STATISTICS
393           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
394                       "Statistic `%s:%s' updated to value %llu.\n",
395                       service, name, pos->value);
396 #endif
397           GNUNET_SERVER_receive_done (client, GNUNET_OK);
398           return;
399         }
400       prev = pos;
401       pos = pos->next;
402     }
403   pos = GNUNET_malloc (sizeof (struct StatsEntry) + msize);
404   pos->next = start;
405   if (((flags & GNUNET_STATISTICS_SETFLAG_RELATIVE) == 0) ||
406       (0 < (int64_t) GNUNET_ntohll (msg->value)))
407     pos->value = GNUNET_ntohll (msg->value);
408   pos->uid = uidgen++;
409   pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
410   pos->msg = (void *) &pos[1];
411   memcpy (pos->msg, message, ntohs (message->size));
412   pos->service = (const char *) &pos->msg[1];
413   pos->name = &pos->service[strlen (pos->service) + 1];
414
415   start = pos;
416 #if DEBUG_STATISTICS
417   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
418               "New statistic on `%s:%s' with value %llu created.\n",
419               service, name, pos->value);
420 #endif
421   GNUNET_SERVER_receive_done (client, GNUNET_OK);
422 }
423
424
425 /**
426  * List of handlers for the messages understood by this
427  * service.
428  */
429 static struct GNUNET_SERVER_MessageHandler handlers[] = {
430   {&handle_set, NULL, GNUNET_MESSAGE_TYPE_STATISTICS_SET, 0},
431   {&handle_get, NULL, GNUNET_MESSAGE_TYPE_STATISTICS_GET, 0},
432   {NULL, NULL, 0, 0}
433 };
434
435
436 /**
437  * Process statistics requests.
438  *
439  * @param cls closure
440  * @param sched scheduler to use
441  * @param server the initialized server
442  * @param cfg configuration to use
443  */
444 static void
445 run (void *cls,
446      struct GNUNET_SCHEDULER_Handle *sched,
447      struct GNUNET_SERVER_Handle *server,
448      const struct GNUNET_CONFIGURATION_Handle *cfg)
449 {
450   GNUNET_SERVER_add_handlers (server, handlers);
451   load (server, cfg);
452 }
453
454
455 /**
456  * The main function for the statistics service.
457  *
458  * @param argc number of arguments from the command line
459  * @param argv command line arguments
460  * @return 0 ok, 1 on error
461  */
462 int
463 main (int argc, char *const *argv)
464 {
465   return (GNUNET_OK ==
466           GNUNET_SERVICE_run (argc,
467                               argv,
468                               "statistics", &run, NULL, &save, NULL)) ? 0 : 1;
469 }
470
471 /* end of gnunet-service-statistics.c */