| 1 | /* SPDX-License-Identifier: GPL-3.0-or-later */ |
| 2 | |
| 3 | #include "config.h" |
| 4 | #include "libnetdata/libnetdata.h" |
| 5 | |
| 6 | void netdata_cleanup_and_exit(int ret) { |
| 7 | exit(ret); |
| 8 | } |
| 9 | |
| 10 | #define PF_PREFIX "PROCFILE" |
| 11 | #define PFWORDS_INCREASE_STEP 200 |
| 12 | #define PFLINES_INCREASE_STEP 10 |
| 13 | #define PROCFILE_INCREMENT_BUFFER 512 |
| 14 | extern size_t procfile_max_lines; |
| 15 | extern size_t procfile_max_words; |
| 16 | extern size_t procfile_max_allocation; |
| 17 | |
| 18 | |
| 19 | static inline void pflines_reset(pflines *fl) { |
| 20 | // netdata_log_debug(D_PROCFILE, PF_PREFIX ": resetting lines"); |
| 21 | |
| 22 | fl->len = 0; |
| 23 | } |
| 24 | |
| 25 | static inline void pflines_free(pflines *fl) { |
| 26 | // netdata_log_debug(D_PROCFILE, PF_PREFIX ": freeing lines"); |
| 27 | |
| 28 | freez(fl); |
| 29 | } |
| 30 | |
| 31 | static inline void pfwords_reset(pfwords *fw) { |
| 32 | // netdata_log_debug(D_PROCFILE, PF_PREFIX ": resetting words"); |
| 33 | fw->len = 0; |
| 34 | } |
| 35 | |
| 36 | |
| 37 | static inline void pfwords_add(procfile *ff, char *str) { |
| 38 | // netdata_log_debug(D_PROCFILE, PF_PREFIX ": adding word No %d: '%s'", fw->len, str); |
| 39 | |
| 40 | pfwords *fw = ff->words; |
| 41 | if(unlikely(fw->len == fw->size)) { |
| 42 | // netdata_log_debug(D_PROCFILE, PF_PREFIX ": expanding words"); |
| 43 | |
| 44 | ff->words = fw = reallocz(fw, sizeof(pfwords) + (fw->size + PFWORDS_INCREASE_STEP) * sizeof(char *)); |
| 45 | fw->size += PFWORDS_INCREASE_STEP; |
| 46 | } |
| 47 | |
| 48 | fw->words[fw->len++] = str; |
| 49 | } |
| 50 | |
| 51 | NEVERNULL |
| 52 | static inline size_t *pflines_add(procfile *ff) { |
| 53 | // netdata_log_debug(D_PROCFILE, PF_PREFIX ": adding line %d at word %d", fl->len, first_word); |
| 54 | |
| 55 | pflines *fl = ff->lines; |
| 56 | if(unlikely(fl->len == fl->size)) { |
| 57 | // netdata_log_debug(D_PROCFILE, PF_PREFIX ": expanding lines"); |
| 58 | |
| 59 | ff->lines = fl = reallocz(fl, sizeof(pflines) + (fl->size + PFLINES_INCREASE_STEP) * sizeof(ffline)); |
| 60 | fl->size += PFLINES_INCREASE_STEP; |
| 61 | } |
| 62 | |
| 63 | ffline *ffl = &fl->lines[fl->len++]; |
| 64 | ffl->words = 0; |
| 65 | ffl->first = ff->words->len; |
| 66 | |
| 67 | return &ffl->words; |
| 68 | } |
| 69 | |
| 70 | |
| 71 | NOINLINE |
| 72 | static void procfile_parser(procfile *ff) { |
| 73 | // netdata_log_debug(D_PROCFILE, PF_PREFIX ": Parsing file '%s'", ff->filename); |
| 74 | |
| 75 | char *s = ff->data // our current position |
| 76 | , *e = &ff->data[ff->len] // the terminating null |
| 77 | , *t = ff->data; // the first character of a word (or quoted / parenthesized string) |
| 78 | |
| 79 | // the look up array to find our type of character |
| 80 | PF_CHAR_TYPE *separators = ff->separators; |
| 81 | |
| 82 | char quote = 0; // the quote character - only when in quoted string |
| 83 | size_t opened = 0; // counts the number of open parenthesis |
| 84 | |
| 85 | size_t *line_words = pflines_add(ff); |
| 86 | |
| 87 | while(s < e) { |
| 88 | PF_CHAR_TYPE ct = separators[(unsigned char)(*s)]; |
| 89 | |
| 90 | // this is faster than a switch() |
| 91 | // read more here: http://lazarenko.me/switch/ |
| 92 | switch(ct) { |
| 93 | case PF_CHAR_IS_SEPARATOR: |
| 94 | if(!quote && !opened) { |
| 95 | if (s != t) { |
| 96 | // separator, but we have word before it |
| 97 | *s = '\0'; |
| 98 | pfwords_add(ff, t); |
| 99 | (*line_words)++; |
| 100 | } |
| 101 | t = s + 1; |
| 102 | } |
| 103 | // fallthrough |
| 104 | |
| 105 | case PF_CHAR_IS_WORD: |
| 106 | s++; |
| 107 | break; |
| 108 | |
| 109 | |
| 110 | case PF_CHAR_IS_NEWLINE: |
| 111 | // end of line |
| 112 | |
| 113 | *s = '\0'; |
| 114 | pfwords_add(ff, t); |
| 115 | (*line_words)++; |
| 116 | t = ++s; |
| 117 | |
| 118 | // netdata_log_debug(D_PROCFILE, PF_PREFIX ": ended line %d with %d words", l, ff->lines->lines[l].words); |
| 119 | |
| 120 | line_words = pflines_add(ff); |
| 121 | break; |
| 122 | |
| 123 | case PF_CHAR_IS_QUOTE: |
| 124 | if(unlikely(!quote && s == t)) { |
| 125 | // quote opened at the beginning |
| 126 | quote = *s; |
| 127 | t = ++s; |
| 128 | } |
| 129 | else if(unlikely(quote && quote == *s)) { |
| 130 | // quote closed |
| 131 | quote = 0; |
| 132 | |
| 133 | *s = '\0'; |
| 134 | pfwords_add(ff, t); |
| 135 | (*line_words)++; |
| 136 | t = ++s; |
| 137 | } |
| 138 | else |
| 139 | s++; |
| 140 | break; |
| 141 | |
| 142 | case PF_CHAR_IS_OPEN: |
| 143 | if(s == t) { |
| 144 | opened++; |
| 145 | t = ++s; |
| 146 | } |
| 147 | else if(opened) { |
| 148 | opened++; |
| 149 | s++; |
| 150 | } |
| 151 | else |
| 152 | s++; |
| 153 | break; |
| 154 | |
| 155 | case PF_CHAR_IS_CLOSE: |
| 156 | if(opened) { |
| 157 | opened--; |
| 158 | |
| 159 | if(!opened) { |
| 160 | *s = '\0'; |
| 161 | pfwords_add(ff, t); |
| 162 | (*line_words)++; |
| 163 | t = ++s; |
| 164 | } |
| 165 | else |
| 166 | s++; |
| 167 | } |
| 168 | else |
| 169 | s++; |
| 170 | break; |
| 171 | |
| 172 | default: |
| 173 | fatal("Internal Error: procfile_readall() does not handle all the cases."); |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | if(likely(s > t && t < e)) { |
| 178 | // the last word |
| 179 | if(unlikely(ff->len >= ff->size)) { |
| 180 | // we are going to loose the last byte |
| 181 | s = &ff->data[ff->size - 1]; |
| 182 | } |
| 183 | |
| 184 | *s = '\0'; |
| 185 | pfwords_add(ff, t); |
| 186 | (*line_words)++; |
| 187 | // t = ++s; |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | |
| 192 | procfile *procfile_readall1(procfile *ff) { |
| 193 | // netdata_log_debug(D_PROCFILE, PF_PREFIX ": Reading file '%s'.", ff->filename); |
| 194 | |
| 195 | ff->len = 0; // zero the used size |
| 196 | ssize_t r = 1; // read at least once |
| 197 | while(r > 0) { |
| 198 | ssize_t s = ff->len; |
| 199 | ssize_t x = ff->size - s; |
| 200 | |
| 201 | if(unlikely(!x)) { |
| 202 | netdata_log_debug(D_PROCFILE, PF_PREFIX ": Expanding data buffer for file '%s'.", procfile_filename(ff)); |
| 203 | ff = reallocz(ff, sizeof(procfile) + ff->size + PROCFILE_INCREMENT_BUFFER); |
| 204 | ff->size += PROCFILE_INCREMENT_BUFFER; |
| 205 | } |
| 206 | |
| 207 | netdata_log_debug(D_PROCFILE, "Reading file '%s', from position %zd with length %zd", procfile_filename(ff), s, (ssize_t)(ff->size - s)); |
| 208 | r = read(ff->fd, &ff->data[s], ff->size - s); |
| 209 | if(unlikely(r == -1)) { |
| 210 | if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) netdata_log_error(PF_PREFIX ": Cannot read from file '%s' on fd %d", procfile_filename(ff), ff->fd); |
| 211 | procfile_close(ff); |
| 212 | return NULL; |
| 213 | } |
| 214 | |
| 215 | ff->len += r; |
| 216 | } |
| 217 | |
| 218 | // netdata_log_debug(D_PROCFILE, "Rewinding file '%s'", ff->filename); |
| 219 | if(unlikely(lseek(ff->fd, 0, SEEK_SET) == -1)) { |
| 220 | if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) netdata_log_error(PF_PREFIX ": Cannot rewind on file '%s'.", procfile_filename(ff)); |
| 221 | procfile_close(ff); |
| 222 | return NULL; |
| 223 | } |
| 224 | |
| 225 | pflines_reset(ff->lines); |
| 226 | pfwords_reset(ff->words); |
| 227 | procfile_parser(ff); |
| 228 | |
| 229 | if(unlikely(procfile_adaptive_initial_allocation)) { |
| 230 | if(unlikely(ff->len > procfile_max_allocation)) procfile_max_allocation = ff->len; |
| 231 | if(unlikely(ff->lines->len > procfile_max_lines)) procfile_max_lines = ff->lines->len; |
| 232 | if(unlikely(ff->words->len > procfile_max_words)) procfile_max_words = ff->words->len; |
| 233 | } |
| 234 | |
| 235 | // netdata_log_debug(D_PROCFILE, "File '%s' updated.", ff->filename); |
| 236 | return ff; |
| 237 | } |
| 238 | |
| 239 | |
| 240 | |
| 241 | |
| 242 | |
| 243 | |
| 244 | |
| 245 | |
| 246 | // ============== |
| 247 | // --- Poor man cycle counting. |
| 248 | static unsigned long tsc; |
| 249 | |
| 250 | void begin_tsc(void) |
| 251 | { |
| 252 | unsigned long a, d; |
| 253 | asm volatile ("cpuid\nrdtsc" : "=a" (a), "=d" (d) : "0" (0) : "ebx", "ecx"); |
| 254 | tsc = ((unsigned long)d << 32) | (unsigned long)a; |
| 255 | } |
| 256 | |
| 257 | unsigned long end_tsc(void) |
| 258 | { |
| 259 | unsigned long a, d; |
| 260 | asm volatile ("rdtscp" : "=a" (a), "=d" (d) : : "ecx"); |
| 261 | return (((unsigned long)d << 32) | (unsigned long)a) - tsc; |
| 262 | } |
| 263 | // ============== |
| 264 | |
| 265 | |
| 266 | unsigned long test_netdata_internal(void) { |
| 267 | static procfile *ff = NULL; |
| 268 | |
| 269 | ff = procfile_reopen(ff, "/proc/self/status", " \t:,-()/", PROCFILE_FLAG_NO_ERROR_ON_FILE_IO); |
| 270 | if(!ff) { |
| 271 | fprintf(stderr, "Failed to open filename\n"); |
| 272 | exit(1); |
| 273 | } |
| 274 | |
| 275 | begin_tsc(); |
| 276 | ff = procfile_readall(ff); |
| 277 | unsigned long c = end_tsc(); |
| 278 | |
| 279 | if(!ff) { |
| 280 | fprintf(stderr, "Failed to read filename\n"); |
| 281 | exit(1); |
| 282 | } |
| 283 | |
| 284 | return c; |
| 285 | } |
| 286 | |
| 287 | unsigned long test_method1(void) { |
| 288 | static procfile *ff = NULL; |
| 289 | |
| 290 | ff = procfile_reopen(ff, "/proc/self/status", " \t:,-()/", PROCFILE_FLAG_NO_ERROR_ON_FILE_IO); |
| 291 | if(!ff) { |
| 292 | fprintf(stderr, "Failed to open filename\n"); |
| 293 | exit(1); |
| 294 | } |
| 295 | |
| 296 | begin_tsc(); |
| 297 | ff = procfile_readall1(ff); |
| 298 | unsigned long c = end_tsc(); |
| 299 | |
| 300 | if(!ff) { |
| 301 | fprintf(stderr, "Failed to read filename\n"); |
| 302 | exit(1); |
| 303 | } |
| 304 | |
| 305 | return c; |
| 306 | } |
| 307 | |
| 308 | //--- Test |
| 309 | int main(int argc, char **argv) |
| 310 | { |
| 311 | (void)argc; (void)argv; |
| 312 | |
| 313 | int i, max = 1000000; |
| 314 | |
| 315 | unsigned long c1 = 0; |
| 316 | test_netdata_internal(); |
| 317 | for(i = 0; i < max ; i++) |
| 318 | c1 += test_netdata_internal(); |
| 319 | |
| 320 | unsigned long c2 = 0; |
| 321 | test_method1(); |
| 322 | for(i = 0; i < max ; i++) |
| 323 | c2 += test_method1(); |
| 324 | |
| 325 | printf("netdata internal: completed in %lu cycles, %lu cycles per read, %0.2f %%.\n", c1, c1 / max, (float)c1 * 100.0 / (float)c1); |
| 326 | printf("method1 : completed in %lu cycles, %lu cycles per read, %0.2f %%.\n", c2, c2 / max, (float)c2 * 100.0 / (float)c1); |
| 327 | |
| 328 | return 0; |
| 329 | } |