Skip to content

Commit ed05ed7

Browse files
Support adhoc benchmarks
With this commit we add a new mode to night-rally: adhoc. It allows to run our benchmark suite for arbitrary commit hashes and publish the results. night-rally now also records the results of release-benchmarks in the ES metrics store. Closes elastic#20
1 parent 9e03272 commit ed05ed7

File tree

5 files changed

+161
-20
lines changed

5 files changed

+161
-20
lines changed

README.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,29 @@ For this three steps are needed:
4949
2. Adjust the menu structure in all other files (if this happens more often, we should think about using a template engine for that...)
5050
3. Add your track and the challenges to run in the `tracks` hash in `night_rally.py`
5151

52-
If you're finished, please submit a PR. After the PR is merged, the new track will show up after the next benchmark.
52+
If you're finished, please submit a PR. After the PR is merged, the new track will show up after the next benchmark.
53+
54+
55+
#### Run a release benchmark
56+
57+
Suppose we want to replace the (already published) results of the Elasticsearch release `5.3.0` with release `5.3.1` on our benchmark page.
58+
59+
1. Replace "5.3.0" with "5.3.1" in the `versions` array in each `index.html` in `external/pages`. Commit and push your changes (commit message convention: "Update comparison charts to 5.3.1")
60+
2. On the benchmark machine, issue the following command:
61+
62+
```
63+
night_rally.sh --target-host=target-551504.benchmark.hetzner-dc17.elasticnet.co:39200 --mode=comparison --release="5.3.1" --replace-release="5.3.0"
64+
```
65+
66+
#### Run an ad-hoc benchmark
67+
68+
_Note: An ad-hoc benchmark is a benchmark of an arbitrary git commit hash that should be published as a separate data series in our release comparison charts._
69+
70+
Suppose we want to publish the results of the commit hash `66202dc` in the Elasticsearch repo as "Lucene 7 upgrade" on our benchmark page.
71+
72+
1. Add "Lucene 7 upgrade" to the `versions` array in each `index.html` in `external/pages`. Commit and push your changes.
73+
2. On the benchmark machine, issue the following command:
74+
75+
```
76+
night_rally.sh --target-host=target-551504.benchmark.hetzner-dc17.elasticnet.co:39200 --mode=adhoc --revision=66202dc --release="Lucene 7" --replace-release="Lucene 7
77+
```

night_rally.py

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -114,19 +114,30 @@ def ensure_dir(directory):
114114
raise
115115

116116

117-
def configure_rally(dry_run):
117+
def sanitize(text):
118+
"""
119+
Sanitizes the input text so it is safe to use as an environment name in Rally.
120+
121+
:param text: A text to sanitize
122+
"""
123+
return text.lower().replace(" ", "-").replace(".", "_")
124+
125+
126+
def configure_rally(configuration_name, dry_run):
118127
user_home = os.getenv("HOME")
119128
root = os.path.dirname(os.path.realpath(__file__))
120-
source = "%s/resources/rally-nightly.ini" % root
121-
destination = "%s/.rally/rally-nightly.ini" % user_home
129+
source = "%s/resources/rally-template.ini" % root
130+
destination = "%s/.rally/rally-%s.ini" % (user_home, configuration_name)
122131
logger.info("Copying rally configuration from [%s] to [%s]" % (source, destination))
123132
if not dry_run:
124133
ensure_dir("%s/.rally" % user_home)
125134
shutil.copyfile(source, destination)
126-
# materialize current user home
135+
# materialize current user home and set environment name
127136
with fileinput.input(files=destination, inplace=True) as f:
128137
for line in f:
129-
print(line.replace("~", user_home))
138+
print(line
139+
.replace("~", user_home)
140+
.replace("<<ENVIRONMENT>>", configuration_name))
130141

131142

132143
class BaseCommand:
@@ -149,37 +160,52 @@ def command_line(self, track, challenge, car):
149160
raise NotImplementedError("abstract method")
150161

151162

