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