feat: Grafana copy publishing, dashboard examples, and optional OTel metrics
Implements three observability improvements: #82 — publish_grafana_copy(): Uses SQLite online backup API (WAL-safe) to atomically publish a consistent read-only copy beside the target. Adds --publish-copy option to grafana-schema and snapshot CLI commands. #83 — examples/grafana/: Minimal working Grafana setup with docker-compose, provisioning datasource/dashboard YAML, and three dashboard JSON files (mt5cli-overview, mt5cli-trades, mt5cli-market). All queries use grafana_* views; no credentials or private paths included. #84 — mt5cli/telemetry.py: Optional OTel metrics behind mt5cli[otel] extra. Base install is unaffected. Adds _Mt5Metrics singleton (no-op until configure_metrics() is called), wraps update_history() and update_observability() with record_history_update / record_snapshot_update context managers, and emits account/position gauges from snapshots. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
d27da02f3f
commit
4c2e25af8a
@@ -3120,3 +3120,62 @@ class TestUpdateObservability:
|
||||
call_kwargs = spy.call_args.kwargs
|
||||
assert call_kwargs["symbols"] == ["EURUSD"]
|
||||
assert call_kwargs["include_account"] is False
|
||||
|
||||
def test_update_observability_invokes_snapshot_telemetry(
|
||||
self,
|
||||
mock_client: MagicMock,
|
||||
mocker: MockerFixture,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""update_observability calls record_snapshot_update on the global metrics."""
|
||||
mock_metrics = MagicMock()
|
||||
mock_cm = MagicMock()
|
||||
mock_cm.__enter__ = MagicMock(return_value=None)
|
||||
mock_cm.__exit__ = MagicMock(return_value=False)
|
||||
mock_metrics.record_snapshot_update.return_value = mock_cm
|
||||
mocker.patch("mt5cli.sdk.get_metrics", return_value=mock_metrics)
|
||||
update_observability(client=mock_client, output=tmp_path / "obs.db")
|
||||
mock_metrics.record_snapshot_update.assert_called_once()
|
||||
|
||||
def test_update_observability_emits_account_metrics(
|
||||
self,
|
||||
mock_client: MagicMock,
|
||||
mocker: MockerFixture,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""_snapshot_account emits account gauges via get_metrics."""
|
||||
mock_metrics = MagicMock()
|
||||
mock_cm = MagicMock()
|
||||
mock_cm.__enter__ = MagicMock(return_value=None)
|
||||
mock_cm.__exit__ = MagicMock(return_value=False)
|
||||
mock_metrics.record_snapshot_update.return_value = mock_cm
|
||||
mocker.patch("mt5cli.sdk.get_metrics", return_value=mock_metrics)
|
||||
update_observability(client=mock_client, output=tmp_path / "obs.db")
|
||||
mock_metrics.record_account_state.assert_called_once()
|
||||
|
||||
|
||||
class TestUpdateHistoryTelemetry:
|
||||
"""Tests for telemetry hooks in update_history."""
|
||||
|
||||
def test_update_history_invokes_history_telemetry(
|
||||
self,
|
||||
mocker: MockerFixture,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""update_history wraps write_incremental_datasets with telemetry."""
|
||||
mock_client = MagicMock()
|
||||
mock_client.copy_rates_range_as_df.return_value = pd.DataFrame()
|
||||
mock_client.history_orders_get_as_df.return_value = pd.DataFrame()
|
||||
mock_client.history_deals_get_as_df.return_value = pd.DataFrame()
|
||||
mock_metrics = MagicMock()
|
||||
mock_cm = MagicMock()
|
||||
mock_cm.__enter__ = MagicMock(return_value=None)
|
||||
mock_cm.__exit__ = MagicMock(return_value=False)
|
||||
mock_metrics.record_history_update.return_value = mock_cm
|
||||
mocker.patch("mt5cli.sdk.get_metrics", return_value=mock_metrics)
|
||||
update_history(
|
||||
client=mock_client,
|
||||
output=tmp_path / "hist.db",
|
||||
symbols=["EURUSD"],
|
||||
)
|
||||
mock_metrics.record_history_update.assert_called_once_with(dataset="history")
|
||||
|
||||
Reference in New Issue
Block a user