Ver Fonte

Added sysinfo tab to settings

AUTOMATIC há 2 anos atrás
pai
commit
7393c1f99c
5 ficheiros alterados com 212 adições e 2 exclusões
  1. 26 0
      modules/errors.py
  2. 135 0
      modules/sysinfo.py
  3. 14 1
      modules/ui.py
  4. 27 1
      modules/ui_settings.py
  5. 10 0
      style.css

+ 26 - 0
modules/errors.py

@@ -3,10 +3,30 @@ import textwrap
 import traceback
 
 
+exception_records = []
+
+
+def record_exception():
+    _, e, tb = sys.exc_info()
+    if e is None:
+        return
+
+    if exception_records and exception_records[-1] == e:
+        return
+
+    exception_records.append((e, tb))
+
+    if len(exception_records) > 5:
+        exception_records.pop(0)
+
+
 def report(message: str, *, exc_info: bool = False) -> None:
     """
     Print an error message to stderr, with optional traceback.
     """
+
+    record_exception()
+
     for line in message.splitlines():
         print("***", line, file=sys.stderr)
     if exc_info:
@@ -15,6 +35,8 @@ def report(message: str, *, exc_info: bool = False) -> None:
 
 
 def print_error_explanation(message):
+    record_exception()
+
     lines = message.strip().split("\n")
     max_len = max([len(x) for x in lines])
 
@@ -25,6 +47,8 @@ def print_error_explanation(message):
 
 
 def display(e: Exception, task, *, full_traceback=False):
+    record_exception()
+
     print(f"{task or 'error'}: {type(e).__name__}", file=sys.stderr)
     te = traceback.TracebackException.from_exception(e)
     if full_traceback:
@@ -44,6 +68,8 @@ already_displayed = {}
 
 
 def display_once(e: Exception, task):
+    record_exception()
+
     if task in already_displayed:
         return
 

+ 135 - 0
modules/sysinfo.py

@@ -0,0 +1,135 @@
+import json
+import os
+import sys
+import traceback
+
+import platform
+import hashlib
+import pkg_resources
+import psutil
+import re
+
+import launch
+from modules import paths_internal, timer
+
+checksum_token = "DontStealMyGamePlz__WINNERS_DONT_USE_DRUGS__DONT_COPY_THAT_FLOPPY"
+
+
+def pretty_bytes(num, suffix="B"):
+    for unit in ["", "K", "M", "G", "T", "P", "E", "Z", "Y"]:
+        if abs(num) < 1024 or unit == 'Y':
+            return f"{num:.0f}{unit}{suffix}"
+        num /= 1024
+
+
+def get():
+    res = get_dict()
+
+    text = json.dumps(res, ensure_ascii=False, indent=4)
+
+    h = hashlib.sha256(text.encode("utf8"))
+    text = text.replace(checksum_token, h.hexdigest())
+
+    return text
+
+
+re_checksum = re.compile(r'"Checksum": "([0-9a-fA-F]{64})"')
+
+
+def check(x):
+    m = re.search(re_checksum, x)
+    if not m:
+        return False
+
+    replaced = re.sub(re_checksum, f'"Checksum": "{checksum_token}"', x)
+
+    h = hashlib.sha256(replaced.encode("utf8"))
+    return h.hexdigest() == m.group(1)
+
+
+def get_dict():
+    ram = psutil.virtual_memory()
+
+    res = {
+        "Platform": platform.platform(),
+        "Python": platform.python_version(),
+        "Version": launch.git_tag(),
+        "Commit": launch.commit_hash(),
+        "Script path": paths_internal.script_path,
+        "Data path": paths_internal.data_path,
+        "Extensions dir": paths_internal.extensions_dir,
+        "Checksum": checksum_token,
+        "Commandline": sys.argv,
+        "Torch env info": get_torch_sysinfo(),
+        "Exceptions": get_exceptions(),
+        "CPU": {
+            "model": platform.processor(),
+            "count logical": psutil.cpu_count(logical=True),
+            "count physical": psutil.cpu_count(logical=False),
+        },
+        "RAM": {
+            x: pretty_bytes(getattr(ram, x, 0)) for x in ["total", "used", "free", "active", "inactive", "buffers", "cached", "shared"] if getattr(ram, x, 0) != 0
+        },
+        "Extensions": get_extensions(enabled=True),
+        "Inactive extensions": get_extensions(enabled=False),
+        "Environment": {k: os.environ[k] for k in sorted(os.environ)},
+        "Config": get_config(),
+        "Startup": timer.startup_record,
+        "Packages": sorted([f"{pkg.key}=={pkg.version}" for pkg in pkg_resources.working_set]),
+    }
+
+    return res
+
+
+def format_traceback(tb):
+    return [[f"{x.filename}, line {x.lineno}, {x.name}", x.line] for x in traceback.extract_tb(tb)]
+
+
+def get_exceptions():
+    try:
+        from modules import errors
+        items = [x for x in reversed(errors.exception_records)]
+
+        return [{"exception": str(e), "traceback": format_traceback(tb)} for e, tb in items]
+    except Exception as e:
+        return str(e)
+
+
+re_newline = re.compile(r"\r*\n")
+
+
+def get_torch_sysinfo():
+    try:
+        import torch.utils.collect_env
+        info = torch.utils.collect_env.get_env_info()._asdict()
+
+        return {k: re.split(re_newline, str(v)) if "\n" in str(v) else v for k, v in info.items()}
+    except Exception as e:
+        return str(e)
+
+
+def get_extensions(*, enabled):
+
+    try:
+        from modules import extensions
+
+        def to_json(x: extensions.Extension):
+            return {
+                "name": x.name,
+                "path": x.path,
+                "version": x.version,
+                "branch": x.branch,
+                "remote": x.remote,
+            }
+
+        return [to_json(x) for x in extensions.extensions if not x.is_builtin and x.enabled == enabled]
+    except Exception as e:
+        return str(e)
+
+
+def get_config():
+    try:
+        from modules import shared
+        return shared.opts.data
+    except Exception as e:
+        return str(e)

+ 14 - 1
modules/ui.py

@@ -1,3 +1,4 @@
+import datetime
 import json
 import mimetypes
 import os
@@ -11,7 +12,7 @@ import numpy as np
 from PIL import Image, PngImagePlugin  # noqa: F401
 from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call
 
-from modules import sd_hijack, sd_models, script_callbacks, ui_extensions, deepbooru, sd_vae, extra_networks, ui_common, ui_postprocessing, progress, ui_loadsave, errors, shared_items, ui_settings, timer
+from modules import sd_hijack, sd_models, script_callbacks, ui_extensions, deepbooru, sd_vae, extra_networks, ui_common, ui_postprocessing, progress, ui_loadsave, errors, shared_items, ui_settings, timer, sysinfo
 from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML
 from modules.paths import script_path
 from modules.ui_common import create_refresh_button
@@ -1598,3 +1599,15 @@ def setup_ui_api(app):
     app.add_api_route("/internal/ping", lambda: {}, methods=["GET"])
 
     app.add_api_route("/internal/profile-startup", lambda: timer.startup_record, methods=["GET"])
+
+    def download_sysinfo(attachment=False):
+        from fastapi.responses import PlainTextResponse
+
+        text = sysinfo.get()
+        filename = f"sysinfo-{datetime.datetime.utcnow().strftime('%Y-%m-%d-%H-%M')}.txt"
+
+        return PlainTextResponse(text, headers={'Content-Disposition': f'{"attachment" if attachment else "inline"}; filename="{filename}"'})
+
+    app.add_api_route("/internal/sysinfo", download_sysinfo, methods=["GET"])
+    app.add_api_route("/internal/sysinfo-download", lambda: download_sysinfo(attachment=True), methods=["GET"])
+

+ 27 - 1
modules/ui_settings.py

@@ -1,6 +1,6 @@
 import gradio as gr
 
-from modules import ui_common, shared, script_callbacks, scripts, sd_models
+from modules import ui_common, shared, script_callbacks, scripts, sd_models, sysinfo
 from modules.call_queue import wrap_gradio_call
 from modules.shared import opts
 from modules.ui_components import FormRow
@@ -157,6 +157,17 @@ class UiSettings:
                 with gr.TabItem("Defaults", id="defaults", elem_id="settings_tab_defaults"):
                     loadsave.create_ui()
 
+                with gr.TabItem("Sysinfo", id="sysinfo", elem_id="settings_tab_sysinfo"):
+                    gr.HTML('<a href="./internal/sysinfo-download" download>Download system info</a>', elem_id="sysinfo_download")
+
+                    with gr.Row():
+                        with gr.Column(scale=1):
+                            sysinfo_check_file = gr.File(label="Check system info for validity", type='binary')
+                        with gr.Column(scale=1):
+                            sysinfo_check_output = gr.HTML("", elem_id="sysinfo_validity")
+                        with gr.Column(scale=100):
+                            pass
+
                 with gr.TabItem("Actions", id="actions", elem_id="settings_tab_actions"):
                     request_notifications = gr.Button(value='Request browser notifications', elem_id="request_notifications")
                     download_localization = gr.Button(value='Download localization template', elem_id="download_localization")
@@ -215,6 +226,21 @@ class UiSettings:
                 outputs=[],
             )
 
+            def check_file(x):
+                if x is None:
+                    return ''
+
+                if sysinfo.check(x.decode('utf8', errors='ignore')):
+                    return 'Valid'
+
+                return 'Invalid'
+
+            sysinfo_check_file.change(
+                fn=check_file,
+                inputs=[sysinfo_check_file],
+                outputs=[sysinfo_check_output],
+            )
+
         self.interface = settings_interface
 
     def add_quicksettings(self):

+ 10 - 0
style.css

@@ -450,6 +450,16 @@ table.popup-table .link{
     opacity: 0.75;
 }
 
+#sysinfo_download a{
+    font-size: 24pt;
+    text-decoration: underline;
+}
+
+#sysinfo_validity{
+    font-size: 18pt;
+}
+
+
 /* live preview */
 .progressDiv{
     position: relative;