aiecs 1.0.1__py3-none-any.whl → 1.7.6__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.
Potentially problematic release.
This version of aiecs might be problematic. Click here for more details.
- aiecs/__init__.py +13 -16
- aiecs/__main__.py +7 -7
- aiecs/aiecs_client.py +269 -75
- aiecs/application/executors/operation_executor.py +79 -54
- aiecs/application/knowledge_graph/__init__.py +7 -0
- aiecs/application/knowledge_graph/builder/__init__.py +37 -0
- aiecs/application/knowledge_graph/builder/data_quality.py +302 -0
- aiecs/application/knowledge_graph/builder/data_reshaping.py +293 -0
- aiecs/application/knowledge_graph/builder/document_builder.py +369 -0
- aiecs/application/knowledge_graph/builder/graph_builder.py +490 -0
- aiecs/application/knowledge_graph/builder/import_optimizer.py +396 -0
- aiecs/application/knowledge_graph/builder/schema_inference.py +462 -0
- aiecs/application/knowledge_graph/builder/schema_mapping.py +563 -0
- aiecs/application/knowledge_graph/builder/structured_pipeline.py +1384 -0
- aiecs/application/knowledge_graph/builder/text_chunker.py +317 -0
- aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
- aiecs/application/knowledge_graph/extractors/base.py +98 -0
- aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +422 -0
- aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +347 -0
- aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +241 -0
- aiecs/application/knowledge_graph/fusion/__init__.py +78 -0
- aiecs/application/knowledge_graph/fusion/ab_testing.py +395 -0
- aiecs/application/knowledge_graph/fusion/abbreviation_expander.py +327 -0
- aiecs/application/knowledge_graph/fusion/alias_index.py +597 -0
- aiecs/application/knowledge_graph/fusion/alias_matcher.py +384 -0
- aiecs/application/knowledge_graph/fusion/cache_coordinator.py +343 -0
- aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +433 -0
- aiecs/application/knowledge_graph/fusion/entity_linker.py +511 -0
- aiecs/application/knowledge_graph/fusion/evaluation_dataset.py +240 -0
- aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +632 -0
- aiecs/application/knowledge_graph/fusion/matching_config.py +489 -0
- aiecs/application/knowledge_graph/fusion/name_normalizer.py +352 -0
- aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +183 -0
- aiecs/application/knowledge_graph/fusion/semantic_name_matcher.py +464 -0
- aiecs/application/knowledge_graph/fusion/similarity_pipeline.py +534 -0
- aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
- aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +342 -0
- aiecs/application/knowledge_graph/pattern_matching/query_executor.py +366 -0
- aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
- aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +195 -0
- aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
- aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
- aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +341 -0
- aiecs/application/knowledge_graph/reasoning/inference_engine.py +500 -0
- aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +163 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +913 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +866 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +475 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +396 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +208 -0
- aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +170 -0
- aiecs/application/knowledge_graph/reasoning/query_planner.py +855 -0
- aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +518 -0
- aiecs/application/knowledge_graph/retrieval/__init__.py +27 -0
- aiecs/application/knowledge_graph/retrieval/query_intent_classifier.py +211 -0
- aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +592 -0
- aiecs/application/knowledge_graph/retrieval/strategy_types.py +23 -0
- aiecs/application/knowledge_graph/search/__init__.py +59 -0
- aiecs/application/knowledge_graph/search/hybrid_search.py +457 -0
- aiecs/application/knowledge_graph/search/reranker.py +293 -0
- aiecs/application/knowledge_graph/search/reranker_strategies.py +535 -0
- aiecs/application/knowledge_graph/search/text_similarity.py +392 -0
- aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
- aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +305 -0
- aiecs/application/knowledge_graph/traversal/path_scorer.py +271 -0
- aiecs/application/knowledge_graph/validators/__init__.py +13 -0
- aiecs/application/knowledge_graph/validators/relation_validator.py +239 -0
- aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
- aiecs/application/knowledge_graph/visualization/graph_visualizer.py +313 -0
- aiecs/common/__init__.py +9 -0
- aiecs/common/knowledge_graph/__init__.py +17 -0
- aiecs/common/knowledge_graph/runnable.py +471 -0
- aiecs/config/__init__.py +20 -5
- aiecs/config/config.py +762 -31
- aiecs/config/graph_config.py +131 -0
- aiecs/config/tool_config.py +399 -0
- aiecs/core/__init__.py +29 -13
- aiecs/core/interface/__init__.py +2 -2
- aiecs/core/interface/execution_interface.py +22 -22
- aiecs/core/interface/storage_interface.py +37 -88
- aiecs/core/registry/__init__.py +31 -0
- aiecs/core/registry/service_registry.py +92 -0
- aiecs/domain/__init__.py +270 -1
- aiecs/domain/agent/__init__.py +191 -0
- aiecs/domain/agent/base_agent.py +3870 -0
- aiecs/domain/agent/exceptions.py +99 -0
- aiecs/domain/agent/graph_aware_mixin.py +569 -0
- aiecs/domain/agent/hybrid_agent.py +1435 -0
- aiecs/domain/agent/integration/__init__.py +29 -0
- aiecs/domain/agent/integration/context_compressor.py +216 -0
- aiecs/domain/agent/integration/context_engine_adapter.py +587 -0
- aiecs/domain/agent/integration/protocols.py +281 -0
- aiecs/domain/agent/integration/retry_policy.py +218 -0
- aiecs/domain/agent/integration/role_config.py +213 -0
- aiecs/domain/agent/knowledge_aware_agent.py +1892 -0
- aiecs/domain/agent/lifecycle.py +291 -0
- aiecs/domain/agent/llm_agent.py +692 -0
- aiecs/domain/agent/memory/__init__.py +12 -0
- aiecs/domain/agent/memory/conversation.py +1124 -0
- aiecs/domain/agent/migration/__init__.py +14 -0
- aiecs/domain/agent/migration/conversion.py +163 -0
- aiecs/domain/agent/migration/legacy_wrapper.py +86 -0
- aiecs/domain/agent/models.py +884 -0
- aiecs/domain/agent/observability.py +479 -0
- aiecs/domain/agent/persistence.py +449 -0
- aiecs/domain/agent/prompts/__init__.py +29 -0
- aiecs/domain/agent/prompts/builder.py +159 -0
- aiecs/domain/agent/prompts/formatters.py +187 -0
- aiecs/domain/agent/prompts/template.py +255 -0
- aiecs/domain/agent/registry.py +253 -0
- aiecs/domain/agent/tool_agent.py +444 -0
- aiecs/domain/agent/tools/__init__.py +15 -0
- aiecs/domain/agent/tools/schema_generator.py +364 -0
- aiecs/domain/community/__init__.py +155 -0
- aiecs/domain/community/agent_adapter.py +469 -0
- aiecs/domain/community/analytics.py +432 -0
- aiecs/domain/community/collaborative_workflow.py +648 -0
- aiecs/domain/community/communication_hub.py +634 -0
- aiecs/domain/community/community_builder.py +320 -0
- aiecs/domain/community/community_integration.py +796 -0
- aiecs/domain/community/community_manager.py +803 -0
- aiecs/domain/community/decision_engine.py +849 -0
- aiecs/domain/community/exceptions.py +231 -0
- aiecs/domain/community/models/__init__.py +33 -0
- aiecs/domain/community/models/community_models.py +234 -0
- aiecs/domain/community/resource_manager.py +461 -0
- aiecs/domain/community/shared_context_manager.py +589 -0
- aiecs/domain/context/__init__.py +40 -10
- aiecs/domain/context/context_engine.py +1910 -0
- aiecs/domain/context/conversation_models.py +87 -53
- aiecs/domain/context/graph_memory.py +582 -0
- aiecs/domain/execution/model.py +12 -4
- aiecs/domain/knowledge_graph/__init__.py +19 -0
- aiecs/domain/knowledge_graph/models/__init__.py +52 -0
- aiecs/domain/knowledge_graph/models/entity.py +148 -0
- aiecs/domain/knowledge_graph/models/evidence.py +178 -0
- aiecs/domain/knowledge_graph/models/inference_rule.py +184 -0
- aiecs/domain/knowledge_graph/models/path.py +171 -0
- aiecs/domain/knowledge_graph/models/path_pattern.py +171 -0
- aiecs/domain/knowledge_graph/models/query.py +261 -0
- aiecs/domain/knowledge_graph/models/query_plan.py +181 -0
- aiecs/domain/knowledge_graph/models/relation.py +202 -0
- aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
- aiecs/domain/knowledge_graph/schema/entity_type.py +131 -0
- aiecs/domain/knowledge_graph/schema/graph_schema.py +253 -0
- aiecs/domain/knowledge_graph/schema/property_schema.py +143 -0
- aiecs/domain/knowledge_graph/schema/relation_type.py +163 -0
- aiecs/domain/knowledge_graph/schema/schema_manager.py +691 -0
- aiecs/domain/knowledge_graph/schema/type_enums.py +209 -0
- aiecs/domain/task/dsl_processor.py +172 -56
- aiecs/domain/task/model.py +20 -8
- aiecs/domain/task/task_context.py +27 -24
- aiecs/infrastructure/__init__.py +0 -2
- aiecs/infrastructure/graph_storage/__init__.py +11 -0
- aiecs/infrastructure/graph_storage/base.py +837 -0
- aiecs/infrastructure/graph_storage/batch_operations.py +458 -0
- aiecs/infrastructure/graph_storage/cache.py +424 -0
- aiecs/infrastructure/graph_storage/distributed.py +223 -0
- aiecs/infrastructure/graph_storage/error_handling.py +380 -0
- aiecs/infrastructure/graph_storage/graceful_degradation.py +294 -0
- aiecs/infrastructure/graph_storage/health_checks.py +378 -0
- aiecs/infrastructure/graph_storage/in_memory.py +1197 -0
- aiecs/infrastructure/graph_storage/index_optimization.py +446 -0
- aiecs/infrastructure/graph_storage/lazy_loading.py +431 -0
- aiecs/infrastructure/graph_storage/metrics.py +344 -0
- aiecs/infrastructure/graph_storage/migration.py +400 -0
- aiecs/infrastructure/graph_storage/pagination.py +483 -0
- aiecs/infrastructure/graph_storage/performance_monitoring.py +456 -0
- aiecs/infrastructure/graph_storage/postgres.py +1563 -0
- aiecs/infrastructure/graph_storage/property_storage.py +353 -0
- aiecs/infrastructure/graph_storage/protocols.py +76 -0
- aiecs/infrastructure/graph_storage/query_optimizer.py +642 -0
- aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
- aiecs/infrastructure/graph_storage/sqlite.py +1373 -0
- aiecs/infrastructure/graph_storage/streaming.py +487 -0
- aiecs/infrastructure/graph_storage/tenant.py +412 -0
- aiecs/infrastructure/messaging/celery_task_manager.py +92 -54
- aiecs/infrastructure/messaging/websocket_manager.py +51 -35
- aiecs/infrastructure/monitoring/__init__.py +22 -0
- aiecs/infrastructure/monitoring/executor_metrics.py +45 -11
- aiecs/infrastructure/monitoring/global_metrics_manager.py +212 -0
- aiecs/infrastructure/monitoring/structured_logger.py +3 -7
- aiecs/infrastructure/monitoring/tracing_manager.py +63 -35
- aiecs/infrastructure/persistence/__init__.py +14 -1
- aiecs/infrastructure/persistence/context_engine_client.py +184 -0
- aiecs/infrastructure/persistence/database_manager.py +67 -43
- aiecs/infrastructure/persistence/file_storage.py +180 -103
- aiecs/infrastructure/persistence/redis_client.py +74 -21
- aiecs/llm/__init__.py +73 -25
- aiecs/llm/callbacks/__init__.py +11 -0
- aiecs/llm/{custom_callbacks.py → callbacks/custom_callbacks.py} +26 -19
- aiecs/llm/client_factory.py +224 -36
- aiecs/llm/client_resolver.py +155 -0
- aiecs/llm/clients/__init__.py +38 -0
- aiecs/llm/clients/base_client.py +324 -0
- aiecs/llm/clients/google_function_calling_mixin.py +457 -0
- aiecs/llm/clients/googleai_client.py +241 -0
- aiecs/llm/clients/openai_client.py +158 -0
- aiecs/llm/clients/openai_compatible_mixin.py +367 -0
- aiecs/llm/clients/vertex_client.py +897 -0
- aiecs/llm/clients/xai_client.py +201 -0
- aiecs/llm/config/__init__.py +51 -0
- aiecs/llm/config/config_loader.py +272 -0
- aiecs/llm/config/config_validator.py +206 -0
- aiecs/llm/config/model_config.py +143 -0
- aiecs/llm/protocols.py +149 -0
- aiecs/llm/utils/__init__.py +10 -0
- aiecs/llm/utils/validate_config.py +89 -0
- aiecs/main.py +140 -121
- aiecs/scripts/aid/VERSION_MANAGEMENT.md +138 -0
- aiecs/scripts/aid/__init__.py +19 -0
- aiecs/scripts/aid/module_checker.py +499 -0
- aiecs/scripts/aid/version_manager.py +235 -0
- aiecs/scripts/{DEPENDENCY_SYSTEM_SUMMARY.md → dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md} +1 -0
- aiecs/scripts/{README_DEPENDENCY_CHECKER.md → dependance_check/README_DEPENDENCY_CHECKER.md} +1 -0
- aiecs/scripts/dependance_check/__init__.py +15 -0
- aiecs/scripts/dependance_check/dependency_checker.py +1835 -0
- aiecs/scripts/{dependency_fixer.py → dependance_check/dependency_fixer.py} +192 -90
- aiecs/scripts/{download_nlp_data.py → dependance_check/download_nlp_data.py} +203 -71
- aiecs/scripts/dependance_patch/__init__.py +7 -0
- aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
- aiecs/scripts/{fix_weasel_validator.py → dependance_patch/fix_weasel/fix_weasel_validator.py} +21 -14
- aiecs/scripts/{patch_weasel_library.sh → dependance_patch/fix_weasel/patch_weasel_library.sh} +1 -1
- aiecs/scripts/knowledge_graph/__init__.py +3 -0
- aiecs/scripts/knowledge_graph/run_threshold_experiments.py +212 -0
- aiecs/scripts/migrations/multi_tenancy/README.md +142 -0
- aiecs/scripts/tools_develop/README.md +671 -0
- aiecs/scripts/tools_develop/README_CONFIG_CHECKER.md +273 -0
- aiecs/scripts/tools_develop/TOOLS_CONFIG_GUIDE.md +1287 -0
- aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
- aiecs/scripts/tools_develop/__init__.py +21 -0
- aiecs/scripts/tools_develop/check_all_tools_config.py +548 -0
- aiecs/scripts/tools_develop/check_type_annotations.py +257 -0
- aiecs/scripts/tools_develop/pre-commit-schema-coverage.sh +66 -0
- aiecs/scripts/tools_develop/schema_coverage.py +511 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +475 -0
- aiecs/scripts/tools_develop/verify_executor_config_fix.py +98 -0
- aiecs/scripts/tools_develop/verify_tools.py +352 -0
- aiecs/tasks/__init__.py +0 -1
- aiecs/tasks/worker.py +115 -47
- aiecs/tools/__init__.py +194 -72
- aiecs/tools/apisource/__init__.py +99 -0
- aiecs/tools/apisource/intelligence/__init__.py +19 -0
- aiecs/tools/apisource/intelligence/data_fusion.py +632 -0
- aiecs/tools/apisource/intelligence/query_analyzer.py +417 -0
- aiecs/tools/apisource/intelligence/search_enhancer.py +385 -0
- aiecs/tools/apisource/monitoring/__init__.py +9 -0
- aiecs/tools/apisource/monitoring/metrics.py +330 -0
- aiecs/tools/apisource/providers/__init__.py +112 -0
- aiecs/tools/apisource/providers/base.py +671 -0
- aiecs/tools/apisource/providers/census.py +397 -0
- aiecs/tools/apisource/providers/fred.py +535 -0
- aiecs/tools/apisource/providers/newsapi.py +409 -0
- aiecs/tools/apisource/providers/worldbank.py +352 -0
- aiecs/tools/apisource/reliability/__init__.py +12 -0
- aiecs/tools/apisource/reliability/error_handler.py +363 -0
- aiecs/tools/apisource/reliability/fallback_strategy.py +376 -0
- aiecs/tools/apisource/tool.py +832 -0
- aiecs/tools/apisource/utils/__init__.py +9 -0
- aiecs/tools/apisource/utils/validators.py +334 -0
- aiecs/tools/base_tool.py +415 -21
- aiecs/tools/docs/__init__.py +121 -0
- aiecs/tools/docs/ai_document_orchestrator.py +607 -0
- aiecs/tools/docs/ai_document_writer_orchestrator.py +2350 -0
- aiecs/tools/docs/content_insertion_tool.py +1320 -0
- aiecs/tools/docs/document_creator_tool.py +1323 -0
- aiecs/tools/docs/document_layout_tool.py +1160 -0
- aiecs/tools/docs/document_parser_tool.py +1011 -0
- aiecs/tools/docs/document_writer_tool.py +1829 -0
- aiecs/tools/knowledge_graph/__init__.py +17 -0
- aiecs/tools/knowledge_graph/graph_reasoning_tool.py +807 -0
- aiecs/tools/knowledge_graph/graph_search_tool.py +944 -0
- aiecs/tools/knowledge_graph/kg_builder_tool.py +524 -0
- aiecs/tools/langchain_adapter.py +300 -138
- aiecs/tools/schema_generator.py +455 -0
- aiecs/tools/search_tool/__init__.py +100 -0
- aiecs/tools/search_tool/analyzers.py +581 -0
- aiecs/tools/search_tool/cache.py +264 -0
- aiecs/tools/search_tool/constants.py +128 -0
- aiecs/tools/search_tool/context.py +224 -0
- aiecs/tools/search_tool/core.py +778 -0
- aiecs/tools/search_tool/deduplicator.py +119 -0
- aiecs/tools/search_tool/error_handler.py +242 -0
- aiecs/tools/search_tool/metrics.py +343 -0
- aiecs/tools/search_tool/rate_limiter.py +172 -0
- aiecs/tools/search_tool/schemas.py +275 -0
- aiecs/tools/statistics/__init__.py +80 -0
- aiecs/tools/statistics/ai_data_analysis_orchestrator.py +646 -0
- aiecs/tools/statistics/ai_insight_generator_tool.py +508 -0
- aiecs/tools/statistics/ai_report_orchestrator_tool.py +684 -0
- aiecs/tools/statistics/data_loader_tool.py +555 -0
- aiecs/tools/statistics/data_profiler_tool.py +638 -0
- aiecs/tools/statistics/data_transformer_tool.py +580 -0
- aiecs/tools/statistics/data_visualizer_tool.py +498 -0
- aiecs/tools/statistics/model_trainer_tool.py +507 -0
- aiecs/tools/statistics/statistical_analyzer_tool.py +472 -0
- aiecs/tools/task_tools/__init__.py +49 -36
- aiecs/tools/task_tools/chart_tool.py +200 -184
- aiecs/tools/task_tools/classfire_tool.py +268 -267
- aiecs/tools/task_tools/image_tool.py +175 -131
- aiecs/tools/task_tools/office_tool.py +226 -146
- aiecs/tools/task_tools/pandas_tool.py +477 -121
- aiecs/tools/task_tools/report_tool.py +390 -142
- aiecs/tools/task_tools/research_tool.py +149 -79
- aiecs/tools/task_tools/scraper_tool.py +339 -145
- aiecs/tools/task_tools/stats_tool.py +448 -209
- aiecs/tools/temp_file_manager.py +26 -24
- aiecs/tools/tool_executor/__init__.py +18 -16
- aiecs/tools/tool_executor/tool_executor.py +364 -52
- aiecs/utils/LLM_output_structor.py +74 -48
- aiecs/utils/__init__.py +14 -3
- aiecs/utils/base_callback.py +0 -3
- aiecs/utils/cache_provider.py +696 -0
- aiecs/utils/execution_utils.py +50 -31
- aiecs/utils/prompt_loader.py +1 -0
- aiecs/utils/token_usage_repository.py +37 -11
- aiecs/ws/socket_server.py +14 -4
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/METADATA +52 -15
- aiecs-1.7.6.dist-info/RECORD +337 -0
- aiecs-1.7.6.dist-info/entry_points.txt +13 -0
- aiecs/config/registry.py +0 -19
- aiecs/domain/context/content_engine.py +0 -982
- aiecs/llm/base_client.py +0 -99
- aiecs/llm/openai_client.py +0 -125
- aiecs/llm/vertex_client.py +0 -186
- aiecs/llm/xai_client.py +0 -184
- aiecs/scripts/dependency_checker.py +0 -857
- aiecs/scripts/quick_dependency_check.py +0 -269
- aiecs/tools/task_tools/search_api.py +0 -7
- aiecs-1.0.1.dist-info/RECORD +0 -90
- aiecs-1.0.1.dist-info/entry_points.txt +0 -7
- /aiecs/scripts/{setup_nlp_data.sh → dependance_check/setup_nlp_data.sh} +0 -0
- /aiecs/scripts/{README_WEASEL_PATCH.md → dependance_patch/fix_weasel/README_WEASEL_PATCH.md} +0 -0
- /aiecs/scripts/{fix_weasel_validator.sh → dependance_patch/fix_weasel/fix_weasel_validator.sh} +0 -0
- /aiecs/scripts/{run_weasel_patch.sh → dependance_patch/fix_weasel/run_weasel_patch.sh} +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/WHEEL +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/licenses/LICENSE +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Performance Monitoring for Graph Storage
|
|
3
|
+
|
|
4
|
+
Provides query performance monitoring, query plan analysis, and optimization suggestions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import time
|
|
8
|
+
import logging
|
|
9
|
+
import asyncio
|
|
10
|
+
import asyncpg # type: ignore[import-untyped]
|
|
11
|
+
from typing import Dict, List, Optional, Any
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from collections import defaultdict
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class QueryStats:
|
|
20
|
+
"""Statistics for a single query"""
|
|
21
|
+
|
|
22
|
+
query_type: str
|
|
23
|
+
query_text: str
|
|
24
|
+
execution_count: int = 0
|
|
25
|
+
total_time_ms: float = 0.0
|
|
26
|
+
min_time_ms: float = float("inf")
|
|
27
|
+
max_time_ms: float = 0.0
|
|
28
|
+
avg_time_ms: float = 0.0
|
|
29
|
+
execution_times: List[float] = field(default_factory=list)
|
|
30
|
+
|
|
31
|
+
def add_execution(self, duration_ms: float) -> None:
|
|
32
|
+
"""Add a query execution time"""
|
|
33
|
+
self.execution_count += 1
|
|
34
|
+
self.total_time_ms += duration_ms
|
|
35
|
+
self.min_time_ms = min(self.min_time_ms, duration_ms)
|
|
36
|
+
self.max_time_ms = max(self.max_time_ms, duration_ms)
|
|
37
|
+
self.execution_times.append(duration_ms)
|
|
38
|
+
|
|
39
|
+
# Keep only last 100 executions for percentile calculations
|
|
40
|
+
if len(self.execution_times) > 100:
|
|
41
|
+
self.execution_times.pop(0)
|
|
42
|
+
|
|
43
|
+
self.avg_time_ms = self.total_time_ms / self.execution_count
|
|
44
|
+
|
|
45
|
+
def get_percentile(self, percentile: float) -> float:
|
|
46
|
+
"""Get percentile execution time"""
|
|
47
|
+
if not self.execution_times:
|
|
48
|
+
return 0.0
|
|
49
|
+
sorted_times = sorted(self.execution_times)
|
|
50
|
+
index = int(len(sorted_times) * percentile / 100)
|
|
51
|
+
return sorted_times[min(index, len(sorted_times) - 1)]
|
|
52
|
+
|
|
53
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
54
|
+
"""Convert to dictionary"""
|
|
55
|
+
return {
|
|
56
|
+
"query_type": self.query_type,
|
|
57
|
+
"query_text": (self.query_text[:100] + "..." if len(self.query_text) > 100 else self.query_text),
|
|
58
|
+
"execution_count": self.execution_count,
|
|
59
|
+
"total_time_ms": round(self.total_time_ms, 2),
|
|
60
|
+
"avg_time_ms": round(self.avg_time_ms, 2),
|
|
61
|
+
"min_time_ms": round(self.min_time_ms, 2),
|
|
62
|
+
"max_time_ms": round(self.max_time_ms, 2),
|
|
63
|
+
"p50_ms": round(self.get_percentile(50), 2),
|
|
64
|
+
"p95_ms": round(self.get_percentile(95), 2),
|
|
65
|
+
"p99_ms": round(self.get_percentile(99), 2),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class QueryPlan:
|
|
71
|
+
"""PostgreSQL query execution plan"""
|
|
72
|
+
|
|
73
|
+
query: str
|
|
74
|
+
plan: Dict[str, Any]
|
|
75
|
+
total_cost: float
|
|
76
|
+
execution_time_ms: Optional[float] = None
|
|
77
|
+
|
|
78
|
+
def get_warnings(self) -> List[str]:
|
|
79
|
+
"""Get performance warnings from query plan"""
|
|
80
|
+
warnings = []
|
|
81
|
+
|
|
82
|
+
# Check for sequential scans
|
|
83
|
+
if self._has_sequential_scan(self.plan):
|
|
84
|
+
warnings.append("Sequential scan detected - consider adding index")
|
|
85
|
+
|
|
86
|
+
# Check for nested loops with large datasets
|
|
87
|
+
if self._has_inefficient_nested_loop(self.plan):
|
|
88
|
+
warnings.append("Inefficient nested loop - consider optimizing join")
|
|
89
|
+
|
|
90
|
+
# Check for high cost
|
|
91
|
+
if self.total_cost > 10000:
|
|
92
|
+
warnings.append(f"High query cost ({self.total_cost:.0f}) - consider optimization")
|
|
93
|
+
|
|
94
|
+
return warnings
|
|
95
|
+
|
|
96
|
+
def _has_sequential_scan(self, node: Dict[str, Any]) -> bool:
|
|
97
|
+
"""Check if plan has sequential scan"""
|
|
98
|
+
if node.get("Node Type") == "Seq Scan":
|
|
99
|
+
return True
|
|
100
|
+
for child in node.get("Plans", []):
|
|
101
|
+
if self._has_sequential_scan(child):
|
|
102
|
+
return True
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
def _has_inefficient_nested_loop(self, node: Dict[str, Any]) -> bool:
|
|
106
|
+
"""Check if plan has inefficient nested loop"""
|
|
107
|
+
if node.get("Node Type") == "Nested Loop":
|
|
108
|
+
# Check if estimated rows is large
|
|
109
|
+
if node.get("Plan Rows", 0) > 1000:
|
|
110
|
+
return True
|
|
111
|
+
for child in node.get("Plans", []):
|
|
112
|
+
if self._has_inefficient_nested_loop(child):
|
|
113
|
+
return True
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class PerformanceMonitor:
|
|
118
|
+
"""
|
|
119
|
+
Monitor query performance and provide optimization suggestions
|
|
120
|
+
|
|
121
|
+
Tracks query execution times, analyzes query plans, and provides
|
|
122
|
+
recommendations for improving query performance.
|
|
123
|
+
|
|
124
|
+
Example:
|
|
125
|
+
```python
|
|
126
|
+
monitor = PerformanceMonitor()
|
|
127
|
+
|
|
128
|
+
# Track query execution
|
|
129
|
+
async with monitor.track_query("get_entity", "SELECT * FROM entities WHERE id = $1"):
|
|
130
|
+
result = await conn.fetch(query, entity_id)
|
|
131
|
+
|
|
132
|
+
# Get performance report
|
|
133
|
+
report = monitor.get_performance_report()
|
|
134
|
+
print(report["slow_queries"])
|
|
135
|
+
```
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def __init__(
|
|
139
|
+
self,
|
|
140
|
+
enabled: bool = True,
|
|
141
|
+
slow_query_threshold_ms: float = 100.0,
|
|
142
|
+
log_slow_queries: bool = True,
|
|
143
|
+
):
|
|
144
|
+
"""
|
|
145
|
+
Initialize performance monitor
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
enabled: Enable/disable monitoring
|
|
149
|
+
slow_query_threshold_ms: Threshold for slow query logging (ms)
|
|
150
|
+
log_slow_queries: Log slow queries to logger
|
|
151
|
+
"""
|
|
152
|
+
self.enabled = enabled
|
|
153
|
+
self.slow_query_threshold_ms = slow_query_threshold_ms
|
|
154
|
+
self.log_slow_queries = log_slow_queries
|
|
155
|
+
|
|
156
|
+
self.query_stats: Dict[str, QueryStats] = {}
|
|
157
|
+
self.slow_queries: List[Dict[str, Any]] = []
|
|
158
|
+
self._lock: Optional[asyncio.Lock] = None
|
|
159
|
+
|
|
160
|
+
async def initialize(self) -> None:
|
|
161
|
+
"""Initialize monitor (create locks, etc.)"""
|
|
162
|
+
self._lock = asyncio.Lock()
|
|
163
|
+
|
|
164
|
+
def track_query(self, query_type: str, query_text: str):
|
|
165
|
+
"""
|
|
166
|
+
Context manager for tracking query execution
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
query_type: Type of query (e.g., "get_entity", "find_paths")
|
|
170
|
+
query_text: SQL query text
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Context manager
|
|
174
|
+
|
|
175
|
+
Example:
|
|
176
|
+
```python
|
|
177
|
+
async with monitor.track_query("get_entity", query):
|
|
178
|
+
result = await conn.fetch(query)
|
|
179
|
+
```
|
|
180
|
+
"""
|
|
181
|
+
return QueryTracker(self, query_type, query_text)
|
|
182
|
+
|
|
183
|
+
async def record_query(
|
|
184
|
+
self,
|
|
185
|
+
query_type: str,
|
|
186
|
+
query_text: str,
|
|
187
|
+
duration_ms: float,
|
|
188
|
+
row_count: Optional[int] = None,
|
|
189
|
+
) -> None:
|
|
190
|
+
"""
|
|
191
|
+
Record a query execution
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
query_type: Type of query
|
|
195
|
+
query_text: SQL query text
|
|
196
|
+
duration_ms: Execution time in milliseconds
|
|
197
|
+
row_count: Number of rows returned/affected
|
|
198
|
+
"""
|
|
199
|
+
if not self.enabled:
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
# Update stats
|
|
203
|
+
key = f"{query_type}:{query_text[:50]}"
|
|
204
|
+
if key not in self.query_stats:
|
|
205
|
+
self.query_stats[key] = QueryStats(query_type, query_text)
|
|
206
|
+
|
|
207
|
+
self.query_stats[key].add_execution(duration_ms)
|
|
208
|
+
|
|
209
|
+
# Check if slow query
|
|
210
|
+
if duration_ms >= self.slow_query_threshold_ms:
|
|
211
|
+
slow_query = {
|
|
212
|
+
"query_type": query_type,
|
|
213
|
+
"query_text": query_text[:200],
|
|
214
|
+
"duration_ms": duration_ms,
|
|
215
|
+
"row_count": row_count,
|
|
216
|
+
"timestamp": time.time(),
|
|
217
|
+
}
|
|
218
|
+
self.slow_queries.append(slow_query)
|
|
219
|
+
|
|
220
|
+
# Keep only last 100 slow queries
|
|
221
|
+
if len(self.slow_queries) > 100:
|
|
222
|
+
self.slow_queries.pop(0)
|
|
223
|
+
|
|
224
|
+
if self.log_slow_queries:
|
|
225
|
+
logger.warning(f"Slow query detected: {query_type} took {duration_ms:.2f}ms " f"(rows: {row_count})")
|
|
226
|
+
|
|
227
|
+
async def analyze_query_plan(self, conn: asyncpg.Connection, query: str, params: tuple = ()) -> QueryPlan:
|
|
228
|
+
"""
|
|
229
|
+
Analyze query execution plan
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
conn: Database connection
|
|
233
|
+
query: SQL query to analyze
|
|
234
|
+
params: Query parameters
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
QueryPlan with analysis results
|
|
238
|
+
|
|
239
|
+
Example:
|
|
240
|
+
```python
|
|
241
|
+
plan = await monitor.analyze_query_plan(
|
|
242
|
+
conn,
|
|
243
|
+
"SELECT * FROM entities WHERE entity_type = $1",
|
|
244
|
+
("Person",)
|
|
245
|
+
)
|
|
246
|
+
print(plan.get_warnings())
|
|
247
|
+
```
|
|
248
|
+
"""
|
|
249
|
+
if not self.enabled:
|
|
250
|
+
raise RuntimeError("Performance monitoring is disabled")
|
|
251
|
+
|
|
252
|
+
# Get query plan with EXPLAIN (ANALYZE, FORMAT JSON)
|
|
253
|
+
explain_query = f"EXPLAIN (ANALYZE, FORMAT JSON) {query}"
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
start = time.time()
|
|
257
|
+
result = await conn.fetch(explain_query, *params)
|
|
258
|
+
duration_ms = (time.time() - start) * 1000
|
|
259
|
+
|
|
260
|
+
if not result:
|
|
261
|
+
raise ValueError("No query plan returned")
|
|
262
|
+
|
|
263
|
+
# Parse JSON plan
|
|
264
|
+
plan_json = result[0]["QUERY PLAN"]
|
|
265
|
+
if isinstance(plan_json, str):
|
|
266
|
+
import json
|
|
267
|
+
|
|
268
|
+
plan_json = json.loads(plan_json)
|
|
269
|
+
|
|
270
|
+
# Extract plan details
|
|
271
|
+
plan_data = plan_json[0] if isinstance(plan_json, list) else plan_json
|
|
272
|
+
total_cost = plan_data.get("Plan", {}).get("Total Cost", 0)
|
|
273
|
+
|
|
274
|
+
return QueryPlan(
|
|
275
|
+
query=query,
|
|
276
|
+
plan=plan_data,
|
|
277
|
+
total_cost=total_cost,
|
|
278
|
+
execution_time_ms=duration_ms,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
except Exception as e:
|
|
282
|
+
logger.error(f"Failed to analyze query plan: {e}")
|
|
283
|
+
raise
|
|
284
|
+
|
|
285
|
+
def get_performance_report(self) -> Dict[str, Any]:
|
|
286
|
+
"""
|
|
287
|
+
Get comprehensive performance report
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
Dictionary with performance statistics
|
|
291
|
+
|
|
292
|
+
Example:
|
|
293
|
+
```python
|
|
294
|
+
report = monitor.get_performance_report()
|
|
295
|
+
print(f"Total queries: {report['total_queries']}")
|
|
296
|
+
print(f"Slow queries: {len(report['slow_queries'])}")
|
|
297
|
+
```
|
|
298
|
+
"""
|
|
299
|
+
if not self.enabled:
|
|
300
|
+
return {"enabled": False}
|
|
301
|
+
|
|
302
|
+
# Calculate aggregate stats
|
|
303
|
+
total_queries = sum(stats.execution_count for stats in self.query_stats.values())
|
|
304
|
+
total_time_ms = sum(stats.total_time_ms for stats in self.query_stats.values())
|
|
305
|
+
|
|
306
|
+
# Get top slow queries
|
|
307
|
+
sorted_stats = sorted(
|
|
308
|
+
self.query_stats.values(),
|
|
309
|
+
key=lambda s: s.avg_time_ms,
|
|
310
|
+
reverse=True,
|
|
311
|
+
)
|
|
312
|
+
top_slow = [stats.to_dict() for stats in sorted_stats[:10]]
|
|
313
|
+
|
|
314
|
+
# Get most frequent queries
|
|
315
|
+
sorted_by_count = sorted(
|
|
316
|
+
self.query_stats.values(),
|
|
317
|
+
key=lambda s: s.execution_count,
|
|
318
|
+
reverse=True,
|
|
319
|
+
)
|
|
320
|
+
most_frequent = [stats.to_dict() for stats in sorted_by_count[:10]]
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
"enabled": True,
|
|
324
|
+
"total_queries": total_queries,
|
|
325
|
+
"total_time_ms": round(total_time_ms, 2),
|
|
326
|
+
"avg_query_time_ms": (round(total_time_ms / total_queries, 2) if total_queries > 0 else 0),
|
|
327
|
+
"unique_queries": len(self.query_stats),
|
|
328
|
+
"slow_query_count": len(self.slow_queries),
|
|
329
|
+
"slow_query_threshold_ms": self.slow_query_threshold_ms,
|
|
330
|
+
"top_slow_queries": top_slow,
|
|
331
|
+
"most_frequent_queries": most_frequent,
|
|
332
|
+
"recent_slow_queries": self.slow_queries[-10:],
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
def get_query_stats(self, query_type: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
336
|
+
"""
|
|
337
|
+
Get statistics for specific query type or all queries
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
query_type: Filter by query type (None for all)
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
List of query statistics
|
|
344
|
+
"""
|
|
345
|
+
if not self.enabled:
|
|
346
|
+
return []
|
|
347
|
+
|
|
348
|
+
stats_list: List[QueryStats] = list(self.query_stats.values())
|
|
349
|
+
if query_type:
|
|
350
|
+
stats_list = [s for s in stats_list if s.query_type == query_type]
|
|
351
|
+
|
|
352
|
+
return [s.to_dict() for s in stats_list]
|
|
353
|
+
|
|
354
|
+
def reset_stats(self) -> None:
|
|
355
|
+
"""Reset all performance statistics"""
|
|
356
|
+
self.query_stats.clear()
|
|
357
|
+
self.slow_queries.clear()
|
|
358
|
+
logger.info("Performance statistics reset")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class QueryTracker:
|
|
362
|
+
"""Context manager for tracking query execution time"""
|
|
363
|
+
|
|
364
|
+
def __init__(self, monitor: PerformanceMonitor, query_type: str, query_text: str):
|
|
365
|
+
"""
|
|
366
|
+
Initialize query tracker
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
monitor: Performance monitor instance
|
|
370
|
+
query_type: Type of query
|
|
371
|
+
query_text: SQL query text
|
|
372
|
+
"""
|
|
373
|
+
self.monitor = monitor
|
|
374
|
+
self.query_type = query_type
|
|
375
|
+
self.query_text = query_text
|
|
376
|
+
self.start_time = 0.0
|
|
377
|
+
|
|
378
|
+
async def __aenter__(self):
|
|
379
|
+
"""Start timing"""
|
|
380
|
+
self.start_time = time.time()
|
|
381
|
+
return self
|
|
382
|
+
|
|
383
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
384
|
+
"""End timing and record"""
|
|
385
|
+
if not self.monitor.enabled:
|
|
386
|
+
return
|
|
387
|
+
|
|
388
|
+
duration_ms = (time.time() - self.start_time) * 1000
|
|
389
|
+
await self.monitor.record_query(self.query_type, self.query_text, duration_ms)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class PreparedStatementCache:
|
|
393
|
+
"""
|
|
394
|
+
Cache for prepared statements
|
|
395
|
+
|
|
396
|
+
Provides caching of prepared statements to reduce query planning overhead.
|
|
397
|
+
|
|
398
|
+
Example:
|
|
399
|
+
```python
|
|
400
|
+
cache = PreparedStatementCache(max_size=100)
|
|
401
|
+
|
|
402
|
+
# Get or create prepared statement
|
|
403
|
+
stmt = await cache.get_or_prepare(
|
|
404
|
+
conn,
|
|
405
|
+
"get_entity",
|
|
406
|
+
"SELECT * FROM entities WHERE id = $1"
|
|
407
|
+
)
|
|
408
|
+
result = await conn.fetch(stmt, entity_id)
|
|
409
|
+
```
|
|
410
|
+
"""
|
|
411
|
+
|
|
412
|
+
def __init__(self, max_size: int = 100):
|
|
413
|
+
"""
|
|
414
|
+
Initialize prepared statement cache
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
max_size: Maximum number of cached statements
|
|
418
|
+
"""
|
|
419
|
+
self.max_size = max_size
|
|
420
|
+
self.cache: Dict[str, Any] = {}
|
|
421
|
+
self.access_count: Dict[str, int] = defaultdict(int)
|
|
422
|
+
|
|
423
|
+
async def get_or_prepare(self, conn: asyncpg.Connection, name: str, query: str) -> Any:
|
|
424
|
+
"""
|
|
425
|
+
Get cached prepared statement or create new one
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
conn: Database connection
|
|
429
|
+
name: Statement name (unique identifier)
|
|
430
|
+
query: SQL query text
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
Prepared statement
|
|
434
|
+
"""
|
|
435
|
+
# Check cache
|
|
436
|
+
if name in self.cache:
|
|
437
|
+
self.access_count[name] += 1
|
|
438
|
+
return self.cache[name]
|
|
439
|
+
|
|
440
|
+
# Evict least used if cache full
|
|
441
|
+
if len(self.cache) >= self.max_size:
|
|
442
|
+
least_used = min(self.access_count.items(), key=lambda x: x[1])[0]
|
|
443
|
+
del self.cache[least_used]
|
|
444
|
+
del self.access_count[least_used]
|
|
445
|
+
|
|
446
|
+
# Prepare statement
|
|
447
|
+
stmt = await conn.prepare(query)
|
|
448
|
+
self.cache[name] = stmt
|
|
449
|
+
self.access_count[name] = 1
|
|
450
|
+
|
|
451
|
+
return stmt
|
|
452
|
+
|
|
453
|
+
def clear(self) -> None:
|
|
454
|
+
"""Clear all cached statements"""
|
|
455
|
+
self.cache.clear()
|
|
456
|
+
self.access_count.clear()
|