Skip to content

Commit ed5ddaf

Browse files
feat: add performance-monitor controls to Metrics service (v11-only)
Mirror the Performance Monitor on/off/read controls onto MetricService, delegating to ServerService, since the v11 }Stats* read paths depend on the monitor running. - ServerService: add get_performance_monitor_state(); guard start/stop/get with @deprecated_in_version("12.0.0") since PerformanceMonitorOn is a v11-only config parameter (previously start/stop silently ran on v12) - MetricService: thin start_/stop_/get_performance_monitor_state delegates, same v12 guard; update by_rule hint + class docstring example - Tests: v11 toggle round-trip, metrics<->server agreement, v12 raises Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2dced30 commit ed5ddaf

3 files changed

Lines changed: 107 additions & 5 deletions

File tree

TM1py/Services/MetricService.py

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
import itertools
3232
import warnings
3333
from datetime import datetime
34-
from typing import Dict, List, Optional, Tuple
34+
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
3535

3636
from TM1py.Exceptions.Exceptions import TM1pyVersionException
3737
from TM1py.Services.CellService import CellService
@@ -42,12 +42,16 @@
4242
CaseAndSpaceInsensitiveDict,
4343
build_url_friendly_object_name,
4444
datetime_to_iso,
45+
deprecated_in_version,
4546
format_url,
4647
require_pandas,
4748
require_version,
4849
verify_version,
4950
)
5051

52+
if TYPE_CHECKING:
53+
from TM1py.Services.ServerService import ServerService
54+
5155
V12_VERSION = "12.0.0"
5256

5357
CATEGORY_BY_CUBE = "by_cube"
@@ -687,8 +691,11 @@ class MetricService(ObjectService):
687691
688692
Per-rule timing (entity-wide), unified across versions — the read is
689693
identical; only how ``}StatsByRule`` gets populated differs. On v11 the
690-
Performance Monitor populates it, so you just read::
694+
Performance Monitor populates it, so ensure it is running, then read::
691695
696+
>>> tm1.metrics.get_performance_monitor_state()
697+
False
698+
>>> tm1.metrics.start_performance_monitor()
692699
>>> tm1.metrics.by_rule(cube="plan_BudgetPlan")
693700
694701
On v12 collect on demand: start, exercise the cube's rules, let the ~60s
@@ -715,6 +722,7 @@ def __init__(self, rest: RestService):
715722
super().__init__(rest)
716723
self._cells = None
717724
self._cubes = None
725+
self._servers = None
718726

719727
@property
720728
def _cell_service(self) -> CellService:
@@ -728,6 +736,14 @@ def _cube_service(self) -> CubeService:
728736
self._cubes = CubeService(self._rest)
729737
return self._cubes
730738

