// Zero all the stats structures. void stats_zero(void) { assert(conf); char *fname = format("%s/stats", conf->cache_dir); x_unlink(fname); free(fname); time_t timestamp = time(NULL); for (int dir = 0; dir <= 0xF; dir++) { struct counters *counters = counters_init(STATS_END); struct stat st; fname = format("%s/%1x/stats", conf->cache_dir, dir); if (stat(fname, &st) != 0) { // No point in trying to reset the stats file if it doesn't exist. free(fname); continue; } if (lockfile_acquire(fname, lock_staleness_limit)) { stats_read(fname, counters); for (unsigned i = 0; stats_info[i].message; i++) { if (!(stats_info[i].flags & FLAG_NOZERO)) { counters->data[stats_info[i].stat] = 0; } } counters->data[STATS_ZEROTIMESTAMP] = timestamp; stats_write(fname, counters); lockfile_release(fname); } counters_free(counters); free(fname); } }
/* 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); } }
// Count directory cleanup run. void stats_add_cleanup(const char *dir, unsigned count) { struct counters *counters = counters_init(STATS_END); char *statsfile = format("%s/stats", dir); if (lockfile_acquire(statsfile, lock_staleness_limit)) { stats_read(statsfile, counters); counters->data[STATS_NUMCLEANUPS] += count; stats_write(statsfile, counters); lockfile_release(statsfile); } free(statsfile); counters_free(counters); }
// Set the per-directory sizes. void stats_set_sizes(const char *dir, unsigned num_files, uint64_t total_size) { struct counters *counters = counters_init(STATS_END); char *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 / 1024; stats_write(statsfile, counters); lockfile_release(statsfile); } free(statsfile); 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; }
int main(int argc, char **argv) { extern char *cache_logfile; cache_logfile = "/dev/stdout"; if (argc == 4) { unsigned staleness_limit = atoi(argv[1]); if (str_eq(argv[2], "acquire")) { return lockfile_acquire(argv[3], staleness_limit) == 0; } else if (str_eq(argv[2], "release")) { lockfile_release(argv[3]); return 0; } } fprintf(stderr, "Usage: testlockfile <staleness_limit> <acquire|release> <path>\n"); return 1; }
// Write counter updates in counter_updates to disk. void stats_flush(void) { assert(conf); if (!conf->stats) { return; } if (!counter_updates) { return; } bool should_flush = false; for (int 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. stats_dir = format("%s/%x", conf->cache_dir, hash_from_int(getpid()) % 16); stats_file = format("%s/stats", stats_dir); free(stats_dir); } if (!lockfile_acquire(stats_file, lock_staleness_limit)) { return; } struct counters *counters = counters_init(STATS_END); stats_read(stats_file, counters); for (int i = 0; i < STATS_END; ++i) { counters->data[i] += counter_updates->data[i]; } stats_write(stats_file, counters); lockfile_release(stats_file); if (!str_eq(conf->log_file, "") || conf->debug) { for (int 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); } } } char *subdir = dirname(stats_file); bool need_cleanup = false; if (conf->max_files != 0 && counters->data[STATS_NUMFILES] > conf->max_files / 16) { cc_log("Need to clean up %s since it holds %u files (limit: %u files)", subdir, counters->data[STATS_NUMFILES], conf->max_files / 16); need_cleanup = true; } if (conf->max_size != 0 && counters->data[STATS_TOTALSIZE] > conf->max_size / 1024 / 16) { cc_log("Need to clean up %s since it holds %u KiB (limit: %lu KiB)", subdir, counters->data[STATS_TOTALSIZE], (unsigned long)conf->max_size / 1024 / 16); need_cleanup = true; } if (need_cleanup) { clean_up_dir(conf, subdir, conf->limit_multiple); } free(subdir); counters_free(counters); }
/* * 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; assert(conf); if (!conf->stats) { return; } if (!counter_updates) { return; } 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. */ stats_dir = format("%s/%x", conf->cache_dir, hash_from_int(getpid()) % 16); stats_file = format("%s/stats", 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 (!str_eq(conf->log_file, "")) { 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 (conf->max_files != 0 && counters->data[STATS_NUMFILES] > conf->max_files / 16) { need_cleanup = true; } if (conf->max_size != 0 && counters->data[STATS_TOTALSIZE] > conf->max_size / 1024 / 16) { need_cleanup = true; } if (need_cleanup) { char *p = dirname(stats_file); cleanup_dir(conf, p); free(p); } counters_free(counters); }
/* * 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); } }
/* * This function acquires a lockfile for the given path. Returns true if the * lock was acquired, otherwise false. If the lock has been considered stale * for the number of microseconds specified by staleness_limit, the function * will (if possible) break the lock and then try to acquire it again. The * staleness limit should be reasonably larger than the longest time the lock * can be expected to be held, and the updates of the locked path should * probably be made with an atomic rename(2) to avoid corruption in the rare * case that the lock is broken by another process. */ bool lockfile_acquire(const char *path, unsigned staleness_limit) { char *lockfile = format("%s.lock", path); char *my_content = NULL, *content = NULL, *initial_content = NULL; const char *hostname = get_hostname(); bool acquired = false; #ifdef _WIN32 const size_t bufsize = 1024; int fd, len; #else int ret; #endif unsigned to_sleep = 1000, slept = 0; /* Microseconds. */ while (1) { free(my_content); my_content = format("%s:%d:%d", hostname, (int)getpid(), (int)time(NULL)); #ifdef _WIN32 fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL|O_BINARY, 0666); if (fd == -1) { cc_log("lockfile_acquire: open WRONLY %s: %s", lockfile, strerror(errno)); if (errno != EEXIST) { /* Directory doesn't exist or isn't writable? */ goto out; } /* Someone else has the lock. */ fd = open(lockfile, O_RDONLY|O_BINARY); if (fd == -1) { if (errno == ENOENT) { /* * The file was removed after the open() call above, so retry * acquiring it. */ continue; } else { cc_log("lockfile_acquire: open RDONLY %s: %s", lockfile, strerror(errno)); goto out; } } free(content); content = x_malloc(bufsize); if ((len = read(fd, content, bufsize - 1)) == -1) { cc_log("lockfile_acquire: read %s: %s", lockfile, strerror(errno)); close(fd); goto out; } close(fd); content[len] = '\0'; } else { /* We got the lock. */ if (write(fd, my_content, strlen(my_content)) == -1) { cc_log("lockfile_acquire: write %s: %s", lockfile, strerror(errno)); close(fd); unlink(lockfile); goto out; } close(fd); acquired = true; goto out; } #else ret = symlink(my_content, lockfile); if (ret == 0) { /* We got the lock. */ acquired = true; goto out; } cc_log("lockfile_acquire: symlink %s: %s", lockfile, strerror(errno)); if (errno != EEXIST) { /* Directory doesn't exist or isn't writable? */ goto out; } free(content); content = x_readlink(lockfile); if (!content) { if (errno == ENOENT) { /* * The symlink was removed after the symlink() call above, so retry * acquiring it. */ continue; } else { cc_log("lockfile_acquire: readlink %s: %s", lockfile, strerror(errno)); goto out; } } #endif if (str_eq(content, my_content)) { /* Lost NFS reply? */ cc_log("lockfile_acquire: symlink %s failed but we got the lock anyway", lockfile); acquired = true; goto out; } /* * A possible improvement here would be to check if the process holding the * lock is still alive and break the lock early if it isn't. */ cc_log("lockfile_acquire: lock info for %s: %s", lockfile, content); if (!initial_content) { initial_content = x_strdup(content); } if (slept > staleness_limit) { if (str_eq(content, initial_content)) { /* The lock seems to be stale -- break it. */ cc_log("lockfile_acquire: breaking %s", lockfile); if (lockfile_acquire(lockfile, staleness_limit)) { lockfile_release(path); lockfile_release(lockfile); to_sleep = 1000; slept = 0; continue; } } cc_log("lockfile_acquire: gave up acquiring %s", lockfile); goto out; } cc_log("lockfile_acquire: failed to acquire %s; sleeping %u microseconds", lockfile, to_sleep); usleep(to_sleep); slept += to_sleep; to_sleep *= 2; } out: if (acquired) { cc_log("Acquired lock %s", lockfile); } else { cc_log("Failed to acquire lock %s", lockfile); } free(lockfile); free(my_content); free(initial_content); free(content); return acquired; }