Skip to content

Support runtime rule hot-update and DSL debugging for meter-analyzer-config - #13969

Merged
wu-sheng merged 4 commits into
masterfrom
feat/meter-analyzer-config-runtime-rule-catalog
Aug 7, 2026
Merged

Support runtime rule hot-update and DSL debugging for meter-analyzer-config#13969
wu-sheng merged 4 commits into
masterfrom
feat/meter-analyzer-config-runtime-rule-catalog

Conversation

@wu-sheng

@wu-sheng wu-sheng commented Aug 6, 2026

Copy link
Copy Markdown
Member

Support runtime rule hot-update and DSL debugging for the meter-analyzer-config catalog

  • If 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.md
  • Tests(including UT, IT, E2E) are added to verify the new feature.

    • UT: new MeterProcessServiceConverterRegistryTest; MeterProcessorTest retargeted onto the shared loader.
    • E2E: new 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 CHANGES log.


Target

Give the meter-analyzer-config catalog the same debugging context and hot-update
lifecycle otel-rules already has. otel-rules behaviour is the reference and does
not change; where the meter path fell short, the meter path moved.

capability otel-rules meter (before) meter (now)
named generated classes Rules.parseRule sets name getSourceName() null → MalExpr_<N> same as otel
DSL-debug session can bind MalStaticBindingHook.publish no publish at all publishes
Catalog entry → RuleKey resolves OTEL_RULES absent METER_ANALYZER_CONFIG
RuleSetMerger / StaticRuleRegistry yes bypassed (raw snakeyaml) yes
hot-swappable converter MalConverterRegistry plain List<MetricConvert> MalConverterRegistry

Principle

Same management, same compiler, same runtime — only the inputs differ. Rather than
teach MeterConfig each capability one at a time, meter rules now load through the
identical Rules.loadRules + Rule model otel uses, and MeterConfig/MeterConfigs
are 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:135 derives the class-name
L<n> segment from the rules-list index, not the YAML line, so production emits
vm_L0_cpu_total_percentage while the test harness emits vm_L37_…. Real, but it
affects every MAL catalog and is not this change's agenda.

Scope

  • Catalog registrationCatalog, MalRuleEngine (CATALOGS +
    resolveConverterRegistryagent-analyzer), DbOverrideRuntimeRuleResolver,
    MALHolderRegistry, MALDebugRecorderFactory, javadoc/proto catalog lists.

  • agent-analyzerMeterProcessService implements MalConverterRegistry
    (volatile copy-on-write map, same shape as the otel receiver) and publishes
    MalStaticBindingHook; registered on AnalyzerModule under both contracts; rule
    loading moved prepare()start() so the override resolver sees a live storage
    module; MeterProcessor re-reads converts() per batch, so a hot-add lands on the
    next batch with no restart.

  • TestsMeterProcessorTest retargeted (3 lines, assertions unchanged); new
    MeterProcessServiceConverterRegistryTest mirroring the otel registry contract test.

  • E2Etest/e2e-v2/cases/runtime-rule/meter/ + one CI matrix entry. Six phases,
    each guarding one thing this PR changes:
    0. admin API reachable;

    1. bundled visibility — shipped batch-meter.yaml appears in runtime-rule list
      (proves StaticRuleRegistry participation; fails on master);
    2. hot add — a twin-less runtime rule becomes queryable with no restart;
    3. edit — re-applying the same (catalog, name) with an extra metric registers the
      new metric and keeps the already-registered one flowing (converter replacement);
    4. DSL debug — a session binds a bundled meter rule and captures records carrying
      verbatim DSL, real samples and a terminal meterEmit (proves the MAL catalog
      predicates plus MeterProcessService's boot-time MalStaticBindingHook publish);
    5. inactivate — row goes explicitly INACTIVE, its metrics stop producing new
      buckets, 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/--end so the assertions are immune to
    host-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 fixlibrary-pprof-parser was the only module in the repo with
    <checkstyle.skip>true</checkstyle.skip>, hiding 2 real violations. Removed the
    exemption and fixed the violations; full-repo checkstyle is clean.

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. The
list is operator-supplied, so the files must exist; this also matches how otel-rules
has always behaved. Documented in backend-meter.md and 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:

  • Missing StorageModule dependency (real bug, introduced by this PR). Meter rule
    loading moved to start() precisely because RuleSetMerger consults the runtime-rule
    DB override resolver — but AnalyzerModuleProvider.requiredModules() never declared
    StorageModule. A boot-order simulation over all 40 default-enabled modules put
    agent-analyzer at sequence 6 and storage at 13, so RuntimeRuleManagementDAO.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.loadRules caller (otel, telegraf, envoy,
    log-analyzer) already declares it. Fixed, with the same rationale comment; no cycle
    (storage requires only CoreModule).
  • E2E raced the async apply. Phase 2 read rule status once immediately after
    addOrUpdate; the apply is async (durable commit returns before the schema fence rolls
    out). Now polls to ACTIVE, as dsl-debugging/mal does — and likewise for the phase-3
    edit and the phase-5 inactivate.
  • Budget vs. timeout. Trimmed internal budgets so the pathological worst case is
    ~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, so
    a timeout kill yields no logs. Internal budgets must always trip first.

Follow-up (separate repo)

skywalking-cli help text lists only 4 catalogs. The Catalogs vars are declared but
never referenced, so --catalog meter-analyzer-config already works — this is
cosmetic: 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.

…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.
@wu-sheng wu-sheng added the enhancement Enhancement on performance or codes label Aug 6, 2026
@wu-sheng wu-sheng added this to the 11.0.0 milestone Aug 6, 2026
@wu-sheng wu-sheng added the backend OAP backend related. label Aug 6, 2026
@wu-sheng
wu-sheng requested a balanced review from Copilot August 6, 2026 23:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread test/e2e-v2/cases/runtime-rule/meter/meter-runtime-rule-flow.sh Outdated
Comment thread test/e2e-v2/cases/runtime-rule/meter/meter-runtime-rule-flow.sh Outdated
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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_resp comes from the pinned swctl, whose ApplyResult model only retains applyStatus, catalog, name, and message; it drops the server's applyId before 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 expose applyId, 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-analyzer a functional dependency, but RuntimeRuleModuleProvider.requiredModules() does not declare it. If that module is absent, resolveConverterRegistry swallows the lookup failure and the API can report a successful meter-rule apply while no ingest path receives the converter. Declare agent-analyzer in requiredModules() (as this provider already does for its other mandatory lookups at RuntimeRuleModuleProvider.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.
@wu-sheng
wu-sheng merged commit c59f7b4 into master Aug 7, 2026
454 of 457 checks passed
@wu-sheng
wu-sheng deleted the feat/meter-analyzer-config-runtime-rule-catalog branch August 7, 2026 03:48
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend OAP backend related. enhancement Enhancement on performance or codes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants