diff options
Diffstat (limited to 'src/statistics/gnunet-service-statistics.c')
-rw-r--r-- | src/statistics/gnunet-service-statistics.c | 470 |
1 files changed, 470 insertions, 0 deletions
diff --git a/src/statistics/gnunet-service-statistics.c b/src/statistics/gnunet-service-statistics.c new file mode 100644 index 000000000..0e7b4853d --- /dev/null +++ b/src/statistics/gnunet-service-statistics.c | |||
@@ -0,0 +1,470 @@ | |||
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 server the server handling the message | ||
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_Handle *server, | ||
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 server the server handling the message | ||
312 | * @param client identification of the client | ||
313 | * @param message the actual message | ||
314 | */ | ||
315 | static void | ||
316 | handle_set (void *cls, | ||
317 | struct GNUNET_SERVER_Handle *server, | ||
318 | struct GNUNET_SERVER_Client *client, | ||
319 | const struct GNUNET_MessageHeader *message) | ||
320 | { | ||
321 | char *service; | ||
322 | char *name; | ||
323 | uint16_t msize; | ||
324 | uint16_t size; | ||
325 | const struct GNUNET_STATISTICS_SetMessage *msg; | ||
326 | struct StatsEntry *pos; | ||
327 | struct StatsEntry *prev; | ||
328 | uint32_t flags; | ||
329 | uint64_t value; | ||
330 | int64_t delta; | ||
331 | |||
332 | msize = ntohs (message->size); | ||
333 | if (msize < sizeof (struct GNUNET_STATISTICS_SetMessage)) | ||
334 | { | ||
335 | GNUNET_break (0); | ||
336 | GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); | ||
337 | return; | ||
338 | } | ||
339 | size = msize - sizeof (struct GNUNET_STATISTICS_SetMessage); | ||
340 | msg = (const struct GNUNET_STATISTICS_SetMessage *) message; | ||
341 | |||
342 | if (size != GNUNET_STRINGS_buffer_tokenize ((const char *) &msg[1], | ||
343 | size, 2, &service, &name)) | ||
344 | { | ||
345 | GNUNET_break (0); | ||
346 | GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); | ||
347 | return; | ||
348 | } | ||
349 | #if DEBUG_STATISTICS | ||
350 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
351 | "Received request to update statistic on `%s:%s'\n", | ||
352 | service, name); | ||
353 | #endif | ||
354 | flags = ntohl (msg->flags); | ||
355 | value = GNUNET_ntohll (msg->value); | ||
356 | pos = start; | ||
357 | prev = NULL; | ||
358 | while (pos != NULL) | ||
359 | { | ||
360 | if (matches (pos, service, name)) | ||
361 | { | ||
362 | if ((flags & GNUNET_STATISTICS_SETFLAG_RELATIVE) == 0) | ||
363 | { | ||
364 | pos->value = value; | ||
365 | } | ||
366 | else | ||
367 | { | ||
368 | delta = (int64_t) value; | ||
369 | if ((delta < 0) && (pos->value < -delta)) | ||
370 | { | ||
371 | pos->value = 0; | ||
372 | } | ||
373 | else | ||
374 | { | ||
375 | GNUNET_break ((delta <= 0) || | ||
376 | (pos->value + delta > pos->value)); | ||
377 | pos->value += delta; | ||
378 | } | ||
379 | } | ||
380 | pos->msg->value = GNUNET_htonll (pos->value); | ||
381 | pos->msg->flags = msg->flags; | ||
382 | pos->persistent = | ||
383 | (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT)); | ||
384 | if (prev != NULL) | ||
385 | { | ||
386 | /* move to front for faster setting next time! */ | ||
387 | prev->next = pos->next; | ||
388 | pos->next = start; | ||
389 | start = pos; | ||
390 | } | ||
391 | #if DEBUG_STATISTICS | ||
392 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
393 | "Statistic `%s:%s' updated to value %llu.\n", | ||
394 | service, name, pos->value); | ||
395 | #endif | ||
396 | GNUNET_SERVER_receive_done (client, GNUNET_OK); | ||
397 | return; | ||
398 | } | ||
399 | prev = pos; | ||
400 | pos = pos->next; | ||
401 | } | ||
402 | pos = GNUNET_malloc (sizeof (struct StatsEntry) + msize); | ||
403 | pos->next = start; | ||
404 | if (((flags & GNUNET_STATISTICS_SETFLAG_RELATIVE) == 0) || | ||
405 | (0 < (int64_t) GNUNET_ntohll (msg->value))) | ||
406 | pos->value = GNUNET_ntohll (msg->value); | ||
407 | pos->uid = uidgen++; | ||
408 | pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT)); | ||
409 | pos->msg = (void *) &pos[1]; | ||
410 | memcpy (pos->msg, message, ntohs (message->size)); | ||
411 | pos->service = (const char *) &pos->msg[1]; | ||
412 | pos->name = &pos->service[strlen (pos->service) + 1]; | ||
413 | |||
414 | start = pos; | ||
415 | #if DEBUG_STATISTICS | ||
416 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
417 | "New statistic on `%s:%s' with value %llu created.\n", | ||
418 | service, name, pos->value); | ||
419 | #endif | ||
420 | GNUNET_SERVER_receive_done (client, GNUNET_OK); | ||
421 | } | ||
422 | |||
423 | |||
424 | /** | ||
425 | * List of handlers for the messages understood by this | ||
426 | * service. | ||
427 | */ | ||
428 | static struct GNUNET_SERVER_MessageHandler handlers[] = { | ||
429 | {&handle_set, NULL, GNUNET_MESSAGE_TYPE_STATISTICS_SET, 0}, | ||
430 | {&handle_get, NULL, GNUNET_MESSAGE_TYPE_STATISTICS_GET, 0}, | ||
431 | {NULL, NULL, 0, 0} | ||
432 | }; | ||
433 | |||
434 | |||
435 | /** | ||
436 | * Process statistics requests. | ||
437 | * | ||
438 | * @param cls closure | ||
439 | * @param sched scheduler to use | ||
440 | * @param server the initialized server | ||
441 | * @param cfg configuration to use | ||
442 | */ | ||
443 | static void | ||
444 | run (void *cls, | ||
445 | struct GNUNET_SCHEDULER_Handle *sched, | ||
446 | struct GNUNET_SERVER_Handle *server, | ||
447 | struct GNUNET_CONFIGURATION_Handle *cfg) | ||
448 | { | ||
449 | GNUNET_SERVER_add_handlers (server, handlers); | ||
450 | load (server, cfg); | ||
451 | } | ||
452 | |||
453 | |||
454 | /** | ||
455 | * The main function for the statistics service. | ||
456 | * | ||
457 | * @param argc number of arguments from the command line | ||
458 | * @param argv command line arguments | ||
459 | * @return 0 ok, 1 on error | ||
460 | */ | ||
461 | int | ||
462 | main (int argc, char *const *argv) | ||
463 | { | ||
464 | return (GNUNET_OK == | ||
465 | GNUNET_SERVICE_run (argc, | ||
466 | argv, | ||
467 | "statistics", &run, NULL, &save, NULL)) ? 0 : 1; | ||
468 | } | ||
469 | |||
470 | /* end of gnunet-service-statistics.c */ | ||