]> arthur.barton.de Git - netdata.git/blob - src/procfile.c
faster procfile parser
[netdata.git] / src / procfile.c
1 #include "common.h"
2 #include "procfile.h"
3
4 #define PF_PREFIX "PROCFILE"
5
6 #define PFWORDS_INCREASE_STEP 200
7 #define PFLINES_INCREASE_STEP 10
8 #define PROCFILE_INCREMENT_BUFFER 512
9
10 int procfile_adaptive_initial_allocation = 0;
11
12 // if adaptive allocation is set, these store the
13 // max values we have seen so far
14 size_t procfile_max_lines = PFLINES_INCREASE_STEP;
15 size_t procfile_max_words = PFWORDS_INCREASE_STEP;
16 size_t procfile_max_allocation = PROCFILE_INCREMENT_BUFFER;
17
18
19 // ----------------------------------------------------------------------------
20
21 char *procfile_filename(procfile *ff) {
22     if(ff->filename[0]) return ff->filename;
23
24     char buffer[FILENAME_MAX + 1];
25     snprintfz(buffer, FILENAME_MAX, "/proc/self/fd/%d", ff->fd);
26
27     ssize_t l = readlink(buffer, ff->filename, FILENAME_MAX);
28     if(unlikely(l == -1))
29         snprintfz(ff->filename, FILENAME_MAX, "unknown filename for fd %d", ff->fd);
30     else
31         ff->filename[l] = '\0';
32
33     // on non-linux systems, something like this will be needed
34     // fcntl(ff->fd, F_GETPATH, ff->filename)
35
36     return ff->filename;
37 }
38
39 // ----------------------------------------------------------------------------
40 // An array of words
41
42 static inline pfwords *pfwords_add(pfwords *fw, char *str) NEVERNULL;
43 static inline pfwords *pfwords_add(pfwords *fw, char *str) {
44     // debug(D_PROCFILE, PF_PREFIX ":   adding word No %d: '%s'", fw->len, str);
45
46     if(unlikely(fw->len == fw->size)) {
47         // debug(D_PROCFILE, PF_PREFIX ":   expanding words");
48
49         fw = reallocz(fw, sizeof(pfwords) + (fw->size + PFWORDS_INCREASE_STEP) * sizeof(char *));
50         fw->size += PFWORDS_INCREASE_STEP;
51     }
52
53     fw->words[fw->len++] = str;
54
55     return fw;
56 }
57
58 static inline pfwords *pfwords_new(void) NEVERNULL;
59 static inline pfwords *pfwords_new(void) {
60     // debug(D_PROCFILE, PF_PREFIX ":   initializing words");
61
62     size_t size = (procfile_adaptive_initial_allocation) ? procfile_max_words : PFWORDS_INCREASE_STEP;
63
64     pfwords *new = mallocz(sizeof(pfwords) + size * sizeof(char *));
65     new->len = 0;
66     new->size = size;
67     return new;
68 }
69
70 static inline void pfwords_reset(pfwords *fw) {
71     // debug(D_PROCFILE, PF_PREFIX ":   reseting words");
72     fw->len = 0;
73 }
74
75 static inline void pfwords_free(pfwords *fw) {
76     // debug(D_PROCFILE, PF_PREFIX ":   freeing words");
77
78     freez(fw);
79 }
80
81
82 // ----------------------------------------------------------------------------
83 // An array of lines
84
85 static inline pflines *pflines_add(pflines *fl, size_t first_word) NEVERNULL;
86 static inline pflines *pflines_add(pflines *fl, size_t first_word) {
87     // debug(D_PROCFILE, PF_PREFIX ":   adding line %d at word %d", fl->len, first_word);
88
89     if(unlikely(fl->len == fl->size)) {
90         // debug(D_PROCFILE, PF_PREFIX ":   expanding lines");
91
92         fl = reallocz(fl, sizeof(pflines) + (fl->size + PFLINES_INCREASE_STEP) * sizeof(ffline));
93         fl->size += PFLINES_INCREASE_STEP;
94     }
95
96     fl->lines[fl->len].words = 0;
97     fl->lines[fl->len++].first = first_word;
98
99     return fl;
100 }
101
102 static inline pflines *pflines_new(void) NEVERNULL;
103 static inline pflines *pflines_new(void) {
104     // debug(D_PROCFILE, PF_PREFIX ":   initializing lines");
105
106     size_t size = (unlikely(procfile_adaptive_initial_allocation)) ? procfile_max_words : PFLINES_INCREASE_STEP;
107
108     pflines *new = mallocz(sizeof(pflines) + size * sizeof(ffline));
109     new->len = 0;
110     new->size = size;
111     return new;
112 }
113
114 static inline void pflines_reset(pflines *fl) {
115     // debug(D_PROCFILE, PF_PREFIX ":   reseting lines");
116
117     fl->len = 0;
118 }
119
120 static inline void pflines_free(pflines *fl) {
121     // debug(D_PROCFILE, PF_PREFIX ":   freeing lines");
122
123     freez(fl);
124 }
125
126
127 // ----------------------------------------------------------------------------
128 // The procfile
129
130 void procfile_close(procfile *ff) {
131     debug(D_PROCFILE, PF_PREFIX ": Closing file '%s'", procfile_filename(ff));
132
133     if(likely(ff->lines)) pflines_free(ff->lines);
134     if(likely(ff->words)) pfwords_free(ff->words);
135
136     if(likely(ff->fd != -1)) close(ff->fd);
137     freez(ff);
138 }
139
140 static inline void procfile_parser(procfile *ff) {
141     // debug(D_PROCFILE, PF_PREFIX ": Parsing file '%s'", ff->filename);
142
143     char  *s = ff->data                 // our current position
144         , *e = &ff->data[ff->len]       // the terminating null
145         , *t = ff->data;                // the first character of a quoted or a parenthesized string
146
147                                         // the look up array to find our type of character
148     PF_CHAR_TYPE *separators = ff->separators;
149
150     char quote = 0;                     // the quote character - only when in quoted string
151
152     size_t
153           l = 0                         // counts the number of lines we added
154         , w = 0                         // counts the number of words we added
155         , opened = 0;                   // counts the number of open parenthesis
156
157     ff->lines = pflines_add(ff->lines, w);
158
159     while(likely(s < e)) {
160         // we are not at the end
161         PF_CHAR_TYPE ct = separators[(unsigned char)(*s)];
162
163         // this is faster than a switch()
164         if(likely(ct == PF_CHAR_IS_WORD)) {
165             s++;
166         }
167         else if(likely(ct == PF_CHAR_IS_SEPARATOR)) {
168             if(unlikely(quote || opened)) {
169                 // we are inside a quote
170                 s++;
171                 continue;
172             }
173
174             if(unlikely(s == t)) {
175                 // skip all leading white spaces
176                 t = ++s;
177                 continue;
178             }
179
180             // end of word
181             *s = '\0';
182
183             ff->words = pfwords_add(ff->words, t);
184             ff->lines->lines[l].words++;
185             w++;
186
187             t = ++s;
188         }
189         else if(likely(ct == PF_CHAR_IS_NEWLINE)) {
190             // end of line
191             *s = '\0';
192
193             ff->words = pfwords_add(ff->words, t);
194             ff->lines->lines[l].words++;
195             w++;
196
197             // debug(D_PROCFILE, PF_PREFIX ":   ended line %d with %d words", l, ff->lines->lines[l].words);
198
199             ff->lines = pflines_add(ff->lines, w);
200             l++;
201
202             t = ++s;
203         }
204         else if(likely(ct == PF_CHAR_IS_QUOTE)) {
205             if(unlikely(!quote && s == t)) {
206                 // quote opened at the beginning
207                 quote = *s;
208                 t = ++s;
209             }
210             else if(unlikely(quote && quote == *s)) {
211                 // quote closed
212                 quote = 0;
213
214                 *s = '\0';
215                 ff->words = pfwords_add(ff->words, t);
216                 ff->lines->lines[l].words++;
217                 w++;
218
219                 t = ++s;
220             }
221             else
222                 s++;
223         }
224         else if(likely(ct == PF_CHAR_IS_OPEN)) {
225             if(s == t) {
226                 opened++;
227                 t = ++s;
228             }
229             else if(opened) {
230                 opened++;
231                 s++;
232             }
233             else
234                 s++;
235         }
236         else if(likely(ct == PF_CHAR_IS_CLOSE)) {
237             if(opened) {
238                 opened--;
239
240                 if(!opened) {
241                     *s = '\0';
242                     ff->words = pfwords_add(ff->words, t);
243                     ff->lines->lines[l].words++;
244                     w++;
245
246                     t = ++s;
247                 }
248                 else
249                     s++;
250             }
251             else
252                 s++;
253         }
254         else
255             fatal("Internal Error: procfile_readall() does not handle all the cases.");
256     }
257
258     if(likely(s > t && t < e)) {
259         // the last word
260         if(likely(ff->len < ff->size))
261             *s = '\0';
262         else {
263             // we are going to loose the last byte
264             ff->data[ff->size - 1] = '\0';
265         }
266
267         ff->words = pfwords_add(ff->words, t);
268         ff->lines->lines[l].words++;
269     }
270 }
271
272 procfile *procfile_readall(procfile *ff) {
273     // debug(D_PROCFILE, PF_PREFIX ": Reading file '%s'.", ff->filename);
274
275     ff->len = 0;    // zero the used size
276     ssize_t r = 1;  // read at least once
277     while(r > 0) {
278         ssize_t s = ff->len;
279         ssize_t x = ff->size - s;
280
281         if(unlikely(!x)) {
282             debug(D_PROCFILE, PF_PREFIX ": Expanding data buffer for file '%s'.", procfile_filename(ff));
283             ff = reallocz(ff, sizeof(procfile) + ff->size + PROCFILE_INCREMENT_BUFFER);
284             ff->size += PROCFILE_INCREMENT_BUFFER;
285         }
286
287         debug(D_PROCFILE, "Reading file '%s', from position %zd with length %zd", procfile_filename(ff), s, (ssize_t)(ff->size - s));
288         r = read(ff->fd, &ff->data[s], ff->size - s);
289         if(unlikely(r == -1)) {
290             if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot read from file '%s'", procfile_filename(ff));
291             procfile_close(ff);
292             return NULL;
293         }
294
295         ff->len += r;
296     }
297
298     // debug(D_PROCFILE, "Rewinding file '%s'", ff->filename);
299     if(unlikely(lseek(ff->fd, 0, SEEK_SET) == -1)) {
300         if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot rewind on file '%s'.", procfile_filename(ff));
301         procfile_close(ff);
302         return NULL;
303     }
304
305     pflines_reset(ff->lines);
306     pfwords_reset(ff->words);
307     procfile_parser(ff);
308
309     if(unlikely(procfile_adaptive_initial_allocation)) {
310         if(unlikely(ff->len > procfile_max_allocation)) procfile_max_allocation = ff->len;
311         if(unlikely(ff->lines->len > procfile_max_lines)) procfile_max_lines = ff->lines->len;
312         if(unlikely(ff->words->len > procfile_max_words)) procfile_max_words = ff->words->len;
313     }
314
315     // debug(D_PROCFILE, "File '%s' updated.", ff->filename);
316     return ff;
317 }
318
319 static inline void procfile_set_separators(procfile *ff, const char *separators) {
320     static PF_CHAR_TYPE def[256];
321     static char initilized = 0;
322
323     if(unlikely(!initilized)) {
324         // this is thread safe
325         // if initialized is zero, multiple threads may be executing
326         // this code at the same time, setting in def[] the exact same values
327         int i = 256;
328         while(i--) {
329             if(unlikely(i == '\n' || i == '\r'))
330                 def[i] = PF_CHAR_IS_NEWLINE;
331
332             else if(unlikely(isspace(i) || !isprint(i)))
333                 def[i] = PF_CHAR_IS_SEPARATOR;
334
335             else
336                 def[i] = PF_CHAR_IS_WORD;
337         }
338
339         initilized = 1;
340     }
341
342     // copy the default
343     PF_CHAR_TYPE *ffs = ff->separators, *ffd = def, *ffe = &def[256];
344     while(ffd != ffe)
345         *ffs++ = *ffd++;
346
347     // set the separators
348     if(unlikely(!separators))
349         separators = " \t=|";
350
351     ffs = ff->separators;
352     const char *s = separators;
353     while(*s)
354         ffs[(int)*s++] = PF_CHAR_IS_SEPARATOR;
355 }
356
357 void procfile_set_quotes(procfile *ff, const char *quotes) {
358     PF_CHAR_TYPE *ffs = ff->separators;
359
360     // remove all quotes
361     int i = 256;
362     while(i--)
363         if(unlikely(ffs[i] == PF_CHAR_IS_QUOTE))
364             ffs[i] = PF_CHAR_IS_WORD;
365
366     // if nothing given, return
367     if(unlikely(!quotes || !*quotes))
368         return;
369
370     // set the quotes
371     const char *s = quotes;
372     while(*s)
373         ffs[(int)*s++] = PF_CHAR_IS_QUOTE;
374 }
375
376 void procfile_set_open_close(procfile *ff, const char *open, const char *close) {
377     PF_CHAR_TYPE *ffs = ff->separators;
378
379     // remove all open/close
380     int i = 256;
381     while(i--)
382         if(unlikely(ffs[i] == PF_CHAR_IS_OPEN || ffs[i] == PF_CHAR_IS_CLOSE))
383             ffs[i] = PF_CHAR_IS_WORD;
384
385     // if nothing given, return
386     if(unlikely(!open || !*open || !close || !*close))
387         return;
388
389     // set the openings
390     const char *s = open;
391     while(*s)
392         ffs[(int)*s++] = PF_CHAR_IS_OPEN;
393
394     // set the closings
395     s = close;
396     while(*s)
397         ffs[(int)*s++] = PF_CHAR_IS_CLOSE;
398 }
399
400 procfile *procfile_open(const char *filename, const char *separators, uint32_t flags) {
401     debug(D_PROCFILE, PF_PREFIX ": Opening file '%s'", filename);
402
403     int fd = open(filename, O_RDONLY, 0666);
404     if(unlikely(fd == -1)) {
405         if(unlikely(!(flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot open file '%s'", filename);
406         return NULL;
407     }
408
409     size_t size = (unlikely(procfile_adaptive_initial_allocation)) ? procfile_max_allocation : PROCFILE_INCREMENT_BUFFER;
410     procfile *ff = mallocz(sizeof(procfile) + size);
411
412     //strncpyz(ff->filename, filename, FILENAME_MAX);
413     ff->filename[0] = '\0';
414
415     ff->fd = fd;
416     ff->size = size;
417     ff->len = 0;
418     ff->flags = flags;
419
420     ff->lines = pflines_new();
421     ff->words = pfwords_new();
422
423     procfile_set_separators(ff, separators);
424
425     debug(D_PROCFILE, "File '%s' opened.", filename);
426     return ff;
427 }
428
429 procfile *procfile_reopen(procfile *ff, const char *filename, const char *separators, uint32_t flags) {
430     if(unlikely(!ff)) return procfile_open(filename, separators, flags);
431
432     if(likely(ff->fd != -1)) close(ff->fd);
433
434     ff->fd = open(filename, O_RDONLY, 0666);
435     if(unlikely(ff->fd == -1)) {
436         procfile_close(ff);
437         return NULL;
438     }
439
440     //strncpyz(ff->filename, filename, FILENAME_MAX);
441     ff->filename[0] = '\0';
442
443     ff->flags = flags;
444
445     // do not do the separators again if NULL is given
446     if(likely(separators)) procfile_set_separators(ff, separators);
447
448     return ff;
449 }
450
451 // ----------------------------------------------------------------------------
452 // example parsing of procfile data
453
454 void procfile_print(procfile *ff) {
455     size_t lines = procfile_lines(ff), l;
456     char *s;
457
458     debug(D_PROCFILE, "File '%s' with %zu lines and %zu words", procfile_filename(ff), ff->lines->len, ff->words->len);
459
460     for(l = 0; likely(l < lines) ;l++) {
461         size_t words = procfile_linewords(ff, l);
462
463         debug(D_PROCFILE, " line %zu starts at word %zu and has %zu words", l, ff->lines->lines[l].first, ff->lines->lines[l].words);
464
465         size_t w;
466         for(w = 0; likely(w < words) ;w++) {
467             s = procfile_lineword(ff, l, w);
468             debug(D_PROCFILE, "     [%zu.%zu] '%s'", l, w, s);
469         }
470     }
471 }