diff --git a/installer.py b/installer.py index 8507ac57e..ee7f0e510 100644 --- a/installer.py +++ b/installer.py @@ -127,8 +127,8 @@ def env_flag(name: str, default: bool = False) -> bool: def print_profile(profiler: cProfile.Profile, msg: str): profiler.disable() - from modules.errors import profile - profile(profiler, msg) + from modules.errors import profile_print + profile_print(msg, local_profiler=profiler) def package_version(package): @@ -942,6 +942,12 @@ def check_torch(): install(torch_command, 'torch torchvision', quiet=False) try: + try: + # import torch pulls torch.distributed immediately which is slow and unnecessary + import torch.distributed.tensor._ops as dtensor_ops + dtensor_ops.single_dim_strategy._resolve_foreach_elementwise_overload = lambda *a, **kw: None # pylint: disable=protected-access + except Exception: + pass import torch try: import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import diff --git a/launch.py b/launch.py index 5357cbedb..c80031380 100755 --- a/launch.py +++ b/launch.py @@ -196,8 +196,8 @@ def clean_server(): def start_server(immediate=True, server=None): if args.profile: import cProfile - pr = cProfile.Profile() - pr.enable() + profiler = cProfile.Profile() + profiler.enable() import gc import importlib.util collected = 0 @@ -221,10 +221,12 @@ def start_server(immediate=True, server=None): server.wants_restart = False uvicorn = server.webui(restart=not immediate, _exit=True) else: - uvicorn = server.webui(restart=not immediate) + uvicorn = server.webui(restart=not immediate, profiler=profiler if args.profile else None) if args.profile: - pr.disable() - installer.print_profile(pr, 'WebUI') + profiler.disable() + installer.print_profile(profiler, 'WebUI') + profiler.clear() + profiler.enable() rec('server') return uvicorn, server @@ -343,10 +345,12 @@ def main(): if uv is not None and uv.wants_restart: clean_server() log.info('Server restarting...') - # uv, instance = start_server(immediate=False, server=instance) os.execv(sys.executable, ['python'] + sys.argv) else: log.info('Exiting...') + from modules import errors + errors.profile_stop() + errors.profile_print('Shutdown') break time.sleep(1.0) diff --git a/modules/call_queue.py b/modules/call_queue.py index 71eee8000..88fc5d170 100644 --- a/modules/call_queue.py +++ b/modules/call_queue.py @@ -3,7 +3,6 @@ import sys import html import threading import time -import cProfile from modules import shared, progress, errors, timer from modules.logger import log @@ -65,8 +64,9 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False, name=None): jobid = shared.state.begin(job_name, task_id=task_id) try: if shared.cmd_opts.profile: - pr = cProfile.Profile() - pr.enable() + errors.profile_stop() + errors.profile_print('BeforeWrapGradioCall') + errors.profile_start() res = func(*args, **kwargs) if res is None: msg = "No result returned from function" @@ -76,8 +76,9 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False, name=None): else: res = list(res) if shared.cmd_opts.profile: - pr.disable() - errors.profile(pr, 'Wrap') + errors.profile_stop() + errors.profile_print('AfterWrapGradioCall') + errors.profile_start() except Exception as e: errors.display(e, 'gradio call') res = extra_outputs_array or [] diff --git a/modules/errors.py b/modules/errors.py index 5faf6f9b7..c06d08a4e 100644 --- a/modules/errors.py +++ b/modules/errors.py @@ -8,6 +8,7 @@ log = get_log() setup_logging() install_traceback() already_displayed = {} +_profiler = None class ValidationError(ValueError): @@ -63,19 +64,21 @@ def exception(suppress=None): console.print_exception(show_locals=False, max_frames=16, extra_lines=2, suppress=suppress, theme="ansi_dark", word_wrap=False, width=min([console.width, 200])) -def profile(profiler, msg: str, n: int = 16): - profiler.disable() +def profile_print(msg: str='', n: int = 16, local_profiler=None): + if local_profiler is None: + local_profiler = _profiler + if local_profiler is None: + return import io import pstats stream = io.StringIO() # pylint: disable=abstract-class-instantiated - p = pstats.Stats(profiler, stream=stream) + p = pstats.Stats(local_profiler, stream=stream) p.sort_stats(pstats.SortKey.CUMULATIVE) p.print_stats(200) # p.print_title() # p.print_call_heading(10, 'time') # p.print_callees(10) # p.print_callers(10) - profiler = None lines = stream.getvalue().split('\n') lines = [x for x in lines if ' str | None: +@lru_cache(maxsize=1024) +def real_path(directory_path: str) -> str | None: + """Cached real_path resolution to avoid repeated abspath/expanduser calls.""" + if not directory_path: + return None try: return os.path.abspath(os.path.expanduser(directory_path)) except Exception: - pass - return None + return None @dataclass(frozen=True) class Directory(Directory): # pylint: disable=E0102 path: str = field(default_factory=str) - mtime: float = field(default_factory=float, init=False) files: FilePathList = field(default_factory=list) directories: DirectoryPathList = field(default_factory=list) - - def __post_init__(self): - object.__setattr__(self, 'mtime', self.live_mtime) + mtime: float = 0.0 @classmethod def from_dict(cls, dict_object: dict) -> Directory: directory = cls.__new__(cls) object.__setattr__(directory, 'path', dict_object.get('path')) - object.__setattr__(directory, 'mtime', dict_object.get('mtime')) - object.__setattr__(directory, 'files', dict_object.get('files')) - object.__setattr__(directory, 'directories', dict_object.get('directories')) + object.__setattr__(directory, 'mtime', dict_object.get('mtime', 0.0)) + object.__setattr__(directory, 'files', dict_object.get('files', [])) + object.__setattr__(directory, 'directories', dict_object.get('directories', [])) return directory def clear(self) -> None: @@ -59,12 +59,15 @@ class Directory(Directory): # pylint: disable=E0102 })) def update(self, source_directory: Directory) -> Directory: - if source_directory is not self: + if source_directory is not self and source_directory is not None: self._update(source_directory) return self - def _update(self, source:Directory) -> None: - assert not source.path or source.path == self.path, f'When updating a directory, the paths must match. Attempted to update Directory `{self.path}` with `{source.path}`' + def _update(self, source: Directory) -> None: + assert not source.path or source.path == self.path, ( + f'When updating a directory, the paths must match. ' + f'Attempted to update Directory `{self.path}` with `{source.path}`' + ) for dead_path in self.directories: if dead_path not in source.directories: delete_cached_directory(dead_path) @@ -74,105 +77,115 @@ class Directory(Directory): # pylint: disable=E0102 @property def exists(self) -> bool: - return self.path and os.path.exists(self.path) + return bool(self.path and os.path.exists(self.path)) @property def is_directory(self) -> bool: - return self.exists and os.path.isdir(self.path) + return bool(self.path and os.path.isdir(self.path)) @property def live_mtime(self) -> float: - return os.path.getmtime(self.path) if self.is_directory else 0 + try: + return os.path.getmtime(self.path) if self.path else 0.0 + except OSError: + return 0.0 @property def is_stale(self) -> bool: - return not self.is_directory or self.mtime != self.live_mtime + return self.mtime != self.live_mtime class DirectoryCache(UserDict, DirectoryCollection): def __delattr__(self, directory_path: str) -> None: directory: Directory = get_directory(directory_path, fetch=False) if directory: - map(delete_cached_directory, directory.directories) + for child in directory.directories: + delete_cached_directory(child) directory.clear() - del self.data[directory_path] + self.data.pop(directory_path, None) -def clean_directory(directory: Directory, /, recursive: RecursiveType=False) -> bool: +def clean_directory(directory: Directory, /, recursive: RecursiveType = False) -> bool: if not directory.is_directory: - is_clean = False delete_cached_directory(directory.path) - else: - is_clean = not directory.is_stale - if not is_clean: - directory.update(fetch_directory(directory.path)) - else: - for directory_path in directory.directories[:]: - try: - recurse = recursive and (not callable(recursive) or recursive(directory.path)) - directory = get_directory(directory_path, fetch=recurse) - if directory: - if directory.is_directory: - if recurse: - is_clean = clean_directory(directory, recursive=recurse) and is_clean - continue - delete_cached_directory(directory_path) - # If we had intended to fetch this directory, but didn't, that means it doesn't exist. Purge. - if recurse: - directory.directories.remove(directory_path) - is_clean = False - except Exception: - pass + return False + + is_clean = not directory.is_stale + if not is_clean: + fetched = fetch_directory(directory.path) + if fetched: + directory.update(fetched) + elif recursive: + for directory_path in list(directory.directories): + try: + recurse = recursive and (not callable(recursive) or recursive(directory.path)) + child_dir = get_directory(directory_path, fetch=recurse) + if child_dir: + if child_dir.is_directory: + if recurse: + is_clean = clean_directory(child_dir, recursive=recurse) and is_clean + continue + delete_cached_directory(directory_path) + if recurse: + directory.directories.remove(directory_path) + is_clean = False + except Exception: + pass return is_clean -def get_directory(directory_or_path: str, /, fetch: bool=True) -> Directory | None: +def get_directory(directory_or_path: str | Directory, /, fetch: bool = True) -> Directory | None: if isinstance(directory_or_path, Directory): if directory_or_path.is_directory: return directory_or_path - else: - directory_or_path = directory_or_path.path - directory_or_path = real_path(directory_or_path) - if not cache_folders.get(directory_or_path, None): + directory_or_path = directory_or_path.path + + resolved_path = real_path(directory_or_path) + if not resolved_path: + return None + + if resolved_path not in cache_folders: if fetch: - directory = fetch_directory(directory_path=directory_or_path) + directory = fetch_directory(directory_path=resolved_path) if directory and do_cache_folders: - cache_folders[directory_or_path] = directory + cache_folders[resolved_path] = directory return directory - else: - clean_directory(cache_folders[directory_or_path]) - return cache_folders[directory_or_path] if directory_or_path in cache_folders else None + return None + + cached = cache_folders[resolved_path] + clean_directory(cached) + return cache_folders.get(resolved_path) def fetch_directory(directory_path: str) -> Directory | None: - directory: Directory for directory in _walk(directory_path, recurse=False): - return directory # The return is intentional, we get a generator, we only need the one + return directory return None -def _walk(top, recurse:RecursiveType=True) -> Directory: - # reimplemented `path.walk()` +def _walk(top: str, recurse: RecursiveType = True) -> Iterator[Directory]: nondirs = [] walk_dirs = [] + top_mtime = 0.0 + try: + top_mtime = os.path.getmtime(top) scandir_it = os.scandir(top) except OSError: return + with scandir_it: - while True: - try: - entry = next(scandir_it) - except StopIteration: - break - if not entry.is_dir(): + for entry in scandir_it: + if not entry.is_dir(follow_symlinks=True): nondirs.append(entry.path) else: if entry.is_symlink() and not os.path.exists(entry.path): log.error(f'Files broken symlink: {entry.path}') else: walk_dirs.append(entry.path) - yield Directory(top, nondirs, walk_dirs) + + yield Directory(path=top, files=nondirs, directories=walk_dirs, mtime=top_mtime) + if recurse: for new_path in walk_dirs: if callable(recurse) and not recurse(new_path): @@ -180,13 +193,13 @@ def _walk(top, recurse:RecursiveType=True) -> Directory: yield from _walk(new_path, recurse=recurse) -def _cached_walk(top, recurse:RecursiveType=True) -> Directory: - top = get_directory(top) - if not top: +def _cached_walk(top: str, recurse: RecursiveType = True) -> Iterator[Directory]: + top_dir = get_directory(top) + if not top_dir: return - yield top + yield top_dir if recurse: - for child_directory in top.directories: + for child_directory in top_dir.directories: if os.path.basename(child_directory).startswith('models--'): continue if callable(recurse) and not recurse(child_directory): @@ -194,28 +207,27 @@ def _cached_walk(top, recurse:RecursiveType=True) -> Directory: yield from _cached_walk(child_directory, recurse=recurse) -def walk(top, recurse:RecursiveType=True, cached=True) -> Directory: +def walk(top: str, recurse: RecursiveType = True, cached: bool = True) -> Iterator[Directory]: yield from _cached_walk(top, recurse=recurse) if cached else _walk(top, recurse=recurse) -def delete_cached_directory(directory_path:str) -> bool: - global cache_folders # pylint: disable=W0602 +def delete_cached_directory(directory_path: str) -> bool: if directory_path in cache_folders: del cache_folders[directory_path] + return True + return False -def is_directory(dir_path:str) -> bool: - return dir_path and os.path.exists(dir_path) and os.path.isdir(dir_path) +def is_directory(dir_path: str) -> bool: + return bool(dir_path and os.path.isdir(dir_path)) -def directory_mtime(directory_path:str, /, recursive:RecursiveType=True) -> float: - return float(max(0, *[directory.mtime for directory in get_directories(directory_path, recursive=recursive)])) +def directory_mtime(directory_path: str, /, recursive: RecursiveType = True) -> float: + dirs = get_directories(directory_path, recursive=recursive) + return max((d.mtime for d in dirs), default=0.0) -def unique_directories(directories:DirectoryPathList, /, recursive:RecursiveType=True) -> DirectoryPathIterator: - '''Ensure no empty, or duplicates''' - '''If we are going recursive, then directories that are children of other directories are redundant''' - ''' @todo this is incredibly inneficient. the hit is small, but it is ugly, no? ''' +def unique_directories(directories: DirectoryPathList, /, recursive: RecursiveType = True) -> DirectoryPathIterator: directories = sorted(unique_paths(directories), reverse=True) while directories: directory = directories.pop() @@ -231,74 +243,81 @@ def unique_directories(directories:DirectoryPathList, /, recursive:RecursiveType child_directory = directories[-1][len(directory):] if child_directory: next_directory = _directory - if not callable(recursive): - _remove_directory = next_directory - else: - for sub_directory in child_directory.split(os.path.sep): - next_directory = os.path.join(next_directory, sub_directory) - if recursive(next_directory): - _remove_directory = os.path.join(next_directory, '') - break + _remove_directory = None + for sub_directory in child_directory.split(os.path.sep): + next_directory = os.path.join(next_directory, sub_directory) + if recursive(next_directory): + _remove_directory = os.path.join(next_directory, '') + break while _remove_directory and directories: - _d = directories.pop() if not directories[-1].startswith(_remove_directory): - del _remove_directory + break + directories.pop() -def unique_paths(directory_paths:DirectoryPathList) -> DirectoryPathIterator: - realpaths = (real_path(directory_path) for directory_path in filter(bool, directory_paths)) - return {real_directory_path: True for real_directory_path in filter(bool, realpaths)}.keys() +def unique_paths(directory_paths: DirectoryPathList) -> DirectoryPathIterator: + seen = set() + for path in directory_paths: + if path: + r = real_path(path) + if r and r not in seen: + seen.add(r) + yield r -def get_directories(*directory_paths: DirectoryPathList, fetch:bool=True, recursive:RecursiveType=True) -> DirectoryCollection: - directory_paths = unique_directories(directory_paths, recursive=recursive) - directories = (get_directory(directory_path, fetch=fetch) for directory_path in directory_paths) - return filter(bool, directories) +def get_directories(*directory_paths: DirectoryPathList, fetch: bool = True, recursive: RecursiveType = True) -> DirectoryCollection: + dirs = unique_directories(directory_paths, recursive=recursive) + return [d for d in (get_directory(p, fetch=fetch) for p in dirs) if d] -def directory_files(*directories_or_paths: DirectoryPathList | DirectoryList, recursive: RecursiveType=True) -> FilePathIterator: - return itertools.chain.from_iterable( - itertools.chain( - directory_object.files, - [] - if not recursive - else itertools.chain.from_iterable( - directory_files(directory, recursive=recursive) - for directory - in filter( - bool, - map(get_directory, filter(((bool if recursive else False) if not callable(recursive) else recursive), directory_object.directories)) - ) - ) - ) - for directory_object - in filter(bool, map(get_directory, directories_or_paths)) - ) +def directory_files(*directories_or_paths: DirectoryPathList | DirectoryList, recursive: RecursiveType = True) -> FilePathIterator: + """Iterative directory file gatherer avoiding deeply nested generator recursion.""" + visited = set() + stack = list(directories_or_paths) + + while stack: + item = stack.pop() + dir_obj = get_directory(item) if not isinstance(item, Directory) else item + if not dir_obj or dir_obj.path in visited: + continue + + visited.add(dir_obj.path) + yield from dir_obj.files + + if recursive: + for child_path in dir_obj.directories: + if callable(recursive) and not recursive(child_path): + continue + stack.append(child_path) -def extension_filter(ext_filter: ExtensionList | None=None, ext_blacklist: ExtensionList | None=None) -> ExtensionFilter: - if ext_filter: - ext_filter = [*map(str.upper, ext_filter)] - if ext_blacklist: - ext_blacklist = [*map(str.upper, ext_blacklist)] - def filter_functon(fp:str): - return (not ext_filter or any(fp.upper().endswith(ew) for ew in ext_filter)) and (not ext_blacklist or not any(fp.upper().endswith(ew) for ew in ext_blacklist)) - return filter_functon +def extension_filter(ext_filter: ExtensionList | None = None, ext_blacklist: ExtensionList | None = None) -> ExtensionFilter: + """Fast C-level tuple.endswith checks.""" + valid_exts = tuple(ext.lower() if ext.startswith('.') else f'.{ext.lower()}' for ext in ext_filter) if ext_filter else None + black_exts = tuple(ext.lower() if ext.startswith('.') else f'.{ext.lower()}' for ext in ext_blacklist) if ext_blacklist else None + + def filter_function(fp: str) -> bool: + fp_lower = fp.lower() + if valid_exts and not fp_lower.endswith(valid_exts): + return False + if black_exts and fp_lower.endswith(black_exts): + return False + return True + + return filter_function def not_hidden(filepath: str) -> bool: return not os.path.basename(filepath).startswith('.') -def filter_files(file_paths: FilePathList, ext_filter: ExtensionList | None=None, ext_blacklist: ExtensionList | None=None) -> FilePathIterator: +def filter_files(file_paths: FilePathList, ext_filter: ExtensionList | None = None, ext_blacklist: ExtensionList | None = None) -> FilePathIterator: return filter(extension_filter(ext_filter, ext_blacklist), file_paths) -def list_files(*directory_paths:DirectoryPathList, ext_filter: ExtensionList | None=None, ext_blacklist: ExtensionList | None=None, recursive:RecursiveType=True) -> FilePathIterator: - return filter_files(itertools.chain.from_iterable( - directory_files(directory, recursive=recursive) - for directory in get_directories(*directory_paths, recursive=recursive) - ), ext_filter, ext_blacklist) +def list_files(*directory_paths: DirectoryPathList, ext_filter: ExtensionList | None = None, ext_blacklist: ExtensionList | None = None, recursive: RecursiveType = True) -> FilePathIterator: + raw_files = directory_files(*directory_paths, recursive=recursive) + return filter_files(raw_files, ext_filter, ext_blacklist) cache_folders = DirectoryCache({}) diff --git a/modules/loader.py b/modules/loader.py index ed47ab628..99d152491 100644 --- a/modules/loader.py +++ b/modules/loader.py @@ -78,6 +78,9 @@ except Exception: pass try: + # del torch._C._c10d_init + import torch.distributed # pylint: disable=ungrouped-imports + torch.distributed.is_available = lambda: False import torch.distributed.distributed_c10d as _c10d # pylint: disable=unused-import,ungrouped-imports except Exception: log.warning('Loader: torch is not built with distributed support') @@ -98,8 +101,9 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) warnings.filterwarnings(action="ignore", category=UserWarning, module="torchvision") torchvision = None try: + sys.modules['torchvision.samples'] = types.ModuleType("torchvision.samples") # monkey-patch to avoid torchvision sample download import torchvision # pylint: disable=W0611,C0411 - import pytorch_lightning # pytorch_lightning should be imported after torch, but it re-enables warnings on import so import once to disable them # pylint: disable=W0611,C0411 + # import pytorch_lightning # pytorch_lightning should be imported after torch, but it re-enables warnings on import so import once to disable them # pylint: disable=W0611,C0411 except Exception as e: report(f'torchvision=={torchvision.__version__ if torchvision is not None else None}', e) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 301edaa94..b674882c2 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -1,5 +1,6 @@ import os import io +from functools import lru_cache import random import re import time @@ -196,8 +197,12 @@ class ExtraNetworksPage: errors.display(e, 'Network version') return all_versions[0] + @lru_cache(maxsize=2048, typed=False) def link_preview(self, filename: str): - if not os.path.exists(filename): + if filename == 'ui/assets/missing.png': + return f"{shared.opts.subpath}/sdapi/v1/network/thumb?filename={filename}" + just_file = not bool(os.path.dirname(filename)) + if just_file or not os.path.exists(filename): ref = os.path.join(paths.reference_path, filename) if os.path.exists(ref): filename = ref @@ -456,6 +461,7 @@ class ExtraNetworksPage: errors.display(e, 'Networks') return "" + @lru_cache(maxsize=2048, typed=False) def find_preview_file(self, path: str | None): if path is None: return 'ui/assets/missing.png' @@ -477,6 +483,7 @@ class ExtraNetworksPage: return file return 'ui/assets/missing.png' + @lru_cache(maxsize=2048, typed=False) def find_preview(self, filename: str): t0 = time.time() preview_file = self.find_preview_file(filename) diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index 93a6e2f09..ecc8b007b 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -63,6 +63,7 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): v['tags'].append(f'Size: {size} GB') shared.reference_models[k] = v + models = [] for k, v in shared.reference_models.items(): count['total'] += 1 url = v['path'] @@ -126,14 +127,14 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): if ready: count['ready'] += 1 - yield { + model = { "type": 'Model', "name": name, "title": name, "filename": url, "preview": self.find_preview(os.path.join(paths.reference_path, preview)), "local_preview": preview_file, - "onclick": '"' + html.escape(f"selectReference({json.dumps(path)})") + '"', + "onclick": '"' + html.escape(f"selectReference({path})") + '"', "hash": None, "mtime": mtime, "size": size, @@ -143,7 +144,10 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): "version": version, "tags": v.get('tags', []), } + models.append(model) + # yield model log.debug(f'Networks: type="reference" {count}') + return models def create_item(self, name): record = None @@ -190,8 +194,7 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): item = future.result() if item is not None: items.append(item) - for record in self.list_reference(): - items.append(record) + items += self.list_reference() self.update_all_previews(items) return items diff --git a/modules/ui_extra_networks_wildcards.py b/modules/ui_extra_networks_wildcards.py index 8538eaeb4..9ca101867 100644 --- a/modules/ui_extra_networks_wildcards.py +++ b/modules/ui_extra_networks_wildcards.py @@ -1,5 +1,4 @@ import os -import json from modules import shared, ui_extra_networks, modelstats, files_cache from modules.logger import log @@ -44,22 +43,25 @@ class ExtraNetworksPageWildcards(ui_extra_networks.ExtraNetworksPage): relname = os.path.relpath(filename, shared.opts.wildcards_dir) name = os.path.splitext(relname)[0] size, mtime = modelstats.stat(filename) + records = [] try: record = { "type": 'Wildcard', "name": name, "filename": filename, - "preview": self.find_preview(filename), + # "preview": self.find_preview(filename), + "preview": None, "local_preview": f"{os.path.splitext(filename)[0]}.{shared.opts.samples_format}", - "prompt": json.dumps(f" __{name}__"), + "prompt": f" __{name}__", "mtime": mtime, "size": size, "description": '', "info": {}, } - yield record + records.append(record) except Exception as e: log.debug(f'Networks error: type=wildcard file="{filename}" {e}') + return records def allowed_directories_for_previews(self): return [v for v in [shared.opts.wildcards_dir] if v is not None] diff --git a/webui.py b/webui.py index 34d26e0a8..3188f0e0f 100644 --- a/webui.py +++ b/webui.py @@ -12,6 +12,7 @@ from threading import Thread from installer import git_commit, custom_excepthook, version from modules.logger import log from modules import timer +import modules.errors import modules.loader import modules.hashes import modules.paths @@ -155,6 +156,8 @@ def initialize(): # make the program just exit at ctrl+c without waiting for anything def sigint_handler(_sig, _frame): log.trace(f'State history: uptime={round(time.time() - shared.state.server_start)} jobs={shared.state.job_history} tasks={shared.state.task_history} latents={shared.state.latent_history} images={shared.state.image_history}') + if modules.errors._profiler is not None: # pylint: disable=protected-access + modules.errors.profile_print('SIGINT') log.info('Exiting') try: for f in glob.glob("*.lock"): @@ -393,7 +396,7 @@ def start_ui(): return app -def webui(restart=False, _exit=False): +def webui(restart=False, _exit=False, profiler=None): if restart: modules.script_callbacks.app_reload_callback() modules.script_callbacks.script_unloaded_callback() @@ -422,6 +425,7 @@ def webui(restart=False, _exit=False): log.info(f"Launch time: {timer.launch.summary(min_time=0)}") log.info(f"Installer time: {timer.init.summary(min_time=0)}") log.info(f"Startup time: {timer.startup.summary(min_time=0)}") + modules.errors._profiler = profiler # pylint: disable=protected-access else: timer.startup.add('launch', timer.launch.get_total()) timer.startup.add('installer', timer.init.get_total())