1 // Copyright (C) 2002-2004 Andrew Tridgell
2 // Copyright (C) 2009-2018 Joel Rosdahl
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)
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
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
18 // Routines to handle the stats files. The stats file is stored one per cache
19 // subdirectory to make this more scalable.
24 #include <sys/types.h>
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;
38 static struct counters *counter_updates;
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
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);
48 static format_fn format_size_times_1024;
49 static format_fn format_timestamp;
51 // Statistics fields in display order.
55 format_fn *format_fn; // NULL -> use plain integer format
72 "cache hit (preprocessed)",
90 "called for preprocessing",
96 "multiple source files",
102 "compiler produced stdout",
108 "compiler produced no output",
114 "compiler produced empty output",
126 "ccache internal error",
132 "preprocessor error",
138 "can't use precompiled header",
144 "couldn't find the compiler",
150 "cache file missing",
156 "bad compiler arguments",
162 "unsupported source language",
168 "compiler check failed",
174 "autoconf compile/link",
179 STATS_UNSUPPORTED_OPTION,
180 "unsupported compiler option",
185 STATS_UNSUPPORTED_DIRECTIVE,
186 "unsupported code directive",
198 "output to a non-regular file",
210 "error hashing extra file",
216 "cleanups performed",
224 FLAG_NOZERO|FLAG_ALWAYS
229 format_size_times_1024,
230 FLAG_NOZERO|FLAG_ALWAYS
233 STATS_OBSOLETE_MAXFILES,
236 FLAG_NOZERO|FLAG_NEVER
239 STATS_OBSOLETE_MAXSIZE,
242 FLAG_NOZERO|FLAG_NEVER
253 format_size(uint64_t size)
255 char *s = format_human_readable_size(size);
256 reformat(&s, "%11s", s);
261 format_size_times_1024(uint64_t size)
263 return format_size(size * 1024);
267 format_timestamp(uint64_t timestamp)
270 struct tm *tm = localtime((time_t *)×tamp);
272 strftime(buffer, sizeof(buffer), "%c", tm);
273 return format(" %s", buffer);
279 // Parse a stats file from a buffer, adding to the counters.
281 parse_stats(struct counters *counters, const char *buf)
287 long val = strtol(p, &p2, 10);
291 if (counters->size < i + 1) {
292 counters_resize(counters, i + 1);
294 counters->data[i] += val;
300 // Write out a stats file.
302 stats_write(const char *path, struct counters *counters)
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);
312 x_rename(tmp_file, path);
317 init_counter_updates(void)
319 if (!counter_updates) {
320 counter_updates = counters_init(STATS_END);
324 // Record that a number of bytes and files have been added to the cache. Size
327 stats_update_size(int64_t size, int files)
329 init_counter_updates();
330 counter_updates->data[STATS_NUMFILES] += files;
331 counter_updates->data[STATS_TOTALSIZE] += size / 1024;
334 // Read in the stats from one directory and add to the counters.
336 stats_read(const char *sfile, struct counters *counters)
338 char *data = read_text_file(sfile, 1024);
340 parse_stats(counters, data);
345 // Write counter updates in counter_updates to disk.
355 if (!counter_updates) {
359 bool should_flush = false;
360 for (int i = 0; i < STATS_END; ++i) {
361 if (counter_updates->data[i] > 0) {
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);
380 if (!lockfile_acquire(stats_file, lock_staleness_limit)) {
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];
389 stats_write(stats_file, counters);
390 lockfile_release(stats_file);
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);
401 char *subdir = dirname(stats_file);
402 bool need_cleanup = false;
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)",
408 counters->data[STATS_NUMFILES],
409 conf->max_files / 16);
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)",
416 counters->data[STATS_TOTALSIZE],
417 (unsigned long)conf->max_size / 1024 / 16);
422 clean_up_dir(conf, subdir, conf->limit_multiple);
426 counters_free(counters);
429 // Update a normal stat.
431 stats_update(enum stats stat)
433 assert(stat > STATS_NONE && stat < STATS_END);
434 init_counter_updates();
435 counter_updates->data[stat]++;
438 // Get the pending update of a counter value.
440 stats_get_pending(enum stats stat)
442 init_counter_updates();
443 return counter_updates->data[stat];
446 // Sum and display the total stats for all cache dirs.
450 struct counters *counters = counters_init(STATS_END);
453 unsigned zero_timestamp = 0;
457 // Add up the stats in each directory.
458 for (int dir = -1; dir <= 0xF; dir++) {
462 fname = format("%s/stats", conf->cache_dir);
464 fname = format("%s/%1x/stats", conf->cache_dir, dir);
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;
476 counters->data[STATS_ZEROTIMESTAMP] = zero_timestamp;
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 : "");
484 struct tm *tm = localtime(&updated);
486 strftime(timestamp, sizeof(timestamp), "%c", tm);
487 printf("stats updated %s\n", timestamp);
490 // ...and display them.
491 for (int i = 0; stats_info[i].message; i++) {
492 enum stats stat = stats_info[i].stat;
494 if (stats_info[i].flags & FLAG_NEVER) {
497 if (counters->data[stat] == 0 && !(stats_info[i].flags & FLAG_ALWAYS)) {
502 if (stats_info[i].format_fn) {
503 value = stats_info[i].format_fn(counters->data[stat]);
505 value = format("%8u", counters->data[stat]);
508 printf("%-31s %s\n", stats_info[i].message, value);
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);
523 if (conf->max_files != 0) {
524 printf("max files %8u\n", conf->max_files);
526 if (conf->max_size != 0) {
527 char *value = format_size(conf->max_size);
528 printf("max cache size %s\n", value);
532 counters_free(counters);
535 // Zero all the stats structures.
541 char *fname = format("%s/stats", conf->cache_dir);
545 time_t timestamp = time(NULL);
547 for (int dir = 0; dir <= 0xF; dir++) {
548 struct counters *counters = counters_init(STATS_END);
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.
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;
563 counters->data[STATS_ZEROTIMESTAMP] = timestamp;
564 stats_write(fname, counters);
565 lockfile_release(fname);
567 counters_free(counters);
572 // Get the per-directory limits.
574 stats_get_obsolete_limits(const char *dir, unsigned *maxfiles,
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;
583 counters_free(counters);
586 // Set the per-directory sizes.
588 stats_set_sizes(const char *dir, unsigned num_files, uint64_t total_size)
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);
600 counters_free(counters);
603 // Count directory cleanup run.
605 stats_add_cleanup(const char *dir, unsigned count)
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);
616 counters_free(counters);