no debug
[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 3, 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  * 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  * @param server handle to the server context
101  */
102 static void
103 load (struct GNUNET_SERVER_Handle *server)
104 {
105   char *fn;
106   struct GNUNET_DISK_FileHandle *fh;
107   struct GNUNET_DISK_MapHandle *mh;
108   struct stat sb;
109   char *buf;
110   size_t off;
111   const struct GNUNET_MessageHeader *msg;
112
113   fn = GNUNET_DISK_get_home_filename (cfg,
114                                       "statistics", "statistics.data", NULL);
115   if (fn == NULL)
116     return;
117   if ((0 != stat (fn, &sb)) || (sb.st_size == 0))
118     {
119       GNUNET_free (fn);
120       return;
121     }
122   fh = GNUNET_DISK_file_open (fn, GNUNET_DISK_OPEN_READ,
123                               GNUNET_DISK_PERM_NONE);
124   if (!fh)
125     {
126       GNUNET_free (fn);
127       return;
128     }
129   buf = GNUNET_DISK_file_map (fh, &mh, GNUNET_DISK_MAP_TYPE_READ, sb.st_size);
130   if (NULL == buf)
131     {
132       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "mmap", fn);
133       GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
134       GNUNET_free (fn);
135       return;
136     }
137   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
138               _("Loading %llu bytes of statistics from `%s'\n"),
139               (unsigned long long) sb.st_size, fn);
140   off = 0;
141   while (off + sizeof (struct GNUNET_MessageHeader) < sb.st_size)
142     {
143       msg = (const struct GNUNET_MessageHeader *) &buf[off];
144       if ((ntohs (msg->size) + off > sb.st_size) ||
145           (GNUNET_OK != GNUNET_SERVER_inject (server, NULL, msg)))
146         {
147           GNUNET_break (0);
148           break;
149         }
150       off += ntohs (msg->size);
151     }
152   GNUNET_break (GNUNET_OK == GNUNET_DISK_file_unmap (mh));
153   GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
154   GNUNET_free (fn);
155 }
156
157 /**
158  * Write persistent statistics to disk.
159  */
160 static void
161 save ()       
162 {
163   struct StatsEntry *pos;
164   char *fn;
165   struct GNUNET_DISK_FileHandle *fh;
166   uint16_t size;
167   unsigned long long total;
168
169   fh = NULL;
170   fn = GNUNET_DISK_get_home_filename (cfg,
171                                       "statistics", "statistics.data", NULL);
172   if (fn != NULL)
173     fh = GNUNET_DISK_file_open (fn, GNUNET_DISK_OPEN_WRITE
174         | GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_TRUNCATE,
175         GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE);
176   total = 0;
177   while (NULL != (pos = start))
178     {
179       start = pos->next;
180       if ((pos->persistent) && (NULL != fh))
181         {
182           size = htons (pos->msg->header.size);
183           if (size != GNUNET_DISK_file_write (fh, pos->msg, size))
184             {
185               GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
186                                         "write", fn);
187               GNUNET_DISK_file_close (fh);
188               fh = NULL;
189             }
190           else
191             total += size;
192         }
193       GNUNET_free (pos);
194     }
195   if (NULL != fh)
196     {
197       GNUNET_DISK_file_close (fh);
198       if (total == 0)
199         GNUNET_break (0 == UNLINK (fn));
200       else
201         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
202                     _("Wrote %llu bytes of statistics to `%s'\n"), total, fn);
203     }
204   GNUNET_free_non_null (fn);
205 }
206
207
208 /**
209  * Transmit the given stats value.
210  */
211 static void
212 transmit (struct GNUNET_SERVER_TransmitContext *tc,
213           const struct StatsEntry *e)
214 {
215   struct GNUNET_STATISTICS_ReplyMessage *m;
216   size_t size;
217
218   size =
219     sizeof (struct GNUNET_STATISTICS_ReplyMessage) + strlen (e->service) + 1 +
220     strlen (e->name) + 1;
221   GNUNET_assert (size < GNUNET_SERVER_MAX_MESSAGE_SIZE);
222   m = GNUNET_malloc (size);
223   m->header.type = htons (GNUNET_MESSAGE_TYPE_STATISTICS_VALUE);
224   m->header.size = htons (size);
225   m->uid = htonl (e->uid);
226   if (e->persistent)
227     m->uid |= htonl (GNUNET_STATISTICS_PERSIST_BIT);
228   m->value = GNUNET_htonll (e->value);
229   size -= sizeof (struct GNUNET_STATISTICS_ReplyMessage);
230   GNUNET_assert (size == GNUNET_STRINGS_buffer_fill ((char *) &m[1],
231                                                      size,
232                                                      2, e->service, e->name));
233 #if DEBUG_STATISTICS
234   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
235               "Transmitting value for `%s:%s': %llu\n",
236               e->service, e->name, e->value);
237 #endif
238   GNUNET_SERVER_transmit_context_append_message (tc, &m->header);
239   GNUNET_free (m);
240 }
241
242
243 /**
244  * Does this entry match the request?
245  */
246 static int
247 matches (const struct StatsEntry *e, const char *service, const char *name)
248 {
249   return ((0 == strlen (service)) ||
250           (0 == strcmp (service, e->service)))
251     && ((0 == strlen (name)) || (0 == strcmp (name, e->name)));
252 }
253
254
255 /**
256  * Handle GET-message.
257  *
258  * @param cls closure
259  * @param client identification of the client
260  * @param message the actual message
261  * @return GNUNET_OK to keep the connection open,
262  *         GNUNET_SYSERR to close it (signal serious error)
263  */
264 static void
265 handle_get (void *cls,
266             struct GNUNET_SERVER_Client *client,
267             const struct GNUNET_MessageHeader *message)
268 {
269   char *service;
270   char *name;
271   struct StatsEntry *pos;
272   struct GNUNET_SERVER_TransmitContext *tc;
273   size_t size;
274
275   size = ntohs (message->size) - sizeof (struct GNUNET_MessageHeader);
276   if (size != GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
277                                               size, 2, &service, &name))
278     {
279       GNUNET_break (0);
280       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
281       return;
282     }
283 #if DEBUG_STATISTICS
284   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
285               "Received request for statistics on `%s:%s'\n",
286               strlen (service) ? service : "*", strlen (name) ? name : "*");
287 #endif
288   tc = GNUNET_SERVER_transmit_context_create (client);
289   pos = start;
290   while (pos != NULL)
291     {
292       if (matches (pos, service, name))
293         transmit (tc, pos);
294       pos = pos->next;
295     }
296   GNUNET_SERVER_transmit_context_append_data (tc, NULL, 0,
297                                               GNUNET_MESSAGE_TYPE_STATISTICS_END);
298   GNUNET_SERVER_transmit_context_run (tc, GNUNET_TIME_UNIT_FOREVER_REL);
299 }
300
301
302 /**
303  * Handle SET-message.
304  *
305  * @param cls closure
306  * @param client identification of the client
307  * @param message the actual message
308  */
309 static void
310 handle_set (void *cls,
311             struct GNUNET_SERVER_Client *client,
312             const struct GNUNET_MessageHeader *message)
313 {
314   /**
315    * Counter used to generate unique values.
316    */
317   static uint32_t uidgen;
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  * Task run during shutdown.
424  *
425  * @param cls unused
426  * @param tc unused
427  */
428 static void
429 shutdown_task (void *cls,
430                const struct GNUNET_SCHEDULER_TaskContext *tc)
431 {
432   save ();
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 c 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 *c)
449 {
450   static const struct GNUNET_SERVER_MessageHandler handlers[] = {
451     {&handle_set, NULL, GNUNET_MESSAGE_TYPE_STATISTICS_SET, 0},
452     {&handle_get, NULL, GNUNET_MESSAGE_TYPE_STATISTICS_GET, 0},
453     {NULL, NULL, 0, 0}
454   };
455   cfg = c;
456   GNUNET_SERVER_add_handlers (server, handlers);
457   load (server);
458   GNUNET_SCHEDULER_add_delayed (sched,
459                                 GNUNET_TIME_UNIT_FOREVER_REL,
460                                 &shutdown_task,
461                                 NULL);
462 }
463
464
465 /**
466  * The main function for the statistics service.
467  *
468  * @param argc number of arguments from the command line
469  * @param argv command line arguments
470  * @return 0 ok, 1 on error
471  */
472 int
473 main (int argc, char *const *argv)
474 {
475   return (GNUNET_OK ==
476           GNUNET_SERVICE_run (argc,
477                               argv,
478                               "statistics",
479                               GNUNET_SERVICE_OPTION_NONE,
480                               &run, NULL)) ? 0 : 1;
481 }
482
483 /* end of gnunet-service-statistics.c */