152-
class NightlyCommand(BaseCommand):
153-
def __init__(self, effective_start_date, target_host, root_dir, override_src_dir=None):
163+
class SourceBasedCommand(BaseCommand):
164+
def __init__(self, effective_start_date, target_host, root_dir, revision, configuration_name, override_src_dir=None):
154165
super().__init__(effective_start_date, target_host, root_dir)
155-
self.revision_ts = to_iso8601(effective_start_date)
166+
self.revision = revision
167+
self.configuration_name = configuration_name
156168
self.pipeline = "from-sources-complete"
157169
if override_src_dir is not None:
158170
self.override = " --override-src-dir=%s" % override_src_dir
159171
else:
160172
self.override = ""
161173

162174
def command_line(self, track, challenge, car):
163-
cmd = "rally --configuration-name=nightly --target-host={8} --pipeline={6} --quiet --revision \"@{0}\" " \
175+
cmd = "rally --configuration-name={9} --target-host={8} --pipeline={6} --quiet --revision \"{0}\" " \
164176
"--effective-start-date \"{1}\" --track={2} --challenge={3} --car={4} --report-format=csv --report-file={5}{7}". \
165-
format(self.revision_ts, self.ts, track, challenge, car, self.report_path(track, challenge, car), self.pipeline, self.override,
166-
self.target_host)
177+
format(self.revision, self.ts, track, challenge, car, self.report_path(track, challenge, car), self.pipeline, self.override,
178+
self.target_host, self.configuration_name)
167179
# after we've executed the first benchmark, there is no reason to build again from sources
168180
self.pipeline = "from-sources-skip-build"
169181
return cmd
170182

171183

184+
class NightlyCommand(SourceBasedCommand):
185+
CONFIG_NAME = "nightly"
186+
187+
def __init__(self, effective_start_date, target_host, root_dir, override_src_dir=None):
188+
super().__init__(effective_start_date, target_host, root_dir, "@%s" % to_iso8601(effective_start_date),
189+
NightlyCommand.CONFIG_NAME, override_src_dir)
190+
191+
192+
class AdHocCommand(SourceBasedCommand):
193+
def __init__(self, revision, effective_start_date, target_host, root_dir, configuration_name, override_src_dir=None):
194+
super().__init__(effective_start_date, target_host, root_dir, revision, configuration_name, override_src_dir)
195+
196+
172197
class ReleaseCommand(BaseCommand):
173-
def __init__(self, effective_start_date, target_host, root_dir, distribution_version):
198+
def __init__(self, effective_start_date, target_host, root_dir, distribution_version, configuration_name):
174199
super().__init__(effective_start_date, target_host, root_dir)
200+
self.configuration_name = configuration_name
175201
self.pipeline = "from-distribution"
176202
self.distribution_version = distribution_version
177203

178204
def command_line(self, track, challenge, car):
179-
cmd = "rally --target-host={7} --pipeline={6} --quiet --distribution-version={0} --effective-start-date \"{1}\" " \
180-
"--track={2} --challenge={3} --car={4} --report-format=csv --report-file={5}". \
205+
cmd = "rally --configuration-name={8} --target-host={7} --pipeline={6} --quiet --distribution-version={0} " \
206+
"--effective-start-date \"{1}\" --track={2} --challenge={3} --car={4} --report-format=csv --report-file={5}". \
181207
format(self.distribution_version, self.ts, track, challenge, car, self.report_path(track, challenge, car), self.pipeline,
182-
self.target_host)
208+
self.target_host, self.configuration_name)
183209
return cmd
184210

185211

