Support for COMPUTE data sources (CDEF data sources). Removes the RPN
[rrdtool.git] / src / rrd_restore.c
1 /*****************************************************************************
2  * RRDtool 1.0.33  Copyright Tobias Oetiker, 1997 - 2000
3  *****************************************************************************
4  * rrd_restore.c  creates new rrd from data dumped by rrd_dump.c
5  *****************************************************************************/
6
7 #include "rrd_tool.h"
8 #include "rrd_rpncalc.h"
9
10 /* Prototypes */
11
12 void xml_lc(char*);
13 int skip(char **);
14 int eat_tag(char **, char *);
15 int read_tag(char **, char *, char *, void *);
16 int xml2rrd(char*, rrd_t*, char);
17 int rrd_write(char *, rrd_t *);
18 void parse_patch1028_RRA_params(char **buf, rrd_t *rrd, int rra_index);
19 void parse_patch1028_CDP_params(char **buf, rrd_t *rrd, int rra_index, int ds_index);
20 void parse_FAILURES_history(char **buf, rrd_t *rrd, int rra_index, int ds_index);
21
22 /* convert all ocurances of <BlaBlaBla> to <blablabla> */
23
24 void xml_lc(char* buf){
25   int intag=0;
26   while((*buf)){
27     if (intag ==0 && (*buf) == '<') {
28       intag = 1;
29     }
30     else if (intag ==1 && (*buf) == '>') {
31       intag = 0;
32       continue;
33     } else  if (intag ==1) {
34       *buf = tolower(*buf);
35     }
36     buf++;    
37   }
38 }
39
40 int skip(char **buf){
41   char *ptr;  
42   ptr=(*buf);
43   do {
44     (*buf)=ptr;
45     while((*(ptr+1)) && ((*ptr)==' ' ||  (*ptr)=='\r' || (*ptr)=='\n' || (*ptr)=='\t')) ptr++;
46     if (strncmp(ptr,"<!--",4) == 0) {
47       ptr= strstr(ptr,"-->");
48       if (ptr) ptr+=3; else {
49         rrd_set_error("Dangling Comment");
50         (*buf) = NULL;
51         return -1;
52       }
53     }
54   } while ((*buf)!=ptr);  
55   return 1;
56 }
57
58 int eat_tag(char **buf, char *tag){ 
59   if ((*buf)==NULL) return -1;   /* fall though clause */
60
61   rrd_clear_error();
62   skip(buf);
63   if ((**buf)=='<' 
64       && strncmp((*buf)+1,tag,strlen(tag)) == 0 
65       && *((*buf)+strlen(tag)+1)=='>') {
66     (*buf) += strlen(tag)+2;
67   }
68   else {
69     rrd_set_error("No <%s> tag found",tag);
70     (*buf) = NULL;
71     return -1;
72   }
73   skip(buf);
74   return 1;
75 }
76
77 int read_tag(char **buf, char *tag, char *format, void *value){
78     char *end_tag;
79     int matches;
80     if ((*buf)==NULL) return -1;   /* fall though clause */
81     rrd_clear_error();
82     if (eat_tag(buf,tag)==1){
83         char *temp;
84         temp = (*buf);
85         while(*((*buf)+1) && (*(*buf) != '<')) (*buf)++; /*find start of endtag*/
86         *(*buf) = '\0';
87         matches =sscanf(temp,format,value);
88         *(*buf) = '<';
89         end_tag = malloc((strlen(tag)+2)*sizeof(char));
90         sprintf(end_tag,"/%s",tag);
91         eat_tag(buf,end_tag);
92         free(end_tag);
93         if (matches == 0 && strcmp(format,"%lf") == 0)
94             (*((double* )(value))) = DNAN;
95         if (matches != 1)       return 0;       
96         return 1;
97     }
98     return -1;
99 }
100
101
102 /* parse the data stored in buf and return a filled rrd structure */
103 int xml2rrd(char* buf, rrd_t* rrd, char rc){
104   /* pass 1 identify number of RRAs  */
105   char *ptr,*ptr2,*ptr3; /* walks thought the buffer */
106   long rows=0,mempool=0,i=0;
107   int rra_index;
108   xml_lc(buf); /* lets lowercase all active parts of the xml */
109   ptr=buf;
110   ptr2=buf;
111   ptr3=buf;
112   /* start with an RRD tag */
113
114   eat_tag(&ptr,"rrd");
115   /* allocate static header */
116   if((rrd->stat_head = calloc(1,sizeof(stat_head_t)))==NULL){
117     rrd_set_error("allocating rrd.stat_head");
118     return -1;    
119   };
120
121   strcpy(rrd->stat_head->cookie,RRD_COOKIE);
122   read_tag(&ptr,"version","%4[0-9]",rrd->stat_head->version);
123   /* added primitive version checking */
124   if (atoi(rrd -> stat_head -> version) != 2)
125   {
126     rrd_set_error("Incompatible file version, detected version %s, required version %s\n",
127                   rrd -> stat_head -> version, RRD_VERSION);
128     free(rrd -> stat_head);
129     return -1;
130   }
131   rrd->stat_head->float_cookie = FLOAT_COOKIE;
132   rrd->stat_head->ds_cnt = 0;
133   rrd->stat_head->rra_cnt = 0;
134   read_tag(&ptr,"step","%lu",&(rrd->stat_head->pdp_step));
135
136   /* allocate live head */
137   if((rrd->live_head = calloc(1,sizeof(live_head_t)))==NULL){
138     rrd_set_error("allocating rrd.live_head");
139     return -1;    
140   }
141   read_tag(&ptr,"lastupdate","%lu",&(rrd->live_head->last_up));
142
143   /* Data Source Definition Part */
144   ptr2 = ptr;
145   while (eat_tag(&ptr2,"ds") == 1){
146       rrd->stat_head->ds_cnt++;
147       if((rrd->ds_def = rrd_realloc(rrd->ds_def,rrd->stat_head->ds_cnt*sizeof(ds_def_t)))==NULL){
148           rrd_set_error("allocating rrd.ds_def");
149           return -1;
150       };
151       /* clean out memory to make sure no data gets stored from previous tasks */
152       memset(&(rrd->ds_def[rrd->stat_head->ds_cnt-1]), 0, sizeof(ds_def_t));
153       if((rrd->pdp_prep = rrd_realloc(rrd->pdp_prep,rrd->stat_head->ds_cnt
154                                   *sizeof(pdp_prep_t)))==NULL){
155         rrd_set_error("allocating pdp_prep");
156         return(-1);
157       }
158       /* clean out memory to make sure no data gets stored from previous tasks */
159       memset(&(rrd->pdp_prep[rrd->stat_head->ds_cnt-1]), 0, sizeof(pdp_prep_t));
160
161       read_tag(&ptr2,"name",DS_NAM_FMT,rrd->ds_def[rrd->stat_head->ds_cnt-1].ds_nam);
162
163       read_tag(&ptr2,"type",DST_FMT,rrd->ds_def[rrd->stat_head->ds_cnt-1].dst);
164       /* test for valid type */
165       if(dst_conv(rrd->ds_def[rrd->stat_head->ds_cnt-1].dst) == -1) return -1;      
166
167           if (dst_conv(rrd->ds_def[rrd->stat_head->ds_cnt-1].dst) != DST_CDEF)
168           {
169       read_tag(&ptr2,"minimal_heartbeat","%lu",
170                &(rrd->ds_def[rrd->stat_head->ds_cnt-1].par[DS_mrhb_cnt].u_cnt));
171       read_tag(&ptr2,"min","%lf",&(rrd->ds_def[rrd->stat_head->ds_cnt-1].par[DS_min_val].u_val));
172       read_tag(&ptr2,"max","%lf",&(rrd->ds_def[rrd->stat_head->ds_cnt-1].par[DS_max_val].u_val));
173           } else { /* DST_CDEF */
174                  char buffer[1024];
175              read_tag(&ptr2,"cdef","%s",buffer);
176                  parseCDEF_DS(buffer,rrd,rrd -> stat_head -> ds_cnt - 1);
177           }
178
179       read_tag(&ptr2,"last_ds","%30s",rrd->pdp_prep[rrd->stat_head->ds_cnt-1].last_ds);
180       read_tag(&ptr2,"value","%lf",&(rrd->pdp_prep[rrd->stat_head->ds_cnt-1].scratch[PDP_val].u_val));
181       read_tag(&ptr2,"unknown_sec","%lu",&(rrd->pdp_prep[rrd->stat_head->ds_cnt-1].scratch[PDP_unkn_sec_cnt].u_cnt));      
182       eat_tag(&ptr2,"/ds");
183       ptr=ptr2;
184   }
185   
186   ptr2 = ptr;
187   while (eat_tag(&ptr2,"rra") == 1){
188       rrd->stat_head->rra_cnt++;
189
190       /* alocate and reset rra definition areas */
191       if((rrd->rra_def = rrd_realloc(rrd->rra_def,rrd->stat_head->rra_cnt*sizeof(rra_def_t)))==NULL){
192           rrd_set_error("allocating rra_def"); return -1; }      
193       memset(&(rrd->rra_def[rrd->stat_head->rra_cnt-1]), 0, sizeof(rra_def_t));
194
195       /* alocate and reset consolidation point areas */
196       if((rrd->cdp_prep = rrd_realloc(rrd->cdp_prep,
197                                   rrd->stat_head->rra_cnt
198                                   *rrd->stat_head->ds_cnt*sizeof(cdp_prep_t)))==NULL){
199           rrd_set_error("allocating cdp_prep"); return -1; }
200
201       memset(&(rrd->cdp_prep[rrd->stat_head->ds_cnt*(rrd->stat_head->rra_cnt-1)]), 
202              0, rrd->stat_head->ds_cnt*sizeof(cdp_prep_t));
203
204       
205       read_tag(&ptr2,"cf",CF_NAM_FMT,rrd->rra_def[rrd->stat_head->rra_cnt-1].cf_nam);
206       /* test for valid type */
207       if(cf_conv(rrd->rra_def[rrd->stat_head->rra_cnt-1].cf_nam) == -1) return -1;
208
209       read_tag(&ptr2,"pdp_per_row","%lu",&(rrd->rra_def[rrd->stat_head->rra_cnt-1].pdp_cnt));
210       /* support to read RRA parameters */
211       eat_tag(&ptr2, "params");
212       skip(&ptr2);
213       rra_index = rrd->stat_head->rra_cnt - 1;
214       /* backwards compatibility w/ old patch */
215       if (strncmp(ptr2, "<value>",7) == 0) {
216          parse_patch1028_RRA_params(&ptr2,rrd,rra_index); 
217       } else {
218       switch(cf_conv(rrd -> rra_def[rra_index].cf_nam)) {
219       case CF_HWPREDICT:
220          read_tag(&ptr2, "hw_alpha", "%lf", 
221             &(rrd->rra_def[rra_index].par[RRA_hw_alpha].u_val));
222          read_tag(&ptr2, "hw_beta", "%lf", 
223             &(rrd->rra_def[rra_index].par[RRA_hw_beta].u_val));
224          read_tag(&ptr2, "dependent_rra_idx", "%lu", 
225             &(rrd->rra_def[rra_index].par[RRA_dependent_rra_idx].u_cnt));
226          break;
227       case CF_SEASONAL:
228       case CF_DEVSEASONAL:
229          read_tag(&ptr2, "seasonal_gamma", "%lf", 
230             &(rrd->rra_def[rra_index].par[RRA_seasonal_gamma].u_val));
231          read_tag(&ptr2, "seasonal_smooth_idx", "%lu", 
232             &(rrd->rra_def[rra_index].par[RRA_seasonal_smooth_idx].u_cnt));
233          read_tag(&ptr2, "dependent_rra_idx", "%lu", 
234             &(rrd->rra_def[rra_index].par[RRA_dependent_rra_idx].u_cnt));
235          break;
236       case CF_FAILURES:
237          read_tag(&ptr2, "delta_pos", "%lf", 
238             &(rrd->rra_def[rra_index].par[RRA_delta_pos].u_val));
239          read_tag(&ptr2, "delta_neg", "%lf", 
240             &(rrd->rra_def[rra_index].par[RRA_delta_neg].u_val));
241          read_tag(&ptr2, "window_len", "%lu", 
242             &(rrd->rra_def[rra_index].par[RRA_window_len].u_cnt));
243          read_tag(&ptr2, "failure_threshold", "%lu", 
244             &(rrd->rra_def[rra_index].par[RRA_failure_threshold].u_cnt));
245          /* fall thru */
246       case CF_DEVPREDICT:
247          read_tag(&ptr2, "dependent_rra_idx", "%lu", 
248             &(rrd->rra_def[rra_index].par[RRA_dependent_rra_idx].u_cnt));
249          break;
250       case CF_AVERAGE:
251       case CF_MAXIMUM:
252       case CF_MINIMUM:
253       case CF_LAST:
254       default:
255          read_tag(&ptr2, "xff","%lf",
256             &(rrd->rra_def[rra_index].par[RRA_cdp_xff_val].u_val));
257       }
258       }
259       eat_tag(&ptr2, "/params");
260       eat_tag(&ptr2,"cdp_prep");
261       for(i=0;i<rrd->stat_head->ds_cnt;i++)
262       {
263       eat_tag(&ptr2,"ds");
264       /* support to read CDP parameters */
265       rra_index = rrd->stat_head->rra_cnt-1; 
266       skip(&ptr2);
267       if (strncmp(ptr2, "<value>",7) == 0) {
268          parse_patch1028_CDP_params(&ptr2,rrd,rra_index,i);
269       } else {
270          read_tag(&ptr2, "primary_value","%lf",
271                &(rrd->cdp_prep[rrd->stat_head->ds_cnt*(rra_index)
272                +i].scratch[CDP_primary_val].u_val));
273          read_tag(&ptr2, "secondary_value","%lf",
274                &(rrd->cdp_prep[rrd->stat_head->ds_cnt*(rra_index)
275                +i].scratch[CDP_secondary_val].u_val));
276          switch(cf_conv(rrd->rra_def[rra_index].cf_nam)) {
277          case CF_HWPREDICT:
278             read_tag(&ptr2,"intercept","%lf", 
279                &(rrd->cdp_prep[rrd->stat_head->ds_cnt*(rra_index)
280                +i].scratch[CDP_hw_intercept].u_val));
281             read_tag(&ptr2,"last_intercept","%lf", 
282                &(rrd->cdp_prep[rrd->stat_head->ds_cnt*(rra_index)
283                +i].scratch[CDP_hw_last_intercept].u_val));
284             read_tag(&ptr2,"slope","%lf", 
285                &(rrd->cdp_prep[rrd->stat_head->ds_cnt*(rra_index)
286                +i].scratch[CDP_hw_slope].u_val));
287             read_tag(&ptr2,"last_slope","%lf", 
288                &(rrd->cdp_prep[rrd->stat_head->ds_cnt*(rra_index)
289                +i].scratch[CDP_hw_last_slope].u_val));
290             read_tag(&ptr2,"nan_count","%lu", 
291                &(rrd->cdp_prep[rrd->stat_head->ds_cnt*(rra_index)
292                +i].scratch[CDP_null_count].u_cnt));
293             read_tag(&ptr2,"last_nan_count","%lu", 
294                &(rrd->cdp_prep[rrd->stat_head->ds_cnt*(rra_index)
295                +i].scratch[CDP_last_null_count].u_cnt));
296             break;
297          case CF_SEASONAL:
298          case CF_DEVSEASONAL:
299             read_tag(&ptr2,"seasonal","%lf", 
300                &(rrd->cdp_prep[rrd->stat_head->ds_cnt*(rra_index)
301                +i].scratch[CDP_hw_seasonal].u_val));
302             read_tag(&ptr2,"last_seasonal","%lf", 
303                &(rrd->cdp_prep[rrd->stat_head->ds_cnt*(rra_index)
304                +i].scratch[CDP_hw_last_seasonal].u_val));
305             read_tag(&ptr2,"init_flag","%lu", 
306                &(rrd->cdp_prep[rrd->stat_head->ds_cnt*(rra_index)
307                +i].scratch[CDP_init_seasonal].u_cnt));
308             break;
309          case CF_DEVPREDICT:
310             break;
311          case CF_FAILURES:
312             parse_FAILURES_history(&ptr2,rrd,rra_index,i); 
313             break;
314          case CF_AVERAGE:
315          case CF_MAXIMUM:
316          case CF_MINIMUM:
317          case CF_LAST:
318          default:
319             read_tag(&ptr2,"value","%lf",&(rrd->cdp_prep[rrd->stat_head->ds_cnt
320                *(rra_index) +i].scratch[CDP_val].u_val));
321             read_tag(&ptr2,"unknown_datapoints","%lu",&(rrd->cdp_prep[rrd->stat_head->ds_cnt
322                *(rra_index) +i].scratch[CDP_unkn_pdp_cnt].u_cnt));
323             break;
324          }
325       }
326       eat_tag(&ptr2,"/ds");
327       }
328       eat_tag(&ptr2,"/cdp_prep");
329       rrd->rra_def[rrd->stat_head->rra_cnt-1].row_cnt=0;
330       eat_tag(&ptr2,"database");
331       ptr3 = ptr2;      
332       while (eat_tag(&ptr3,"row") == 1){
333         
334           if(mempool==0){
335             mempool = 1000;
336             if((rrd->rrd_value = rrd_realloc(rrd->rrd_value,
337                                          (rows+mempool)*(rrd->stat_head->ds_cnt)
338                                          *sizeof(rrd_value_t)))==NULL) {
339               rrd_set_error("allocating rrd_values"); return -1; }
340           }
341           rows++;
342           mempool--;
343           rrd->rra_def[rrd->stat_head->rra_cnt-1].row_cnt++;
344           for(i=0;i<rrd->stat_head->ds_cnt;i++){
345
346                   rrd_value_t  * value = &(rrd->rrd_value[(rows-1)*rrd->stat_head->ds_cnt+i]);
347
348                   read_tag(&ptr3,"v","%lf", value);
349                   
350                   if (
351                           (rc == 1)                     /* do we have to check for the ranges */
352                           &&
353                       (!isnan(*value))  /* not a NAN value */
354                       &&
355                           (dst_conv(rrd->ds_def[i].dst) != DST_CDEF)
356                           &&
357                       (                                 /* min defined and in the range ? */
358                           (!isnan(rrd->ds_def[i].par[DS_min_val].u_val) 
359                                 && (*value < rrd->ds_def[i].par[DS_min_val].u_val)) 
360                           ||                            /* max defined and in the range ? */
361                           (!isnan(rrd->ds_def[i].par[DS_max_val].u_val) 
362                                 && (*value > rrd->ds_def[i].par[DS_max_val].u_val))
363                       )
364                   ) {
365                       fprintf (stderr, "out of range found [ds: %lu], [value : %0.10e]\n", i, *value);
366                       *value = DNAN;
367                   }
368           }
369           eat_tag(&ptr3,"/row");                  
370           ptr2=ptr3;
371       }
372       eat_tag(&ptr2,"/database");
373       eat_tag(&ptr2,"/rra");                  
374       ptr=ptr2;
375   }  
376   eat_tag(&ptr,"/rrd");
377
378   if((rrd->rra_ptr = calloc(1,sizeof(rra_ptr_t)*rrd->stat_head->rra_cnt)) == NULL) {
379       rrd_set_error("allocating rra_ptr");
380       return(-1);
381   }
382
383   for(i=0; i <rrd->stat_head->rra_cnt; i++) {
384           /* last row in the xml file is the most recent; as
385            * rrd_update increments the current row pointer, set cur_row
386            * here to the last row. */
387       rrd->rra_ptr[i].cur_row = rrd->rra_def[i].row_cnt-1;
388   }
389   if (ptr==NULL)
390       return -1;
391   return 1;
392 }
393   
394     
395
396
397
398 /* create and empty rrd file according to the specs given */
399
400 int
401 rrd_write(char *file_name, rrd_t *rrd)
402 {
403     unsigned long    i,ii,val_cnt;
404     FILE             *rrd_file=NULL;
405
406     if (strcmp("-",file_name)==0){
407       *rrd_file= *stdout;
408     } else {
409       if ((rrd_file = fopen(file_name,"wb")) == NULL ) {
410         rrd_set_error("creating '%s': %s",file_name,strerror(errno));
411         rrd_free(rrd);
412         return(-1);
413       }
414     }
415     fwrite(rrd->stat_head,
416            sizeof(stat_head_t), 1, rrd_file);
417
418     fwrite(rrd->ds_def,
419            sizeof(ds_def_t), rrd->stat_head->ds_cnt, rrd_file);
420
421     fwrite(rrd->rra_def,
422            sizeof(rra_def_t), rrd->stat_head->rra_cnt, rrd_file);
423     
424     fwrite(rrd->live_head, sizeof(live_head_t),1, rrd_file);
425
426     fwrite( rrd->pdp_prep, sizeof(pdp_prep_t),rrd->stat_head->ds_cnt,rrd_file);
427     
428     fwrite( rrd->cdp_prep, sizeof(cdp_prep_t),rrd->stat_head->rra_cnt*
429             rrd->stat_head->ds_cnt,rrd_file);
430     fwrite( rrd->rra_ptr, sizeof(rra_ptr_t), rrd->stat_head->rra_cnt,rrd_file);
431
432
433
434     /* calculate the number of rrd_values to dump */
435     val_cnt=0;
436     for(i=0; i <  rrd->stat_head->rra_cnt; i++)
437         for(ii=0; ii <  rrd->rra_def[i].row_cnt * rrd->stat_head->ds_cnt;ii++)
438             val_cnt++;
439     fwrite( rrd->rrd_value, sizeof(rrd_value_t),val_cnt,rrd_file);
440
441     /* lets see if we had an error */
442     if(ferror(rrd_file)){
443         rrd_set_error("a file error occurred while creating '%s'",file_name);
444         fclose(rrd_file);       
445         return(-1);
446     }
447     
448     fclose(rrd_file);    
449     return 0;
450 }
451
452
453 int
454 rrd_restore(int argc, char **argv) 
455 {
456     rrd_t          rrd;
457     char          *buf;
458         char                    rc = 0;
459
460     /* init rrd clean */
461     rrd_init(&rrd);
462     if (argc<3) {
463                 rrd_set_error("usage rrdtool %s [--range-check/-r] file.xml file.rrd",argv[0]);
464                 return -1;
465     }
466         
467         while (1) {
468                 static struct option long_options[] =
469                 {
470                         {"range-check",      required_argument, 0,  'r'},
471                         {0,0,0,0}
472                 };
473                 int option_index = 0;
474                 int opt;
475                 
476                 
477                 opt = getopt_long(argc, argv, "r", long_options, &option_index);
478                 
479                 if (opt == EOF)
480                         break;
481                 
482                 switch(opt) {
483                 case 'r':
484                         rc=1;
485                         break;
486                 default:
487                         rrd_set_error("usage rrdtool %s [--range-check|-r] file.xml file.rrd",argv[0]);
488         return -1;
489                         break;
490                 }
491     }
492         
493     if (readfile(argv[optind],&buf,0)==-1){
494       return -1;
495     }
496     if (xml2rrd(buf,&rrd,rc)==-1) {
497         rrd_free(&rrd);
498         free(buf);
499         return -1;
500     }
501     free(buf);
502     if(rrd_write(argv[optind+1],&rrd)==-1){
503         rrd_free(&rrd); 
504         return -1;      
505     };
506     rrd_free(&rrd);    
507     return 0;
508 }
509
510 /* a backwards compatibility routine that will parse the RRA params section
511  * generated by the aberrant patch to 1.0.28. */
512 void
513 parse_patch1028_RRA_params(char **buf, rrd_t *rrd, int rra_index)
514 {
515    int i;
516    for (i = 0; i < MAX_RRA_PAR_EN; i++)
517    {
518    if (i == RRA_dependent_rra_idx ||
519        i == RRA_seasonal_smooth_idx ||
520        i == RRA_failure_threshold)
521       read_tag(buf, "value","%lu",
522          &(rrd->rra_def[rra_index].par[i].u_cnt));
523    else
524       read_tag(buf, "value","%lf",
525          &(rrd->rra_def[rra_index].par[i].u_val));
526    }
527 }
528
529 /* a backwards compatibility routine that will parse the CDP params section
530  * generated by the aberrant patch to 1.0.28. */
531 void
532 parse_patch1028_CDP_params(char **buf, rrd_t *rrd, int rra_index, int ds_index)
533 {
534    int ii;
535    for (ii = 0; ii < MAX_CDP_PAR_EN; ii++)
536    {
537    if (cf_conv(rrd->rra_def[rra_index].cf_nam) == CF_FAILURES ||
538        ii == CDP_unkn_pdp_cnt ||
539        ii == CDP_null_count ||
540        ii == CDP_last_null_count)
541    {
542       read_tag(buf,"value","%lu",
543        &(rrd->cdp_prep[rrd->stat_head->ds_cnt*(rra_index) + ds_index].scratch[ii].u_cnt));
544    } else {
545       read_tag(buf,"value","%lf",&(rrd->cdp_prep[rrd->stat_head->ds_cnt*
546        (rra_index) + ds_index].scratch[ii].u_val));
547    }
548    }
549 }
550
551 void
552 parse_FAILURES_history(char **buf, rrd_t *rrd, int rra_index, int ds_index)
553 {
554    char history[MAX_FAILURES_WINDOW_LEN + 1];
555    char *violations_array;
556    short i;
557
558    /* 28 = MAX_FAILURES_WINDOW_LEN */ 
559    read_tag(buf, "history", "%28[0-1]", history);
560    violations_array = (char*) rrd -> cdp_prep[rrd->stat_head->ds_cnt*(rra_index)
561       + ds_index].scratch;
562    
563    for (i = 0; i < rrd -> rra_def[rra_index].par[RRA_window_len].u_cnt; ++i)
564       violations_array[i] = (history[i] == '1') ? 1 : 0;
565
566 }