Branch data Line data Source code
1 : : /*
2 : : * Copyright (C) 2002-2004 Andrew Tridgell
3 : : * Copyright (C) 2009-2015 Joel Rosdahl
4 : : *
5 : : * This program is free software; you can redistribute it and/or modify it
6 : : * under the terms of the GNU General Public License as published by the Free
7 : : * Software Foundation; either version 3 of the License, or (at your option)
8 : : * any later version.
9 : : *
10 : : * This program is distributed in the hope that it will be useful, but WITHOUT
11 : : * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 : : * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 : : * more details.
14 : : *
15 : : * You should have received a copy of the GNU General Public License along with
16 : : * this program; if not, write to the Free Software Foundation, Inc., 51
17 : : * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 : : */
19 : :
20 : : /*
21 : : * Routines to handle the stats files. The stats file is stored one per cache
22 : : * subdirectory to make this more scalable.
23 : : */
24 : :
25 : : #include "ccache.h"
26 : : #include "hashutil.h"
27 : :
28 : : #include <sys/types.h>
29 : : #include <sys/stat.h>
30 : : #include <fcntl.h>
31 : : #include <stdio.h>
32 : : #include <stdlib.h>
33 : : #include <string.h>
34 : : #include <unistd.h>
35 : :
36 : : extern char *stats_file;
37 : : extern struct conf *conf;
38 : : extern unsigned lock_staleness_limit;
39 : : extern char *primary_config_path;
40 : : extern char *secondary_config_path;
41 : :
42 : : static struct counters *counter_updates;
43 : :
44 : : #define FLAG_NOZERO 1 /* don't zero with the -z option */
45 : : #define FLAG_ALWAYS 2 /* always show, even if zero */
46 : : #define FLAG_NEVER 4 /* never show */
47 : :
48 : : static void display_size_times_1024(uint64_t size);
49 : :
50 : : /* statistics fields in display order */
51 : : static struct {
52 : : enum stats stat;
53 : : char *message;
54 : : void (*fn)(uint64_t);
55 : : unsigned flags;
56 : : } stats_info[] = {
57 : : { STATS_CACHEHIT_DIR, "cache hit (direct) ", NULL, FLAG_ALWAYS },
58 : : { STATS_CACHEHIT_CPP, "cache hit (preprocessed) ", NULL, FLAG_ALWAYS },
59 : : { STATS_TOCACHE, "cache miss ", NULL, FLAG_ALWAYS },
60 : : { STATS_LINK, "called for link ", NULL, 0 },
61 : : { STATS_PREPROCESSING, "called for preprocessing ", NULL, 0 },
62 : : { STATS_MULTIPLE, "multiple source files ", NULL, 0 },
63 : : { STATS_STDOUT, "compiler produced stdout ", NULL, 0 },
64 : : { STATS_NOOUTPUT, "compiler produced no output ", NULL, 0 },
65 : : { STATS_EMPTYOUTPUT, "compiler produced empty output ", NULL, 0 },
66 : : { STATS_STATUS, "compile failed ", NULL, 0 },
67 : : { STATS_ERROR, "ccache internal error ", NULL, 0 },
68 : : { STATS_PREPROCESSOR, "preprocessor error ", NULL, 0 },
69 : : { STATS_CANTUSEPCH, "can't use precompiled header ", NULL, 0 },
70 : : { STATS_COMPILER, "couldn't find the compiler ", NULL, 0 },
71 : : { STATS_MISSING, "cache file missing ", NULL, 0 },
72 : : { STATS_ARGS, "bad compiler arguments ", NULL, 0 },
73 : : { STATS_SOURCELANG, "unsupported source language ", NULL, 0 },
74 : : { STATS_COMPCHECK, "compiler check failed ", NULL, 0 },
75 : : { STATS_CONFTEST, "autoconf compile/link ", NULL, 0 },
76 : : { STATS_UNSUPPORTED, "unsupported compiler option ", NULL, 0 },
77 : : { STATS_OUTSTDOUT, "output to stdout ", NULL, 0 },
78 : : { STATS_DEVICE, "output to a non-regular file ", NULL, 0 },
79 : : { STATS_NOINPUT, "no input file ", NULL, 0 },
80 : : { STATS_BADEXTRAFILE, "error hashing extra file ", NULL, 0 },
81 : : { STATS_NUMFILES, "files in cache ", NULL, FLAG_NOZERO|FLAG_ALWAYS },
82 : : { STATS_TOTALSIZE, "cache size ", display_size_times_1024 , FLAG_NOZERO|FLAG_ALWAYS },
83 : : { STATS_OBSOLETE_MAXFILES, "OBSOLETE", NULL, FLAG_NOZERO|FLAG_NEVER},
84 : : { STATS_OBSOLETE_MAXSIZE, "OBSOLETE", NULL, FLAG_NOZERO|FLAG_NEVER},
85 : : { STATS_NONE, NULL, NULL, 0 }
86 : : };
87 : :
88 : : static void
89 : 12 : display_size(uint64_t size)
90 : : {
91 : 12 : char *s = format_human_readable_size(size);
92 : 12 : printf("%11s", s);
93 : 12 : free(s);
94 : 12 : }
95 : :
96 : : static void
97 : 6 : display_size_times_1024(uint64_t size)
98 : : {
99 : 6 : display_size(size * 1024);
100 : 6 : }
101 : :
102 : : /* parse a stats file from a buffer - adding to the counters */
103 : : static void
104 : 10 : parse_stats(struct counters *counters, const char *buf)
105 : : {
106 : 10 : size_t i = 0;
107 : : const char *p;
108 : : char *p2;
109 : : long val;
110 : :
111 : 10 : p = buf;
112 : : while (true) {
113 : 329 : val = strtol(p, &p2, 10);
114 [ + + ]: 329 : if (p2 == p) {
115 : : break;
116 : : }
117 [ + + ]: 319 : if (counters->size < i + 1) {
118 : 100 : counters_resize(counters, i + 1);
119 : : }
120 : 319 : counters->data[i] += val;
121 : 319 : i++;
122 : 319 : p = p2;
123 : 319 : }
124 : 10 : }
125 : :
126 : : /* write out a stats file */
127 : : void
128 : 2 : stats_write(const char *path, struct counters *counters)
129 : : {
130 : : size_t i;
131 : : char *tmp_file;
132 : : FILE *f;
133 : :
134 : 2 : tmp_file = format("%s.tmp", path);
135 : 2 : f = create_tmp_file(&tmp_file, "wb");
136 [ + + ]: 131 : for (i = 0; i < counters->size; i++) {
137 [ - + ]: 129 : if (fprintf(f, "%u\n", counters->data[i]) < 0) {
138 : 0 : fatal("Failed to write to %s", tmp_file);
139 : : }
140 : : }
141 : 2 : fclose(f);
142 : 2 : x_rename(tmp_file, path);
143 : 2 : free(tmp_file);
144 : 2 : }
145 : :
146 : : static void
147 : 8 : init_counter_updates(void)
148 : : {
149 [ + + ]: 8 : if (!counter_updates) {
150 : 2 : counter_updates = counters_init(STATS_END);
151 : : }
152 : 8 : }
153 : :
154 : : /*
155 : : * Record that a number of bytes and files have been added to the cache. Size
156 : : * is in bytes.
157 : : */
158 : : void
159 : 2 : stats_update_size(uint64_t size, unsigned files)
160 : : {
161 : 2 : init_counter_updates();
162 : 2 : counter_updates->data[STATS_NUMFILES] += files;
163 : 2 : counter_updates->data[STATS_TOTALSIZE] += size / 1024;
164 : 2 : }
165 : :
166 : : /* Read in the stats from one directory and add to the counters. */
167 : : void
168 : 104 : stats_read(const char *sfile, struct counters *counters)
169 : : {
170 : 104 : char *data = read_text_file(sfile, 1024);
171 [ + + ]: 104 : if (data) {
172 : 10 : parse_stats(counters, data);
173 : : }
174 : 104 : free(data);
175 : 104 : }
176 : :
177 : : /*
178 : : * Write counter updates in counter_updates to disk.
179 : : */
180 : : void
181 : 7 : stats_flush(void)
182 : : {
183 : : struct counters *counters;
184 : 7 : bool need_cleanup = false;
185 : 7 : bool should_flush = false;
186 : : int i;
187 : :
188 [ - + ]: 7 : assert(conf);
189 : :
190 [ - + ]: 7 : if (!conf->stats) {
191 : 0 : return;
192 : : }
193 : :
194 [ + + ]: 7 : if (!counter_updates) {
195 : 6 : return;
196 : : }
197 : :
198 [ + - ]: 5 : for (i = 0; i < STATS_END; ++i) {
199 [ + + ]: 5 : if (counter_updates->data[i] > 0) {
200 : 1 : should_flush = true;
201 : 1 : break;
202 : : }
203 : : }
204 [ - + ]: 1 : if (!should_flush) {
205 : 0 : return;
206 : : }
207 : :
208 [ - + ]: 1 : if (!stats_file) {
209 : : char *stats_dir;
210 : :
211 : : /*
212 : : * A NULL stats_file means that we didn't get past calculate_object_hash(),
213 : : * so we just choose one of stats files in the 16 subdirectories.
214 : : */
215 : 0 : stats_dir = format("%s/%x", conf->cache_dir, hash_from_int(getpid()) % 16);
216 : 0 : stats_file = format("%s/stats", stats_dir);
217 : 0 : free(stats_dir);
218 : : }
219 : :
220 [ - + ]: 1 : if (!lockfile_acquire(stats_file, lock_staleness_limit)) {
221 : 0 : return;
222 : : }
223 : 1 : counters = counters_init(STATS_END);
224 : 1 : stats_read(stats_file, counters);
225 [ + + ]: 30 : for (i = 0; i < STATS_END; ++i) {
226 : 29 : counters->data[i] += counter_updates->data[i];
227 : : }
228 : 1 : stats_write(stats_file, counters);
229 : 1 : lockfile_release(stats_file);
230 : :
231 [ + - ]: 1 : if (!str_eq(conf->log_file, "")) {
232 [ + + ]: 30 : for (i = 0; i < STATS_END; ++i) {
233 [ + + ][ + + ]: 29 : if (counter_updates->data[stats_info[i].stat] != 0
234 : 3 : && !(stats_info[i].flags & FLAG_NOZERO)) {
235 : 1 : cc_log("Result: %s", stats_info[i].message);
236 : : }
237 : : }
238 : : }
239 : :
240 [ - + ][ # # ]: 1 : if (conf->max_files != 0
241 : 1 : && counters->data[STATS_NUMFILES] > conf->max_files / 16) {
242 : 0 : need_cleanup = true;
243 : : }
244 [ + - ][ - + ]: 1 : if (conf->max_size != 0
245 : 2 : && counters->data[STATS_TOTALSIZE] > conf->max_size / 1024 / 16) {
246 : 0 : need_cleanup = true;
247 : : }
248 : :
249 [ - + ]: 1 : if (need_cleanup) {
250 : 0 : char *p = dirname(stats_file);
251 : 0 : cleanup_dir(conf, p);
252 : 0 : free(p);
253 : : }
254 : :
255 : 7 : counters_free(counters);
256 : : }
257 : :
258 : : /* update a normal stat */
259 : : void
260 : 4 : stats_update(enum stats stat)
261 : : {
262 [ + - ][ - + ]: 4 : assert(stat > STATS_NONE && stat < STATS_END);
263 : 4 : init_counter_updates();
264 : 4 : counter_updates->data[stat]++;
265 : 4 : }
266 : :
267 : : /* Get the pending update of a counter value. */
268 : : unsigned
269 : 2 : stats_get_pending(enum stats stat)
270 : : {
271 : 2 : init_counter_updates();
272 : 2 : return counter_updates->data[stat];
273 : : }
274 : :
275 : : /* sum and display the total stats for all cache dirs */
276 : : void
277 : 6 : stats_summary(struct conf *conf)
278 : : {
279 : : int dir, i;
280 : 6 : struct counters *counters = counters_init(STATS_END);
281 : :
282 [ - + ]: 6 : assert(conf);
283 : :
284 : : /* add up the stats in each directory */
285 [ + + ]: 108 : for (dir = -1; dir <= 0xF; dir++) {
286 : : char *fname;
287 : :
288 [ + + ]: 102 : if (dir == -1) {
289 : 6 : fname = format("%s/stats", conf->cache_dir);
290 : : } else {
291 : 96 : fname = format("%s/%1x/stats", conf->cache_dir, dir);
292 : : }
293 : :
294 : 102 : stats_read(fname, counters);
295 : 102 : free(fname);
296 : : }
297 : :
298 : 6 : printf("cache directory %s\n", conf->cache_dir);
299 [ + - ]: 6 : printf("primary config %s\n",
300 : 6 : primary_config_path ? primary_config_path : "");
301 [ - + ]: 6 : printf("secondary config (readonly) %s\n",
302 : 6 : secondary_config_path ? secondary_config_path : "");
303 : :
304 : : /* and display them */
305 [ + + ]: 174 : for (i = 0; stats_info[i].message; i++) {
306 : 168 : enum stats stat = stats_info[i].stat;
307 : :
308 [ + + ]: 168 : if (stats_info[i].flags & FLAG_NEVER) {
309 : 12 : continue;
310 : : }
311 [ + + ][ + + ]: 156 : if (counters->data[stat] == 0 && !(stats_info[i].flags & FLAG_ALWAYS)) {
312 : 126 : continue;
313 : : }
314 : :
315 : 30 : printf("%s ", stats_info[i].message);
316 [ + + ]: 30 : if (stats_info[i].fn) {
317 : 6 : stats_info[i].fn(counters->data[stat]);
318 : 6 : printf("\n");
319 : : } else {
320 : 24 : printf("%8u\n", counters->data[stat]);
321 : : }
322 : : }
323 : :
324 [ - + ]: 6 : if (conf->max_files != 0) {
325 : 0 : printf("max files %8u\n", conf->max_files);
326 : : }
327 [ + - ]: 6 : if (conf->max_size != 0) {
328 : 6 : printf("max cache size ");
329 : 6 : display_size(conf->max_size);
330 : 6 : printf("\n");
331 : : }
332 : :
333 : 6 : counters_free(counters);
334 : 6 : }
335 : :
336 : : /* zero all the stats structures */
337 : : void
338 : 0 : stats_zero(void)
339 : : {
340 : : int dir;
341 : : unsigned i;
342 : : char *fname;
343 : :
344 [ # # ]: 0 : assert(conf);
345 : :
346 : 0 : fname = format("%s/stats", conf->cache_dir);
347 : 0 : x_unlink(fname);
348 : 0 : free(fname);
349 : :
350 [ # # ]: 0 : for (dir = 0; dir <= 0xF; dir++) {
351 : 0 : struct counters *counters = counters_init(STATS_END);
352 : : struct stat st;
353 : 0 : fname = format("%s/%1x/stats", conf->cache_dir, dir);
354 [ # # ]: 0 : if (stat(fname, &st) != 0) {
355 : : /* No point in trying to reset the stats file if it doesn't exist. */
356 : 0 : free(fname);
357 : 0 : continue;
358 : : }
359 [ # # ]: 0 : if (lockfile_acquire(fname, lock_staleness_limit)) {
360 : 0 : stats_read(fname, counters);
361 [ # # ]: 0 : for (i = 0; stats_info[i].message; i++) {
362 [ # # ]: 0 : if (!(stats_info[i].flags & FLAG_NOZERO)) {
363 : 0 : counters->data[stats_info[i].stat] = 0;
364 : : }
365 : : }
366 : 0 : stats_write(fname, counters);
367 : 0 : lockfile_release(fname);
368 : : }
369 : 0 : counters_free(counters);
370 : 0 : free(fname);
371 : : }
372 : 0 : }
373 : :
374 : : /* Get the per directory limits */
375 : : void
376 : 0 : stats_get_obsolete_limits(const char *dir, unsigned *maxfiles,
377 : : uint64_t *maxsize)
378 : : {
379 : 0 : struct counters *counters = counters_init(STATS_END);
380 : 0 : char *sname = format("%s/stats", dir);
381 : 0 : stats_read(sname, counters);
382 : 0 : *maxfiles = counters->data[STATS_OBSOLETE_MAXFILES];
383 : 0 : *maxsize = (uint64_t)counters->data[STATS_OBSOLETE_MAXSIZE] * 1024;
384 : 0 : free(sname);
385 : 0 : counters_free(counters);
386 : 0 : }
387 : :
388 : : /* set the per directory sizes */
389 : : void
390 : 0 : stats_set_sizes(const char *dir, size_t num_files, size_t total_size)
391 : : {
392 : 0 : struct counters *counters = counters_init(STATS_END);
393 : : char *statsfile;
394 : :
395 : 0 : statsfile = format("%s/stats", dir);
396 : :
397 [ # # ]: 0 : if (lockfile_acquire(statsfile, lock_staleness_limit)) {
398 : 0 : stats_read(statsfile, counters);
399 : 0 : counters->data[STATS_NUMFILES] = num_files;
400 : 0 : counters->data[STATS_TOTALSIZE] = total_size / 1024;
401 : 0 : stats_write(statsfile, counters);
402 : 0 : lockfile_release(statsfile);
403 : : }
404 : 0 : free(statsfile);
405 : 0 : counters_free(counters);
406 : 0 : }
|