e4c3fe040d798a64c4f0cbf4e27e76f875a90f3e
[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       const 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, 
161       const struct GNUNET_CONFIGURATION_Handle *cfg)
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   struct GNUNET_MessageHeader *h;
217   size_t size;
218   uint16_t msize;
219
220   size =
221     sizeof (struct GNUNET_STATISTICS_ReplyMessage) + strlen (e->service) + 1 +
222     strlen (e->name) + 1;
223   GNUNET_assert (size < GNUNET_SERVER_MAX_MESSAGE_SIZE);
224   msize = size - sizeof (struct GNUNET_MessageHeader);
225   m = GNUNET_malloc (size);
226   m->uid = htonl (e->uid);
227   if (e->persistent)
228     m->uid |= htonl (GNUNET_STATISTICS_PERSIST_BIT);
229   m->value = GNUNET_htonll (e->value);
230   size -= sizeof (struct GNUNET_STATISTICS_ReplyMessage);
231   GNUNET_assert (size == GNUNET_STRINGS_buffer_fill ((char *) &m[1],
232                                                      size,
233                                                      2, e->service, e->name));
234 #if DEBUG_STATISTICS
235   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
236               "Transmitting value for `%s:%s': %llu\n",
237               e->service, e->name, e->value);
238 #endif
239   h = &m->header;
240   GNUNET_SERVER_transmit_context_append (tc,
241                                          &h[1],
242                                          msize,
243                                          GNUNET_MESSAGE_TYPE_STATISTICS_VALUE);
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 (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  * Process statistics requests.
435  *
436  * @param cls closure
437  * @param sched scheduler to use
438  * @param server the initialized server
439  * @param cfg configuration to use
440  */
441 static void
442 run (void *cls,
443      struct GNUNET_SCHEDULER_Handle *sched,
444      struct GNUNET_SERVER_Handle *server,
445      const struct GNUNET_CONFIGURATION_Handle *cfg)
446 {
447   GNUNET_SERVER_add_handlers (server, handlers);
448   load (server, cfg);
449 }
450
451
452 /**
453  * The main function for the statistics service.
454  *
455  * @param argc number of arguments from the command line
456  * @param argv command line arguments
457  * @return 0 ok, 1 on error
458  */
459 int
460 main (int argc, char *const *argv)
461 {
462   return (GNUNET_OK ==
463           GNUNET_SERVICE_run (argc,
464                               argv,
465                               "statistics", &run, NULL, &save, NULL)) ? 0 : 1;
466 }
467
468 /* end of gnunet-service-statistics.c */