]> arthur.barton.de Git - netdata.git/blob - src/rrd2json.c
build: migrate to autotools
[netdata.git] / src / rrd2json.c
1 #ifdef HAVE_CONFIG_H
2 #include <config.h>
3 #endif
4 #include <pthread.h>
5 #include <sys/time.h>
6 #include <stdlib.h>
7
8 #include "log.h"
9 #include "common.h"
10 #include "rrd2json.h"
11
12 #define HOSTNAME_MAX 1024
13 char *hostname = "unknown";
14
15 unsigned long rrd_stats_one_json(RRDSET *st, char *options, struct web_buffer *wb)
16 {
17         time_t now = time(NULL);
18         web_buffer_increase(wb, 65536);
19
20         pthread_rwlock_rdlock(&st->rwlock);
21
22         web_buffer_printf(wb,
23                 "\t\t{\n"
24                 "\t\t\t\"id\": \"%s\",\n"
25                 "\t\t\t\"name\": \"%s\",\n"
26                 "\t\t\t\"type\": \"%s\",\n"
27                 "\t\t\t\"family\": \"%s\",\n"
28                 "\t\t\t\"title\": \"%s\",\n"
29                 "\t\t\t\"priority\": %ld,\n"
30                 "\t\t\t\"enabled\": %d,\n"
31                 "\t\t\t\"units\": \"%s\",\n"
32                 "\t\t\t\"url\": \"/data/%s/%s\",\n"
33                 "\t\t\t\"chart_type\": \"%s\",\n"
34                 "\t\t\t\"counter\": %ld,\n"
35                 "\t\t\t\"entries\": %ld,\n"
36                 "\t\t\t\"first_entry_t\": %lu,\n"
37                 "\t\t\t\"last_entry\": %ld,\n"
38                 "\t\t\t\"last_entry_t\": %lu,\n"
39                 "\t\t\t\"last_entry_secs_ago\": %lu,\n"
40                 "\t\t\t\"update_every\": %d,\n"
41                 "\t\t\t\"isdetail\": %d,\n"
42                 "\t\t\t\"usec_since_last_update\": %llu,\n"
43                 "\t\t\t\"collected_total\": " TOTAL_NUMBER_FORMAT ",\n"
44                 "\t\t\t\"last_collected_total\": " TOTAL_NUMBER_FORMAT ",\n"
45                 "\t\t\t\"dimensions\": [\n"
46                 , st->id
47                 , st->name
48                 , st->type
49                 , st->family
50                 , st->title
51                 , st->priority
52                 , st->enabled
53                 , st->units
54                 , st->name, options?options:""
55                 , rrdset_type_name(st->chart_type)
56                 , st->counter
57                 , st->entries
58                 , rrdset_first_entry_t(st)
59                 , rrdset_last_slot(st)
60                 , rrdset_last_entry_t(st)
61                 , (now < rrdset_last_entry_t(st)) ? (time_t)0 : now - rrdset_last_entry_t(st)
62                 , st->update_every
63                 , st->isdetail
64                 , st->usec_since_last_update
65                 , st->collected_total
66                 , st->last_collected_total
67                 );
68
69         unsigned long memory = st->memsize;
70
71         RRDDIM *rd;
72         for(rd = st->dimensions; rd ; rd = rd->next) {
73                 web_buffer_increase(wb, 4096);
74
75                 memory += rd->memsize;
76
77                 web_buffer_printf(wb,
78                         "\t\t\t\t{\n"
79                         "\t\t\t\t\t\"id\": \"%s\",\n"
80                         "\t\t\t\t\t\"name\": \"%s\",\n"
81                         "\t\t\t\t\t\"entries\": %ld,\n"
82                         "\t\t\t\t\t\"isHidden\": %d,\n"
83                         "\t\t\t\t\t\"algorithm\": \"%s\",\n"
84                         "\t\t\t\t\t\"multiplier\": %ld,\n"
85                         "\t\t\t\t\t\"divisor\": %ld,\n"
86                         "\t\t\t\t\t\"last_entry_t\": %lu,\n"
87                         "\t\t\t\t\t\"collected_value\": " COLLECTED_NUMBER_FORMAT ",\n"
88                         "\t\t\t\t\t\"calculated_value\": " CALCULATED_NUMBER_FORMAT ",\n"
89                         "\t\t\t\t\t\"last_collected_value\": " COLLECTED_NUMBER_FORMAT ",\n"
90                         "\t\t\t\t\t\"last_calculated_value\": " CALCULATED_NUMBER_FORMAT ",\n"
91                         "\t\t\t\t\t\"memory\": %lu\n"
92                         "\t\t\t\t}%s\n"
93                         , rd->id
94                         , rd->name
95                         , rd->entries
96                         , rd->hidden
97                         , rrddim_algorithm_name(rd->algorithm)
98                         , rd->multiplier
99                         , rd->divisor
100                         , rd->last_collected_time.tv_sec
101                         , rd->collected_value
102                         , rd->calculated_value
103                         , rd->last_collected_value
104                         , rd->last_calculated_value
105                         , rd->memsize
106                         , rd->next?",":""
107                         );
108         }
109
110         web_buffer_printf(wb,
111                 "\t\t\t],\n"
112                 "\t\t\t\"memory\" : %lu\n"
113                 "\t\t}"
114                 , memory
115                 );
116
117         pthread_rwlock_unlock(&st->rwlock);
118         return memory;
119 }
120
121 #define RRD_GRAPH_JSON_HEADER "{\n\t\"charts\": [\n"
122 #define RRD_GRAPH_JSON_FOOTER "\n\t]\n}\n"
123
124 void rrd_stats_graph_json(RRDSET *st, char *options, struct web_buffer *wb)
125 {
126         web_buffer_increase(wb, 2048);
127
128         web_buffer_printf(wb, RRD_GRAPH_JSON_HEADER);
129         rrd_stats_one_json(st, options, wb);
130         web_buffer_printf(wb, RRD_GRAPH_JSON_FOOTER);
131 }
132
133 void rrd_stats_all_json(struct web_buffer *wb)
134 {
135         web_buffer_increase(wb, 2048);
136
137         unsigned long memory = 0;
138         long c;
139         RRDSET *st;
140
141         web_buffer_printf(wb, RRD_GRAPH_JSON_HEADER);
142
143         pthread_rwlock_rdlock(&rrdset_root_rwlock);
144         for(st = rrdset_root, c = 0; st ; st = st->next) {
145                 if(st->enabled) {
146                         if(c) web_buffer_printf(wb, "%s", ",\n");
147                         memory += rrd_stats_one_json(st, NULL, wb);
148                         c++;
149                 }
150         }
151         pthread_rwlock_unlock(&rrdset_root_rwlock);
152         
153         web_buffer_printf(wb, "\n\t],\n"
154                 "\t\"hostname\": \"%s\",\n"
155                 "\t\"update_every\": %d,\n"
156                 "\t\"history\": %d,\n"
157                 "\t\"memory\": %lu\n"
158                 "}\n"
159                 , hostname
160                 , rrd_update_every
161                 , rrd_default_history_entries
162                 , memory
163                 );
164 }
165
166
167
168 // ----------------------------------------------------------------------------
169
170 // RRDR options
171 #define RRDR_EMPTY      0x01
172 #define RRDR_RESET      0x02
173 #define RRDR_HIDDEN     0x04
174 #define RRDR_NONZERO    0x08
175
176
177 typedef struct rrdresult {
178         RRDSET *st;                     // the chart this result refers to
179
180         int d;                                  // the number of dimensions
181         int n;                                  // the number of values in the arrays
182
183         uint8_t *od;                    // the options for the dimensions
184
185         time_t *t;                              // array of n timestamps
186         calculated_number *v;   // array n x d values
187         uint8_t *o;                             // array n x d options
188
189         int c;                                  // current line ( 0 ~ n )
190
191         int has_st_lock;                // if st is read locked by us
192 } RRDR;
193
194 static void rrdr_dump(RRDR *r)
195 {
196         long c, i;
197         RRDDIM *d;
198
199         fprintf(stderr, "\nCHART %s (%s)\n", r->st->id, r->st->name);
200
201         for(c = 0, d = r->st->dimensions; d ;c++, d = d->next) {
202                 fprintf(stderr, "DIMENSION %s (%s), %s%s%s%s\n"
203                                 , d->id
204                                 , d->name
205                                 , (r->od[c] & RRDR_EMPTY)?"EMPTY ":""
206                                 , (r->od[c] & RRDR_RESET)?"RESET ":""
207                                 , (r->od[c] & RRDR_HIDDEN)?"HIDDEN ":""
208                                 , (r->od[c] & RRDR_NONZERO)?"NONZERO ":""
209                                 );
210         }
211
212         if(r->c < 0) {
213                 fprintf(stderr, "RRDR does not have any values in it.\n");
214                 return;
215         }
216
217         fprintf(stderr, "RRDR includes %d values in it:\n", r->c + 1);
218
219         // for each line in the array
220         for(i = 0; i <= r->c ;i++) {
221                 calculated_number *cn = &r->v[ i * r->d ];
222                 uint8_t *co = &r->o[ i * r->d ];
223
224                 // print the id and the timestamp of the line
225                 fprintf(stderr, "%ld %ld ", i + 1, r->t[i]);
226
227                 // for each dimension
228                 for(c = 0, d = r->st->dimensions; d ;c++, d = d->next) {
229                         if(unlikely(r->od[c] & RRDR_HIDDEN)) continue;
230                         if(unlikely(!(r->od[c] & RRDR_NONZERO))) continue;
231
232                         if(co[c] & RRDR_EMPTY)
233                                 fprintf(stderr, "null ");
234                         else
235                                 fprintf(stderr, CALCULATED_NUMBER_FORMAT " %s%s%s%s "
236                                         , cn[c]
237                                         , (co[c] & RRDR_EMPTY)?"E":" "
238                                         , (co[c] & RRDR_RESET)?"R":" "
239                                         , (co[c] & RRDR_HIDDEN)?"H":" "
240                                         , (co[c] & RRDR_NONZERO)?"N":" "
241                                         );
242                 }
243
244                 fprintf(stderr, "\n");
245         }
246 }
247
248 inline static calculated_number *rrdr_line_values(RRDR *r)
249 {
250         return &r->v[ r->c * r->d ];
251 }
252
253 inline static uint8_t *rrdr_line_options(RRDR *r)
254 {
255         return &r->o[ r->c * r->d ];
256 }
257
258 inline static int rrdr_line_init(RRDR *r, time_t t)
259 {
260         r->c++;
261         if(unlikely(r->c >= r->n)) {
262                 r->c = r->n - 1;
263                 return 0;
264         }
265
266         // save the time
267         r->t[r->c] = t;
268
269         return 1;
270 }
271
272 inline static void rrdr_lock_rrdset(RRDR *r) {
273         if(unlikely(!r)) {
274                 error("NULL value given!");
275                 return;
276         }
277
278         pthread_rwlock_rdlock(&r->st->rwlock);
279         r->has_st_lock = 1;
280 }
281
282 inline static void rrdr_unlock_rrdset(RRDR *r) {
283         if(unlikely(!r)) {
284                 error("NULL value given!");
285                 return;
286         }
287
288         if(likely(r->has_st_lock)) {
289                 pthread_rwlock_unlock(&r->st->rwlock);
290                 r->has_st_lock = 0;
291         }
292 }
293
294 inline static void rrdr_free(RRDR *r)
295 {
296         if(unlikely(!r)) {
297                 error("NULL value given!");
298                 return;
299         }
300
301         rrdr_unlock_rrdset(r);
302         if(likely(r->t)) free(r->t);
303         if(likely(r->v)) free(r->v);
304         if(likely(r->o)) free(r->o);
305         if(likely(r->od)) free(r->od);
306         free(r);
307 }
308
309 static RRDR *rrdr_create(RRDSET *st, int n)
310 {
311         if(unlikely(!st)) {
312                 error("NULL value given!");
313                 return NULL;
314         }
315
316         RRDR *r = calloc(1, sizeof(RRDR));
317         if(unlikely(!r)) goto cleanup;
318
319         r->st = st;
320
321         rrdr_lock_rrdset(r);
322
323         RRDDIM *rd;
324         for(rd = st->dimensions ; rd ; rd = rd->next) r->d++;
325
326         r->n = n;
327         r->t = malloc(n * sizeof(time_t));
328         if(unlikely(!r->t)) goto cleanup;
329
330         r->t = malloc(n * sizeof(time_t));
331         if(unlikely(!r->t)) goto cleanup;
332
333         r->v = malloc(n * r->d * sizeof(calculated_number));
334         if(unlikely(!r->v)) goto cleanup;
335
336         r->o = malloc(n * r->d * sizeof(uint8_t));
337         if(unlikely(!r->o)) goto cleanup;
338
339         r->od = calloc(r->d, sizeof(uint8_t));
340         if(unlikely(!r->od)) goto cleanup;
341
342         r->c = -1;
343
344         return r;
345
346 cleanup:
347         error("Cannot allocate memory");
348         if(likely(r)) rrdr_free(r);
349         return NULL;
350 }
351
352 RRDR *rrd2rrdr(RRDSET *st, long points, time_t after, time_t before, int group_method)
353 {
354         int debug = st->debug;
355
356         time_t first_entry_t = rrdset_first_entry_t(st);
357         time_t last_entry_t = rrdset_last_entry_t(st);
358
359         // allow relative for before and after
360         if(before <= st->update_every * st->entries) before = last_entry_t + before;
361         if(after <= st->update_every * st->entries) after = last_entry_t + after;
362
363         // make sure they are within our timeframe
364         if(before > last_entry_t) before = last_entry_t;
365         if(before < first_entry_t) before = first_entry_t;
366
367         if(after > last_entry_t) after = last_entry_t;
368         if(after < first_entry_t) after = first_entry_t;
369
370         // check if they are upside down
371         if(after > before) {
372                 time_t t = before;
373                 before = after;
374                 after = t;
375         }
376
377         // the duration of the chart
378         time_t duration = before - after;
379         if(duration <= 0) return NULL;
380
381         // check the required points
382         if(points <= 0) points = duration;
383
384         // calculate proper grouping of source data
385         long group = duration / points;
386         if(group <= 0) group = 1;
387         if(duration / group > points) group++;
388
389         // error("NEW: points=%d after=%d before=%d group=%d, duration=%d", points, after, before, group, duration);
390
391         // Now we have:
392         // before = the end time of the calculation
393         // after = the start time of the calculation
394         // duration = the duration of the calculation
395         // group = the number of source points to aggregate / group together
396         // method = the method of grouping source points
397         // points = the number of points to generate
398
399
400         // -------------------------------------------------------------------------
401         // initialize our result set
402
403         RRDR *r = rrdr_create(st, points);
404         if(!r) return NULL;
405         if(!r->d) {
406                 rrdr_free(r);
407                 return NULL;
408         }
409
410         // find how many dimensions we have
411         long dimensions = r->d;
412
413
414         // -------------------------------------------------------------------------
415         // checks for debugging
416
417         if(debug) debug(D_RRD_STATS, "INFO %s first_t: %lu, last_t: %lu, all_duration: %lu, after: %lu, before: %lu, duration: %lu, points: %ld, group: %ld"
418                         , st->id
419                         , first_entry_t
420                         , last_entry_t
421                         , last_entry_t - first_entry_t
422                         , after
423                         , before
424                         , duration
425                         , points
426                         , group
427                         );
428
429
430         // -------------------------------------------------------------------------
431         // temp arrays for keeping values per dimension
432
433         calculated_number       group_values[dimensions]; // keep sums when grouping
434         long                            group_counts[dimensions]; // keep the number of values added to group_values
435         uint8_t                         group_options[dimensions];
436         uint8_t                         found_non_zero[dimensions];
437
438
439         // initialize them
440         RRDDIM *rd;
441         long c;
442         for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) {
443                 group_values[c] = 0;
444                 group_counts[c] = 0;
445                 group_options[c] = 0;
446                 found_non_zero[c] = 0;
447         }
448
449
450         // -------------------------------------------------------------------------
451         // the main loop
452
453         long    t = rrdset_time2slot(st, before), // rrdset_last_slot(st),
454                         stop_at_t = rrdset_time2slot(st, after),
455                         added = 0,
456                         group_count = 0,
457                         add_this = 0,
458                         stop_now = 0;
459
460         // align to group for proper panning of data
461         t -= t % group;
462
463         time_t  now = rrdset_slot2time(st, t),
464                         dt = st->update_every,
465                         group_start_t = 0;
466
467         if(debug) debug(D_RRD_STATS, "BEGIN %s after_t: %lu (stop slot %ld), before_t: %lu (start slot %ld), start_t(now): %lu"
468                         , st->id
469                         , after
470                         , stop_at_t
471                         , before
472                         , t
473                         , now
474                         );
475
476         for( ; !stop_now ; now -= dt, t--) {
477                 if(unlikely(t < 0)) c = st->entries - 1;
478                 if(t == stop_at_t) stop_now = 1;
479
480                 if(debug) debug(D_RRD_STATS, "ROW %s c: %ld, group_count: %ld, added: %ld, now: %lu, %s %s"
481                                 , st->id
482                                 , t
483                                 , group_count + 1
484                                 , added
485                                 , now
486                                 , (group_count + 1 == group)?"PRINT":"  -  "
487                                 , (now >= after && now <= before)?"RANGE":"  -  "
488                                 );
489
490                 // make sure we return data in the proper time range
491                 if(unlikely(now > before)) continue;
492                 if(unlikely(now < after)) break;
493
494                 if(group_count == 0) group_start_t = now;
495                 group_count++;
496
497                 if(unlikely(group_count == group)) {
498                         if(unlikely(added >= points)) break;
499                         add_this = 1;
500                 }
501
502                 // do the calculations
503                 for(rd = st->dimensions, c = 0 ; likely(rd && c < dimensions) ; rd = rd->next, c++) {
504                         storage_number n = rd->values[t];
505                         if(unlikely(!does_storage_number_exist(n))) continue;
506
507                         group_counts[c]++;
508
509                         calculated_number value = unpack_storage_number(n);
510                         if(value != 0.0) {
511                                 group_options[c] |= RRDR_NONZERO;
512                                 found_non_zero[c] = 1;
513                         }
514
515                         if(unlikely(did_storage_number_reset(n)))
516                                 group_options[c] |= RRDR_RESET;
517
518                         switch(group_method) {
519                                 case GROUP_MAX:
520                                         if(unlikely(abs(value) > abs(group_values[c])))
521                                                 group_values[c] = value;
522                                         break;
523
524                                 default:
525                                 case GROUP_SUM:
526                                 case GROUP_AVERAGE:
527                                         group_values[c] += value;
528                                         break;
529                         }
530                 }
531
532                 // added it
533                 if(unlikely(add_this)) {
534                         if(!rrdr_line_init(r, group_start_t)) break;
535
536                         calculated_number *cn = rrdr_line_values(r);
537                         uint8_t *co = rrdr_line_options(r);
538
539                         for(rd = st->dimensions, c = 0 ; likely(rd && c < dimensions) ; rd = rd->next, c++) {
540
541                                 // update the dimension options
542                                 if(found_non_zero[c]) r->od[c] |= RRDR_NONZERO;
543                                 if(rd->hidden) r->od[c] |= RRDR_HIDDEN;
544
545                                 // store the specific point options
546                                 co[c] = group_options[c];
547
548                                 // store the value
549                                 if(group_counts[c] == 0) {
550                                         cn[c] = 0.0;
551                                         co[c] |= RRDR_EMPTY;
552                                 }
553                                 else if(unlikely(group_method == GROUP_AVERAGE)) {
554                                         cn[c] = group_values[c] / group_counts[c];
555                                 }
556                                 else {
557                                         cn[c] = group_values[c];
558                                 }
559
560                                 // reset them for the next loop
561                                 group_values[c] = 0;
562                                 group_counts[c] = 0;
563                                 group_options[c] = 0;
564                         }
565
566                         added++;
567                         group_count = 0;
568                         add_this = 0;
569                 }
570         }
571
572         rrdr_dump(r);
573         rrdr_free(r);
574         return NULL;
575 }
576
577 unsigned long rrd_stats_json(int type, RRDSET *st, struct web_buffer *wb, int points, int group, int group_method, time_t after, time_t before, int only_non_zero)
578 {
579         int c;
580         pthread_rwlock_rdlock(&st->rwlock);
581
582
583         // -------------------------------------------------------------------------
584         // switch from JSON to google JSON
585
586         char kq[2] = "\"";
587         char sq[2] = "\"";
588         switch(type) {
589                 case DATASOURCE_GOOGLE_JSON:
590                 case DATASOURCE_GOOGLE_JSONP:
591                         kq[0] = '\0';
592                         sq[0] = '\'';
593                         break;
594
595                 case DATASOURCE_JSON:
596                 default:
597                         break;
598         }
599
600
601         // -------------------------------------------------------------------------
602         // validate the parameters
603
604         if(points < 1) points = 1;
605         if(group < 1) group = 1;
606
607         if(before == 0 || before > rrdset_last_entry_t(st)) before = rrdset_last_entry_t(st);
608         if(after  == 0 || after < rrdset_first_entry_t(st)) after = rrdset_first_entry_t(st);
609
610         // ---
611
612         // our return value (the last timestamp printed)
613         // this is required to detect re-transmit in google JSONP
614         time_t last_timestamp = 0;
615
616
617         // -------------------------------------------------------------------------
618         // find how many dimensions we have
619
620         int dimensions = 0;
621         RRDDIM *rd;
622         for( rd = st->dimensions ; rd ; rd = rd->next) dimensions++;
623         if(!dimensions) {
624                 pthread_rwlock_unlock(&st->rwlock);
625                 web_buffer_printf(wb, "No dimensions yet.");
626                 return 0;
627         }
628
629
630         // -------------------------------------------------------------------------
631         // prepare various strings, to speed up the loop
632
633         char overflow_annotation[201]; snprintf(overflow_annotation, 200, ",{%sv%s:%sRESET OR OVERFLOW%s},{%sv%s:%sThe counters have been wrapped.%s}", kq, kq, sq, sq, kq, kq, sq, sq);
634         char normal_annotation[201];   snprintf(normal_annotation,   200, ",{%sv%s:null},{%sv%s:null}", kq, kq, kq, kq);
635         char pre_date[51];             snprintf(pre_date,             50, "             {%sc%s:[{%sv%s:%s", kq, kq, kq, kq, sq);
636         char post_date[21];            snprintf(post_date,            20, "%s}", sq);
637         char pre_value[21];            snprintf(pre_value,            20, ",{%sv%s:", kq, kq);
638         char post_value[21];           snprintf(post_value,           20, "}");
639
640
641         // -------------------------------------------------------------------------
642         // checks for debugging
643
644         if(st->debug) {
645                 debug(D_RRD_STATS, "%s first_entry_t = %lu, last_entry_t = %lu, duration = %lu, after = %lu, before = %lu, duration = %lu, entries_to_show = %lu, group = %lu"
646                         , st->id
647                         , rrdset_first_entry_t(st)
648                         , rrdset_last_entry_t(st)
649                         , rrdset_last_entry_t(st) - rrdset_first_entry_t(st)
650                         , after
651                         , before
652                         , before - after
653                         , points
654                         , group
655                         );
656
657                 if(before < after)
658                         debug(D_RRD_STATS, "WARNING: %s The newest value in the database (%lu) is earlier than the oldest (%lu)", st->name, before, after);
659
660                 if((before - after) > st->entries * st->update_every)
661                         debug(D_RRD_STATS, "WARNING: %s The time difference between the oldest and the newest entries (%lu) is higher than the capacity of the database (%lu)", st->name, before - after, st->entries * st->update_every);
662         }
663
664
665         // -------------------------------------------------------------------------
666         // temp arrays for keeping values per dimension
667
668         calculated_number group_values[dimensions]; // keep sums when grouping
669         int               print_hidden[dimensions]; // keep hidden flags
670         int               found_non_zero[dimensions];
671         int               found_non_existing[dimensions];
672
673         // initialize them
674         for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) {
675                 group_values[c] = 0;
676                 print_hidden[c] = rd->hidden;
677                 found_non_zero[c] = 0;
678                 found_non_existing[c] = 0;
679         }
680
681
682         // error("OLD: points=%d after=%d before=%d group=%d, duration=%d", entries_to_show, before - (st->update_every * group * entries_to_show), before, group, before - after + 1);
683         // rrd2array(st, entries_to_show, before - (st->update_every * group * entries_to_show), before, group_method, only_non_zero);
684         // rrd2rrdr(st, entries_to_show, before - (st->update_every * group * entries_to_show), before, group_method);
685
686         // -------------------------------------------------------------------------
687         // remove dimensions that contain only zeros
688
689         int max_loop = 1;
690         if(only_non_zero) max_loop = 2;
691
692         for(; max_loop ; max_loop--) {
693
694                 // -------------------------------------------------------------------------
695                 // print the JSON header
696
697                 web_buffer_printf(wb, "{\n      %scols%s:\n     [\n", kq, kq);
698                 web_buffer_printf(wb, "         {%sid%s:%s%s,%slabel%s:%stime%s,%spattern%s:%s%s,%stype%s:%sdatetime%s},\n", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq);
699                 web_buffer_printf(wb, "         {%sid%s:%s%s,%slabel%s:%s%s,%spattern%s:%s%s,%stype%s:%sstring%s,%sp%s:{%srole%s:%sannotation%s}},\n", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, kq, kq, sq, sq);
700                 web_buffer_printf(wb, "         {%sid%s:%s%s,%slabel%s:%s%s,%spattern%s:%s%s,%stype%s:%sstring%s,%sp%s:{%srole%s:%sannotationText%s}}", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, kq, kq, sq, sq);
701
702                 // print the header for each dimension
703                 // and update the print_hidden array for the dimensions that should be hidden
704                 int pc = 0;
705                 for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) {
706                         if(!print_hidden[c]) {
707                                 pc++;
708                                 web_buffer_printf(wb, ",\n              {%sid%s:%s%s,%slabel%s:%s%s%s,%spattern%s:%s%s,%stype%s:%snumber%s}", kq, kq, sq, sq, kq, kq, sq, rd->name, sq, kq, kq, sq, sq, kq, kq, sq, sq);
709                         }
710                 }
711                 if(!pc) {
712                         web_buffer_printf(wb, ",\n              {%sid%s:%s%s,%slabel%s:%s%s%s,%spattern%s:%s%s,%stype%s:%snumber%s}", kq, kq, sq, sq, kq, kq, sq, "no data", sq, kq, kq, sq, sq, kq, kq, sq, sq);
713                 }
714
715                 // print the begin of row data
716                 web_buffer_printf(wb, "\n       ],\n    %srows%s:\n     [\n", kq, kq);
717
718
719                 // -------------------------------------------------------------------------
720                 // the main loop
721
722                 int annotate_reset = 0;
723                 int annotation_count = 0;
724
725                 // the minimum line length we expect
726                 int line_size = 4096 + (dimensions * 200);
727
728                 long    t = rrdset_time2slot(st, before),
729                                 stop_at_t = rrdset_time2slot(st, after),
730                                 stop_now = 0;
731
732                 t -= t % group;
733
734                 time_t  now = rrdset_slot2time(st, t),
735                                 dt = st->update_every;
736
737                 long count = 0, printed = 0, group_count = 0;
738                 last_timestamp = 0;
739
740                 if(st->debug) debug(D_RRD_STATS, "%s: REQUEST after:%lu before:%lu, points:%d, group:%d, CHART cur:%ld first: %lu last:%lu, CALC start_t:%ld, stop_t:%ld"
741                                         , st->id
742                                         , after
743                                         , before
744                                         , points
745                                         , group
746                                         , st->current_entry
747                                         , rrdset_first_entry_t(st)
748                                         , rrdset_last_entry_t(st)
749                                         , t
750                                         , stop_at_t
751                                         );
752
753                 for(; !stop_now ; now -= dt, t--) {
754                         if(t < 0) t = st->entries - 1;
755                         if(t == stop_at_t) stop_now = 1;
756
757                         int print_this = 0;
758
759                         if(st->debug) debug(D_RRD_STATS, "%s t = %ld, count = %ld, group_count = %ld, printed = %ld, now = %lu, %s %s"
760                                         , st->id
761                                         , t
762                                         , count + 1
763                                         , group_count + 1
764                                         , printed
765                                         , now
766                                         , (group_count + 1 == group)?"PRINT":"  -  "
767                                         , (now >= after && now <= before)?"RANGE":"  -  "
768                                         );
769
770
771                         // make sure we return data in the proper time range
772                         if(now > before) continue;
773                         if(now < after) break;
774
775                         //if(rrdset_slot2time(st, t) != now)
776                         //      error("%s: slot=%ld, now=%ld, slot2time=%ld, diff=%ld, last_entry_t=%ld, rrdset_last_slot=%ld", st->id, t, now, rrdset_slot2time(st,t), now - rrdset_slot2time(st,t), rrdset_last_entry_t(st), rrdset_last_slot(st));
777
778                         count++;
779                         group_count++;
780
781                         // check if we have to print this now
782                         if(group_count == group) {
783                                 if(printed >= points) {
784                                         // debug(D_RRD_STATS, "Already printed all rows. Stopping.");
785                                         break;
786                                 }
787
788                                 // check if we may exceed the buffer provided
789                                 web_buffer_increase(wb, line_size);
790
791                                 // generate the local date time
792                                 struct tm *tm = localtime(&now);
793                                 if(!tm) { error("localtime() failed."); continue; }
794                                 if(now > last_timestamp) last_timestamp = now;
795
796                                 if(printed) web_buffer_strcpy(wb, "]},\n");
797                                 web_buffer_strcpy(wb, pre_date);
798                                 web_buffer_jsdate(wb, tm->tm_year + 1900, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
799                                 web_buffer_strcpy(wb, post_date);
800
801                                 print_this = 1;
802                         }
803
804                         // do the calculations
805                         for(rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) {
806                                 storage_number n = rd->values[t];
807                                 calculated_number value = unpack_storage_number(n);
808
809                                 if(!does_storage_number_exist(n)) {
810                                         value = 0.0;
811                                         found_non_existing[c]++;
812                                 }
813                                 if(did_storage_number_reset(n)) annotate_reset = 1;
814
815                                 switch(group_method) {
816                                         case GROUP_MAX:
817                                                 if(abs(value) > abs(group_values[c])) group_values[c] = value;
818                                                 break;
819
820                                         case GROUP_SUM:
821                                                 group_values[c] += value;
822                                                 break;
823
824                                         default:
825                                         case GROUP_AVERAGE:
826                                                 group_values[c] += value;
827                                                 if(print_this) group_values[c] /= ( group_count - found_non_existing[c] );
828                                                 break;
829                                 }
830                         }
831
832                         if(print_this) {
833                                 if(annotate_reset) {
834                                         annotation_count++;
835                                         web_buffer_strcpy(wb, overflow_annotation);
836                                         annotate_reset = 0;
837                                 }
838                                 else
839                                         web_buffer_strcpy(wb, normal_annotation);
840
841                                 pc = 0;
842                                 for(c = 0 ; c < dimensions ; c++) {
843                                         if(found_non_existing[c] == group_count) {
844                                                 // all entries are non-existing
845                                                 pc++;
846                                                 web_buffer_strcpy(wb, pre_value);
847                                                 web_buffer_strcpy(wb, "null");
848                                                 web_buffer_strcpy(wb, post_value);
849                                         }
850                                         else if(!print_hidden[c]) {
851                                                 pc++;
852                                                 web_buffer_strcpy(wb, pre_value);
853                                                 web_buffer_rrd_value(wb, group_values[c]);
854                                                 web_buffer_strcpy(wb, post_value);
855
856                                                 if(group_values[c]) found_non_zero[c]++;
857                                         }
858
859                                         // reset them for the next loop
860                                         group_values[c] = 0;
861                                         found_non_existing[c] = 0;
862                                 }
863
864                                 // if all dimensions are hidden, print a null
865                                 if(!pc) {
866                                         web_buffer_strcpy(wb, pre_value);
867                                         web_buffer_strcpy(wb, "null");
868                                         web_buffer_strcpy(wb, post_value);
869                                 }
870
871                                 printed++;
872                                 group_count = 0;
873                         }
874                 }
875
876                 if(printed) web_buffer_printf(wb, "]}");
877                 web_buffer_printf(wb, "\n       ]\n}\n");
878
879                 if(only_non_zero && max_loop > 1) {
880                         int changed = 0;
881                         for(rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) {
882                                 group_values[c] = 0;
883                                 found_non_existing[c] = 0;
884
885                                 if(!print_hidden[c] && !found_non_zero[c]) {
886                                         changed = 1;
887                                         print_hidden[c] = 1;
888                                 }
889                         }
890
891                         if(changed) web_buffer_reset(wb);
892                         else break;
893                 }
894                 else break;
895
896         } // max_loop
897
898         debug(D_RRD_STATS, "RRD_STATS_JSON: %s total %ld bytes", st->name, wb->bytes);
899
900         pthread_rwlock_unlock(&st->rwlock);
901         return last_timestamp;
902 }