flock-core 0.5.0b28__py3-none-any.whl → 0.5.56b0__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 flock-core might be problematic. Click here for more details.
- flock/__init__.py +12 -217
- flock/agent.py +678 -0
- flock/api/themes.py +71 -0
- flock/artifacts.py +79 -0
- flock/cli.py +75 -0
- flock/components.py +173 -0
- flock/dashboard/__init__.py +28 -0
- flock/dashboard/collector.py +283 -0
- flock/dashboard/events.py +182 -0
- flock/dashboard/launcher.py +230 -0
- flock/dashboard/service.py +537 -0
- flock/dashboard/websocket.py +235 -0
- flock/engines/__init__.py +6 -0
- flock/engines/dspy_engine.py +856 -0
- flock/examples.py +128 -0
- flock/{core/util → helper}/cli_helper.py +4 -3
- flock/{core/logging → logging}/__init__.py +2 -3
- flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
- flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
- flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
- flock/{core/logging → logging}/logging.py +77 -61
- flock/{core/logging → logging}/telemetry.py +20 -26
- flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
- flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
- flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
- flock/{core/logging → logging}/trace_and_logged.py +20 -24
- flock/mcp/__init__.py +91 -0
- flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
- flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
- flock/mcp/manager.py +255 -0
- flock/mcp/servers/sse/__init__.py +1 -1
- flock/mcp/servers/sse/flock_sse_server.py +11 -53
- flock/mcp/servers/stdio/__init__.py +1 -1
- flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
- flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
- flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
- flock/mcp/types/__init__.py +42 -0
- flock/{core/mcp → mcp}/types/callbacks.py +9 -15
- flock/{core/mcp → mcp}/types/factories.py +7 -6
- flock/{core/mcp → mcp}/types/handlers.py +13 -18
- flock/{core/mcp → mcp}/types/types.py +70 -74
- flock/{core/mcp → mcp}/util/helpers.py +1 -1
- flock/orchestrator.py +645 -0
- flock/registry.py +148 -0
- flock/runtime.py +262 -0
- flock/service.py +140 -0
- flock/store.py +69 -0
- flock/subscription.py +111 -0
- flock/themes/andromeda.toml +1 -1
- flock/themes/apple-system-colors.toml +1 -1
- flock/themes/arcoiris.toml +1 -1
- flock/themes/atomonelight.toml +1 -1
- flock/themes/ayu copy.toml +1 -1
- flock/themes/ayu-light.toml +1 -1
- flock/themes/belafonte-day.toml +1 -1
- flock/themes/belafonte-night.toml +1 -1
- flock/themes/blulocodark.toml +1 -1
- flock/themes/breeze.toml +1 -1
- flock/themes/broadcast.toml +1 -1
- flock/themes/brogrammer.toml +1 -1
- flock/themes/builtin-dark.toml +1 -1
- flock/themes/builtin-pastel-dark.toml +1 -1
- flock/themes/catppuccin-latte.toml +1 -1
- flock/themes/catppuccin-macchiato.toml +1 -1
- flock/themes/catppuccin-mocha.toml +1 -1
- flock/themes/cga.toml +1 -1
- flock/themes/chalk.toml +1 -1
- flock/themes/ciapre.toml +1 -1
- flock/themes/coffee-theme.toml +1 -1
- flock/themes/cyberpunkscarletprotocol.toml +1 -1
- flock/themes/dark+.toml +1 -1
- flock/themes/darkermatrix.toml +1 -1
- flock/themes/darkside.toml +1 -1
- flock/themes/desert.toml +1 -1
- flock/themes/django.toml +1 -1
- flock/themes/djangosmooth.toml +1 -1
- flock/themes/doomone.toml +1 -1
- flock/themes/dotgov.toml +1 -1
- flock/themes/dracula+.toml +1 -1
- flock/themes/duckbones.toml +1 -1
- flock/themes/encom.toml +1 -1
- flock/themes/espresso.toml +1 -1
- flock/themes/everblush.toml +1 -1
- flock/themes/fairyfloss.toml +1 -1
- flock/themes/fideloper.toml +1 -1
- flock/themes/fishtank.toml +1 -1
- flock/themes/flexoki-light.toml +1 -1
- flock/themes/floraverse.toml +1 -1
- flock/themes/framer.toml +1 -1
- flock/themes/galizur.toml +1 -1
- flock/themes/github.toml +1 -1
- flock/themes/grass.toml +1 -1
- flock/themes/grey-green.toml +1 -1
- flock/themes/gruvboxlight.toml +1 -1
- flock/themes/guezwhoz.toml +1 -1
- flock/themes/harper.toml +1 -1
- flock/themes/hax0r-blue.toml +1 -1
- flock/themes/hopscotch.256.toml +1 -1
- flock/themes/ic-green-ppl.toml +1 -1
- flock/themes/iceberg-dark.toml +1 -1
- flock/themes/japanesque.toml +1 -1
- flock/themes/jubi.toml +1 -1
- flock/themes/kibble.toml +1 -1
- flock/themes/kolorit.toml +1 -1
- flock/themes/kurokula.toml +1 -1
- flock/themes/materialdesigncolors.toml +1 -1
- flock/themes/matrix.toml +1 -1
- flock/themes/mellifluous.toml +1 -1
- flock/themes/midnight-in-mojave.toml +1 -1
- flock/themes/monokai-remastered.toml +1 -1
- flock/themes/monokai-soda.toml +1 -1
- flock/themes/neon.toml +1 -1
- flock/themes/neopolitan.toml +1 -1
- flock/themes/nord-light.toml +1 -1
- flock/themes/ocean.toml +1 -1
- flock/themes/onehalfdark.toml +1 -1
- flock/themes/onehalflight.toml +1 -1
- flock/themes/palenighthc.toml +1 -1
- flock/themes/paulmillr.toml +1 -1
- flock/themes/pencildark.toml +1 -1
- flock/themes/pnevma.toml +1 -1
- flock/themes/purple-rain.toml +1 -1
- flock/themes/purplepeter.toml +1 -1
- flock/themes/raycast-dark.toml +1 -1
- flock/themes/red-sands.toml +1 -1
- flock/themes/relaxed.toml +1 -1
- flock/themes/retro.toml +1 -1
- flock/themes/rose-pine.toml +1 -1
- flock/themes/royal.toml +1 -1
- flock/themes/ryuuko.toml +1 -1
- flock/themes/sakura.toml +1 -1
- flock/themes/scarlet-protocol.toml +1 -1
- flock/themes/seoulbones-dark.toml +1 -1
- flock/themes/shades-of-purple.toml +1 -1
- flock/themes/smyck.toml +1 -1
- flock/themes/softserver.toml +1 -1
- flock/themes/solarized-darcula.toml +1 -1
- flock/themes/square.toml +1 -1
- flock/themes/sugarplum.toml +1 -1
- flock/themes/thayer-bright.toml +1 -1
- flock/themes/tokyonight.toml +1 -1
- flock/themes/tomorrow.toml +1 -1
- flock/themes/ubuntu.toml +1 -1
- flock/themes/ultradark.toml +1 -1
- flock/themes/ultraviolent.toml +1 -1
- flock/themes/unikitty.toml +1 -1
- flock/themes/urple.toml +1 -1
- flock/themes/vesper.toml +1 -1
- flock/themes/vimbones.toml +1 -1
- flock/themes/wildcherry.toml +1 -1
- flock/themes/wilmersdorf.toml +1 -1
- flock/themes/wryan.toml +1 -1
- flock/themes/xcodedarkhc.toml +1 -1
- flock/themes/xcodelight.toml +1 -1
- flock/themes/zenbones-light.toml +1 -1
- flock/themes/zenwritten-dark.toml +1 -1
- flock/utilities.py +301 -0
- flock/{components/utility → utility}/output_utility_component.py +68 -53
- flock/visibility.py +107 -0
- flock_core-0.5.56b0.dist-info/METADATA +747 -0
- flock_core-0.5.56b0.dist-info/RECORD +398 -0
- flock_core-0.5.56b0.dist-info/entry_points.txt +2 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/licenses/LICENSE +1 -1
- flock/adapter/__init__.py +0 -14
- flock/adapter/azure_adapter.py +0 -68
- flock/adapter/chroma_adapter.py +0 -73
- flock/adapter/faiss_adapter.py +0 -97
- flock/adapter/pinecone_adapter.py +0 -51
- flock/adapter/vector_base.py +0 -47
- flock/cli/assets/release_notes.md +0 -140
- flock/cli/config.py +0 -8
- flock/cli/constants.py +0 -36
- flock/cli/create_agent.py +0 -1
- flock/cli/create_flock.py +0 -280
- flock/cli/execute_flock.py +0 -620
- flock/cli/load_agent.py +0 -1
- flock/cli/load_examples.py +0 -1
- flock/cli/load_flock.py +0 -192
- flock/cli/load_release_notes.py +0 -20
- flock/cli/loaded_flock_cli.py +0 -254
- flock/cli/manage_agents.py +0 -459
- flock/cli/registry_management.py +0 -889
- flock/cli/runner.py +0 -41
- flock/cli/settings.py +0 -857
- flock/cli/utils.py +0 -135
- flock/cli/view_results.py +0 -29
- flock/cli/yaml_editor.py +0 -396
- flock/components/__init__.py +0 -30
- flock/components/evaluation/__init__.py +0 -9
- flock/components/evaluation/declarative_evaluation_component.py +0 -606
- flock/components/routing/__init__.py +0 -15
- flock/components/routing/conditional_routing_component.py +0 -494
- flock/components/routing/default_routing_component.py +0 -103
- flock/components/routing/llm_routing_component.py +0 -206
- flock/components/utility/__init__.py +0 -22
- flock/components/utility/example_utility_component.py +0 -250
- flock/components/utility/feedback_utility_component.py +0 -206
- flock/components/utility/memory_utility_component.py +0 -550
- flock/components/utility/metrics_utility_component.py +0 -700
- flock/config.py +0 -61
- flock/core/__init__.py +0 -110
- flock/core/agent/__init__.py +0 -16
- flock/core/agent/default_agent.py +0 -216
- flock/core/agent/flock_agent_components.py +0 -104
- flock/core/agent/flock_agent_execution.py +0 -101
- flock/core/agent/flock_agent_integration.py +0 -260
- flock/core/agent/flock_agent_lifecycle.py +0 -186
- flock/core/agent/flock_agent_serialization.py +0 -381
- flock/core/api/__init__.py +0 -10
- flock/core/api/custom_endpoint.py +0 -45
- flock/core/api/endpoints.py +0 -254
- flock/core/api/main.py +0 -162
- flock/core/api/models.py +0 -97
- flock/core/api/run_store.py +0 -224
- flock/core/api/runner.py +0 -44
- flock/core/api/service.py +0 -214
- flock/core/component/__init__.py +0 -15
- flock/core/component/agent_component_base.py +0 -309
- flock/core/component/evaluation_component.py +0 -62
- flock/core/component/routing_component.py +0 -74
- flock/core/component/utility_component.py +0 -69
- flock/core/config/flock_agent_config.py +0 -58
- flock/core/config/scheduled_agent_config.py +0 -40
- flock/core/context/context.py +0 -213
- flock/core/context/context_manager.py +0 -37
- flock/core/context/context_vars.py +0 -10
- flock/core/evaluation/utils.py +0 -396
- flock/core/execution/batch_executor.py +0 -369
- flock/core/execution/evaluation_executor.py +0 -438
- flock/core/execution/local_executor.py +0 -31
- flock/core/execution/opik_executor.py +0 -103
- flock/core/execution/temporal_executor.py +0 -164
- flock/core/flock.py +0 -634
- flock/core/flock_agent.py +0 -336
- flock/core/flock_factory.py +0 -613
- flock/core/flock_scheduler.py +0 -166
- flock/core/flock_server_manager.py +0 -136
- flock/core/interpreter/python_interpreter.py +0 -689
- flock/core/mcp/__init__.py +0 -1
- flock/core/mcp/flock_mcp_server.py +0 -680
- flock/core/mcp/mcp_client_manager.py +0 -201
- flock/core/mcp/types/__init__.py +0 -1
- flock/core/mixin/dspy_integration.py +0 -403
- flock/core/mixin/prompt_parser.py +0 -125
- flock/core/orchestration/__init__.py +0 -15
- flock/core/orchestration/flock_batch_processor.py +0 -94
- flock/core/orchestration/flock_evaluator.py +0 -113
- flock/core/orchestration/flock_execution.py +0 -295
- flock/core/orchestration/flock_initialization.py +0 -149
- flock/core/orchestration/flock_server_manager.py +0 -67
- flock/core/orchestration/flock_web_server.py +0 -117
- flock/core/registry/__init__.py +0 -45
- flock/core/registry/agent_registry.py +0 -69
- flock/core/registry/callable_registry.py +0 -139
- flock/core/registry/component_discovery.py +0 -142
- flock/core/registry/component_registry.py +0 -64
- flock/core/registry/config_mapping.py +0 -64
- flock/core/registry/decorators.py +0 -137
- flock/core/registry/registry_hub.py +0 -205
- flock/core/registry/server_registry.py +0 -57
- flock/core/registry/type_registry.py +0 -86
- flock/core/serialization/__init__.py +0 -13
- flock/core/serialization/callable_registry.py +0 -52
- flock/core/serialization/flock_serializer.py +0 -832
- flock/core/serialization/json_encoder.py +0 -41
- flock/core/serialization/secure_serializer.py +0 -175
- flock/core/serialization/serializable.py +0 -342
- flock/core/serialization/serialization_utils.py +0 -412
- flock/core/util/file_path_utils.py +0 -223
- flock/core/util/hydrator.py +0 -309
- flock/core/util/input_resolver.py +0 -164
- flock/core/util/loader.py +0 -59
- flock/core/util/splitter.py +0 -219
- flock/di.py +0 -27
- flock/platform/docker_tools.py +0 -49
- flock/platform/jaeger_install.py +0 -86
- flock/webapp/__init__.py +0 -1
- flock/webapp/app/__init__.py +0 -0
- flock/webapp/app/api/__init__.py +0 -0
- flock/webapp/app/api/agent_management.py +0 -241
- flock/webapp/app/api/execution.py +0 -709
- flock/webapp/app/api/flock_management.py +0 -129
- flock/webapp/app/api/registry_viewer.py +0 -30
- flock/webapp/app/chat.py +0 -665
- flock/webapp/app/config.py +0 -104
- flock/webapp/app/dependencies.py +0 -117
- flock/webapp/app/main.py +0 -1070
- flock/webapp/app/middleware.py +0 -113
- flock/webapp/app/models_ui.py +0 -7
- flock/webapp/app/services/__init__.py +0 -0
- flock/webapp/app/services/feedback_file_service.py +0 -363
- flock/webapp/app/services/flock_service.py +0 -337
- flock/webapp/app/services/sharing_models.py +0 -81
- flock/webapp/app/services/sharing_store.py +0 -762
- flock/webapp/app/templates/theme_mapper.html +0 -326
- flock/webapp/app/theme_mapper.py +0 -812
- flock/webapp/app/utils.py +0 -85
- flock/webapp/run.py +0 -215
- flock/webapp/static/css/chat.css +0 -301
- flock/webapp/static/css/components.css +0 -167
- flock/webapp/static/css/header.css +0 -39
- flock/webapp/static/css/layout.css +0 -46
- flock/webapp/static/css/sidebar.css +0 -127
- flock/webapp/static/css/two-pane.css +0 -48
- flock/webapp/templates/base.html +0 -200
- flock/webapp/templates/chat.html +0 -152
- flock/webapp/templates/chat_settings.html +0 -19
- flock/webapp/templates/flock_editor.html +0 -16
- flock/webapp/templates/index.html +0 -12
- flock/webapp/templates/partials/_agent_detail_form.html +0 -93
- flock/webapp/templates/partials/_agent_list.html +0 -18
- flock/webapp/templates/partials/_agent_manager_view.html +0 -51
- flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
- flock/webapp/templates/partials/_chat_container.html +0 -15
- flock/webapp/templates/partials/_chat_messages.html +0 -57
- flock/webapp/templates/partials/_chat_settings_form.html +0 -85
- flock/webapp/templates/partials/_create_flock_form.html +0 -50
- flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
- flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
- flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
- flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
- flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
- flock/webapp/templates/partials/_env_vars_table.html +0 -23
- flock/webapp/templates/partials/_execution_form.html +0 -118
- flock/webapp/templates/partials/_execution_view_container.html +0 -28
- flock/webapp/templates/partials/_flock_file_list.html +0 -23
- flock/webapp/templates/partials/_flock_properties_form.html +0 -52
- flock/webapp/templates/partials/_flock_upload_form.html +0 -16
- flock/webapp/templates/partials/_header_flock_status.html +0 -5
- flock/webapp/templates/partials/_load_manager_view.html +0 -49
- flock/webapp/templates/partials/_registry_table.html +0 -25
- flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
- flock/webapp/templates/partials/_results_display.html +0 -78
- flock/webapp/templates/partials/_settings_env_content.html +0 -9
- flock/webapp/templates/partials/_settings_theme_content.html +0 -14
- flock/webapp/templates/partials/_settings_view.html +0 -36
- flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
- flock/webapp/templates/partials/_share_link_snippet.html +0 -35
- flock/webapp/templates/partials/_sidebar.html +0 -74
- flock/webapp/templates/partials/_streaming_results_container.html +0 -195
- flock/webapp/templates/partials/_structured_data_view.html +0 -40
- flock/webapp/templates/partials/_theme_preview.html +0 -36
- flock/webapp/templates/registry_viewer.html +0 -84
- flock/webapp/templates/shared_run_page.html +0 -140
- flock/workflow/__init__.py +0 -0
- flock/workflow/activities.py +0 -196
- flock/workflow/agent_activities.py +0 -24
- flock/workflow/agent_execution_activity.py +0 -202
- flock/workflow/flock_workflow.py +0 -214
- flock/workflow/temporal_config.py +0 -96
- flock/workflow/temporal_setup.py +0 -68
- flock_core-0.5.0b28.dist-info/METADATA +0 -274
- flock_core-0.5.0b28.dist-info/RECORD +0 -561
- flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
- /flock/{core/logging → logging}/formatters/themes.py +0 -0
- /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
- /flock/{core/mcp → mcp}/util/__init__.py +0 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/WHEEL +0 -0
|
@@ -1,700 +0,0 @@
|
|
|
1
|
-
# src/flock/components/utility/metrics_utility_component.py
|
|
2
|
-
"""Performance and metrics tracking for Flock agents using unified component architecture."""
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
import os
|
|
6
|
-
import time
|
|
7
|
-
from collections import defaultdict
|
|
8
|
-
from datetime import datetime
|
|
9
|
-
from typing import TYPE_CHECKING, Any, Literal
|
|
10
|
-
|
|
11
|
-
import numpy as np
|
|
12
|
-
import psutil
|
|
13
|
-
from pydantic import BaseModel, Field, field_validator
|
|
14
|
-
|
|
15
|
-
from flock.core.component.agent_component_base import AgentComponentConfig
|
|
16
|
-
from flock.core.component.utility_component import UtilityComponent
|
|
17
|
-
from flock.core.context.context import FlockContext
|
|
18
|
-
from flock.core.logging.logging import get_logger
|
|
19
|
-
from flock.core.mcp.flock_mcp_server import FlockMCPServer
|
|
20
|
-
from flock.core.registry import flock_component
|
|
21
|
-
|
|
22
|
-
logger = get_logger(__name__)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if TYPE_CHECKING:
|
|
26
|
-
from flock.core.flock_agent import FlockAgent
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class MetricPoint(BaseModel):
|
|
30
|
-
"""Single metric measurement."""
|
|
31
|
-
|
|
32
|
-
timestamp: datetime
|
|
33
|
-
value: int | float | str
|
|
34
|
-
tags: dict[str, str] = {}
|
|
35
|
-
|
|
36
|
-
model_config = {"arbitrary_types_allowed": True}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class MetricsUtilityConfig(AgentComponentConfig):
|
|
40
|
-
"""Configuration for performance metrics collection."""
|
|
41
|
-
|
|
42
|
-
# Collection settings
|
|
43
|
-
collect_timing: bool = Field(
|
|
44
|
-
default=True, description="Collect timing metrics"
|
|
45
|
-
)
|
|
46
|
-
collect_memory: bool = Field(
|
|
47
|
-
default=True, description="Collect memory usage"
|
|
48
|
-
)
|
|
49
|
-
collect_token_usage: bool = Field(
|
|
50
|
-
default=True, description="Collect token usage stats"
|
|
51
|
-
)
|
|
52
|
-
collect_cpu: bool = Field(default=True, description="Collect CPU usage")
|
|
53
|
-
|
|
54
|
-
# Storage settings
|
|
55
|
-
storage_type: Literal["json", "prometheus", "memory"] = Field(
|
|
56
|
-
default="json", description="Where to store metrics"
|
|
57
|
-
)
|
|
58
|
-
metrics_dir: str = Field(
|
|
59
|
-
default=".flock/metrics/", description="Directory for metrics storage"
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
# Aggregation settings
|
|
63
|
-
aggregation_interval: str = Field(
|
|
64
|
-
default="1h", description="Interval for metric aggregation"
|
|
65
|
-
)
|
|
66
|
-
retention_days: int = Field(default=30, description="Days to keep metrics")
|
|
67
|
-
|
|
68
|
-
# Alerting settings
|
|
69
|
-
alert_on_high_latency: bool = Field(
|
|
70
|
-
default=True, description="Alert on high latency"
|
|
71
|
-
)
|
|
72
|
-
latency_threshold_ms: int = Field(
|
|
73
|
-
default=1000, description="Threshold for latency alerts"
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
@field_validator("aggregation_interval")
|
|
77
|
-
@classmethod
|
|
78
|
-
def validate_interval(cls, v):
|
|
79
|
-
"""Validate time interval format."""
|
|
80
|
-
if v[-1] not in ["s", "m", "h", "d"]:
|
|
81
|
-
raise ValueError("Interval must end with s, m, h, or d")
|
|
82
|
-
return v
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
@flock_component(config_class=MetricsUtilityConfig)
|
|
86
|
-
class MetricsUtilityComponent(UtilityComponent):
|
|
87
|
-
"""Utility component for collecting and analyzing agent performance metrics."""
|
|
88
|
-
|
|
89
|
-
# --- Singleton holder for convenient static access ---
|
|
90
|
-
_INSTANCE: "MetricsUtilityComponent | None" = None
|
|
91
|
-
|
|
92
|
-
config: MetricsUtilityConfig = Field(
|
|
93
|
-
default_factory=MetricsUtilityConfig,
|
|
94
|
-
description="Performance metrics configuration",
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
def __init__(
|
|
98
|
-
self,
|
|
99
|
-
name: str = "metrics",
|
|
100
|
-
config: MetricsUtilityConfig | None = None,
|
|
101
|
-
**data,
|
|
102
|
-
):
|
|
103
|
-
if config is None:
|
|
104
|
-
config = MetricsUtilityConfig()
|
|
105
|
-
super().__init__(name=name, config=config, **data)
|
|
106
|
-
|
|
107
|
-
# Register singleton for static helpers
|
|
108
|
-
MetricsUtilityComponent._INSTANCE = self
|
|
109
|
-
self._metrics = defaultdict(list)
|
|
110
|
-
self._start_time: float | None = None
|
|
111
|
-
self._server_start_time: float | None = None
|
|
112
|
-
self._start_memory: int | None = None
|
|
113
|
-
self._server_start_memory: int | None = None
|
|
114
|
-
self._client_refreshs: int = 0
|
|
115
|
-
|
|
116
|
-
# Set up storage
|
|
117
|
-
if self.config.storage_type == "json":
|
|
118
|
-
os.makedirs(self.config.metrics_dir, exist_ok=True)
|
|
119
|
-
|
|
120
|
-
# Set up prometheus if needed
|
|
121
|
-
if self.config.storage_type == "prometheus":
|
|
122
|
-
try:
|
|
123
|
-
from prometheus_client import Counter, Gauge, Histogram
|
|
124
|
-
|
|
125
|
-
self._prom_latency = Histogram(
|
|
126
|
-
"flock_agent_latency_seconds",
|
|
127
|
-
"Time taken for agent evaluation",
|
|
128
|
-
["agent_name"],
|
|
129
|
-
)
|
|
130
|
-
self._prom_memory = Gauge(
|
|
131
|
-
"flock_agent_memory_bytes",
|
|
132
|
-
"Memory usage by agent",
|
|
133
|
-
["agent_name"],
|
|
134
|
-
)
|
|
135
|
-
self._prom_tokens = Counter(
|
|
136
|
-
"flock_agent_tokens_total",
|
|
137
|
-
"Token usage by agent",
|
|
138
|
-
["agent_name", "type"],
|
|
139
|
-
)
|
|
140
|
-
self._prom_errors = Counter(
|
|
141
|
-
"flock_agent_errors_total",
|
|
142
|
-
"Error count by agent",
|
|
143
|
-
["agent_name", "error_type"],
|
|
144
|
-
)
|
|
145
|
-
except ImportError:
|
|
146
|
-
self.config.storage_type = "json"
|
|
147
|
-
|
|
148
|
-
def _load_metrics_from_files(
|
|
149
|
-
self, metric_name: str = None
|
|
150
|
-
) -> dict[str, list[MetricPoint]]:
|
|
151
|
-
"""Load metrics from JSON files."""
|
|
152
|
-
metrics = defaultdict(list)
|
|
153
|
-
|
|
154
|
-
try:
|
|
155
|
-
# Get all metric files
|
|
156
|
-
files = [
|
|
157
|
-
f
|
|
158
|
-
for f in os.listdir(self.config.metrics_dir)
|
|
159
|
-
if f.endswith(".json") and not f.startswith("summary_")
|
|
160
|
-
]
|
|
161
|
-
|
|
162
|
-
# Filter by metric name if specified
|
|
163
|
-
if metric_name:
|
|
164
|
-
files = [f for f in files if f.startswith(f"{metric_name}_")]
|
|
165
|
-
|
|
166
|
-
for filename in files:
|
|
167
|
-
filepath = os.path.join(self.config.metrics_dir, filename)
|
|
168
|
-
with open(filepath) as f:
|
|
169
|
-
for line in f:
|
|
170
|
-
try:
|
|
171
|
-
data = json.loads(line)
|
|
172
|
-
point = MetricPoint(
|
|
173
|
-
timestamp=datetime.fromisoformat(
|
|
174
|
-
data["timestamp"]
|
|
175
|
-
),
|
|
176
|
-
value=data["value"],
|
|
177
|
-
tags=data["tags"],
|
|
178
|
-
)
|
|
179
|
-
name = filename.split("_")[
|
|
180
|
-
0
|
|
181
|
-
] # Get metric name from filename
|
|
182
|
-
metrics[name].append(point)
|
|
183
|
-
except json.JSONDecodeError:
|
|
184
|
-
continue
|
|
185
|
-
|
|
186
|
-
return dict(metrics)
|
|
187
|
-
except Exception as e:
|
|
188
|
-
logger.error(f"Error loading metrics from files: {e}")
|
|
189
|
-
return {}
|
|
190
|
-
|
|
191
|
-
def get_metrics(
|
|
192
|
-
self,
|
|
193
|
-
metric_name: str | None = None,
|
|
194
|
-
start_time: datetime | None = None,
|
|
195
|
-
end_time: datetime | None = None,
|
|
196
|
-
) -> dict[str, list[MetricPoint]]:
|
|
197
|
-
"""Get recorded metrics with optional filtering."""
|
|
198
|
-
# Get metrics from appropriate source
|
|
199
|
-
if self.config.storage_type == "json":
|
|
200
|
-
metrics = self._load_metrics_from_files(metric_name)
|
|
201
|
-
else:
|
|
202
|
-
metrics = self._metrics
|
|
203
|
-
if metric_name:
|
|
204
|
-
metrics = {metric_name: metrics[metric_name]}
|
|
205
|
-
|
|
206
|
-
# Apply time filtering if needed
|
|
207
|
-
if start_time or end_time:
|
|
208
|
-
filtered_metrics = defaultdict(list)
|
|
209
|
-
for name, points in metrics.items():
|
|
210
|
-
filtered_points = [
|
|
211
|
-
p
|
|
212
|
-
for p in points
|
|
213
|
-
if (not start_time or p.timestamp >= start_time)
|
|
214
|
-
and (not end_time or p.timestamp <= end_time)
|
|
215
|
-
]
|
|
216
|
-
filtered_metrics[name] = filtered_points
|
|
217
|
-
metrics = filtered_metrics
|
|
218
|
-
|
|
219
|
-
return dict(metrics)
|
|
220
|
-
|
|
221
|
-
def get_statistics(
|
|
222
|
-
self, metric_name: str, percentiles: list[float] = [50, 90, 95, 99]
|
|
223
|
-
) -> dict[str, float]:
|
|
224
|
-
"""Calculate statistics for a metric."""
|
|
225
|
-
# Get all points for this metric
|
|
226
|
-
metrics = self.get_metrics(metric_name=metric_name)
|
|
227
|
-
points = metrics.get(metric_name, [])
|
|
228
|
-
|
|
229
|
-
if not points:
|
|
230
|
-
return {}
|
|
231
|
-
|
|
232
|
-
values = [p.value for p in points if isinstance(p.value, (int, float))]
|
|
233
|
-
if not values:
|
|
234
|
-
return {}
|
|
235
|
-
|
|
236
|
-
stats = {
|
|
237
|
-
"min": min(values),
|
|
238
|
-
"max": max(values),
|
|
239
|
-
"mean": float(
|
|
240
|
-
np.mean(values)
|
|
241
|
-
), # Convert to float for JSON serialization
|
|
242
|
-
"std": float(np.std(values)),
|
|
243
|
-
"count": len(values),
|
|
244
|
-
"last_value": values[-1],
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
for p in percentiles:
|
|
248
|
-
stats[f"p{p}"] = float(np.percentile(values, p))
|
|
249
|
-
|
|
250
|
-
return stats
|
|
251
|
-
|
|
252
|
-
def _record_metric(
|
|
253
|
-
self, name: str, value: int | float | str, tags: dict[str, str] = None
|
|
254
|
-
) -> None:
|
|
255
|
-
"""Record a single metric point."""
|
|
256
|
-
point = MetricPoint(
|
|
257
|
-
timestamp=datetime.now(), value=value, tags=tags or {}
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
# Store metric
|
|
261
|
-
if self.config.storage_type == "memory":
|
|
262
|
-
self._metrics[name].append(point)
|
|
263
|
-
|
|
264
|
-
elif self.config.storage_type == "prometheus":
|
|
265
|
-
if name == "latency":
|
|
266
|
-
self._prom_latency.labels(**tags).observe(value)
|
|
267
|
-
elif name == "memory":
|
|
268
|
-
self._prom_memory.labels(**tags).set(value)
|
|
269
|
-
elif name == "tokens":
|
|
270
|
-
self._prom_tokens.labels(**tags).inc(value)
|
|
271
|
-
|
|
272
|
-
elif self.config.storage_type == "json":
|
|
273
|
-
self._save_metric_to_file(name, point)
|
|
274
|
-
|
|
275
|
-
def _save_metric_to_file(self, name: str, point: MetricPoint) -> None:
|
|
276
|
-
"""Save metric to JSON file."""
|
|
277
|
-
filename = f"{name}_{point.timestamp.strftime('%Y%m')}.json"
|
|
278
|
-
filepath = os.path.join(self.config.metrics_dir, filename)
|
|
279
|
-
|
|
280
|
-
data = {
|
|
281
|
-
"timestamp": point.timestamp.isoformat(),
|
|
282
|
-
"value": point.value,
|
|
283
|
-
"tags": point.tags,
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
# Append to file
|
|
287
|
-
with open(filepath, "a") as f:
|
|
288
|
-
f.write(json.dumps(data) + "\n")
|
|
289
|
-
|
|
290
|
-
def _get_tokenizer(self, model: str):
|
|
291
|
-
"""Get the appropriate tokenizer for the model."""
|
|
292
|
-
try:
|
|
293
|
-
import tiktoken
|
|
294
|
-
|
|
295
|
-
# Handle different model naming conventions
|
|
296
|
-
if model.startswith("openai/"):
|
|
297
|
-
model = model[7:] # Strip 'openai/' prefix
|
|
298
|
-
|
|
299
|
-
try:
|
|
300
|
-
return tiktoken.encoding_for_model(model)
|
|
301
|
-
except KeyError:
|
|
302
|
-
# Fallback to cl100k_base for unknown models
|
|
303
|
-
return tiktoken.get_encoding("cl100k_base")
|
|
304
|
-
|
|
305
|
-
except ImportError:
|
|
306
|
-
return None
|
|
307
|
-
|
|
308
|
-
def _calculate_token_usage(self, text: str, model: str = "gpt-4") -> int:
|
|
309
|
-
"""Calculate token count using tiktoken when available."""
|
|
310
|
-
tokenizer = self._get_tokenizer(model)
|
|
311
|
-
|
|
312
|
-
if tokenizer:
|
|
313
|
-
# Use tiktoken for accurate count
|
|
314
|
-
return len(tokenizer.encode(text))
|
|
315
|
-
else:
|
|
316
|
-
# Fallback to estimation if tiktoken not available
|
|
317
|
-
# Simple estimation - words / 0.75 for average tokens per word
|
|
318
|
-
token_estimate = int(len(text.split()) / 0.75)
|
|
319
|
-
|
|
320
|
-
# Log warning about estimation
|
|
321
|
-
print(
|
|
322
|
-
f"Warning: Using estimated token count. Install tiktoken for accurate counting."
|
|
323
|
-
)
|
|
324
|
-
return token_estimate
|
|
325
|
-
|
|
326
|
-
def _calculate_cost(
|
|
327
|
-
self, text: str, model: str, is_completion: bool = False
|
|
328
|
-
) -> tuple[int, float]:
|
|
329
|
-
"""Calculate both token count and cost."""
|
|
330
|
-
# Get token count
|
|
331
|
-
try:
|
|
332
|
-
from litellm import cost_per_token
|
|
333
|
-
|
|
334
|
-
token_count = self._calculate_token_usage(text, model)
|
|
335
|
-
# Calculate total cost
|
|
336
|
-
if is_completion:
|
|
337
|
-
total_cost = token_count * cost_per_token(
|
|
338
|
-
model, completion_tokens=token_count
|
|
339
|
-
)
|
|
340
|
-
else:
|
|
341
|
-
total_cost = token_count * cost_per_token(
|
|
342
|
-
model, prompt_tokens=token_count
|
|
343
|
-
)
|
|
344
|
-
|
|
345
|
-
return token_count, total_cost
|
|
346
|
-
except Exception:
|
|
347
|
-
token_count = 0
|
|
348
|
-
total_cost = 0.0
|
|
349
|
-
return token_count, total_cost
|
|
350
|
-
|
|
351
|
-
def _should_alert(self, metric: str, value: float) -> bool:
|
|
352
|
-
"""Check if metric should trigger alert."""
|
|
353
|
-
if metric == "latency" and self.config.alert_on_high_latency:
|
|
354
|
-
return value * 1000 > self.config.latency_threshold_ms
|
|
355
|
-
return False
|
|
356
|
-
|
|
357
|
-
async def on_initialize(
|
|
358
|
-
self,
|
|
359
|
-
agent: "FlockAgent",
|
|
360
|
-
inputs: dict[str, Any],
|
|
361
|
-
context: FlockContext | None = None,
|
|
362
|
-
) -> None:
|
|
363
|
-
"""Initialize metrics collection."""
|
|
364
|
-
self._start_time = time.time()
|
|
365
|
-
|
|
366
|
-
if self.config.collect_memory:
|
|
367
|
-
self._start_memory = psutil.Process().memory_info().rss
|
|
368
|
-
self._record_metric(
|
|
369
|
-
"memory",
|
|
370
|
-
self._start_memory,
|
|
371
|
-
{"agent": agent.name, "phase": "start"},
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
async def on_pre_evaluate(
|
|
375
|
-
self,
|
|
376
|
-
agent: "FlockAgent",
|
|
377
|
-
inputs: dict[str, Any],
|
|
378
|
-
context: FlockContext | None = None,
|
|
379
|
-
) -> dict[str, Any]:
|
|
380
|
-
"""Record pre-evaluation metrics."""
|
|
381
|
-
if self.config.collect_token_usage:
|
|
382
|
-
# Calculate input tokens and cost
|
|
383
|
-
total_input_tokens = 0
|
|
384
|
-
total_input_cost = 0.0
|
|
385
|
-
|
|
386
|
-
for v in inputs.values():
|
|
387
|
-
tokens, cost = self._calculate_cost(
|
|
388
|
-
str(v), agent.model, is_completion=False
|
|
389
|
-
)
|
|
390
|
-
total_input_tokens += tokens
|
|
391
|
-
if isinstance(cost, float):
|
|
392
|
-
total_input_cost += cost
|
|
393
|
-
else:
|
|
394
|
-
total_input_cost += cost[1]
|
|
395
|
-
|
|
396
|
-
self._record_metric(
|
|
397
|
-
"tokens",
|
|
398
|
-
total_input_tokens,
|
|
399
|
-
{"agent": agent.name, "type": "input"},
|
|
400
|
-
)
|
|
401
|
-
self._record_metric(
|
|
402
|
-
"cost", total_input_cost, {"agent": agent.name, "type": "input"}
|
|
403
|
-
)
|
|
404
|
-
|
|
405
|
-
if self.config.collect_cpu:
|
|
406
|
-
cpu_percent = psutil.Process().cpu_percent()
|
|
407
|
-
self._record_metric(
|
|
408
|
-
"cpu",
|
|
409
|
-
cpu_percent,
|
|
410
|
-
{"agent": agent.name, "phase": "pre_evaluate"},
|
|
411
|
-
)
|
|
412
|
-
|
|
413
|
-
return inputs
|
|
414
|
-
|
|
415
|
-
async def on_post_evaluate(
|
|
416
|
-
self,
|
|
417
|
-
agent: "FlockAgent",
|
|
418
|
-
inputs: dict[str, Any],
|
|
419
|
-
context: FlockContext | None = None,
|
|
420
|
-
result: dict[str, Any] | None = None,
|
|
421
|
-
) -> dict[str, Any]:
|
|
422
|
-
"""Record post-evaluation metrics."""
|
|
423
|
-
if self.config.collect_timing and self._start_time:
|
|
424
|
-
latency = time.time() - self._start_time
|
|
425
|
-
self._record_metric("latency", latency, {"agent": agent.name})
|
|
426
|
-
print(f"Latency: {latency * 1000:.2f}ms")
|
|
427
|
-
|
|
428
|
-
# Check for alerts
|
|
429
|
-
if self._should_alert("latency", latency):
|
|
430
|
-
# In practice, you'd want to integrate with a proper alerting system
|
|
431
|
-
logger.warning(f"ALERT: High latency detected!")
|
|
432
|
-
|
|
433
|
-
if self.config.collect_token_usage and result:
|
|
434
|
-
# Calculate output tokens and cost
|
|
435
|
-
total_output_tokens = 0
|
|
436
|
-
total_output_cost = 0.0
|
|
437
|
-
|
|
438
|
-
for v in result.values():
|
|
439
|
-
tokens, cost = self._calculate_cost(
|
|
440
|
-
str(v), agent.model, is_completion=True
|
|
441
|
-
)
|
|
442
|
-
total_output_tokens += tokens
|
|
443
|
-
if isinstance(cost, float):
|
|
444
|
-
total_output_cost += cost
|
|
445
|
-
else:
|
|
446
|
-
total_output_cost += cost[1]
|
|
447
|
-
|
|
448
|
-
print(f"Total output tokens: {total_output_tokens}")
|
|
449
|
-
|
|
450
|
-
self._record_metric(
|
|
451
|
-
"tokens",
|
|
452
|
-
total_output_tokens,
|
|
453
|
-
{"agent": agent.name, "type": "output"},
|
|
454
|
-
)
|
|
455
|
-
self._record_metric(
|
|
456
|
-
"cost",
|
|
457
|
-
total_output_cost,
|
|
458
|
-
{"agent": agent.name, "type": "output"},
|
|
459
|
-
)
|
|
460
|
-
|
|
461
|
-
# Record total cost for this operation
|
|
462
|
-
self._record_metric(
|
|
463
|
-
"total_cost",
|
|
464
|
-
total_output_cost + total_output_cost,
|
|
465
|
-
{"agent": agent.name},
|
|
466
|
-
)
|
|
467
|
-
|
|
468
|
-
if self.config.collect_memory and self._start_memory:
|
|
469
|
-
current_memory = psutil.Process().memory_info().rss
|
|
470
|
-
memory_diff = current_memory - self._start_memory
|
|
471
|
-
self._record_metric(
|
|
472
|
-
"memory", memory_diff, {"agent": agent.name, "phase": "end"}
|
|
473
|
-
)
|
|
474
|
-
|
|
475
|
-
return result
|
|
476
|
-
|
|
477
|
-
async def on_error(
|
|
478
|
-
self,
|
|
479
|
-
agent: "FlockAgent",
|
|
480
|
-
error: Exception,
|
|
481
|
-
inputs: dict[str, Any],
|
|
482
|
-
context: FlockContext | None = None,
|
|
483
|
-
) -> None:
|
|
484
|
-
"""Record error metrics."""
|
|
485
|
-
self._record_metric(
|
|
486
|
-
"errors",
|
|
487
|
-
1,
|
|
488
|
-
{"agent": agent.name, "error_type": type(error).__name__},
|
|
489
|
-
)
|
|
490
|
-
|
|
491
|
-
async def on_terminate(
|
|
492
|
-
self,
|
|
493
|
-
agent: "FlockAgent",
|
|
494
|
-
inputs: dict[str, Any],
|
|
495
|
-
context: FlockContext | None = None,
|
|
496
|
-
result: dict[str, Any] | None = None,
|
|
497
|
-
) -> None:
|
|
498
|
-
"""Clean up and final metric recording."""
|
|
499
|
-
if self.config.storage_type == "json":
|
|
500
|
-
# Save aggregated metrics
|
|
501
|
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
502
|
-
summary_file = os.path.join(
|
|
503
|
-
self.config.metrics_dir,
|
|
504
|
-
f"summary_{agent.name}_{timestamp}.json",
|
|
505
|
-
)
|
|
506
|
-
|
|
507
|
-
# Calculate summary for all metrics
|
|
508
|
-
summary = {
|
|
509
|
-
"agent": agent.name,
|
|
510
|
-
"timestamp": timestamp,
|
|
511
|
-
"metrics": {},
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
# Get all unique metric names from files
|
|
515
|
-
all_metrics = self._load_metrics_from_files()
|
|
516
|
-
|
|
517
|
-
for metric_name in all_metrics:
|
|
518
|
-
stats = self.get_statistics(metric_name)
|
|
519
|
-
if stats: # Only include metrics that have data
|
|
520
|
-
summary["metrics"][metric_name] = stats
|
|
521
|
-
|
|
522
|
-
with open(summary_file, "w") as f:
|
|
523
|
-
json.dump(summary, f, indent=2)
|
|
524
|
-
|
|
525
|
-
# --------------------------------------------------
|
|
526
|
-
# Public helper for external modules
|
|
527
|
-
# --------------------------------------------------
|
|
528
|
-
@classmethod
|
|
529
|
-
def record(
|
|
530
|
-
cls,
|
|
531
|
-
name: str,
|
|
532
|
-
value: int | float | str,
|
|
533
|
-
tags: dict[str, str] | None = None,
|
|
534
|
-
):
|
|
535
|
-
"""Record a metric from anywhere in the codebase.
|
|
536
|
-
|
|
537
|
-
Example:
|
|
538
|
-
MetricsUtilityComponent.record("custom_latency", 123, {"stage": "inference"})
|
|
539
|
-
The call will forward to the *first* instantiated MetricsUtilityComponent. If no
|
|
540
|
-
instance exists in the current run the call is a no-op so that importing
|
|
541
|
-
this helper never crashes test-code.
|
|
542
|
-
"""
|
|
543
|
-
instance = cls._INSTANCE
|
|
544
|
-
if instance is None:
|
|
545
|
-
return # silently ignore if module isn't active
|
|
546
|
-
instance._record_metric(name, value, tags or {})
|
|
547
|
-
|
|
548
|
-
# --- MCP Server Lifecycle Hooks ---
|
|
549
|
-
async def on_server_error(
|
|
550
|
-
self, server: FlockMCPServer, error: Exception
|
|
551
|
-
) -> None:
|
|
552
|
-
"""Record server error metrics."""
|
|
553
|
-
self._record_metric(
|
|
554
|
-
"errors",
|
|
555
|
-
1,
|
|
556
|
-
{
|
|
557
|
-
"server": server.config.name,
|
|
558
|
-
"error_type": type(error).__name__,
|
|
559
|
-
},
|
|
560
|
-
)
|
|
561
|
-
|
|
562
|
-
async def on_pre_server_init(self, server: FlockMCPServer):
|
|
563
|
-
"""Initialize metrics collection for server."""
|
|
564
|
-
self._server_start_time = time.time()
|
|
565
|
-
|
|
566
|
-
if self.config.collect_memory:
|
|
567
|
-
self._server_start_memory = psutil.Process().memory_info().rss
|
|
568
|
-
self._record_metric(
|
|
569
|
-
"server_memory",
|
|
570
|
-
self._server_start_memory,
|
|
571
|
-
{"server": server.config.name, "phase": "pre_init"},
|
|
572
|
-
)
|
|
573
|
-
|
|
574
|
-
async def on_post_server_init(self, server: FlockMCPServer):
|
|
575
|
-
"""Collect metrics after server starts."""
|
|
576
|
-
if self.config.collect_memory:
|
|
577
|
-
checkpoint_memory = psutil.Process().memory_info().rss
|
|
578
|
-
self._record_metric(
|
|
579
|
-
"server_memory",
|
|
580
|
-
checkpoint_memory,
|
|
581
|
-
{"server": server.config.name, "phase": "post_init"},
|
|
582
|
-
)
|
|
583
|
-
|
|
584
|
-
async def on_pre_server_terminate(self, server: FlockMCPServer):
|
|
585
|
-
"""Collect metrics before server terminates."""
|
|
586
|
-
if self.config.collect_memory:
|
|
587
|
-
checkpoint_memory = psutil.Process().memory_info().rss
|
|
588
|
-
self._record_metric(
|
|
589
|
-
"server_memory",
|
|
590
|
-
checkpoint_memory,
|
|
591
|
-
{"server": server.config.name, "phase": "pre_terminate"},
|
|
592
|
-
)
|
|
593
|
-
|
|
594
|
-
async def on_post_server_terminate(self, server: FlockMCPServer):
|
|
595
|
-
"""Collect metrics after server terminates.
|
|
596
|
-
|
|
597
|
-
Clean up and final metric recording.
|
|
598
|
-
"""
|
|
599
|
-
if self.config.storage_type == "json":
|
|
600
|
-
# Save aggregated metrics
|
|
601
|
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
602
|
-
summary_file = os.path.join(
|
|
603
|
-
self.config.metrics_dir,
|
|
604
|
-
f"summary_{server.config.name}_{timestamp}.json",
|
|
605
|
-
)
|
|
606
|
-
|
|
607
|
-
# Calculate summary for all metrics
|
|
608
|
-
summary = {
|
|
609
|
-
"server": server.config.name,
|
|
610
|
-
"timestamp": timestamp,
|
|
611
|
-
"metrics": {},
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
# Get all unique metric names from files
|
|
615
|
-
all_metrics = self._load_metrics_from_files()
|
|
616
|
-
|
|
617
|
-
for metric_name in all_metrics:
|
|
618
|
-
stats = self.get_statistics(metric_name)
|
|
619
|
-
if stats: # Only include metrics that have data
|
|
620
|
-
summary["metrics"][metric_name] = stats
|
|
621
|
-
with open(summary_file, "w") as f:
|
|
622
|
-
json.dump(summary, f, indent=2)
|
|
623
|
-
|
|
624
|
-
async def on_pre_mcp_call(
|
|
625
|
-
self, server: FlockMCPServer, arguments: Any | None = None
|
|
626
|
-
):
|
|
627
|
-
"""Record pre-call metrics."""
|
|
628
|
-
if self.config.collect_cpu:
|
|
629
|
-
cpu_percent = psutil.Process().cpu_percent()
|
|
630
|
-
self._record_metric(
|
|
631
|
-
"cpu",
|
|
632
|
-
cpu_percent,
|
|
633
|
-
{"server": server.config.name, "phase": "pre_mcp_call"},
|
|
634
|
-
)
|
|
635
|
-
if self.config.collect_memory:
|
|
636
|
-
current_memory = psutil.Process().memory_info().rss
|
|
637
|
-
memory_diff = current_memory - self._server_start_memory
|
|
638
|
-
self._record_metric(
|
|
639
|
-
"memory",
|
|
640
|
-
memory_diff,
|
|
641
|
-
{"server": server.config.name, "phase": "pre_mcp_call"},
|
|
642
|
-
)
|
|
643
|
-
|
|
644
|
-
if isinstance(arguments, dict):
|
|
645
|
-
self._record_metric(
|
|
646
|
-
"arguments",
|
|
647
|
-
len(arguments),
|
|
648
|
-
{
|
|
649
|
-
"server": server.config.name,
|
|
650
|
-
"phase": "pre_mcp_call",
|
|
651
|
-
}.update(arguments),
|
|
652
|
-
)
|
|
653
|
-
|
|
654
|
-
async def on_post_mcp_call(
|
|
655
|
-
self, server: FlockMCPServer, result: Any | None = None
|
|
656
|
-
):
|
|
657
|
-
"""Record post-call metrics."""
|
|
658
|
-
if self.config.collect_timing and self._server_start_time:
|
|
659
|
-
latency = time.time() - self._server_start_time
|
|
660
|
-
self._record_metric(
|
|
661
|
-
"latency", latency, {"server": server.config.name}
|
|
662
|
-
)
|
|
663
|
-
|
|
664
|
-
# Check for alerts
|
|
665
|
-
if self._should_alert("latency", latency):
|
|
666
|
-
# In practice, you'd want to integrate with a proper alerting system
|
|
667
|
-
print(f"ALERT: High latency detected: {latency * 1000:.2f}ms")
|
|
668
|
-
|
|
669
|
-
if self.config.collect_cpu:
|
|
670
|
-
cpu_percent = psutil.Process().cpu_percent()
|
|
671
|
-
self._record_metric(
|
|
672
|
-
"cpu",
|
|
673
|
-
cpu_percent,
|
|
674
|
-
{"server": server.config.name, "phase": "post_mcp_call"},
|
|
675
|
-
)
|
|
676
|
-
if self.config.collect_memory:
|
|
677
|
-
current_memory = psutil.Process().memory_info().rss
|
|
678
|
-
memory_diff = current_memory - self._server_start_memory
|
|
679
|
-
self._record_metric(
|
|
680
|
-
"memory",
|
|
681
|
-
memory_diff,
|
|
682
|
-
{"server": server.config.name, "phase": "post_mcp_call"},
|
|
683
|
-
)
|
|
684
|
-
|
|
685
|
-
async def on_connect(
|
|
686
|
-
self, server: FlockMCPServer, additional_params: dict[str, Any]
|
|
687
|
-
) -> dict[str, Any]:
|
|
688
|
-
"""Collect metrics during connect."""
|
|
689
|
-
# We should track the refresh rate for clients
|
|
690
|
-
if "refresh_client" in additional_params and additional_params.get(
|
|
691
|
-
"refresh_client", False
|
|
692
|
-
):
|
|
693
|
-
self._client_refreshs += 1
|
|
694
|
-
self._record_metric(
|
|
695
|
-
"client_refreshs",
|
|
696
|
-
self._client_refreshs,
|
|
697
|
-
{"server": server.config.name, "phase": "connect"},
|
|
698
|
-
)
|
|
699
|
-
|
|
700
|
-
return additional_params
|