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