@@ -528,7 +554,11 @@ def parse_args():
528554
"--mode",
529555
help="In which mode to run?",
530556
default="nightly",
531-
choices=["nightly", "comparison"])
557+
choices=["nightly", "comparison", "adhoc"])
558+
parser.add_argument(
559+
"--revision",
560+
help="Specify the source code revision to build for adhoc benchmarks.",
561+
default="latest")
532562
parser.add_argument(
533563
"--release",
534564
help="Specify release string to use for comparison reports",
@@ -543,16 +573,23 @@ def parse_args():
543573
def main():
544574
args = parse_args()
545575
compare_mode = args.mode == "comparison"
576+
adhoc_mode = args.mode == "adhoc"
546577
root_dir = config["root.dir"] if not args.override_root_dir else args.override_root_dir
547578
if compare_mode:
579+
env_name = sanitize(args.release)
580+
configure_rally(env_name, args.dry_run)
548581
if args.release.startswith("Docker"):
549582
command = DockerCommand(args.effective_start_date, args.target_host, root_dir, args.release)
550583
else:
551584
command = ReleaseCommand(args.effective_start_date, args.target_host, root_dir, args.release)
585+
elif adhoc_mode:
586+
env_name = sanitize(args.release)
587+
command = AdHocCommand(args.revision, args.effective_start_date, args.target_host, root_dir, env_name, args.override_src_dir)
552588
else:
553-
configure_rally(args.dry_run)
589+
env_name = NightlyCommand.CONFIG_NAME
554590
command = NightlyCommand(args.effective_start_date, args.target_host, root_dir, args.override_src_dir)
555591

592+
configure_rally(env_name, args.dry_run)
556593
rally_failure = run_rally(tracks, command, args.dry_run)
557594
replace_release = args.replace_release if args.replace_release else args.release
558595
report(args.effective_start_date, tracks, defaults, replace_release, args.release, root_dir, compare_mode)

night_rally.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ DRY_RUN=NO
3535
START_DATE=`date -u "+%Y-%m-%d %H:%M:%S"`
3636
MODE="nightly"
3737
RELEASE="master"
38+
# only needed for ad-hoc benchmarks
39+
REVISION="latest"
3840
REPLACE_RELEASE=${RELEASE}
3941
TARGET_HOST="localhost:9200"
4042

@@ -61,6 +63,10 @@ case ${i} in
6163
MODE="${i#*=}"
6264
shift # past argument=value
6365
;;
66+
--revision=*)
67+
REVISION="${i#*=}"
68+
shift # past argument=value
69+
;;
6470
--release=*)
6571
RELEASE="${i#*=}"
6672
shift # past argument=value
@@ -136,7 +142,7 @@ fi
136142
#****************************
137143
set +e
138144
# Avoid failing before we transferred all results. Usually only a single benchmark trial run fails but lots of other succeed.
139-
python3 ${NIGHT_RALLY_HOME}/night_rally.py --target-host=${TARGET_HOST} --effective-start-date="${START_DATE}" ${NIGHT_RALLY_OVERRIDE} --mode=${MODE} ${NIGHT_RALLY_DRY_RUN} --release="${RELEASE}" --replace-release="${REPLACE_RELEASE}"
145+
python3 ${NIGHT_RALLY_HOME}/night_rally.py --target-host=${TARGET_HOST} --effective-start-date="${START_DATE}" ${NIGHT_RALLY_OVERRIDE} --mode=${MODE} ${NIGHT_RALLY_DRY_RUN} --revision="${REVISION}" --release="${RELEASE}" --replace-release="${REPLACE_RELEASE}"
140146
exit_code=$?
141147

142148
echo "Killing any lingering Rally processes"

resources/rally-nightly.ini renamed to resources/rally-template.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ config.version = 8
55
root.dir = ~/.rally/benchmarks
66

77
[system]
8-
env.name = nightly
8+
env.name = <<ENVIRONMENT>>
99

1010
[source]
1111
local.src.dir = ~/.rally/benchmarks/src

tests.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import collections
22
import datetime
33
import unittest
4+
import os
5+
import os.path
46

57
if __name__ == "__main__" and __package__ is None:
68
__package__ = "night_rally"
@@ -148,6 +150,22 @@ def __call__(self, *args, **kwargs):
148150

149151

150152
class NightRallyTests(unittest.TestCase):
153+
def test_sanitize(self):
154+
self.assertEqual("lucene-7-upgrade", night_rally.sanitize("Lucene 7 Upgrade"))
155+
self.assertEqual("lucene-7-upgrade", night_rally.sanitize("lucene-7-upgrade"))
156+
self.assertEqual("elasticsearch-6_0_0-alpha1-docker", night_rally.sanitize("Elasticsearch 6.0.0-alpha1 Docker"))
157+
158+
def test_configure_rally(self):
159+
path = "%s/.rally/rally-night-rally-unit-test.ini" % os.getenv("HOME")
160+
try:
161+
night_rally.configure_rally("night-rally-unit-test", dry_run=False)
162+
self.assertTrue(os.path.isfile(path), "configuration routine did not create [%s]" % path)
163+
finally:
164+
try:
165+
os.remove(path)
166+
except Exception:
167+
pass
168+
151169
def test_run_two_challenges_successfully(self):
152170
system_call = RecordingSystemCall(return_value=False)
153171

