This repository has been archived on 2023-02-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-benchmark-bundle/benchmark/farm.py
Francesco Siddi a04a10068a Prevent crash when generating JSONString
The issue is discussed in rBBB4726ee0c9c05
2018-09-06 15:51:36 +02:00

404 lines
14 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import datetime
import json
import os
import shutil
import sys
import tempfile
SCRIPT_PATH = os.path.realpath(__file__)
SCRIPT_DIR = os.path.dirname(SCRIPT_PATH)
RESULT_JSON_SCHEMA_VERSION = 2
import foundation
from foundation import (benchrunner,
buildbot,
config,
context,
logger,
system_info,
util)
########################################
# 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('-c', '--scenes',
nargs='+',
help='Scenes to be rendered',
default=[])
parser.add_argument('-t', '--device-type',
help="Type of the device to render on",
default="")
parser.add_argument('-n', '--device-name',
help="Device name to render on",
default="")
parser.add_argument('-o', '--device-name-override',
help="Override compute device name with this",
default="")
parser.add_argument('-s', '--device-single',
help="Use single device when multiple matches",
action='store_true',
default=False)
parser.add_argument('-v', '--verbose',
help="Do verbose logging",
action='store_true',
default=False)
parser.add_argument('-i', '--system-info',
help="Only dump system info to the screen",
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": "",
"scenes_dir": os.path.join(root_dir, "scenes"),
"device_name": "",
"device_single": "False",
"device_name_override": "",
}
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:
section['scenes'] = ",".join(args.scenes)
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
if args.device_name_override:
section['device_name_override'] = args.device_name_override
if args.device_single:
section['device_single'] = args.device_single
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 "output_dir" not in section:
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.
"""
platform = sys.platform
latest_blender_url = buildbot.buildbotGetLatetsVersion(platform, "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")
elif platform == "win32":
return os.path.join(blender_dir, "blender.exe")
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):
if f.endswith(".DS_Store"):
continue
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.makedirs(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.
results['schema_version'] = RESULT_JSON_SCHEMA_VERSION
stats = 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(results, sort_keys=True, indent=2)
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']
if farm_config['device_single'].lower() == "true":
ctx.device_single = True
else:
ctx.device_single = False
ctx.image_output_dir = images_output_dir
# Don't do any scenes when only querying for system information.
if args.system_info:
ctx.scenes = []
else:
if farm_config['scenes']:
ctx.scenes = farm_config['scenes'].split(",")
else:
ctx.scenes = ctx.listAllScenes(ctx.scenes_dir)
# Print additional informaiton.
if farm_config["device_name_override"]:
print("Using device name override: {}" .
format(farm_config["device_name_override"]))
# Print prelmiinary information.
blender_device_info = benchrunner.benchmarkGetDeviceInfo(ctx)
if not blender_device_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_device_info["device_type"]))
logger.INFO(" Compute devices:")
for compute_device in blender_device_info["compute_devices"]:
logger.INFO(" {}" . format(compute_device))
# Run benchmark.
all_stats = benchrunner.benchmarkAll(ctx)
# Gather all information together.
timestamp = datetime.datetime.now(datetime.timezone.utc).isoformat()
results = {
"timestamp": timestamp,
"blender_version": system_info.getBlenderVersion(ctx),
"system_info": system_info.gatherSystemInfo(ctx),
"device_info": blender_device_info,
"stats": all_stats if all_stats else {}
}
if farm_config["device_name_override"]:
results["device_name_override"] = farm_config["device_name_override"]
saveResults(ctx, results, results_output_dir)
return all_stats is not None
finally:
if not args.system_info:
deleteTempDirectory(config, temp_dir)
return True
if __name__ == "__main__":
if not main():
sys.exit(1)