Skip to content

Layer extension: register custom layers without modifying OAP source#13856

Merged
wu-sheng merged 3 commits intomasterfrom
feature/layer-extension
Apr 30, 2026
Merged

Layer extension: register custom layers without modifying OAP source#13856
wu-sheng merged 3 commits intomasterfrom
feature/layer-extension

Conversation

@wu-sheng
Copy link
Copy Markdown
Member

Custom Layers without modifying the OAP source

Layer becomes a registry-backed value type with the same public API as the prior enum (Layer.SERVICE, value(), name(), valueOf(int), valueOf(String), nameOf(String), values()). Built-in layers stay as public static final Layer constants with frozen ordinals 0–49.

External layers can register through any of three new paths, all funneling through Layer.register(name, ordinal, normal):

  • layer-extensions.yml on the OAP classpath / config dir — operator config, one line per layer, no code change.
  • LayerExtension Java SPI under META-INF/services/ — for plugin jars.
  • Inline layerDefinitions: block at the top of any MAL or LAL rule file — the cleanest path when a layer ships together with the rules that produce its telemetry.

Storage encoding is unchanged (ordinal int in BanyanDB / ES / JDBC). The registry is sealed at the start of Core.notifyAfterCompleted() after every module's prepare/start has run; runtime-rule MAL/LAL /addOrUpdate requests now reject inline layerDefinitions: with a clear error pointing operators at the boot-time registration paths.

UITemplateInitializer no longer iterates Layer.values() for folder discovery — it walks ui-initialized-templates/**/*.json recursively and trusts each template's own configuration.layer, so dashboards for external layers are auto-discovered with no code change.

Built-in ordinals 0–49 stay frozen; 50–999 are reserved by convention for future built-ins; external layers are recommended (not required) to start at >= 1000. Collisions are reported loudly at boot via the ordinal-uniqueness check.

  • If this is non-trivial feature, paste the links/URLs to the design doc.

  • Update the documentation to include this new feature.

  • Tests(including UT, IT, E2E) are added to verify the new feature.

  • 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.

@wu-sheng wu-sheng requested a review from wankai123 April 30, 2026 08:54
@wu-sheng wu-sheng added this to the 10.5.0 milestone Apr 30, 2026
@wu-sheng wu-sheng added enhancement Enhancement on performance or codes chore Chores about the project, like code cleaning up, typos, upgrading dependencies, etc. core feature Core and important feature. Sometimes, break backwards compatibility. and removed chore Chores about the project, like code cleaning up, typos, upgrading dependencies, etc. labels Apr 30, 2026
@wu-sheng wu-sheng force-pushed the feature/layer-extension branch 2 times, most recently from 18ac7f6 to 3035228 Compare April 30, 2026 08:55
`Layer` becomes a registry-backed value type with the same public API as
the prior enum (`Layer.SERVICE`, `value()`, `name()`, `valueOf(int)`,
`valueOf(String)`, `nameOf(String)`, `values()`). Built-in layers stay as
`public static final Layer` constants with frozen ordinals 0–49.

External layers can be registered through any of three new paths, all
funneling through `Layer.register(name, ordinal, normal)`:

  - `layer-extensions.yml` on the OAP classpath / config dir — operator
    config; one line per layer, no code change required.
  - `LayerExtension` Java SPI under `META-INF/services/` — for plugin jars.
  - Inline `layerDefinitions:` block at the top of any MAL or LAL rule
    file — the cleanest path when a layer ships together with the rules
    that produce its telemetry.

Storage encoding is unchanged: layers persist by ordinal int in BanyanDB,
Elasticsearch, and JDBC. Built-in ordinals 0–49 stay frozen; 50–999 are
reserved by convention for future built-ins; external layers are
recommended to start at >= 1000. The recommendation is informational —
collisions are reported loudly at boot via the ordinal-uniqueness check.

The registry is sealed at the start of `Core.notifyAfterCompleted()` after
every module's prepare/start has run. Subsequent `Layer.register` calls
throw, so runtime-rule MAL/LAL `/addOrUpdate` requests now reject inline
`layerDefinitions:` with a clear error pointing operators at the boot-time
registration paths.

`UITemplateInitializer` no longer iterates `Layer.values()` for folder
discovery; it walks `ui-initialized-templates/**/*.json` recursively and
trusts each template's own `configuration.layer` field, so dashboards for
external layers are auto-discovered without code changes.

`Layer.values()` now throws before seal — pre-seal the registry may still
grow, and a partial snapshot would silently mislead callers. The cached
sorted snapshot is computed once at seal so post-seal calls are O(1) plus
an array clone.

BanyanDB `MetadataRegistry.parseTagSpec` previously relied on
`Class.isEnum()` to map Layer columns to TAG_TYPE_INT; with `Layer` no
longer an enum, that path now treats `Layer.class` explicitly, matching
the existing ES / JDBC handling.
- LayerExtension.java: javadoc inside `<pre>{@code ... @OverRide ...}</pre>`
  blew up because `{@code}` parses `@Override` as a nested unknown tag.
  Drop the `{@code}` wrapper and use HTML-escaped `&#64;Override` inside
  plain `<pre>`.

- LALBlockCodegen.generateFieldToOutput: previously detected enum-typed
  setters via `paramType.isEnum()` and emitted `Type.valueOf(string)` to
  cast LAL extractor `layer "MYSQL"` (a String literal) into the typed
  setter argument. With Layer no longer an enum, the `isEnum()` branch
  was missed and codegen tried to pass `String` to `setLayer(Layer)`,
  failing at compile-time. Add `paramType == Layer.class` to the same
  branch — Layer's registry-backed `valueOf(String)` keeps the call
  shape identical.
@wu-sheng wu-sheng force-pushed the feature/layer-extension branch from 649abce to aa6831e Compare April 30, 2026 11:36
…AL/LAL inline

Four new test classes covering each of the registration paths added by this PR. All
tests use partitioned ordinal ranges and idempotent re-registration so they share the
process-wide Layer registry safely without calling Layer.seal() (which would taint
sibling tests in the same surefire fork).

  - LayerTest (9 tests, server-core, ordinals 1100-1149) — built-in lookups, valueOf
    by name and ordinal, idempotent re-registration, name conflict, ordinal conflict,
    name-shape rejection, external layer lookup parity.

  - LayerExtensionLoaderTest (4 tests, server-core, ordinals 1150-1169) — yaml
    fixture loaded via the new package-private LayerExtensionLoader.loadFromYaml(path)
    overload, missing-file silent path, SPI loaded via a META-INF/services entry on
    the test classpath registering TestSpiLayerExtension, and idempotent reload.

  - RulesLayerDefinitionsTest (2 tests, meter-analyzer, ordinals 1200-1209) — MAL
    rule file with a top-level layerDefinitions: block; verifies layers register
    before the rule's expressions parse, and reload is idempotent.

  - LALConfigsLayerDefinitionsTest (2 tests, log-analyzer, ordinals 1300-1309) —
    same shape for LAL.

LayerExtensionLoader.loadFromYaml and loadFromSpi were narrowed from private to
package-private so tests can drive each path independently of the other.
@wu-sheng wu-sheng merged commit 2b745b2 into master Apr 30, 2026
646 of 654 checks passed
@wu-sheng wu-sheng deleted the feature/layer-extension branch April 30, 2026 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core feature Core and important feature. Sometimes, break backwards compatibility. enhancement Enhancement on performance or codes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants