Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/skywalking.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,8 @@ jobs:
- name: Runtime Rule Cluster (kind)
config: test/e2e-v2/cases/runtime-rule/cluster/e2e.yaml
runs-on: ubuntu-24.04
- name: Runtime Rule Meter Hot-Update
config: test/e2e-v2/cases/runtime-rule/meter/e2e.yaml
- name: DSL Debug API — MAL
config: test/e2e-v2/cases/dsl-debugging/mal/e2e.yaml
- name: DSL Debug API — OAL
Expand Down
6 changes: 6 additions & 0 deletions docs/en/changes/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,11 @@
admin-host only" entry above for the public REST retirement.

#### OAP Server
* Support runtime rule hot-update and DSL debugging for the `meter-analyzer-config` catalog, bringing native meter (`MeterReportService`) rules to parity with `otel-rules`.
- Meter 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` now implements `MalConverterRegistry` and publishes debug holders at boot, so a meter rule can be added / overridden / 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.
- **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 how `otel-rules` has always behaved.
* Support Elasticsearch 9.x as storage.
* Add Node.js runtime metrics via the Node.js agent **`MeterReportService`** pipeline (`meter_instance_nodejs_*`, 1s collect/report). OAP analyzes raw meters through `nodejs-runtime.yaml`. Node.js E2E asserts six `meter_instance_nodejs_*` metrics (`test/e2e-v2/cases/nodejs/e2e.yaml`).
* Add PHP runtime PHM meter analyzer (`php-runtime.yaml`) for SkyWalking PHP agent process
Expand Down Expand Up @@ -353,6 +358,7 @@
* Support trace V1 view in trace single page.

#### Documentation
* Document the `meter-analyzer-config` catalog in the runtime-rule hot-update and DSL-debugging references, and add the optional `layerDefinitions` block, the active-files startup-failure behaviour, and a hot-update / debugging section to the meter setup doc.
* Update LAL documentation with `sourceAttribute()` function and `layer: auto` mode.
* Add Airflow monitoring setup documentation (SWIP-7).
* Add iOS app monitoring setup documentation.
Expand Down
9 changes: 5 additions & 4 deletions docs/en/concepts-and-designs/runtime-rule-hot-update.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ something goes wrong. The HTTP surface is documented separately in
live in management storage (the same persistence layer used for UI templates, UI
menus, and other cluster-wide operator state).
- **Catalog** — the rule group named in the API, currently `otel-rules`, `log-mal-rules`,
`telegraf-rules`, or `lal`. It mirrors the on-disk directory layout so a rule's
`(catalog, name)` identity is portable between disk and the runtime-rule entry store.
`telegraf-rules`, `meter-analyzer-config`, or `lal`. It mirrors the on-disk directory
layout so a rule's `(catalog, name)` identity is portable between disk and the
runtime-rule entry store.
- **Main** — the single OAP node designated to run the on-demand workflow. Every node
can compute it locally from the sorted cluster peer list; no election.
- **Peer** — every node other than the main.
Expand All @@ -26,8 +27,8 @@ something goes wrong. The HTTP surface is documented separately in
## Scope: MAL and LAL, not OAL

Runtime hot-update covers only the **MAL** (`otel-rules`, `log-mal-rules`,
`telegraf-rules`) and **LAL** (`lal`) catalogs. OAL rules are deliberately out of
scope. Three reasons, in order of weight:
`telegraf-rules`, `meter-analyzer-config`) and **LAL** (`lal`) catalogs. OAL rules are
deliberately out of scope. Three reasons, in order of weight:

1. **OAL targets SkyWalking-native traffic sources; MAL and LAL target third-party
data.** OAL rules derive metrics from the fixed set of sources the platform
Expand Down
10 changes: 5 additions & 5 deletions docs/en/setup/backend/admin-api/dsl-debugging-mal.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@ SW_DSL_DEBUGGING_INJECTION_ENABLED=false # default is true; set false to disab
A session targets one MAL metric rule. The key tuple is
`(catalog, name, ruleName)`:

| Field | Source |
|------------|----------------------------------------------------------------------------------------------|
| `catalog` | One of `otel-rules`, `log-mal-rules`, `telegraf-rules` — the directory the rule file lives in |
| `name` | The rule **file** name, without `.yaml` |
| `ruleName` | The full metric name (`metricPrefix` + `_` + per-rule `name`) |
| Field | Source |
|------------|-----------------------------------------------------------------------------------------------------------------------|
| `catalog` | One of `otel-rules`, `log-mal-rules`, `telegraf-rules`, `meter-analyzer-config` — the directory the rule file lives in |
| `name` | The rule **file** name, without `.yaml` |
| `ruleName` | The full metric name (`metricPrefix` + `_` + per-rule `name`) |

Example — the shipped `otel-rules/vm.yaml` declares a metric prefix
`vm` and per-rule name `cpu_total_percentage`. The full metric name is
Expand Down
2 changes: 1 addition & 1 deletion docs/en/setup/backend/admin-api/dsl-debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
Each DSL has its own probe surface, payload shape, and rule-key conventions
— pick the page that matches the rule you're debugging:

- **[MAL](dsl-debugging-mal.md)** — meter rules under `otel-rules`, `log-mal-rules`, `telegraf-rules`. Each captured record is one `SampleFamily` walking through the rule end-to-end (filter → chain ops → meterEmit). Sample payloads carry the complete `SampleFamily` (every sample's name + labels + value + timestamp).
- **[MAL](dsl-debugging-mal.md)** — meter rules under `otel-rules`, `log-mal-rules`, `telegraf-rules`, `meter-analyzer-config`. Each captured record is one `SampleFamily` walking through the rule end-to-end (filter → chain ops → meterEmit). Sample payloads carry the complete `SampleFamily` (every sample's name + labels + value + timestamp).
- **[OAL](dsl-debugging-oal.md)** — per-metric dispatcher capture under `catalog=oal`. Each record is one `ISource` walking through (source entry → filter clauses → aggregation function → emit). Source samples carry the rich `ServiceRelation`-style payload (sourceServiceName, destServiceName, layers, latency, status, detectPoint, ...).
- **[LAL](dsl-debugging-lal.md)** — log analysis under `catalog=lal`. Each record is one log walking through (text → parser → extractor statements → sink). `granularity=statement` emits one sample per extractor statement; `granularity=block` (default) collapses extractor into one sample.

Expand Down
5 changes: 3 additions & 2 deletions docs/en/setup/backend/admin-api/runtime-rule.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ Implicit catalog in the path — useful when scripting against a single catalog:
- `/runtime/mal/log/{addOrUpdate,inactivate,delete}` → `catalog=log-mal-rules`
- `/runtime/lal/{addOrUpdate,inactivate,delete}` → `catalog=lal`

`telegraf-rules` is supported by the canonical `/runtime/rule/...` routes; it does not
currently have a shortcut route.
`telegraf-rules` and `meter-analyzer-config` are supported by the canonical
`/runtime/rule/...` routes; they do not currently have shortcut routes.

### Valid catalogs + names

Expand All @@ -153,6 +153,7 @@ currently have a shortcut route.
| `otel-rules` | OTEL MAL rule YAML files |
| `log-mal-rules` | Log-derived MAL rule YAML files |
| `telegraf-rules` | Telegraf MAL rule YAML files |
| `meter-analyzer-config` | Native meter protocol MAL rule YAML files |
| `lal` | LAL rule YAML files |

Rule `name` mirrors the static filesystem layout — a relative path under the catalog root
Expand Down
24 changes: 24 additions & 0 deletions docs/en/setup/backend/backend-meter.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ section `agent-analyzer` in `application.yml` of skywalking backend.
meterAnalyzerActiveFiles: ${SW_METER_ANALYZER_ACTIVE_FILES:your-custom-meter-conf-without-ext-name} # The multiple files should be separated by ","
```

