meltygui 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meltygui/__init__.py +107 -0
- meltygui/accounts/__init__.py +0 -0
- meltygui/accounts/internet_accounts.py +1355 -0
- meltygui/chat/__init__.py +91 -0
- meltygui/chat/activity.py +75 -0
- meltygui/chat/backends.py +36 -0
- meltygui/chat/chat_interface.py +732 -0
- meltygui/chat/chat_proxy.py +352 -0
- meltygui/chat/codex_proxy.py +592 -0
- meltygui/chat/codex_settings.py +100 -0
- meltygui/chat/codex_transport.py +60 -0
- meltygui/chat/command_parser.py +204 -0
- meltygui/chat/images.py +227 -0
- meltygui/chat/messages.py +417 -0
- meltygui/chat/metadata.py +139 -0
- meltygui/chat/writer_locks.py +64 -0
- meltygui/code/__init__.py +0 -0
- meltygui/code/basic_converters.py +533 -0
- meltygui/code/chain_converters.py +2111 -0
- meltygui/code/code_checks.py +2209 -0
- meltygui/code/core_syntax.py +1430 -0
- meltygui/code/file_converters.py +1933 -0
- meltygui/code/fileref.py +702 -0
- meltygui/code/hotswap_guard.py +144 -0
- meltygui/code/libcst_conversion.py +9724 -0
- meltygui/code/live_instrument.py +392 -0
- meltygui/code/live_view.py +2490 -0
- meltygui/code/melty_scan.py +2684 -0
- meltygui/code/new_codecs.py +1255 -0
- meltygui/code/new_converters.py +3017 -0
- meltygui/code/project_code.py +278 -0
- meltygui/code/source_context.py +63 -0
- meltygui/code/symbol_roster.py +1588 -0
- meltygui/code/syntax_check.py +34 -0
- meltygui/code/syntax_check_worker.py +114 -0
- meltygui/completion/__init__.py +0 -0
- meltygui/completion/fim.py +1232 -0
- meltygui/completion/fim_context.py +481 -0
- meltygui/completion/providers/__init__.py +0 -0
- meltygui/completion/providers/anthropic_oauth.py +446 -0
- meltygui/completion/providers/anthropic_requests.py +69 -0
- meltygui/completion/providers/claude.py +203 -0
- meltygui/completion/providers/claude_usage.py +499 -0
- meltygui/completion/providers/codex_accounts.py +180 -0
- meltygui/completion/providers/copilot.py +617 -0
- meltygui/completion/providers/oauth_popup.py +220 -0
- meltygui/completion/providers/ollama.py +320 -0
- meltygui/completion/providers/profiles.py +23 -0
- meltygui/core/README.md +88 -0
- meltygui/core/__init__.py +1 -0
- meltygui/core/automation/__init__.py +1 -0
- meltygui/core/automation/action_core.py +153 -0
- meltygui/core/automation/collection_action.py +45 -0
- meltygui/core/automation/mcp_eval.py +167 -0
- meltygui/core/automation/mcp_hotswap.py +107 -0
- meltygui/core/automation/mcp_query.py +552 -0
- meltygui/core/automation/mcp_server.py +657 -0
- meltygui/core/automation/orchestration_core.py +2636 -0
- meltygui/core/automation/query_core.py +124 -0
- meltygui/core/automation/search_core.py +26 -0
- meltygui/core/automation/selector_core.py +340 -0
- meltygui/core/automation/value_core.py +1752 -0
- meltygui/core/cache/__init__.py +1 -0
- meltygui/core/cache/cache_diagnostics.py +0 -0
- meltygui/core/cache/invalidation_decoration.py +153 -0
- meltygui/core/cache/invalidation_tracker.py +43 -0
- meltygui/core/cache/tile_cache.py +5968 -0
- meltygui/core/conversion/__init__.py +1 -0
- meltygui/core/conversion/bubbling.py +599 -0
- meltygui/core/conversion/cache_tree.py +188 -0
- meltygui/core/conversion/chain.py +113 -0
- meltygui/core/conversion/converter_register.py +145 -0
- meltygui/core/conversion/data_decoration.py +67 -0
- meltygui/core/conversion/dict_conversion.py +1815 -0
- meltygui/core/conversion/dict_conversion_util.py +177 -0
- meltygui/core/conversion/dynamic_obj.py +89 -0
- meltygui/core/conversion/graph_compare.py +183 -0
- meltygui/core/conversion/load_save_v2.py +1032 -0
- meltygui/core/conversion/missing_saved_class.py +39 -0
- meltygui/core/conversion/path_finder.py +606 -0
- meltygui/core/conversion/render_host.py +1083 -0
- meltygui/core/core_render.py +6537 -0
- meltygui/core/definition_hotswap.py +231 -0
- meltygui/core/diagnostics/__init__.py +1 -0
- meltygui/core/diagnostics/attribute_churn.py +38 -0
- meltygui/core/diagnostics/fps_counter.py +39 -0
- meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
- meltygui/core/diagnostics/inspection_core.py +169 -0
- meltygui/core/diagnostics/monitor_core.py +105 -0
- meltygui/core/diagnostics/notifications.py +706 -0
- meltygui/core/diagnostics/perf_trace.py +281 -0
- meltygui/core/diagnostics/profile_decoration.py +88 -0
- meltygui/core/diagnostics/resize_trace.py +62 -0
- meltygui/core/diagnostics/screenshot_core.py +247 -0
- meltygui/core/diagnostics/session_status.py +98 -0
- meltygui/core/diagnostics/trace_core.py +445 -0
- meltygui/core/files/__init__.py +1 -0
- meltygui/core/files/file_core.py +208 -0
- meltygui/core/files/file_explorer_core.py +104 -0
- meltygui/core/files/file_tree_core.py +198 -0
- meltygui/core/files/file_watch_core.py +43 -0
- meltygui/core/files/import_graph_core.py +43 -0
- meltygui/core/files/metadata_core.py +51 -0
- meltygui/core/graphics/__init__.py +1 -0
- meltygui/core/graphics/cuda_context_core.py +166 -0
- meltygui/core/graphics/cuda_interop_core.py +136 -0
- meltygui/core/graphics/cuda_kernel_core.py +91 -0
- meltygui/core/graphics/framebuffer_recorder.py +337 -0
- meltygui/core/graphics/gl_state.py +658 -0
- meltygui/core/graphics/lut_core.py +52 -0
- meltygui/core/graphics/overlay_renderer.py +984 -0
- meltygui/core/graphics/scene_target.py +180 -0
- meltygui/core/graphics/screenshot.py +439 -0
- meltygui/core/graphics/shader_func.py +478 -0
- meltygui/core/graphics/tensor_core.py +45 -0
- meltygui/core/graphics/text_texture.py +329 -0
- meltygui/core/graphics/wayland_color.py +635 -0
- meltygui/core/input/__init__.py +1 -0
- meltygui/core/input/collision.py +165 -0
- meltygui/core/input/drag_drop_core.py +1525 -0
- meltygui/core/input/hypr_left_drag.py +323 -0
- meltygui/core/input/input_core.py +245 -0
- meltygui/core/input/input_handler.py +1101 -0
- meltygui/core/input/mouse_cursor.py +355 -0
- meltygui/core/input/pynput_backend.py +1054 -0
- meltygui/core/input/space_mouse.py +338 -0
- meltygui/core/input/touchpad_backend.py +393 -0
- meltygui/core/input/view_selection.py +177 -0
- meltygui/core/layout/__init__.py +1 -0
- meltygui/core/layout/column_core.py +2153 -0
- meltygui/core/layout/cursor_core.py +161 -0
- meltygui/core/layout/dropdown_core.py +278 -0
- meltygui/core/layout/edge_constraints.py +155 -0
- meltygui/core/layout/grid_core.py +117 -0
- meltygui/core/layout/header_core.py +23 -0
- meltygui/core/layout/header_runtime.py +67 -0
- meltygui/core/layout/layout_core.py +87 -0
- meltygui/core/layout/tile_manager_core.py +591 -0
- meltygui/core/melty.py +6726 -0
- meltygui/core/module_map.json +896 -0
- meltygui/core/module_names.py +19 -0
- meltygui/core/rendering/__init__.py +1 -0
- meltygui/core/rendering/core_decoration.py +431 -0
- meltygui/core/rendering/core_render_helpers.py +328 -0
- meltygui/core/rendering/func_metadata.py +398 -0
- meltygui/core/rendering/mode.py +818 -0
- meltygui/core/rendering/mode_defaults.py +41 -0
- meltygui/core/rendering/modes.py +136 -0
- meltygui/core/rendering/parameter_core.py +1665 -0
- meltygui/core/rendering/render_dispatch.py +1891 -0
- meltygui/core/rendering/render_funcs.py +273 -0
- meltygui/core/rendering/shaped.py +312 -0
- meltygui/core/rendering/window_decoration.py +25 -0
- meltygui/core/runtime/__init__.py +1 -0
- meltygui/core/runtime/app.py +735 -0
- meltygui/core/runtime/app_session.py +140 -0
- meltygui/core/runtime/background.py +564 -0
- meltygui/core/runtime/extensions.py +53 -0
- meltygui/core/runtime/gc_manager.py +1163 -0
- meltygui/core/runtime/lifecycle.py +21 -0
- meltygui/core/runtime/paths.py +27 -0
- meltygui/core/runtime/settings.py +14 -0
- meltygui/core/runtime/singleton.py +16 -0
- meltygui/core/runtime/thread_safe_bool.py +24 -0
- meltygui/core/runtime/thread_signal.py +30 -0
- meltygui/core/runtime/toggles.py +3115 -0
- meltygui/core/services/__init__.py +1 -0
- meltygui/core/services/account_core.py +10 -0
- meltygui/core/services/chat_core.py +19 -0
- meltygui/core/services/claude_terminal_core.py +346 -0
- meltygui/core/services/terminal_core.py +458 -0
- meltygui/core/services/terminal_runtime.py +78 -0
- meltygui/core/styling/__init__.py +1 -0
- meltygui/core/styling/color_core.py +46 -0
- meltygui/core/styling/fonts.py +639 -0
- meltygui/core/styling/global_style.py +338 -0
- meltygui/core/styling/style.py +198 -0
- meltygui/core/styling/style_core.py +522 -0
- meltygui/core/styling/warm_start.py +149 -0
- meltygui/core/windowing/__init__.py +1 -0
- meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
- meltygui/core/windowing/backends/__init__.py +1 -0
- meltygui/core/windowing/backends/imgui_renderer.py +138 -0
- meltygui/core/windowing/backends/native_wayland.py +850 -0
- meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
- meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
- meltygui/core/windowing/backends/wayland_protocol.py +101 -0
- meltygui/core/windowing/dock_core.py +182 -0
- meltygui/core/windowing/frame_geometry.py +42 -0
- meltygui/core/windowing/geometry_feed.py +864 -0
- meltygui/core/windowing/glfw_utils.py +1343 -0
- meltygui/core/windowing/os_frame.py +1552 -0
- meltygui/core/windowing/surface.py +643 -0
- meltygui/core/windowing/titlebar.py +1560 -0
- meltygui/core/windowing/titlebar_buttons.py +281 -0
- meltygui/core/windowing/wayland_move.py +932 -0
- meltygui/core/windowing/window_api.py +62 -0
- meltygui/core/windowing/window_constants.py +339 -0
- meltygui/core/windowing/window_visibility.py +162 -0
- meltygui/debug/__init__.py +0 -0
- meltygui/debug/app_view_utils.py +9 -0
- meltygui/editor/__init__.py +0 -0
- meltygui/editor/bash_syntax.py +30 -0
- meltygui/editor/code_line_fast.py +152 -0
- meltygui/editor/diff.py +139 -0
- meltygui/editor/external_changes.py +159 -0
- meltygui/editor/file_header.py +41 -0
- meltygui/editor/live_usage.py +169 -0
- meltygui/editor/live_view_views.py +1803 -0
- meltygui/editor/pending_save.py +1351 -0
- meltygui/editor/roster_tints.py +547 -0
- meltygui/editor/source_preview.py +16 -0
- meltygui/editor/source_tools.py +10 -0
- meltygui/editor/source_ui.py +70 -0
- meltygui/editor/spell_check.py +77 -0
- meltygui/editor/text_editor.py +9076 -0
- meltygui/editor/usage_picker.py +538 -0
- meltygui/events/__init__.py +0 -0
- meltygui/events/example.py +118 -0
- meltygui/examples/__init__.py +0 -0
- meltygui/examples/columns_demo.py +33 -0
- meltygui/examples/columns_window_demo.py +60 -0
- meltygui/examples/context_menu_demo.py +24 -0
- meltygui/examples/context_menu_window_demo.py +49 -0
- meltygui/examples/gui_playground.py +127 -0
- meltygui/examples/live_view_playground.py +256 -0
- meltygui/examples/lora.py +43 -0
- meltygui/examples/lora_data.py +59 -0
- meltygui/examples/lora_policies.py +67 -0
- meltygui/examples/mode_demo.py +49 -0
- meltygui/examples/modifies_demo.py +119 -0
- meltygui/examples/scalar_policies.py +60 -0
- meltygui/examples/style_layouts.py +149 -0
- meltygui/examples/tile_manager_demo.py +74 -0
- meltygui/examples/tint_demo.py +160 -0
- meltygui/examples/tint_functions.py +56 -0
- meltygui/examples/trace_demo.py +88 -0
- meltygui/examples/two_windows.py +36 -0
- meltygui/files/__init__.py +0 -0
- meltygui/files/fast_file_explorer.py +439 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
- meltygui/graphics/__init__.py +6 -0
- meltygui/graphics/base.py +85 -0
- meltygui/graphics/examples.py +507 -0
- meltygui/graphics/executor.py +520 -0
- meltygui/graphics/filter.py +667 -0
- meltygui/graphics/filter.pyi +856 -0
- meltygui/graphics/generate_stubs.py +22 -0
- meltygui/graphics/registry.py +203 -0
- meltygui/graphics/shader_compiler.py +155 -0
- meltygui/graphics/shaders.py +1302 -0
- meltygui/graphics/stub_generator.py +250 -0
- meltygui/graphics/texture_manager.py +170 -0
- meltygui/graphics/texture_min_max.py +295 -0
- meltygui/hdr_color.py +757 -0
- meltygui/image_load.py +308 -0
- meltygui/model/__init__.py +1 -0
- meltygui/model/account_model.py +142 -0
- meltygui/model/camera_model.py +159 -0
- meltygui/model/chat_model.py +86 -0
- meltygui/model/code_model.py +62 -0
- meltygui/model/code_proxy_model.py +876 -0
- meltygui/model/collection_model.py +41 -0
- meltygui/model/color_model.py +127 -0
- meltygui/model/cuda_tensor_model.py +36 -0
- meltygui/model/cuda_texture_model.py +149 -0
- meltygui/model/dropdown_model.py +137 -0
- meltygui/model/file_metadata_model.py +73 -0
- meltygui/model/file_model.py +289 -0
- meltygui/model/format_model.py +390 -0
- meltygui/model/graph_model.py +98 -0
- meltygui/model/icon_model.py +1024 -0
- meltygui/model/import_graph_model.py +468 -0
- meltygui/model/layout_model.py +15 -0
- meltygui/model/lut_model.py +266 -0
- meltygui/model/search_model.py +173 -0
- meltygui/model/tensor_model.py +402 -0
- meltygui/model/terminal_model.py +329 -0
- meltygui/model/texture_model.py +146 -0
- meltygui/model/tile_model.py +24 -0
- meltygui/model/trace_model.py +198 -0
- meltygui/model/trace_report_model.py +146 -0
- meltygui/models/__init__.py +0 -0
- meltygui/models/file_meta.py +613 -0
- meltygui/models/function_console.py +184 -0
- meltygui/models/orchestration.py +48 -0
- meltygui/pbr.py +1576 -0
- meltygui/png_unfilter.c +49 -0
- meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
- meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
- meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
- meltygui/resources/dejavu/LICENSE.txt +78 -0
- meltygui/resources/fontawesome-LICENSE.txt +121 -0
- meltygui/resources/fontawesome-webfont.ttf +0 -0
- meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
- meltygui/resources/jetbrains-weights/OFL.txt +93 -0
- meltygui/resources/jetbrains-weights/README.md +2 -0
- meltygui/state/__init__.py +0 -0
- meltygui/state/account_state.py +22 -0
- meltygui/state/animation_state.py +97 -0
- meltygui/state/annotation_state.py +22 -0
- meltygui/state/chat_state.py +36 -0
- meltygui/state/code_state.py +10 -0
- meltygui/state/core_enums.py +59 -0
- meltygui/state/core_markers.py +115 -0
- meltygui/state/core_undo.py +1075 -0
- meltygui/state/file_state.py +75 -0
- meltygui/state/graph_state.py +26 -0
- meltygui/state/inspection_state.py +47 -0
- meltygui/state/menu_state.py +10 -0
- meltygui/state/model_enums.py +11 -0
- meltygui/state/new_core_model.py +2394 -0
- meltygui/state/orchestration_state.py +14 -0
- meltygui/state/query_state.py +24 -0
- meltygui/state/tensor_state.py +10 -0
- meltygui/state/terminal_state.py +23 -0
- meltygui/state/trace_state.py +32 -0
- meltygui/state/voxel_state.py +12 -0
- meltygui/text_index.py +816 -0
- meltygui/utils/__init__.py +0 -0
- meltygui/utils/jump_to_code.py +344 -0
- meltygui/utils/pkl_inspect.py +90 -0
- meltygui/utils/render_utils.py +1419 -0
- meltygui/view/__init__.py +1 -0
- meltygui/view/account_view.py +524 -0
- meltygui/view/action_view.py +73 -0
- meltygui/view/chat_decoration_view.py +112 -0
- meltygui/view/chat_view.py +1703 -0
- meltygui/view/code_view.py +2677 -0
- meltygui/view/collection_view.py +1185 -0
- meltygui/view/color_view.py +824 -0
- meltygui/view/control_view.py +559 -0
- meltygui/view/decoration_view.py +439 -0
- meltygui/view/diagnostic_view.py +177 -0
- meltygui/view/dropdown_view.py +1082 -0
- meltygui/view/file_view.py +1599 -0
- meltygui/view/graph_cuda_view.py +105 -0
- meltygui/view/graph_view.py +882 -0
- meltygui/view/header_view.py +848 -0
- meltygui/view/input_view.py +238 -0
- meltygui/view/inspection_view.py +1783 -0
- meltygui/view/layout_view.py +596 -0
- meltygui/view/lut_view.py +45 -0
- meltygui/view/menu_view.py +212 -0
- meltygui/view/orchestration_view.py +658 -0
- meltygui/view/query_view.py +118 -0
- meltygui/view/search_view.py +342 -0
- meltygui/view/tab_view.py +258 -0
- meltygui/view/tensor_view.py +430 -0
- meltygui/view/terminal_view.py +355 -0
- meltygui/view/text_view.py +8031 -0
- meltygui/view/texture_view.py +480 -0
- meltygui/view/tile_view.py +38 -0
- meltygui/view/trace_view.py +923 -0
- meltygui/view/voxel_cuda_view.py +824 -0
- meltygui/view/voxel_view.py +1860 -0
- meltygui/view/window_view.py +111 -0
- meltygui-0.1.0.dist-info/METADATA +143 -0
- meltygui-0.1.0.dist-info/RECORD +372 -0
- meltygui-0.1.0.dist-info/WHEEL +4 -0
- meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,1419 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import os
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from traceback import _parse_value_tb
|
|
6
|
+
|
|
7
|
+
import meltygui.core.windowing.window_api as glfw
|
|
8
|
+
import meltygui_imgui as imgui
|
|
9
|
+
from meltygui_imgui import ImGuiError
|
|
10
|
+
|
|
11
|
+
from meltygui.state.model_enums import RelaxedEnum
|
|
12
|
+
from meltygui.core.windowing.glfw_utils import _needs_render
|
|
13
|
+
from meltygui.core.windowing.glfw_utils import print_stack_trace
|
|
14
|
+
from meltygui.core.rendering.core_decoration import Core
|
|
15
|
+
from meltygui.core.runtime.singleton import singleton
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GroupType(Enum):
|
|
19
|
+
WINDOW = 0
|
|
20
|
+
CHILD = 1
|
|
21
|
+
FRAME = 2
|
|
22
|
+
STYLE = 3
|
|
23
|
+
COLOR = 4
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@singleton
|
|
27
|
+
class LSDView:
|
|
28
|
+
vis = None
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
self.group_stack = []
|
|
32
|
+
self.style_stack = []
|
|
33
|
+
self.color_stack = []
|
|
34
|
+
self.style_manager = None
|
|
35
|
+
self.obj_types = set()
|
|
36
|
+
self.vis = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def is_key_release(self, key=glfw.KEY_ESCAPE):
|
|
41
|
+
return (glfw.get_key(Core.melty.vis.window, key) == glfw.RELEASE and
|
|
42
|
+
key in Core.melty.vis.last_frame_keys)
|
|
43
|
+
|
|
44
|
+
def set_style_manager(self, style_manager):
|
|
45
|
+
"""
|
|
46
|
+
Set the style manager for this view.
|
|
47
|
+
:param style_manager: The style manager to set.
|
|
48
|
+
"""
|
|
49
|
+
self.style_manager = style_manager
|
|
50
|
+
|
|
51
|
+
def set_vis(self, vis):
|
|
52
|
+
"""
|
|
53
|
+
Set the visualizer for this view.
|
|
54
|
+
:param vis: The visualizer to set.
|
|
55
|
+
"""
|
|
56
|
+
self.vis = vis
|
|
57
|
+
|
|
58
|
+
def clear_unstack(self):
|
|
59
|
+
"""
|
|
60
|
+
Clear the current style and group stacks.
|
|
61
|
+
This is used to reset the view state.
|
|
62
|
+
"""
|
|
63
|
+
self.style_stack.clear()
|
|
64
|
+
self.color_stack.clear()
|
|
65
|
+
self.group_stack.clear()
|
|
66
|
+
|
|
67
|
+
def unstack_group(self):
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
for group_type in reversed(self.style_stack):
|
|
71
|
+
if group_type == GroupType.WINDOW:
|
|
72
|
+
imgui.end()
|
|
73
|
+
elif group_type == GroupType.CHILD:
|
|
74
|
+
imgui.end_child()
|
|
75
|
+
elif group_type == GroupType.FRAME:
|
|
76
|
+
imgui.end_frame()
|
|
77
|
+
elif group_type == GroupType.STYLE:
|
|
78
|
+
imgui.pop_style_var(1)
|
|
79
|
+
elif group_type == GroupType.COLOR:
|
|
80
|
+
imgui.pop_style_color(1)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
print(f"Error unstacking styles: {e}")
|
|
83
|
+
print(self.style_stack)
|
|
84
|
+
print_colored_traceback(*sys.exc_info(), limit=50)
|
|
85
|
+
|
|
86
|
+
self.style_stack.clear()
|
|
87
|
+
raise
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
for group_type in reversed(self.color_stack):
|
|
91
|
+
if group_type == GroupType.WINDOW:
|
|
92
|
+
imgui.end()
|
|
93
|
+
elif group_type == GroupType.CHILD:
|
|
94
|
+
imgui.end_child()
|
|
95
|
+
elif group_type == GroupType.FRAME:
|
|
96
|
+
imgui.end_frame()
|
|
97
|
+
elif group_type == GroupType.STYLE:
|
|
98
|
+
imgui.pop_style_var(1)
|
|
99
|
+
elif group_type == GroupType.COLOR:
|
|
100
|
+
imgui.pop_style_color(1)
|
|
101
|
+
except Exception as e:
|
|
102
|
+
print(f"Error unstacking colors: {e}")
|
|
103
|
+
|
|
104
|
+
print(self.color_stack)
|
|
105
|
+
print_colored_traceback(*sys.exc_info(), limit=50)
|
|
106
|
+
|
|
107
|
+
self.color_stack.clear()
|
|
108
|
+
raise
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
for group_type in reversed(self.group_stack):
|
|
112
|
+
if group_type == GroupType.WINDOW:
|
|
113
|
+
imgui.end()
|
|
114
|
+
elif group_type == GroupType.CHILD:
|
|
115
|
+
imgui.end_child()
|
|
116
|
+
elif group_type == GroupType.FRAME:
|
|
117
|
+
imgui.end_frame()
|
|
118
|
+
elif group_type == GroupType.STYLE:
|
|
119
|
+
imgui.pop_style_var(1)
|
|
120
|
+
elif group_type == GroupType.COLOR:
|
|
121
|
+
imgui.pop_style_color(1)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
print(f"Error unstacking group: {e}")
|
|
124
|
+
# Print out the stack
|
|
125
|
+
print_colored_traceback(*sys.exc_info(), limit=50)
|
|
126
|
+
|
|
127
|
+
print(self.group_stack)
|
|
128
|
+
|
|
129
|
+
self.group_stack.clear()
|
|
130
|
+
# Raise
|
|
131
|
+
raise
|
|
132
|
+
|
|
133
|
+
self.style_stack.clear()
|
|
134
|
+
self.group_stack.clear()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def list_width(str_list):
|
|
138
|
+
max_width = 0
|
|
139
|
+
for string in str_list:
|
|
140
|
+
width = imgui.calc_text_size(string).x
|
|
141
|
+
if width > max_width:
|
|
142
|
+
max_width = width
|
|
143
|
+
return max_width
|
|
144
|
+
|
|
145
|
+
def background(color, width=0, height=0):
|
|
146
|
+
"""
|
|
147
|
+
Draws a background rectangle with the specified color.
|
|
148
|
+
:param color: The color to fill the rectangle with.
|
|
149
|
+
:param width: The width of the rectangle. If 0, uses the available width.
|
|
150
|
+
:param height: The height of the rectangle. If 0, uses the available height.
|
|
151
|
+
"""
|
|
152
|
+
# Store position to restore later
|
|
153
|
+
cursor_pos = imgui.get_cursor_pos()
|
|
154
|
+
|
|
155
|
+
if width <= 0:
|
|
156
|
+
width = imgui.get_content_region_available().x
|
|
157
|
+
if height <= 0:
|
|
158
|
+
height = imgui.get_content_region_available().y
|
|
159
|
+
|
|
160
|
+
push_style_color(imgui.COLOR_WINDOW_BACKGROUND, *color)
|
|
161
|
+
begin_child("background", width, height, border=False, flags=imgui.WINDOW_NO_SCROLLBAR)
|
|
162
|
+
end_child()
|
|
163
|
+
pop_style_color(1)
|
|
164
|
+
|
|
165
|
+
# Restore cursor position
|
|
166
|
+
imgui.set_cursor_pos(cursor_pos)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def radio_buttons_enum(vis, name, selected_enum: RelaxedEnum, label_width=0, grey_out=False):
|
|
170
|
+
imgui.set_next_item_width(imgui.get_content_region_available().x)
|
|
171
|
+
selected_idx = 0
|
|
172
|
+
visible_name = name.split("##")[0]
|
|
173
|
+
changed = False
|
|
174
|
+
|
|
175
|
+
if grey_out:
|
|
176
|
+
push_style_var(imgui.STYLE_ALPHA, 0.5)
|
|
177
|
+
|
|
178
|
+
left_edge = imgui.get_cursor_pos_x()
|
|
179
|
+
margin = imgui.get_style().item_spacing.x * 2
|
|
180
|
+
if len(visible_name) > 0:
|
|
181
|
+
imgui.text(visible_name)
|
|
182
|
+
imgui.same_line()
|
|
183
|
+
if label_width > 0:
|
|
184
|
+
imgui.set_cursor_pos_x(left_edge + label_width + margin)
|
|
185
|
+
|
|
186
|
+
push_style_var(imgui.STYLE_ITEM_SPACING, (2, 4))
|
|
187
|
+
for i, option in enumerate(selected_enum.__class__):
|
|
188
|
+
pretty_name = option.name.replace("_", " ").capitalize()
|
|
189
|
+
if vis.square_radio_button(f"{pretty_name}##{name.split('##')[1]}", selected_enum.value == i):
|
|
190
|
+
selected_idx = i
|
|
191
|
+
changed = True
|
|
192
|
+
imgui.same_line()
|
|
193
|
+
imgui.new_line()
|
|
194
|
+
enum_class = selected_enum.__class__
|
|
195
|
+
selected_enum = enum_class(selected_idx)
|
|
196
|
+
pop_style_var(1)
|
|
197
|
+
|
|
198
|
+
if grey_out:
|
|
199
|
+
pop_style_var(1)
|
|
200
|
+
|
|
201
|
+
return changed, selected_enum
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def radio_buttons(vis, name, options, selected_idx):
|
|
205
|
+
imgui.set_next_item_width(imgui.get_content_region_available().x)
|
|
206
|
+
changed = False
|
|
207
|
+
|
|
208
|
+
visible_name = name.split("##")[0]
|
|
209
|
+
if len(visible_name) > 0:
|
|
210
|
+
imgui.text(visible_name)
|
|
211
|
+
push_style_var(imgui.STYLE_ITEM_SPACING, (2, 5))
|
|
212
|
+
for i, option in enumerate(options):
|
|
213
|
+
if vis.square_radio_button(f"{option}##{name}", selected_idx == i):
|
|
214
|
+
selected_idx = i
|
|
215
|
+
changed = True
|
|
216
|
+
imgui.same_line()
|
|
217
|
+
imgui.new_line()
|
|
218
|
+
|
|
219
|
+
pop_style_var(1)
|
|
220
|
+
return changed, selected_idx
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def radio_buttons_str(vis, name, options, selected_str):
|
|
224
|
+
imgui.set_next_item_width(imgui.get_content_region_available().x)
|
|
225
|
+
changed = False
|
|
226
|
+
|
|
227
|
+
visible_name = name.split("##")[0]
|
|
228
|
+
if len(visible_name) > 0:
|
|
229
|
+
imgui.text(visible_name)
|
|
230
|
+
push_style_var(imgui.STYLE_ITEM_SPACING, (2, 5))
|
|
231
|
+
for i, option in enumerate(options):
|
|
232
|
+
if vis.square_radio_button(f"{option}##{name}{i}", selected_str == option):
|
|
233
|
+
selected_str = option
|
|
234
|
+
changed = True
|
|
235
|
+
print(f"Selected: {selected_str}")
|
|
236
|
+
imgui.same_line()
|
|
237
|
+
imgui.new_line()
|
|
238
|
+
pop_style_var(1)
|
|
239
|
+
return changed, selected_str
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def button(text, width=0, height=0):
|
|
243
|
+
return imgui.button(text, width=width, height=height)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def text_wrapped(text, wrap_width=None):
|
|
247
|
+
"""
|
|
248
|
+
Alternative using internal text functions if available.
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
# Get the current window
|
|
252
|
+
start_pos = imgui.get_cursor_screen_pos()
|
|
253
|
+
|
|
254
|
+
# Render the text
|
|
255
|
+
imgui.text_wrapped(text)
|
|
256
|
+
|
|
257
|
+
# Try to access the last rendered position
|
|
258
|
+
# Some Python bindings expose these methods:
|
|
259
|
+
try:
|
|
260
|
+
# Get the draw list that was just used
|
|
261
|
+
draw_list = imgui.get_window_draw_list()
|
|
262
|
+
|
|
263
|
+
# The last vertex position might tell us where the text ended
|
|
264
|
+
# This is very implementation-specific
|
|
265
|
+
vtx_buffer = draw_list.vtx_buffer
|
|
266
|
+
if vtx_buffer:
|
|
267
|
+
# Last vertex might be the bottom-right of the last character
|
|
268
|
+
last_vtx = vtx_buffer[-1]
|
|
269
|
+
return (last_vtx.pos.x, last_vtx.pos.y)
|
|
270
|
+
except:
|
|
271
|
+
pass
|
|
272
|
+
|
|
273
|
+
# Fallback to item rect
|
|
274
|
+
rect_max = imgui.get_item_rect_max()
|
|
275
|
+
return (rect_max[0], rect_max[1] - imgui.get_text_line_height())
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def delete_button(text, width=0, height=0, white_text=True):
|
|
279
|
+
push_style_var(imgui.STYLE_FRAME_BORDERSIZE, 2)
|
|
280
|
+
|
|
281
|
+
if LSDView().style_manager is not None:
|
|
282
|
+
if white_text:
|
|
283
|
+
r, g, b, a = LSDView().style_manager.make_color_rgb(0.5, 0.0, 0.0)
|
|
284
|
+
push_style_color(imgui.COLOR_BUTTON, r, g, b)
|
|
285
|
+
|
|
286
|
+
r, g, b, a = LSDView().style_manager.make_color_rgb(0.55, 0.0, 0.0, saturation_scale=1.0)
|
|
287
|
+
push_style_color(imgui.COLOR_BORDER, r, g, b)
|
|
288
|
+
|
|
289
|
+
r, g, b, a = LSDView().style_manager.make_color_rgb(0.75, 0.0, 0.0, saturation_scale=1.0)
|
|
290
|
+
push_style_color(imgui.COLOR_BUTTON_HOVERED, r, g, b)
|
|
291
|
+
|
|
292
|
+
r, g, b, a = LSDView().style_manager.make_color_rgb(0.9, 0.0, 0.0, saturation_scale=1.0)
|
|
293
|
+
push_style_color(imgui.COLOR_BUTTON_ACTIVE, r, g, b)
|
|
294
|
+
|
|
295
|
+
else:
|
|
296
|
+
r, g, b, a = LSDView().style_manager.make_color_rgb(1.0, 1.0, 1.0, saturation_scale=1.0)
|
|
297
|
+
push_style_color(imgui.COLOR_BUTTON, r, g, b)
|
|
298
|
+
|
|
299
|
+
r, g, b, a = LSDView().style_manager.make_color_rgb(0.55, 0.0, 0.0, saturation_scale=1.0)
|
|
300
|
+
push_style_color(imgui.COLOR_BORDER, r, g, b)
|
|
301
|
+
|
|
302
|
+
r, g, b, a = LSDView().style_manager.make_color_rgb(0.75, 0.0, 0.0, saturation_scale=1.0)
|
|
303
|
+
push_style_color(imgui.COLOR_BUTTON_HOVERED, r, g, b)
|
|
304
|
+
|
|
305
|
+
r, g, b, a = LSDView().style_manager.make_color_rgb(0.9, 0.0, 0.0, saturation_scale=1.0)
|
|
306
|
+
push_style_color(imgui.COLOR_BUTTON_ACTIVE, r, g, b)
|
|
307
|
+
|
|
308
|
+
r, g, b, a = LSDView().style_manager.make_color_rgb(1.0, 0.1, 0.1, saturation_scale=1.0)
|
|
309
|
+
push_style_color(imgui.COLOR_TEXT, r, g, b)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
else:
|
|
313
|
+
push_style_color(imgui.COLOR_BUTTON, 0.5, 0.2, 0.2)
|
|
314
|
+
push_style_color(imgui.COLOR_BUTTON, 0.55, 0.2, 0.2)
|
|
315
|
+
push_style_color(imgui.COLOR_BUTTON_HOVERED, 0.9, 0.3, 0.3)
|
|
316
|
+
push_style_color(imgui.COLOR_BUTTON_ACTIVE, 1.0, 0.4, 0.4)
|
|
317
|
+
|
|
318
|
+
original_cursor_pos = imgui.get_cursor_pos()
|
|
319
|
+
offset_width = width
|
|
320
|
+
|
|
321
|
+
if text.split("##")[0] == "":
|
|
322
|
+
val = imgui.button(f"\uf1f8{text}", width=width, height=height)
|
|
323
|
+
else:
|
|
324
|
+
val = imgui.button(f"\uf1f8 {text}", width=width, height=height)
|
|
325
|
+
|
|
326
|
+
if white_text:
|
|
327
|
+
pop_style_color(4)
|
|
328
|
+
else:
|
|
329
|
+
pop_style_color(5)
|
|
330
|
+
|
|
331
|
+
pop_style_var(1)
|
|
332
|
+
|
|
333
|
+
return val
|
|
334
|
+
|
|
335
|
+
def button_red(text, width=0, height=0):
|
|
336
|
+
push_style_color(imgui.COLOR_BUTTON, 0.6, 0.2, 0.2)
|
|
337
|
+
push_style_color(imgui.COLOR_BUTTON_HOVERED, 0.6, 0.3, 0.4)
|
|
338
|
+
push_style_color(imgui.COLOR_BUTTON_ACTIVE, 1.0, 0.4, 0.4)
|
|
339
|
+
push_style_color(imgui.COLOR_TEXT, 1.0, 1.0, 1.0)
|
|
340
|
+
val = imgui.button(text, width=width, height=height)
|
|
341
|
+
pop_style_color(4)
|
|
342
|
+
return val
|
|
343
|
+
|
|
344
|
+
# noinspection PyArgumentList
|
|
345
|
+
def tree(text, open=True, width=0, height=0):
|
|
346
|
+
## Returns 'true' if the node is drawn
|
|
347
|
+
if open:
|
|
348
|
+
flags = (imgui.TREE_NODE_DEFAULT_OPEN |
|
|
349
|
+
imgui.TREE_NODE_COLLAPSING_HEADER | imgui.TREE_NODE_ALLOW_ITEM_OVERLAP)
|
|
350
|
+
else:
|
|
351
|
+
flags = imgui.TREE_NODE_COLLAPSING_HEADER | imgui.TREE_NODE_ALLOW_ITEM_OVERLAP
|
|
352
|
+
|
|
353
|
+
imgui.set_next_item_open(open)
|
|
354
|
+
if width > 0:
|
|
355
|
+
imgui.set_next_item_width(width)
|
|
356
|
+
|
|
357
|
+
imgui.push_style_color(imgui.COLOR_HEADER_HOVERED, 0,0,0,0)
|
|
358
|
+
opened = imgui.tree_node(text, flags=flags)
|
|
359
|
+
imgui.pop_style_color(1)
|
|
360
|
+
|
|
361
|
+
return opened
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def does_need_render():
|
|
365
|
+
return _needs_render.is_set()
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def new_frame():
|
|
369
|
+
if Core.melty.imgui_crashed:
|
|
370
|
+
return
|
|
371
|
+
|
|
372
|
+
LSDView().group_stack.append(GroupType.FRAME)
|
|
373
|
+
return imgui.new_frame()
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def end_frame():
|
|
377
|
+
if Core.melty.imgui_crashed:
|
|
378
|
+
return
|
|
379
|
+
|
|
380
|
+
# Merge the foreground/overlay channels before ImGui finalizes the frame because
|
|
381
|
+
# ImGui's render path requires merged channels, and this is the last frame
|
|
382
|
+
# after all user draws.
|
|
383
|
+
Core.melty.finalize_overlay_channels()
|
|
384
|
+
|
|
385
|
+
if LSDView().group_stack[-1] == GroupType.FRAME:
|
|
386
|
+
LSDView().group_stack.pop()
|
|
387
|
+
return imgui.end_frame()
|
|
388
|
+
else:
|
|
389
|
+
print_stack_trace()
|
|
390
|
+
print("Error: end_frame() called without matching new_frame()")
|
|
391
|
+
return imgui.end_frame()
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def begin_child(signatures, *args, **kwargs):
|
|
395
|
+
if Core.melty.imgui_crashed:
|
|
396
|
+
return False
|
|
397
|
+
|
|
398
|
+
LSDView().group_stack.append(GroupType.CHILD)
|
|
399
|
+
return imgui.begin_child(signatures, *args, **kwargs)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def end_child():
|
|
403
|
+
if Core.melty.imgui_crashed:
|
|
404
|
+
return
|
|
405
|
+
|
|
406
|
+
if LSDView().group_stack[-1] == GroupType.CHILD:
|
|
407
|
+
LSDView().group_stack.pop()
|
|
408
|
+
return imgui.end_child()
|
|
409
|
+
else:
|
|
410
|
+
print_stack_trace()
|
|
411
|
+
print("Error: end_child() called without matching begin_child()")
|
|
412
|
+
return imgui.end_child()
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def begin(str_label, closable=False, flags=0):
|
|
416
|
+
if Core.melty.imgui_crashed:
|
|
417
|
+
return False, False
|
|
418
|
+
|
|
419
|
+
LSDView().group_stack.append(GroupType.WINDOW)
|
|
420
|
+
return imgui.begin(str_label, closable, flags)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def end():
|
|
424
|
+
if Core.melty.imgui_crashed:
|
|
425
|
+
return
|
|
426
|
+
|
|
427
|
+
if LSDView().group_stack[-1] == GroupType.WINDOW:
|
|
428
|
+
LSDView().group_stack.pop()
|
|
429
|
+
return imgui.end()
|
|
430
|
+
else:
|
|
431
|
+
print_stack_trace()
|
|
432
|
+
print("Error: end() called without matching begin()")
|
|
433
|
+
return imgui.end()
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def push_style_color(ImGuiCol_variable, float_r, float_g, float_b, float_a=1.):
|
|
437
|
+
if Core.melty.imgui_crashed:
|
|
438
|
+
return
|
|
439
|
+
LSDView().color_stack.append(GroupType.COLOR)
|
|
440
|
+
return imgui.push_style_color(ImGuiCol_variable, float_r, float_g, float_b, float_a)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def pop_style_color(size=1):
|
|
444
|
+
if Core.melty.imgui_crashed:
|
|
445
|
+
return
|
|
446
|
+
|
|
447
|
+
for _ in range(size):
|
|
448
|
+
if LSDView().color_stack[-1] == GroupType.COLOR:
|
|
449
|
+
LSDView().color_stack.pop()
|
|
450
|
+
imgui.pop_style_color(1)
|
|
451
|
+
else:
|
|
452
|
+
print("Error: pop_style_color() called without matching push_style_color()")
|
|
453
|
+
# Print stack trace to help debug
|
|
454
|
+
print_stack_trace()
|
|
455
|
+
imgui.pop_style_color(1)
|
|
456
|
+
|
|
457
|
+
def push_style_var(ImGuiStyleVar_variable, value):
|
|
458
|
+
if Core.melty.imgui_crashed:
|
|
459
|
+
return
|
|
460
|
+
|
|
461
|
+
LSDView().style_stack.append(GroupType.STYLE)
|
|
462
|
+
return imgui.push_style_var(ImGuiStyleVar_variable, value)
|
|
463
|
+
|
|
464
|
+
def pop_style_var(size=1):
|
|
465
|
+
if Core.melty.imgui_crashed:
|
|
466
|
+
return
|
|
467
|
+
|
|
468
|
+
for _ in range(size):
|
|
469
|
+
if LSDView().style_stack[-1] == GroupType.STYLE:
|
|
470
|
+
LSDView().style_stack.pop()
|
|
471
|
+
imgui.pop_style_var(1)
|
|
472
|
+
else:
|
|
473
|
+
print_stack_trace()
|
|
474
|
+
print(f"Error: {LSDView().style_stack[-1]} pop_style_var() called without matching push_style_var()")
|
|
475
|
+
imgui.pop_style_var(1)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
import re
|
|
479
|
+
|
|
480
|
+
# ANSI color codes inspired by IntelliJ IDEA's default color scheme
|
|
481
|
+
COLORS = {
|
|
482
|
+
# Structural elements
|
|
483
|
+
'HEADER': '\033[95m',
|
|
484
|
+
'RESET': '\033[0m',
|
|
485
|
+
'BOLD': '\033[1m',
|
|
486
|
+
'UNDERLINE': '\033[4m',
|
|
487
|
+
|
|
488
|
+
# IntelliJ-like syntax colors
|
|
489
|
+
'KEYWORD': '\033[38;5;204m', # Pink/purple for keywords like def, class, import
|
|
490
|
+
'METHOD': '\033[38;5;75m', # Blue for method names
|
|
491
|
+
'STRING': '\033[38;5;113m', # Green for strings
|
|
492
|
+
'NUMBER': '\033[38;5;141m', # Purple for numbers
|
|
493
|
+
'COMMENT': '\033[38;5;102m', # Gray for comments
|
|
494
|
+
'CONSTANT': '\033[38;5;174m', # Light red for constants
|
|
495
|
+
|
|
496
|
+
# Traceback specific colors
|
|
497
|
+
'FILENAME': '\033[38;5;186m', # Light yellow for filenames
|
|
498
|
+
'LINENO': '\033[38;5;37m', # Teal for line numbers
|
|
499
|
+
'ERROR': '\033[38;5;196m', # Bright red for errors
|
|
500
|
+
'WARNING': '\033[38;5;214m', # Orange for warnings
|
|
501
|
+
'EXCEPTION': '\033[38;5;203m' # Red for exception names
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
COLORS = {
|
|
505
|
+
'HEADER': '\033[95m',
|
|
506
|
+
'BLUE': '\033[94m',
|
|
507
|
+
'CYAN': '\033[96m',
|
|
508
|
+
'GREEN': '\033[92m',
|
|
509
|
+
'YELLOW': '\033[93m',
|
|
510
|
+
'PURPLE': '\033[95m',
|
|
511
|
+
'RED': '\033[91m',
|
|
512
|
+
'BOLD': '\033[1m',
|
|
513
|
+
'UNDERLINE': '\033[4m',
|
|
514
|
+
'RESET': '\033[0m'
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
import sys
|
|
518
|
+
import traceback
|
|
519
|
+
# Create a console for rich output
|
|
520
|
+
|
|
521
|
+
def stack_trace():
|
|
522
|
+
print_colored_traceback(*sys.exc_info(), limit=50)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def print_colored_traceback(exc_type=None, exc_value=None, exc_traceback=None, limit=None, file=None, color=None):
|
|
527
|
+
"""
|
|
528
|
+
Print the traceback with colors and clickable links that open in IntelliJ IDEA.
|
|
529
|
+
|
|
530
|
+
Args:
|
|
531
|
+
exc_type: Exception type
|
|
532
|
+
exc_value: Exception value
|
|
533
|
+
exc_traceback: Exception traceback
|
|
534
|
+
limit: Maximum number of stack frames to show
|
|
535
|
+
file: File to write the traceback to
|
|
536
|
+
"""
|
|
537
|
+
if exc_type == None:
|
|
538
|
+
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
539
|
+
|
|
540
|
+
if file is None:
|
|
541
|
+
file = sys.stdout
|
|
542
|
+
|
|
543
|
+
if color is None:
|
|
544
|
+
color = "CYAN"
|
|
545
|
+
|
|
546
|
+
e = exc_value
|
|
547
|
+
if not Core.melty.imgui_crashed:
|
|
548
|
+
if isinstance(e, ImGuiError):
|
|
549
|
+
if Core.melty.vis is not None:
|
|
550
|
+
Core.melty.imgui_crashed = True
|
|
551
|
+
if Core.melty.vis.imgui_ctx is not None:
|
|
552
|
+
|
|
553
|
+
# if Core.melty.vis._impl is not None:
|
|
554
|
+
# Core.melty.vis._impl.shutdown()
|
|
555
|
+
# Core.melty.vis.impl = None
|
|
556
|
+
RED_BOLD = "\033[1;31m"
|
|
557
|
+
RESET = "\033[0m"
|
|
558
|
+
print("-" * 80)
|
|
559
|
+
info = sys.exc_info()
|
|
560
|
+
print(f"{RED_BOLD}ImGui Crashed! Recreating context from print\n{RESET}: {e}")
|
|
561
|
+
stack_from_e = traceback.extract_tb(exc_traceback)
|
|
562
|
+
print("-" * 80)
|
|
563
|
+
|
|
564
|
+
Core.melty.vis.exception_raised = True
|
|
565
|
+
Core.melty.imgui_crashed = True
|
|
566
|
+
# Stack trace
|
|
567
|
+
print_colored_traceback(*info, limit=50)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def extract_vars(exc_traceback):
|
|
571
|
+
"""
|
|
572
|
+
Extract the stack trace from the traceback object.
|
|
573
|
+
"""
|
|
574
|
+
attr_names = []
|
|
575
|
+
configs = []
|
|
576
|
+
if exc_traceback is not None:
|
|
577
|
+
while exc_traceback.tb_next is not None:
|
|
578
|
+
frame = exc_traceback.tb_frame
|
|
579
|
+
locals = frame.f_locals
|
|
580
|
+
attr_name = locals.get('attr_name', "")
|
|
581
|
+
if attr_name == "":
|
|
582
|
+
if 'input_value' in locals:
|
|
583
|
+
if hasattr(locals['input_value'], 'name'):
|
|
584
|
+
attr_name = locals['input_value'].name
|
|
585
|
+
else:
|
|
586
|
+
if locals['input_value'] is not None:
|
|
587
|
+
attr_name = locals['input_value'].__class__.__name__
|
|
588
|
+
|
|
589
|
+
else:
|
|
590
|
+
if 'self' in locals:
|
|
591
|
+
attr_name = f"self is {locals['self'].__class__.__name__}"
|
|
592
|
+
|
|
593
|
+
config = locals.get('config', None)
|
|
594
|
+
attr_names.append(attr_name)
|
|
595
|
+
configs.append(config)
|
|
596
|
+
exc_traceback = exc_traceback.tb_next
|
|
597
|
+
|
|
598
|
+
return attr_names, configs
|
|
599
|
+
|
|
600
|
+
exp_vars, configs = extract_vars(exc_traceback)
|
|
601
|
+
if exc_traceback is None:
|
|
602
|
+
stack = []
|
|
603
|
+
else:
|
|
604
|
+
stack = traceback.extract_stack(exc_traceback.tb_frame)
|
|
605
|
+
|
|
606
|
+
value, tb = _parse_value_tb(exc_type, exc_value, exc_traceback)
|
|
607
|
+
te = traceback.TracebackException(type(value), value, tb, limit=limit, compact=True)
|
|
608
|
+
exception_stack = te.stack
|
|
609
|
+
|
|
610
|
+
if value is None:
|
|
611
|
+
print_stack_trace()
|
|
612
|
+
|
|
613
|
+
for idx, frame in enumerate(stack[:-1]):
|
|
614
|
+
line_number = frame.lineno
|
|
615
|
+
filename = frame.filename
|
|
616
|
+
line = frame.line
|
|
617
|
+
function_name = frame.name
|
|
618
|
+
green = COLORS['GREEN']
|
|
619
|
+
blue = COLORS['BLUE']
|
|
620
|
+
yellow = COLORS['YELLOW']
|
|
621
|
+
|
|
622
|
+
print(f"{yellow}File \"{filename}\", line {line_number}{COLORS['RESET']}{COLORS['BOLD']}{blue} {function_name}{COLORS['RESET']}")
|
|
623
|
+
print(f" {yellow}{line}{COLORS['RESET']}")
|
|
624
|
+
|
|
625
|
+
yellow = COLORS['YELLOW']
|
|
626
|
+
red = COLORS['RED']
|
|
627
|
+
print(f"{yellow}-------- Error Caught -------{COLORS['RESET']}")
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
for idx, frame in enumerate(exception_stack):
|
|
631
|
+
line_number = frame.lineno
|
|
632
|
+
filename = frame.filename
|
|
633
|
+
line = frame.line
|
|
634
|
+
function_name = frame.name
|
|
635
|
+
green = COLORS['GREEN']
|
|
636
|
+
blue = COLORS['BLUE']
|
|
637
|
+
attr_name = exp_vars[idx] if idx < len(exp_vars) else ""
|
|
638
|
+
config = configs[idx] if idx < len(configs) else None
|
|
639
|
+
|
|
640
|
+
print(
|
|
641
|
+
f"{yellow}File \"{filename}\", line {line_number}{COLORS['RESET']}{COLORS['BOLD']}{blue} {function_name}{COLORS['RESET']} {green}{attr_name}{COLORS['RESET']}")
|
|
642
|
+
# if attr_name is not None:
|
|
643
|
+
# print(f" {yellow}attr_name{COLORS['RESET']} {COLORS['BOLD']}{green}{attr_name}{COLORS['RESET']}")
|
|
644
|
+
# if input_value is not None:
|
|
645
|
+
# print(f" {yellow}input_value{COLORS['RESET']} {COLORS['BOLD']}{yellow}{input_value}{COLORS['RESET']}")
|
|
646
|
+
# if datatype is not None:
|
|
647
|
+
# print(f" {yellow}datatype.name{COLORS['RESET']} {COLORS['BOLD']}{yellow}{datatype}{COLORS['RESET']}")
|
|
648
|
+
|
|
649
|
+
if idx == len(exception_stack) - 1:
|
|
650
|
+
print(f" {COLORS['BOLD']}{red}{line}{COLORS['RESET']}")
|
|
651
|
+
else:
|
|
652
|
+
print(f" {yellow}{line}{COLORS['RESET']}")
|
|
653
|
+
|
|
654
|
+
traceback_lines = traceback.format_exception_only(exc_value)
|
|
655
|
+
for line in traceback_lines:
|
|
656
|
+
line = f"{yellow}{line}{COLORS['RESET']}"
|
|
657
|
+
file.write(line)
|
|
658
|
+
|
|
659
|
+
if file is None:
|
|
660
|
+
file = sys.stdout
|
|
661
|
+
|
|
662
|
+
# NOTE: this used to os._exit(1) when Melty.imgui_crashed was latched.
|
|
663
|
+
# The latch persists across studio runs inside the launcher process, so a
|
|
664
|
+
# recovered ImGui hiccup earlier in the session turned any later traceback
|
|
665
|
+
# print (launcher, restart, studio teardown) into a silent hard-kill of
|
|
666
|
+
# the whole launcher. A crash printer should never kill the process.
|
|
667
|
+
|
|
668
|
+
#
|
|
669
|
+
# traceback_lines = traceback.format_exception(exc_type, exc_value, exc_traceback, limit=limit)
|
|
670
|
+
#
|
|
671
|
+
# for line in traceback_lines:
|
|
672
|
+
# line = f"{COLORS['BOLD']}{COLORS[color]}{line}{COLORS['RESET']}"
|
|
673
|
+
# file.write(line)
|
|
674
|
+
|
|
675
|
+
import gc
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
def memory_flame_chart(scope=None, threshold_kb=1, depth=10000, width=80, color=True, aggregate_by_type=True,
|
|
679
|
+
include_cuda=True, scan_all_objects=True, max_objects=50000000):
|
|
680
|
+
"""
|
|
681
|
+
Generate a flame chart visualization of memory usage by variables.
|
|
682
|
+
|
|
683
|
+
Args:
|
|
684
|
+
scope: The scope/namespace to analyze. If None, uses the caller's globals and locals.
|
|
685
|
+
threshold_kb: Minimum size in KB to include in the chart (default: 1KB)
|
|
686
|
+
depth: Maximum depth for nested objects to traverse (default: 3)
|
|
687
|
+
width: Width of the terminal output (default: 80 characters)
|
|
688
|
+
color: Whether to use ANSI colors in output (default: True)
|
|
689
|
+
aggregate_by_type: Whether to aggregate memory usage by class type (default: True)
|
|
690
|
+
include_cuda: Whether to include CUDA memory in the chart (default: True)
|
|
691
|
+
scan_all_objects: Whether to scan all objects in memory (default: True)
|
|
692
|
+
max_objects: Maximum number of objects to scan (default: 500000)
|
|
693
|
+
|
|
694
|
+
Returns:
|
|
695
|
+
None: Prints the flame chart to stdout
|
|
696
|
+
"""
|
|
697
|
+
# ANSI color codes
|
|
698
|
+
colors = {
|
|
699
|
+
'reset': '\033[0m',
|
|
700
|
+
'red': '\033[91m',
|
|
701
|
+
'yellow': '\033[93m',
|
|
702
|
+
'green': '\033[92m',
|
|
703
|
+
'blue': '\033[94m',
|
|
704
|
+
'cyan': '\033[96m',
|
|
705
|
+
'magenta': '\033[95m',
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
if not color:
|
|
709
|
+
# Disable colors if not wanted
|
|
710
|
+
for k in colors:
|
|
711
|
+
colors[k] = ''
|
|
712
|
+
|
|
713
|
+
# Get the namespace to analyze
|
|
714
|
+
if scope is None:
|
|
715
|
+
# Get caller's frame to access its variables
|
|
716
|
+
caller_frame = inspect.currentframe().f_back
|
|
717
|
+
global_vars = caller_frame.f_globals
|
|
718
|
+
local_vars = caller_frame.f_locals
|
|
719
|
+
else:
|
|
720
|
+
global_vars = scope
|
|
721
|
+
local_vars = {}
|
|
722
|
+
|
|
723
|
+
# Track already seen objects to avoid cycles
|
|
724
|
+
seen_ids = set()
|
|
725
|
+
|
|
726
|
+
# Store type-specific information when aggregating
|
|
727
|
+
type_sizes = defaultdict(int)
|
|
728
|
+
type_counts = defaultdict(int)
|
|
729
|
+
type_examples = {}
|
|
730
|
+
|
|
731
|
+
# Flag to detect if PyTorch is available
|
|
732
|
+
has_pytorch = False
|
|
733
|
+
try:
|
|
734
|
+
import torch
|
|
735
|
+
has_pytorch = True
|
|
736
|
+
except ImportError:
|
|
737
|
+
pass
|
|
738
|
+
|
|
739
|
+
# Flag to detect if NumPy is available
|
|
740
|
+
has_numpy = False
|
|
741
|
+
try:
|
|
742
|
+
import numpy as np
|
|
743
|
+
has_numpy = True
|
|
744
|
+
except ImportError:
|
|
745
|
+
pass
|
|
746
|
+
|
|
747
|
+
# Stores the size of each variable and its path when not aggregating
|
|
748
|
+
var_sizes = []
|
|
749
|
+
threshold_bytes = threshold_kb * 1024
|
|
750
|
+
|
|
751
|
+
# For tracking tensor memory
|
|
752
|
+
cpu_tensor_size = 0
|
|
753
|
+
numpy_array_size = 0
|
|
754
|
+
|
|
755
|
+
# Risky module patterns to avoid
|
|
756
|
+
risky_modules_patterns = [
|
|
757
|
+
r'transformers\.models\.auto',
|
|
758
|
+
r'transformers\.utils\.import_utils',
|
|
759
|
+
r'transformers\.deepspeed',
|
|
760
|
+
r'importlib\._bootstrap',
|
|
761
|
+
r'lazy_loader',
|
|
762
|
+
r'google\.protobuf',
|
|
763
|
+
]
|
|
764
|
+
|
|
765
|
+
def is_risky_object(obj):
|
|
766
|
+
"""Check if an object is from a module that might cause import or recursion issues"""
|
|
767
|
+
try:
|
|
768
|
+
if not hasattr(obj, '__class__'):
|
|
769
|
+
return False
|
|
770
|
+
|
|
771
|
+
module_name = obj.__class__.__module__
|
|
772
|
+
# Check if module name matches any risky pattern
|
|
773
|
+
if any(re.search(pattern, module_name) for pattern in risky_modules_patterns):
|
|
774
|
+
return True
|
|
775
|
+
|
|
776
|
+
# Check for specific object types
|
|
777
|
+
if hasattr(obj, '__getattr__') and not isinstance(obj, dict) and not hasattr(obj, 'items'):
|
|
778
|
+
# Objects with custom __getattr__ might trigger imports
|
|
779
|
+
return True
|
|
780
|
+
|
|
781
|
+
# Dynamic attribute objects that might trigger imports
|
|
782
|
+
risky_class_names = ['LazyLoader', 'LazyImport', 'DynamicModule', '_LazyModule']
|
|
783
|
+
if obj.__class__.__name__ in risky_class_names:
|
|
784
|
+
return True
|
|
785
|
+
|
|
786
|
+
return False
|
|
787
|
+
except:
|
|
788
|
+
# If any error occurs while checking, consider it risky
|
|
789
|
+
return True
|
|
790
|
+
|
|
791
|
+
def estimate_tensor_memory(tensor):
|
|
792
|
+
"""Estimate memory used by a tensor"""
|
|
793
|
+
try:
|
|
794
|
+
if hasattr(tensor, 'element_size') and hasattr(tensor, 'nelement'):
|
|
795
|
+
return tensor.element_size() * tensor.nelement()
|
|
796
|
+
elif hasattr(tensor, 'itemsize') and hasattr(tensor, 'size'):
|
|
797
|
+
# For numpy arrays
|
|
798
|
+
return tensor.itemsize * tensor.size
|
|
799
|
+
return 0
|
|
800
|
+
except:
|
|
801
|
+
return 0
|
|
802
|
+
|
|
803
|
+
def get_size(obj, name, current_depth=0, path=""):
|
|
804
|
+
"""Recursively find the size of objects and their attributes"""
|
|
805
|
+
nonlocal cpu_tensor_size, numpy_array_size
|
|
806
|
+
|
|
807
|
+
if current_depth > depth:
|
|
808
|
+
return 0
|
|
809
|
+
|
|
810
|
+
# Skip already seen objects
|
|
811
|
+
obj_id = id(obj)
|
|
812
|
+
if obj_id in seen_ids:
|
|
813
|
+
return 0
|
|
814
|
+
|
|
815
|
+
seen_ids.add(obj_id)
|
|
816
|
+
|
|
817
|
+
# Skip risky objects
|
|
818
|
+
if is_risky_object(obj):
|
|
819
|
+
# Just estimate the basic size without recursion
|
|
820
|
+
try:
|
|
821
|
+
obj_size = sys.getsizeof(obj)
|
|
822
|
+
|
|
823
|
+
# Record basic type information
|
|
824
|
+
if aggregate_by_type:
|
|
825
|
+
obj_type = obj.__class__.__name__
|
|
826
|
+
type_sizes[obj_type] += obj_size
|
|
827
|
+
type_counts[obj_type] += 1
|
|
828
|
+
if obj_type not in type_examples:
|
|
829
|
+
type_examples[obj_type] = path if path else name
|
|
830
|
+
|
|
831
|
+
return obj_size
|
|
832
|
+
except:
|
|
833
|
+
return 0
|
|
834
|
+
|
|
835
|
+
# Default object size
|
|
836
|
+
obj_size = 0
|
|
837
|
+
tensor_data_size = 0
|
|
838
|
+
|
|
839
|
+
# Special handling for PyTorch tensors
|
|
840
|
+
if has_pytorch and isinstance(obj, torch.Tensor):
|
|
841
|
+
try:
|
|
842
|
+
# Get base object size
|
|
843
|
+
obj_size = sys.getsizeof(obj)
|
|
844
|
+
|
|
845
|
+
if obj.is_cuda:
|
|
846
|
+
# For CUDA tensors, only count the object size
|
|
847
|
+
# Record custom size information for CUDA tensors
|
|
848
|
+
if aggregate_by_type:
|
|
849
|
+
cuda_type = f"torch.Tensor(CUDA)"
|
|
850
|
+
type_sizes[cuda_type] += obj_size
|
|
851
|
+
type_counts[cuda_type] += 1
|
|
852
|
+
if cuda_type not in type_examples:
|
|
853
|
+
type_examples[cuda_type] = path
|
|
854
|
+
else:
|
|
855
|
+
# For CPU tensors, estimate memory
|
|
856
|
+
tensor_data_size = estimate_tensor_memory(obj)
|
|
857
|
+
obj_size += tensor_data_size
|
|
858
|
+
|
|
859
|
+
# Track CPU tensor memory separately
|
|
860
|
+
cpu_tensor_size += tensor_data_size
|
|
861
|
+
|
|
862
|
+
# Record CPU tensor information
|
|
863
|
+
if aggregate_by_type:
|
|
864
|
+
cpu_type = f"torch.Tensor(CPU)"
|
|
865
|
+
type_sizes[cpu_type] += obj_size
|
|
866
|
+
type_counts[cpu_type] += 1
|
|
867
|
+
if cpu_type not in type_examples:
|
|
868
|
+
type_examples[cpu_type] = path
|
|
869
|
+
except:
|
|
870
|
+
obj_size = sys.getsizeof(obj)
|
|
871
|
+
|
|
872
|
+
# Special handling for NumPy arrays
|
|
873
|
+
elif has_numpy and isinstance(obj, np.ndarray):
|
|
874
|
+
try:
|
|
875
|
+
# Get base object size
|
|
876
|
+
obj_size = sys.getsizeof(obj)
|
|
877
|
+
|
|
878
|
+
# Add size of array data
|
|
879
|
+
array_data_size = estimate_tensor_memory(obj)
|
|
880
|
+
obj_size += array_data_size
|
|
881
|
+
|
|
882
|
+
# Track numpy array memory separately
|
|
883
|
+
numpy_array_size += array_data_size
|
|
884
|
+
|
|
885
|
+
# Record numpy array information
|
|
886
|
+
if aggregate_by_type:
|
|
887
|
+
numpy_type = f"numpy.ndarray"
|
|
888
|
+
type_sizes[numpy_type] += obj_size
|
|
889
|
+
type_counts[numpy_type] += 1
|
|
890
|
+
if numpy_type not in type_examples:
|
|
891
|
+
type_examples[numpy_type] = path
|
|
892
|
+
except:
|
|
893
|
+
obj_size = sys.getsizeof(obj)
|
|
894
|
+
else:
|
|
895
|
+
try:
|
|
896
|
+
# Get the object's size for other objects
|
|
897
|
+
obj_size = sys.getsizeof(obj)
|
|
898
|
+
except Exception:
|
|
899
|
+
# Some objects don't support getsizeof
|
|
900
|
+
obj_size = 0
|
|
901
|
+
|
|
902
|
+
current_path = f"{path}.{name}" if path else name
|
|
903
|
+
|
|
904
|
+
# Record size information if aggregating
|
|
905
|
+
if aggregate_by_type:
|
|
906
|
+
if has_pytorch and isinstance(obj, torch.Tensor):
|
|
907
|
+
if obj.is_cuda:
|
|
908
|
+
obj_type = "torch.Tensor(CUDA)"
|
|
909
|
+
else:
|
|
910
|
+
obj_type = "torch.Tensor(CPU)"
|
|
911
|
+
elif has_numpy and isinstance(obj, np.ndarray):
|
|
912
|
+
obj_type = "numpy.ndarray"
|
|
913
|
+
else:
|
|
914
|
+
obj_type = type(obj).__name__
|
|
915
|
+
|
|
916
|
+
type_sizes[obj_type] += obj_size
|
|
917
|
+
type_counts[obj_type] += 1
|
|
918
|
+
if obj_type not in type_examples:
|
|
919
|
+
type_examples[obj_type] = current_path
|
|
920
|
+
|
|
921
|
+
# Store the size info for display if not aggregating
|
|
922
|
+
if not aggregate_by_type and obj_size >= threshold_bytes:
|
|
923
|
+
var_sizes.append((current_path, obj_size, type(obj).__name__))
|
|
924
|
+
|
|
925
|
+
# Skip recursive inspection of tensors and special types
|
|
926
|
+
if (has_pytorch and isinstance(obj, torch.Tensor)) or (has_numpy and isinstance(obj, np.ndarray)):
|
|
927
|
+
return obj_size
|
|
928
|
+
|
|
929
|
+
# For some collection types, add the size of their items
|
|
930
|
+
try:
|
|
931
|
+
if isinstance(obj, (list, tuple, set, frozenset)):
|
|
932
|
+
try:
|
|
933
|
+
for i, item in enumerate(obj):
|
|
934
|
+
if current_depth < depth: # Respect depth limit
|
|
935
|
+
item_path = f"{current_path}[{i}]"
|
|
936
|
+
obj_size += get_size(item, f"[{i}]", current_depth + 1, current_path)
|
|
937
|
+
except:
|
|
938
|
+
pass
|
|
939
|
+
|
|
940
|
+
elif isinstance(obj, dict):
|
|
941
|
+
try:
|
|
942
|
+
# Safe iteration over dictionary items
|
|
943
|
+
safe_items = list(obj.items())
|
|
944
|
+
for k, v in safe_items:
|
|
945
|
+
if current_depth < depth: # Respect depth limit
|
|
946
|
+
# Convert key to string representation
|
|
947
|
+
try:
|
|
948
|
+
k_str = str(k) if len(str(k)) < 20 else f"{str(k)[:17]}..."
|
|
949
|
+
except:
|
|
950
|
+
k_str = "?"
|
|
951
|
+
item_path = f"{current_path}[{k_str}]"
|
|
952
|
+
obj_size += get_size(v, f"[{k_str}]", current_depth + 1, current_path)
|
|
953
|
+
except:
|
|
954
|
+
pass
|
|
955
|
+
|
|
956
|
+
# For custom objects, inspect attributes
|
|
957
|
+
elif hasattr(obj, '__dict__') and not isinstance(obj, type):
|
|
958
|
+
try:
|
|
959
|
+
# For PyTorch modules, handle parameters specially
|
|
960
|
+
if has_pytorch and hasattr(obj, 'parameters') and callable(getattr(obj, 'parameters', None)):
|
|
961
|
+
try:
|
|
962
|
+
for name, param in list(obj.named_parameters()):
|
|
963
|
+
if current_depth < depth:
|
|
964
|
+
param_path = f"{current_path}.{name}"
|
|
965
|
+
obj_size += get_size(param, name, current_depth + 1, current_path)
|
|
966
|
+
except:
|
|
967
|
+
pass
|
|
968
|
+
|
|
969
|
+
# Get standard attributes
|
|
970
|
+
safe_dict = dict(obj.__dict__)
|
|
971
|
+
for attr, value in safe_dict.items():
|
|
972
|
+
if not attr.startswith('__') and current_depth < depth:
|
|
973
|
+
attr_path = f"{current_path}.{attr}"
|
|
974
|
+
obj_size += get_size(value, attr, current_depth + 1, current_path)
|
|
975
|
+
except:
|
|
976
|
+
pass
|
|
977
|
+
except:
|
|
978
|
+
# If any exception occurs during traversal, just use the object's own size
|
|
979
|
+
pass
|
|
980
|
+
|
|
981
|
+
return obj_size
|
|
982
|
+
|
|
983
|
+
# Get total memory of this process as a comparison
|
|
984
|
+
import psutil # lazy: only this profiler needs it
|
|
985
|
+
process = psutil.Process(os.getpid())
|
|
986
|
+
total_process_memory = process.memory_info().rss
|
|
987
|
+
|
|
988
|
+
# Analyze memory usage
|
|
989
|
+
print(f"\n{colors['cyan']}===== Memory Usage Flame Chart ====={colors['reset']}")
|
|
990
|
+
print(f"Process total: {total_process_memory / (1024 * 1024):.2f} MB")
|
|
991
|
+
print(f"Threshold: {threshold_kb} KB\n")
|
|
992
|
+
|
|
993
|
+
# Process variables from both globals and locals
|
|
994
|
+
all_vars = {}
|
|
995
|
+
all_vars.update(global_vars)
|
|
996
|
+
all_vars.update(local_vars)
|
|
997
|
+
|
|
998
|
+
# Start memory analysis from explicit variables
|
|
999
|
+
print(f"{colors['blue']}Analyzing {len(all_vars)} variables in current scope...{colors['reset']}")
|
|
1000
|
+
for name, obj in all_vars.items():
|
|
1001
|
+
try:
|
|
1002
|
+
# Skip modules, functions, and other non-data objects for direct analysis
|
|
1003
|
+
if name.startswith('__') or inspect.ismodule(obj) or inspect.isfunction(obj) or inspect.isbuiltin(obj):
|
|
1004
|
+
continue
|
|
1005
|
+
|
|
1006
|
+
get_size(obj, name)
|
|
1007
|
+
except Exception as e:
|
|
1008
|
+
# Skip objects that can't be inspected
|
|
1009
|
+
continue
|
|
1010
|
+
|
|
1011
|
+
# If scanning all objects, use garbage collector to find objects not directly accessible
|
|
1012
|
+
total_objects_scanned = len(seen_ids)
|
|
1013
|
+
if scan_all_objects:
|
|
1014
|
+
print(f"{colors['blue']}Scanning all objects in memory (this may take a while)...{colors['reset']}")
|
|
1015
|
+
|
|
1016
|
+
# Get all objects from garbage collector
|
|
1017
|
+
gc.collect() # Force collection to free up unreferenced objects
|
|
1018
|
+
|
|
1019
|
+
try:
|
|
1020
|
+
all_objects = gc.get_objects()
|
|
1021
|
+
|
|
1022
|
+
# Skip some problematic types
|
|
1023
|
+
skip_types = set([type, type(None), type(NotImplemented), type(Ellipsis)])
|
|
1024
|
+
if has_pytorch:
|
|
1025
|
+
try:
|
|
1026
|
+
# Skip tensor storage types
|
|
1027
|
+
import torch.storage
|
|
1028
|
+
skip_types.add(type(torch.storage.TypedStorage))
|
|
1029
|
+
skip_types.add(type(torch.storage._TypedStorage))
|
|
1030
|
+
except:
|
|
1031
|
+
pass
|
|
1032
|
+
|
|
1033
|
+
print(f"{colors['blue']}Found {len(all_objects)} total objects. Analyzing...{colors['reset']}")
|
|
1034
|
+
|
|
1035
|
+
# Process a subset of objects to avoid taking too long
|
|
1036
|
+
objects_to_process = min(len(all_objects), max_objects)
|
|
1037
|
+
for i, obj in enumerate(all_objects[:objects_to_process]):
|
|
1038
|
+
if i % 50000 == 0 and i > 0:
|
|
1039
|
+
print(f"{colors['blue']}Processed {i}/{objects_to_process} objects...{colors['reset']}")
|
|
1040
|
+
|
|
1041
|
+
try:
|
|
1042
|
+
# Skip if already seen
|
|
1043
|
+
if id(obj) in seen_ids:
|
|
1044
|
+
continue
|
|
1045
|
+
|
|
1046
|
+
# Skip problematic types
|
|
1047
|
+
if type(obj) in skip_types:
|
|
1048
|
+
continue
|
|
1049
|
+
|
|
1050
|
+
# Skip modules, functions, etc.
|
|
1051
|
+
if inspect.ismodule(obj) or inspect.isfunction(obj) or inspect.isbuiltin(obj):
|
|
1052
|
+
continue
|
|
1053
|
+
|
|
1054
|
+
# Skip risky objects right away
|
|
1055
|
+
if is_risky_object(obj):
|
|
1056
|
+
# Just get a basic size estimate without traversing
|
|
1057
|
+
try:
|
|
1058
|
+
obj_size = sys.getsizeof(obj)
|
|
1059
|
+
|
|
1060
|
+
# Record basic type information
|
|
1061
|
+
if aggregate_by_type:
|
|
1062
|
+
obj_type = obj.__class__.__name__
|
|
1063
|
+
type_sizes[obj_type] += obj_size
|
|
1064
|
+
type_counts[obj_type] += 1
|
|
1065
|
+
if obj_type not in type_examples:
|
|
1066
|
+
type_examples[obj_type] = f"<{obj_type}>"
|
|
1067
|
+
|
|
1068
|
+
seen_ids.add(id(obj))
|
|
1069
|
+
except:
|
|
1070
|
+
pass
|
|
1071
|
+
continue
|
|
1072
|
+
|
|
1073
|
+
# Process this object - use its type name as an identifier
|
|
1074
|
+
obj_type = type(obj).__name__
|
|
1075
|
+
get_size(obj, f"<{obj_type}>")
|
|
1076
|
+
|
|
1077
|
+
except:
|
|
1078
|
+
# Skip problematic objects
|
|
1079
|
+
continue
|
|
1080
|
+
|
|
1081
|
+
total_objects_scanned = len(seen_ids)
|
|
1082
|
+
print(f"{colors['blue']}Scanned {total_objects_scanned} unique objects.{colors['reset']}")
|
|
1083
|
+
except Exception as e:
|
|
1084
|
+
print(f"{colors['red']}Error during object scanning: {str(e)}{colors['reset']}")
|
|
1085
|
+
|
|
1086
|
+
# Collect CUDA memory info if available
|
|
1087
|
+
cuda_mem_total = 0
|
|
1088
|
+
cuda_entries = []
|
|
1089
|
+
if has_pytorch and torch.cuda.is_available() and include_cuda:
|
|
1090
|
+
try:
|
|
1091
|
+
# Get CUDA memory stats
|
|
1092
|
+
for device_idx in range(torch.cuda.device_count()):
|
|
1093
|
+
cuda_mem = torch.cuda.memory_reserved(device_idx)
|
|
1094
|
+
if cuda_mem > 0:
|
|
1095
|
+
cuda_name = f"CUDA:{device_idx} Memory"
|
|
1096
|
+
cuda_entries.append((cuda_name, cuda_mem, "cuda_memory"))
|
|
1097
|
+
cuda_mem_total += cuda_mem
|
|
1098
|
+
except:
|
|
1099
|
+
# In case of errors accessing CUDA memory info
|
|
1100
|
+
pass
|
|
1101
|
+
|
|
1102
|
+
# Prepare the data for display
|
|
1103
|
+
if aggregate_by_type:
|
|
1104
|
+
# Convert type data to the same format as var_sizes
|
|
1105
|
+
var_sizes = [] # Clear and rebuild with type data
|
|
1106
|
+
for type_name, size in type_sizes.items():
|
|
1107
|
+
if size >= threshold_bytes:
|
|
1108
|
+
example = type_examples.get(type_name, "<unknown>")
|
|
1109
|
+
count = type_counts[type_name]
|
|
1110
|
+
display_name = f"{type_name} ({count} instances)"
|
|
1111
|
+
var_sizes.append((display_name, size, example))
|
|
1112
|
+
|
|
1113
|
+
# Get the total accounted memory (excluding CUDA - counted separately)
|
|
1114
|
+
ram_accounted = sum(size for _, size, _ in var_sizes)
|
|
1115
|
+
|
|
1116
|
+
# Add CUDA entries to the display list
|
|
1117
|
+
if include_cuda:
|
|
1118
|
+
var_sizes.extend(cuda_entries)
|
|
1119
|
+
|
|
1120
|
+
# Sort by size (largest first)
|
|
1121
|
+
var_sizes.sort(key=lambda x: x[1], reverse=True)
|
|
1122
|
+
|
|
1123
|
+
# Calculate max name length for formatting
|
|
1124
|
+
max_name_len = min(max((len(name) for name, _, _ in var_sizes), default=20), 50)
|
|
1125
|
+
|
|
1126
|
+
# Print the flame chart
|
|
1127
|
+
if not var_sizes:
|
|
1128
|
+
print(f"{colors['yellow']}No variables found above the threshold of {threshold_kb} KB{colors['reset']}")
|
|
1129
|
+
return
|
|
1130
|
+
|
|
1131
|
+
# Report memory statistics
|
|
1132
|
+
print(f"RAM memory accounted for: {ram_accounted / (1024 * 1024):.2f} MB " +
|
|
1133
|
+
f"({ram_accounted / total_process_memory * 100:.1f}% of process total)")
|
|
1134
|
+
|
|
1135
|
+
if cpu_tensor_size > 0:
|
|
1136
|
+
print(f"CPU tensor data: {cpu_tensor_size / (1024 * 1024):.2f} MB " +
|
|
1137
|
+
f"({cpu_tensor_size / total_process_memory * 100:.1f}% of process total)")
|
|
1138
|
+
|
|
1139
|
+
if numpy_array_size > 0:
|
|
1140
|
+
print(f"NumPy array data: {numpy_array_size / (1024 * 1024):.2f} MB " +
|
|
1141
|
+
f"({numpy_array_size / total_process_memory * 100:.1f}% of process total)")
|
|
1142
|
+
|
|
1143
|
+
if include_cuda and cuda_mem_total > 0:
|
|
1144
|
+
print(f"CUDA memory: {cuda_mem_total / (1024 * 1024):.2f} MB")
|
|
1145
|
+
print(f"Total (RAM + CUDA): {(ram_accounted + cuda_mem_total) / (1024 * 1024):.2f} MB")
|
|
1146
|
+
|
|
1147
|
+
# For bar width calculation based on the largest object
|
|
1148
|
+
max_size = max(size for _, size, _ in var_sizes)
|
|
1149
|
+
|
|
1150
|
+
# Print header
|
|
1151
|
+
if aggregate_by_type:
|
|
1152
|
+
print(
|
|
1153
|
+
f"\n{colors['magenta']}{'Type (instances)':<{max_name_len}} | {'Size':>10} | {'Example':>50} | Usage{colors['reset']}")
|
|
1154
|
+
else:
|
|
1155
|
+
print(
|
|
1156
|
+
f"\n{colors['magenta']}{'Variable':<{max_name_len}} | {'Size':>10} | {'Type':>50} | Usage{colors['reset']}")
|
|
1157
|
+
print("-" * (max_name_len + 33 + width))
|
|
1158
|
+
|
|
1159
|
+
# Print each variable with a bar representing its size
|
|
1160
|
+
for name, size, type_info in var_sizes:
|
|
1161
|
+
# Format name (truncate if too long)
|
|
1162
|
+
if len(name) > max_name_len:
|
|
1163
|
+
name = name[:max_name_len - 3] + "..."
|
|
1164
|
+
|
|
1165
|
+
# Calculate the bar width
|
|
1166
|
+
bar_width = int((size / max_size) * (width - 10))
|
|
1167
|
+
|
|
1168
|
+
# Choose color based on size and type
|
|
1169
|
+
if type_info == "cuda_memory":
|
|
1170
|
+
color_code = colors['blue'] # CUDA memory in blue
|
|
1171
|
+
elif "torch.Tensor(CPU)" in name:
|
|
1172
|
+
color_code = colors['cyan'] # CPU tensors in cyan
|
|
1173
|
+
elif "numpy.ndarray" in name:
|
|
1174
|
+
color_code = colors['magenta'] # NumPy arrays in magenta
|
|
1175
|
+
elif size > 100 * 1024 * 1024: # >100MB
|
|
1176
|
+
color_code = colors['red']
|
|
1177
|
+
elif size > 10 * 1024 * 1024: # >10MB
|
|
1178
|
+
color_code = colors['yellow']
|
|
1179
|
+
else:
|
|
1180
|
+
color_code = colors['green']
|
|
1181
|
+
|
|
1182
|
+
# Format size
|
|
1183
|
+
if size > 1024 * 1024 * 1024: # GB range
|
|
1184
|
+
size_str = f"{size / (1024 * 1024 * 1024):.2f} GB"
|
|
1185
|
+
elif size > 1024 * 1024: # MB range
|
|
1186
|
+
size_str = f"{size / (1024 * 1024):.2f} MB"
|
|
1187
|
+
else:
|
|
1188
|
+
size_str = f"{size / 1024:.2f} KB"
|
|
1189
|
+
|
|
1190
|
+
# Truncate type_info if it's too long
|
|
1191
|
+
if len(str(type_info)) > 50:
|
|
1192
|
+
type_info = str(type_info)[:46] + "..."
|
|
1193
|
+
|
|
1194
|
+
# Print the bar
|
|
1195
|
+
bar = "█" * bar_width
|
|
1196
|
+
print(f"{name:<{max_name_len}} | {size_str:>10} | {type_info:>50} | {color_code}{bar}{colors['reset']}")
|
|
1197
|
+
|
|
1198
|
+
# Display memory that couldn't be accounted for (for RAM only)
|
|
1199
|
+
unaccounted = total_process_memory - ram_accounted
|
|
1200
|
+
if unaccounted > 0:
|
|
1201
|
+
print("\n" + "-" * (max_name_len + 33 + width))
|
|
1202
|
+
print(f"{colors['yellow']}RAM memory not accounted for: {unaccounted / (1024 * 1024):.2f} MB " +
|
|
1203
|
+
f"({unaccounted / total_process_memory * 100:.1f}% of process total){colors['reset']}")
|
|
1204
|
+
print(
|
|
1205
|
+
f"{colors['yellow']}This includes memory used by C extensions, memory fragmentation, and system overhead.{colors['reset']}")
|
|
1206
|
+
|
|
1207
|
+
# If we've scanned all objects and still missing a lot, suggest reasons
|
|
1208
|
+
if scan_all_objects and unaccounted > 0.5 * total_process_memory:
|
|
1209
|
+
print(f"{colors['yellow']}Possible reasons for large unaccounted memory:{colors['reset']}")
|
|
1210
|
+
print(f"{colors['yellow']}1. Memory allocated in C/C++ extensions not visible to Python{colors['reset']}")
|
|
1211
|
+
print(f"{colors['yellow']}2. Memory fragmentation due to many allocations/deallocations{colors['reset']}")
|
|
1212
|
+
print(
|
|
1213
|
+
f"{colors['yellow']}3. Tensors in modules not fully traversed due to safety measures{colors['reset']}")
|
|
1214
|
+
|
|
1215
|
+
# Add note about additional memory profiling
|
|
1216
|
+
print(
|
|
1217
|
+
f"\n{colors['blue']}Note: For a more complete memory profile, consider using specialized tools like:{colors['reset']}")
|
|
1218
|
+
print(f" - memory_profiler: pip install memory_profiler")
|
|
1219
|
+
print(f" - py-spy: pip install py-spy")
|
|
1220
|
+
if has_pytorch:
|
|
1221
|
+
print(f" - pytorch_memlab: for PyTorch memory analysis")
|
|
1222
|
+
print(f" - torch.cuda.memory_summary(): for detailed CUDA memory breakdown")
|
|
1223
|
+
|
|
1224
|
+
print("\n")
|
|
1225
|
+
|
|
1226
|
+
|
|
1227
|
+
def object_combo(label, current_object, objects, width=None):
|
|
1228
|
+
"""
|
|
1229
|
+
Custom implementation of imgui.combo with the same signature.
|
|
1230
|
+
|
|
1231
|
+
Args:
|
|
1232
|
+
label (str): The label for the combo box
|
|
1233
|
+
current_item (int): The index of the currently selected item
|
|
1234
|
+
items (list): List of strings containing the items
|
|
1235
|
+
height_in_items (int, optional): Number of items to display in the dropdown. Defaults to -1 (auto).
|
|
1236
|
+
|
|
1237
|
+
Returns:
|
|
1238
|
+
tuple: (changed, new_current_item)
|
|
1239
|
+
"""
|
|
1240
|
+
# Remember the initial current_item to detect changes
|
|
1241
|
+
changed = False
|
|
1242
|
+
|
|
1243
|
+
visible_label = label.split("##")[0]
|
|
1244
|
+
if current_object is None and len(objects) > 0:
|
|
1245
|
+
current_model = list(objects.values())[0]
|
|
1246
|
+
|
|
1247
|
+
# Begin the combo widget
|
|
1248
|
+
LSDView().style_manager.save_style()
|
|
1249
|
+
|
|
1250
|
+
# vis.style_manager.set_imgui_tint(*sub_layers[current_item].tint)
|
|
1251
|
+
if current_object is None:
|
|
1252
|
+
if len(objects) == 0:
|
|
1253
|
+
imgui.text(f"No {visible_label} available")
|
|
1254
|
+
return False, None
|
|
1255
|
+
|
|
1256
|
+
# Get first object from dict
|
|
1257
|
+
current_object = list(objects.values())[0]
|
|
1258
|
+
changed = True
|
|
1259
|
+
|
|
1260
|
+
if width is not None:
|
|
1261
|
+
imgui.set_next_item_width(width)
|
|
1262
|
+
|
|
1263
|
+
if current_object is not None:
|
|
1264
|
+
name = current_object.name
|
|
1265
|
+
else:
|
|
1266
|
+
name = "Unset"
|
|
1267
|
+
|
|
1268
|
+
if imgui.begin_combo(f"##{label}", name, imgui.COMBO_HEIGHT_LARGEST):
|
|
1269
|
+
# Loop through each item in the list
|
|
1270
|
+
current_tint = LSDView().style_manager.get_tint()
|
|
1271
|
+
for i, (name, object) in enumerate(objects.items()):
|
|
1272
|
+
# Check if this item is selected
|
|
1273
|
+
is_selected = (object == current_object)
|
|
1274
|
+
if hasattr(object, 'tint') and object.tint is not None:
|
|
1275
|
+
LSDView().style_manager.set_imgui_tint(*object.tint)
|
|
1276
|
+
text_color = LSDView().style_manager.make_color_unpacked(value=0.7, saturation_scale=1.0)
|
|
1277
|
+
push_style_color(imgui.COLOR_TEXT, *text_color)
|
|
1278
|
+
# Create a selectable item for each option
|
|
1279
|
+
|
|
1280
|
+
if imgui.selectable(f"{object.name}##{object.id}", is_selected)[0]:
|
|
1281
|
+
# Update current item if user selects a different one
|
|
1282
|
+
current_object = object
|
|
1283
|
+
changed = True
|
|
1284
|
+
|
|
1285
|
+
if hasattr(object, 'tint') and object.tint is not None:
|
|
1286
|
+
pop_style_color()
|
|
1287
|
+
|
|
1288
|
+
# Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
|
|
1289
|
+
if is_selected:
|
|
1290
|
+
imgui.set_item_default_focus()
|
|
1291
|
+
|
|
1292
|
+
# Restore the tint after each selectable
|
|
1293
|
+
LSDView().style_manager.set_imgui_tint(*current_tint)
|
|
1294
|
+
|
|
1295
|
+
# End the combo widget
|
|
1296
|
+
imgui.end_combo()
|
|
1297
|
+
# vis.style_manager.restore_style()
|
|
1298
|
+
|
|
1299
|
+
# Return whether the selection changed and the (possibly new) current item
|
|
1300
|
+
return changed, current_object
|
|
1301
|
+
|
|
1302
|
+
|
|
1303
|
+
def cleanup_cuda_memory(verbose=False, vis=None):
|
|
1304
|
+
"""
|
|
1305
|
+
Clean up unreferenced CUDA memory that might not be automatically released by PyTorch.
|
|
1306
|
+
|
|
1307
|
+
Parameters:
|
|
1308
|
+
verbose (bool): Whether to print memory usage information before and after cleanup
|
|
1309
|
+
|
|
1310
|
+
Returns:
|
|
1311
|
+
tuple: (initial_allocated, final_allocated, freed_memory) in MB
|
|
1312
|
+
"""
|
|
1313
|
+
# Check if CUDA is available
|
|
1314
|
+
print_stack_trace(1)
|
|
1315
|
+
|
|
1316
|
+
import torch
|
|
1317
|
+
if not torch.cuda.is_available():
|
|
1318
|
+
print("CUDA is not available")
|
|
1319
|
+
return (0, 0, 0)
|
|
1320
|
+
|
|
1321
|
+
if vis is not None:
|
|
1322
|
+
for view in vis.root.tensor_views.values():
|
|
1323
|
+
parent = view
|
|
1324
|
+
subview = parent.sub()
|
|
1325
|
+
if view.tensor is not None:
|
|
1326
|
+
del view.tensor
|
|
1327
|
+
view.tensor = None
|
|
1328
|
+
if view.tensor_b is not None:
|
|
1329
|
+
del view.tensor_b
|
|
1330
|
+
view.tensor_b = None
|
|
1331
|
+
if view.tensor_c is not None:
|
|
1332
|
+
del view.tensor_c
|
|
1333
|
+
view.tensor_c = None
|
|
1334
|
+
torch.cuda.empty_cache()
|
|
1335
|
+
|
|
1336
|
+
vis.root.selected_views = {}
|
|
1337
|
+
|
|
1338
|
+
# Get initial memory usage
|
|
1339
|
+
initial_allocated = torch.cuda.memory_allocated() / (1024 * 1024) # Convert to MB
|
|
1340
|
+
initial_reserved = torch.cuda.memory_reserved() / (1024 * 1024) # Convert to MB
|
|
1341
|
+
|
|
1342
|
+
if verbose:
|
|
1343
|
+
print(f"Initial CUDA memory allocated: {initial_allocated:.2f} MB")
|
|
1344
|
+
print(f"Initial CUDA memory reserved: {initial_reserved:.2f} MB")
|
|
1345
|
+
|
|
1346
|
+
|
|
1347
|
+
# Get final memory usage
|
|
1348
|
+
final_allocated = torch.cuda.memory_allocated() / (1024 * 1024) # Convert to MB
|
|
1349
|
+
final_reserved = torch.cuda.memory_reserved() / (1024 * 1024) # Convert to MB
|
|
1350
|
+
|
|
1351
|
+
freed_memory = initial_allocated - final_allocated
|
|
1352
|
+
|
|
1353
|
+
if verbose:
|
|
1354
|
+
print(f"Final CUDA memory allocated: {final_allocated:.2f} MB")
|
|
1355
|
+
print(f"Final CUDA memory reserved: {final_reserved:.2f} MB")
|
|
1356
|
+
print(f"Freed memory: {freed_memory:.2f} MB")
|
|
1357
|
+
|
|
1358
|
+
if final_reserved > final_allocated:
|
|
1359
|
+
print(
|
|
1360
|
+
f"Note: {final_reserved - final_allocated:.2f} MB is still reserved by PyTorch but not allocated to tensors")
|
|
1361
|
+
print("This memory can be used by PyTorch without additional GPU memory allocation")
|
|
1362
|
+
|
|
1363
|
+
torch.cuda.empty_cache()
|
|
1364
|
+
|
|
1365
|
+
# Clear PyTorch cache
|
|
1366
|
+
torch.cuda.empty_cache()
|
|
1367
|
+
|
|
1368
|
+
# No gc.collect here: on first boot this walked (and reaped) the whole
|
|
1369
|
+
# PREVIOUS session - a multi-second stall. The launcher runs that by
|
|
1370
|
+
# itself once the session has ended and its UI is up (server_gui.render_gui).
|
|
1371
|
+
|
|
1372
|
+
# Force CUDA synchronization - ensures all operations are complete
|
|
1373
|
+
torch.cuda.synchronize()
|
|
1374
|
+
|
|
1375
|
+
# Clear cache again after collecting garbage
|
|
1376
|
+
torch.cuda.empty_cache()
|
|
1377
|
+
|
|
1378
|
+
return (initial_allocated, final_allocated, freed_memory)
|
|
1379
|
+
|
|
1380
|
+
|
|
1381
|
+
def find_cuda_tensors():
|
|
1382
|
+
"""
|
|
1383
|
+
Find and print information about all CUDA tensors currently in memory
|
|
1384
|
+
|
|
1385
|
+
Returns:
|
|
1386
|
+
int: Count of CUDA tensors found
|
|
1387
|
+
"""
|
|
1388
|
+
cuda_tensors = []
|
|
1389
|
+
|
|
1390
|
+
import torch
|
|
1391
|
+
# Get all objects in memory
|
|
1392
|
+
for obj in gc.get_objects():
|
|
1393
|
+
try:
|
|
1394
|
+
# Check if it is a torch tensor and on CUDA
|
|
1395
|
+
if torch.is_tensor(obj) and obj.device.type == 'cuda':
|
|
1396
|
+
cuda_tensors.append(obj)
|
|
1397
|
+
except:
|
|
1398
|
+
# Some objects might raise exceptions when checking attributes
|
|
1399
|
+
pass
|
|
1400
|
+
|
|
1401
|
+
# Print summary
|
|
1402
|
+
print(f"Found {len(cuda_tensors)} CUDA tensors in memory")
|
|
1403
|
+
|
|
1404
|
+
# Group by shape for better overview
|
|
1405
|
+
shape_count = {}
|
|
1406
|
+
|
|
1407
|
+
for tensor in cuda_tensors:
|
|
1408
|
+
shape = str(tensor.shape)
|
|
1409
|
+
if shape in shape_count:
|
|
1410
|
+
shape_count[shape] += 1
|
|
1411
|
+
else:
|
|
1412
|
+
shape_count[shape] = 1
|
|
1413
|
+
|
|
1414
|
+
# Print shape statistics
|
|
1415
|
+
print("\nTensor shapes:")
|
|
1416
|
+
for shape, count in sorted(shape_count.items(), key=lambda x: x[1], reverse=True):
|
|
1417
|
+
print(f" {shape}: {count} tensors")
|
|
1418
|
+
|
|
1419
|
+
return len(cuda_tensors)
|