misc fixed and TREND and reduce functionality by
[rrdtool.git] / src / rrd_graph_helper.c
1 #include "rrd_graph.h"
2
3 #define dprintf if (gdp->debug) printf
4
5 /* Define prototypes for the parsing methods.
6   Inputs:
7    char *line         - pointer to base of input source
8    unsigned int eaten - index to next input character (INPUT/OUTPUT)
9    graph_desc_t *gdp  - pointer to a graph description
10    image_desc_t *im   - pointer to an image description
11 */
12
13 int rrd_parse_find_gf (char *, unsigned int *, graph_desc_t *);
14 int rrd_parse_legend  (char *, unsigned int *, graph_desc_t *);
15 int rrd_parse_color   (char *, graph_desc_t *);
16 int rrd_parse_CF      (char *, unsigned int *, graph_desc_t *, enum cf_en *);
17 int rrd_parse_print   (char *, unsigned int *, graph_desc_t *, image_desc_t *);
18 int rrd_parse_shift   (char *, unsigned int *, graph_desc_t *, image_desc_t *);
19 int rrd_parse_xport   (char *, unsigned int *, graph_desc_t *, image_desc_t *);
20 int rrd_parse_PVHLAST (char *, unsigned int *, graph_desc_t *, image_desc_t *);
21 int rrd_parse_vname   (char *, unsigned int *, graph_desc_t *, image_desc_t *);
22 int rrd_parse_def     (char *, unsigned int *, graph_desc_t *, image_desc_t *);
23 int rrd_parse_vdef    (char *, unsigned int *, graph_desc_t *, image_desc_t *);
24 int rrd_parse_cdef    (char *, unsigned int *, graph_desc_t *, image_desc_t *);
25
26
27
28 int
29 rrd_parse_find_gf(char *line, unsigned int *eaten, graph_desc_t *gdp) {
30     char funcname[11],c1=0,c2=0;
31     int i=0;
32
33     sscanf(&line[*eaten], "DEBUG%n", &i);
34     if (i) {
35         gdp->debug=1;
36         (*eaten)+=i;
37         i=0;
38         dprintf("Scanning line '%s'\n",&line[*eaten]);
39     }
40     sscanf(&line[*eaten], "%10[A-Z]%n%c%c", funcname, &i, &c1, &c2);
41     if (!i) {
42         rrd_set_error("Could not make sense out of '%s'",line);
43         return 1;
44     }
45     if ((int)(gdp->gf=gf_conv(funcname)) == -1) {
46         rrd_set_error("'%s' is not a valid function name", funcname);
47         return 1;
48     }
49     if (gdp->gf == GF_LINE) {
50         if (c1 < '1' || c1 > '3' || c2 != ':') {
51             rrd_set_error("Malformed LINE command: %s",line);
52             return 1;
53         }
54         gdp->linewidth=c1-'0';
55         i++;
56     } else {
57         if (c1 != ':') {
58             rrd_set_error("Malformed %s command: %s",funcname,line);
59             return 1;
60         }
61     }
62     *eaten+=++i;
63     return 0;
64 }
65
66 int
67 rrd_parse_legend(char *line, unsigned int *eaten, graph_desc_t *gdp) {
68     int i;
69
70     dprintf("- examining '%s'\n",&line[*eaten]);
71
72     i=scan_for_col(&line[*eaten],FMT_LEG_LEN,gdp->legend);
73
74     *eaten += i;
75     if (line[*eaten]!='\0' && line[*eaten]!=':') {
76         rrd_set_error("Legend too long");
77         return 1;
78     } else {
79         dprintf("- found legend '%s'\n", gdp->legend);
80         return 0;
81     }
82 }
83
84 int
85 rrd_parse_color(char *string, graph_desc_t *gdp) {
86     unsigned int r=0,g=0,b=0,a=0;
87     int i1=0,i2=0,i3=0;
88
89     if (string[0] != '#') return 1;
90     sscanf(string, "#%02x%02x%02x%n%02x%n%*s%n",
91                                 &r,&g,&b,&i1,&a,&i2,&i3);
92
93     if (i3) return 1; /* garbage after color */
94     if (!i2) a=0xFF;
95     if (!i1) return 1; /* no color after '#' */
96     gdp->col = r<<24|g<<16|b<<8|a;
97     return 0;
98 }
99
100 int
101 rrd_parse_CF(char *line, unsigned int *eaten, graph_desc_t *gdp, enum cf_en *cf) {
102     char                symname[CF_NAM_SIZE];
103     int                 i=0;
104
105     sscanf(&line[*eaten], CF_NAM_FMT "%n", symname,&i);
106     if ((!i)||((line[*eaten+i]!='\0')&&(line[*eaten+i]!=':'))) {
107         rrd_set_error("Cannot parse CF in '%s'",line);
108         return 1;
109     }
110     (*eaten)+=i;
111     dprintf("- using CF '%s'\n",symname);
112
113     if ((int)(*cf = cf_conv(symname))==-1) {
114         rrd_set_error("Unknown CF '%s' in '%s'",symname,line);
115         return 1;
116     }
117
118     if (line[*eaten]!='\0') (*eaten)++;
119     return 0;
120 }
121
122 /* Parsing old-style xPRINT and new-style xPRINT */
123 int
124 rrd_parse_print(char *line, unsigned int *eaten, graph_desc_t *gdp, image_desc_t *im) {
125     /* vname:CF:format in case of DEF-based vname
126     ** vname:CF:format in case of CDEF-based vname
127     ** vname:format in case of VDEF-based vname
128     */
129     char tmpstr[MAX_VNAME_LEN+1];
130     int i=0;
131
132     sscanf(&line[*eaten], DEF_NAM_FMT ":%n", tmpstr,&i);
133     if (!i) {
134         rrd_set_error("Could not parse line '%s'",line);
135         return 1;
136     }
137     (*eaten)+=i;
138     dprintf("- Found candidate vname '%s'\n",tmpstr);
139
140     if ((gdp->vidx=find_var(im,tmpstr))<0) {
141         rrd_set_error("Not a valid vname: %s in line %s",tmpstr,line);
142         return 1;
143     }
144     switch (im->gdes[gdp->vidx].gf) {
145         case GF_DEF:
146         case GF_CDEF:
147             dprintf("- vname is of type DEF or CDEF, looking for CF\n");
148             if (rrd_parse_CF(line,eaten,gdp,&gdp->cf)) return 1;
149             break;
150         case GF_VDEF:
151             dprintf("- vname is of type VDEF\n");
152             break;
153         default:
154             rrd_set_error("Encountered unknown type variable '%s'",tmpstr);
155             return 1;
156     }
157
158     if (rrd_parse_legend(line,eaten,gdp)) return 1;
159
160     /* Why is there a separate structure member "format" ??? */
161     strcpy(gdp->format,gdp->legend);
162
163     return 0;
164 }
165
166 int
167 rrd_parse_shift(char *line, unsigned int *eaten, graph_desc_t *gdp, image_desc_t *im) {
168         char    *l = strdup(line + *eaten), *p;
169         int     rc = 1;
170
171         p = strchr(l, ',');
172         if (p == NULL) {
173                 rrd_set_error("Invalid SHIFT syntax");
174                 goto out;
175         }
176         *p++ = '\0';
177         
178         if ((gdp->vidx=find_var(im,l))<0) {
179                 rrd_set_error("Not a valid vname: %s in line %s",l,line);
180                 goto out;
181         }
182         
183         /* constant will parse; otherwise, must be VDEF reference */
184         if (sscanf(p, "%ld", &gdp->shval) != 1) {
185                 graph_desc_t    *vdp;
186                 
187                 if ((gdp->shidx=find_var(im, p))<0) {
188                         rrd_set_error("invalid offset vname: %s", p);
189                         goto out;
190                 }
191                 
192                 vdp = &im->gdes[gdp->shidx];
193                 if (vdp->gf != GF_VDEF) {
194                         rrd_set_error("offset must specify value or VDEF");
195                         goto out;
196                 }
197         } else {
198                 gdp->shidx = -1;
199         }
200         
201         *eaten = strlen(line);
202         rc = 0;
203
204  out:
205         free(l);
206         return rc;
207 }
208
209 int
210 rrd_parse_xport(char *line, unsigned int *eaten, graph_desc_t *gdp, image_desc_t *im) {
211         char    *l = strdup(line + *eaten), *p;
212         int     rc = 1;
213
214         p = strchr(l, ':');
215         if (p != NULL)
216                 *p++ = '\0';
217         else
218                 p = "";
219         
220         if ((gdp->vidx=find_var(im, l))==-1){
221                 rrd_set_error("unknown variable '%s'",l);
222                 goto out;
223         }
224         
225         if (strlen(p) >= FMT_LEG_LEN)
226                 *(p + FMT_LEG_LEN) = '\0';
227         
228         strcpy(gdp->legend, p);
229         *eaten = strlen(line);
230         rc = 0;
231         
232  out:
233         free(l);
234         return rc;
235 }
236
237 /* Parsing of PART, VRULE, HRULE, LINE, AREA, STACK and TICK
238 ** is done in one function.  Stacking STACK is silently ignored
239 ** as it is redundant.  Stacking PART, VRULE, HRULE or TICK is
240 ** not allowed.  The check for color doesn't need to be so strict
241 ** anymore, the user can specify the color '#00000000' and
242 ** effectively circumvent this check, so why bother.
243 **
244 ** If a number (which is valid to enter) is more than a
245 ** certain amount of characters, it is caught as an error.
246 ** While this is arguable, so is entering fixed numbers
247 ** with more than MAX_VNAME_LEN significant digits.
248 */
249 int
250 rrd_parse_PVHLAST(char *line, unsigned int *eaten, graph_desc_t *gdp, image_desc_t *im) {
251     int i,j;
252     int colorfound=0;
253     char tmpstr[MAX_VNAME_LEN + 10];    /* vname#RRGGBBAA\0 */
254
255     dprintf("- parsing '%s'\n",&line[*eaten]);
256     dprintf("- from line '%s'\n",line);
257
258     i=scan_for_col(&line[*eaten],MAX_VNAME_LEN+9,tmpstr);
259     if (line[*eaten+i]!='\0' && line[*eaten+i]!=':') {
260         rrd_set_error("Cannot parse line '%s'",line);
261         return 1;
262     }
263
264     j=i; while (j>0 && tmpstr[j]!='#') j--;
265
266     if (tmpstr[j]=='#') {
267         if (rrd_parse_color(&tmpstr[j],gdp)) {
268             rrd_set_error("Could not parse color in '%s'",tmpstr[j]);
269             return 1;
270         }
271         tmpstr[j]='\0';
272         dprintf("- parsed color 0x%08x\n",(unsigned int)gdp->col);
273         colorfound=1;
274     }
275
276     dprintf("- examining '%s'\n",tmpstr);
277     j=0;
278     if (gdp->gf == GF_VRULE) {
279         sscanf(tmpstr,"%li%n",&gdp->xrule,&j);
280         if (j) dprintf("- found time: %li\n",gdp->xrule);
281     } else {
282         sscanf(tmpstr,"%lf%n",&gdp->yrule,&j);
283         if (j) dprintf("- found number: %f\n",gdp->yrule);
284     }
285     if (!j) {
286         if ((gdp->vidx=find_var(im,tmpstr))<0) {
287             rrd_set_error("Not a valid vname: %s in line %s",tmpstr,line);
288             return 1;
289         }
290         dprintf("- found vname: '%s' vidx %li\n",tmpstr,gdp->vidx);
291     }
292     /* "*eaten" is still pointing to the original location,
293     ** "*eaten +i" is pointing to the character after the color
294     ** or to the terminating '\0' in which case we're finished.
295     */
296     if (line[*eaten+i]=='\0') {
297         *eaten+=i;
298         return 0;
299     }
300     *eaten+=++i;
301
302     /* If a color is specified and the only remaining part is
303     ** ":STACK" then it is assumed to be the legend.  An empty
304     ** legend can be specified as expected.  This means the
305     ** following can be done:  LINE1:x#FF0000FF::STACK
306     */
307     if (colorfound) { /* no legend if no color */
308         if (gdp->gf == GF_TICK) {
309             dprintf("- looking for optional number\n");
310             sscanf(&line[*eaten],"%lf%n",&gdp->yrule,&j);
311             if (j) {
312                 dprintf("- found number %f\n",gdp->yrule);
313                 (*eaten)+=j;
314                 if (gdp->yrule > 1.0 || gdp->yrule < -1.0) {
315                     rrd_set_error("Tick factor should be <= 1.0");
316                     return 1;
317                 }
318                 if (line[*eaten] == ':')
319                     (*eaten)++;
320             } else {
321                 dprintf("- not found, defaulting to 0.1\n");
322                 gdp->yrule=0.1;
323                 return 0;
324             }
325         }
326         dprintf("- looking for optional legend\n");
327         dprintf("- in '%s'\n",&line[*eaten]);
328         if (rrd_parse_legend(line, eaten, gdp)) return 1;
329     }
330
331     /* PART, HRULE, VRULE and TICK cannot be stacked.  We're finished */
332     if (   (gdp->gf == GF_HRULE)
333         || (gdp->gf == GF_VRULE)
334         || (gdp->gf == GF_PART)
335         || (gdp->gf == GF_TICK)
336         ) return 0;
337
338     if (line[*eaten]!='\0') {
339         dprintf("- still more, should be STACK\n");
340         (*eaten)++;
341         j=scan_for_col(&line[*eaten],5,tmpstr);
342         if (line[*eaten+j]!='\0') {
343             rrd_set_error("Garbage found where STACK expected");
344             return 1;
345         }
346         if (!strcmp("STACK",tmpstr)) {
347             dprintf("- found STACK\n");
348             gdp->stack=1;
349             (*eaten)+=5;
350         } else {
351             rrd_set_error("Garbage found where STACK expected");
352             return 1;
353         }
354     }
355
356     return 0;
357 }
358
359 int
360 rrd_parse_vname(char *line, unsigned int *eaten, graph_desc_t *gdp, image_desc_t *im) {
361     char tmpstr[MAX_VNAME_LEN + 10];
362     int i=0;
363
364     sscanf(&line[*eaten], DEF_NAM_FMT "=%n", tmpstr,&i);
365     if (!i) {
366         rrd_set_error("Cannot parse vname from '%s'",line);
367         return 1;
368     }
369     dprintf("- found candidate '%s'\n",tmpstr);
370
371     if ((gdp->vidx=find_var(im,tmpstr))>=0) {
372         rrd_set_error("Attempting to reuse '%s'",im->gdes[gdp->vidx].vname);
373         return 1;
374     }
375     strcpy(gdp->vname,tmpstr);
376     dprintf("- created vname '%s' vidx %lu\n", gdp->vname,im->gdes_c-1);
377     (*eaten)+=i;
378     return 0;
379 }
380
381 int
382 rrd_parse_def(char *line, unsigned int *eaten, graph_desc_t *gdp, image_desc_t *im) {
383     int                 i=0;
384     char                command[7]; /* step, start, end, reduce */
385     char                tmpstr[256];
386     struct rrd_time_value       start_tv,end_tv;
387     time_t              start_tmp=0,end_tmp=0;
388     char                *parsetime_error=NULL;
389
390     start_tv.type   = end_tv.type=ABSOLUTE_TIME;
391     start_tv.offset = end_tv.offset=0;
392     localtime_r(&gdp->start, &start_tv.tm);
393     localtime_r(&gdp->end, &end_tv.tm);
394     
395     dprintf("- parsing '%s'\n",&line[*eaten]);
396     dprintf("- from line '%s'\n",line);
397
398     if (rrd_parse_vname(line,eaten,gdp,im)) return 1;
399     i=scan_for_col(&line[*eaten],254,gdp->rrd);
400     if (line[*eaten+i]!=':') {
401         rrd_set_error("Problems reading database name");
402         return 1;
403     }
404     (*eaten)+=++i;
405     dprintf("- using file '%s'\n",gdp->rrd);
406
407     i=0;
408     sscanf(&line[*eaten], DS_NAM_FMT ":%n", gdp->ds_nam,&i);
409     if (!i) {
410         rrd_set_error("Cannot parse DS in '%s'",line);
411         return 1;
412     }
413     (*eaten)+=i;
414     dprintf("- using DS '%s'\n",gdp->ds_nam);
415
416     if (rrd_parse_CF(line,eaten,gdp,&gdp->cf)) return 1;
417     gdp->cf_reduce = gdp->cf;
418     
419     if (line[*eaten]=='\0') return 0;
420
421     while (1) {
422         dprintf("- optional parameter follows: %s\n", &line[*eaten]);
423         i=0;
424         sscanf(&line[*eaten], "%6[a-z]=%n", command, &i);
425         if (!i) {
426             rrd_set_error("Parse error in '%s'",line);
427             return 1;
428         }
429         (*eaten)+=i;
430         dprintf("- processing '%s'\n",command);
431         if (!strcmp("reduce",command)) {
432           if (rrd_parse_CF(line,eaten,gdp,&gdp->cf_reduce)) return 1;
433           if (line[*eaten] != '\0')
434               (*eaten)--;
435         } else if (!strcmp("step",command)) {
436             i=0;
437             sscanf(&line[*eaten],"%lu%n",&gdp->step,&i);
438             (*eaten)+=i;
439             dprintf("- using step %lu\n",gdp->step);
440         } else if (!strcmp("start",command)) {
441             i=scan_for_col(&line[*eaten],255,tmpstr);
442             (*eaten)+=i;
443             if ((parsetime_error = parsetime(tmpstr, &start_tv))) {
444                 rrd_set_error( "start time: %s", parsetime_error );
445                 return 1;
446             }
447             dprintf("- done parsing:  '%s'\n",&line[*eaten]);
448         } else if (!strcmp("end",command)) {
449             i=scan_for_col(&line[*eaten],255,tmpstr);
450             (*eaten)+=i;
451             if ((parsetime_error = parsetime(tmpstr, &end_tv))) {
452                 rrd_set_error( "end time: %s", parsetime_error );
453                 return 1;
454             }
455             dprintf("- done parsing:  '%s'\n",&line[*eaten]);
456         } else {
457             rrd_set_error("Parse error in '%s'",line);
458             return 1;
459         }
460         if (line[*eaten]=='\0') break;
461         if (line[*eaten]!=':') {
462             dprintf("- Expected to see end of string but got '%s'\n",\
463                                                          &line[*eaten]);
464             rrd_set_error("Parse error in '%s'",line);
465             return 1;
466         }
467         (*eaten)++;
468     }
469     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
470         /* error string is set in parsetime.c */
471         return 1;
472     }
473     if (start_tmp < 3600*24*365*10) {
474         rrd_set_error("the first entry to fetch should be "
475                         "after 1980 (%ld)",start_tmp);
476         return 1;
477     }
478
479     if (end_tmp < start_tmp) {
480         rrd_set_error("start (%ld) should be less than end (%ld)",
481                         start_tmp, end_tmp);
482         return 1;
483     }
484
485     gdp->start = start_tmp;
486     gdp->end = end_tmp;
487
488     dprintf("- start time %lu\n",gdp->start);
489     dprintf("- end   time %lu\n",gdp->end);
490
491     return 0;
492 }
493
494 int
495 rrd_parse_vdef(char *line, unsigned int *eaten, graph_desc_t *gdp, image_desc_t *im) {
496     char tmpstr[MAX_VNAME_LEN+1];       /* vname\0 */
497     int i=0;
498
499     dprintf("- parsing '%s'\n",&line[*eaten]);
500     if (rrd_parse_vname(line,eaten,gdp,im)) return 1;
501
502     sscanf(&line[*eaten], DEF_NAM_FMT ",%n", tmpstr,&i);
503     if (!i) {
504         rrd_set_error("Cannot parse line '%s'",line);
505         return 1;
506     }
507     if ((gdp->vidx=find_var(im,tmpstr))<0) {
508         rrd_set_error("Not a valid vname: %s in line %s",tmpstr,line);
509         return 1;
510     }
511     if (   im->gdes[gdp->vidx].gf != GF_DEF
512         && im->gdes[gdp->vidx].gf != GF_CDEF) {
513         rrd_set_error("variable '%s' not DEF nor "
514                         "CDEF in VDEF '%s'", tmpstr,gdp->vname);
515         return 1;
516     }
517     dprintf("- found vname: '%s' vidx %li\n",tmpstr,gdp->vidx);
518     (*eaten)+=i;
519
520     dprintf("- calling vdef_parse with param '%s'\n",&line[*eaten]);
521     vdef_parse(gdp,&line[*eaten]);
522     while (line[*eaten]!='\0'&&line[*eaten]!=':')
523         (*eaten)++;
524
525     return 0;
526 }
527
528 int
529 rrd_parse_cdef(char *line, unsigned int *eaten, graph_desc_t *gdp, image_desc_t *im) {
530     dprintf("- parsing '%s'\n",&line[*eaten]);
531     if (rrd_parse_vname(line,eaten,gdp,im)) return 1;
532     if ((gdp->rpnp = rpn_parse(
533         (void *)im,
534         &line[*eaten],
535         &find_var_wrapper)
536     )==NULL) {
537         rrd_set_error("invalid rpn expression in: %s",&line[*eaten]);
538         return 1;
539     };
540     while (line[*eaten]!='\0'&&line[*eaten]!=':')
541         (*eaten)++;
542     return 0;
543 }
544
545 void
546 rrd_graph_script(int argc, char *argv[], image_desc_t *im, int optno) {
547     int i;
548
549     for (i=optind+optno;i<argc;i++) {
550         graph_desc_t *gdp;
551         unsigned int eaten=0;
552
553         if (gdes_alloc(im)) return; /* the error string is already set */
554         gdp = &im->gdes[im->gdes_c-1];
555 #ifdef DEBUG
556         gdp->debug = 1;
557 #endif
558
559         if (rrd_parse_find_gf(argv[i],&eaten,gdp)) return;
560
561         switch (gdp->gf) {
562             case GF_SHIFT:      /* vname:value */
563                 if (rrd_parse_shift(argv[i],&eaten,gdp,im)) return;
564                 break;
565             case GF_XPORT:
566                 if (rrd_parse_xport(argv[i],&eaten,gdp,im)) return;
567                 break;
568             case GF_PRINT:      /* vname:CF:format -or- vname:format */
569             case GF_GPRINT:     /* vname:CF:format -or- vname:format */
570                 if (rrd_parse_print(argv[i],&eaten,gdp,im)) return;
571                 break;
572             case GF_COMMENT:    /* text */
573                 if (rrd_parse_legend(argv[i],&eaten,gdp)) return;
574                 break;
575             case GF_PART:       /* value[#color[:legend]] */
576             case GF_VRULE:      /* value#color[:legend] */
577             case GF_HRULE:      /* value#color[:legend] */
578             case GF_LINE:       /* vname-or-value[#color[:legend]][:STACK] */
579             case GF_AREA:       /* vname-or-value[#color[:legend]][:STACK] */
580             case GF_STACK:      /* vname-or-value[#color[:legend]] */
581             case GF_TICK:       /* vname#color[:num[:legend]] */
582                 if (rrd_parse_PVHLAST(argv[i],&eaten,gdp,im)) return;
583                 break;
584         /* data acquisition */
585             case GF_DEF:        /* vname=x:DS:CF:[:step=#][:start=#][:end=#] */
586                 if (rrd_parse_def(argv[i],&eaten,gdp,im)) return;
587                 break;
588             case GF_CDEF:       /* vname=rpn-expression */
589                 if (rrd_parse_cdef(argv[i],&eaten,gdp,im)) return;
590                 break;
591             case GF_VDEF:       /* vname=rpn-expression */
592                 if (rrd_parse_vdef(argv[i],&eaten,gdp,im)) return;
593                 break;
594         }
595         if (gdp->debug) {
596             dprintf("used %i out of %i chars\n",eaten,strlen(argv[i]));
597             dprintf("parsed line: '%s'\n",argv[i]);
598             dprintf("remaining: '%s'\n",&argv[i][eaten]);
599             if (eaten >= strlen(argv[i]))
600                 dprintf("Command finished successfully\n");
601         }
602         if (eaten < strlen(argv[i])) {
603             rrd_set_error("Garbage '%s' after command:\n%s",
604                         &argv[i][eaten],argv[i]);
605             return;
606         }
607     }
608 }