blob: 06aa42756eff64449c88c5dac8f4fa94332866f1 [file] [log] [blame]
Jan Tattermuschb2758442016-03-28 09:32:20 -07001#!/usr/bin/env python2.7
2# Copyright 2016, Google Inc.
3# All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""Run performance tests locally or remotely."""
32
siddharthshukla0589e532016-07-07 16:08:01 +020033from __future__ import print_function
34
Jan Tattermuschb2758442016-03-28 09:32:20 -070035import argparse
Craig Tilleraccf16b2016-09-15 09:08:32 -070036import collections
Jan Tattermuschbb1a4532016-03-30 18:04:01 -070037import itertools
Jan Tattermuschb2758442016-03-28 09:32:20 -070038import jobset
Craig Tiller0bda0b32016-03-03 12:51:53 -080039import json
Jan Tattermuschb2758442016-03-28 09:32:20 -070040import multiprocessing
41import os
Craig Tilleraccf16b2016-09-15 09:08:32 -070042import performance.scenario_config as scenario_config
Craig Tiller0bda0b32016-03-03 12:51:53 -080043import pipes
Jan Tattermusch38becc22016-04-14 08:00:35 -070044import re
Jan Tattermusch94d40cb2016-10-24 21:06:40 +020045import report_utils
Jan Tattermuschb2758442016-03-28 09:32:20 -070046import subprocess
47import sys
48import tempfile
49import time
Jan Tattermuschee9032c2016-04-14 08:35:51 -070050import traceback
Jan Tattermuschb2758442016-03-28 09:32:20 -070051import uuid
Alexander Polcyn9f08d112016-10-24 12:25:02 -070052import report_utils
Jan Tattermuschb2758442016-03-28 09:32:20 -070053
54
55_ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
56os.chdir(_ROOT)
57
58
59_REMOTE_HOST_USERNAME = 'jenkins'
60
61
Jan Tattermuschb2758442016-03-28 09:32:20 -070062class QpsWorkerJob:
63 """Encapsulates a qps worker server job."""
64
Alexander Polcyn9f08d112016-10-24 12:25:02 -070065 def __init__(self, spec, language, host_and_port, perf_file_base_name=None):
Jan Tattermuschb2758442016-03-28 09:32:20 -070066 self._spec = spec
Jan Tattermuschbb1a4532016-03-30 18:04:01 -070067 self.language = language
Jan Tattermuschb2758442016-03-28 09:32:20 -070068 self.host_and_port = host_and_port
Craig Tillerc1b54f22016-09-15 08:57:14 -070069 self._job = None
Alexander Polcyn9f08d112016-10-24 12:25:02 -070070 self.perf_file_base_name = perf_file_base_name
Craig Tillerc1b54f22016-09-15 08:57:14 -070071
72 def start(self):
Craig Tillerc197ec12016-09-15 09:19:33 -070073 self._job = jobset.Job(self._spec, newline_on_success=True, travis=True, add_env={})
Jan Tattermuschb2758442016-03-28 09:32:20 -070074
75 def is_running(self):
76 """Polls a job and returns True if given job is still running."""
Craig Tillerc1b54f22016-09-15 08:57:14 -070077 return self._job and self._job.state() == jobset._RUNNING
Jan Tattermuschb2758442016-03-28 09:32:20 -070078
79 def kill(self):
Craig Tillerc1b54f22016-09-15 08:57:14 -070080 if self._job:
81 self._job.kill()
82 self._job = None
Jan Tattermuschb2758442016-03-28 09:32:20 -070083
84
Alexander Polcyn9f08d112016-10-24 12:25:02 -070085def create_qpsworker_job(language, shortname=None, port=10000, remote_host=None, perf_cmd=None):
86 cmdline = (language.worker_cmdline() + ['--driver_port=%s' % port])
87
Jan Tattermuschb2758442016-03-28 09:32:20 -070088 if remote_host:
Jan Tattermuschb2758442016-03-28 09:32:20 -070089 host_and_port='%s:%s' % (remote_host, port)
90 else:
91 host_and_port='localhost:%s' % port
92
Alexander Polcyn9f08d112016-10-24 12:25:02 -070093 perf_file_base_name = None
94 if perf_cmd:
95 perf_file_base_name = '%s-%s' % (host_and_port, shortname)
96 # specify -o output file so perf.data gets collected when worker stopped
97 cmdline = perf_cmd + ['-o', '%s-perf.data' % perf_file_base_name] + cmdline
98
99 if remote_host:
100 user_at_host = '%s@%s' % (_REMOTE_HOST_USERNAME, remote_host)
101 ssh_cmd = ['ssh']
102 ssh_cmd.extend([str(user_at_host), 'cd ~/performance_workspace/grpc/ && %s' % ' '.join(cmdline)])
103 cmdline = ssh_cmd
104
Jan Tattermuschb2758442016-03-28 09:32:20 -0700105 jobspec = jobset.JobSpec(
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700106 cmdline=cmdline,
107 shortname=shortname,
Jan Tattermusch447548b2016-10-17 12:04:56 +0200108 timeout_seconds=5*60, # workers get restarted after each scenario
109 verbose_success=True)
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700110 return QpsWorkerJob(jobspec, language, host_and_port, perf_file_base_name)
Jan Tattermuschb2758442016-03-28 09:32:20 -0700111
112
Jan Tattermusch6d7fa552016-04-14 17:42:54 -0700113def create_scenario_jobspec(scenario_json, workers, remote_host=None,
114 bq_result_table=None):
Jan Tattermuschb2758442016-03-28 09:32:20 -0700115 """Runs one scenario using QPS driver."""
116 # setting QPS_WORKERS env variable here makes sure it works with SSH too.
Jan Tattermusch6d7fa552016-04-14 17:42:54 -0700117 cmd = 'QPS_WORKERS="%s" ' % ','.join(workers)
118 if bq_result_table:
119 cmd += 'BQ_RESULT_TABLE="%s" ' % bq_result_table
120 cmd += 'tools/run_tests/performance/run_qps_driver.sh '
121 cmd += '--scenarios_json=%s ' % pipes.quote(json.dumps({'scenarios': [scenario_json]}))
Vijay Paic23d33b2016-07-19 11:19:12 -0700122 cmd += '--scenario_result_file=scenario_result.json'
Jan Tattermuschb2758442016-03-28 09:32:20 -0700123 if remote_host:
124 user_at_host = '%s@%s' % (_REMOTE_HOST_USERNAME, remote_host)
Jan Tattermusch6d7fa552016-04-14 17:42:54 -0700125 cmd = 'ssh %s "cd ~/performance_workspace/grpc/ && "%s' % (user_at_host, pipes.quote(cmd))
Jan Tattermuschb2758442016-03-28 09:32:20 -0700126
127 return jobset.JobSpec(
128 cmdline=[cmd],
Craig Tiller0bda0b32016-03-03 12:51:53 -0800129 shortname='qps_json_driver.%s' % scenario_json['name'],
130 timeout_seconds=3*60,
131 shell=True,
132 verbose_success=True)
133
134
135def create_quit_jobspec(workers, remote_host=None):
136 """Runs quit using QPS driver."""
137 # setting QPS_WORKERS env variable here makes sure it works with SSH too.
Craig Tiller025972d2016-09-15 09:26:50 -0700138 cmd = 'QPS_WORKERS="%s" bins/opt/qps_json_driver --quit' % ','.join(w.host_and_port for w in workers)
Craig Tiller0bda0b32016-03-03 12:51:53 -0800139 if remote_host:
140 user_at_host = '%s@%s' % (_REMOTE_HOST_USERNAME, remote_host)
Jan Tattermusch6d7fa552016-04-14 17:42:54 -0700141 cmd = 'ssh %s "cd ~/performance_workspace/grpc/ && "%s' % (user_at_host, pipes.quote(cmd))
Craig Tiller0bda0b32016-03-03 12:51:53 -0800142
143 return jobset.JobSpec(
144 cmdline=[cmd],
vjpai29089c72016-04-20 12:38:16 -0700145 shortname='qps_json_driver.quit',
Jan Tattermuschb2758442016-03-28 09:32:20 -0700146 timeout_seconds=3*60,
147 shell=True,
148 verbose_success=True)
149
150
Jan Tattermusch4de2c322016-05-10 14:33:07 -0700151def create_netperf_jobspec(server_host='localhost', client_host=None,
152 bq_result_table=None):
153 """Runs netperf benchmark."""
154 cmd = 'NETPERF_SERVER_HOST="%s" ' % server_host
155 if bq_result_table:
156 cmd += 'BQ_RESULT_TABLE="%s" ' % bq_result_table
Jan Tattermuschad17bf72016-05-11 12:41:37 -0700157 if client_host:
158 # If netperf is running remotely, the env variables populated by Jenkins
159 # won't be available on the client, but we need them for uploading results
160 # to BigQuery.
161 jenkins_job_name = os.getenv('JOB_NAME')
162 if jenkins_job_name:
163 cmd += 'JOB_NAME="%s" ' % jenkins_job_name
164 jenkins_build_number = os.getenv('BUILD_NUMBER')
165 if jenkins_build_number:
166 cmd += 'BUILD_NUMBER="%s" ' % jenkins_build_number
167
Jan Tattermusch4de2c322016-05-10 14:33:07 -0700168 cmd += 'tools/run_tests/performance/run_netperf.sh'
169 if client_host:
170 user_at_host = '%s@%s' % (_REMOTE_HOST_USERNAME, client_host)
171 cmd = 'ssh %s "cd ~/performance_workspace/grpc/ && "%s' % (user_at_host, pipes.quote(cmd))
172
173 return jobset.JobSpec(
174 cmdline=[cmd],
175 shortname='netperf',
176 timeout_seconds=60,
177 shell=True,
178 verbose_success=True)
179
180
Jan Tattermuschde874a12016-04-18 09:21:37 -0700181def archive_repo(languages):
Jan Tattermuschb2758442016-03-28 09:32:20 -0700182 """Archives local version of repo including submodules."""
Jan Tattermuschde874a12016-04-18 09:21:37 -0700183 cmdline=['tar', '-cf', '../grpc.tar', '../grpc/']
184 if 'java' in languages:
185 cmdline.append('../grpc-java')
186 if 'go' in languages:
187 cmdline.append('../grpc-go')
188
Jan Tattermuschb2758442016-03-28 09:32:20 -0700189 archive_job = jobset.JobSpec(
Jan Tattermuschde874a12016-04-18 09:21:37 -0700190 cmdline=cmdline,
Jan Tattermuschb2758442016-03-28 09:32:20 -0700191 shortname='archive_repo',
192 timeout_seconds=3*60)
193
194 jobset.message('START', 'Archiving local repository.', do_newline=True)
195 num_failures, _ = jobset.run(
196 [archive_job], newline_on_success=True, maxjobs=1)
197 if num_failures == 0:
198 jobset.message('SUCCESS',
Jan Tattermuschde874a12016-04-18 09:21:37 -0700199 'Archive with local repository created successfully.',
Jan Tattermuschb2758442016-03-28 09:32:20 -0700200 do_newline=True)
201 else:
202 jobset.message('FAILED', 'Failed to archive local repository.',
203 do_newline=True)
204 sys.exit(1)
205
206
Jan Tattermusch14089202016-04-27 17:55:27 -0700207def prepare_remote_hosts(hosts, prepare_local=False):
208 """Prepares remote hosts (and maybe prepare localhost as well)."""
209 prepare_timeout = 5*60
Jan Tattermuschb2758442016-03-28 09:32:20 -0700210 prepare_jobs = []
211 for host in hosts:
212 user_at_host = '%s@%s' % (_REMOTE_HOST_USERNAME, host)
213 prepare_jobs.append(
214 jobset.JobSpec(
215 cmdline=['tools/run_tests/performance/remote_host_prepare.sh'],
216 shortname='remote_host_prepare.%s' % host,
217 environ = {'USER_AT_HOST': user_at_host},
Jan Tattermusch14089202016-04-27 17:55:27 -0700218 timeout_seconds=prepare_timeout))
219 if prepare_local:
220 # Prepare localhost as well
221 prepare_jobs.append(
222 jobset.JobSpec(
223 cmdline=['tools/run_tests/performance/kill_workers.sh'],
224 shortname='local_prepare',
225 timeout_seconds=prepare_timeout))
226 jobset.message('START', 'Preparing hosts.', do_newline=True)
Jan Tattermuschb2758442016-03-28 09:32:20 -0700227 num_failures, _ = jobset.run(
228 prepare_jobs, newline_on_success=True, maxjobs=10)
229 if num_failures == 0:
230 jobset.message('SUCCESS',
Jan Tattermusch14089202016-04-27 17:55:27 -0700231 'Prepare step completed successfully.',
Jan Tattermuschb2758442016-03-28 09:32:20 -0700232 do_newline=True)
233 else:
234 jobset.message('FAILED', 'Failed to prepare remote hosts.',
235 do_newline=True)
236 sys.exit(1)
237
238
Craig Tillerd92d5c52016-04-04 13:49:29 -0700239def build_on_remote_hosts(hosts, languages=scenario_config.LANGUAGES.keys(), build_local=False):
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700240 """Builds performance worker on remote hosts (and maybe also locally)."""
Jan Tattermuschb2758442016-03-28 09:32:20 -0700241 build_timeout = 15*60
242 build_jobs = []
243 for host in hosts:
244 user_at_host = '%s@%s' % (_REMOTE_HOST_USERNAME, host)
245 build_jobs.append(
246 jobset.JobSpec(
Craig Tiller7797e3f2016-04-01 07:41:05 -0700247 cmdline=['tools/run_tests/performance/remote_host_build.sh'] + languages,
Jan Tattermuschb2758442016-03-28 09:32:20 -0700248 shortname='remote_host_build.%s' % host,
249 environ = {'USER_AT_HOST': user_at_host, 'CONFIG': 'opt'},
250 timeout_seconds=build_timeout))
251 if build_local:
252 # Build locally as well
253 build_jobs.append(
254 jobset.JobSpec(
Craig Tiller7797e3f2016-04-01 07:41:05 -0700255 cmdline=['tools/run_tests/performance/build_performance.sh'] + languages,
Jan Tattermuschb2758442016-03-28 09:32:20 -0700256 shortname='local_build',
257 environ = {'CONFIG': 'opt'},
258 timeout_seconds=build_timeout))
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700259 jobset.message('START', 'Building.', do_newline=True)
Jan Tattermuschb2758442016-03-28 09:32:20 -0700260 num_failures, _ = jobset.run(
261 build_jobs, newline_on_success=True, maxjobs=10)
262 if num_failures == 0:
263 jobset.message('SUCCESS',
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700264 'Built successfully.',
Jan Tattermuschb2758442016-03-28 09:32:20 -0700265 do_newline=True)
266 else:
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700267 jobset.message('FAILED', 'Build failed.',
Jan Tattermuschb2758442016-03-28 09:32:20 -0700268 do_newline=True)
269 sys.exit(1)
270
271
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700272def create_qpsworkers(languages, worker_hosts, perf_cmd=None):
Craig Tillerc1b54f22016-09-15 08:57:14 -0700273 """Creates QPS workers (but does not start them)."""
Jan Tattermuschb2758442016-03-28 09:32:20 -0700274 if not worker_hosts:
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700275 # run two workers locally (for each language)
Jan Tattermuschb2758442016-03-28 09:32:20 -0700276 workers=[(None, 10000), (None, 10010)]
277 elif len(worker_hosts) == 1:
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700278 # run two workers on the remote host (for each language)
Jan Tattermuschb2758442016-03-28 09:32:20 -0700279 workers=[(worker_hosts[0], 10000), (worker_hosts[0], 10010)]
280 else:
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700281 # run one worker per each remote host (for each language)
Jan Tattermuschb2758442016-03-28 09:32:20 -0700282 workers=[(worker_host, 10000) for worker_host in worker_hosts]
283
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700284 return [create_qpsworker_job(language,
285 shortname= 'qps_worker_%s_%s' % (language,
286 worker_idx),
287 port=worker[1] + language.worker_port_offset(),
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700288 remote_host=worker[0],
289 perf_cmd=perf_cmd)
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700290 for language in languages
291 for worker_idx, worker in enumerate(workers)]
Jan Tattermuschb2758442016-03-28 09:32:20 -0700292
293
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700294def perf_report_processor_job(worker_host, perf_base_name, output_filename):
295 print('Creating perf report collection job for %s' % worker_host)
296 cmd = ''
297 if worker_host != 'localhost':
298 user_at_host = "%s@%s" % (_REMOTE_HOST_USERNAME, worker_host)
299 cmd = "USER_AT_HOST=%s OUTPUT_FILENAME=%s OUTPUT_DIR=%s PERF_BASE_NAME=%s\
300 tools/run_tests/performance/process_remote_perf_flamegraphs.sh" \
Alexander Polcyn66c67822016-12-09 10:22:50 -0800301 % (user_at_host, output_filename, args.flame_graph_reports, perf_base_name)
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700302 else:
303 cmd = "OUTPUT_FILENAME=%s OUTPUT_DIR=%s PERF_BASE_NAME=%s\
304 tools/run_tests/performance/process_local_perf_flamegraphs.sh" \
Alexander Polcyn66c67822016-12-09 10:22:50 -0800305 % (output_filename, args.flame_graph_reports, perf_base_name)
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700306
307 return jobset.JobSpec(cmdline=cmd,
308 timeout_seconds=3*60,
309 shell=True,
310 verbose_success=True,
311 shortname='process perf report')
312
313
Craig Tiller677966a2016-09-26 07:37:28 -0700314Scenario = collections.namedtuple('Scenario', 'jobspec workers name')
Craig Tillerc1b54f22016-09-15 08:57:14 -0700315
316
Jan Tattermusch6d7fa552016-04-14 17:42:54 -0700317def create_scenarios(languages, workers_by_lang, remote_host=None, regex='.*',
Jan Tattermusch4de2c322016-05-10 14:33:07 -0700318 category='all', bq_result_table=None,
319 netperf=False, netperf_hosts=[]):
Jan Tattermuschb2758442016-03-28 09:32:20 -0700320 """Create jobspecs for scenarios to run."""
Ken Payson0482c102016-04-19 12:08:34 -0700321 all_workers = [worker
322 for workers in workers_by_lang.values()
323 for worker in workers]
Jan Tattermuschb2758442016-03-28 09:32:20 -0700324 scenarios = []
Craig Tillerc1b54f22016-09-15 08:57:14 -0700325 _NO_WORKERS = []
Jan Tattermusch4de2c322016-05-10 14:33:07 -0700326
327 if netperf:
328 if not netperf_hosts:
329 netperf_server='localhost'
330 netperf_client=None
331 elif len(netperf_hosts) == 1:
332 netperf_server=netperf_hosts[0]
333 netperf_client=netperf_hosts[0]
334 else:
335 netperf_server=netperf_hosts[0]
336 netperf_client=netperf_hosts[1]
Craig Tillerc1b54f22016-09-15 08:57:14 -0700337 scenarios.append(Scenario(
338 create_netperf_jobspec(server_host=netperf_server,
339 client_host=netperf_client,
340 bq_result_table=bq_result_table),
Craig Tiller677966a2016-09-26 07:37:28 -0700341 _NO_WORKERS, 'netperf'))
Jan Tattermusch4de2c322016-05-10 14:33:07 -0700342
Jan Tattermuschb2758442016-03-28 09:32:20 -0700343 for language in languages:
Craig Tiller0bda0b32016-03-03 12:51:53 -0800344 for scenario_json in language.scenarios():
Jan Tattermusch38becc22016-04-14 08:00:35 -0700345 if re.search(args.regex, scenario_json['name']):
Craig Tillerb6df2472016-09-13 09:41:26 -0700346 categories = scenario_json.get('CATEGORIES', ['scalable', 'smoketest'])
347 if category in categories or category == 'all':
Craig Tillerc1b54f22016-09-15 08:57:14 -0700348 workers = workers_by_lang[str(language)][:]
Jan Tattermusch427699b2016-05-05 18:10:14 -0700349 # 'SERVER_LANGUAGE' is an indicator for this script to pick
350 # a server in different language.
351 custom_server_lang = scenario_json.get('SERVER_LANGUAGE', None)
Jan Tattermusch37a907e2016-05-13 13:49:43 -0700352 custom_client_lang = scenario_json.get('CLIENT_LANGUAGE', None)
Jan Tattermusch427699b2016-05-05 18:10:14 -0700353 scenario_json = scenario_config.remove_nonproto_fields(scenario_json)
Jan Tattermusch37a907e2016-05-13 13:49:43 -0700354 if custom_server_lang and custom_client_lang:
355 raise Exception('Cannot set both custom CLIENT_LANGUAGE and SERVER_LANGUAGE'
356 'in the same scenario')
Jan Tattermusch427699b2016-05-05 18:10:14 -0700357 if custom_server_lang:
358 if not workers_by_lang.get(custom_server_lang, []):
siddharthshukla0589e532016-07-07 16:08:01 +0200359 print('Warning: Skipping scenario %s as' % scenario_json['name'])
Jan Tattermusch427699b2016-05-05 18:10:14 -0700360 print('SERVER_LANGUAGE is set to %s yet the language has '
361 'not been selected with -l' % custom_server_lang)
362 continue
363 for idx in range(0, scenario_json['num_servers']):
364 # replace first X workers by workers of a different language
365 workers[idx] = workers_by_lang[custom_server_lang][idx]
Jan Tattermusch37a907e2016-05-13 13:49:43 -0700366 if custom_client_lang:
367 if not workers_by_lang.get(custom_client_lang, []):
siddharthshukla0589e532016-07-07 16:08:01 +0200368 print('Warning: Skipping scenario %s as' % scenario_json['name'])
Jan Tattermusch37a907e2016-05-13 13:49:43 -0700369 print('CLIENT_LANGUAGE is set to %s yet the language has '
370 'not been selected with -l' % custom_client_lang)
371 continue
372 for idx in range(scenario_json['num_servers'], len(workers)):
373 # replace all client workers by workers of a different language,
374 # leave num_server workers as they are server workers.
375 workers[idx] = workers_by_lang[custom_client_lang][idx]
Craig Tillerc1b54f22016-09-15 08:57:14 -0700376 scenario = Scenario(
377 create_scenario_jobspec(scenario_json,
378 [w.host_and_port for w in workers],
379 remote_host=remote_host,
380 bq_result_table=bq_result_table),
Craig Tiller677966a2016-09-26 07:37:28 -0700381 workers,
382 scenario_json['name'])
Jan Tattermusch427699b2016-05-05 18:10:14 -0700383 scenarios.append(scenario)
Jan Tattermuschb2758442016-03-28 09:32:20 -0700384
Jan Tattermuschb2758442016-03-28 09:32:20 -0700385 return scenarios
386
387
388def finish_qps_workers(jobs):
389 """Waits for given jobs to finish and eventually kills them."""
390 retries = 0
Alexander Polcyn49796672016-10-17 10:01:37 -0700391 num_killed = 0
Jan Tattermuschb2758442016-03-28 09:32:20 -0700392 while any(job.is_running() for job in jobs):
393 for job in qpsworker_jobs:
394 if job.is_running():
siddharthshukla0589e532016-07-07 16:08:01 +0200395 print('QPS worker "%s" is still running.' % job.host_and_port)
Jan Tattermuschb2758442016-03-28 09:32:20 -0700396 if retries > 10:
siddharthshukla0589e532016-07-07 16:08:01 +0200397 print('Killing all QPS workers.')
Jan Tattermuschb2758442016-03-28 09:32:20 -0700398 for job in jobs:
399 job.kill()
Alexander Polcyn49796672016-10-17 10:01:37 -0700400 num_killed += 1
Jan Tattermuschb2758442016-03-28 09:32:20 -0700401 retries += 1
402 time.sleep(3)
siddharthshukla0589e532016-07-07 16:08:01 +0200403 print('All QPS workers finished.')
Alexander Polcyn49796672016-10-17 10:01:37 -0700404 return num_killed
Jan Tattermuschb2758442016-03-28 09:32:20 -0700405
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700406profile_output_files = []
407
408# Collect perf text reports and flamegraphs if perf_cmd was used
409# Note the base names of perf text reports are used when creating and processing
410# perf data. The scenario name uniqifies the output name in the final
411# perf reports directory.
412# Alos, the perf profiles need to be fetched and processed after each scenario
413# in order to avoid clobbering the output files.
414def run_collect_perf_profile_jobs(hosts_and_base_names, scenario_name):
415 perf_report_jobs = []
416 global profile_output_files
417 for host_and_port in hosts_and_base_names:
418 perf_base_name = hosts_and_base_names[host_and_port]
419 output_filename = '%s-%s' % (scenario_name, perf_base_name)
420 # from the base filename, create .svg output filename
421 host = host_and_port.split(':')[0]
422 profile_output_files.append('%s.svg' % output_filename)
423 perf_report_jobs.append(perf_report_processor_job(host, perf_base_name, output_filename))
424
425 jobset.message('START', 'Collecting perf reports from qps workers', do_newline=True)
426 failures, _ = jobset.run(perf_report_jobs, newline_on_success=True, maxjobs=1)
427 jobset.message('END', 'Collecting perf reports from qps workers', do_newline=True)
428 return failures
429
430
Jan Tattermuschb2758442016-03-28 09:32:20 -0700431argp = argparse.ArgumentParser(description='Run performance tests.')
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700432argp.add_argument('-l', '--language',
Craig Tillerd92d5c52016-04-04 13:49:29 -0700433 choices=['all'] + sorted(scenario_config.LANGUAGES.keys()),
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700434 nargs='+',
Jan Tattermusch427699b2016-05-05 18:10:14 -0700435 required=True,
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700436 help='Languages to benchmark.')
Jan Tattermuschb2758442016-03-28 09:32:20 -0700437argp.add_argument('--remote_driver_host',
438 default=None,
439 help='Run QPS driver on given host. By default, QPS driver is run locally.')
440argp.add_argument('--remote_worker_host',
441 nargs='+',
442 default=[],
443 help='Worker hosts where to start QPS workers.')
Craig Tiller677966a2016-09-26 07:37:28 -0700444argp.add_argument('--dry_run',
445 default=False,
446 action='store_const',
447 const=True,
448 help='Just list scenarios to be run, but don\'t run them.')
Jan Tattermusch38becc22016-04-14 08:00:35 -0700449argp.add_argument('-r', '--regex', default='.*', type=str,
450 help='Regex to select scenarios to run.')
Jan Tattermusch6d7fa552016-04-14 17:42:54 -0700451argp.add_argument('--bq_result_table', default=None, type=str,
452 help='Bigquery "dataset.table" to upload results to.')
Jan Tattermusch427699b2016-05-05 18:10:14 -0700453argp.add_argument('--category',
Craig Tiller6388da52016-09-07 17:06:29 -0700454 choices=['smoketest','all','scalable','sweep'],
Jan Tattermusch37a907e2016-05-13 13:49:43 -0700455 default='all',
456 help='Select a category of tests to run.')
Jan Tattermusch4de2c322016-05-10 14:33:07 -0700457argp.add_argument('--netperf',
458 default=False,
459 action='store_const',
460 const=True,
461 help='Run netperf benchmark as one of the scenarios.')
Jan Tattermusch88818ae2016-11-18 14:21:33 +0100462argp.add_argument('-x', '--xml_report', default='report.xml', type=str,
463 help='Name of XML report file to generate.')
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700464argp.add_argument('--perf_args',
465 help=('Example usage: "--perf_args=record -F 99 -g". '
466 'Wrap QPS workers in a perf command '
467 'with the arguments to perf specified here. '
468 '".svg" flame graph profiles will be '
469 'created for each Qps Worker on each scenario. '
Alexander Polcyn66c67822016-12-09 10:22:50 -0800470 'Files will output to "<repo_root>/<args.flame_graph_reports>" '
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700471 'directory. Output files from running the worker '
472 'under perf are saved in the repo root where its ran. '
473 'Note that the perf "-g" flag is necessary for '
474 'flame graphs generation to work (assuming the binary '
475 'being profiled uses frame pointers, check out '
476 '"--call-graph dwarf" option using libunwind otherwise.) '
477 'Also note that the entire "--perf_args=<arg(s)>" must '
478 'be wrapped in quotes as in the example usage. '
479 'If the "--perg_args" is unspecified, "perf" will '
480 'not be used at all. '
481 'See http://www.brendangregg.com/perf.html '
482 'for more general perf examples.'))
483argp.add_argument('--skip_generate_flamegraphs',
484 default=False,
485 action='store_const',
486 const=True,
487 help=('Turn flame graph generation off. '
488 'May be useful if "perf_args" arguments do not make sense for '
489 'generating flamegraphs (e.g., "--perf_args=stat ...")'))
Alexander Polcyn66c67822016-12-09 10:22:50 -0800490argp.add_argument('-f', '--flame_graph_reports', default='perf_reports', type=str,
491 help='Name of directory to output flame graph profiles to, if any are created.')
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700492
Jan Tattermuschb2758442016-03-28 09:32:20 -0700493
494args = argp.parse_args()
495
Craig Tillerd92d5c52016-04-04 13:49:29 -0700496languages = set(scenario_config.LANGUAGES[l]
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700497 for l in itertools.chain.from_iterable(
Craig Tillerd92d5c52016-04-04 13:49:29 -0700498 scenario_config.LANGUAGES.iterkeys() if x == 'all' else [x]
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700499 for x in args.language))
500
Jan Tattermusch6d7fa552016-04-14 17:42:54 -0700501
Jan Tattermuschb2758442016-03-28 09:32:20 -0700502# Put together set of remote hosts where to run and build
503remote_hosts = set()
504if args.remote_worker_host:
505 for host in args.remote_worker_host:
506 remote_hosts.add(host)
507if args.remote_driver_host:
508 remote_hosts.add(args.remote_driver_host)
509
Craig Tiller677966a2016-09-26 07:37:28 -0700510if not args.dry_run:
511 if remote_hosts:
512 archive_repo(languages=[str(l) for l in languages])
513 prepare_remote_hosts(remote_hosts, prepare_local=True)
514 else:
515 prepare_remote_hosts([], prepare_local=True)
Jan Tattermuschb2758442016-03-28 09:32:20 -0700516
517build_local = False
518if not args.remote_driver_host:
519 build_local = True
Craig Tiller677966a2016-09-26 07:37:28 -0700520if not args.dry_run:
521 build_on_remote_hosts(remote_hosts, languages=[str(l) for l in languages], build_local=build_local)
Jan Tattermuschb2758442016-03-28 09:32:20 -0700522
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700523perf_cmd = None
524if args.perf_args:
Alexander Polcyn66c67822016-12-09 10:22:50 -0800525 print('Running workers under perf profiler')
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700526 # Expect /usr/bin/perf to be installed here, as is usual
Alexander Polcyn66c67822016-12-09 10:22:50 -0800527 perf_cmd = ['/usr/bin/perf']
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700528 perf_cmd.extend(re.split('\s+', args.perf_args))
529
530qpsworker_jobs = create_qpsworkers(languages, args.remote_worker_host, perf_cmd=perf_cmd)
Jan Tattermusch38becc22016-04-14 08:00:35 -0700531
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700532# get list of worker addresses for each language.
Craig Tillerc1b54f22016-09-15 08:57:14 -0700533workers_by_lang = dict([(str(language), []) for language in languages])
Jan Tattermuschbb1a4532016-03-30 18:04:01 -0700534for job in qpsworker_jobs:
Craig Tillerc1b54f22016-09-15 08:57:14 -0700535 workers_by_lang[str(job.language)].append(job)
Jan Tattermuschb2758442016-03-28 09:32:20 -0700536
Craig Tillerc1b54f22016-09-15 08:57:14 -0700537scenarios = create_scenarios(languages,
Craig Tillerd82fccc2016-09-15 09:15:23 -0700538 workers_by_lang=workers_by_lang,
Craig Tillerc1b54f22016-09-15 08:57:14 -0700539 remote_host=args.remote_driver_host,
540 regex=args.regex,
541 category=args.category,
542 bq_result_table=args.bq_result_table,
543 netperf=args.netperf,
544 netperf_hosts=args.remote_worker_host)
Jan Tattermusch4de2c322016-05-10 14:33:07 -0700545
Craig Tillerc1b54f22016-09-15 08:57:14 -0700546if not scenarios:
547 raise Exception('No scenarios to run')
Jan Tattermuschb2758442016-03-28 09:32:20 -0700548
Alex Polcyncac93f62016-10-19 09:27:57 -0700549total_scenario_failures = 0
Alexander Polcyn898a2e92016-10-22 17:41:23 -0700550qps_workers_killed = 0
Jan Tattermusch94d40cb2016-10-24 21:06:40 +0200551merged_resultset = {}
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700552perf_report_failures = 0
553
Craig Tillerc1b54f22016-09-15 08:57:14 -0700554for scenario in scenarios:
Craig Tiller677966a2016-09-26 07:37:28 -0700555 if args.dry_run:
556 print(scenario.name)
557 else:
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700558 scenario_failures = 0
Craig Tiller677966a2016-09-26 07:37:28 -0700559 try:
560 for worker in scenario.workers:
561 worker.start()
Alex Polcynfcf09ea2016-12-06 04:00:05 +0000562 jobs = [scenario.jobspec]
Alex Polcynca5e9242016-12-06 04:21:37 +0000563 if scenario.workers:
Alex Polcynfcf09ea2016-12-06 04:00:05 +0000564 jobs.append(create_quit_jobspec(scenario.workers, remote_host=args.remote_driver_host))
565 scenario_failures, resultset = jobset.run(jobs, newline_on_success=True, maxjobs=1)
Alex Polcyncac93f62016-10-19 09:27:57 -0700566 total_scenario_failures += scenario_failures
Jan Tattermusch94d40cb2016-10-24 21:06:40 +0200567 merged_resultset = dict(itertools.chain(merged_resultset.iteritems(),
568 resultset.iteritems()))
Craig Tiller677966a2016-09-26 07:37:28 -0700569 finally:
Alexander Polcyn898a2e92016-10-22 17:41:23 -0700570 # Consider qps workers that need to be killed as failures
571 qps_workers_killed += finish_qps_workers(scenario.workers)
Alexander Polcyn49796672016-10-17 10:01:37 -0700572
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700573 if perf_cmd and scenario_failures == 0 and not args.skip_generate_flamegraphs:
574 workers_and_base_names = {}
575 for worker in scenario.workers:
576 if not worker.perf_file_base_name:
577 raise Exception('using perf buf perf report filename is unspecified')
578 workers_and_base_names[worker.host_and_port] = worker.perf_file_base_name
579 perf_report_failures += run_collect_perf_profile_jobs(workers_and_base_names, scenario.name)
580
581
582# Still write the index.html even if some scenarios failed.
583# 'profile_output_files' will only have names for scenarios that passed
584if perf_cmd and not args.skip_generate_flamegraphs:
585 # write the index fil to the output dir, with all profiles from all scenarios/workers
Alexander Polcyn66c67822016-12-09 10:22:50 -0800586 report_utils.render_perf_profiling_results('%s/index.html' % args.flame_graph_reports, profile_output_files)
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700587
588if total_scenario_failures > 0 or qps_workers_killed > 0:
589 print('%s scenarios failed and %s qps worker jobs killed' % (total_scenario_failures, qps_workers_killed))
590 sys.exit(1)
Jan Tattermusch94d40cb2016-10-24 21:06:40 +0200591
Jan Tattermusch88818ae2016-11-18 14:21:33 +0100592report_utils.render_junit_xml_report(merged_resultset, args.xml_report,
Jan Tattermusch94d40cb2016-10-24 21:06:40 +0200593 suite_name='benchmarks')
Alexander Polcyn9f08d112016-10-24 12:25:02 -0700594if perf_report_failures > 0:
595 print('%s perf profile collection jobs failed' % perf_report_failures)
Alex Polcyncac93f62016-10-19 09:27:57 -0700596 sys.exit(1)