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