]> arthur.barton.de Git - netdata.git/blob - src/registry.c
registry: working prototype with load/save
[netdata.git] / src / registry.c
1 #ifdef HAVE_CONFIG_H
2 #include <config.h>
3 #endif
4
5 // gcc -O1 -ggdb -Wall -Wextra -I ../src/ -I ../ -o registry ../src/registry.c ../src/dictionary.o ../src/log.o ../src/avl.o ../src/common.o ../src/appconfig.o ../src/web_buffer.o ../src/storage_number.o  -pthread -luuid -lm -DHAVE_CONFIG_H -DVARLIB_DIR="\"/tmp\""
6
7 #include <uuid/uuid.h>
8 #include <inttypes.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <ctype.h>
12 #include <unistd.h>
13 #include <sys/stat.h>
14 #include <sys/types.h>
15 #include <errno.h>
16
17 #include "log.h"
18 #include "common.h"
19 #include "dictionary.h"
20 #include "appconfig.h"
21
22 #define REGISTRY_URL_FLAGS_DEFAULT 0x00
23 #define REGISTRY_URL_FLAGS_EXPIRED 0x01
24
25 #define DICTIONARY_FLAGS DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE | DICTIONARY_FLAG_NAME_LINK_DONT_CLONE
26
27 // ----------------------------------------------------------------------------
28 // COMMON structures
29
30 struct registry {
31         unsigned long long persons_count;
32         unsigned long long machines_count;
33         unsigned long long usages_count;
34         unsigned long long urls_count;
35         unsigned long long persons_urls_count;
36         unsigned long long machines_urls_count;
37
38         char *pathname;
39         char *db_filename;
40         char *log_filename;
41         FILE *registry_log_fp;
42
43         DICTIONARY *persons;    // dictionary of PERSON *, with key the PERSON.guid
44         DICTIONARY *machines;   // dictionary of MACHINE *, with key the MACHINE.guid
45         DICTIONARY *urls;               // dictionary of URL *, with key the URL.url
46
47 } registry;
48
49
50 // ----------------------------------------------------------------------------
51 // URL structures
52 // Save memory by de-duplicating URLs
53
54 struct url {
55         uint32_t links;
56         uint16_t len;
57         char url[1];
58 };
59 typedef struct url URL;
60
61
62 // ----------------------------------------------------------------------------
63 // MACHINE structures
64
65 // For each MACHINE-URL pair we keep this
66 struct machine_url {
67         URL *url;                                       // de-duplicated URL
68 //      DICTIONARY *persons;            // dictionary of PERSON *
69
70         uint8_t flags;
71         uint32_t first_t;                       // the first time we saw this
72         uint32_t last_t;                        // the last time we saw this
73         uint32_t usages;                        // how many times this has been accessed
74 };
75 typedef struct machine_url MACHINE_URL;
76
77 // A machine
78 struct machine {
79         char guid[36 + 1];
80
81         DICTIONARY *urls;                       // MACHINE_URL *
82
83         uint32_t first_t;                       // the first time we saw this
84         uint32_t last_t;                        // the last time we saw this
85         uint32_t usages;                        // how many times this has been accessed
86 };
87 typedef struct machine MACHINE;
88
89
90 // ----------------------------------------------------------------------------
91 // PERSON structures
92
93 // for each PERSON-URL pair we keep this
94 struct person_url {
95         URL *url;                               // de-duplicated URL
96         MACHINE *machine;
97
98         uint8_t flags;
99         uint32_t first_t;                       // the first time we saw this
100         uint32_t last_t;                        // the last time we saw this
101         uint32_t usages;                        // how many times this has been accessed
102 };
103 typedef struct person_url PERSON_URL;
104
105 // A person
106 struct person {
107         char guid[36 + 1];
108
109         DICTIONARY *urls; // PERSON_URL *
110
111         uint32_t first_t;                       // the first time we saw this
112         uint32_t last_t;                        // the last time we saw this
113         uint32_t usages;                        // how many times this has been accessed
114 };
115 typedef struct person PERSON;
116
117 extern PERSON *registry_request(const char *person_guid, const char *machine_guid, const char *url, time_t when);
118
119 // ----------------------------------------------------------------------------
120 // URL
121
122 static inline URL *registry_url_allocate(const char *url) {
123         size_t len = strlen(url);
124
125         debug(D_REGISTRY, "Registry: registry_url_allocate('%s'): allocating %zu bytes", url, sizeof(URL) + len);
126         URL *u = malloc(sizeof(URL) + len);
127         if(!u) fatal("Cannot allocate %zu bytes for URL '%s'", sizeof(URL) + len);
128
129         strcpy(u->url, url);
130         u->len = len;
131         u->links = 0;
132
133         debug(D_REGISTRY, "Registry: registry_url_allocate('%s'): indexing it", url);
134         dictionary_set(registry.urls, u->url, u, sizeof(URL));
135
136         return u;
137 }
138
139 static inline URL *registry_url_get(const char *url) {
140         debug(D_REGISTRY, "Registry: registry_url_get('%s')", url);
141
142         URL *u = dictionary_get(registry.urls, url);
143         if(!u) {
144                 u = registry_url_allocate(url);
145                 registry.urls_count++;
146         }
147
148         return u;
149 }
150
151 static inline void registry_url_link(URL *u) {
152         u->links++;
153         debug(D_REGISTRY, "Registry: registry_url_unlink('%s'): This URL has now %u links", u->url, u->links);
154 }
155
156 static inline void registry_url_unlink(URL *u) {
157         u->links--;
158         if(!u->links) {
159                 debug(D_REGISTRY, "Registry: registry_url_unlink('%s'): No more links for this URL", u->url);
160                 dictionary_del(registry.urls, u->url);
161                 free(u);
162         }
163         else
164                 debug(D_REGISTRY, "Registry: registry_url_unlink('%s'): This URL has %u links left", u->url, u->links);
165 }
166
167
168 // ----------------------------------------------------------------------------
169 // MACHINE
170
171 static inline MACHINE *registry_machine_find(const char *machine_guid) {
172         debug(D_REGISTRY, "Registry: registry_machine_find('%s')", machine_guid);
173         return dictionary_get(registry.machines, machine_guid);
174 }
175
176 static inline MACHINE_URL *registry_machine_url_allocate(MACHINE *m, URL *u, time_t when) {
177         debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s'): allocating %zu bytes", m->guid, u->url, sizeof(MACHINE_URL));
178
179         MACHINE_URL *mu = malloc(sizeof(MACHINE_URL));
180         if(!mu) fatal("registry_machine_link_to_url('%s', '%s'): cannot allocate %zu bytes.", m->guid, u->url, sizeof(MACHINE_URL));
181
182         // mu->persons = dictionary_create(DICTIONARY_FLAGS);
183         // dictionary_set(mu->persons, p->guid, p, sizeof(PERSON));
184
185         mu->first_t = mu->last_t = when;
186         mu->usages = 1;
187         mu->url = u;
188         mu->flags = REGISTRY_URL_FLAGS_DEFAULT;
189
190         debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s'): indexing URL in machine", m->guid, u->url);
191         dictionary_set(m->urls, u->url, mu, sizeof(MACHINE_URL));
192         registry_url_link(u);
193
194         return mu;
195 }
196
197 static inline MACHINE *registry_machine_allocate(const char *machine_guid, time_t when) {
198         debug(D_REGISTRY, "Registry: registry_machine_allocate('%s'): creating new machine, sizeof(MACHINE)=%zu", machine_guid, sizeof(MACHINE));
199
200         MACHINE *m = calloc(1, sizeof(MACHINE));
201         if(!m) fatal("Registry: cannot allocate memory for new machine '%s'", machine_guid);
202
203         strncpy(m->guid, machine_guid, 36);
204
205         debug(D_REGISTRY, "Registry: registry_machine_allocate('%s'): creating dictionary of urls", machine_guid);
206         m->urls = dictionary_create(DICTIONARY_FLAGS);
207
208         m->first_t = m->last_t = when;
209         m->usages = 0;
210
211         dictionary_set(registry.machines, m->guid, m, sizeof(MACHINE));
212
213         return m;
214 }
215
216 // 1. validate machine GUID
217 // 2. if it is valid, find it or create it and return it
218 // 3. if it is not valid, return NULL
219 static inline MACHINE *registry_machine_get(const char *machine_guid, time_t when) {
220         MACHINE *m = NULL;
221
222         if(likely(machine_guid && *machine_guid)) {
223                 // validate it is a GUID
224                 uuid_t uuid;
225                 if(uuid_parse(machine_guid, uuid) == -1) {
226                         info("Registry: machine guid '%s' is not a valid guid. Ignoring it.", machine_guid);
227                 }
228                 else {
229                         char buf[36 + 1];
230                         uuid_unparse_lower(uuid, buf);
231                         if(strcmp(machine_guid, buf))
232                                 info("Registry: machine guid '%s' and re-generated '%s' differ!", machine_guid, buf);
233
234                         machine_guid = buf;
235                         m = registry_machine_find(machine_guid);
236                         if(!m) {
237                                 m = registry_machine_allocate(machine_guid, when);
238                                 registry.machines_count++;
239                         }
240                 }
241         }
242
243         return m;
244 }
245
246
247 // ----------------------------------------------------------------------------
248 // PERSON
249
250 static inline PERSON *registry_person_find(const char *person_guid) {
251         debug(D_REGISTRY, "Registry: registry_person_find('%s')", person_guid);
252         return dictionary_get(registry.persons, person_guid);
253 }
254
255 static inline PERSON_URL *registry_person_url_allocate(PERSON *p, MACHINE *m, URL *u, time_t when) {
256         debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): allocating %zu bytes", p->guid, m->guid, u->url, sizeof(PERSON_URL));
257         PERSON_URL *pu = malloc(sizeof(PERSON_URL));
258         if(!pu) fatal("registry_person_link_to_url('%s', '%s', '%s'): cannot allocate %zu bytes.", p->guid, m->guid, u->url, sizeof(PERSON_URL));
259
260         pu->machine = m;
261         pu->first_t = pu->last_t = when;
262         pu->usages = 1;
263         pu->url = u;
264         pu->flags = REGISTRY_URL_FLAGS_DEFAULT;
265
266         debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): indexing URL in person", p->guid, m->guid, u->url);
267         dictionary_set(p->urls, u->url, pu, sizeof(PERSON_URL));
268         registry_url_link(u);
269
270         return pu;
271 }
272
273 static inline PERSON *registry_person_allocate(const char *person_guid, time_t when) {
274         PERSON *p = NULL;
275
276         debug(D_REGISTRY, "Registry: registry_person_allocate('%s'): allocating new person, sizeof(PERSON)=%zu", (person_guid)?person_guid:"", sizeof(PERSON));
277
278         p = calloc(1, sizeof(PERSON));
279         if(!p) fatal("Registry: cannot allocate memory for new person.");
280
281         if(!person_guid) {
282                 for (; ;) {
283                         uuid_t uuid;
284                         if (uuid_generate_time_safe(uuid) == -1)
285                                 info("Registry: uuid_generate_time_safe() reports UUID generation is not safe for uniqueness.");
286
287                         uuid_unparse_lower(uuid, p->guid);
288
289                         debug(D_REGISTRY, "Registry: Checking if the generated person guid '%s' is unique", p->guid);
290                         if (!dictionary_get(registry.persons, p->guid)) {
291                                 debug(D_REGISTRY, "Registry: generated person guid '%s' is unique", p->guid);
292                                 break;
293                         }
294                         else
295                                 info("Registry: generated person guid '%s' found in the registry. Retrying...", p->guid);
296                 }
297         }
298         else {
299                 strncpy(p->guid, person_guid, 36);
300                 p->guid[36] = '\0';
301         }
302
303         debug(D_REGISTRY, "Registry: registry_person_allocate('%s'): creating dictionary of urls", p->guid);
304         p->urls = dictionary_create(DICTIONARY_FLAGS);
305
306         p->first_t = p->last_t = when;
307         p->usages = 0;
308
309         dictionary_set(registry.persons, p->guid, p, sizeof(PERSON));
310         return p;
311 }
312
313
314 // 1. validate person GUID
315 // 2. if it is valid, find it
316 // 3. if it is not valid, create a new one
317 // 4. return it
318 static inline PERSON *registry_person_get(const char *person_guid, time_t when) {
319         PERSON *p = NULL;
320
321         if(person_guid && *person_guid) {
322                 // validate it is a GUID
323                 uuid_t uuid;
324                 if(uuid_parse(person_guid, uuid) == -1) {
325                         info("Registry: person guid '%s' is not a valid guid. Ignoring it.", person_guid);
326                 }
327                 else {
328                         char buf[36 + 1];
329                         uuid_unparse_lower(uuid, buf);
330                         if(strcmp(person_guid, buf))
331                                 info("Registry: person guid '%s' and re-generated '%s' differ!", person_guid, buf);
332
333                         person_guid = buf;
334                         p = registry_person_find(person_guid);
335                         if(!p) person_guid = NULL;
336                 }
337         }
338
339         if(!p) {
340                 p = registry_person_allocate(NULL, when);
341                 registry.persons_count++;
342         }
343
344         return p;
345 }
346
347 // ----------------------------------------------------------------------------
348 // LINKING OF OBJECTS
349
350 static inline PERSON_URL *registry_person_link_to_url(PERSON *p, MACHINE *m, URL *u, time_t when) {
351         debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): searching for URL in person", p->guid, m->guid, u->url);
352
353         PERSON_URL *pu = dictionary_get(p->urls, u->url);
354         if(!pu) {
355                 debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): not found", p->guid, m->guid, u->url);
356                 pu = registry_person_url_allocate(p, m, u, when);
357                 registry.persons_urls_count++;
358         }
359         else {
360                 debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): found", p->guid, m->guid, u->url);
361                 pu->usages++;
362
363                 if(pu->machine != m) {
364                         MACHINE_URL *mu = dictionary_get(pu->machine->urls, u->url);
365                         if(mu) {
366                                 info("registry_person_link_to_url('%s', '%s', '%s'): URL switched machines (old was '%s') - expiring it from previous machine.",
367                                          p->guid, m->guid, u->url, pu->machine->guid);
368                                 mu->flags |= REGISTRY_URL_FLAGS_EXPIRED;
369                         }
370
371                         pu->machine = m;
372                 }
373         }
374
375         p->usages++;
376         p->last_t = when;
377
378         if(pu->flags & REGISTRY_URL_FLAGS_EXPIRED)
379                 info("registry_person_link_to_url('%s', '%s', '%s'): accessing an expired URL.", p->guid, m->guid, u->url);
380
381         return pu;
382 }
383
384 static inline MACHINE_URL *registry_machine_link_to_url(PERSON *p, MACHINE *m, URL *u, time_t when) {
385         debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s', '%s'): searching for URL in machine", p->guid, m->guid, u->url);
386
387         MACHINE_URL *mu = dictionary_get(m->urls, u->url);
388         if(!mu) {
389                 debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s', '%s'): not found", p->guid, m->guid, u->url);
390                 mu = registry_machine_url_allocate(m, u, when);
391                 registry.machines_urls_count++;
392         }
393         else {
394                 debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s', '%s'): found", p->guid, m->guid, u->url);
395                 mu->usages++;
396         }
397
398         //debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s', '%s'): indexing person in machine", p->guid, m->guid, u->url);
399         //dictionary_set(mu->persons, p->guid, p, sizeof(PERSON));
400
401         m->usages++;
402         m->last_t = when;
403
404         if(mu->flags & REGISTRY_URL_FLAGS_EXPIRED)
405                 info("registry_person_link_to_url('%s', '%s', '%s'): accessing an expired URL.", p->guid, m->guid, u->url);
406
407         return mu;
408 }
409
410 // ----------------------------------------------------------------------------
411 // REGISTRY LOG LOAD/SAVE
412
413 static inline void registry_log(const char action, PERSON *p, MACHINE *m, URL *u) {
414         if(likely(registry.registry_log_fp))
415                 fprintf(registry.registry_log_fp, "%c\t%08x\t%s\t%s\t%s\n",
416                                 action,
417                                 p->last_t,
418                                 p->guid,
419                                 m->guid,
420                                 u->url
421                 );
422 }
423
424 void registry_log_open(void) {
425         registry.registry_log_fp = fopen(registry.log_filename, "a");
426
427         if(registry.registry_log_fp) {
428                 if (setvbuf(registry.registry_log_fp, NULL, _IOLBF, 0) != 0)
429                         error("Cannot set line buffering on registry log file.");
430         }
431 }
432
433 void registry_log_close(void) {
434         if(registry.registry_log_fp)
435                 fclose(registry.registry_log_fp);
436
437         registry.registry_log_fp = NULL;
438 }
439
440 void registry_log_recreate(void) {
441         if(registry.registry_log_fp != NULL) {
442                 fclose(registry.registry_log_fp);
443
444                 registry.registry_log_fp = fopen(registry.log_filename, "w");
445                 if(registry.registry_log_fp) fclose(registry.registry_log_fp);
446                 registry.registry_log_fp = NULL;
447
448                 registry_log_open();
449         }
450 }
451
452 int registry_log_load(void) {
453         char *s, buf[4096 + 1];
454         size_t line = 0;
455
456         registry_log_close();
457
458         debug(D_REGISTRY, "Registry: loading active db from: %s", registry.log_filename);
459         FILE *fp = fopen(registry.log_filename, "r");
460         if(!fp) {
461                 error("Registry: cannot open registry file: %s", registry.log_filename);
462                 return 0;
463         }
464
465         size_t len = 0;
466         while((s = fgets_trim_len(buf, 4096, fp, &len))) {
467                 line++;
468
469                 switch(s[0]) {
470                         case 'A':
471                                 // verify it is valid
472                                 if(unlikely(len < 85 || s[1] != '\t' || s[10] != '\t' || s[47] != '\t' || s[84] != '\t'))
473                                         error("Registry log line %u is wrong (len = %zu).", line, len);
474
475                                 s[1] = s[10] = s[47] = s[84] = '\0';
476                                 registry_request(&s[11], &s[48], &s[85], strtoul(&s[2], NULL, 16));
477                                 break;
478
479                         default:
480                                 error("Registry: ignoring line %zu of filename '%s': %s.", line, registry.log_filename, s);
481                                 break;
482                 }
483         }
484
485         registry_log_open();
486
487         return 0;
488 }
489
490
491 // ----------------------------------------------------------------------------
492 // REGISTRY REQUESTS
493
494 PERSON *registry_request(const char *person_guid, const char *machine_guid, const char *url, time_t when) {
495         debug(D_REGISTRY, "registry_request('%s', '%s', '%s'): NEW REQUEST", (person_guid)?person_guid:"", machine_guid, url);
496
497         MACHINE *m = registry_machine_get(machine_guid, when);
498         if(!m) return NULL;
499
500         URL *u = registry_url_get(url);
501         PERSON *p = registry_person_get(person_guid, when);
502
503         registry_person_link_to_url(p, m, u, when);
504         registry_machine_link_to_url(p, m, u, when);
505
506         registry_log('A', p, m, u);
507
508         registry.usages_count++;
509         return p;
510 }
511
512
513 // ----------------------------------------------------------------------------
514 // REGISTRY LOAD/SAVE
515
516 int registry_machine_save_url(void *entry, void *file) {
517         MACHINE_URL *mu = entry;
518         FILE *fp = file;
519
520         debug(D_REGISTRY, "Registry: registry_machine_save_url('%s')", mu->url->url);
521
522         int ret = fprintf(fp, "V\t%08x\t%08x\t%08x\t%02x\t%s\n",
523                         mu->first_t,
524                         mu->last_t,
525                         mu->usages,
526                         mu->flags,
527                         mu->url->url
528         );
529
530         return ret;
531 }
532
533 int registry_machine_save(void *entry, void *file) {
534         MACHINE *m = entry;
535         FILE *fp = file;
536
537         debug(D_REGISTRY, "Registry: registry_machine_save('%s')", m->guid);
538
539         int ret = fprintf(fp, "M\t%08x\t%08x\t%08x\t%s\n",
540                         m->first_t,
541                         m->last_t,
542                         m->usages,
543                         m->guid
544         );
545
546         if(ret >= 0) {
547                 int ret2 = dictionary_get_all(m->urls, registry_machine_save_url, fp);
548                 if(ret2 < 0) return ret2;
549                 ret += ret2;
550         }
551
552         return ret;
553 }
554
555 static inline int registry_person_save_url(void *entry, void *file) {
556         PERSON_URL *pu = entry;
557         FILE *fp = file;
558
559         debug(D_REGISTRY, "Registry: registry_person_save_url('%s')", pu->url->url);
560
561         int ret = fprintf(fp, "U\t%08x\t%08x\t%08x\t%02x\t%s\t%s\n",
562                         pu->first_t,
563                         pu->last_t,
564                         pu->usages,
565                         pu->flags,
566                         pu->machine->guid,
567                         pu->url->url
568         );
569
570         return ret;
571 }
572
573 static inline int registry_person_save(void *entry, void *file) {
574         PERSON *p = entry;
575         FILE *fp = file;
576
577         debug(D_REGISTRY, "Registry: registry_person_save('%s')", p->guid);
578
579         int ret = fprintf(fp, "P\t%08x\t%08x\t%08x\t%s\n",
580                         p->first_t,
581                         p->last_t,
582                         p->usages,
583                         p->guid
584         );
585
586         if(ret >= 0) {
587                 int ret2 = dictionary_get_all(p->urls, registry_person_save_url, fp);
588                 if (ret2 < 0) return ret2;
589                 ret += ret2;
590         }
591
592         return ret;
593 }
594
595 static int registry_save(void) {
596         char tmp_filename[FILENAME_MAX + 1];
597         char old_filename[FILENAME_MAX + 1];
598
599         snprintf(old_filename, FILENAME_MAX, "%s.old", registry.db_filename);
600         snprintf(tmp_filename, FILENAME_MAX, "%s.tmp", registry.db_filename);
601
602         debug(D_REGISTRY, "Registry: Creating file '%s'", tmp_filename);
603         FILE *fp = fopen(tmp_filename, "w");
604         if(!fp) {
605                 error("Registry: Cannot create file: %s", tmp_filename);
606                 return -1;
607         }
608
609         debug(D_REGISTRY, "Saving all machines");
610         int bytes1 = dictionary_get_all(registry.machines, registry_machine_save, fp);
611         if(bytes1 < 0) {
612                 error("Registry: Cannot save registry machines - return value %d", bytes1);
613                 fclose(fp);
614                 return bytes1;
615         }
616         debug(D_REGISTRY, "Registry: saving machines took %d bytes", bytes1);
617
618         debug(D_REGISTRY, "Saving all persons");
619         int bytes2 = dictionary_get_all(registry.persons, registry_person_save, fp);
620         if(bytes2 < 0) {
621                 error("Registry: Cannot save registry persons - return value %d", bytes2);
622                 fclose(fp);
623                 return bytes2;
624         }
625         debug(D_REGISTRY, "Registry: saving persons took %d bytes", bytes2);
626
627         fclose(fp);
628
629         errno = 0;
630
631         // remove the .old db
632         debug(D_REGISTRY, "Registry: Removing old db '%s'", old_filename);
633         if(unlink(old_filename) == -1 && errno != ENOENT)
634                 error("Registry: cannot remove old registry file '%s'", old_filename);
635
636         // rename the db to .old
637         debug(D_REGISTRY, "Registry: Link current db '%s' to .old: '%s'", registry.db_filename, old_filename);
638         if(link(registry.db_filename, old_filename) == -1 && errno != ENOENT)
639                 error("Registry: cannot move file '%s' to '%s'. Saving registry DB failed!", tmp_filename, registry.db_filename);
640
641         else {
642                 // remove the database (it is saved in .old)
643                 debug(D_REGISTRY, "Registry: removing db '%s'", registry.db_filename);
644                 if (unlink(registry.db_filename) == -1 && errno != ENOENT)
645                         error("Registry: cannot remove old registry file '%s'", registry.db_filename);
646
647                 // move the .tmp to make it active
648                 debug(D_REGISTRY, "Registry: linking tmp db '%s' to active db '%s'", tmp_filename, registry.db_filename);
649                 if (link(tmp_filename, registry.db_filename) == -1) {
650                         error("Registry: cannot move file '%s' to '%s'. Saving registry DB failed!", tmp_filename,
651                                   registry.db_filename);
652
653                         // move the .old back
654                         debug(D_REGISTRY, "Registry: linking old db '%s' to active db '%s'", old_filename, registry.db_filename);
655                         if(link(old_filename, registry.db_filename) == -1)
656                                 error("Registry: cannot move file '%s' to '%s'. Recovering the old registry DB failed!", old_filename, registry.db_filename);
657                 }
658                 else {
659                         debug(D_REGISTRY, "Registry: removing tmp db '%s'", tmp_filename);
660                         if(unlink(tmp_filename) == -1)
661                                 error("Registry: cannot remove tmp registry file '%s'", tmp_filename);
662
663                         // it has been moved successfully
664                         // discard the current registry log
665                         registry_log_recreate();
666                 }
667         }
668
669         return -1;
670 }
671
672 static inline size_t registry_load(void) {
673         char *s, buf[4096 + 1];
674         PERSON *p = NULL;
675         MACHINE *m = NULL;
676         URL *u = NULL;
677         size_t line = 0;
678
679         debug(D_REGISTRY, "Registry: loading active db from: %s", registry.db_filename);
680         FILE *fp = fopen(registry.db_filename, "r");
681         if(!fp) {
682                 error("Registry: cannot open registry file: %s", registry.db_filename);
683                 return 0;
684         }
685
686         size_t len = 0;
687         while((s = fgets_trim_len(buf, 4096, fp, &len))) {
688                 line++;
689
690                 debug(D_REGISTRY, "Registry: read line %zu to length %zu: %s", line, len, s);
691
692                 switch(*s) {
693                         case 'P': // person
694                                 m = NULL;
695                                 // verify it is valid
696                                 if(unlikely(len != 65 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[65] != '\0'))
697                                         error("Registry person line %u is wrong (len = %zu).", line, len);
698
699                                 s[1] = s[10] = s[19] = s[28] = '\0';
700                                 p = registry_person_allocate(&s[29], strtoul(&s[2], NULL, 16));
701                                 p->last_t = strtoul(&s[11], NULL, 16);
702                                 p->usages = strtoul(&s[20], NULL, 16);
703                                 debug(D_REGISTRY, "Registry loaded person '%s', first: %u, last: %u, usages: %u", p->guid, p->first_t, p->last_t, p->usages);
704                                 break;
705
706                         case 'M': // machine
707                                 p = NULL;
708                                 // verify it is valid
709                                 if(unlikely(len != 65 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[65] != '\0'))
710                                         error("Registry person line %u is wrong (len = %zu).", line, len);
711
712                                 s[1] = s[10] = s[19] = s[28] = '\0';
713                                 m = registry_machine_allocate(&s[29], strtoul(&s[2], NULL, 16));
714                                 m->last_t = strtoul(&s[11], NULL, 16);
715                                 m->usages = strtoul(&s[20], NULL, 16);
716                                 debug(D_REGISTRY, "Registry loaded machine '%s', first: %u, last: %u, usages: %u", m->guid, m->first_t, m->last_t, m->usages);
717                                 break;
718
719                         case 'U': // person URL
720                                 if(unlikely(!p)) {
721                                         error("Registry: ignoring line %zu, no person loaded: %s", line, s);
722                                         break;
723                                 }
724
725                                 // verify it is valid
726                                 if(len < 69 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[31] != '\t' || s[68] != '\t')
727                                         error("Registry person URL line %u is wrong (len = %zu).", line, len);
728
729                                 s[1] = s[10] = s[19] = s[28] = s[31] = s[68] = '\0';
730                                 u = registry_url_allocate(&s[69]);
731
732                                 time_t first_t = strtoul(&s[2], NULL, 16);
733
734                                 m = registry_machine_find(&s[32]);
735                                 if(!m) m = registry_machine_allocate(&s[32], first_t);
736
737                                 PERSON_URL *pu = registry_person_url_allocate(p, m, u, first_t);
738                                 pu->last_t = strtoul(&s[11], NULL, 16);
739                                 pu->usages = strtoul(&s[20], NULL, 16);
740                                 pu->flags = strtoul(&s[29], NULL, 16);
741                                 debug(D_REGISTRY, "Registry loaded person URL '%s', machine '%s', first: %u, last: %u, usages: %u, flags: %02x", u->url, m->guid, pu->first_t, pu->last_t, pu->usages, pu->flags);
742                                 break;
743
744                         case 'V': // machine URL
745                                 if(unlikely(!m)) {
746                                         error("Registry: ignoring line %zu, no machine loaded: %s", line, s);
747                                         break;
748                                 }
749
750                                 // verify it is valid
751                                 if(len < 32 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[31] != '\t')
752                                         error("Registry person URL line %u is wrong (len = %zu).", line, len);
753
754                                 s[1] = s[10] = s[19] = s[28] = s[31] = '\0';
755                                 u = registry_url_allocate(&s[32]);
756
757                                 MACHINE_URL *mu = registry_machine_url_allocate(m, u, strtoul(&s[2], NULL, 16));
758                                 mu->last_t = strtoul(&s[11], NULL, 16);
759                                 mu->usages = strtoul(&s[20], NULL, 16);
760                                 mu->flags = strtoul(&s[29], NULL, 16);
761                                 debug(D_REGISTRY, "Registry loaded machine URL '%s', machine '%s', first: %u, last: %u, usages: %u, flags: %02x", u->url, m->guid, mu->first_t, mu->last_t, mu->usages, mu->flags);
762                                 break;
763
764                         default:
765                                 error("Registry: ignoring line %zu of filename '%s': %s.", line, registry.db_filename, s);
766                                 break;
767                 }
768         }
769         fclose(fp);
770
771         registry_log_load();
772
773         return line;
774 }
775
776 // ----------------------------------------------------------------------------
777 // REGISTRY
778
779 void registry_init(void) {
780         char filename[FILENAME_MAX + 1];
781
782         registry.pathname = config_get("registry", "registry db directory", VARLIB_DIR);
783         if(mkdir(registry.pathname, 0644) == -1 && errno != EEXIST)
784                 error("Cannot create directory '%s'", registry.pathname);
785
786         snprintf(filename, FILENAME_MAX, "%s/%s", registry.pathname, "registry.db");
787         registry.db_filename = config_get("registry", "registry db file", filename);
788
789         snprintf(filename, FILENAME_MAX, "%s/%s", registry.pathname, "registry-log.db");
790         registry.log_filename = config_get("registry", "registry log file", filename);
791
792         registry.persons_count = 0;
793         registry.machines_count = 0;
794         registry.usages_count = 0;
795         registry.urls_count = 0;
796         registry.persons_urls_count = 0;
797         registry.machines_urls_count = 0;
798
799         debug(D_REGISTRY, "Registry: creating global registry dictionary for persons.");
800         registry.persons = dictionary_create(DICTIONARY_FLAGS);
801
802         debug(D_REGISTRY, "Registry: creating global registry dictionary for machines.");
803         registry.machines = dictionary_create(DICTIONARY_FLAGS);
804
805         debug(D_REGISTRY, "Registry: creating global registry dictionary for urls.");
806         registry.urls = dictionary_create(DICTIONARY_FLAGS);
807
808         registry_log_open();
809         registry_load();
810 }
811
812 void registry_free(void) {
813         while(registry.persons->values_index.root) {
814                 PERSON *p = ((NAME_VALUE *)registry.persons->values_index.root)->value;
815
816                 // fprintf(stderr, "\nPERSON: '%s', first: %u, last: %u, usages: %u\n", p->guid, p->first_t, p->last_t, p->usages);
817
818                 while(p->urls->values_index.root) {
819                         PERSON_URL *pu = ((NAME_VALUE *)p->urls->values_index.root)->value;
820
821                         // fprintf(stderr, "\tURL: '%s', first: %u, last: %u, usages: %u, flags: 0x%02x\n", pu->url->url, pu->first_t, pu->last_t, pu->usages, pu->flags);
822
823                         debug(D_REGISTRY, "Registry: deleting url '%s' from person '%s'", pu->url->url, p->guid);
824                         dictionary_del(p->urls, pu->url->url);
825
826                         debug(D_REGISTRY, "Registry: unlinking url '%s' from person", pu->url->url);
827                         registry_url_unlink(pu->url);
828
829                         debug(D_REGISTRY, "Registry: freeing person url");
830                         free(pu);
831                 }
832
833                 debug(D_REGISTRY, "Registry: deleting person '%s' from persons registry", p->guid);
834                 dictionary_del(registry.persons, p->guid);
835
836                 debug(D_REGISTRY, "Registry: destroying URL dictionary of person '%s'", p->guid);
837                 dictionary_destroy(p->urls);
838
839                 debug(D_REGISTRY, "Registry: freeing person '%s'", p->guid);
840                 free(p);
841         }
842
843         while(registry.machines->values_index.root) {
844                 MACHINE *m = ((NAME_VALUE *)registry.machines->values_index.root)->value;
845
846                 // fprintf(stderr, "\nMACHINE: '%s', first: %u, last: %u, usages: %u\n", m->guid, m->first_t, m->last_t, m->usages);
847
848                 while(m->urls->values_index.root) {
849                         MACHINE_URL *mu = ((NAME_VALUE *)m->urls->values_index.root)->value;
850
851                         // fprintf(stderr, "\tURL: '%s', first: %u, last: %u, usages: %u, flags: 0x%02x\n", mu->url->url, mu->first_t, mu->last_t, mu->usages, mu->flags);
852
853                         //debug(D_REGISTRY, "Registry: destroying persons dictionary from url '%s'", mu->url->url);
854                         //dictionary_destroy(mu->persons);
855
856                         debug(D_REGISTRY, "Registry: deleting url '%s' from person '%s'", mu->url->url, m->guid);
857                         dictionary_del(m->urls, mu->url->url);
858
859                         debug(D_REGISTRY, "Registry: unlinking url '%s' from machine", mu->url->url);
860                         registry_url_unlink(mu->url);
861
862                         debug(D_REGISTRY, "Registry: freeing machine url");
863                         free(mu);
864                 }
865
866                 debug(D_REGISTRY, "Registry: deleting machine '%s' from machines registry", m->guid);
867                 dictionary_del(registry.machines, m->guid);
868
869                 debug(D_REGISTRY, "Registry: destroying URL dictionary of machine '%s'", m->guid);
870                 dictionary_destroy(m->urls);
871
872                 debug(D_REGISTRY, "Registry: freeing machine '%s'", m->guid);
873                 free(m);
874         }
875
876         debug(D_REGISTRY, "Registry: destroying persons dictionary");
877         dictionary_destroy(registry.persons);
878
879         debug(D_REGISTRY, "Registry: destroying machines dictionary");
880         dictionary_destroy(registry.machines);
881
882         debug(D_REGISTRY, "Registry: destroying urls dictionary");
883         dictionary_destroy(registry.urls);
884 }
885
886
887 // ----------------------------------------------------------------------------
888 // TESTS
889
890 int test1(int argc, char **argv) {
891
892         void print_stats(uint32_t requests, unsigned long long start, unsigned long long end) {
893                 fprintf(stderr, " > SPEED: %u requests served in %0.2f seconds ( >>> %llu per second <<< )\n",
894                                 requests, (end-start) / 1000000.0, (unsigned long long)requests * 1000000ULL / (end-start));
895
896                 fprintf(stderr, " > DB   : persons %llu, machines %llu, unique URLs %llu, accesses %llu, URLs: for persons %llu, for machines %llu\n",
897                                 registry.persons_count, registry.machines_count, registry.urls_count, registry.usages_count,
898                                 registry.persons_urls_count, registry.machines_urls_count);
899         }
900
901         (void) argc;
902         (void) argv;
903
904         uint32_t u, users = 1000000;
905         uint32_t m, machines = 200000;
906         uint32_t machines2 = machines * 2;
907
908         char **users_guids = malloc(users * sizeof(char *));
909         char **machines_guids = malloc(machines2 * sizeof(char *));
910         char **machines_urls = malloc(machines2 * sizeof(char *));
911         unsigned long long start;
912
913         registry_init();
914
915         fprintf(stderr, "Generating %u machine guids\n", machines2);
916         for(m = 0; m < machines2 ;m++) {
917                 uuid_t uuid;
918                 machines_guids[m] = malloc(36+1);
919                 uuid_generate(uuid);
920                 uuid_unparse(uuid, machines_guids[m]);
921
922                 char buf[FILENAME_MAX + 1];
923                 snprintf(buf, FILENAME_MAX, "http://%u.netdata.rocks/", m+1);
924                 machines_urls[m] = strdup(buf);
925
926                 // fprintf(stderr, "\tmachine %u: '%s', url: '%s'\n", m + 1, machines_guids[m], machines_urls[m]);
927         }
928
929         start = timems();
930         fprintf(stderr, "\nGenerating %u users accessing %u machines\n", users, machines);
931         m = 0;
932         time_t now = time(NULL);
933         for(u = 0; u < users ; u++) {
934                 if(++m == machines) m = 0;
935
936                 PERSON *p = registry_request(NULL, machines_guids[m], machines_urls[m], now);
937                 users_guids[u] = p->guid;
938         }
939         print_stats(u, start, timems());
940
941         start = timems();
942         fprintf(stderr, "\nAll %u users accessing again the same %u servers\n", users, machines);
943         m = 0;
944         now = time(NULL);
945         for(u = 0; u < users ; u++) {
946                 if(++m == machines) m = 0;
947
948                 PERSON *p = registry_request(users_guids[u], machines_guids[m], machines_urls[m], now);
949
950                 if(p->guid != users_guids[u])
951                         fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[u], p->guid);
952         }
953         print_stats(u, start, timems());
954
955         start = timems();
956         fprintf(stderr, "\nAll %u users accessing a new server, out of the %u servers\n", users, machines);
957         m = 1;
958         now = time(NULL);
959         for(u = 0; u < users ; u++) {
960                 if(++m == machines) m = 0;
961
962                 PERSON *p = registry_request(users_guids[u], machines_guids[m], machines_urls[m], now);
963
964                 if(p->guid != users_guids[u])
965                         fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[u], p->guid);
966         }
967         print_stats(u, start, timems());
968
969         start = timems();
970         fprintf(stderr, "\n%u random users accessing a random server, out of the %u servers\n", users, machines);
971         now = time(NULL);
972         for(u = 0; u < users ; u++) {
973                 uint32_t tu = random() * users / RAND_MAX;
974                 uint32_t tm = random() * machines / RAND_MAX;
975
976                 PERSON *p = registry_request(users_guids[tu], machines_guids[tm], machines_urls[tm], now);
977
978                 if(p->guid != users_guids[tu])
979                         fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid);
980         }
981         print_stats(u, start, timems());
982
983         start = timems();
984         fprintf(stderr, "\n%u random users accessing a random server, out of %u servers\n", users, machines2);
985         now = time(NULL);
986         for(u = 0; u < users ; u++) {
987                 uint32_t tu = random() * users / RAND_MAX;
988                 uint32_t tm = random() * machines2 / RAND_MAX;
989
990                 PERSON *p = registry_request(users_guids[tu], machines_guids[tm], machines_urls[tm], now);
991
992                 if(p->guid != users_guids[tu])
993                         fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid);
994         }
995         print_stats(u, start, timems());
996
997         for(m = 0; m < 10; m++) {
998                 start = timems();
999                 fprintf(stderr,
1000                                 "\n%u random user accesses to a random server, out of %u servers,\n > using 1/10000 with a random url, 1/1000 with a mismatched url\n",
1001                                 users * 2, machines2);
1002                 now = time(NULL);
1003                 for (u = 0; u < users * 2; u++) {
1004                         uint32_t tu = random() * users / RAND_MAX;
1005                         uint32_t tm = random() * machines2 / RAND_MAX;
1006
1007                         char *url = machines_urls[tm];
1008                         char buf[FILENAME_MAX + 1];
1009                         if (random() % 10000 == 1234) {
1010                                 snprintf(buf, FILENAME_MAX, "http://random.%ld.netdata.rocks/", random());
1011                                 url = buf;
1012                         }
1013                         else if (random() % 1000 == 123)
1014                                 url = machines_urls[random() * machines2 / RAND_MAX];
1015
1016                         PERSON *p = registry_request(users_guids[tu], machines_guids[tm], url, now);
1017
1018                         if (p->guid != users_guids[tu])
1019                                 fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid);
1020                 }
1021                 print_stats(u, start, timems());
1022         }
1023
1024         fprintf(stderr, "\n\nSAVE\n");
1025         start = timems();
1026         registry_save();
1027         print_stats(registry.persons_count, start, timems());
1028
1029         fprintf(stderr, "\n\nCLEANUP\n");
1030         start = timems();
1031         registry_free();
1032         print_stats(registry.persons_count, start, timems());
1033         return 0;
1034 }
1035
1036 // ----------------------------------------------------------------------------
1037 // TESTING
1038
1039 int main(int argc, char **argv) {
1040         debug_flags = 0xFFFFFFFF;
1041         //test1(argc, argv);
1042         //exit(0);
1043
1044         (void)argc;
1045         (void)argv;
1046
1047
1048         PERSON *p1, *p2;
1049
1050         fprintf(stderr, "\n\nINITIALIZATION\n");
1051
1052         registry_init();
1053
1054         int i = 2;
1055
1056         fprintf(stderr, "\n\nADDING ENTRY\n");
1057         // p1 = registry_request("2c95abd0-1542-11e6-8c66-00508db7e9c9", "7c173980-145c-11e6-b86f-00508db7e9c1", "http://localhost:19999/", time(NULL));
1058
1059         if(0)
1060         while(i--) {
1061 #ifdef REGISTRY_STDOUT_DUMP
1062                 fprintf(stderr, "\n\nADDING ENTRY\n");
1063 #endif /* REGISTRY_STDOUT_DUMP */
1064                 p1 = registry_request(NULL, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://localhost:19999/", time(NULL));
1065
1066 #ifdef REGISTRY_STDOUT_DUMP
1067                 fprintf(stderr, "\n\nADDING ANOTHER URL\n");
1068 #endif /* REGISTRY_STDOUT_DUMP */
1069                 p1 = registry_request(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://127.0.0.1:19999/", time(NULL));
1070
1071 #ifdef REGISTRY_STDOUT_DUMP
1072                 fprintf(stderr, "\n\nADDING ANOTHER URL\n");
1073 #endif /* REGISTRY_STDOUT_DUMP */
1074                 p1 = registry_request(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://my.server:19999/", time(NULL));
1075
1076 #ifdef REGISTRY_STDOUT_DUMP
1077                 fprintf(stderr, "\n\nADDING ANOTHER MACHINE\n");
1078 #endif /* REGISTRY_STDOUT_DUMP */
1079                 p1 = registry_request(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://my.server:19999/", time(NULL));
1080
1081 #ifdef REGISTRY_STDOUT_DUMP
1082                 fprintf(stderr, "\n\nADDING ANOTHER PERSON\n");
1083 #endif /* REGISTRY_STDOUT_DUMP */
1084                 p2 = registry_request(NULL, "7c173980-145c-11e6-b86f-00508db7e9c3", "http://localhost:19999/", time(NULL));
1085
1086 #ifdef REGISTRY_STDOUT_DUMP
1087                 fprintf(stderr, "\n\nADDING ANOTHER MACHINE\n");
1088 #endif /* REGISTRY_STDOUT_DUMP */
1089                 p2 = registry_request(p2->guid, "7c173980-145c-11e6-b86f-00508db7e9c3", "http://localhost:19999/", time(NULL));
1090         }
1091
1092         fprintf(stderr, "\n\nSAVE\n");
1093         registry_save();
1094
1095         fprintf(stderr, "\n\nCLEANUP\n");
1096         registry_free();
1097         return 0;
1098 }