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