diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index a48027904d..cbb3bdfaf4 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -30,6 +30,7 @@ import arm.material.make as make_material import arm.material.mat_batch as mat_batch import arm.utils +import arm.profiler @unique @@ -127,7 +128,6 @@ def __init__(self, context: bpy.types.Context, filepath: str, scene: bpy.types.S self.world_array = [] self.particle_system_array = {} - # `True` if there is at least one spawned camera in the scene self.camera_spawned = False @@ -150,7 +150,8 @@ def export_scene(cls, context: bpy.types.Context, filepath: str, scene: bpy.type """Exports the given scene to the given file path. This is the function that is called in make.py and the entry point of the exporter.""" - cls(context, filepath, scene, depsgraph).execute() + with arm.profiler.Profile('profile_exporter.prof', arm.utils.get_pref_or_default('profile_exporter', False)): + cls(context, filepath, scene, depsgraph).execute() @classmethod def preprocess(cls): diff --git a/blender/arm/profiler.py b/blender/arm/profiler.py new file mode 100644 index 0000000000..87f7a0a22a --- /dev/null +++ b/blender/arm/profiler.py @@ -0,0 +1,35 @@ +import cProfile +import os +import pstats + +import arm.log as log +import arm.utils as utils + + +class Profile: + """Context manager for profiling the enclosed code when the given condition is true. + The output file is stored in the SDK directory and can be opened by tools such as SnakeViz. + """ + def __init__(self, filename_out: str, condition: bool): + self.filename_out = filename_out + self.condition = condition + self.pr = cProfile.Profile() + + def __enter__(self): + if self.condition: + self.pr.enable() + log.debug("Profiling started") + + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.condition: + self.pr.disable() + log.debug("Profiling finished") + + profile_path = os.path.join(utils.get_sdk_path(), self.filename_out) + with open(profile_path, 'w') as profile_file: + stats = pstats.Stats(self.pr, stream=profile_file) + stats.dump_stats(profile_path) + + return False diff --git a/blender/arm/utils.py b/blender/arm/utils.py index e194565062..2f3559a23b 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -4,6 +4,7 @@ import platform import re import subprocess +from typing import Any import webbrowser import shlex @@ -226,6 +227,11 @@ def get_relative_paths(): addon_prefs = get_arm_preferences() return False if not hasattr(addon_prefs, 'relative_paths') else addon_prefs.relative_paths +def get_pref_or_default(prop_name: str, default: Any) -> Any: + """Return the preference setting for prop_name, or the value given as default if the property does not exist.""" + addon_prefs = get_arm_preferences() + return getattr(addon_prefs, prop_name, default) + def get_node_path(): if get_os() == 'win': return get_sdk_path() + '/nodejs/node.exe'