mcpywrap-core 0.6.4__tar.gz → 0.7.0__tar.gz
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.
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/MANIFEST.in +1 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/PKG-INFO +1 -1
- mcpywrap_core-0.7.0/mcpywrap_core/_version.py +1 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/cli.py +2 -1
- mcpywrap_core-0.7.0/mcpywrap_core/commands/profile_cmd.py +159 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/engines/install.py +1 -1
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/engines/macos.py +1 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/engines/macos_network.py +3 -1
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/sessions.py +3 -0
- mcpywrap_core-0.7.0/mcpywrap_core/profiling/__init__.py +1 -0
- mcpywrap_core-0.7.0/mcpywrap_core/profiling/analysis.py +309 -0
- mcpywrap_core-0.7.0/mcpywrap_core/profiling/endpoint.py +112 -0
- mcpywrap_core-0.7.0/mcpywrap_core/profiling/tools.json +22 -0
- mcpywrap_core-0.7.0/mcpywrap_core/profiling/tools.py +226 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/SKILL.md +2 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/macos-local.md +4 -3
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/network-sessions.md +6 -0
- mcpywrap_core-0.7.0/mcpywrap_core/skill/references/profiling.md +70 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core.egg-info/PKG-INFO +1 -1
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core.egg-info/SOURCES.txt +6 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/pyproject.toml +1 -1
- mcpywrap_core-0.6.4/mcpywrap_core/_version.py +0 -1
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/LICENSE +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/README.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/THIRD_PARTY_NOTICES.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/__init__.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/__main__.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/_process_identity.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/_script_worker.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/api.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/builders/AddonsPack.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/builders/MapPack.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/builders/__init__.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/builders/assembly.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/builders/dependency_manager.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/builders/file_merge.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/builders/output_lock.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/builders/project_builder.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/builders/watcher.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/code_libraries.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/command_context.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/__init__.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/add_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/build_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/connect_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/default_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/dependency_prompt.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/deployment_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/deployment_output.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/deps_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/desktop_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/dev_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/doctor_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/edit_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/engine_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/host_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/init_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/mod_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/modsdk_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/package_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/publish_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/record_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/remove_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/run_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/runtime_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/runtime_input_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/runtime_player_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/runtime_ui_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/script_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/serve_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/session_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/skill_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/sync_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/commands/ui_cmd.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/config.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/consumer_prepare.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/core-contract.json +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/custom_packaging.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/dependencies.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/dependency_feedback.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/engines/__init__.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/engines/android_icon.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/engines/apk_profile.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/engines/backend.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/engines/host.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/engines/macos_icons.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/engines/official.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/engines/preferences.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/engines/tui.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/engines/windows.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/framework_presets.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/frameworks.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/git_cache.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/git_projects.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/host.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/host_assets.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/input_executor.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/input_plan.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/SimpleMonitor.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/__init__.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/auth_interaction.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/bridge_assets.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/deployment_links.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/diagnostics.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/discovery.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/editor.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/file_logs.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/game.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/host_input.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/hot_reload.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/image.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/input_sequence.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/lan_locator.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/lan_memory.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/lan_window.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/launcher_control.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/launcher_python.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/log_colorizer.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/log_protocol.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/log_query.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/log_stream.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/mcs.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/mcs_auth.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/network.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/private_logs.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/processes.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/recording_worker.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/recordings.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/runtime_cppconfig.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/runtime_debug.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/runtime_input_payload.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/runtime_key_timeline_payload.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/runtime_player_payload.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/runtime_ui.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/runtime_ui_outline.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/runtime_ui_payload.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/screenshot.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/session_capabilities.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/session_worker.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/studio_server.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/studio_server_ui.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/symlink_helper_global.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/symlink_helper_map.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/symlinks.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/unified_input.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/window.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mcstudio/window_activation.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/minecraft/__init__.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/minecraft/addons.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/minecraft/level_dat.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/minecraft/map.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/minecraft/modsdk_environment.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/minecraft/modsdk_versions.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/minecraft/netease_modsdk.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/minecraft/template/generate_mod_files.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/minecraft/template/mod_template.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mobile_host/__init__.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mobile_host/client.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mobile_host/intervention.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mobile_host/options.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mobile_host/preparation.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mobile_host/profiles.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mobile_host/receipts.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mobile_host/routing.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/mobile_host/streams.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/operations/__init__.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/operations/build.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/operations/input_snapshot.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/operations/package.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/operations/project.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/operations/sync.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/prepare_environment.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/project_dependencies.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/project_init.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/project_scripts.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/recording_io.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/remote/__init__.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/remote/client.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/remote/http_server.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/remote/protocol.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/remote/service.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/__init__.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/deployment.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/deployment_inspection.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/deployment_service.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/diagnostics.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/groups.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/instances.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/lan_menu.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/native.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/occupancy.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/preparation.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/profiles.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/storage.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/targets.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/watch.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/window_policy.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/windows.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/runtime/windows_join.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/agents/openai.yaml +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/assets/consumer-prepare/framework/behavior_pack/CatalogRuntime/__init__.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/assets/consumer-prepare/framework/behavior_pack/manifest.json +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/assets/consumer-prepare/framework/build_tools/example.toml +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/assets/consumer-prepare/framework/build_tools/prepare.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/assets/consumer-prepare/framework/build_tools/schema.json +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/assets/consumer-prepare/framework/pyproject.toml +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/assets/consumer-prepare/game/.gitignore +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/assets/consumer-prepare/game/behavior_pack/manifest.json +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/assets/consumer-prepare/game/data/items.json +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/assets/consumer-prepare/game/pyproject.toml +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/assets/consumer-prepare/game/resource_pack/manifest.json +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/application-host.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/capture.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/consumer-prepare-author.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/consumer-prepare.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/dual-clients.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/input-sequence.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/input-writing.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/instance-world-settings.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/local-development.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/project-dependencies.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/project-scripts.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/project-workflow.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/remote-testing.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/runtime-debug.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/runtime-input.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/runtime-key-timeline.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/runtime-player.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/runtime-ui.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/script-example.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/setup.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/references/troubleshooting.md +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/scripts/bootstrap.ps1 +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/scripts/bootstrap.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/scripts/game_window.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/skill/scripts/smoke.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/source_files.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/timeline.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/ui/__init__.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/ui/application.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/ui/assets/mcpy.icns +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/ui/assets/mcpy.icon/Assets/01-cube.svg +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/ui/assets/mcpy.icon/Assets/02-prompt.svg +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/ui/assets/mcpy.icon/icon.json +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/ui/assets/mcpy.png +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/ui/engine_setup.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/ui/log_view.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/ui/log_window.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/ui/project_ui.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/ui/run_session.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/ui/session_controller.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/ui/session_tools.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/ui/session_views.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/ui/world_config.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/utils/__init__.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/utils/console.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/utils/link_snapshot.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/utils/locks.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/utils/paths.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/utils/pip_error_parser.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/utils/print_guide.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/utils/project_setup.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core/utils/utils.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core.egg-info/dependency_links.txt +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core.egg-info/requires.txt +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_core.egg-info/top_level.txt +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_host/__init__.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/mcpywrap_host/store.py +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/setup.cfg +0 -0
- {mcpywrap_core-0.6.4 → mcpywrap_core-0.7.0}/setup.py +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
include README.md LICENSE THIRD_PARTY_NOTICES.md
|
|
2
2
|
include mcpywrap_core/core-contract.json
|
|
3
|
+
include mcpywrap_core/profiling/tools.json
|
|
3
4
|
recursive-include mcpywrap_core/ui/assets *.svg *.png *.icns *.json
|
|
4
5
|
prune mcpywrap_core/mcstudio/bridge_payload
|
|
5
6
|
prune mcpywrap_core/mcstudio/window_capture
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.7.0'
|
|
@@ -8,7 +8,8 @@ from .command_context import OperationGroup, configure_context
|
|
|
8
8
|
COMMANDS = {
|
|
9
9
|
name: (name + '_cmd', name + '_cmd') for name in
|
|
10
10
|
('host', 'skill', 'modsdk', 'init', 'add', 'remove', 'build', 'dev', 'package', 'publish',
|
|
11
|
-
'mod', 'run', 'deps', 'connect', 'edit', 'ui', 'doctor', 'engine', 'sync', 'serve', 'record', 'script', 'deployment'
|
|
11
|
+
'mod', 'run', 'deps', 'connect', 'edit', 'ui', 'doctor', 'engine', 'sync', 'serve', 'record', 'script', 'deployment',
|
|
12
|
+
'profile')
|
|
12
13
|
}
|
|
13
14
|
COMMANDS.update({name: ('session_cmd', name + '_cmd') for name in ('status', 'logs', 'stop', 'restart')})
|
|
14
15
|
COMMANDS.update({name: ('runtime_cmd', name + '_cmd') for name in ('runtime', 'py', 'reload')})
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""性能分析:基于游戏内置 Tracy 0.11.1(协议 69)的实时查看、采集、报告与对比。"""
|
|
2
|
+
import datetime
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from ..command_context import OperationCommand, json_output, project_dir
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _download_progress():
|
|
14
|
+
last = [0.0]
|
|
15
|
+
|
|
16
|
+
def update(done, total):
|
|
17
|
+
now = time.monotonic()
|
|
18
|
+
if now - last[0] >= 1 or done == total:
|
|
19
|
+
click.echo('下载 Tracy 工具:%.1f / %.1f MiB' % (done / 1048576, total / 1048576), err=True)
|
|
20
|
+
last[0] = now
|
|
21
|
+
return update
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _target(session, port):
|
|
25
|
+
from ..profiling import endpoint, tools
|
|
26
|
+
if session and port:
|
|
27
|
+
raise click.UsageError('--session 与 --port 只能指定一个')
|
|
28
|
+
return endpoint.resolve(project_dir(), tools.manifest()['protocol'], session=session, port=port)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _launch(command):
|
|
32
|
+
from ..mcstudio.processes import background_options
|
|
33
|
+
process = subprocess.Popen(command, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
|
|
34
|
+
stderr=subprocess.DEVNULL, **background_options())
|
|
35
|
+
return process.pid
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _summary(trace, top, filter):
|
|
39
|
+
from ..profiling import analysis
|
|
40
|
+
summary = analysis.summarize(analysis.load(trace), project_dir(), top=top, filter=filter)
|
|
41
|
+
summary['trace'] = str(Path(trace).resolve())
|
|
42
|
+
summary['report'] = str(analysis.report_path(Path(trace).resolve()))
|
|
43
|
+
return summary
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _show(summary):
|
|
47
|
+
"""人类模式打印文本并返回简短结果;JSON 模式返回完整摘要。"""
|
|
48
|
+
if json_output():
|
|
49
|
+
return summary
|
|
50
|
+
from ..profiling.analysis import format_summary
|
|
51
|
+
click.echo(format_summary(summary))
|
|
52
|
+
click.echo('')
|
|
53
|
+
return {'trace': summary['trace']}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@click.group(name='profile')
|
|
57
|
+
def profile_cmd():
|
|
58
|
+
"""性能分析:实时分析器、离线查看、采集、报告与前后对比(Tracy 0.11.1)。"""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@profile_cmd.command(cls=OperationCommand, local_only=True, name='install')
|
|
62
|
+
@click.option('--from', 'from_archive', type=click.Path(exists=True, dir_okay=False), help='导入已下载的工具包 ZIP(须与清单摘要一致)')
|
|
63
|
+
@click.option('--force', is_flag=True, help='已安装时也重新解压')
|
|
64
|
+
def profile_install(from_archive, force):
|
|
65
|
+
"""下载并校验固定版本的 Tracy 工具;多镜像、可续传,可用 MCPY_TRACY_MIRROR 指定镜像。"""
|
|
66
|
+
from ..profiling import tools
|
|
67
|
+
return tools.install(from_archive, force, None if json_output() else _download_progress())
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@profile_cmd.command(cls=OperationCommand, local_only=True, name='status')
|
|
71
|
+
def profile_status():
|
|
72
|
+
"""工具安装状态与本机正在监听 Tracy 的游戏进程(只查连接表,不连接游戏)。"""
|
|
73
|
+
from ..profiling import endpoint, tools
|
|
74
|
+
return dict(tools.status(), endpoints=endpoint.discover())
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@profile_cmd.command(cls=OperationCommand, local_only=True, name='open')
|
|
78
|
+
@click.argument('trace', required=False, type=click.Path(exists=True, dir_okay=False))
|
|
79
|
+
@click.option('--session', help='连接此会话的游戏')
|
|
80
|
+
@click.option('--port', type=click.IntRange(1, 65535), help='直接指定本机 Tracy 端口')
|
|
81
|
+
def profile_open(trace, session, port):
|
|
82
|
+
"""打开 Tracy 分析器:给出 .tracy 文件时离线查看,否则实时连接游戏。"""
|
|
83
|
+
from ..profiling import tools
|
|
84
|
+
profiler = str(tools.require('tracy-profiler'))
|
|
85
|
+
if trace:
|
|
86
|
+
if session or port:
|
|
87
|
+
raise click.UsageError('离线查看文件时不能同时指定 --session 或 --port')
|
|
88
|
+
path = Path(trace).resolve()
|
|
89
|
+
return {'mode': 'file', 'trace': str(path), 'pid': _launch([profiler, str(path)])}
|
|
90
|
+
target = _target(session, port)
|
|
91
|
+
pid = _launch([profiler, '-a', '127.0.0.1', '-p', str(target['port'])])
|
|
92
|
+
return dict(target, mode='live', pid=pid,
|
|
93
|
+
hint='分析器已连接;Tracy 同时只接受一个连接,关闭分析器窗口后才能再次采集。')
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@profile_cmd.command(cls=OperationCommand, local_only=True, name='capture')
|
|
97
|
+
@click.option('--session', help='采集此会话的游戏')
|
|
98
|
+
@click.option('--port', type=click.IntRange(1, 65535), help='直接指定本机 Tracy 端口')
|
|
99
|
+
@click.option('--seconds', type=click.IntRange(1, 600), default=20, show_default=True, help='采集时长')
|
|
100
|
+
@click.option('--output', type=click.Path(dir_okay=False), help='.tracy 输出路径,默认 .runtime/profiles/ 下按时间命名')
|
|
101
|
+
@click.option('--top', type=click.IntRange(1, 200), default=15, show_default=True, help='报告中每类列出的条目数')
|
|
102
|
+
@click.option('--filter', help='只列出名称或模块路径包含该文本的脚本函数')
|
|
103
|
+
@click.option('--no-report', is_flag=True, help='只保存采集文件,不生成报告')
|
|
104
|
+
def profile_capture(session, port, seconds, output, top, filter, no_report):
|
|
105
|
+
"""无界面采集一段时间并生成报告;采集期间请让游戏处于要分析的场景。"""
|
|
106
|
+
from ..engines.host import EngineError
|
|
107
|
+
from ..profiling import tools
|
|
108
|
+
capture = str(tools.require('tracy-capture'))
|
|
109
|
+
if not no_report:
|
|
110
|
+
tools.require('mcpy-tracy-report')
|
|
111
|
+
if output:
|
|
112
|
+
path = Path(output).expanduser().resolve()
|
|
113
|
+
if path.suffix != '.tracy' or path.exists():
|
|
114
|
+
raise click.UsageError('输出必须是尚不存在的 .tracy 文件')
|
|
115
|
+
else:
|
|
116
|
+
stamp = datetime.datetime.now().strftime('%Y%m%d-%H%M%S')
|
|
117
|
+
path = project_dir() / '.runtime' / 'profiles' / ('%s-%s.tracy' % (stamp, (session or 'port')[:8]))
|
|
118
|
+
target = _target(session, port)
|
|
119
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
120
|
+
if not json_output():
|
|
121
|
+
click.echo('采集 %d 秒(127.0.0.1:%d)…' % (seconds, target['port']), err=True)
|
|
122
|
+
command = [capture, '-o', str(path), '-a', '127.0.0.1', '-p', str(target['port']), '-s', str(seconds)]
|
|
123
|
+
try:
|
|
124
|
+
done = subprocess.run(command, capture_output=True, timeout=seconds + 60)
|
|
125
|
+
except subprocess.TimeoutExpired:
|
|
126
|
+
raise EngineError('采集超时:未能连接或结束', 'capture_failed',
|
|
127
|
+
'确认没有其他分析器占用该端口后重试。', port=target['port']) from None
|
|
128
|
+
if done.returncode != 0 or not path.is_file():
|
|
129
|
+
tail = done.stdout.decode('utf-8', 'replace').replace('\r', '\n').strip().splitlines()[-3:]
|
|
130
|
+
raise EngineError('采集失败', 'capture_failed', details='\n'.join(tail), port=target['port'])
|
|
131
|
+
if no_report:
|
|
132
|
+
return dict(target, trace=str(path), size=path.stat().st_size)
|
|
133
|
+
return _show(dict(_summary(path, top, filter), target=target))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@profile_cmd.command(cls=OperationCommand, local_only=True, name='report')
|
|
137
|
+
@click.argument('trace', type=click.Path(exists=True, dir_okay=False))
|
|
138
|
+
@click.option('--top', type=click.IntRange(1, 200), default=15, show_default=True)
|
|
139
|
+
@click.option('--filter', help='只列出名称或模块路径包含该文本的脚本函数')
|
|
140
|
+
def profile_report(trace, top, filter):
|
|
141
|
+
"""汇总 .tracy 文件:主循环与 tick 分位数、脚本函数热点与尖峰、最慢帧归因。"""
|
|
142
|
+
return _show(_summary(trace, top, filter))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@profile_cmd.command(cls=OperationCommand, local_only=True, name='diff')
|
|
146
|
+
@click.argument('base', type=click.Path(exists=True, dir_okay=False))
|
|
147
|
+
@click.argument('new', type=click.Path(exists=True, dir_okay=False))
|
|
148
|
+
@click.option('--top', type=click.IntRange(1, 200), default=15, show_default=True)
|
|
149
|
+
def profile_diff(base, new, top):
|
|
150
|
+
"""对比两次采集(按每秒耗时,时长可不同);用于验证优化前后的变化。"""
|
|
151
|
+
from ..profiling import analysis
|
|
152
|
+
before = analysis.summarize(analysis.load(base), project_dir(), top=200)
|
|
153
|
+
after = analysis.summarize(analysis.load(new), project_dir(), top=200)
|
|
154
|
+
result = dict(analysis.diff(before, after, top), base=str(Path(base).resolve()), new=str(Path(new).resolve()))
|
|
155
|
+
if json_output():
|
|
156
|
+
return result
|
|
157
|
+
click.echo(analysis.format_diff(result))
|
|
158
|
+
click.echo('')
|
|
159
|
+
return {'base': result['base'], 'new': result['new']}
|
|
@@ -24,7 +24,7 @@ from .host import EngineError, describe, require_macos
|
|
|
24
24
|
|
|
25
25
|
CATALOG_ENV = 'MCPY_RUNTIME_CATALOG'
|
|
26
26
|
DEFAULT_CATALOG = ('https://github.com/EaseCation/mcpelauncher-manifest/releases/download/'
|
|
27
|
-
'mcpy-runtime-v0.4.
|
|
27
|
+
'mcpy-runtime-v0.4.8/catalog.json')
|
|
28
28
|
# Public constant from the official ApkDownload component, verified 2026-10-05.
|
|
29
29
|
# https://mcdev.webapp.163.com/static/js/4.05713153ba17aea6db53.js
|
|
30
30
|
CDN_CONSTANT = 'mEE7Cot48r9j2AvEL2N6jpXEc'
|
|
@@ -75,6 +75,7 @@ def pinned_runtime(item):
|
|
|
75
75
|
return {'runtime': str(app), 'application': str(application), 'game': str(game), 'profile': plan['profile'], 'runtime_id': plan['runtime_id'],
|
|
76
76
|
'compat_report': compatibility, 'cppconfig_protocol': meta.get('cppconfig_protocol'),
|
|
77
77
|
'network_connect_protocol': meta.get('network_connect_protocol'),
|
|
78
|
+
'network_plain_source_protocol': meta.get('network_plain_source_protocol'),
|
|
78
79
|
'developer_identity_protocol': meta.get('developer_identity_protocol'),
|
|
79
80
|
'background_window_protocol': meta.get('background_window_protocol')}
|
|
80
81
|
|
|
@@ -51,7 +51,9 @@ def connect(target, *, project_dir=None, packs=(), engine_overrides=None, detach
|
|
|
51
51
|
data = sessions.start(root, backend='macos-arm64', session_id=session, launch=plan,
|
|
52
52
|
group=group, role=role, background=background,
|
|
53
53
|
network={'target': asdict(target), 'engine': {'version': version},
|
|
54
|
-
'player': player
|
|
54
|
+
'player': player,
|
|
55
|
+
# 运行包是否为开发服下发的纯明文源码包补充导入;.mcp 包始终由游戏加载
|
|
56
|
+
'plain_source_packs': state.get('network_plain_source_protocol') == 1})
|
|
55
57
|
result = sessions.handoff(data)
|
|
56
58
|
if detach:
|
|
57
59
|
return result
|
|
@@ -212,6 +212,9 @@ def handoff(data):
|
|
|
212
212
|
identity_provided=data['mcs_auth'], authenticated=False,
|
|
213
213
|
connection_verified=data.get('connection_verified', False), addons_assembled=False,
|
|
214
214
|
engine_version=data['network']['engine']['version'])
|
|
215
|
+
if 'plain_source_packs' in data['network']:
|
|
216
|
+
# 仅报告运行包能力;是否有包按明文加载以游戏日志 [PlainSourcePack] 为准
|
|
217
|
+
result['plain_source_packs'] = data['network']['plain_source_packs']
|
|
215
218
|
return result
|
|
216
219
|
|
|
217
220
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""基于游戏内置 Tracy 0.11.1(协议 69)的性能分析:工具分发、端点发现、采集与报告。"""
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""把 mcpy-tracy-report 的原始统计整理为面向开发者与 Agent 的摘要、对比与文本。
|
|
2
|
+
|
|
3
|
+
游戏内置 Tracy 为每个 Python 函数生成区段:名称为函数名,文件为部署后的 .py 路径,行号为 def 行;
|
|
4
|
+
Python 内置函数(如 time.time)也有区段,文件为模块名、行号为 0。
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
import re
|
|
8
|
+
import subprocess
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from ..engines.host import EngineError
|
|
12
|
+
from . import tools
|
|
13
|
+
|
|
14
|
+
MAIN_LOOP = 'Main Update'
|
|
15
|
+
SERVER_TICK = 'serverUpdate'
|
|
16
|
+
REPORT_SCHEMA = 1
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def report_path(trace):
|
|
20
|
+
trace = Path(trace)
|
|
21
|
+
return trace.with_name(trace.name + '.report.json')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def load(trace):
|
|
25
|
+
"""读取或生成原始报告;与采集文件同目录缓存,采集文件更新后重新生成。"""
|
|
26
|
+
trace = Path(trace).expanduser().resolve()
|
|
27
|
+
if not trace.is_file():
|
|
28
|
+
raise EngineError('采集文件不存在:' + str(trace), 'trace_missing')
|
|
29
|
+
cache = report_path(trace)
|
|
30
|
+
executable = tools.require('mcpy-tracy-report')
|
|
31
|
+
# 缓存绑定工具包摘要,工具更新后重新生成
|
|
32
|
+
identity = tools.installed_identity()
|
|
33
|
+
if cache.is_file() and cache.stat().st_mtime >= trace.stat().st_mtime:
|
|
34
|
+
try:
|
|
35
|
+
data = json.loads(cache.read_text(encoding='utf-8'))
|
|
36
|
+
if data.get('schema') == REPORT_SCHEMA and data.get('mcpy_tools') == identity:
|
|
37
|
+
return data
|
|
38
|
+
except ValueError:
|
|
39
|
+
pass
|
|
40
|
+
command = [str(executable), str(trace)]
|
|
41
|
+
try:
|
|
42
|
+
done = subprocess.run(command, capture_output=True, timeout=900)
|
|
43
|
+
except subprocess.TimeoutExpired:
|
|
44
|
+
raise EngineError('采集文件分析超时', 'report_failed') from None
|
|
45
|
+
if done.returncode != 0:
|
|
46
|
+
raise EngineError('采集文件分析失败:' + done.stderr.decode('utf-8', 'replace').strip()[-400:], 'report_failed')
|
|
47
|
+
try:
|
|
48
|
+
data = json.loads(done.stdout.decode('utf-8', 'replace'))
|
|
49
|
+
except ValueError:
|
|
50
|
+
raise EngineError('分析工具输出无效', 'report_failed') from None
|
|
51
|
+
data['mcpy_tools'] = identity
|
|
52
|
+
try:
|
|
53
|
+
cache.write_text(json.dumps(data, ensure_ascii=False), encoding='utf-8')
|
|
54
|
+
except OSError:
|
|
55
|
+
pass
|
|
56
|
+
return data
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def classify(zone):
|
|
60
|
+
file = zone.get('file') or ''
|
|
61
|
+
if file.lower().endswith('.py'):
|
|
62
|
+
return 'script'
|
|
63
|
+
if zone.get('line') == 0 and file != 'Engine' and not re.search(r'[\\/]', file):
|
|
64
|
+
return 'builtin'
|
|
65
|
+
return 'engine'
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def module_path(file):
|
|
69
|
+
"""部署路径 .../behavior_packs/<包目录>/<模块路径> → 模块路径;不在包内时返回 None。"""
|
|
70
|
+
parts = [p for p in re.split(r'[\\/]', file or '') if p]
|
|
71
|
+
lowered = [p.lower() for p in parts]
|
|
72
|
+
for marker, skip in (('behavior_packs', 2), ('behavior_pack', 1)):
|
|
73
|
+
if marker in lowered:
|
|
74
|
+
index = len(lowered) - 1 - lowered[::-1].index(marker)
|
|
75
|
+
if index + skip < len(parts):
|
|
76
|
+
return '/'.join(parts[index + skip:])
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def source_roots(project):
|
|
81
|
+
"""项目与依赖的行为包目录;解析失败时退回项目内的常规目录,不安装或运行钩子。"""
|
|
82
|
+
if not project:
|
|
83
|
+
return []
|
|
84
|
+
project = Path(project).resolve()
|
|
85
|
+
roots = []
|
|
86
|
+
if (project / 'pyproject.toml').is_file():
|
|
87
|
+
try:
|
|
88
|
+
from ..dependencies import DependencyService, read_project
|
|
89
|
+
manager = DependencyService(project).resolve(read_project(project))
|
|
90
|
+
packs = [getattr(manager.root_node, 'addon_pack', None)] + list(manager.get_all_dependencies().values())
|
|
91
|
+
roots += [Path(p.behavior_pack_dir) for p in packs if p is not None and getattr(p, 'behavior_pack_dir', None)]
|
|
92
|
+
except Exception:
|
|
93
|
+
pass
|
|
94
|
+
roots += [project / 'behavior_pack']
|
|
95
|
+
parent = project / 'behavior_packs'
|
|
96
|
+
roots += sorted(p for p in parent.iterdir() if p.is_dir()) if parent.is_dir() else []
|
|
97
|
+
return [r for r in dict.fromkeys(roots) if r.is_dir()]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class _Sources:
|
|
101
|
+
def __init__(self, project):
|
|
102
|
+
self.project = Path(project).resolve() if project else None
|
|
103
|
+
self.roots = None
|
|
104
|
+
|
|
105
|
+
def find(self, module):
|
|
106
|
+
if not module or not self.project:
|
|
107
|
+
return None
|
|
108
|
+
if self.roots is None:
|
|
109
|
+
self.roots = source_roots(self.project)
|
|
110
|
+
for root in self.roots:
|
|
111
|
+
candidate = root / module
|
|
112
|
+
if candidate.is_file():
|
|
113
|
+
try:
|
|
114
|
+
return str(candidate.relative_to(self.project))
|
|
115
|
+
except ValueError:
|
|
116
|
+
return str(candidate)
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _ms(value):
|
|
121
|
+
return round(value / 1e6, 3)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def summarize(raw, project=None, top=15, filter=None):
|
|
125
|
+
zones = raw['zones']
|
|
126
|
+
span = max(raw['capture']['span_ns'], 1) / 1e9
|
|
127
|
+
thread_names = {t['id']: t['name'] for t in raw['threads']}
|
|
128
|
+
sources = _Sources(project)
|
|
129
|
+
kinds = [classify(z) for z in zones]
|
|
130
|
+
modules = [module_path(z['file']) if kind == 'script' else None for z, kind in zip(zones, kinds)]
|
|
131
|
+
callers = {}
|
|
132
|
+
builtin_from_script = 0
|
|
133
|
+
for edge in raw['edges']:
|
|
134
|
+
if edge['parent'] >= 0:
|
|
135
|
+
callers.setdefault(edge['child'], []).append(edge)
|
|
136
|
+
if kinds[edge['child']] == 'builtin' and kinds[edge['parent']] == 'script':
|
|
137
|
+
builtin_from_script += edge['total_ns']
|
|
138
|
+
|
|
139
|
+
def label(i):
|
|
140
|
+
z = zones[i]
|
|
141
|
+
if kinds[i] == 'script':
|
|
142
|
+
return '%s (%s:%d)' % (z['name'], modules[i] or z['file'], z['line'])
|
|
143
|
+
return z['name']
|
|
144
|
+
|
|
145
|
+
def view(i):
|
|
146
|
+
z = zones[i]
|
|
147
|
+
item = {'name': z['name'], 'kind': kinds[i], 'count': z['count'], 'per_second': round(z['count'] / span, 2),
|
|
148
|
+
'total_ms': _ms(z['total_ns']), 'self_ms': _ms(z['self_ns']),
|
|
149
|
+
'mean_ms': _ms(z['total_ns'] / z['count']) if z['count'] else 0,
|
|
150
|
+
'p50_ms': _ms(z['p50_ns']), 'p95_ms': _ms(z['p95_ns']), 'p99_ms': _ms(z['p99_ns']),
|
|
151
|
+
'max_ms': _ms(z['max_ns'])}
|
|
152
|
+
if kinds[i] == 'script':
|
|
153
|
+
item.update(module=modules[i], line=z['line'], source=sources.find(modules[i]))
|
|
154
|
+
if not modules[i]:
|
|
155
|
+
item['file'] = z['file']
|
|
156
|
+
item['threads'] = [thread_names.get(t['id'], str(t['id']))
|
|
157
|
+
for t in sorted(z['threads'], key=lambda t: -t['self_ns'])][:3]
|
|
158
|
+
item['callers'] = [label(e['parent']) for e in sorted(callers.get(i, []), key=lambda e: -e['total_ns'])[:3]]
|
|
159
|
+
return item
|
|
160
|
+
|
|
161
|
+
def loop(name):
|
|
162
|
+
index = next((i for i, z in enumerate(zones) if z['name'] == name and kinds[i] == 'engine'), None)
|
|
163
|
+
if index is None:
|
|
164
|
+
return None
|
|
165
|
+
z = zones[index]
|
|
166
|
+
return {'zone': name, 'count': z['count'], 'per_second': round(z['count'] / span, 2),
|
|
167
|
+
'mean_ms': _ms(z['total_ns'] / max(z['count'], 1)), 'p50_ms': _ms(z['p50_ns']),
|
|
168
|
+
'p95_ms': _ms(z['p95_ns']), 'p99_ms': _ms(z['p99_ns']), 'max_ms': _ms(z['max_ns'])}
|
|
169
|
+
|
|
170
|
+
needle = (filter or '').lower()
|
|
171
|
+
script_ids = [i for i, kind in enumerate(kinds) if kind == 'script' and
|
|
172
|
+
(not needle or needle in zones[i]['name'].lower() or needle in (modules[i] or zones[i]['file']).lower())]
|
|
173
|
+
script_self = sum(zones[i]['self_ns'] for i, kind in enumerate(kinds) if kind == 'script')
|
|
174
|
+
engine_ids = [i for i, kind in enumerate(kinds) if kind == 'engine']
|
|
175
|
+
|
|
176
|
+
windows = []
|
|
177
|
+
for window in raw.get('windows', []):
|
|
178
|
+
slowest = []
|
|
179
|
+
for instance in window['slowest']:
|
|
180
|
+
entries = []
|
|
181
|
+
# 窗口所在线程的条目优先,其他线程(可能只是等待)其次
|
|
182
|
+
ordered = sorted(instance['top_self'], key=lambda e: (e['thread'] != instance['thread'], -e['self_ns']))
|
|
183
|
+
for entry in ordered[:8]:
|
|
184
|
+
i = entry['zone']
|
|
185
|
+
entries.append({'zone': label(i), 'kind': kinds[i], 'thread': thread_names.get(entry['thread'], str(entry['thread'])),
|
|
186
|
+
'self_ms': _ms(entry['self_ns'])})
|
|
187
|
+
slowest.append({'at_s': round(instance['start_ns'] / 1e9, 3), 'duration_ms': _ms(instance['duration_ns']),
|
|
188
|
+
'thread': thread_names.get(instance['thread'], str(instance['thread'])), 'top_self': entries})
|
|
189
|
+
windows.append({'zone': window['zone'], 'slowest': slowest})
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
'capture': {'program': raw['capture']['program'], 'span_s': round(span, 3), 'zone_count': raw['capture']['zone_count'],
|
|
193
|
+
'tracy_version': raw.get('tracy_version')},
|
|
194
|
+
'main_loop': loop(MAIN_LOOP),
|
|
195
|
+
'server_tick': loop(SERVER_TICK),
|
|
196
|
+
'script': {'self_ms_per_s': round(script_self / 1e6 / span, 3),
|
|
197
|
+
'builtin_from_script_ms_per_s': round(builtin_from_script / 1e6 / span, 3),
|
|
198
|
+
'functions': sum(1 for kind in kinds if kind == 'script')},
|
|
199
|
+
'scripts': [view(i) for i in sorted(script_ids, key=lambda i: -zones[i]['total_ns'])[:top]],
|
|
200
|
+
'script_spikes': [view(i) for i in sorted(script_ids, key=lambda i: -zones[i]['max_ns'])[:min(top, 8)]],
|
|
201
|
+
'engine': [view(i) for i in sorted(engine_ids, key=lambda i: -zones[i]['self_ns'])[:top]],
|
|
202
|
+
'windows': windows,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _key(item):
|
|
207
|
+
return (item['kind'], item['name'], item.get('module') or item.get('file'), item.get('line'))
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def diff(base, new, top=15):
|
|
211
|
+
"""按每秒耗时对比两次摘要(采集时长可以不同)。摘要需使用较大的 top 生成以覆盖足够函数。"""
|
|
212
|
+
def rate(item, summary):
|
|
213
|
+
return item['total_ms'] / max(summary['capture']['span_s'], 1e-9)
|
|
214
|
+
|
|
215
|
+
def compare(section):
|
|
216
|
+
before = {_key(item): item for item in base[section]}
|
|
217
|
+
after = {_key(item): item for item in new[section]}
|
|
218
|
+
rows = []
|
|
219
|
+
for key in dict.fromkeys(list(before) + list(after)):
|
|
220
|
+
a, b = before.get(key), after.get(key)
|
|
221
|
+
ref = b or a
|
|
222
|
+
row = {'name': ref['name'], 'kind': ref['kind'],
|
|
223
|
+
'base_ms_per_s': round(rate(a, base), 3) if a else None,
|
|
224
|
+
'new_ms_per_s': round(rate(b, new), 3) if b else None,
|
|
225
|
+
'base_mean_ms': a['mean_ms'] if a else None, 'new_mean_ms': b['mean_ms'] if b else None,
|
|
226
|
+
'base_max_ms': a['max_ms'] if a else None, 'new_max_ms': b['max_ms'] if b else None}
|
|
227
|
+
if ref.get('module') or ref.get('line') is not None:
|
|
228
|
+
row.update(module=ref.get('module'), line=ref.get('line'))
|
|
229
|
+
row['delta_ms_per_s'] = round((row['new_ms_per_s'] or 0) - (row['base_ms_per_s'] or 0), 3)
|
|
230
|
+
rows.append(row)
|
|
231
|
+
return sorted(rows, key=lambda r: -abs(r['delta_ms_per_s']))[:top]
|
|
232
|
+
|
|
233
|
+
def loop(name):
|
|
234
|
+
a, b = base.get(name), new.get(name)
|
|
235
|
+
if not a or not b:
|
|
236
|
+
return None
|
|
237
|
+
return {k: {'base': a[k], 'new': b[k], 'delta': round(b[k] - a[k], 3)}
|
|
238
|
+
for k in ('mean_ms', 'p50_ms', 'p95_ms', 'p99_ms', 'max_ms', 'per_second')}
|
|
239
|
+
|
|
240
|
+
return {'main_loop': loop('main_loop'), 'server_tick': loop('server_tick'),
|
|
241
|
+
'script_self_ms_per_s': {'base': base['script']['self_ms_per_s'], 'new': new['script']['self_ms_per_s'],
|
|
242
|
+
'delta': round(new['script']['self_ms_per_s'] - base['script']['self_ms_per_s'], 3)},
|
|
243
|
+
'scripts': compare('scripts'), 'engine': compare('engine')}
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _where(item):
|
|
247
|
+
if item['kind'] != 'script':
|
|
248
|
+
return ''
|
|
249
|
+
return '%s:%s' % (item.get('source') or item.get('module') or item.get('file'), item.get('line'))
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def format_summary(summary):
|
|
253
|
+
lines = []
|
|
254
|
+
capture = summary['capture']
|
|
255
|
+
lines.append('采集 %.1f 秒 · %s 个区段 · %s' % (capture['span_s'], format(capture['zone_count'], ','), capture['program']))
|
|
256
|
+
for key, title in (('main_loop', '主循环'), ('server_tick', '服务端更新')):
|
|
257
|
+
loop = summary.get(key)
|
|
258
|
+
if loop:
|
|
259
|
+
lines.append('%s %s:p50 %.2fms p95 %.2fms p99 %.2fms 最大 %.2fms (%.1f 次/秒)' % (
|
|
260
|
+
title, loop['zone'], loop['p50_ms'], loop['p95_ms'], loop['p99_ms'], loop['max_ms'], loop['per_second']))
|
|
261
|
+
script = summary['script']
|
|
262
|
+
lines.append('脚本:自身 %.2f ms/秒,脚本调用的内置函数 %.2f ms/秒,%d 个函数' % (
|
|
263
|
+
script['self_ms_per_s'], script['builtin_from_script_ms_per_s'], script['functions']))
|
|
264
|
+
span = max(capture['span_s'], 1e-9)
|
|
265
|
+
|
|
266
|
+
def table(title, items):
|
|
267
|
+
if not items:
|
|
268
|
+
return
|
|
269
|
+
lines.append('')
|
|
270
|
+
lines.append(title)
|
|
271
|
+
lines.append(' %9s %9s %8s %8s %8s %8s %s' % ('总ms/秒', '自身ms/秒', '平均ms', 'p95ms', '最大ms', '次/秒', '函数'))
|
|
272
|
+
for item in items:
|
|
273
|
+
lines.append(' %9.2f %9.2f %8.3f %8.3f %8.2f %8.1f %s %s' % (
|
|
274
|
+
item['total_ms'] / span, item['self_ms'] / span, item['mean_ms'], item['p95_ms'], item['max_ms'],
|
|
275
|
+
item['per_second'], item['name'], _where(item)))
|
|
276
|
+
|
|
277
|
+
table('脚本函数(按总耗时)', summary['scripts'])
|
|
278
|
+
table('脚本耗时尖峰(按单次最大)', summary['script_spikes'])
|
|
279
|
+
table('引擎区段(按自身耗时)', summary['engine'])
|
|
280
|
+
for window in summary['windows']:
|
|
281
|
+
if not window['slowest']:
|
|
282
|
+
continue
|
|
283
|
+
lines.append('')
|
|
284
|
+
lines.append('最慢的 %s:' % window['zone'])
|
|
285
|
+
for instance in window['slowest']:
|
|
286
|
+
parts = ['%s[%s] %.1f' % (e['zone'], e['thread'], e['self_ms']) for e in instance['top_self'][:5]]
|
|
287
|
+
lines.append(' %.2fms @%.2fs %s' % (instance['duration_ms'], instance['at_s'], ';'.join(parts)))
|
|
288
|
+
return '\n'.join(lines)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def format_diff(result):
|
|
292
|
+
lines = []
|
|
293
|
+
for key, title in (('main_loop', '主循环'), ('server_tick', '服务端更新')):
|
|
294
|
+
loop = result.get(key)
|
|
295
|
+
if loop:
|
|
296
|
+
lines.append('%s:p50 %.2f→%.2fms p95 %.2f→%.2fms 最大 %.2f→%.2fms' % (
|
|
297
|
+
title, loop['p50_ms']['base'], loop['p50_ms']['new'], loop['p95_ms']['base'], loop['p95_ms']['new'],
|
|
298
|
+
loop['max_ms']['base'], loop['max_ms']['new']))
|
|
299
|
+
s = result['script_self_ms_per_s']
|
|
300
|
+
lines.append('脚本自身:%.2f → %.2f ms/秒(%+.2f)' % (s['base'], s['new'], s['delta']))
|
|
301
|
+
for section, title in (('scripts', '脚本函数变化'), ('engine', '引擎区段变化')):
|
|
302
|
+
lines.append('')
|
|
303
|
+
lines.append(title + '(按每秒耗时变化):')
|
|
304
|
+
for row in result[section]:
|
|
305
|
+
where = ' %s:%s' % (row.get('module'), row.get('line')) if row.get('module') else ''
|
|
306
|
+
lines.append(' %+9.2f ms/秒 %s → %s %s%s' % (
|
|
307
|
+
row['delta_ms_per_s'], '-' if row['base_ms_per_s'] is None else '%.2f' % row['base_ms_per_s'],
|
|
308
|
+
'-' if row['new_ms_per_s'] is None else '%.2f' % row['new_ms_per_s'], row['name'], where))
|
|
309
|
+
return '\n'.join(lines)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""定位游戏内置 Tracy 端点并核对协议。
|
|
2
|
+
|
|
3
|
+
游戏只监听 127.0.0.1,从 8086 起顺延到 8105(多实例)。Tracy 同时只接受一个分析器连接;
|
|
4
|
+
握手成功会被视为一次短暂连接,随后断开,游戏回到等待状态。
|
|
5
|
+
"""
|
|
6
|
+
import socket
|
|
7
|
+
import struct
|
|
8
|
+
|
|
9
|
+
from ..engines.host import EngineError
|
|
10
|
+
|
|
11
|
+
HOST = '127.0.0.1'
|
|
12
|
+
PORTS = range(8086, 8106)
|
|
13
|
+
SHIBBOLETH = b'TracyPrf'
|
|
14
|
+
STATUS = {0: 'pending', 1: 'welcome', 2: 'protocol_mismatch', 3: 'not_available', 4: 'dropped'}
|
|
15
|
+
GAME_PREFIXES = ('mcpelauncher', 'minecraft.windows')
|
|
16
|
+
BUSY_HINT = 'Tracy 同时只接受一个连接;关闭已打开的分析器或正在进行的采集后重试。'
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def handshake(port, protocol, timeout=2.0, host=HOST):
|
|
20
|
+
"""返回 welcome / protocol_mismatch / busy / closed 等状态;不读取分析数据。"""
|
|
21
|
+
try:
|
|
22
|
+
# Windows 对未监听的本机端口会重试 SYN 而非立即拒绝,连接阶段超时同样视为未监听
|
|
23
|
+
sock = socket.create_connection((host, port), timeout=timeout)
|
|
24
|
+
except OSError:
|
|
25
|
+
return 'closed'
|
|
26
|
+
try:
|
|
27
|
+
with sock:
|
|
28
|
+
sock.sendall(SHIBBOLETH + struct.pack('<I', protocol))
|
|
29
|
+
reply = sock.recv(1)
|
|
30
|
+
except socket.timeout:
|
|
31
|
+
# 连接已建立但没有应答:游戏已有分析器连接,不再处理新的握手
|
|
32
|
+
return 'busy'
|
|
33
|
+
except OSError:
|
|
34
|
+
return 'closed'
|
|
35
|
+
return STATUS.get(reply[0], 'unknown') if reply else 'dropped'
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _connections(process):
|
|
39
|
+
getter = getattr(process, 'net_connections', None) or process.connections
|
|
40
|
+
return getter(kind='tcp')
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def listening_ports(process):
|
|
44
|
+
import psutil
|
|
45
|
+
try:
|
|
46
|
+
ports = {c.laddr.port for c in _connections(process)
|
|
47
|
+
if c.status == psutil.CONN_LISTEN and c.laddr and c.laddr.port in PORTS}
|
|
48
|
+
except (psutil.AccessDenied, psutil.NoSuchProcess):
|
|
49
|
+
return []
|
|
50
|
+
return sorted(ports)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def discover():
|
|
54
|
+
"""列出本机游戏进程的 Tracy 监听端口;只查询系统连接表,不建立连接。"""
|
|
55
|
+
import psutil
|
|
56
|
+
found = []
|
|
57
|
+
for process in psutil.process_iter(['name', 'pid']):
|
|
58
|
+
name = (process.info.get('name') or '').lower()
|
|
59
|
+
if name.startswith(GAME_PREFIXES):
|
|
60
|
+
for port in listening_ports(process):
|
|
61
|
+
found.append({'pid': process.info['pid'], 'port': port, 'process': process.info.get('name')})
|
|
62
|
+
return sorted(found, key=lambda item: item['port'])
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def session_endpoint(project, session):
|
|
66
|
+
from ..mcstudio import sessions
|
|
67
|
+
from ..mcstudio.processes import checked_process
|
|
68
|
+
try:
|
|
69
|
+
data = sessions.read(project, session)
|
|
70
|
+
except ValueError:
|
|
71
|
+
from ..command_context import remote_url
|
|
72
|
+
if remote_url():
|
|
73
|
+
raise EngineError('本机没有该会话:' + session, 'session_unavailable',
|
|
74
|
+
'暂不支持远程会话的性能采集;请在 Windows 测试机本机运行 mcpy profile。') from None
|
|
75
|
+
raise
|
|
76
|
+
if data.get('state') not in ('starting', 'running') or not data.get('game'):
|
|
77
|
+
raise EngineError('会话没有运行中的游戏进程', 'session_unavailable')
|
|
78
|
+
process = checked_process(data['game'])
|
|
79
|
+
if process is None:
|
|
80
|
+
raise EngineError('会话的游戏进程已退出', 'session_unavailable')
|
|
81
|
+
ports = listening_ports(process)
|
|
82
|
+
if not ports:
|
|
83
|
+
hint = '游戏启动后需要数秒才开始监听;稍后重试。'
|
|
84
|
+
if data.get('backend') == 'macos-arm64':
|
|
85
|
+
hint += 'macOS 需要运行包 0.4.7 及以上,旧实例请更新运行包后用 run --new 新建。'
|
|
86
|
+
raise EngineError('游戏进程没有监听 Tracy 端口', 'profiler_unavailable', hint, session=session)
|
|
87
|
+
return {'session': session, 'pid': process.pid, 'port': ports[0], 'backend': data.get('backend', 'windows')}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def resolve(project, protocol, session=None, port=None):
|
|
91
|
+
"""确定目标端口并用固定协议握手;协议不符时直接报错,不尝试其他版本。"""
|
|
92
|
+
if session:
|
|
93
|
+
target = session_endpoint(project, session)
|
|
94
|
+
elif port:
|
|
95
|
+
target = {'port': port}
|
|
96
|
+
else:
|
|
97
|
+
found = discover()
|
|
98
|
+
if not found:
|
|
99
|
+
raise EngineError('未发现监听 Tracy 的游戏进程', 'profiler_unavailable',
|
|
100
|
+
'先启动游戏(mcpy run),或用 --session / --port 指定目标。')
|
|
101
|
+
if len(found) > 1:
|
|
102
|
+
raise EngineError('发现多个游戏实例,请用 --session 或 --port 指定', 'ambiguous_target', endpoints=found)
|
|
103
|
+
target = found[0]
|
|
104
|
+
state = handshake(target['port'], protocol)
|
|
105
|
+
if state == 'welcome':
|
|
106
|
+
return dict(target, protocol=protocol)
|
|
107
|
+
if state == 'protocol_mismatch':
|
|
108
|
+
raise EngineError('游戏内置 Tracy 协议与固定版本(协议 %d)不一致' % protocol, 'protocol_mismatch',
|
|
109
|
+
'不要换用其他 Tracy 版本;请反馈游戏版本,等待 mcpywrap 更新固定的分析器版本。', port=target['port'])
|
|
110
|
+
if state == 'busy':
|
|
111
|
+
raise EngineError('Tracy 端口 %d 已有分析器连接' % target['port'], 'profiler_busy', BUSY_HINT, port=target['port'])
|
|
112
|
+
raise EngineError('Tracy 端口 %d 不可用(%s)' % (target['port'], state), 'profiler_unavailable', port=target['port'])
|