Bump to ccache 4.4
[platform/upstream/ccache.git] / misc / performance
1 #! /usr/bin/env python3
2 #
3 # Copyright (C) 2010-2020 Joel Rosdahl and other contributors
4 #
5 # See doc/AUTHORS.adoc for a complete list of contributors.
6 #
7 # This program is free software; you can redistribute it and/or modify it under
8 # the terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 3 of the License, or (at your option) any later
10 # version.
11 #
12 # This program is distributed in the hope that it will be useful, but WITHOUT
13 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15 # details.
16 #
17 # You should have received a copy of the GNU General Public License along with
18 # this program; if not, write to the Free Software Foundation, Inc., 51
19 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
21 from optparse import OptionParser
22 from os import access, environ, mkdir, getpid, X_OK
23 from os.path import (
24     abspath,
25     basename,
26     exists,
27     isabs,
28     isfile,
29     join as joinpath,
30     realpath,
31     splitext,
32 )
33 from shutil import rmtree
34 from subprocess import call
35 from statistics import median
36 from time import time
37 import sys
38
39 USAGE = """%prog [options] <compiler> [compiler options] <source code file>"""
40
41 DESCRIPTION = """\
42 This program compiles a C/C++ file with/without ccache a number of times to get
43 some idea of ccache speedup and overhead in the preprocessor and direct modes.
44 The arguments to the program should be the compiler, optionally followed by
45 compiler options, and finally the source file to compile. The compiler options
46 must not contain -c or -o as these options will be added later. Example:
47 misc/performance gcc -g -O2 -Idir file.c
48 """
49
50 DEFAULT_CCACHE = "./ccache"
51 DEFAULT_DIRECTORY = "."
52 DEFAULT_HIT_FACTOR = 1
53 DEFAULT_TIMES = 30
54
55 PHASES = [
56     "without ccache",
57     "with ccache, preprocessor mode, cache miss",
58     "with ccache, preprocessor mode, cache hit",
59     "with ccache, direct mode, cache miss",
60     "with ccache, direct mode, cache hit",
61     "with ccache, depend mode, cache miss",
62     "with ccache, depend mode, cache hit",
63 ]
64
65 verbose = False
66
67
68 def progress(msg):
69     if verbose:
70         sys.stderr.write(msg)
71         sys.stderr.flush()
72
73
74 def recreate_dir(x):
75     if exists(x):
76         rmtree(x)
77     mkdir(x)
78
79
80 def test(tmp_dir, options, compiler_args, source_file):
81     src_dir = "%s/src" % tmp_dir
82     obj_dir = "%s/obj" % tmp_dir
83     ccache_dir = "%s/ccache" % tmp_dir
84     mkdir(src_dir)
85     mkdir(obj_dir)
86
87     compiler_args += ["-c", "-o"]
88     extension = splitext(source_file)[1]
89     hit_factor = options.hit_factor
90     times = options.times
91
92     progress("Creating source code\n")
93     for i in range(times):
94         with open("%s/%d%s" % (src_dir, i, extension), "w") as fp:
95             with open(source_file) as fp2:
96                 content = fp2.read()
97             fp.write(content)
98             fp.write("\nint ccache_perf_test_%d;\n" % i)
99
100     environment = {"CCACHE_DIR": ccache_dir, "PATH": environ["PATH"]}
101     environment["CCACHE_COMPILERCHECK"] = options.compilercheck
102     if options.compression_level:
103         environment["CCACHE_COMPRESSLEVEL"] = str(options.compression_level)
104     if options.file_clone:
105         environment["CCACHE_FILECLONE"] = "1"
106     if options.hardlink:
107         environment["CCACHE_HARDLINK"] = "1"
108     if options.no_compression:
109         environment["CCACHE_NOCOMPRESS"] = "1"
110     if options.no_cpp2:
111         environment["CCACHE_NOCPP2"] = "1"
112     if options.no_stats:
113         environment["CCACHE_NOSTATS"] = "1"
114
115     results = []
116
117     def run(
118         times, *, use_direct, use_depend, use_ccache=True, print_progress=True
119     ):
120         timings = []
121         for i in range(times):
122             obj = "%s/%d.o" % (obj_dir, i)
123             src = "%s/%d%s" % (src_dir, i, extension)
124             if use_ccache:
125                 args = [options.ccache]
126             else:
127                 args = []
128             args += compiler_args + [obj, src]
129             env = environment.copy()
130             if not use_direct:
131                 env["CCACHE_NODIRECT"] = "1"
132             if use_depend:
133                 env["CCACHE_DEPEND"] = "1"
134             if print_progress:
135                 progress(".")
136             t0 = time()
137             if call(args, env=env) != 0:
138                 sys.stderr.write(
139                     'Error running "%s"; please correct\n' % " ".join(args)
140                 )
141                 sys.exit(1)
142             timings.append(time() - t0)
143         return timings
144
145     # Warm up the disk cache.
146     recreate_dir(ccache_dir)
147     recreate_dir(obj_dir)
148     run(1, use_direct=True, use_depend=False, print_progress=False)
149
150     ###########################################################################
151     # Without ccache
152     recreate_dir(ccache_dir)
153     recreate_dir(obj_dir)
154     progress("Compiling %s\n" % PHASES[0])
155     results.append(
156         run(times, use_direct=False, use_depend=False, use_ccache=False)
157     )
158     progress("\n")
159
160     ###########################################################################
161     # Preprocessor mode
162     recreate_dir(ccache_dir)
163     recreate_dir(obj_dir)
164     progress("Compiling %s\n" % PHASES[1])
165     results.append(run(times, use_direct=False, use_depend=False))
166     progress("\n")
167
168     recreate_dir(obj_dir)
169     progress("Compiling %s\n" % PHASES[2])
170     res = []
171     for j in range(hit_factor):
172         res += run(times, use_direct=False, use_depend=False)
173     results.append(res)
174     progress("\n")
175
176     ###########################################################################
177     # Direct mode
178     recreate_dir(ccache_dir)
179     recreate_dir(obj_dir)
180     progress("Compiling %s\n" % PHASES[3])
181     results.append(run(times, use_direct=True, use_depend=False))
182     progress("\n")
183
184     recreate_dir(obj_dir)
185     progress("Compiling %s\n" % PHASES[4])
186     res = []
187     for j in range(hit_factor):
188         res += run(times, use_direct=True, use_depend=False)
189     results.append(res)
190     progress("\n")
191
192     ###########################################################################
193     # Direct+depend mode
194     recreate_dir(ccache_dir)
195     recreate_dir(obj_dir)
196     progress("Compiling %s\n" % PHASES[5])
197     results.append(run(times, use_direct=True, use_depend=True))
198     progress("\n")
199
200     recreate_dir(obj_dir)
201     progress("Compiling %s\n" % PHASES[6])
202     res = []
203     for j in range(hit_factor):
204         res += run(times, use_direct=True, use_depend=True)
205     results.append(res)
206     progress("\n")
207
208     for i, x in enumerate(results):
209         results[i] = median(x)
210     return results
211
212
213 def print_result_as_text(results):
214     for i, x in enumerate(PHASES):
215         print(
216             "%-43s %6.4f s (%8.4f %%) (%8.4f x)"
217             % (
218                 x.capitalize() + ":",
219                 results[i],
220                 100 * (results[i] / results[0]),
221                 results[0] / results[i],
222             )
223         )
224
225
226 def print_result_as_xml(results):
227     print('<?xml version="1.0" encoding="UTF-8"?>')
228     print("<ccache-perf>")
229     for i, x in enumerate(PHASES):
230         print("<measurement>")
231         print("<name>%s</name>" % x.capitalize())
232         print("<seconds>%.4f</seconds>" % results[i])
233         print("<percent>%.4f</percent>" % (100 * (results[i] / results[0])))
234         print("<times>%.4f</times>" % (results[0] / results[i]))
235         print("</measurement>")
236     print("</ccache-perf>")
237
238
239 def on_off(x):
240     return "on" if x else "off"
241
242
243 def find_in_path(cmd):
244     if isabs(cmd):
245         return cmd
246     else:
247         for path in environ["PATH"].split(":"):
248             p = joinpath(path, cmd)
249             if isfile(p) and access(p, X_OK):
250                 return p
251         return None
252
253
254 def main(argv):
255     op = OptionParser(usage=USAGE, description=DESCRIPTION)
256     op.disable_interspersed_args()
257     op.add_option(
258         "--ccache", help="location of ccache (default: %s)" % DEFAULT_CCACHE
259     )
260     op.add_option(
261         "--compilercheck", help="specify compilercheck (default: mtime)"
262     )
263     op.add_option(
264         "--no-compression", help="disable compression", action="store_true"
265     )
266     op.add_option("--compression-level", help="set compression level", type=int)
267     op.add_option(
268         "-d",
269         "--directory",
270         help=(
271             "where to create the temporary directory with the cache and other"
272             " files (default: %s)" % DEFAULT_DIRECTORY
273         ),
274     )
275     op.add_option("--file-clone", help="use file cloning", action="store_true")
276     op.add_option("--hardlink", help="use hard links", action="store_true")
277     op.add_option(
278         "--hit-factor",
279         help=(
280             "how many times more to compile the file for cache hits (default:"
281             " %d)" % DEFAULT_HIT_FACTOR
282         ),
283         type="int",
284     )
285     op.add_option(
286         "--no-cpp2", help="compile preprocessed output", action="store_true"
287     )
288     op.add_option(
289         "--no-stats", help="don't write statistics", action="store_true"
290     )
291     op.add_option(
292         "-n",
293         "--times",
294         help=(
295             "number of times to compile the file (default: %d)" % DEFAULT_TIMES
296         ),
297         type="int",
298     )
299     op.add_option(
300         "-v", "--verbose", help="print progress messages", action="store_true"
301     )
302     op.add_option("--xml", help="print results as XML", action="store_true")
303     op.set_defaults(
304         ccache=DEFAULT_CCACHE,
305         compilercheck="mtime",
306         directory=DEFAULT_DIRECTORY,
307         hit_factor=DEFAULT_HIT_FACTOR,
308         times=DEFAULT_TIMES,
309     )
310     options, args = op.parse_args(argv[1:])
311     if len(args) < 2:
312         op.error("Missing arguments; pass -h/--help for help")
313
314     global verbose
315     verbose = options.verbose
316
317     options.ccache = abspath(options.ccache)
318
319     compiler = find_in_path(args[0])
320     if compiler is None:
321         op.error("Could not find %s in PATH" % args[0])
322     if "ccache" in basename(realpath(compiler)):
323         op.error(
324             "%s seems to be a symlink to ccache; please specify the path to"
325             " the real compiler instead" % compiler
326         )
327
328     if not options.xml:
329         print(
330             "Compilation command: %s -c -o %s.o"
331             % (" ".join(args), splitext(argv[-1])[0])
332         )
333         print("Compilercheck:", options.compilercheck)
334         print("Compression:", on_off(not options.no_compression))
335         print("Compression level:", options.compression_level or "default")
336         print("File cloning:", on_off(options.file_clone))
337         print("Hard linking:", on_off(options.hardlink))
338         print("No cpp2:", on_off(options.no_cpp2))
339         print("No stats:", on_off(options.no_stats))
340
341     tmp_dir = "%s/perfdir.%d" % (abspath(options.directory), getpid())
342     recreate_dir(tmp_dir)
343     results = test(tmp_dir, options, args[:-1], args[-1])
344     rmtree(tmp_dir)
345     if options.xml:
346         print_result_as_xml(results)
347     else:
348         print_result_as_text(results)
349
350
351 main(sys.argv)