Initial commit
This commit is contained in:
351
benchmark/farm.py
Executable file
351
benchmark/farm.py
Executable file
@@ -0,0 +1,351 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import foundation
|
||||
from foundation import (benchrunner,
|
||||
buildbot,
|
||||
config,
|
||||
context,
|
||||
logger,
|
||||
system_info,
|
||||
util)
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
########################################
|
||||
# Parser helpers.
|
||||
########################################
|
||||
|
||||
|
||||
def configureArgumentParser():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Cycles benchmark helper script.")
|
||||
parser.add_argument("-b", "--blender",
|
||||
help="Full file path to Blender's binary " +
|
||||
"to use for rendering",
|
||||
default="")
|
||||
parser.add_argument("-d", "--scenes-dir",
|
||||
help="Directory with scenes",
|
||||
default="")
|
||||
# parser.add_argument('-s', '--scenes',
|
||||
# nargs='+',
|
||||
# help='Scenes to be rendered',
|
||||
# default=[])
|
||||
parser.add_argument('-t', '--device-type',
|
||||
help="Type of the device to render on",
|
||||
default="CPU")
|
||||
parser.add_argument('-n', '--device-name',
|
||||
help="Device name to render on",
|
||||
default="")
|
||||
parser.add_argument('-v', '--verbose',
|
||||
help="Do verbose logging",
|
||||
action='store_true',
|
||||
default=False)
|
||||
return parser
|
||||
|
||||
########################################
|
||||
# Configuration helpers.
|
||||
########################################
|
||||
|
||||
|
||||
def injectDefaultConfiguration(config):
|
||||
"""
|
||||
For a specified configuration object, set all possible properties to their
|
||||
default value.
|
||||
"""
|
||||
root_dir = util.getBundleRootDirectory()
|
||||
section = {
|
||||
"scenes_dir": os.path.join(root_dir, "scenes"),
|
||||
"device_name": "",
|
||||
}
|
||||
config['farm'] = section
|
||||
|
||||
|
||||
def injectArgparseConfiguration(config, args):
|
||||
"""
|
||||
Override settings wit harguments passed from the command line.
|
||||
"""
|
||||
section = config['farm']
|
||||
if args.blender:
|
||||
section['blender'] = args.blender
|
||||
if args.scenes_dir:
|
||||
section['scenes_dir'] = args.scenes_dir
|
||||
if args.device_type:
|
||||
section['device_type'] = args.device_type
|
||||
if args.device_name:
|
||||
section['device_name'] = args.device_name
|
||||
|
||||
|
||||
def readConfiguration(args):
|
||||
"""
|
||||
Read configuration file and return BenchmarkConfig with all the settings
|
||||
we will need to use.
|
||||
"""
|
||||
config = foundation.config.BenchmarkConfig()
|
||||
injectDefaultConfiguration(config)
|
||||
read_configs = config.readGlobalConfig("farm")
|
||||
if read_configs:
|
||||
logger.INFO("Configuration was read from:")
|
||||
for cfg in read_configs:
|
||||
print(" " + util.stripSensitiveInfo(cfg))
|
||||
injectArgparseConfiguration(config, args)
|
||||
return config
|
||||
|
||||
|
||||
def checkConfiguration(config):
|
||||
"""
|
||||
Check whether configuration is complete and usable.
|
||||
"""
|
||||
required_keys = ('device_type', )
|
||||
known_device_types = ('CPU', 'GPU', 'CUDA', 'OPENCL', )
|
||||
logger.INFO("Validating configuration...")
|
||||
# Check whether section exists.
|
||||
if 'farm' not in config.sections():
|
||||
logger.INFO(" Missing configuration section for 'farm'.")
|
||||
return False
|
||||
section = config['farm']
|
||||
# Check whether required keys exists.
|
||||
for required_key in required_keys:
|
||||
if required_key not in section:
|
||||
logger.INFO(" Missing configuration key {}" .
|
||||
format(required_key))
|
||||
return False
|
||||
# Check whether device configuration is correct.
|
||||
device_type = section['device_type']
|
||||
if device_type not in known_device_types:
|
||||
logger.INFO(" Unknown device type {}" . format(device_type))
|
||||
return False
|
||||
if device_type != 'CPU':
|
||||
if 'device_name' not in section or not section['device_name']:
|
||||
logger.INFO(" Need to explicitly specify device name.")
|
||||
return False
|
||||
# Check whether directories are correct.
|
||||
if not os.path.exists(section["scenes_dir"]):
|
||||
logger.INFO(" Scenes directory does not exist.")
|
||||
return False
|
||||
if not section["output_dir"]:
|
||||
logger.INFO(" Missing configuration for output directory.")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
########################################
|
||||
# Temporary directories helpers.
|
||||
########################################
|
||||
|
||||
def createTempDirectory(config):
|
||||
"""
|
||||
Usually creates an unique temporary directory and returns full path to it.
|
||||
|
||||
In the development environment uses pre-defined directory, where all data
|
||||
might already be existing (for example, blender might be already downloaded
|
||||
and unpacked).
|
||||
"""
|
||||
temp_dir = tempfile.mkdtemp(prefix="blender-benchmark-")
|
||||
return temp_dir
|
||||
|
||||
|
||||
def deleteTempDirectory(config, temp_dir):
|
||||
"""
|
||||
Get rid of automatically created temp directory/
|
||||
"""
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
|
||||
########################################
|
||||
# Latets Blender helper script.
|
||||
########################################
|
||||
|
||||
def downloadLatestBlender(directory):
|
||||
"""
|
||||
Download latets Blender from buildbot to given directory.
|
||||
"""
|
||||
# TODO(sergey): This we need to change to currently running configuration.
|
||||
latest_blender_url = buildbot.buildbotGetLatetsVersion("Linux", "64bit")
|
||||
if not latest_blender_url:
|
||||
logger.ERROR("Unable to figure out latest Blender version")
|
||||
return
|
||||
blender_filename = latest_blender_url.split('/')[-1]
|
||||
logger.INFO("Found latest Blender: {}" . format(blender_filename))
|
||||
local_blender = os.path.join(directory, blender_filename)
|
||||
logger.INFO("Downloading Blender...")
|
||||
util.downloadFile(latest_blender_url, local_blender)
|
||||
return local_blender
|
||||
|
||||
|
||||
def findBlenderDirInDirectory(directory):
|
||||
"""
|
||||
Find Blender directory in given folder.
|
||||
"""
|
||||
for filename in os.listdir(directory):
|
||||
if filename.startswith("blender"):
|
||||
full_filename = os.path.join(directory, filename)
|
||||
if os.path.isdir(full_filename):
|
||||
return full_filename
|
||||
return None
|
||||
|
||||
|
||||
def findBlenderInDirectory(directory):
|
||||
"""
|
||||
Find blender executable file in given directory. Will recurse into
|
||||
Blender directory in there
|
||||
"""
|
||||
blender_dir = findBlenderDirInDirectory(directory)
|
||||
platform = sys.platform
|
||||
if platform == 'linux':
|
||||
return os.path.join(blender_dir, "blender")
|
||||
else:
|
||||
raise Exception("Need to support your OS!")
|
||||
|
||||
|
||||
def getLatetsBlenderBinary(config, temp_dir):
|
||||
"""
|
||||
Get full file path to latest Blender executable which will be used for
|
||||
actual benchmark.
|
||||
|
||||
It will either use Blender from already specified location or it will
|
||||
download latest Blender from buildbot.
|
||||
"""
|
||||
# Firts try to use Blender specified in configuration.
|
||||
if 'blender' in config['farm']:
|
||||
return config['farm']['blender']
|
||||
# Well, download and unpack the latest Blender from buildbot.
|
||||
logger.INFO("Will get latest Blender from buildbot.")
|
||||
blender_archive = downloadLatestBlender(temp_dir)
|
||||
logger.INFO("Unpacking Blender...")
|
||||
util.unpackArchive(blender_archive, temp_dir)
|
||||
return findBlenderInDirectory(temp_dir)
|
||||
|
||||
########################################
|
||||
# Results output.
|
||||
########################################
|
||||
|
||||
|
||||
def latestDirGet(path):
|
||||
"""
|
||||
Get directory with bigger number in the given path
|
||||
"""
|
||||
max_file = None
|
||||
for f in os.listdir(path):
|
||||
x = int(f)
|
||||
if not max_file or x > max_file:
|
||||
max_file = x
|
||||
return max_file
|
||||
|
||||
|
||||
def ensureOutputDir(config):
|
||||
output_dir = config['farm']['output_dir']
|
||||
if not os.path.exists(output_dir):
|
||||
os.mkdir(output_dir)
|
||||
max_dir = latestDirGet(output_dir)
|
||||
if not max_dir:
|
||||
max_dir = 0
|
||||
new_dir = str(max_dir + 1).zfill(8)
|
||||
new_full_dir = os.path.join(output_dir, new_dir)
|
||||
os.mkdir(new_full_dir)
|
||||
return new_full_dir
|
||||
|
||||
|
||||
def ensureImageOutputDir(results_output_dir):
|
||||
images_output_dir = os.path.join(results_output_dir, "images")
|
||||
os.mkdir(images_output_dir)
|
||||
return images_output_dir
|
||||
|
||||
|
||||
def getResultJSONString(ctx, results):
|
||||
# Convert custom classes to dictionaries for easier JSON dump.
|
||||
json_results = results
|
||||
stats = json_results['stats']
|
||||
for scene in ctx.scenes:
|
||||
if scene not in stats:
|
||||
continue
|
||||
if stats[scene]:
|
||||
stats[scene] = stats[scene].asDict()
|
||||
stats[scene]['result'] = 'OK'
|
||||
else:
|
||||
stats[scene] = {'result': 'CRASH'}
|
||||
return json.dumps(json_results, sort_keys=True, indent=4)
|
||||
|
||||
|
||||
def saveResults(ctx, results, output_dir):
|
||||
json_string = getResultJSONString(ctx, results)
|
||||
results_file = os.path.join(output_dir, "results.json")
|
||||
with open(results_file, "w") as f:
|
||||
f.write(json_string)
|
||||
|
||||
########################################
|
||||
# Main logic.
|
||||
########################################
|
||||
|
||||
def main():
|
||||
parser = configureArgumentParser()
|
||||
args = parser.parse_args()
|
||||
logger.VERBOSE = args.verbose
|
||||
logger.init()
|
||||
logger.HEADER("Cycles Benchmark Suite v{}, farm edition" .
|
||||
format(foundation.VERSION))
|
||||
# Some platform independent directories to helper scripts.
|
||||
script_directory = os.path.dirname(os.path.realpath(__file__))
|
||||
configure_script = os.path.join(script_directory, "configure.py")
|
||||
# Read configuration file, so we know what we will be doing.
|
||||
config = readConfiguration(args)
|
||||
if not checkConfiguration(config):
|
||||
logger.ERROR("Configuration is not complete or valid, aborting.")
|
||||
return False
|
||||
logger.INFO("Configuration looks reasonable, continuing.")
|
||||
# Create temporary directory, all runtime files will be stored there.
|
||||
temp_dir = createTempDirectory(config)
|
||||
results_output_dir = ensureOutputDir(config)
|
||||
images_output_dir = ensureImageOutputDir(results_output_dir)
|
||||
try:
|
||||
blender_binary = getLatetsBlenderBinary(config, temp_dir)
|
||||
logger.INFO("Will benchmark the following Blender: {}".
|
||||
format(util.stripSensitiveInfo(blender_binary)))
|
||||
# Bechmark context initialization.
|
||||
farm_config = config['farm']
|
||||
ctx = context.Context()
|
||||
ctx.blender = blender_binary
|
||||
ctx.configure_script = configure_script
|
||||
ctx.scenes_dir = farm_config['scenes_dir']
|
||||
ctx.device_type = farm_config['device_type']
|
||||
ctx.device_name = farm_config['device_name']
|
||||
ctx.image_output_dir = images_output_dir
|
||||
ctx.scenes = ctx.listAllScenes(ctx.scenes_dir)
|
||||
# Print prelmiinary information.
|
||||
blender_dvice_info = benchrunner.benchmarkGetDeviceInfo(ctx)
|
||||
if not blender_dvice_info['device_type']:
|
||||
logger.ERROR("Requested device can not be enabled in Blender.")
|
||||
logger.INFO("Requested device details:")
|
||||
logger.INFO(" Device type: {}" . format(ctx.device_type))
|
||||
logger.INFO(" Device name: {}" . format(ctx.device_name))
|
||||
all_stats = None
|
||||
else:
|
||||
logger.INFO("Configured device details:")
|
||||
logger.INFO(" Device type: {}" .
|
||||
format(blender_dvice_info["device_type"]))
|
||||
logger.INFO(" Compute devices:")
|
||||
for compute_device in blender_dvice_info["compute_devices"]:
|
||||
logger.INFO(" {}" . format(compute_device))
|
||||
# Run benchmark.
|
||||
all_stats = benchrunner.benchmarkAll(ctx)
|
||||
# Gather all information together.
|
||||
results = {
|
||||
"blender_version": system_info.getBlenderVersion(ctx),
|
||||
"system_info": system_info.gatherSystemInfo(ctx),
|
||||
"device_info": blender_dvice_info,
|
||||
"stats": all_stats if all_stats else {}
|
||||
}
|
||||
saveResults(ctx, results, results_output_dir)
|
||||
return all_stats is not None
|
||||
finally:
|
||||
deleteTempDirectory(config, temp_dir)
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not main():
|
||||
sys.exit(1)
|
Reference in New Issue
Block a user