feat: Metrics service unifying model-performance stats across v11 and v12#1411
Merged
Conversation
… and v12 Add `tm1.metrics`, exposing TM1 model-performance statistics with one method per Stats Category (by_cube, by_server, by_rule, by_client, by_cube_by_client, by_chore, by_process) that returns the same shape regardless of TM1 version. - v11: reads the }Stats* control cubes via MDX/cellset. - v12: reads the Metrics() OData endpoint plus the cube-bound rule-stats actions (start/stop/flush_collecting_rule_stats). The v11/v12 branch is hidden inside each method. Gauge categories (by_cube, by_server) return a long Metric/Value/Unit shape; entity categories return a wide one-row-per-entity shape. Canonical metric names follow IBM's v12 vocabulary; units pass through unconverted with the source NativeName retained. Logic lives in deep, server-free modules behind the service: vocabulary (v11->canonical map), mdx (v11 query builder), odata_filter (v12 $filter, extracted from #1396), shapers (raw payload -> records). 44 unit + 12 integration tests. Supersedes #1396 — its Metrics() $filter logic and service wiring are preserved here. Also adds Utils.datetime_to_iso and a max_version bound on TM1pyVersionException (for v11-only categories invoked on v12). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collapse the TM1py/Metrics/ sub-package (vocabulary, mdx, odata_filter, shapers) into TM1py/Services/MetricService.py as module-level helpers, matching the single-flat-file pattern every other service follows. Consolidate the five Metric*_test.py files into one Tests/MetricService_test.py per the one-test-file-per-service convention. Fold usage examples into the service docstring so they render in the auto-generated mkdocs API Reference. Remove ADR/ PRD references from docstrings and comments. No behavior change: 44 unit tests pass; black and ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verified live on PA Database 12.5.9: start/stop/flush_collecting_rule_stats return 204 but flush creates no }StatsByRule cube and exposes no rule-stats OData entity ($metadata defines only the three lifecycle actions; Metrics() carries no rule_* names). The previous claim that flush 'creates }StatsByRule on demand' was false, and by_rule on v12 silently returned [] forever. by_rule now raises NotImplementedError on v12 with a clear message; the v11 Performance-Monitor path is unchanged. Corrected the flush/by_rule docstrings and the class usage example; split the lifecycle/by_rule integration tests to assert the real behavior (lifecycle 204s, no cube created, by_rule raises). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Corrects the prior commit. On PA Database 12.5.9, flush_collected_rule_stats DOES create/populate }StatsByRule — verified live: dims }Cubes x }LineNumber x }RuleStats (identical to v11), and the existing v11 cellset read/shape path returns correct records on v12. The cube appears only after the full sequence: start_collecting_rule_stats -> change/exercise the rules -> wait ~60s sampling interval -> flush_collected_rule_stats. My earlier probes skipped the rule activity and the wait, so the cube hadn't materialized. Restores the unified by_rule (read on both versions; [] + warning when the cube is absent, with a v12-specific hint describing the collection sequence) and the accurate flush docstring. Tests assert by_rule returns the list/shape on both versions and the lifecycle returns 204s (cleanup only deletes }StatsByRule if the test itself created it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
…s, tests) - datetime_to_iso: convert tz-aware datetimes to UTC and preserve sub-second precision to milliseconds (was hard-coded .000Z, ignored tzinfo) - MetricService: wire v12-only `since` Timestamp filter into by_cube/by_server (rejected on v11, symmetric to time_interval on v12) - drop misleading required_version=V12 from v11-only version errors (max_version carries the meaning); document the deliberate split between version-gated reads (TM1pyVersionException) and deprecated perfmon mutators - warn on truncated v11 cellsets (len(Cells) < axis product) instead of silently reading missing cells as None - add context to bare KeyErrors (unknown measure / missing measure dimension) - tests: mocked-REST dispatch + metrics= filtering + by_rule missing-cube warning (G1-G3), entity shapers for by_process/by_chore/by_client (G4), and datetime_to_iso tz/precision coverage Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
|
Tests completed for environment: tm1-12-cloud. Check artifacts for details. |
Contributor
|
Tests completed for environment: tm1-11-cloud. Check artifacts for details. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
tm1.metrics, exposing TM1 model-performance statistics with one method per Stats Category that returns the same shape regardless of the underlying TM1 version:by_cube,by_server— gauge categories (longMetric/Value/Unitrows)by_rule— per-rule-line stats (both versions; same}StatsByRuleread path)by_client,by_cube_by_client,by_chore,by_process— entity categories (v11 only; raise a clear version error on v12)How it works
}Stats*control cubes via MDX/cellset, withLATESTas the default snapshot and an opt-intime_intervalfor the rolling window.Metrics()OData endpoint, plus the cube-bound rule-stats actionsstart_collecting_rule_stats/stop_collecting_rule_stats/flush_collected_rule_stats.The v11/v12 branch is hidden inside each method. Canonical metric names follow IBM's v12 vocabulary verbatim; v11 measures map onto them. Units pass through unconverted, with the source
NativeNameretained for traceability.flowchart TD User(["User code"]) subgraph API["tm1.metrics — unified by_* methods"] Gauge["Gauge: by_cube, by_server"] Entity["Entity: by_rule, by_client, by_cube_by_client, by_chore, by_process"] Lifecycle["Rule-stats lifecycle: start / stop / flush (v12)"] PerfMon["Performance Monitor: start / stop / get state (v11)"] end Dispatch{"version dispatch (self._is_v12)"} subgraph Helpers["Pure helpers in MetricService (server-free)"] Vocabulary["vocabulary — v11 to canonical map"] MDX["MDX builders — v11 query builder"] ODataFilter["filter builder — v12 $filter builder"] Shapers["shapers — raw payload to records"] end V11[("TM1 v11: Stats control cubes (MDX / cellset)")] V12[("TM1 v12: Metrics() OData + rule-stats actions")] Records["Unified records: list of dict / DataFrame"] User --> API API --> Dispatch Dispatch -->|v11| MDX Dispatch -->|v12| ODataFilter MDX --> V11 ODataFilter --> V12 V11 -->|raw cellset| Shapers V12 -->|raw JSON| Shapers Vocabulary -.-> Shapers Shapers --> Records Records --> UserUsage
Per-cube metrics (gauge-long), unified across versions:
The canonical
Metricname is stable across versions;NativeNamecarries the source's own name. Values pass through unconverted — readUnitto interpret them (cube_memory_usedis bytes on v11, KB on v12). On v12 the same call addsDatabaseName/DatabaseIDand populatesTimestamp.As a filtered DataFrame:
Server/replica-level metrics — v12 (highly-available) yields one row per replica; v11 is a single replica (
ReplicaID=0):Per-rule timing — unified across versions.
}StatsByRuleis structurally identical on v11 and v12 (}Cubes×}LineNumber×}RuleStats), so the same read/shape path serves both; only how the cube gets populated differs.On v11 the Performance Monitor populates it, so ensure it is running, then read:
On v12 you collect on demand — start, exercise the cube's rules, wait for the ~60s sampling interval, flush — then read the same way (verified live on 12.5.9: this creates/populates
}StatsByRulewith v11-identical dimensionality):Performance Monitor controls (v11 only —
PerformanceMonitorOnis a v11 configuration parameter, so these raiseTM1pyVersionDeprecationExceptionon v12). They mirror the existingServerServicecontrols and delegate to them, so the whole v11 stats workflow is reachable fromtm1.metrics:v11-only categories (the cubes were removed in v12; these raise
TM1pyVersionExceptionon a v12 database):Every read method has a parallel
*_as_dataframe(...)variant. These examples also live in theMetricServicedocstring, so they render in the auto-generated mkdocs API Reference.Note on extensibility
New v12 gauges surface automatically:
by_cube/by_serverkeep everyMetrics()row matching thecube_*/replica_*prefix and v12 names are already canonical, so no code change is needed when IBM adds a metric. v11 is the asymmetric case — a new}Stats*measure must be added to the vocabulary tables (the MDX only selects known measures, and the normalizer raises on unknowns), because v11 native names are not canonical and must be mapped.Design
MetricServicefollows TM1py's single-flat-file service convention (likeCellService,CubeService, etc.): a thin service over pure, server-free, module-level helpers in the same file —Metric/Unitmapping (single source of truth)}Stats*query builders (gauge + entity)$filterbuilder: v12Metrics()URL builder (extracted from Introduce Metric service (for v12) #1396's inline logic)The Performance Monitor controls live on
ServerService(where the existing start/stop already were, alongside static-config management) and are mirrored as thin delegates onMetricServicefor workflow ergonomics. The pre-existingServerService.start/stop_performance_monitorare now version-guarded too (previously unguarded, they would silently attempt the config write on v12).Testing
44 unit tests (pure helpers, fixture-driven) + integration tests, in a single
Tests/MetricService_test.pyper TM1py's one-test-file-per-service convention; passing against live v11 (11.8) and v12 (12.5.9) servers. Integration coverage includes the Performance Monitor controls: a v11 on/off round-trip (restoring original state), metrics↔server-service agreement, and the v12 deprecation guard.Relationship to #1396
Supersedes #1396. That PR's
Metrics()$filterlogic and service wiring are preserved and extended here; this branch is cut from currentmaster(which #1396 had drifted 10 commits behind). #1396 can be closed once this is reviewed.🤖 Generated with Claude Code