Support runtime rule hot-update and DSL debugging for meter-analyzer-config - #13969
Merged
Merged
Conversation
…config Bring the meter-analyzer-config catalog to parity with otel-rules. Native meter (MeterReportService) rules now load through the same Rules/Rule pipeline otel-rules uses, so they participate in RuleSetMerger, are recorded in StaticRuleRegistry, support the optional layerDefinitions block, and generate source-named expression classes instead of falling back to MalExpr_<N>. MeterProcessService implements MalConverterRegistry and publishes MalStaticBindingHook at boot, so a meter rule can be added, overridden or inactivated at runtime, and attached to a DSL debug session, without restarting the OAP. The internal MeterConfig / MeterConfigs model is removed in favour of the shared one. AnalyzerModuleProvider now declares StorageModule, matching every other Rules.loadRules caller: meter rule loading moved to start() because the merge consults the runtime-rule DB override resolver, which needs a live storage module. Behaviour change: an entry in meterAnalyzerActiveFiles (SW_METER_ANALYZER_ACTIVE_FILES) with no matching rule file now fails OAP startup instead of being silently ignored, matching otel-rules. Also removes the checkstyle.skip exemption from library-pprof-parser - the only module in the repo carrying one - and fixes the two violations it was hiding.
There was a problem hiding this comment.
Pull request overview
Adds runtime hot-update and DSL debugging support for native meter analyzer rules.
Changes:
- Unifies meter rule loading with the shared MAL pipeline.
- Adds hot-swappable converter registration and debugging bindings.
- Adds documentation, unit tests, and an end-to-end workflow.
Reviewed changes
Copilot reviewed 36 out of 36 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/skywalking.yaml |
Adds the meter runtime-rule E2E job. |
docs/en/changes/changes.md |
Records the feature and behavior change. |
docs/en/concepts-and-designs/runtime-rule-hot-update.md |
Adds the meter catalog to MAL scope. |
docs/en/setup/backend/admin-api/dsl-debugging-mal.md |
Documents the meter MAL catalog. |
docs/en/setup/backend/admin-api/dsl-debugging.md |
Updates the MAL catalog list. |
docs/en/setup/backend/admin-api/runtime-rule.md |
Documents runtime-rule support. |
docs/en/setup/backend/backend-meter.md |
Documents hot updates, debugging, and validation. |
oap-server/analyzer/agent-analyzer/.../AnalyzerModule.java |
Exposes MalConverterRegistry. |
oap-server/analyzer/agent-analyzer/.../AnalyzerModuleProvider.java |
Loads shared rules and registers the converter service. |
oap-server/analyzer/agent-analyzer/.../MeterConfig.java |
Removes the former meter rule model. |
oap-server/analyzer/agent-analyzer/.../MeterConfigs.java |
Removes the former meter loader. |
oap-server/analyzer/agent-analyzer/.../MeterProcessService.java |
Implements hot-swappable converter storage. |
oap-server/analyzer/agent-analyzer/.../MeterProcessor.java |
Reads the current converter snapshot per batch. |
oap-server/analyzer/agent-analyzer/.../MeterProcessServiceConverterRegistryTest.java |
Tests converter replacement and removal. |
oap-server/analyzer/agent-analyzer/.../MeterProcessorTest.java |
Uses the shared rule loader. |
oap-server/server-admin/dsl-debugging/.../MALDebugRecorderFactory.java |
Accepts meter debug keys. |
oap-server/server-admin/dsl-debugging/.../MALHolderRegistry.java |
Registers meter debug holders. |
oap-server/server-admin/runtime-rule/.../MalFileApplier.java |
Documents meter catalog application. |
oap-server/server-admin/runtime-rule/.../MalRuleEngine.java |
Routes meter rules to agent analyzer. |
oap-server/server-admin/runtime-rule/.../RuleEngine.java |
Updates supported-catalog documentation. |
oap-server/server-admin/runtime-rule/.../DbOverrideRuntimeRuleResolver.java |
Parses meter layer definitions. |
oap-server/server-admin/runtime-rule/.../RuntimeRuleModuleProvider.java |
Updates engine-routing documentation. |
oap-server/server-admin/runtime-rule/.../RuntimeRuleService.java |
Updates catalog scope documentation. |
oap-server/server-admin/runtime-rule/.../runtime-rule-cluster.proto |
Updates the cluster catalog list. |
oap-server/server-core/.../Catalog.java |
Adds the meter catalog enum. |
oap-server/server-core/.../RuleKey.java |
Documents meter debug keys. |
oap-server/server-core/.../RuntimeRule.java |
Documents meter runtime rows. |
oap-server/server-library/library-pprof-parser/pom.xml |
Enables checkstyle for the module. |
oap-server/server-library/library-pprof-parser/.../FrameTreeBuilder.java |
Fixes checkstyle violations. |
test/e2e-v2/cases/runtime-rule/meter/bundled/batch-meter.yaml |
Defines the bundled test rule. |
test/e2e-v2/cases/runtime-rule/meter/docker-compose.yml |
Configures the E2E services. |
test/e2e-v2/cases/runtime-rule/meter/e2e.yaml |
Defines setup, execution, and log collection. |
test/e2e-v2/cases/runtime-rule/meter/expected/ok.txt |
Defines the verification output. |
test/e2e-v2/cases/runtime-rule/meter/meter-runtime-rule-flow.sh |
Exercises add, edit, debug, and inactivate flows. |
test/e2e-v2/cases/runtime-rule/meter/seed-rules/meter-v1.yaml |
Defines the initial runtime rule. |
test/e2e-v2/cases/runtime-rule/meter/seed-rules/meter-v2.yaml |
Defines the structural update. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The row's ACTIVE status is not a synchronisation point for an apply. A STRUCTURAL addOrUpdate returns immediately at FENCING with an applyId while the schema fence and peer roll-out finish in the background, and on an edit the row is already ACTIVE from the previous apply - so polling for ACTIVE returned instantly and proved nothing, and could never surface FAILED or DEGRADED. Adopt the await_apply_terminal helper the otel mal-storage flow uses: poll GET /runtime/rule/status by applyId until APPLIED/DEGRADED, fail on FAILED. Also re-baseline the v1 metric AFTER the edit reaches a terminal phase. The previous baseline was captured before the edit, so the "v1 still flowing" assertion could be satisfied by buckets the OLD converter emitted while the structural apply was still fencing - proving the old converter was alive rather than that the replacement kept v1's metric registered. Raise the case timeout to 50m to keep every internal budget tripping before the harness deadline: a harness timeout cancels the job, and upload-artifact: if failure() does not run on cancellation, so a timeout kill yields no logs to debug from.
CI showed phase 1 failing with "bundled batch-meter is NOT visible in runtime-rule list". The feature is fine - the OAP log confirms the boot resolver ran for meter-analyzer-config, StaticRuleLoader recorded the bundled rules, and MalRuleEngine completed a bundled fall-over for batch-meter. The assertion was simply pointed at the wrong endpoint. /list's bundled branch only walks snapshot entries carrying a non-null DSLRuntimeState, and MalRuleEngine.installBundled deliberately resets state to null after a bundled fall-over so the next gone-keys pass skips it as an untouched bundled-only entry. A fall-over'd bundled rule is therefore legitimately absent from /list. GET /runtime/rule/bundled reads StaticRuleRegistry directly, which is exactly the registration this change adds, so it is both the correct probe and still a genuine regression guard - before this change the meter catalog was empty there. Also poll rather than assert once: agent-analyzer now declares StorageModule and so starts late in the boot sequence, meaning the admin API can answer before its static rules are recorded.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.
Suppressed comments (2)
test/e2e-v2/cases/runtime-rule/meter/meter-runtime-rule-flow.sh:310
add_respcomes from the pinnedswctl, whoseApplyResultmodel only retainsapplyStatus,catalog,name, andmessage; it drops the server'sapplyIdbefore rendering JSON. Consequently this is always empty and every structural add/edit is treated as synchronous, so the intended 200-second fence wait never runs (and the subsequent 150-second metric wait can expire before the server's 180-second fence). Submit these two add/update requests with a client that preserves the raw response, or update the pinned CLI model to exposeapplyId, before polling it here.
oap-server/server-admin/runtime-rule/src/main/java/org/apache/skywalking/oap/server/receiver/runtimerule/engine/mal/MalRuleEngine.java:164- This new lookup makes
agent-analyzera functional dependency, butRuntimeRuleModuleProvider.requiredModules()does not declare it. If that module is absent,resolveConverterRegistryswallows the lookup failure and the API can report a successful meter-rule apply while no ingest path receives the converter. Declareagent-analyzerinrequiredModules()(as this provider already does for its other mandatory lookups atRuntimeRuleModuleProvider.java:459-489) so bootstrap orders and validates the dependency instead of silently degrading.
case "meter-analyzer-config":
// String literal keeps agent-analyzer out of runtime-rule's pom. The registry
// is MeterProcessService, which serves both the native meter gRPC receiver and
// the Kafka meter fetcher — one registry covers both ingest paths.
moduleName = "agent-analyzer";
break;
CI got through phases 0-4 (bundled visibility via /runtime/rule/bundled, hot-add, structural edit with converter replacement, and a DSL debug session binding a bundled meter rule) and then failed phase 5 with HTTP 503 on /inactivate. That is the cluster routing layer's transient cluster_not_ready while its peer refresh is in flight. The otel catalog's flow already documents it as happening reliably right after a STRUCTURAL apply, and phase 3 is exactly that, so phase 5 lands inside the window. Adopt the same retry_admin treatment for the three mutating calls. Unlike the otel helper this keeps stdout clean - stderr is captured to a temp file instead of being folded in with 2>&1 - because the add/edit responses are piped into jq to read .applyId, and a merged stderr line would corrupt that JSON and silently reduce await_apply_terminal to a no-op. Both streams are scanned for the 503 marker so a change in where swctl writes its fatal envelope cannot quietly disable the retry.
wankai123
approved these changes
Aug 7, 2026
wu-sheng
added a commit
to apache/skywalking-cli
that referenced
this pull request
Aug 8, 2026
…dsl-debug (#233) apache/skywalking#13969 brings native meter (MeterReportService) rules to parity with otel-rules: they now load through the shared Rules pipeline, so a meter rule can be hot-added, overridden or inactivated at runtime and attached to a MAL DSL debug session. On the wire that is purely additive — one more value in the shared `Catalog` enum (`METER_ANALYZER_CONFIG`). No new endpoints, no new request or response fields. swctl therefore needs no functional change: `--catalog` is passed through verbatim, and pkg/admin/runtimerule only ever calls the canonical `/runtime/rule/...` routes, never the per-catalog shortcut routes that `meter-analyzer-config` deliberately lacks. The OAP-side e2e added in that commit already drives the whole flow (bundled / add / edit / dsl-debug / inactivate) through `swctl admin ...` against this catalog. What was stale was discoverability, so: - add `meter-analyzer-config` to the `--catalog` flag help of `admin runtime-rule` and `admin dsl-debug session start`, and to the `admin runtime-rule` catalog list. - drop the two unreferenced `Catalogs` vars in pkg/admin/{runtimerule,dsldebug}. Nothing read them — they were a second copy of the catalog list, free to drift out of sync with OAP's enum, and this change is exactly the drift they would have caused. The rendered help text is now the single place the list lives; widening it is not a client-side allow-list, so a newer OAP catalog keeps working without a CLI release.
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.
Support runtime rule hot-update and DSL debugging for the
meter-analyzer-configcatalogIf this is non-trivial feature, paste the links/URLs to the design doc.
Update the documentation to include this new feature.
docs/en/setup/backend/backend-meter.md,docs/en/concepts-and-designs/runtime-rule-hot-update.md,docs/en/setup/backend/admin-api/runtime-rule.md,docs/en/setup/backend/admin-api/dsl-debugging.md,docs/en/setup/backend/admin-api/dsl-debugging-mal.mdTests(including UT, IT, E2E) are added to verify the new feature.
MeterProcessServiceConverterRegistryTest;MeterProcessorTestretargeted onto the shared loader.test/e2e-v2/cases/runtime-rule/meter/(readiness + 5 capability phases) plus a CI matrix entry.If it's UI related, attach the screenshots below.
If this pull request closes/resolves/fixes an existing issue, replace the issue number. Closes #.
Update the
CHANGESlog.Target
Give the
meter-analyzer-configcatalog the same debugging context and hot-updatelifecycle
otel-rulesalready has.otel-rulesbehaviour is the reference and doesnot change; where the meter path fell short, the meter path moved.
Rules.parseRulesetsnamegetSourceName()null →MalExpr_<N>MalStaticBindingHook.publishCatalogentry →RuleKeyresolvesOTEL_RULESMETER_ANALYZER_CONFIGRuleSetMerger/StaticRuleRegistryMalConverterRegistryList<MetricConvert>MalConverterRegistryPrinciple
Same management, same compiler, same runtime — only the inputs differ. Rather than
teach
MeterConfigeach capability one at a time, meter rules now load through theidentical
Rules.loadRules+Rulemodel otel uses, andMeterConfig/MeterConfigsare deleted. One MAL rule model repo-wide, so this divergence cannot silently return.
No compiler changes (
meter-analyzer/.../compiler/**,.../compiler/rt/**,.../dsl/**untouched). No MAL runtime changes.Out of scope, noted for a future PR:
MetricConvert.java:135derives the class-nameL<n>segment from the rules-list index, not the YAML line, so production emitsvm_L0_cpu_total_percentagewhile the test harness emitsvm_L37_…. Real, but itaffects every MAL catalog and is not this change's agenda.
Scope
Catalog registration —
Catalog,MalRuleEngine(CATALOGS+resolveConverterRegistry→agent-analyzer),DbOverrideRuntimeRuleResolver,MALHolderRegistry,MALDebugRecorderFactory, javadoc/proto catalog lists.agent-analyzer —
MeterProcessServiceimplementsMalConverterRegistry(volatile copy-on-write map, same shape as the otel receiver) and publishes
MalStaticBindingHook; registered onAnalyzerModuleunder both contracts; ruleloading moved
prepare()→start()so the override resolver sees a live storagemodule;
MeterProcessorre-readsconverts()per batch, so a hot-add lands on thenext batch with no restart.
Tests —
MeterProcessorTestretargeted (3 lines, assertions unchanged); newMeterProcessServiceConverterRegistryTestmirroring the otel registry contract test.E2E —
test/e2e-v2/cases/runtime-rule/meter/+ one CI matrix entry. Six phases,each guarding one thing this PR changes:
0. admin API reachable;
batch-meter.yamlappears inruntime-rule list(proves
StaticRuleRegistryparticipation; fails on master);(catalog, name)with an extra metric registers thenew metric and keeps the already-registered one flowing (converter replacement);
verbatim DSL, real samples and a terminal
meterEmit(proves the MAL catalogpredicates plus
MeterProcessService's boot-timeMalStaticBindingHookpublish);INACTIVE, its metrics stop producing newbuckets, bundled keeps producing (converter removal).
Freshness: every "flowing / stopped" assertion diffs the set of time-bucket ids carrying
a non-null value against a baseline taken at the transition, so a metric cannot pass on
stale pre-transition buckets and a stopped metric cannot be masked by history. Bucket-id
deltas are used instead of absolute
--start/--endso the assertions are immune tohost-vs-container clock/timezone skew.
Failure vs. absence: the bucket-reading helper returns a non-zero status for transport
errors, non-JSON bodies and malformed envelopes, which is deliberately distinct from a
successful query that found no data. The negative ("has it stopped") assertion counts
only successful observations and refuses to conclude anything below a floor of 3, so a
broken query path fails the test instead of silently satisfying it. No existing e2e case
was modified.
Docs — 5 docs + changelog.
Drive-by fix —
library-pprof-parserwas the only module in the repo with<checkstyle.skip>true</checkstyle.skip>, hiding 2 real violations. Removed theexemption and fixed the violations; full-repo checkstyle is clean.
Behaviour change
An entry in
meterAnalyzerActiveFiles/SW_METER_ANALYZER_ACTIVE_FILESwith nomatching rule file now fails OAP startup instead of being silently ignored. The
list is operator-supplied, so the files must exist; this also matches how
otel-ruleshas always behaved. Documented in
backend-meter.mdand the changelog.Verification
Passing locally: compile, full-repo
checkstyle:check,license-eye(0 invalid /4969 files),
MeterProcessorTest+MeterProcessServiceConverterRegistryTest(4/4),docker compose config(mounts and the 3-level relative jar path resolve),bash -n,and the bucket-delta helper unit-tested offline across 5 cases — including the critical
"stale buckets only must NOT count as fresh".
Not run locally: the e2e case itself — needs a built OAP image and a full compose
cycle. Static validation only; watch it in CI.
Review
A multi-agent adversarial review over the whole change set raised 15 candidate findings;
12 were refuted on inspection, 3 confirmed and fixed here:
StorageModuledependency (real bug, introduced by this PR). Meter ruleloading moved to
start()precisely becauseRuleSetMergerconsults the runtime-ruleDB override resolver — but
AnalyzerModuleProvider.requiredModules()never declaredStorageModule. A boot-order simulation over all 40 default-enabled modules putagent-analyzerat sequence 6 andstorageat 13, soRuntimeRuleManagementDAO.getAll()would throw "management schema not registered yet", the resolver would log a WARN and
return empty, and an operator's meter-rule override / inactivate would be ignored until
the reconciler's next tick. Every other
Rules.loadRulescaller (otel, telegraf, envoy,log-analyzer) already declares it. Fixed, with the same rationale comment; no cycle
(storage requires only
CoreModule).addOrUpdate; the apply is async (durable commit returns before the schema fence rollsout). Now polls to
ACTIVE, asdsl-debugging/maldoes — and likewise for the phase-3edit and the phase-5 inactivate.
~27.5m inside a 40m case timeout. This matters beyond tidiness: a harness timeout
cancels the job, and
upload-artifact: if failure()does not run on cancellation, soa timeout kill yields no logs. Internal budgets must always trip first.
Follow-up (separate repo)
skywalking-clihelp text lists only 4 catalogs. TheCatalogsvars are declared butnever referenced, so
--catalog meter-analyzer-configalready works — this iscosmetic:
pkg/admin/runtimerule/runtimerule.go:35,pkg/admin/dsldebug/dsldebug.go:40,internal/commands/admin/runtimerule/runtimerule.go:39,54,internal/commands/admin/dsldebug/dsldebug.go:89.