Every entry listed in `meterAnalyzerActiveFiles` must have a matching rule file under
`$CLASSPATH/meter-analyzer-config`. An entry with no matching file fails the OAP startup; in previous
releases such an entry was silently ignored.

Meter-analyzer-config file is written in YAML format, defined by the scheme described below. Brackets indicate that a
parameter is optional.

Expand All @@ -91,6 +95,17 @@ expPrefix: <string>
expSuffix: <string>
# insert metricPrefix into metric name: <metricPrefix>_<raw_metric_name>
metricPrefix: <string>
# Optional. Declares custom layers inline, registered before the rules in this file compile,
# so that a layer referenced by `expSuffix`/`exp` needs no change in the OAP source.
layerDefinitions:
# Layer name, must match [A-Z][A-Z0-9_]*.
- name: <string>
# Ordinal, unique across all layers and persisted in storage. The ordinal space is
# partitioned by tier: 0-9999 built-in, 10000-99999 boot-time external (this file when
# shipped on disk), 100000+ runtime rules pushed through the hot-update API.
ordinal: <int>
# true = services in this layer are agent-installed (default), false = conjectured.
normal: <bool>
# Metrics rule allow you to recompute queries.
metricsRules:
# The name of rule, which combinates with a prefix '<metricPrefix>_' as the index/table name in storage.
Expand All @@ -110,3 +125,12 @@ client-side APIs to run these functions. The reasons are as follows:
1. The OAP has to set up caches to calculate the values.
1. Once the agent reconnects to another OAP instance, the time windows of rate calculation break. This leads to
inaccurate results.

## Runtime hot-update and debugging

Meter-analyzer-config rules are loaded through the same rule pipeline as `otel-rules`, so they can be
added, overridden, inactivated, and reverted to the bundled content at runtime, without restarting the
OAP. They can also be attached to a sampling debug session to inspect the intermediate result of every
stage of a MAL expression. The catalog name to use in both APIs is `meter-analyzer-config`.
See [Runtime Rule Hot-Update API](admin-api/runtime-rule.md)
and [DSL Debug API — MAL](admin-api/dsl-debugging-mal.md).
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package org.apache.skywalking.oap.server.analyzer.module;

import org.apache.skywalking.oap.meter.analyzer.v2.MalConverterRegistry;
import org.apache.skywalking.oap.server.analyzer.provider.meter.process.IMeterProcessService;
import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.ISegmentParserService;
import org.apache.skywalking.oap.server.library.module.ModuleDefine;
Expand All @@ -33,7 +34,10 @@ public AnalyzerModule() {
public Class[] services() {
return new Class[] {
ISegmentParserService.class,
IMeterProcessService.class
IMeterProcessService.class,
// Lets the runtime-rule plugin hot-swap a single meter-analyzer-config rule.
// Resolved by MalRuleEngine via moduleManager.find("agent-analyzer").
MalConverterRegistry.class
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@

package org.apache.skywalking.oap.server.analyzer.provider;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import lombok.Getter;
import org.apache.skywalking.oap.meter.analyzer.v2.MalConverterRegistry;
import org.apache.skywalking.oap.meter.analyzer.v2.prometheus.rule.Rule;
import org.apache.skywalking.oap.meter.analyzer.v2.prometheus.rule.Rules;
import org.apache.skywalking.oap.server.analyzer.module.AnalyzerModule;
import org.apache.skywalking.oap.server.analyzer.provider.meter.config.MeterConfig;
import org.apache.skywalking.oap.server.analyzer.provider.meter.config.MeterConfigs;
import org.apache.skywalking.oap.server.analyzer.provider.meter.process.IMeterProcessService;
import org.apache.skywalking.oap.server.analyzer.provider.meter.process.MeterProcessService;
import org.apache.skywalking.oap.server.analyzer.provider.trace.CacheReadLatencyThresholdsAndWatcher;
Expand All @@ -43,10 +46,12 @@
import org.apache.skywalking.oap.server.core.CoreModule;
import org.apache.skywalking.oap.server.core.oal.rt.CoreOALDefine;
import org.apache.skywalking.oap.server.core.oal.rt.OALEngineLoaderService;
import org.apache.skywalking.oap.server.core.storage.StorageModule;
import org.apache.skywalking.oap.server.library.module.ModuleDefine;
import org.apache.skywalking.oap.server.library.module.ModuleProvider;
import org.apache.skywalking.oap.server.library.module.ModuleStartException;
import org.apache.skywalking.oap.server.library.module.ServiceNotProvidedException;
import org.apache.skywalking.oap.server.library.util.CollectionUtils;
import org.apache.skywalking.oap.server.telemetry.TelemetryModule;

public class AnalyzerModuleProvider extends ModuleProvider {
Expand All @@ -63,7 +68,6 @@ public class AnalyzerModuleProvider extends ModuleProvider {
@Getter
private TraceSamplingPolicyWatcher traceSamplingPolicyWatcher;

private List<MeterConfig> meterConfigs;
@Getter
private MeterProcessService processService;

Expand Down Expand Up @@ -109,10 +113,11 @@ public void prepare() throws ServiceNotProvidedException, ModuleStartException {
segmentParserService = new SegmentParserServiceImpl(getManager(), moduleConfig);
this.registerServiceImplementation(ISegmentParserService.class, segmentParserService);

meterConfigs = MeterConfigs.loadConfig(
moduleConfig.getConfigPath(), moduleConfig.meterAnalyzerActiveFileNames());
processService = new MeterProcessService(getManager());
this.registerServiceImplementation(IMeterProcessService.class, processService);
// Same instance under both contracts: IMeterProcessService is the ingest-side view
// (receiver + Kafka fetcher), MalConverterRegistry is the runtime-rule hot-update view.
this.registerServiceImplementation(MalConverterRegistry.class, processService);
}

@Override
Expand All @@ -135,7 +140,31 @@ public void start() throws ModuleStartException {

segmentParserService.setListenerManager(listenerManager());

processService.start(meterConfigs);
processService.start(loadMeterRules());
}

/**
* Load the active {@code meter-analyzer-config} rule files through the same
* {@link Rules} loader the otel catalog uses, so meter rules participate in
* {@code RuleSetMerger} (runtime-rule DB overrides win over disk) and land in
* {@code StaticRuleRegistry} (which is what makes {@code /runtime/rule/list},
* {@code /inactivate} and revert-to-bundled see a shipped meter rule at all).
*
* <p>Runs in {@code start()} rather than {@code prepare()} — the merge consults the
* runtime-rule override resolver, which needs a live storage module. This mirrors
* {@code OpenTelemetryMetricRequestProcessor.start()}.
*/
private List<Rule> loadMeterRules() throws ModuleStartException {
final List<String> activeFiles = moduleConfig.meterAnalyzerActiveFileNames();
// Null when meterAnalyzerActiveFiles is unset; Rules.loadRules would NPE on it.
if (CollectionUtils.isEmpty(activeFiles)) {
return Collections.emptyList();
}
try {
return Rules.loadRules(moduleConfig.getConfigPath(), activeFiles);
} catch (IOException e) {
throw new ModuleStartException("Load meter analyzer configs failed", e);
}
}

@Override
Expand All @@ -148,7 +177,16 @@ public String[] requiredModules() {
return new String[] {
TelemetryModule.NAME,
CoreModule.NAME,
ConfigurationModule.NAME
ConfigurationModule.NAME,
// StorageModule is declared so Storage.start() (which registers the
// runtime_rule management table) runs before this provider's start(),
// guaranteeing the RuntimeRuleOverrideResolver's DB-backed resolver can load
// while loadMeterRules() registers the static meter rules. Without this dep
// the module-system sort places agent-analyzer ahead of Storage, the resolver
// silently no-ops at boot, and an operator's meter-rule override / inactivate
// would not take effect until the reconciler's next tick. Same rationale the
// otel / telegraf / envoy / log-analyzer providers carry.
StorageModule.NAME
};
}

Expand Down

This file was deleted.

Loading
Loading