Imported Upstream version 3.5.1
[platform/upstream/ccache.git] / src / stats.c
1 // Copyright (C) 2002-2004 Andrew Tridgell
2 // Copyright (C) 2009-2018 Joel Rosdahl
3 //
4 // This program is free software; you can redistribute it and/or modify it
5 // under the terms of the GNU General Public License as published by the Free
6 // Software Foundation; either version 3 of the License, or (at your option)
7 // any later version.
8 //
9 // This program is distributed in the hope that it will be useful, but WITHOUT
10 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 // more details.
13 //
14 // You should have received a copy of the GNU General Public License along with
15 // this program; if not, write to the Free Software Foundation, Inc., 51
16 // Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
18 // Routines to handle the stats files. The stats file is stored one per cache
19 // subdirectory to make this more scalable.
20
21 #include "ccache.h"
22 #include "hashutil.h"
23
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31
32 extern char *stats_file;
33 extern struct conf *conf;
34 extern unsigned lock_staleness_limit;
35 extern char *primary_config_path;
36 extern char *secondary_config_path;
37
38 static struct counters *counter_updates;
39
40 #define FLAG_NOZERO 1 // don't zero with the -z option
41 #define FLAG_ALWAYS 2 // always show, even if zero
42 #define FLAG_NEVER 4 // never show
43
44 // Returns a formatted version of a statistics value, or NULL if the statistics
45 // line shouldn't be printed. Caller frees.
46 typedef char *format_fn(uint64_t value);
47
48 static format_fn format_size_times_1024;
49 static format_fn format_timestamp;
50
51 // Statistics fields in display order.
52 static struct {
53         enum stats stat;
54         const char *message;
55         format_fn *format_fn; // NULL -> use plain integer format
56         unsigned flags;
57 } stats_info[] = {
58         {
59                 STATS_ZEROTIMESTAMP,
60                 "stats zeroed",
61                 format_timestamp,
62                 FLAG_ALWAYS
63         },
64         {
65                 STATS_CACHEHIT_DIR,
66                 "cache hit (direct)",
67                 NULL,
68                 FLAG_ALWAYS
69         },
70         {
71                 STATS_CACHEHIT_CPP,
72                 "cache hit (preprocessed)",
73                 NULL,
74                 FLAG_ALWAYS
75         },
76         {
77                 STATS_TOCACHE,
78                 "cache miss",
79                 NULL,
80                 FLAG_ALWAYS
81         },
82         {
83                 STATS_LINK,
84                 "called for link",
85                 NULL,
86                 0
87         },
88         {
89                 STATS_PREPROCESSING,
90                 "called for preprocessing",
91                 NULL,
92                 0
93         },
94         {
95                 STATS_MULTIPLE,
96                 "multiple source files",
97                 NULL,
98                 0
99         },
100         {
101                 STATS_STDOUT,
102                 "compiler produced stdout",
103                 NULL,
104                 0
105         },
106         {
107                 STATS_NOOUTPUT,
108                 "compiler produced no output",
109                 NULL,
110                 0
111         },
112         {
113                 STATS_EMPTYOUTPUT,
114                 "compiler produced empty output",
115                 NULL,
116                 0
117         },
118         {
119                 STATS_STATUS,
120                 "compile failed",
121                 NULL,
122                 0
123         },
124         {
125                 STATS_ERROR,
126                 "ccache internal error",
127                 NULL,
128                 0
129         },
130         {
131                 STATS_PREPROCESSOR,
132                 "preprocessor error",
133                 NULL,
134                 0
135         },
136         {
137                 STATS_CANTUSEPCH,
138                 "can't use precompiled header",
139                 NULL,
140                 0
141         },
142         {
143                 STATS_COMPILER,
144                 "couldn't find the compiler",
145                 NULL,
146                 0
147         },
148         {
149                 STATS_MISSING,
150                 "cache file missing",
151                 NULL,
152                 0
153         },
154         {
155                 STATS_ARGS,
156                 "bad compiler arguments",
157                 NULL,
158                 0
159         },
160         {
161                 STATS_SOURCELANG,
162                 "unsupported source language",
163                 NULL,
164                 0
165         },
166         {
167                 STATS_COMPCHECK,
168                 "compiler check failed",
169                 NULL,
170                 0
171         },
172         {
173                 STATS_CONFTEST,
174                 "autoconf compile/link",
175                 NULL,
176                 0
177         },
178         {
179                 STATS_UNSUPPORTED_OPTION,
180                 "unsupported compiler option",
181                 NULL,
182                 0
183         },
184         {
185                 STATS_UNSUPPORTED_DIRECTIVE,
186                 "unsupported code directive",
187                 NULL,
188                 0
189         },
190         {
191                 STATS_OUTSTDOUT,
192                 "output to stdout",
193                 NULL,
194                 0
195         },
196         {
197                 STATS_DEVICE,
198                 "output to a non-regular file",
199                 NULL,
200                 0
201         },
202         {
203                 STATS_NOINPUT,
204                 "no input file",
205                 NULL,
206                 0
207         },
208         {
209                 STATS_BADEXTRAFILE,
210                 "error hashing extra file",
211                 NULL,
212                 0
213         },
214         {
215                 STATS_NUMCLEANUPS,
216                 "cleanups performed",
217                 NULL,
218                 FLAG_ALWAYS
219         },
220         {
221                 STATS_NUMFILES,
222                 "files in cache",
223                 NULL,
224                 FLAG_NOZERO|FLAG_ALWAYS
225         },
226         {
227                 STATS_TOTALSIZE,
228                 "cache size",
229                 format_size_times_1024,
230                 FLAG_NOZERO|FLAG_ALWAYS
231         },
232         {
233                 STATS_OBSOLETE_MAXFILES,
234                 "OBSOLETE",
235                 NULL,
236                 FLAG_NOZERO|FLAG_NEVER
237         },
238         {
239                 STATS_OBSOLETE_MAXSIZE,
240                 "OBSOLETE",
241                 NULL,
242                 FLAG_NOZERO|FLAG_NEVER
243         },
244         {
245                 STATS_NONE,
246                 NULL,
247                 NULL,
248                 0
249         }
250 };
251
252 static char *
253 format_size(uint64_t size)
254 {
255         char *s = format_human_readable_size(size);
256         reformat(&s, "%11s", s);
257         return s;
258 }
259
260 static char *
261 format_size_times_1024(uint64_t size)
262 {
263         return format_size(size * 1024);
264 }
265
266 static char *
267 format_timestamp(uint64_t timestamp)
268 {
269         if (timestamp > 0) {
270                 struct tm *tm = localtime((time_t *)&timestamp);
271                 char buffer[100];
272                 strftime(buffer, sizeof(buffer), "%c", tm);
273                 return format("    %s", buffer);
274         } else {
275                 return NULL;
276         }
277 }
278
279 // Parse a stats file from a buffer, adding to the counters.
280 static void
281 parse_stats(struct counters *counters, const char *buf)
282 {
283         size_t i = 0;
284         const char *p = buf;
285         while (true) {
286                 char *p2;
287                 long val = strtol(p, &p2, 10);
288                 if (p2 == p) {
289                         break;
290                 }
291                 if (counters->size < i + 1) {
292                         counters_resize(counters, i + 1);
293                 }
294                 counters->data[i] += val;
295                 i++;
296                 p = p2;
297         }
298 }
299
300 // Write out a stats file.
301 void
302 stats_write(const char *path, struct counters *counters)
303 {
304         char *tmp_file = format("%s.tmp", path);
305         FILE *f = create_tmp_file(&tmp_file, "wb");
306         for (size_t i = 0; i < counters->size; i++) {
307                 if (fprintf(f, "%u\n", counters->data[i]) < 0) {
308                         fatal("Failed to write to %s", tmp_file);
309                 }
310         }
311         fclose(f);
312         x_rename(tmp_file, path);
313         free(tmp_file);
314 }
315
316 static void
317 init_counter_updates(void)
318 {
319         if (!counter_updates) {
320                 counter_updates = counters_init(STATS_END);
321         }
322 }
323
324 // Record that a number of bytes and files have been added to the cache. Size
325 // is in bytes.
326 void
327 stats_update_size(int64_t size, int files)
328 {
329         init_counter_updates();
330         counter_updates->data[STATS_NUMFILES] += files;
331         counter_updates->data[STATS_TOTALSIZE] += size / 1024;
332 }
333
334 // Read in the stats from one directory and add to the counters.
335 void
336 stats_read(const char *sfile, struct counters *counters)
337 {
338         char *data = read_text_file(sfile, 1024);
339         if (data) {
340                 parse_stats(counters, data);
341         }
342         free(data);
343 }
344
345 // Write counter updates in counter_updates to disk.
346 void
347 stats_flush(void)
348 {
349         assert(conf);
350
351         if (!conf->stats) {
352                 return;
353         }
354
355         if (!counter_updates) {
356                 return;
357         }
358
359         bool should_flush = false;
360         for (int i = 0; i < STATS_END; ++i) {
361                 if (counter_updates->data[i] > 0) {
362                         should_flush = true;
363                         break;
364                 }
365         }
366         if (!should_flush) {
367                 return;
368         }
369
370         if (!stats_file) {
371                 char *stats_dir;
372
373                 // A NULL stats_file means that we didn't get past calculate_object_hash(),
374                 // so we just choose one of stats files in the 16 subdirectories.
375                 stats_dir = format("%s/%x", conf->cache_dir, hash_from_int(getpid()) % 16);
376                 stats_file = format("%s/stats", stats_dir);
377                 free(stats_dir);
378         }
379
380         if (!lockfile_acquire(stats_file, lock_staleness_limit)) {
381                 return;
382         }
383
384         struct counters *counters = counters_init(STATS_END);
385         stats_read(stats_file, counters);
386         for (int i = 0; i < STATS_END; ++i) {
387                 counters->data[i] += counter_updates->data[i];
388         }
389         stats_write(stats_file, counters);
390         lockfile_release(stats_file);
391
392         if (!str_eq(conf->log_file, "") || conf->debug) {
393                 for (int i = 0; i < STATS_END; ++i) {
394                         if (counter_updates->data[stats_info[i].stat] != 0
395                             && !(stats_info[i].flags & FLAG_NOZERO)) {
396                                 cc_log("Result: %s", stats_info[i].message);
397                         }
398                 }
399         }
400
401         char *subdir = dirname(stats_file);
402         bool need_cleanup = false;
403
404         if (conf->max_files != 0
405             && counters->data[STATS_NUMFILES] > conf->max_files / 16) {
406                 cc_log("Need to clean up %s since it holds %u files (limit: %u files)",
407                        subdir,
408                        counters->data[STATS_NUMFILES],
409                        conf->max_files / 16);
410                 need_cleanup = true;
411         }
412         if (conf->max_size != 0
413             && counters->data[STATS_TOTALSIZE] > conf->max_size / 1024 / 16) {
414                 cc_log("Need to clean up %s since it holds %u KiB (limit: %lu KiB)",
415                        subdir,
416                        counters->data[STATS_TOTALSIZE],
417                        (unsigned long)conf->max_size / 1024 / 16);
418                 need_cleanup = true;
419         }
420
421         if (need_cleanup) {
422                 clean_up_dir(conf, subdir, conf->limit_multiple);
423         }
424
425         free(subdir);
426         counters_free(counters);
427 }
428
429 // Update a normal stat.
430 void
431 stats_update(enum stats stat)
432 {
433         assert(stat > STATS_NONE && stat < STATS_END);
434         init_counter_updates();
435         counter_updates->data[stat]++;
436 }
437
438 // Get the pending update of a counter value.
439 unsigned
440 stats_get_pending(enum stats stat)
441 {
442         init_counter_updates();
443         return counter_updates->data[stat];
444 }
445
446 // Sum and display the total stats for all cache dirs.
447 void
448 stats_summary(void)
449 {
450         struct counters *counters = counters_init(STATS_END);
451         time_t updated = 0;
452         struct stat st;
453         unsigned zero_timestamp = 0;
454
455         assert(conf);
456
457         // Add up the stats in each directory.
458         for (int dir = -1; dir <= 0xF; dir++) {
459                 char *fname;
460
461                 if (dir == -1) {
462                         fname = format("%s/stats", conf->cache_dir);
463                 } else {
464                         fname = format("%s/%1x/stats", conf->cache_dir, dir);
465                 }
466
467                 counters->data[STATS_ZEROTIMESTAMP] = 0; // Don't add
468                 stats_read(fname, counters);
469                 zero_timestamp = MAX(counters->data[STATS_ZEROTIMESTAMP], zero_timestamp);
470                 if (stat(fname, &st) == 0 && st.st_mtime > updated) {
471                         updated = st.st_mtime;
472                 }
473                 free(fname);
474         }
475
476         counters->data[STATS_ZEROTIMESTAMP] = zero_timestamp;
477
478         printf("cache directory                     %s\n", conf->cache_dir);
479         printf("primary config                      %s\n",
480                primary_config_path ? primary_config_path : "");
481         printf("secondary config      (readonly)    %s\n",
482                secondary_config_path ? secondary_config_path : "");
483         if (updated) {
484                 struct tm *tm = localtime(&updated);
485                 char timestamp[100];
486                 strftime(timestamp, sizeof(timestamp), "%c", tm);
487                 printf("stats updated                       %s\n", timestamp);
488         }
489
490         // ...and display them.
491         for (int i = 0; stats_info[i].message; i++) {
492                 enum stats stat = stats_info[i].stat;
493
494                 if (stats_info[i].flags & FLAG_NEVER) {
495                         continue;
496                 }
497                 if (counters->data[stat] == 0 && !(stats_info[i].flags & FLAG_ALWAYS)) {
498                         continue;
499                 }
500
501                 char *value;
502                 if (stats_info[i].format_fn) {
503                         value = stats_info[i].format_fn(counters->data[stat]);
504                 } else {
505                         value = format("%8u", counters->data[stat]);
506                 }
507                 if (value) {
508                         printf("%-31s %s\n", stats_info[i].message, value);
509                         free(value);
510                 }
511
512                 if (stat == STATS_TOCACHE) {
513                         unsigned direct = counters->data[STATS_CACHEHIT_DIR];
514                         unsigned preprocessed = counters->data[STATS_CACHEHIT_CPP];
515                         unsigned hit = direct + preprocessed;
516                         unsigned miss = counters->data[STATS_TOCACHE];
517                         unsigned total = hit + miss;
518                         double percent = total > 0 ? (100.0 * hit) / total : 0.0;
519                         printf("cache hit rate                    %6.2f %%\n", percent);
520                 }
521         }
522
523         if (conf->max_files != 0) {
524                 printf("max files                       %8u\n", conf->max_files);
525         }
526         if (conf->max_size != 0) {
527                 char *value = format_size(conf->max_size);
528                 printf("max cache size                  %s\n", value);
529                 free(value);
530         }
531
532         counters_free(counters);
533 }
534
535 // Zero all the stats structures.
536 void
537 stats_zero(void)
538 {
539         assert(conf);
540
541         char *fname = format("%s/stats", conf->cache_dir);
542         x_unlink(fname);
543         free(fname);
544
545         time_t timestamp = time(NULL);
546
547         for (int dir = 0; dir <= 0xF; dir++) {
548                 struct counters *counters = counters_init(STATS_END);
549                 struct stat st;
550                 fname = format("%s/%1x/stats", conf->cache_dir, dir);
551                 if (stat(fname, &st) != 0) {
552                         // No point in trying to reset the stats file if it doesn't exist.
553                         free(fname);
554                         continue;
555                 }
556                 if (lockfile_acquire(fname, lock_staleness_limit)) {
557                         stats_read(fname, counters);
558                         for (unsigned i = 0; stats_info[i].message; i++) {
559                                 if (!(stats_info[i].flags & FLAG_NOZERO)) {
560                                         counters->data[stats_info[i].stat] = 0;
561                                 }
562                         }
563                         counters->data[STATS_ZEROTIMESTAMP] = timestamp;
564                         stats_write(fname, counters);
565                         lockfile_release(fname);
566                 }
567                 counters_free(counters);
568                 free(fname);
569         }
570 }
571
572 // Get the per-directory limits.
573 void
574 stats_get_obsolete_limits(const char *dir, unsigned *maxfiles,
575                           uint64_t *maxsize)
576 {
577         struct counters *counters = counters_init(STATS_END);
578         char *sname = format("%s/stats", dir);
579         stats_read(sname, counters);
580         *maxfiles = counters->data[STATS_OBSOLETE_MAXFILES];
581         *maxsize = (uint64_t)counters->data[STATS_OBSOLETE_MAXSIZE] * 1024;
582         free(sname);
583         counters_free(counters);
584 }
585
586 // Set the per-directory sizes.
587 void
588 stats_set_sizes(const char *dir, unsigned num_files, uint64_t total_size)
589 {
590         struct counters *counters = counters_init(STATS_END);
591         char *statsfile = format("%s/stats", dir);
592         if (lockfile_acquire(statsfile, lock_staleness_limit)) {
593                 stats_read(statsfile, counters);
594                 counters->data[STATS_NUMFILES] = num_files;
595                 counters->data[STATS_TOTALSIZE] = total_size / 1024;
596                 stats_write(statsfile, counters);
597                 lockfile_release(statsfile);
598         }
599         free(statsfile);
600         counters_free(counters);
601 }
602
603 // Count directory cleanup run.
604 void
605 stats_add_cleanup(const char *dir, unsigned count)
606 {
607         struct counters *counters = counters_init(STATS_END);
608         char *statsfile = format("%s/stats", dir);
609         if (lockfile_acquire(statsfile, lock_staleness_limit)) {
610                 stats_read(statsfile, counters);
611                 counters->data[STATS_NUMCLEANUPS] += count;
612                 stats_write(statsfile, counters);
613                 lockfile_release(statsfile);
614         }
615         free(statsfile);
616         counters_free(counters);
617 }