/* * Copyright (C) 2002-2004 Andrew Tridgell * Copyright (C) 2009-2011 Joel Rosdahl * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Routines to handle the stats files The stats file is stored one per cache * subdirectory to make this more scalable. */ #include "ccache.h" #include "hashutil.h" #include #include #include #include #include #include #include extern char *stats_file; extern char *cache_dir; extern unsigned lock_staleness_limit; static struct counters *counter_updates; /* default maximum cache size */ #ifndef DEFAULT_MAXSIZE #define DEFAULT_MAXSIZE (1024*1024) #endif #define FLAG_NOZERO 1 /* don't zero with the -z option */ #define FLAG_ALWAYS 2 /* always show, even if zero */ static void display_size(size_t v); /* statistics fields in display order */ static struct { enum stats stat; char *message; void (*fn)(size_t ); unsigned flags; } stats_info[] = { { STATS_CACHEHIT_DIR, "cache hit (direct) ", NULL, FLAG_ALWAYS }, { STATS_CACHEHIT_CPP, "cache hit (preprocessed) ", NULL, FLAG_ALWAYS }, { STATS_TOCACHE, "cache miss ", NULL, FLAG_ALWAYS }, { STATS_LINK, "called for link ", NULL, 0 }, { STATS_PREPROCESSING, "called for preprocessing ", NULL, 0 }, { STATS_MULTIPLE, "multiple source files ", NULL, 0 }, { STATS_STDOUT, "compiler produced stdout ", NULL, 0 }, { STATS_NOOUTPUT, "compiler produced no output ", NULL, 0 }, { STATS_EMPTYOUTPUT, "compiler produced empty output ", NULL, 0 }, { STATS_STATUS, "compile failed ", NULL, 0 }, { STATS_ERROR, "ccache internal error ", NULL, 0 }, { STATS_PREPROCESSOR, "preprocessor error ", NULL, 0 }, { STATS_CANTUSEPCH, "can't use precompiled header ", NULL, 0 }, { STATS_COMPILER, "couldn't find the compiler ", NULL, 0 }, { STATS_MISSING, "cache file missing ", NULL, 0 }, { STATS_ARGS, "bad compiler arguments ", NULL, 0 }, { STATS_SOURCELANG, "unsupported source language ", NULL, 0 }, { STATS_COMPCHECK, "compiler check failed ", NULL, 0 }, { STATS_CONFTEST, "autoconf compile/link ", NULL, 0 }, { STATS_UNSUPPORTED, "unsupported compiler option ", NULL, 0 }, { STATS_OUTSTDOUT, "output to stdout ", NULL, 0 }, { STATS_DEVICE, "output to a non-regular file ", NULL, 0 }, { STATS_NOINPUT, "no input file ", NULL, 0 }, { STATS_BADEXTRAFILE, "error hashing extra file ", NULL, 0 }, { STATS_NUMFILES, "files in cache ", NULL, FLAG_NOZERO|FLAG_ALWAYS }, { STATS_TOTALSIZE, "cache size ", display_size , FLAG_NOZERO|FLAG_ALWAYS }, { STATS_MAXFILES, "max files ", NULL, FLAG_NOZERO }, { STATS_MAXSIZE, "max cache size ", display_size, FLAG_NOZERO }, { STATS_NONE, NULL, NULL, 0 } }; static void display_size(size_t v) { char *s = format_size(v); printf("%15s", s); free(s); } /* parse a stats file from a buffer - adding to the counters */ static void parse_stats(struct counters *counters, const char *buf) { size_t i = 0; const char *p; char *p2; long val; p = buf; while (1) { val = strtol(p, &p2, 10); if (p2 == p) { break; } if (counters->size < i + 1) { counters_resize(counters, i + 1); } counters->data[i] += val; i++; p = p2; } } /* write out a stats file */ void stats_write(const char *path, struct counters *counters) { size_t i; char *tmp_file; FILE *f; tmp_file = format("%s.tmp.%s", path, tmp_string()); f = fopen(tmp_file, "wb"); if (!f) { cc_log("Failed to open %s", tmp_file); goto end; } for (i = 0; i < counters->size; i++) { if (fprintf(f, "%u\n", counters->data[i]) < 0) { fatal("Failed to write to %s", tmp_file); } } fclose(f); x_rename(tmp_file, path); end: free(tmp_file); } /* fill in some default stats values */ static void stats_default(struct counters *counters) { counters->data[STATS_MAXSIZE] += DEFAULT_MAXSIZE / 16; } static void init_counter_updates(void) { if (!counter_updates) { counter_updates = counters_init(STATS_END); } } /* * Update a statistics counter (unless it's STATS_NONE) and also record that a * number of bytes and files have been added to the cache. Size is in KiB. */ void stats_update_size(enum stats stat, size_t size, unsigned files) { init_counter_updates(); if (stat != STATS_NONE) { counter_updates->data[stat]++; } counter_updates->data[STATS_NUMFILES] += files; counter_updates->data[STATS_TOTALSIZE] += size; } /* Read in the stats from one directory and add to the counters. */ void stats_read(const char *sfile, struct counters *counters) { char *data = read_text_file(sfile); if (data) { parse_stats(counters, data); } else { stats_default(counters); } free(data); } /* * Write counter updates in counter_updates to disk. */ void stats_flush(void) { struct counters *counters; bool need_cleanup = false; bool should_flush = false; int i; extern char *cache_logfile; if (getenv("CCACHE_NOSTATS")) return; init_counter_updates(); for (i = 0; i < STATS_END; ++i) { if (counter_updates->data[i] > 0) { should_flush = true; break; } } if (!should_flush) return; if (!stats_file) { char *stats_dir; /* * A NULL stats_file means that we didn't get past calculate_object_hash(), * so we just choose one of stats files in the 16 subdirectories. */ if (!cache_dir) return; stats_dir = format("%s/%x", cache_dir, hash_from_int(getpid()) % 16); stats_file = format("%s/stats", stats_dir); create_dir(stats_dir); free(stats_dir); } if (!lockfile_acquire(stats_file, lock_staleness_limit)) { return; } counters = counters_init(STATS_END); stats_read(stats_file, counters); for (i = 0; i < STATS_END; ++i) { counters->data[i] += counter_updates->data[i]; } stats_write(stats_file, counters); lockfile_release(stats_file); if (cache_logfile) { for (i = 0; i < STATS_END; ++i) { if (counter_updates->data[stats_info[i].stat] != 0 && !(stats_info[i].flags & FLAG_NOZERO)) { cc_log("Result: %s", stats_info[i].message); } } } if (counters->data[STATS_MAXFILES] != 0 && counters->data[STATS_NUMFILES] > counters->data[STATS_MAXFILES]) { need_cleanup = true; } if (counters->data[STATS_MAXSIZE] != 0 && counters->data[STATS_TOTALSIZE] > counters->data[STATS_MAXSIZE]) { need_cleanup = true; } if (need_cleanup) { char *p = dirname(stats_file); cleanup_dir(p, counters->data[STATS_MAXFILES], counters->data[STATS_MAXSIZE]); free(p); } } /* update a normal stat */ void stats_update(enum stats stat) { stats_update_size(stat, 0, 0); } /* Get the pending update of a counter value. */ unsigned stats_get_pending(enum stats stat) { init_counter_updates(); return counter_updates->data[stat]; } /* sum and display the total stats for all cache dirs */ void stats_summary(void) { int dir, i; struct counters *counters = counters_init(STATS_END); /* add up the stats in each directory */ for (dir = -1; dir <= 0xF; dir++) { char *fname; if (dir == -1) { fname = format("%s/stats", cache_dir); } else { fname = format("%s/%1x/stats", cache_dir, dir); } stats_read(fname, counters); free(fname); /* oh what a nasty hack ... */ if (dir == -1) { counters->data[STATS_MAXSIZE] = 0; } } printf("cache directory %s\n", cache_dir); /* and display them */ for (i = 0; stats_info[i].message; i++) { enum stats stat = stats_info[i].stat; if (counters->data[stat] == 0 && !(stats_info[i].flags & FLAG_ALWAYS)) { continue; } printf("%s ", stats_info[i].message); if (stats_info[i].fn) { stats_info[i].fn(counters->data[stat]); printf("\n"); } else { printf("%8u\n", counters->data[stat]); } } counters_free(counters); } /* zero all the stats structures */ void stats_zero(void) { int dir; unsigned i; char *fname; fname = format("%s/stats", cache_dir); x_unlink(fname); free(fname); for (dir = 0; dir <= 0xF; dir++) { struct counters *counters = counters_init(STATS_END); fname = format("%s/%1x/stats", cache_dir, dir); if (lockfile_acquire(fname, lock_staleness_limit)) { stats_read(fname, counters); for (i = 0; stats_info[i].message; i++) { if (!(stats_info[i].flags & FLAG_NOZERO)) { counters->data[stats_info[i].stat] = 0; } } stats_write(fname, counters); lockfile_release(fname); } counters_free(counters); free(fname); } } /* Get the per directory limits */ void stats_get_limits(const char *dir, unsigned *maxfiles, unsigned *maxsize) { struct counters *counters = counters_init(STATS_END); char *sname = format("%s/stats", dir); stats_read(sname, counters); *maxfiles = counters->data[STATS_MAXFILES]; *maxsize = counters->data[STATS_MAXSIZE]; free(sname); counters_free(counters); } /* set the per directory limits */ int stats_set_limits(long maxfiles, long maxsize) { int dir; if (maxfiles != -1) { maxfiles /= 16; } if (maxsize != -1) { maxsize /= 16; } if (create_dir(cache_dir) != 0) { return 1; } /* set the limits in each directory */ for (dir = 0; dir <= 0xF; dir++) { char *fname, *cdir; cdir = format("%s/%1x", cache_dir, dir); if (create_dir(cdir) != 0) { free(cdir); return 1; } fname = format("%s/stats", cdir); free(cdir); if (lockfile_acquire(fname, lock_staleness_limit)) { struct counters *counters = counters_init(STATS_END); stats_read(fname, counters); if (maxfiles != -1) { counters->data[STATS_MAXFILES] = maxfiles; } if (maxsize != -1) { counters->data[STATS_MAXSIZE] = maxsize; } stats_write(fname, counters); lockfile_release(fname); counters_free(counters); } free(fname); } return 0; } /* set the per directory sizes */ void stats_set_sizes(const char *dir, size_t num_files, size_t total_size) { struct counters *counters = counters_init(STATS_END); char *statsfile; create_dir(dir); statsfile = format("%s/stats", dir); if (lockfile_acquire(statsfile, lock_staleness_limit)) { stats_read(statsfile, counters); counters->data[STATS_NUMFILES] = num_files; counters->data[STATS_TOTALSIZE] = total_size; stats_write(statsfile, counters); lockfile_release(statsfile); } free(statsfile); counters_free(counters); }