perfkitbenchmarker/linux_benchmarks/oldisim_benchmark.py (140 lines of code) (raw):
# Copyright 2015 PerfKitBenchmarker Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Runs oldisim.
oldisim is a framework to support benchmarks that emulate Online Data-Intensive
(OLDI) workloads, such as web search and social networking. oldisim includes
sample workloads built on top of this framework.
With its default config, oldisim models an example search topology. A user query
is first processed by a front-end server, which then eventually fans out the
query to a large number of leaf nodes. The latency is measured at the root of
the tree, and often increases with the increase of fan-out. oldisim reports a
scaling efficiency for a given topology. The scaling efficiency is defined
as queries per second (QPS) at the current fan-out normalized to QPS at fan-out
1 with ISO root latency.
Sample command line:
./pkb.py --benchmarks=oldisim --project='YOUR_PROJECT' --oldisim_num_leaves=4
--oldisim_fanout=1,2,3,4 --oldisim_latency_target=40
--oldisim_latency_metric=avg
The above command will build a tree with one root node and four leaf nodes. The
average latency target is 40ms. The root node will vary the fanout from 1 to 4
and measure the scaling efficiency.
"""
import logging
import re
import time
from absl import flags
from perfkitbenchmarker import background_tasks
from perfkitbenchmarker import configs
from perfkitbenchmarker import sample
from perfkitbenchmarker.linux_packages import oldisim_dependencies
FLAGS = flags.FLAGS
flags.DEFINE_integer(
'oldisim_num_leaves',
4,
'number of leaf nodes',
lower_bound=1,
upper_bound=64,
)
flags.DEFINE_list(
'oldisim_fanout',
[],
'a list of fanouts to be tested. '
'a root can connect to a subset of leaf nodes (fanout). '
'the value of fanout has to be smaller than num_leaves.',
)
flags.DEFINE_enum(
'oldisim_latency_metric',
'avg',
['avg', '50p', '90p', '95p', '99p', '99.9p'],
'Allowable metrics for end-to-end latency',
)
flags.DEFINE_float('oldisim_latency_target', '30', 'latency target in ms')
NUM_DRIVERS = 1
NUM_ROOTS = 1
BENCHMARK_NAME = 'oldisim'
BENCHMARK_CONFIG = """
oldisim:
description: >
Run oldisim. Specify the number of leaf
nodes with --oldisim_num_leaves
vm_groups:
default:
vm_spec: *default_dual_core
"""
def GetConfig(user_config):
"""Decide number of vms needed to run oldisim."""
config = configs.LoadConfig(BENCHMARK_CONFIG, user_config, BENCHMARK_NAME)
config['vm_groups']['default']['vm_count'] = (
FLAGS.oldisim_num_leaves + NUM_DRIVERS + NUM_ROOTS
)
return config
def InstallAndBuild(vm):
"""Install and build oldisim on the target vm.
Args:
vm: A vm instance that runs oldisim.
"""
logging.info('prepare oldisim on %s', vm)
vm.Install('oldisim_dependencies')
def Prepare(benchmark_spec):
"""Install and build oldisim on the target vm.
Args:
benchmark_spec: The benchmark specification. Contains all data that is
required to run the benchmark.
"""
vms = benchmark_spec.vms
leaf_vms = [
vm for vm_idx, vm in enumerate(vms) if vm_idx >= (NUM_DRIVERS + NUM_ROOTS)
]
if vms:
background_tasks.RunThreaded(InstallAndBuild, vms)
# Launch job on the leaf nodes.
leaf_server_bin = oldisim_dependencies.BinaryPath('LeafNode')
for vm in leaf_vms:
leaf_cmd = '%s --threads=%s' % (leaf_server_bin, vm.NumCpusForBenchmark())
vm.RemoteCommand('%s &> /dev/null &' % leaf_cmd)
def SetupRoot(root_vm, leaf_vms):
"""Connect a root node to a list of leaf nodes.
Args:
root_vm: A root vm instance.
leaf_vms: A list of leaf vm instances.
"""
fanout_args = ' '.join(['--leaf=%s' % i.internal_ip for i in leaf_vms])
root_server_bin = oldisim_dependencies.BinaryPath('ParentNode')
root_cmd = '%s --threads=%s %s' % (
root_server_bin,
root_vm.NumCpusForBenchmark(),
fanout_args,
)
logging.info('Root cmdline: %s', root_cmd)
root_vm.RemoteCommand('%s &> /dev/null &' % root_cmd)
def ParseOutput(oldisim_output):
"""Parses the output from oldisim.
Args:
oldisim_output: A string containing the text of oldisim output.
Returns:
A tuple of (peak_qps, peak_lat, target_qps, target_lat).
"""
re_peak = re.compile(r'peak qps = (?P<qps>\S+), latency = (?P<lat>\S+)')
re_target = re.compile(r'measured_qps = (?P<qps>\S+), latency = (?P<lat>\S+)')
for line in oldisim_output.splitlines():
match = re.search(re_peak, line)
if match:
peak_qps = float(match.group('qps'))
peak_lat = float(match.group('lat'))
target_qps = float(peak_qps)
target_lat = float(peak_lat)
continue
match = re.search(re_target, line)
if match:
target_qps = float(match.group('qps'))
target_lat = float(match.group('lat'))
return peak_qps, peak_lat, target_qps, target_lat
def RunLoadTest(benchmark_spec, fanout):
"""Run Loadtest for a given topology.
Args:
benchmark_spec: The benchmark specification. Contains all data that is
required to run the benchmark.
fanout: Request is first processed by a root node, which then fans out to a
subset of leaf nodes.
Returns:
A tuple of (peak_qps, peak_lat, target_qps, target_lat).
"""
assert fanout <= FLAGS.oldisim_num_leaves, (
'The number of leaf nodes a root node connected to is defined by the '
'flag fanout. Its current value %s is bigger than the total number of '
'leaves %s.' % (fanout, FLAGS.oldisim_num_leaves)
)
vms = benchmark_spec.vms
driver_vms = []
root_vms = []
leaf_vms = []
for vm_index, vm in enumerate(vms):
if vm_index < NUM_DRIVERS:
driver_vms.append(vm)
elif vm_index < (NUM_DRIVERS + NUM_ROOTS):
root_vms.append(vm)
else:
leaf_vms.append(vm)
leaf_vms = leaf_vms[:fanout]
for root_vm in root_vms:
SetupRoot(root_vm, leaf_vms)
driver_vm = driver_vms[0]
driver_binary = oldisim_dependencies.BinaryPath('DriverNode')
launch_script = oldisim_dependencies.Path('workloads/search/search_qps.sh')
driver_args = ' '.join(['--server=%s' % i.internal_ip for i in root_vms])
# Make sure server is up.
time.sleep(5)
driver_cmd = '%s -s %s:%s -t 30 -- %s %s --threads=%s --depth=16' % (
launch_script,
FLAGS.oldisim_latency_metric,
FLAGS.oldisim_latency_target,
driver_binary,
driver_args,
driver_vm.NumCpusForBenchmark(),
)
logging.info('Driver cmdline: %s', driver_cmd)
stdout, _ = driver_vm.RemoteCommand(driver_cmd)
return ParseOutput(stdout)
def Run(benchmark_spec):
"""Run oldisim on the target vm.
Args:
benchmark_spec: The benchmark specification. Contains all data that is
required to run the benchmark.
Returns:
A list of sample.Sample objects.
"""
results = []
qps_dict = dict()
fanout_list = {1, FLAGS.oldisim_num_leaves}
for fanout in map(int, FLAGS.oldisim_fanout):
if fanout > 1 and fanout < FLAGS.oldisim_num_leaves:
fanout_list.add(fanout)
for fanout in sorted(fanout_list):
qps = RunLoadTest(benchmark_spec, fanout)[2]
qps_dict[fanout] = qps
if fanout == 1:
base_qps = qps
name = 'Scaling efficiency of %s leaves' % fanout
scaling_efficiency = round(min(qps_dict[fanout] / base_qps, 1), 2)
metadata = {}
results.append(sample.Sample(name, scaling_efficiency, '', metadata))
return results
def Cleanup(benchmark_spec): # pylint: disable=unused-argument
"""Cleanup oldisim on the target vm.
Args:
benchmark_spec: The benchmark specification. Contains all data that is
required to run the benchmark.
"""
vms = benchmark_spec.vms
for vm_index, vm in enumerate(vms):
if vm_index >= NUM_DRIVERS and vm_index < (NUM_DRIVERS + NUM_ROOTS):
vm.RemoteCommand('sudo pkill ParentNode')
elif vm_index >= (NUM_DRIVERS + NUM_ROOTS):
vm.RemoteCommand('sudo pkill LeafNode')