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