@@ -202,6 +220,61 @@ def test_run_two_tracks_successfully(self):
202220
system_call.calls
203221
)
204222

223+
def test_run_adhoc_benchmark(self):
224+
system_call = RecordingSystemCall(return_value=False)
225+
226+
tracks = collections.OrderedDict()
227+
tracks["geonames"] = [["append-no-conflicts", "defaults"]]
228+
tracks["percolator"] = [["append-no-conflicts", "4gheap"]]
229+
230+
start_date = datetime.datetime(2016, 10, 1)
231+
cmd = night_rally.AdHocCommand("66202dc", start_date, "localhost", "/rally_root", "lucene-7", override_src_dir="~/src/")
232+
night_rally.run_rally(tracks, cmd, system=system_call)
233+
self.assertEqual(2, len(system_call.calls))
234+
self.assertEqual(
235+
[
236+
"rally --configuration-name=lucene-7 --target-host=localhost --pipeline=from-sources-complete --quiet "
237+
"--revision \"66202dc\" --effective-start-date \"2016-10-01 00:00:00\" --track=geonames "
238+
"--challenge=append-no-conflicts --car=defaults --report-format=csv "
239+
"--report-file=/rally_root/reports/rally/2016-10-01-00-00-00/geonames/append-no-conflicts/defaults/report.csv "
240+
"--override-src-dir=~/src/",
241+
"rally --configuration-name=lucene-7 --target-host=localhost --pipeline=from-sources-skip-build --quiet "
242+
"--revision \"66202dc\" --effective-start-date \"2016-10-01 00:00:00\" --track=percolator "
243+
"--challenge=append-no-conflicts --car=4gheap --report-format=csv "
244+
"--report-file=/rally_root/reports/rally/2016-10-01-00-00-00/percolator/append-no-conflicts/4gheap/report.csv "
245+
"--override-src-dir=~/src/"]
246+
,
247+
system_call.calls
248+
)
249+
250+
251+
def test_run_release_benchmark(self):
252+
system_call = RecordingSystemCall(return_value=False)
253+
254+
tracks = {"geonames": [
255+
["append-no-conflicts", "defaults"],
256+
["append-no-conflicts", "4gheap"]
257+
]}
258+
start_date = datetime.datetime(2016, 1, 1)
259+
cmd = night_rally.ReleaseCommand(start_date, "localhost", "/rally_root", "5.3.0", "5_3_0")
260+
night_rally.run_rally(tracks, cmd, system=system_call)
261+
self.assertEqual(2, len(system_call.calls))
262+
self.assertEqual(
263+
[
264+
"rally --configuration-name=5_3_0 --target-host=localhost --pipeline=from-distribution --quiet "
265+
"--distribution-version=5.3.0 --effective-start-date \"2016-01-01 00:00:00\" --track=geonames "
266+
"--challenge=append-no-conflicts --car=defaults --report-format=csv "
267+
"--report-file=/rally_root/reports/rally/2016-01-01-00-00-00/geonames/append-no-conflicts/defaults/report.csv",
268+
269+
"rally --configuration-name=5_3_0 --target-host=localhost --pipeline=from-distribution --quiet "
270+
"--distribution-version=5.3.0 --effective-start-date \"2016-01-01 00:00:00\" --track=geonames "
271+
"--challenge=append-no-conflicts --car=4gheap --report-format=csv "
272+
"--report-file=/rally_root/reports/rally/2016-01-01-00-00-00/geonames/append-no-conflicts/4gheap/report.csv"
273+
]
274+
,
275+
system_call.calls
276+
)
277+
205278
def test_run_continues_on_error(self):
206279
self.maxDiff = None
207280
system_call = RecordingSystemCall(return_value=True)

0 commit comments

Comments
 (0)