739+
@property
740+
def _server_service(self) -> "ServerService":
741+
if self._servers is None:
742+
from TM1py.Services.ServerService import ServerService
743+
744+
self._servers = ServerService(self._rest)
745+
return self._servers
746+
731747
@property
732748
def _is_v12(self) -> bool:
733749
return verify_version(required_version=V12_VERSION, version=self.version)
@@ -811,8 +827,8 @@ def by_rule(self, cube: str = None, **kwargs) -> List[Dict]:
811827
)
812828
else:
813829
hint = (
814-
"rule stats have not been collected — ensure Performance Monitor is running "
815-
"(ServerService.start_performance_monitor)"
830+
"rule stats have not been collected — ensure the Performance Monitor is running "
831+
"(start_performance_monitor)"
816832
)
817833
warnings.warn(
818834
f"'{self.STATS_BY_RULE_CUBE}' does not exist; {hint}. Returning no records.",
@@ -861,6 +877,39 @@ def by_cube_by_client(self, cube: str = None, **kwargs) -> List[Dict]:
861877
def by_cube_by_client_as_dataframe(self, *args, **kwargs) -> "pd.DataFrame":
862878
return pd.DataFrame.from_records(self.by_cube_by_client(*args, **kwargs))
863879

880+
# ------------------------------------------------------------------ #
881+
# performance monitor (v11-only: populates the }Stats* cubes while it runs)
882+
# ------------------------------------------------------------------ #
883+
884+
@deprecated_in_version(version="12.0.0")
885+
def start_performance_monitor(self):
886+
"""Turn the Performance Monitor on (v11 only).
887+
888+
While it runs, TM1 populates the ``}Stats*`` cubes that :meth:`by_cube`,
889+
:meth:`by_server`, and :meth:`by_rule` read on v11. ``PerformanceMonitorOn``
890+
is a v11-only setting, so this raises :class:`TM1pyVersionDeprecationException`
891+
on v12. Delegates to :meth:`ServerService.start_performance_monitor`.
892+
"""
893+
return self._server_service.start_performance_monitor()
894+
895+
@deprecated_in_version(version="12.0.0")
896+
def stop_performance_monitor(self):
897+
"""Turn the Performance Monitor off (v11 only).
898+
899+
Raises :class:`TM1pyVersionDeprecationException` on v12. Delegates to
900+
:meth:`ServerService.stop_performance_monitor`.
901+
"""
902+
return self._server_service.stop_performance_monitor()
903+
904+
@deprecated_in_version(version="12.0.0")
905+
def get_performance_monitor_state(self) -> bool:
906+
"""Return whether the Performance Monitor is currently active (v11 only).
907+
908+
Raises :class:`TM1pyVersionDeprecationException` on v12. Delegates to
909+
:meth:`ServerService.get_performance_monitor_state`.
910+
"""
911+
return self._server_service.get_performance_monitor_state()
912+
864913
# ------------------------------------------------------------------ #
865914
# v12 rule-stats collection lifecycle (cube-bound actions)
866915
# ------------------------------------------------------------------ #

TM1py/Services/ServerService.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,14 +254,28 @@ def delete_persistent_feeders(self, **kwargs) -> Response:
254254
process_service = ProcessService(self._rest)
255255
return process_service.execute_ti_code(ti, **kwargs)
256256

257+
@deprecated_in_version(version="12.0.0")
257258
def start_performance_monitor(self):
258259
config = {"Administration": {"PerformanceMonitorOn": True}}
259260
self.configuration.update_static(config)
260261

262+
@deprecated_in_version(version="12.0.0")
261263
def stop_performance_monitor(self):
262264
config = {"Administration": {"PerformanceMonitorOn": False}}
263265
self.configuration.update_static(config)
264266

267+
@deprecated_in_version(version="12.0.0")
268+
def get_performance_monitor_state(self) -> bool:
269+
"""Read whether the Performance Monitor is currently active.
270+
271+
The ``PerformanceMonitorOn`` parameter is a v11-only configuration
272+
setting; this raises :class:`TM1pyVersionDeprecationException` on v12.
273+
274+
:return: True if ``PerformanceMonitorOn`` is enabled, otherwise False
275+
"""
276+
config = self.get_active_configuration()
277+
return bool(config.get("Administration", {}).get("PerformanceMonitorOn", False))
278+
265279
def activate_audit_log(self):
266280
self.audit_logs.activate()
267281

Tests/MetricService_test.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
from pathlib import Path
2020

2121
from TM1py import TM1Service
22-
from TM1py.Exceptions.Exceptions import TM1pyVersionException
22+
from TM1py.Exceptions.Exceptions import (
23+
TM1pyVersionDeprecationException,
24+
TM1pyVersionException,
25+
)
2326
from TM1py.Services.MetricService import (
2427
ALL_TIME_INTERVALS,
2528
CATEGORY_BY_CUBE,
@@ -761,6 +764,42 @@ def test_lifecycle_methods_require_v12(self):
761764
with self.assertRaises(TM1pyVersionException):
762765
tm1.metrics.start_collecting_rule_stats("plan_BudgetPlan")
763766

767+
# ---------------- performance monitor ---------------- #
768+
769+
def test_performance_monitor_toggle_v11(self):
770+
# the metrics service mirrors the ServerService controls; toggling round-
771+
# trips and the state getter reflects each change. Restore the original
772+
# state so the suite leaves the server as it found it.
773+
tm1 = self._v11()
774+
original = tm1.metrics.get_performance_monitor_state()
775+
self.assertIsInstance(original, bool)
776+
try:
777+
tm1.metrics.start_performance_monitor()
778+
self.assertTrue(tm1.metrics.get_performance_monitor_state())
779+
tm1.metrics.stop_performance_monitor()
780+
self.assertFalse(tm1.metrics.get_performance_monitor_state())
781+
finally:
782+
if original:
783+
tm1.metrics.start_performance_monitor()
784+
else:
785+
tm1.metrics.stop_performance_monitor()
786+
787+
def test_performance_monitor_delegates_to_server_service(self):
788+
# metrics and server services must agree on the current state
789+
tm1 = self._v11()
790+
self.assertEqual(
791+
tm1.metrics.get_performance_monitor_state(),
792+
tm1.server.get_performance_monitor_state(),
793+
)
794+
795+
def test_performance_monitor_methods_deprecated_on_v12(self):
796+
# PerformanceMonitorOn is a v11-only setting; the controls must refuse v12
797+
tm1 = self._v12()
798+
for method in ("start_performance_monitor", "stop_performance_monitor", "get_performance_monitor_state"):
799+
with self.subTest(method=method):
800+
with self.assertRaises(TM1pyVersionDeprecationException):
801+
getattr(tm1.metrics, method)()
802+
764803

765804
if __name__ == "__main__":
766805
unittest.main()

0 commit comments

Comments
 (0)