CDEF operators SHIFT, SQRT, SORT, and REV (reverse). See documentation for what
[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 *);
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) {
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)(gdp->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)) 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             } else {
319                 dprintf("- not found, defaulting to 0.1\n");
320                 gdp->yrule=0.1;
321                 return 0;
322             }
323         }
324         dprintf("- looking for optional legend\n");
325         dprintf("- in '%s'\n",&line[*eaten]);
326         if (rrd_parse_legend(line, eaten, gdp)) return 1;
327     }
328
329     /* PART, HRULE, VRULE and TICK cannot be stacked.  We're finished */
330     if (   (gdp->gf == GF_HRULE)
331         || (gdp->gf == GF_VRULE)
332         || (gdp->gf == GF_PART)
333         || (gdp->gf == GF_TICK)
334         ) return 0;
335
336     if (line[*eaten]!='\0') {
337         dprintf("- still more, should be STACK\n");
338         (*eaten)++;
339         j=scan_for_col(&line[*eaten],5,tmpstr);
340         if (line[*eaten+j]!='\0') {
341             rrd_set_error("Garbage found where STACK expected");
342             return 1;
343         }
344         if (!strcmp("STACK",tmpstr)) {
345             dprintf("- found STACK\n");
346             gdp->stack=1;
347             (*eaten)+=5;
348         } else {
349             rrd_set_error("Garbage found where STACK expected");
350             return 1;
351         }
352     }
353
354     return 0;
355 }
356
357 int
358 rrd_parse_vname(char *line, unsigned int *eaten, graph_desc_t *gdp, image_desc_t *im) {
359     char tmpstr[MAX_VNAME_LEN + 10];
360     int i=0;
361
362     sscanf(&line[*eaten], DEF_NAM_FMT "=%n", tmpstr,&i);
363     if (!i) {
364         rrd_set_error("Cannot parse vname from '%s'",line);
365         return 1;
366     }
367     dprintf("- found candidate '%s'\n",tmpstr);
368
369     if ((gdp->vidx=find_var(im,tmpstr))>=0) {
370         rrd_set_error("Attempting to reuse '%s'",im->gdes[gdp->vidx].vname);
371         return 1;
372     }
373     strcpy(gdp->vname,tmpstr);
374     dprintf("- created vname '%s' vidx %lu\n", gdp->vname,im->gdes_c-1);
375     (*eaten)+=i;
376     return 0;
377 }
378
379 int
380 rrd_parse_def(char *line, unsigned int *eaten, graph_desc_t *gdp, image_desc_t *im) {
381     int                 i=0;
382     char                command[6]; /* step, start, end */
383     char                tmpstr[256];
384     struct rrd_time_value       start_tv,end_tv;
385     time_t              start_tmp=0,end_tmp=0;
386     char                *parsetime_error=NULL;
387
388     start_tv.type   = end_tv.type=ABSOLUTE_TIME;
389     start_tv.offset = end_tv.offset=0;
390     localtime_r(&gdp->start, &start_tv.tm);
391     localtime_r(&gdp->end, &end_tv.tm);
392     
393     dprintf("- parsing '%s'\n",&line[*eaten]);
394     dprintf("- from line '%s'\n",line);
395
396     if (rrd_parse_vname(line,eaten,gdp,im)) return 1;
397     i=scan_for_col(&line[*eaten],254,gdp->rrd);
398     if (line[*eaten+i]!=':') {
399         rrd_set_error("Problems reading database name");
400         return 1;
401     }
402     (*eaten)+=++i;
403     dprintf("- using file '%s'\n",gdp->rrd);
404
405     i=0;
406     sscanf(&line[*eaten], DS_NAM_FMT ":%n", gdp->ds_nam,&i);
407     if (!i) {
408         rrd_set_error("Cannot parse DS in '%s'",line);
409         return 1;
410     }
411     (*eaten)+=i;
412     dprintf("- using DS '%s'\n",gdp->ds_nam);
413
414     if (rrd_parse_CF(line,eaten,gdp)) return 1;
415
416     if (line[*eaten]=='\0') return 0;
417
418     while (1) {
419         dprintf("- optional parameter follows: %s\n", &line[*eaten]);
420         i=0;
421         sscanf(&line[*eaten], "%5[a-z]=%n", command, &i);
422         if (!i) {
423             rrd_set_error("Parse error in '%s'",line);
424             return 1;
425         }
426         (*eaten)+=i;
427         dprintf("- processing '%s'\n",command);
428         if (!strcmp("step",command)) {
429             i=0;
430             sscanf(&line[*eaten],"%lu%n",&gdp->step,&i);
431             (*eaten)+=i;
432             dprintf("- using step %lu\n",gdp->step);
433         } else if (!strcmp("start",command)) {
434             i=scan_for_col(&line[*eaten],255,tmpstr);
435             (*eaten)+=i;
436             if ((parsetime_error = parsetime(tmpstr, &start_tv))) {
437                 rrd_set_error( "start time: %s", parsetime_error );
438                 return 1;
439             }
440             dprintf("- done parsing:  '%s'\n",&line[*eaten]);
441         } else if (!strcmp("end",command)) {
442             i=scan_for_col(&line[*eaten],255,tmpstr);
443             (*eaten)+=i;
444             if ((parsetime_error = parsetime(tmpstr, &end_tv))) {
445                 rrd_set_error( "end time: %s", parsetime_error );
446                 return 1;
447             }
448             dprintf("- done parsing:  '%s'\n",&line[*eaten]);
449         } else {
450             rrd_set_error("Parse error in '%s'",line);
451             return 1;
452         }
453         if (line[*eaten]=='\0') break;
454         if (line[*eaten]!=':') {
455             dprintf("- Expected to see end of string but got '%s'\n",\
456                                                          &line[*eaten]);
457             rrd_set_error("Parse error in '%s'",line);
458             return 1;
459         }
460         (*eaten)++;
461     }
462     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
463         /* error string is set in parsetime.c */
464         return 1;
465     }
466     if (start_tmp < 3600*24*365*10) {
467         rrd_set_error("the first entry to fetch should be "
468                         "after 1980 (%ld)",start_tmp);
469         return 1;
470     }
471
472     if (end_tmp < start_tmp) {
473         rrd_set_error("start (%ld) should be less than end (%ld)",
474                         start_tmp, end_tmp);
475         return 1;
476     }
477
478     gdp->start = start_tmp;
479     gdp->end = end_tmp;
480
481     dprintf("- start time %lu\n",gdp->start);
482     dprintf("- end   time %lu\n",gdp->end);
483
484     return 0;
485 }
486
487 int
488 rrd_parse_vdef(char *line, unsigned int *eaten, graph_desc_t *gdp, image_desc_t *im) {
489     char tmpstr[MAX_VNAME_LEN+1];       /* vname\0 */
490     int i=0;
491
492     dprintf("- parsing '%s'\n",&line[*eaten]);
493     if (rrd_parse_vname(line,eaten,gdp,im)) return 1;
494
495     sscanf(&line[*eaten], DEF_NAM_FMT ",%n", tmpstr,&i);
496     if (!i) {
497         rrd_set_error("Cannot parse line '%s'",line);
498         return 1;
499     }
500     if ((gdp->vidx=find_var(im,tmpstr))<0) {
501         rrd_set_error("Not a valid vname: %s in line %s",tmpstr,line);
502         return 1;
503     }
504     if (   im->gdes[gdp->vidx].gf != GF_DEF
505         && im->gdes[gdp->vidx].gf != GF_CDEF) {
506         rrd_set_error("variable '%s' not DEF nor "
507                         "CDEF in VDEF '%s'", tmpstr,gdp->vname);
508         return 1;
509     }
510     dprintf("- found vname: '%s' vidx %li\n",tmpstr,gdp->vidx);
511     (*eaten)+=i;
512
513     dprintf("- calling vdef_parse with param '%s'\n",&line[*eaten]);
514     vdef_parse(gdp,&line[*eaten]);
515     while (line[*eaten]!='\0'&&line[*eaten]!=':')
516         (*eaten)++;
517
518     return 0;
519 }
520
521 int
522 rrd_parse_cdef(char *line, unsigned int *eaten, graph_desc_t *gdp, image_desc_t *im) {
523     dprintf("- parsing '%s'\n",&line[*eaten]);
524     if (rrd_parse_vname(line,eaten,gdp,im)) return 1;
525     if ((gdp->rpnp = rpn_parse(
526         (void *)im,
527         &line[*eaten],
528         &find_var_wrapper)
529     )==NULL) {
530         rrd_set_error("invalid rpn expression in: %s",&line[*eaten]);
531         return 1;
532     };
533     while (line[*eaten]!='\0'&&line[*eaten]!=':')
534         (*eaten)++;
535     return 0;
536 }
537
538 void
539 rrd_graph_script(int argc, char *argv[], image_desc_t *im, int optno) {
540     int i;
541
542     for (i=optind+optno;i<argc;i++) {
543         graph_desc_t *gdp;
544         unsigned int eaten=0;
545
546         if (gdes_alloc(im)) return; /* the error string is already set */
547         gdp = &im->gdes[im->gdes_c-1];
548 #ifdef DEBUG
549         gdp->debug = 1;
550 #endif
551
552         if (rrd_parse_find_gf(argv[i],&eaten,gdp)) return;
553
554         switch (gdp->gf) {
555             case GF_SHIFT:      /* vname:value */
556                 if (rrd_parse_shift(argv[i],&eaten,gdp,im)) return;
557                 break;
558             case GF_XPORT:
559                 if (rrd_parse_xport(argv[i],&eaten,gdp,im)) return;
560                 break;
561             case GF_PRINT:      /* vname:CF:format -or- vname:format */
562             case GF_GPRINT:     /* vname:CF:format -or- vname:format */
563                 if (rrd_parse_print(argv[i],&eaten,gdp,im)) return;
564                 break;
565             case GF_COMMENT:    /* text */
566                 if (rrd_parse_legend(argv[i],&eaten,gdp)) return;
567                 break;
568             case GF_PART:       /* value[#color[:legend]] */
569             case GF_VRULE:      /* value#color[:legend] */
570             case GF_HRULE:      /* value#color[:legend] */
571             case GF_LINE:       /* vname-or-value[#color[:legend]][:STACK] */
572             case GF_AREA:       /* vname-or-value[#color[:legend]][:STACK] */
573             case GF_STACK:      /* vname-or-value[#color[:legend]] */
574             case GF_TICK:       /* vname#color[:num[:legend]] */
575                 if (rrd_parse_PVHLAST(argv[i],&eaten,gdp,im)) return;
576                 break;
577         /* data acquisition */
578             case GF_DEF:        /* vname=x:DS:CF:[:step=#][:start=#][:end=#] */
579                 if (rrd_parse_def(argv[i],&eaten,gdp,im)) return;
580                 break;
581             case GF_CDEF:       /* vname=rpn-expression */
582                 if (rrd_parse_cdef(argv[i],&eaten,gdp,im)) return;
583                 break;
584             case GF_VDEF:       /* vname=rpn-expression */
585                 if (rrd_parse_vdef(argv[i],&eaten,gdp,im)) return;
586                 break;
587         }
588         if (gdp->debug) {
589             dprintf("used %i out of %i chars\n",eaten,strlen(argv[i]));
590             dprintf("parsed line: '%s'\n",argv[i]);
591             dprintf("remaining: '%s'\n",&argv[i][eaten]);
592             if (eaten >= strlen(argv[i]))
593                 dprintf("Command finished successfully\n");
594         }
595         if (eaten < strlen(argv[i])) {
596             rrd_set_error("Garbage '%s' after command:\n%s",
597                         &argv[i][eaten],argv[i]);
598             return;
599         }
600     }
601 }