PraisonAI 3.0.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.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- praisonai-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core profiler implementation for PraisonAI CLI.
|
|
3
|
+
|
|
4
|
+
Provides cProfile-based profiling with detailed per-file/per-function timing,
|
|
5
|
+
call graphs, and import time analysis.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import cProfile
|
|
9
|
+
import io
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import pstats
|
|
13
|
+
import re
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
import time
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Patterns for secrets to redact
|
|
24
|
+
SECRET_PATTERNS = [
|
|
25
|
+
r'sk-[a-zA-Z0-9]{20,}', # OpenAI keys
|
|
26
|
+
r'sk-ant-[a-zA-Z0-9-]{20,}', # Anthropic keys
|
|
27
|
+
r'AIza[a-zA-Z0-9_-]{35}', # Google API keys
|
|
28
|
+
r'tvly-[a-zA-Z0-9-]{20,}', # Tavily keys
|
|
29
|
+
r'[a-zA-Z0-9_-]{32,}', # Generic long tokens (be careful)
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def redact_secrets(text: str) -> str:
|
|
34
|
+
"""Redact potential secrets from text."""
|
|
35
|
+
result = text
|
|
36
|
+
for pattern in SECRET_PATTERNS[:4]: # Skip generic pattern for safety
|
|
37
|
+
result = re.sub(pattern, '[REDACTED]', result)
|
|
38
|
+
return result
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ProfilerConfig:
|
|
43
|
+
"""Configuration for profiler."""
|
|
44
|
+
deep: bool = False # Enable deep call tracing
|
|
45
|
+
limit: int = 30 # Top N functions to show
|
|
46
|
+
sort_by: str = "cumulative" # cumulative or tottime
|
|
47
|
+
show_files: bool = False # Group by file
|
|
48
|
+
show_callers: bool = False # Show callers
|
|
49
|
+
show_callees: bool = False # Show callees
|
|
50
|
+
importtime: bool = False # Show import timing
|
|
51
|
+
first_token: bool = False # Track time to first token
|
|
52
|
+
save_path: Optional[str] = None # Path to save artifacts
|
|
53
|
+
output_format: str = "text" # text or json
|
|
54
|
+
stream: bool = False # Streaming mode
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class TimingBreakdown:
|
|
59
|
+
"""Timing breakdown for profiling."""
|
|
60
|
+
cli_parse_ms: float = 0.0
|
|
61
|
+
imports_ms: float = 0.0
|
|
62
|
+
agent_construction_ms: float = 0.0
|
|
63
|
+
model_init_ms: float = 0.0
|
|
64
|
+
first_token_ms: float = 0.0
|
|
65
|
+
total_run_ms: float = 0.0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class FunctionStats:
|
|
70
|
+
"""Statistics for a single function."""
|
|
71
|
+
name: str
|
|
72
|
+
filename: str
|
|
73
|
+
lineno: int
|
|
74
|
+
calls: int
|
|
75
|
+
tottime: float # Time in function excluding subcalls
|
|
76
|
+
cumtime: float # Time in function including subcalls
|
|
77
|
+
|
|
78
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
79
|
+
return {
|
|
80
|
+
"name": self.name,
|
|
81
|
+
"filename": self.filename,
|
|
82
|
+
"lineno": self.lineno,
|
|
83
|
+
"calls": self.calls,
|
|
84
|
+
"tottime_ms": self.tottime * 1000,
|
|
85
|
+
"cumtime_ms": self.cumtime * 1000,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class FileStats:
|
|
91
|
+
"""Statistics aggregated by file."""
|
|
92
|
+
filename: str
|
|
93
|
+
total_time: float
|
|
94
|
+
functions: List[FunctionStats] = field(default_factory=list)
|
|
95
|
+
|
|
96
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
97
|
+
return {
|
|
98
|
+
"filename": self.filename,
|
|
99
|
+
"total_time_ms": self.total_time * 1000,
|
|
100
|
+
"function_count": len(self.functions),
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass
|
|
105
|
+
class ProfilerResult:
|
|
106
|
+
"""Result from a profiling run."""
|
|
107
|
+
prompt: str
|
|
108
|
+
response: str
|
|
109
|
+
timing: TimingBreakdown
|
|
110
|
+
function_stats: List[FunctionStats]
|
|
111
|
+
file_stats: List[FileStats]
|
|
112
|
+
callers: Dict[str, List[str]] = field(default_factory=dict)
|
|
113
|
+
callees: Dict[str, List[str]] = field(default_factory=dict)
|
|
114
|
+
import_times: List[Tuple[str, float]] = field(default_factory=list)
|
|
115
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
116
|
+
timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat() + "Z")
|
|
117
|
+
|
|
118
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
119
|
+
return {
|
|
120
|
+
"timestamp": self.timestamp,
|
|
121
|
+
"metadata": self.metadata,
|
|
122
|
+
"prompt": self.prompt[:200] + "..." if len(self.prompt) > 200 else self.prompt,
|
|
123
|
+
"response_preview": self.response[:200] + "..." if len(self.response) > 200 else self.response,
|
|
124
|
+
"timing": {
|
|
125
|
+
"cli_parse_ms": self.timing.cli_parse_ms,
|
|
126
|
+
"imports_ms": self.timing.imports_ms,
|
|
127
|
+
"agent_construction_ms": self.timing.agent_construction_ms,
|
|
128
|
+
"model_init_ms": self.timing.model_init_ms,
|
|
129
|
+
"first_token_ms": self.timing.first_token_ms,
|
|
130
|
+
"total_run_ms": self.timing.total_run_ms,
|
|
131
|
+
},
|
|
132
|
+
"top_functions": [f.to_dict() for f in self.function_stats[:20]],
|
|
133
|
+
"top_files": [f.to_dict() for f in self.file_stats[:10]],
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class QueryProfiler:
|
|
138
|
+
"""
|
|
139
|
+
Profiler for query execution.
|
|
140
|
+
|
|
141
|
+
Uses cProfile for function-level profiling and optional sys.setprofile
|
|
142
|
+
for deep call tracing.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
def __init__(self, config: Optional[ProfilerConfig] = None):
|
|
146
|
+
self.config = config or ProfilerConfig()
|
|
147
|
+
self.profiler = cProfile.Profile()
|
|
148
|
+
self._first_token_time: Optional[float] = None
|
|
149
|
+
self._start_time: float = 0.0
|
|
150
|
+
self._deep_trace_data: Dict[str, Dict] = {}
|
|
151
|
+
|
|
152
|
+
def _on_first_token(self):
|
|
153
|
+
"""Called when first token is received (for streaming)."""
|
|
154
|
+
if self._first_token_time is None:
|
|
155
|
+
self._first_token_time = time.perf_counter()
|
|
156
|
+
|
|
157
|
+
def _deep_trace_callback(self, frame, event, arg):
|
|
158
|
+
"""Callback for sys.setprofile deep tracing."""
|
|
159
|
+
if event not in ('call', 'return'):
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
filename = frame.f_code.co_filename
|
|
163
|
+
funcname = frame.f_code.co_name
|
|
164
|
+
lineno = frame.f_lineno
|
|
165
|
+
|
|
166
|
+
key = f"{filename}:{funcname}:{lineno}"
|
|
167
|
+
|
|
168
|
+
if key not in self._deep_trace_data:
|
|
169
|
+
self._deep_trace_data[key] = {
|
|
170
|
+
'filename': filename,
|
|
171
|
+
'funcname': funcname,
|
|
172
|
+
'lineno': lineno,
|
|
173
|
+
'calls': 0,
|
|
174
|
+
'total_time': 0.0,
|
|
175
|
+
'start_times': [],
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if event == 'call':
|
|
179
|
+
self._deep_trace_data[key]['calls'] += 1
|
|
180
|
+
self._deep_trace_data[key]['start_times'].append(time.perf_counter())
|
|
181
|
+
elif event == 'return' and self._deep_trace_data[key]['start_times']:
|
|
182
|
+
start = self._deep_trace_data[key]['start_times'].pop()
|
|
183
|
+
self._deep_trace_data[key]['total_time'] += time.perf_counter() - start
|
|
184
|
+
|
|
185
|
+
def profile_query(
|
|
186
|
+
self,
|
|
187
|
+
prompt: str,
|
|
188
|
+
model: Optional[str] = None,
|
|
189
|
+
stream: bool = False,
|
|
190
|
+
) -> ProfilerResult:
|
|
191
|
+
"""
|
|
192
|
+
Profile a query execution.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
prompt: The prompt to execute
|
|
196
|
+
model: Optional model to use
|
|
197
|
+
stream: Whether to use streaming mode
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
ProfilerResult with detailed timing and statistics
|
|
201
|
+
"""
|
|
202
|
+
timing = TimingBreakdown()
|
|
203
|
+
response = ""
|
|
204
|
+
|
|
205
|
+
# Metadata
|
|
206
|
+
metadata = self._collect_metadata(model)
|
|
207
|
+
|
|
208
|
+
# Time CLI parsing (simulated - actual parsing happens before this)
|
|
209
|
+
cli_start = time.perf_counter()
|
|
210
|
+
timing.cli_parse_ms = (time.perf_counter() - cli_start) * 1000
|
|
211
|
+
|
|
212
|
+
# Time imports
|
|
213
|
+
import_start = time.perf_counter()
|
|
214
|
+
try:
|
|
215
|
+
from praisonaiagents import Agent
|
|
216
|
+
except ImportError:
|
|
217
|
+
raise ImportError("praisonaiagents not installed")
|
|
218
|
+
timing.imports_ms = (time.perf_counter() - import_start) * 1000
|
|
219
|
+
|
|
220
|
+
# Time agent construction
|
|
221
|
+
construct_start = time.perf_counter()
|
|
222
|
+
agent_config = {
|
|
223
|
+
"name": "ProfilerAgent",
|
|
224
|
+
"role": "Assistant",
|
|
225
|
+
"goal": "Complete the task",
|
|
226
|
+
"verbose": False,
|
|
227
|
+
}
|
|
228
|
+
if model:
|
|
229
|
+
agent_config["llm"] = model
|
|
230
|
+
|
|
231
|
+
agent = Agent(**agent_config)
|
|
232
|
+
timing.agent_construction_ms = (time.perf_counter() - construct_start) * 1000
|
|
233
|
+
|
|
234
|
+
# Time model initialization (first call)
|
|
235
|
+
model_init_start = time.perf_counter()
|
|
236
|
+
timing.model_init_ms = (time.perf_counter() - model_init_start) * 1000
|
|
237
|
+
|
|
238
|
+
# Profile the actual execution
|
|
239
|
+
self._start_time = time.perf_counter()
|
|
240
|
+
self._first_token_time = None
|
|
241
|
+
|
|
242
|
+
if self.config.deep:
|
|
243
|
+
sys.setprofile(self._deep_trace_callback)
|
|
244
|
+
|
|
245
|
+
self.profiler.enable()
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
if stream and hasattr(agent, '_start_stream'):
|
|
249
|
+
# Streaming mode with first token tracking
|
|
250
|
+
chunks = []
|
|
251
|
+
for chunk in agent._start_stream(prompt):
|
|
252
|
+
if not chunks:
|
|
253
|
+
self._on_first_token()
|
|
254
|
+
chunks.append(chunk)
|
|
255
|
+
response = ''.join(chunks)
|
|
256
|
+
else:
|
|
257
|
+
# Non-streaming mode
|
|
258
|
+
response = agent.start(prompt)
|
|
259
|
+
if response is None:
|
|
260
|
+
response = ""
|
|
261
|
+
finally:
|
|
262
|
+
self.profiler.disable()
|
|
263
|
+
if self.config.deep:
|
|
264
|
+
sys.setprofile(None)
|
|
265
|
+
|
|
266
|
+
end_time = time.perf_counter()
|
|
267
|
+
timing.total_run_ms = (end_time - self._start_time) * 1000
|
|
268
|
+
|
|
269
|
+
if self._first_token_time:
|
|
270
|
+
timing.first_token_ms = (self._first_token_time - self._start_time) * 1000
|
|
271
|
+
|
|
272
|
+
# Extract statistics
|
|
273
|
+
function_stats = self._extract_function_stats()
|
|
274
|
+
file_stats = self._extract_file_stats(function_stats)
|
|
275
|
+
callers, callees = self._extract_call_graph()
|
|
276
|
+
|
|
277
|
+
# Get import times if requested
|
|
278
|
+
import_times = []
|
|
279
|
+
if self.config.importtime:
|
|
280
|
+
import_times = self._get_import_times()
|
|
281
|
+
|
|
282
|
+
return ProfilerResult(
|
|
283
|
+
prompt=prompt,
|
|
284
|
+
response=str(response),
|
|
285
|
+
timing=timing,
|
|
286
|
+
function_stats=function_stats,
|
|
287
|
+
file_stats=file_stats,
|
|
288
|
+
callers=callers if self.config.show_callers else {},
|
|
289
|
+
callees=callees if self.config.show_callees else {},
|
|
290
|
+
import_times=import_times,
|
|
291
|
+
metadata=metadata,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
def _collect_metadata(self, model: Optional[str]) -> Dict[str, Any]:
|
|
295
|
+
"""Collect system metadata."""
|
|
296
|
+
import platform
|
|
297
|
+
try:
|
|
298
|
+
from praisonai.version import __version__ as praisonai_version
|
|
299
|
+
except ImportError:
|
|
300
|
+
praisonai_version = "unknown"
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
"python_version": platform.python_version(),
|
|
304
|
+
"platform": platform.platform(),
|
|
305
|
+
"praisonai_version": praisonai_version,
|
|
306
|
+
"model": model or "default",
|
|
307
|
+
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
def _extract_function_stats(self) -> List[FunctionStats]:
|
|
311
|
+
"""Extract function statistics from profiler."""
|
|
312
|
+
stats_stream = io.StringIO()
|
|
313
|
+
stats = pstats.Stats(self.profiler, stream=stats_stream)
|
|
314
|
+
|
|
315
|
+
# Sort by configured key
|
|
316
|
+
sort_key = pstats.SortKey.CUMULATIVE if self.config.sort_by == "cumulative" else pstats.SortKey.TIME
|
|
317
|
+
stats.sort_stats(sort_key)
|
|
318
|
+
|
|
319
|
+
function_stats = []
|
|
320
|
+
for (filename, lineno, funcname), (cc, nc, tt, ct, callers) in stats.stats.items():
|
|
321
|
+
function_stats.append(FunctionStats(
|
|
322
|
+
name=funcname,
|
|
323
|
+
filename=filename,
|
|
324
|
+
lineno=lineno,
|
|
325
|
+
calls=nc,
|
|
326
|
+
tottime=tt,
|
|
327
|
+
cumtime=ct,
|
|
328
|
+
))
|
|
329
|
+
|
|
330
|
+
# Sort and limit
|
|
331
|
+
if self.config.sort_by == "cumulative":
|
|
332
|
+
function_stats.sort(key=lambda x: x.cumtime, reverse=True)
|
|
333
|
+
else:
|
|
334
|
+
function_stats.sort(key=lambda x: x.tottime, reverse=True)
|
|
335
|
+
|
|
336
|
+
return function_stats[:self.config.limit]
|
|
337
|
+
|
|
338
|
+
def _extract_file_stats(self, function_stats: List[FunctionStats]) -> List[FileStats]:
|
|
339
|
+
"""Aggregate statistics by file."""
|
|
340
|
+
file_map: Dict[str, FileStats] = {}
|
|
341
|
+
|
|
342
|
+
for func in function_stats:
|
|
343
|
+
if func.filename not in file_map:
|
|
344
|
+
file_map[func.filename] = FileStats(
|
|
345
|
+
filename=func.filename,
|
|
346
|
+
total_time=0.0,
|
|
347
|
+
functions=[],
|
|
348
|
+
)
|
|
349
|
+
file_map[func.filename].total_time += func.cumtime
|
|
350
|
+
file_map[func.filename].functions.append(func)
|
|
351
|
+
|
|
352
|
+
file_stats = list(file_map.values())
|
|
353
|
+
file_stats.sort(key=lambda x: x.total_time, reverse=True)
|
|
354
|
+
|
|
355
|
+
return file_stats[:self.config.limit]
|
|
356
|
+
|
|
357
|
+
def _extract_call_graph(self) -> Tuple[Dict[str, List[str]], Dict[str, List[str]]]:
|
|
358
|
+
"""Extract caller/callee relationships."""
|
|
359
|
+
callers: Dict[str, List[str]] = {}
|
|
360
|
+
callees: Dict[str, List[str]] = {}
|
|
361
|
+
|
|
362
|
+
stats = pstats.Stats(self.profiler)
|
|
363
|
+
|
|
364
|
+
for (filename, lineno, funcname), (cc, nc, tt, ct, caller_dict) in stats.stats.items():
|
|
365
|
+
func_key = f"{funcname} ({os.path.basename(filename)}:{lineno})"
|
|
366
|
+
|
|
367
|
+
if caller_dict:
|
|
368
|
+
callers[func_key] = []
|
|
369
|
+
for (caller_file, caller_line, caller_name), _ in caller_dict.items():
|
|
370
|
+
caller_key = f"{caller_name} ({os.path.basename(caller_file)}:{caller_line})"
|
|
371
|
+
callers[func_key].append(caller_key)
|
|
372
|
+
|
|
373
|
+
if caller_key not in callees:
|
|
374
|
+
callees[caller_key] = []
|
|
375
|
+
callees[caller_key].append(func_key)
|
|
376
|
+
|
|
377
|
+
return callers, callees
|
|
378
|
+
|
|
379
|
+
def _get_import_times(self) -> List[Tuple[str, float]]:
|
|
380
|
+
"""Get import timing by running subprocess with -X importtime."""
|
|
381
|
+
try:
|
|
382
|
+
result = subprocess.run(
|
|
383
|
+
[sys.executable, "-X", "importtime", "-c", "import praisonaiagents"],
|
|
384
|
+
capture_output=True,
|
|
385
|
+
text=True,
|
|
386
|
+
timeout=30,
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
import_times = []
|
|
390
|
+
for line in result.stderr.split('\n'):
|
|
391
|
+
if 'import time:' in line:
|
|
392
|
+
# Parse: "import time: 123 | 456 | module_name"
|
|
393
|
+
match = re.search(r'import time:\s+(\d+)\s+\|\s+(\d+)\s+\|\s+(.+)', line)
|
|
394
|
+
if match:
|
|
395
|
+
self_time = int(match.group(1))
|
|
396
|
+
cumulative = int(match.group(2))
|
|
397
|
+
module = match.group(3).strip()
|
|
398
|
+
import_times.append((module, cumulative / 1000000)) # Convert to seconds
|
|
399
|
+
|
|
400
|
+
# Sort by time descending
|
|
401
|
+
import_times.sort(key=lambda x: x[1], reverse=True)
|
|
402
|
+
return import_times[:20] # Top 20
|
|
403
|
+
|
|
404
|
+
except Exception:
|
|
405
|
+
return []
|
|
406
|
+
|
|
407
|
+
def save_artifacts(self, result: ProfilerResult, base_path: str):
|
|
408
|
+
"""Save profiling artifacts to files."""
|
|
409
|
+
base = Path(base_path)
|
|
410
|
+
base.parent.mkdir(parents=True, exist_ok=True)
|
|
411
|
+
|
|
412
|
+
# Save .prof binary
|
|
413
|
+
prof_path = str(base) + ".prof"
|
|
414
|
+
self.profiler.dump_stats(prof_path)
|
|
415
|
+
|
|
416
|
+
# Save .txt report
|
|
417
|
+
txt_path = str(base) + ".txt"
|
|
418
|
+
with open(txt_path, 'w') as f:
|
|
419
|
+
f.write(format_profile_report(result, self.config))
|
|
420
|
+
|
|
421
|
+
# Save .json if requested
|
|
422
|
+
if self.config.output_format == "json":
|
|
423
|
+
json_path = str(base) + ".json"
|
|
424
|
+
with open(json_path, 'w') as f:
|
|
425
|
+
json.dump(result.to_dict(), f, indent=2, default=str)
|
|
426
|
+
|
|
427
|
+
return prof_path, txt_path
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def run_profiled_query(
|
|
431
|
+
prompt: str,
|
|
432
|
+
config: Optional[ProfilerConfig] = None,
|
|
433
|
+
model: Optional[str] = None,
|
|
434
|
+
) -> ProfilerResult:
|
|
435
|
+
"""
|
|
436
|
+
Run a profiled query.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
prompt: The prompt to execute
|
|
440
|
+
config: Profiler configuration
|
|
441
|
+
model: Optional model to use
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
ProfilerResult with detailed timing and statistics
|
|
445
|
+
"""
|
|
446
|
+
profiler = QueryProfiler(config)
|
|
447
|
+
return profiler.profile_query(prompt, model=model, stream=config.stream if config else False)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def format_profile_report(result: ProfilerResult, config: Optional[ProfilerConfig] = None) -> str:
|
|
451
|
+
"""
|
|
452
|
+
Format a profile result as a text report.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
result: ProfilerResult to format
|
|
456
|
+
config: Optional profiler config
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
Formatted text report
|
|
460
|
+
"""
|
|
461
|
+
config = config or ProfilerConfig()
|
|
462
|
+
lines = []
|
|
463
|
+
|
|
464
|
+
# Header
|
|
465
|
+
lines.append("=" * 70)
|
|
466
|
+
lines.append("PraisonAI Profile Report")
|
|
467
|
+
lines.append("=" * 70)
|
|
468
|
+
lines.append("")
|
|
469
|
+
|
|
470
|
+
# Metadata
|
|
471
|
+
lines.append("## System Information")
|
|
472
|
+
lines.append(f" Timestamp: {result.timestamp}")
|
|
473
|
+
lines.append(f" Python Version: {result.metadata.get('python_version', 'N/A')}")
|
|
474
|
+
lines.append(f" Platform: {result.metadata.get('platform', 'N/A')}")
|
|
475
|
+
lines.append(f" PraisonAI: {result.metadata.get('praisonai_version', 'N/A')}")
|
|
476
|
+
lines.append(f" Model: {result.metadata.get('model', 'N/A')}")
|
|
477
|
+
lines.append("")
|
|
478
|
+
|
|
479
|
+
# Timing breakdown
|
|
480
|
+
lines.append("## Timing Breakdown")
|
|
481
|
+
lines.append(f" CLI Parse: {result.timing.cli_parse_ms:>10.2f} ms")
|
|
482
|
+
lines.append(f" Imports: {result.timing.imports_ms:>10.2f} ms")
|
|
483
|
+
lines.append(f" Agent Construct: {result.timing.agent_construction_ms:>10.2f} ms")
|
|
484
|
+
lines.append(f" Model Init: {result.timing.model_init_ms:>10.2f} ms")
|
|
485
|
+
if result.timing.first_token_ms > 0:
|
|
486
|
+
lines.append(f" First Token: {result.timing.first_token_ms:>10.2f} ms")
|
|
487
|
+
lines.append(f" Total Run: {result.timing.total_run_ms:>10.2f} ms")
|
|
488
|
+
lines.append("")
|
|
489
|
+
|
|
490
|
+
# Per-file timing (if requested)
|
|
491
|
+
if config.show_files and result.file_stats:
|
|
492
|
+
lines.append("## Per-File Timing (Top Files)")
|
|
493
|
+
lines.append("-" * 70)
|
|
494
|
+
lines.append(f"{'File':<50} {'Time (ms)':>15}")
|
|
495
|
+
lines.append("-" * 70)
|
|
496
|
+
for fs in result.file_stats[:15]:
|
|
497
|
+
filename = os.path.basename(fs.filename)
|
|
498
|
+
if len(filename) > 48:
|
|
499
|
+
filename = "..." + filename[-45:]
|
|
500
|
+
lines.append(f"{filename:<50} {fs.total_time * 1000:>15.2f}")
|
|
501
|
+
lines.append("")
|
|
502
|
+
|
|
503
|
+
# Per-function timing
|
|
504
|
+
lines.append("## Per-Function Timing (Top Functions)")
|
|
505
|
+
lines.append("-" * 70)
|
|
506
|
+
sort_label = "Cumulative" if config.sort_by == "cumulative" else "Total"
|
|
507
|
+
lines.append(f"{'Function':<35} {'Calls':>8} {sort_label + ' (ms)':>12} {'Self (ms)':>12}")
|
|
508
|
+
lines.append("-" * 70)
|
|
509
|
+
for fs in result.function_stats[:config.limit]:
|
|
510
|
+
funcname = fs.name
|
|
511
|
+
if len(funcname) > 33:
|
|
512
|
+
funcname = funcname[:30] + "..."
|
|
513
|
+
lines.append(f"{funcname:<35} {fs.calls:>8} {fs.cumtime * 1000:>12.2f} {fs.tottime * 1000:>12.2f}")
|
|
514
|
+
lines.append("")
|
|
515
|
+
|
|
516
|
+
# Callers (if requested)
|
|
517
|
+
if config.show_callers and result.callers:
|
|
518
|
+
lines.append("## Callers (Who called each function)")
|
|
519
|
+
lines.append("-" * 70)
|
|
520
|
+
for func, caller_list in list(result.callers.items())[:10]:
|
|
521
|
+
lines.append(f" {func}:")
|
|
522
|
+
for caller in caller_list[:5]:
|
|
523
|
+
lines.append(f" <- {caller}")
|
|
524
|
+
lines.append("")
|
|
525
|
+
|
|
526
|
+
# Callees (if requested)
|
|
527
|
+
if config.show_callees and result.callees:
|
|
528
|
+
lines.append("## Callees (What each function called)")
|
|
529
|
+
lines.append("-" * 70)
|
|
530
|
+
for func, callee_list in list(result.callees.items())[:10]:
|
|
531
|
+
lines.append(f" {func}:")
|
|
532
|
+
for callee in callee_list[:5]:
|
|
533
|
+
lines.append(f" -> {callee}")
|
|
534
|
+
lines.append("")
|
|
535
|
+
|
|
536
|
+
# Import times (if requested)
|
|
537
|
+
if config.importtime and result.import_times:
|
|
538
|
+
lines.append("## Import Times (Top Modules)")
|
|
539
|
+
lines.append("-" * 70)
|
|
540
|
+
lines.append(f"{'Module':<50} {'Time (ms)':>15}")
|
|
541
|
+
lines.append("-" * 70)
|
|
542
|
+
for module, time_sec in result.import_times[:15]:
|
|
543
|
+
module_name = module
|
|
544
|
+
if len(module_name) > 48:
|
|
545
|
+
module_name = "..." + module_name[-45:]
|
|
546
|
+
lines.append(f"{module_name:<50} {time_sec * 1000:>15.2f}")
|
|
547
|
+
lines.append("")
|
|
548
|
+
|
|
549
|
+
# Response preview
|
|
550
|
+
lines.append("## Response Preview")
|
|
551
|
+
lines.append("-" * 70)
|
|
552
|
+
preview = result.response[:500] + "..." if len(result.response) > 500 else result.response
|
|
553
|
+
lines.append(preview)
|
|
554
|
+
lines.append("")
|
|
555
|
+
|
|
556
|
+
lines.append("=" * 70)
|
|
557
|
+
|
|
558
|
+
return "\n".join(lines)
|