You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wa...@apache.org on 2026/05/12 02:55:04 UTC
(skywalking) branch master updated: SWIP-14: Inspect API on admin-server (with Status API relocation) (#13869)
This is an automated email from the ASF dual-hosted git repository.
wankai123 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git
The following commit(s) were added to refs/heads/master by this push:
new dc77df696a SWIP-14: Inspect API on admin-server (with Status API relocation) (#13869)
dc77df696a is described below
commit dc77df696afb5d97cca79ec123d0e93f7ae84939
Author: 吴晟 Wu Sheng <wu...@foxmail.com>
AuthorDate: Tue May 12 10:54:58 2026 +0800
SWIP-14: Inspect API on admin-server (with Status API relocation) (#13869)
---
docs/en/changes/changes.md | 52 +-
docs/en/security/README.md | 11 +-
docs/en/setup/backend/admin-api/admin-ui.md | 25 +
.../setup/backend/admin-api/dsl-debugging-lal.md | 5 +-
.../setup/backend/admin-api/dsl-debugging-mal.md | 5 +-
.../setup/backend/admin-api/dsl-debugging-oal.md | 5 +-
docs/en/setup/backend/admin-api/dsl-debugging.md | 6 +-
docs/en/setup/backend/admin-api/inspect.md | 193 ++++++
docs/en/setup/backend/admin-api/readme.md | 69 +-
docs/en/setup/backend/admin-api/runtime-rule.md | 5 +-
docs/en/setup/backend/admin-api/status.md | 115 ++++
docs/en/setup/backend/backend-cluster.md | 2 +-
docs/en/setup/backend/configuration-vocabulary.md | 9 +-
docs/en/status/query_alarm_runtime_status.md | 277 --------
docs/en/status/query_cluster_nodes.md | 39 --
docs/en/status/query_ttl_setup.md | 79 ---
docs/en/status/status_apis.md | 57 +-
docs/en/swip/SWIP-14.md | 764 +++++++++++++++++++++
docs/en/swip/readme.md | 3 +-
docs/menu.yml | 26 +-
.../debugging/rest/DSLDebuggingRestHandler.java | 1 -
.../inspect}/pom.xml | 29 +-
.../oap/server/admin/inspect/InspectModule.java} | 21 +-
.../server/admin/inspect/InspectModuleConfig.java} | 12 +-
.../admin/inspect/InspectModuleProvider.java} | 60 +-
.../admin/inspect/decoder/EntityDecoder.java | 232 +++++++
.../admin/inspect/handler/InspectRestHandler.java | 376 ++++++++++
.../admin/inspect/response/EntitiesResponse.java} | 18 +-
.../server/admin/inspect/response/EntityRow.java} | 28 +-
.../admin/inspect/response/ErrorResponse.java} | 19 +-
.../server/admin/inspect/response/MetricRow.java} | 28 +-
.../admin/inspect/response/MetricsResponse.java} | 16 +-
.../server/admin/inspect/response/MqeEntity.java | 47 ++
...walking.oap.server.library.module.ModuleDefine} | 2 +-
...alking.oap.server.library.module.ModuleProvider | 2 +-
.../admin/inspect/decoder/EntityDecoderTest.java | 189 +++++
oap-server/server-admin/pom.xml | 2 +
.../status}/pom.xml | 42 +-
.../admin/status}/AlarmStatusQueryHandler.java | 4 +-
.../admin/status}/AlarmStatusQueryService.java | 2 +-
.../admin/status}/ClusterStatusQueryHandler.java | 2 +-
.../server/admin/status}/DebuggingHTTPHandler.java | 26 +-
.../oap/server/admin/status}/DebuggingSpanRsp.java | 2 +-
.../server/admin/status}/DebuggingTraceRsp.java | 2 +-
.../oap/server/admin/status/StatusModule.java} | 26 +-
.../server/admin/status/StatusModuleConfig.java} | 6 +-
.../server/admin/status/StatusModuleProvider.java | 158 +++++
.../admin/status}/StatusQueryExceptionHandler.java | 2 +-
.../admin/status}/TTLConfigQueryHandler.java | 2 +-
.../admin/status}/log/DebuggingQueryLogsRsp.java | 4 +-
.../server/admin/status}/mqe/DebuggingMQERsp.java | 4 +-
.../DebuggingQueryEndpointTopologyRsp.java | 4 +-
.../DebuggingQueryInstanceTopologyRsp.java | 4 +-
.../topology/DebuggingQueryProcessTopologyRsp.java | 4 +-
.../topology/DebuggingQueryServiceTopologyRsp.java | 4 +-
.../status}/trace/DebuggingQueryTraceBriefRsp.java | 4 +-
.../status}/trace/DebuggingQueryTraceRsp.java | 4 +-
.../trace/zipkin/DebuggingZipkinQueryTraceRsp.java | 4 +-
.../zipkin/DebuggingZipkinQueryTracesRsp.java | 4 +-
...ywalking.oap.server.library.module.ModuleDefine | 2 +-
...alking.oap.server.library.module.ModuleProvider | 2 +-
.../admin/status/StatusModuleProviderTest.java | 158 +++++
.../core/storage/query/IMetricsQueryDAO.java | 26 +
oap-server/server-query-plugin/pom.xml | 1 -
oap-server/server-starter/pom.xml | 15 +-
.../src/main/resources/application.yml | 37 +-
.../banyandb/measure/BanyanDBMetricsQueryDAO.java | 40 ++
.../elasticsearch/query/MetricsQueryEsDAO.java | 38 +
.../jdbc/common/dao/JDBCMetricsQueryDAO.java | 54 ++
test/e2e-v2/CLAUDE.md | 2 +-
.../cases/storage/banyandb/docker-compose.yml | 3 +
.../storage/banyandb/stages/docker-compose.yml | 3 +
.../cases/storage/banyandb/tls/docker-compose.yml | 3 +
test/e2e-v2/cases/storage/es/docker-compose.yml | 3 +
.../storage/es/es-sharding/docker-compose.yml | 3 +
test/e2e-v2/cases/storage/expected/config-dump.yml | 188 ++---
.../expected/inspect-entities-endpoint-cpm.yml | 24 +-
.../expected/inspect-entities-service-cpm-hour.yml | 32 +-
.../inspect-entities-service-cpm-minute.yml | 32 +-
.../expected/inspect-entities-service-cpm.yml | 44 ++
.../inspect-entities-service-instance-cpm.yml | 24 +-
.../inspect-entities-service-percentile.yml | 23 +-
.../inspect-entities-service-relation-cpm.yml | 28 +-
.../expected/inspect-entities-unknown-metric.yml | 8 +-
.../cases/storage/expected/inspect-metrics.yml | 20 +-
test/e2e-v2/cases/storage/mysql/docker-compose.yml | 3 +
.../cases/storage/opensearch/docker-compose.yml | 3 +
.../cases/storage/postgres/docker-compose.yml | 3 +
test/e2e-v2/cases/storage/storage-cases.yaml | 68 ++
89 files changed, 3244 insertions(+), 789 deletions(-)
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index c0d15e5a5e..d73c6077a8 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -10,18 +10,18 @@
public agent / cluster gRPC port (`core.gRPCPort`, default `11800`) so privileged
admin RPCs stay out of the agent network's blast radius — operators bind `gRPCHost`
to a private peer-to-peer interface only. Both the runtime-rule plugin and the new
- DSL Debug API (below) mount onto this shared host. Disabled by default
- (`SW_ADMIN_SERVER=default` to enable); has no built-in authentication and **must** be
+ DSL Debug API (below) mount onto this shared host. **Enabled by default** so the
+ status feature module is reachable out of the box; the host binds to
+ `0.0.0.0:17128` and has no built-in authentication and **must** be
gateway-protected with IP allow-lists, never exposed to the public internet (see the
- [Admin API security notice](../setup/backend/admin-api/readme.md)). The runtime-rule
+ [Admin API security notice](../setup/backend/admin-api/readme.md)). Set
+ `SW_ADMIN_SERVER=` (empty) to disable entirely. The runtime-rule
config block loses its `restHost`/`restPort`/`restContextPath`/`restIdleTimeOut`/
`restAcceptQueueSize`/`httpMaxRequestHeaderSize` keys (and the matching
`SW_RECEIVER_RUNTIME_RULE_REST_*` env vars); host-level knobs move under the new
`admin-server` block (`SW_ADMIN_SERVER_HOST` / `SW_ADMIN_SERVER_PORT` /
`SW_ADMIN_SERVER_GRPC_HOST` / `SW_ADMIN_SERVER_GRPC_PORT` /
- `SW_ADMIN_SERVER_INTERNAL_COMM_TIMEOUT` etc.). Operators upgrading to 10.5.0 with
- `SW_RECEIVER_RUNTIME_RULE=default` must also set `SW_ADMIN_SERVER=default`; OAP
- fails fast at startup otherwise.
+ `SW_ADMIN_SERVER_INTERNAL_COMM_TIMEOUT` etc.).
* **Runtime rule hot-update for MAL and LAL.** Operators can now ship metric (MAL) and log
(LAL) rule changes without restarting OAP. A push to a new admin endpoint persists the rule
to the configured storage backend, and every node in the cluster converges to the new
@@ -65,8 +65,8 @@
disabled by default and listens on port `17128` (HTTP) when enabled. It has no
built-in authentication — operators must gateway-protect it with IP allow-lists and
never expose it to the public internet.** Routes mount on the new `admin-server`
- HTTP host, so enabling the runtime-rule API requires both `SW_ADMIN_SERVER=default`
- and `SW_RECEIVER_RUNTIME_RULE=default`.
+ HTTP host, which is on by default; enable the runtime-rule feature with
+ `SW_RECEIVER_RUNTIME_RULE=default`.
* **Live debugger for MAL / LAL / OAL** — implements [SWIP-13 Live Debugger for MAL / LAL / OAL](../swip/SWIP-13.md).
Sample-based runtime debugger that captures per-stage inputs/outputs as the three DSLs
process live ingest. Idle-path cost is one volatile-bool read per probe call site that
@@ -77,7 +77,8 @@
L7 load balancer in front of the admin port routes operator requests freely. Mounts on
the shared `admin-server` host (`/dsl-debugging/*` for session control plane,
`/runtime/oal/*` for the OAL rule picker). Disabled by default; enable with
- `SW_DSL_DEBUGGING=default` *and* `SW_ADMIN_SERVER=default`. `injectionEnabled` is a
+ `SW_DSL_DEBUGGING=default` (admin-server itself is on by default).
+ `injectionEnabled` is a
boot-time codegen switch defaulting to `true` — once the module is enabled, probes
fire and sessions record samples; set `false` only if the REST surface is wanted but
no codegen-side probe overhead is acceptable. Per-session limits enforce hard caps
@@ -112,6 +113,39 @@
`4.2.10.Final` → `4.2.12.Final`, Netty-tcnative `2.0.75` → `2.0.77`, pgv (protoc-gen-validate)
`1.2.1` → `1.3.0`. Driven by the new BanyanDB schema-consistency RPCs whose generated
validation code requires the `protobuf-java 4.x` runtime.
+* **Inspect API on admin-server.** Two new admin-only HTTP endpoints for
+ browsing the live metric catalog and the entities currently emitting
+ values for a given metric. `GET /inspect/metrics` lists every registered
+ metric with its type / scope / catalog / value-column name / supported
+ downsamplings (pure metadata, no I/O). `GET /inspect/entities` runs the
+ storage backend's entity scan for a metric over a time range + step
+ (capped at 300 rows) and returns each entity decoded into an MQE-ready
+ payload — the response includes a `mqeEntity` block the operator pastes
+ verbatim into the public GraphQL `execExpression` mutation, plus the
+ source service's layer(s) (multi-layer services emit one row per layer).
+ Restricted to `REGULAR_VALUE` / `LABELED_VALUE` metrics and to non-Process
+ scopes; `HEATMAP` / `SAMPLED_RECORD` / `Process` / `ProcessRelation`
+ return 400. Adds `IMetricsQueryDAO.listEntityIdsInRange` as an abstract
+ method on the interface — any 3rd party storage backend must explicitly
+ override or the build fails. **Enabled by default** (both `SW_INSPECT` and
+ `SW_ADMIN_SERVER` are on by default); set `SW_INSPECT=` empty to disable.
+ Operator reference: [Inspect API](../setup/backend/admin-api/inspect.md).
+* **Status API relocated onto `server-admin/`.** The legacy
+ `status-query-plugin` is replaced by a new `status` feature module under
+ `server-admin/`. Every existing route — `/status/cluster/nodes`,
+ `/status/alarm/*`, `/status/config/ttl`, `/debugging/config/dump`,
+ `/debugging/query/*` — keeps its URI and payload unchanged. The module
+ dual-binds the handlers: always on the public REST register (preserving
+ the `core.restPort` binding `skywalking-ui` consumes) and additionally
+ on the admin-server register when admin-server is enabled, so operators
+ driving the admin host get the same surface alongside `/inspect/*` /
+ `/dsl-debugging/*` / `/runtime/rule/*`. The selector renames from the
+ QUERY-plugin form (`SW_QUERY=…,status-query-plugin`) to a top-level
+ `SW_STATUS=default` (on by default); custom `application.yml` overrides
+ referencing `status-query` need to repoint to `status`. Default
+ deployments need no change. Status docs move from `docs/en/status/` to
+ [admin-api/status.md](../setup/backend/admin-api/status.md); the legacy
+ `docs/en/status/status_apis.md` keeps a one-cycle redirect stub.
#### OAP Server
* Fix: remove the redundant tags from the `envoy-ai-gateway.yaml` LAL configuration.
diff --git a/docs/en/security/README.md b/docs/en/security/README.md
index 67f9d2bbad..6fbb6a0977 100644
--- a/docs/en/security/README.md
+++ b/docs/en/security/README.md
@@ -68,12 +68,13 @@ cluster RPCs between OAP nodes — runtime-rule `Suspend / Resume / Forward`, DS
agent / cluster gRPC port (`core.gRPCPort`, default `11800`). An attacker who reaches `11800`
to submit telemetry MUST NOT be able to invoke privileged admin RPCs.
-`admin-server` is **disabled by default**. Enabling it (`SW_ADMIN_SERVER=default` plus the
-relevant feature module's selector) opens both ports with **no built-in authentication**.
-This is intentional for now — the design goal is a simple admin socket that a gateway /
-service mesh wraps with the operator's existing auth story.
+`admin-server` is **enabled by default** so the bundled `status` and `inspect` feature
+modules are reachable out of the box. Both ports open with **no built-in authentication**
+— the design goal is a simple admin socket that a gateway / service mesh wraps with the
+operator's existing auth story. Set `SW_ADMIN_SERVER=` (empty) to keep the host closed
+if no admin-side API is needed.
-Required operator actions when enabling:
+Required operator actions:
1. **Never expose port 17128 (HTTP) to the public internet.** Bind to a private network
interface or `localhost` and reach it through an operator-controlled gateway.
diff --git a/docs/en/setup/backend/admin-api/admin-ui.md b/docs/en/setup/backend/admin-api/admin-ui.md
new file mode 100644
index 0000000000..c56672f866
--- /dev/null
+++ b/docs/en/setup/backend/admin-api/admin-ui.md
@@ -0,0 +1,25 @@
+# Admin UI
+
+The Admin API is a set of HTTP endpoints (Runtime Rule Hot-Update, DSL
+Debug, Status, Inspect) — operators drive them with `curl`, `swctl`, or a
+front-end of their choice. SkyWalking does not ship an official admin UI.
+
+## Vantage Studio (community)
+
+[**Vantage Studio**](https://github.com/SkyAPM/vantage-studio) is a
+community-built web UI that consumes SkyWalking's Admin API surface. It
+provides a visual driver for the same endpoints documented in this
+section.
+
+* Project: <https://github.com/SkyAPM/vantage-studio>
+* Author: [Sheng Wu](https://github.com/wu-sheng)
+* Status: Community project under the SkyAPM organization — not an Apache
+ SkyWalking release artifact. License, release cadence, and support
+ follow that project's repository.
+
+Operators looking for a UI in front of `/runtime/rule/*`,
+`/dsl-debugging/*`, `/status/*`, and `/inspect/*` can adopt it the same
+way they would any third-party tool — install separately, point it at the
+admin port (default `17128`) behind the same gateway / authenticating
+reverse proxy the [admin-server security
+notice](readme.md#security-notice) requires.
diff --git a/docs/en/setup/backend/admin-api/dsl-debugging-lal.md b/docs/en/setup/backend/admin-api/dsl-debugging-lal.md
index 1d49ff0863..86534c9ec8 100644
--- a/docs/en/setup/backend/admin-api/dsl-debugging-lal.md
+++ b/docs/en/setup/backend/admin-api/dsl-debugging-lal.md
@@ -78,11 +78,10 @@ volatile-bool reads — idle cost is effectively free.
## Enabling
-Two selectors must be enabled — the shared admin HTTP host (`admin-server`)
-and the DSL-debug feature on top of it:
+The shared admin HTTP host (`admin-server`) is enabled by default; turn on the
+DSL-debug feature on top of it:
```bash
-SW_ADMIN_SERVER=default
SW_DSL_DEBUGGING=default
```
diff --git a/docs/en/setup/backend/admin-api/dsl-debugging-mal.md b/docs/en/setup/backend/admin-api/dsl-debugging-mal.md
index a720d6dd0f..d31a7930ef 100644
--- a/docs/en/setup/backend/admin-api/dsl-debugging-mal.md
+++ b/docs/en/setup/backend/admin-api/dsl-debugging-mal.md
@@ -56,11 +56,10 @@ effectively free.
## Enabling
-Two selectors must be enabled — the shared admin HTTP host (`admin-server`)
-and the DSL-debug feature on top of it:
+The shared admin HTTP host (`admin-server`) is enabled by default; turn on the
+DSL-debug feature on top of it:
```bash
-SW_ADMIN_SERVER=default
SW_DSL_DEBUGGING=default
```
diff --git a/docs/en/setup/backend/admin-api/dsl-debugging-oal.md b/docs/en/setup/backend/admin-api/dsl-debugging-oal.md
index 05b6bb5fd2..6ba31caafe 100644
--- a/docs/en/setup/backend/admin-api/dsl-debugging-oal.md
+++ b/docs/en/setup/backend/admin-api/dsl-debugging-oal.md
@@ -69,11 +69,10 @@ volatile-bool reads — idle cost is effectively free.
## Enabling
-Two selectors must be enabled — the shared admin HTTP host (`admin-server`)
-and the DSL-debug feature on top of it:
+The shared admin HTTP host (`admin-server`) is enabled by default; turn on the
+DSL-debug feature on top of it:
```bash
-SW_ADMIN_SERVER=default
SW_DSL_DEBUGGING=default
```
diff --git a/docs/en/setup/backend/admin-api/dsl-debugging.md b/docs/en/setup/backend/admin-api/dsl-debugging.md
index 290d912c3d..4e3a061f0b 100644
--- a/docs/en/setup/backend/admin-api/dsl-debugging.md
+++ b/docs/en/setup/backend/admin-api/dsl-debugging.md
@@ -56,8 +56,8 @@ What the three DSLs share:
`admin-server` HTTP host (default port `17128`). The shape of session
lifecycle (install, poll, stop) is identical across DSLs — only the
rule-key tuple and payload schema differ.
-- **One enable contract**: requires `SW_ADMIN_SERVER=default` and
- `SW_DSL_DEBUGGING=default`. Probe insertion is gated by the boot-time
+- **One enable contract**: requires `SW_DSL_DEBUGGING=default`
+ (`admin-server` itself is enabled by default). Probe insertion is gated by the boot-time
switch `SW_DSL_DEBUGGING_INJECTION_ENABLED` (default `true` — once the
module is enabled, probes fire and sessions record samples). Set this
to `false` only if the REST surface is wanted but probe overhead is
@@ -144,7 +144,7 @@ table. The DSL-debug-relevant keys:
| Key | Default | Purpose |
|--------------------------------------|---------|---------------------------------------------------------------|
-| `SW_ADMIN_SERVER` | (empty) | Enables the shared admin HTTP host. Must be set. |
+| `SW_ADMIN_SERVER` | `default` | Shared admin HTTP host. On by default; set empty to disable. |
| `SW_DSL_DEBUGGING` | (empty) | Enables the DSL debug API. |
| `SW_DSL_DEBUGGING_INJECTION_ENABLED` | `true` | Boot-time codegen switch. Set `false` to disable probes. |
diff --git a/docs/en/setup/backend/admin-api/inspect.md b/docs/en/setup/backend/admin-api/inspect.md
new file mode 100644
index 0000000000..de5f8996f9
--- /dev/null
+++ b/docs/en/setup/backend/admin-api/inspect.md
@@ -0,0 +1,193 @@
+# Inspect API
+
+> Available since 10.5.0.
+
+The Inspect API lives on `admin-server` and exposes two browse endpoints that
+let operators answer two questions without writing exploratory MQE:
+
+1. *Which metrics has OAP registered, and at what downsampling?*
+2. *For metric `X` in time range `T`, which entities currently hold values?*
+
+The output of (2) carries a ready-to-paste `mqeEntity` payload, so the
+follow-up MQE call against the public GraphQL `execExpression` mutation is
+copy-paste from the inspect response.
+
+## Enabling
+
+Enabled by default. Both `SW_ADMIN_SERVER=default` and `SW_INSPECT=default`
+are on out of the box; no opt-in required. To disable:
+
+```bash
+export SW_INSPECT= # disable inspect only
+export SW_ADMIN_SERVER= # close the admin host entirely
+```
+
+If `SW_INSPECT=default` is explicitly kept while `SW_ADMIN_SERVER=` is set
+empty, OAP fails fast at startup with a `ModuleNotFoundException: admin-server`
+— same gate that protects every other admin feature module.
+
+## Security
+
+Inspect routes bind on the **admin port** only (default `17128`). They do
+**not** mirror onto the public REST port — the entity-enumeration scan in
+`/inspect/entities` competes with user-facing query traffic for storage and
+is intentionally gated behind the private admin surface.
+
+The general admin-port security guidance applies: gateway-protect with an
+IP allow-list and an authenticating reverse proxy, bind only to a private
+interface, never expose to the public internet. See the
+[admin-server security notice](readme.md#security-notice).
+
+## Endpoints
+
+### `GET /inspect/metrics`
+
+Lists registered metrics with type, scope, catalog, value-column name, and
+the downsamplings each metric is materialised at.
+
+Internal-only entries are filtered:
+
+* `Column.ValueDataType.NOT_VALUE` columns (topology lines, service
+ entries — persisted but not queryable) are skipped silently.
+* `Scope.All`-scoped entries (deprecated, not routable through MQE) are
+ skipped silently.
+
+Query parameters:
+
+| Name | Required | Description |
+|------|----------|-------------|
+| `regex` | no | Java regex over metric name. Default `.*`. |
+| `type` | no | Filter by `REGULAR_VALUE` / `LABELED_VALUE` / `HEATMAP` / `SAMPLED_RECORD`. Repeatable. |
+| `catalog` | no | Filter by catalog (`SERVICE`, `SERVICE_INSTANCE`, `ENDPOINT`, `SERVICE_RELATION`, `SERVICE_INSTANCE_RELATION`, `ENDPOINT_RELATION`). Repeatable. |
+| `mqeQueryable` | no | If `true`, return only metrics that `/inspect/entities` accepts (`REGULAR_VALUE`, `LABELED_VALUE`). Default `false`. |
+
+Example:
+
+```bash
+curl 'http://oap-admin:17128/inspect/metrics?regex=service_cpm'
+```
+
+```json
+{
+ "metrics": [
+ {
+ "name": "service_cpm",
+ "type": "REGULAR_VALUE",
+ "catalog": "SERVICE",
+ "scopeId": 1,
+ "scope": "Service",
+ "valueColumnName": "value",
+ "downsamplings": ["MINUTE", "HOUR", "DAY"]
+ }
+ ]
+}
+```
+
+### `GET /inspect/entities`
+
+For a metric + time range + step, returns the entities holding values, each
+decoded into a human-readable shape and an MQE-ready `mqeEntity` payload.
+
+Restricted to `REGULAR_VALUE` and `LABELED_VALUE` metrics. The non-MQE
+metric types (`HEATMAP` / `SAMPLED_RECORD`) and the out-of-scope scopes
+(`Process` / `ProcessRelation`) are rejected with `400`.
+
+Query parameters:
+
+| Name | Required | Description |
+|------|----------|-------------|
+| `metric` | yes | Metric name. Must resolve in `ValueColumnMetadata`. |
+| `start` | yes | Time-range start. Same format as MQE `Duration.start`: `yyyy-MM-dd` (DAY), `yyyy-MM-dd HH` (HOUR), `yyyy-MM-dd HHmm` (MINUTE), `yyyy-MM-dd HHmmss` (SECOND). Note `HHmm` is no-separator — use `1230`, not `12:30`. |
+| `end` | yes | Time-range end. Format mirrors `start`. |
+| `step` | yes | One of `MINUTE` / `HOUR` / `DAY`. Must be one of the metric's `downsamplings`. |
+| `limit` | no | Server-side cap. Default 300, hard-capped at 300. |
+
+The `limit` is applied as `LIMIT N` at the storage layer — it bounds the
+total rows scanned (300 ≈ 10 buckets × 30 entities), not 300 distinct
+entities. Backends dedup on `entity_id` before returning.
+
+Multi-layer services emit **one row per layer**. The `mqeEntity` block is
+identical across layer rows (MQE itself does not take a layer parameter);
+the duplication exists so the operator can pick any single layer as the
+investigation entry point. An empty result is `{ "rows": [] }` — not an
+error.
+
+Example — `service_cpm` over the last 10 minutes, with `payment` registered
+under both `MESH` and `GENERAL`:
+
+```bash
+curl 'http://oap-admin:17128/inspect/entities?metric=service_cpm&start=2026-05-10%201230&end=2026-05-10%201240&step=MINUTE'
+```
+
+```json
+{
+ "metric": "service_cpm",
+ "scope": "Service",
+ "step": "MINUTE",
+ "start": "2026-05-10 1230",
+ "end": "2026-05-10 1240",
+ "rows": [
+ {
+ "entityId": "cGF5bWVudA==.1",
+ "decoded": { "serviceName": "payment", "isReal": true },
+ "layer": "GENERAL",
+ "mqeEntity": {
+ "scope": "Service",
+ "serviceName": "payment",
+ "normal": true
+ }
+ },
+ {
+ "entityId": "cGF5bWVudA==.1",
+ "decoded": { "serviceName": "payment", "isReal": true },
+ "layer": "MESH",
+ "mqeEntity": {
+ "scope": "Service",
+ "serviceName": "payment",
+ "normal": true
+ }
+ },
+ {
+ "entityId": "bXlzcWw=.0",
+ "decoded": { "serviceName": "mysql", "isReal": false },
+ "layer": "VIRTUAL_DATABASE",
+ "mqeEntity": {
+ "scope": "Service",
+ "serviceName": "mysql",
+ "normal": false
+ }
+ }
+ ]
+}
+```
+
+## Discovering the OAP REST URL for the MQE follow-up
+
+To keep the surface minimal, the inspect API does not introduce a separate
+discovery endpoint. Clients that need to learn which host:port serves the
+public GraphQL / MQE surface parse the response of the existing
+[`/debugging/config/dump`](status.md#debuggingconfigdump) — also hosted by
+the relocated status feature module — for `core.restHost` / `core.restPort`
+(or the sharing-server overrides if configured). One curl + `jq` once at
+session start is enough.
+
+## Errors
+
+| Status | Body | Cause |
+|--------|------|-------|
+| 400 | `{"error":"unknown metric: foo"}` | Metric not in `ValueColumnMetadata`. |
+| 400 | `{"error":"step DAY not supported by metric foo (MINUTE,HOUR)"}` | Metric not materialised at the requested downsampling. |
+| 400 | `{"error":"metric type HEATMAP is not MQE-queryable; /inspect/entities only accepts REGULAR_VALUE and LABELED_VALUE"}` | Metric is `HEATMAP` (`HISTOGRAM` `dataType`). |
+| 400 | `{"error":"metric type SAMPLED_RECORD is out of scope for /inspect/entities"}` | Metric is `SAMPLED_RECORD`. |
+| 400 | `{"error":"process scope is out of scope"}` | Scope is `Process` / `ProcessRelation`. |
+| 400 | `{"error":"limit must be between 1 and 300"}` | `limit` out of range. |
+
+## Limits
+
+* Per-call cap is **300 rows scanned** at the storage layer, not relaxable
+ via parameter. The 300 envelope tracks 10 buckets × 30 entities.
+* No caching layer in v1. The endpoints are intended for human-driven
+ exploration, not high-frequency polling.
+* `Process` and `ProcessRelation` scopes are skipped because `ProcessID` is
+ a SHA-256 hash with no decoder; readable rendering would require a
+ `ProcessTraffic` join, deferred to a future revision.
diff --git a/docs/en/setup/backend/admin-api/readme.md b/docs/en/setup/backend/admin-api/readme.md
index 0d5c10e0f1..71f045834a 100644
--- a/docs/en/setup/backend/admin-api/readme.md
+++ b/docs/en/setup/backend/admin-api/readme.md
@@ -16,8 +16,10 @@ endpoint hosted there.
>
> 1. **Gateway-protect the admin port** with an IP allow-list and an
> authenticating reverse proxy (sidecar, gateway, mTLS terminator).
-> SkyWalking ships `admin-server` *disabled by default* (`SW_ADMIN_SERVER`
-> selector empty); enabling it is an explicit operator decision.
+> SkyWalking ships `admin-server` **enabled by default** so the status
+> feature module is reachable out of the box; the host binds to
+> `0.0.0.0:17128` by default. Set `SW_ADMIN_SERVER=` (empty) to disable
+> entirely.
> 2. **Bind to a private interface.** Set `SW_ADMIN_SERVER_HOST` to a private
> address (or `127.0.0.1`) — never the public-facing interface.
> 3. **Never expose the admin port to the public internet.** No exception.
@@ -33,14 +35,13 @@ endpoint hosted there.
## Endpoints by feature
`admin-server` itself only provides the HTTP host. Each feature module
-registers its own routes on it. Today there are two:
+registers its own routes on it. Today there are four:
### [Runtime Rule Hot-Update API](runtime-rule.md)
Operators add, override, inactivate, and delete MAL / LAL rule files at
runtime without restarting OAP. Routes live under `/runtime/rule/*`.
-Requires `SW_RECEIVER_RUNTIME_RULE=default` (which in turn requires
-`SW_ADMIN_SERVER=default`).
+Requires `SW_RECEIVER_RUNTIME_RULE=default` (`admin-server` is on by default).
Common operations:
@@ -58,8 +59,7 @@ Operators run sampling debug sessions that capture how MAL / LAL / OAL
rules transform live ingest, with per-stage / per-block payloads
available for inspection. Routes live under `/dsl-debugging/*` and
`/runtime/oal/*` (read-only OAL rule listing for the debugger's rule
-picker). Requires `SW_DSL_DEBUGGING=default` (which in turn requires
-`SW_ADMIN_SERVER=default`).
+picker). Requires `SW_DSL_DEBUGGING=default` (`admin-server` is on by default).
Common operations:
@@ -73,13 +73,60 @@ Common operations:
Design reference: [SWIP-13](../../../swip/SWIP-13.md).
Operator reference: [DSL Debug API](dsl-debugging.md).
+### [Inspect API](inspect.md)
+
+Operators browse the live metric catalog and the entities currently emitting
+values for a given metric, getting MQE-ready output that pastes directly into
+the public GraphQL `execExpression` mutation. Routes live under `/inspect/*`.
+Enabled by default (both `SW_INSPECT=default` and `SW_ADMIN_SERVER=default` are on
+by default); set `SW_INSPECT=` (empty) to disable.
+
+Common operations:
+
+- `GET /inspect/metrics` — metric catalog with type / scope / supported downsamplings.
+- `GET /inspect/entities?metric=&start=&end=&step=` — capped (≤300) list of
+ entities holding values, decoded into MQE-ready form.
+
+Operator reference: [Inspect API](inspect.md).
+
+### [Status API](status.md)
+
+Cluster, alarm, TTL, and per-query debug-trace endpoints. Hosted by the
+`status` feature module — relocated from the legacy `status-query-plugin`
+in 10.5.0. **Dual-bound**: always on the public REST port (`core.restPort`,
+default `12800`, where skywalking-ui consumes it) and additionally on the
+admin-server port (default `17128`). Both `status` and `admin-server` are
+enabled by default — no opt-in required.
+
+Common operations:
+
+- `GET /status/cluster/nodes` — OAP cluster peer list.
+- `GET /status/alarm/rules`, `/status/alarm/{ruleId}`, `/status/alarm/{ruleId}/{entityName}` — alarm runtime state.
+- `GET /status/config/ttl` — effective TTL configuration.
+- `GET /debugging/config/dump` — effective configuration dump (also serves as the REST-URL discovery primitive for inspect clients).
+- `GET /debugging/query/...` — run a named query path with debug tracing enabled.
+
+Operator reference: [Status API](status.md).
+
+### Admin UI (community)
+
+SkyWalking does not ship an official admin UI; operators drive the
+endpoints above with `curl`, `swctl`, or a front-end of their choice.
+[Vantage Studio](https://github.com/SkyAPM/vantage-studio) is a
+community-built web UI (under the SkyAPM organization, authored by
+[Sheng Wu](https://github.com/wu-sheng)) that consumes the Admin API
+surface. See [Admin UI](admin-ui.md) for the operator note.
+
## Enabling
-`admin-server` and its feature modules ship disabled by default. Enable
-the features the deployment needs:
+`admin-server` itself is enabled by default; the feature modules that
+mount onto it are individually opt-in. Enable the features the deployment
+needs:
```bash
-export SW_ADMIN_SERVER=default # the shared HTTP host
+# SW_ADMIN_SERVER=default is on by default — see security notice above.
+# SW_STATUS=default is on by default — see [Status API](status.md).
+# SW_INSPECT=default is on by default — see [Inspect API](inspect.md).
export SW_RECEIVER_RUNTIME_RULE=default # to use /runtime/rule/*
export SW_DSL_DEBUGGING=default # to use /dsl-debugging/* and /runtime/oal/*
```
@@ -106,7 +153,7 @@ Operators MUST:
```yaml
admin-server:
- selector: ${SW_ADMIN_SERVER:-}
+ selector: ${SW_ADMIN_SERVER:default}
default:
# HTTP REST surface (operator API).
host: ${SW_ADMIN_SERVER_HOST:0.0.0.0}
diff --git a/docs/en/setup/backend/admin-api/runtime-rule.md b/docs/en/setup/backend/admin-api/runtime-rule.md
index 30355df7b0..4f056fb543 100644
--- a/docs/en/setup/backend/admin-api/runtime-rule.md
+++ b/docs/en/setup/backend/admin-api/runtime-rule.md
@@ -19,11 +19,10 @@ binding, the public-internet posture, and access auditing.
## Enabling the module
-Set the selectors to `default` in `application.yml` or via env vars (the runtime-rule
-routes mount on the shared `admin-server` HTTP host, so both modules must be enabled):
+The runtime-rule routes mount on the shared `admin-server` HTTP host, which is
+enabled by default. Turn on the runtime-rule feature module:
```bash
-SW_ADMIN_SERVER=default
SW_RECEIVER_RUNTIME_RULE=default
```
diff --git a/docs/en/setup/backend/admin-api/status.md b/docs/en/setup/backend/admin-api/status.md
new file mode 100644
index 0000000000..30479f1fe6
--- /dev/null
+++ b/docs/en/setup/backend/admin-api/status.md
@@ -0,0 +1,115 @@
+# Status API
+
+The Status API is a set of read-only HTTP endpoints for inspecting cluster
+membership, alarm runtime state, effective configuration / TTL settings,
+and per-query debug traces. Hosted by the `status` feature module.
+
+Status API is **bi-host**: every route is published on **both** the public
+REST surface (`core.restPort`, default `12800` — where `skywalking-ui` and
+any existing operator scripts already reach it) and the admin-server
+surface (default `17128`) when admin-server is enabled. Operators can drive
+status from whichever surface their network policy allows; URIs and
+payloads are identical on both hosts. See [Hosting](#hosting) for the
+selector and the dual-bind contract.
+
+> The legacy `status-query-plugin` was relocated into
+> `server-admin/status` in 10.5.0. URI paths and response payloads are
+> unchanged. See the [10.5.0 changelog](../../../changes/changes.md).
+
+## Hosting
+
+The `status` feature module **dual-binds** every handler:
+
+* **Always on the public REST surface** (`core.restPort`, default `12800`,
+ or the sharing-server REST port if configured). This preserves the
+ binding `skywalking-ui` and any existing operator scripts already use.
+* **Additionally on the admin-server surface** (default `17128`).
+ `admin-server` is enabled by default, and the admin port is
+ gateway-protected per the [admin-server security notice](readme.md#security-notice),
+ so operators that lock down their public REST surface can drive status
+ from the same private host as `/inspect/*`, `/dsl-debugging/*`, and
+ `/runtime/rule/*`. Set `SW_ADMIN_SERVER=` (empty) to close the admin
+ port — the public REST binding still serves `/status/*`.
+
+The module is enabled by default — the legacy posture is preserved. To
+disable explicitly, unset `SW_STATUS`:
+
+```bash
+export SW_STATUS= # disable
+export SW_STATUS=default # default (enabled)
+```
+
+## Configuration
+
+```yaml
+status:
+ selector: ${SW_STATUS:default}
+ default:
+ keywords4MaskingSecretsOfConfig: ${SW_DEBUGGING_QUERY_KEYWORDS_FOR_MASKING_SECRETS:user,password,token,accessKey,secretKey,authentication}
+```
+
+`keywords4MaskingSecretsOfConfig` is consumed by `/debugging/config/dump`
+to redact configuration values whose key contains any listed substring.
+
+## Endpoints
+
+### `/status/cluster/nodes`
+
+Returns the OAP cluster peer list as the cluster module sees it. Useful
+for confirming that every node has joined and is reporting back.
+
+```bash
+curl http://oap:12800/status/cluster/nodes
+```
+
+### `/status/alarm/rules`
+
+Lists registered alarm rules.
+
+### `/status/alarm/{ruleId}`
+
+Per-rule runtime status — current window state, last evaluation, the
+entities the rule has triggered against.
+
+### `/status/alarm/{ruleId}/{entityName}`
+
+Drill-down: status of one specific entity under a given rule.
+
+### `/status/config/ttl`
+
+Returns the effective TTL configuration the OAP loaded at boot.
+
+### `/debugging/config/dump`
+
+Dumps the effective configuration that was applied at boot. Values whose
+key contains any substring listed in `keywords4MaskingSecretsOfConfig` are
+redacted. Output is YAML-shaped key=value lines.
+
+The Inspect API uses this endpoint as its REST-URL discovery primitive —
+clients parse the dump for `core.restHost` / `core.restPort` (or the
+sharing-server overrides) once at session start to learn where the public
+GraphQL / MQE surface lives.
+
+### `/debugging/query/...`
+
+Runs a named query path with debug tracing enabled and returns the
+captured DAO / storage spans alongside the result. Useful for diagnosing
+why a query is slow or returning unexpected data.
+
+| URI | Purpose |
+|---|---|
+| `/debugging/query/mqe` | Run an MQE expression with tracing. |
+| `/debugging/query/trace/queryBasicTraces` | Trace search brief. |
+| `/debugging/query/trace/queryTrace` | Trace detail. |
+| `/debugging/query/zipkin/api/v2/traces` | Zipkin compat brief. |
+| `/debugging/query/zipkin/api/v2/trace` | Zipkin compat detail. |
+| `/debugging/query/topology/getGlobalTopology` | Global topology debug. |
+| `/debugging/query/topology/getServicesTopology` | Per-service topology debug. |
+| `/debugging/query/topology/getServiceInstanceTopology` | Per-instance topology debug. |
+| `/debugging/query/topology/getEndpointDependencies` | Endpoint dependencies debug. |
+| `/debugging/query/topology/getProcessTopology` | Process topology debug. |
+| `/debugging/query/log/queryLogs` | Log query debug. |
+
+The query parameters mirror the corresponding GraphQL inputs (consult the
+schema definitions under
+`oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol`).
diff --git a/docs/en/setup/backend/backend-cluster.md b/docs/en/setup/backend/backend-cluster.md
index 0a43546830..ff1ea0e09f 100644
--- a/docs/en/setup/backend/backend-cluster.md
+++ b/docs/en/setup/backend/backend-cluster.md
@@ -27,7 +27,7 @@ section `cluster`. You can specify any of them in the `selector` property to ena
___
**NOTICE**,
-Before you set up the cluster, please read the [Query Cluster Nodes](../../status/query_cluster_nodes.md) API to understand how to
+Before you set up the cluster, please read the [Query Cluster Nodes](admin-api/status.md#statusclusternodes) API to understand how to
verify the cluster node list. If the nodes don't match the expectation, the cluster is not working properly, there could
be many feature impacts, e.g. the metrics could be inaccurate and the alarms could not be triggered correctly.
___
diff --git a/docs/en/setup/backend/configuration-vocabulary.md b/docs/en/setup/backend/configuration-vocabulary.md
index dbc0218151..c51119181c 100644
--- a/docs/en/setup/backend/configuration-vocabulary.md
+++ b/docs/en/setup/backend/configuration-vocabulary.md
@@ -334,8 +334,9 @@ It divided into several modules, each of which has its own settings. The followi
| - | - | kafkaTopicLog | Kafka topic name for log. [...]
| - | - | exportErrorStatusTraceOnly | Export error status trace segments through the Kafka channel. [...]
| health-checker | default | checkIntervalSeconds | The period of checking OAP internal health status (in seconds). [...]
-| status-query | default | | [...]
+| status | default | (selector; relocated from status-query-plugin in 10.5.0) | Status feature module — hosts /status/* and /debugging/* routes. Dual-bound: always on the public REST register, plus on the admin-server port. Both `status` and `admin-server` are enabled by default; set SW_STATUS empty to disable. [...]
| - | - | keywords4MaskingSecretsOfConfig | Include the list of keywords to filter configurations including secrets. Separate keywords by a comma. [...]
+| inspect | default | (selector) | Inspect API on admin-server — /inspect/metrics (catalog) and /inspect/entities (entity enumeration with MQE-ready output). Routes only on the admin-server register. **Enabled by default** (admin-server is on by default too); set SW_INSPECT empty to disable. [...]
| configuration-discovery | default | disableMessageDigest | If true, agent receives the latest configuration every time, even without making any changes. By default, OAP uses the SHA512 message digest mechanism to detect changes in configuration. [...]
| receiver-event | default | gRPC services that handle events data. | - [...]
| aws-firehose-receiver | default | host | Binding IP of HTTP server [...]
@@ -348,7 +349,7 @@ It divided into several modules, each of which has its own settings. The followi
| - | - | enableTLS | Indicate if enable HTTPS for the server [...]
| - | - | tlsKeyPath | TLS key path [...]
| - | - | tlsCertChainPath | TLS certificate chain path [...]
-| admin-server | default | - | Shared HTTP host for admin / on-demand write APIs (runtime-rule hot-update, DSL debug session). **Disabled by default**: leave the selector empty to keep the host closed; set `SW_ADMIN_SERVER=default` to enable. The host has **no built-in authentication** — gateway-protect wit [...]
+| admin-server | default | - | Shared HTTP host for admin / on-demand write APIs (status, inspect, runtime-rule hot-update, DSL debug session). **Enabled by default** so the bundled status / inspect feature modules are reachable out of the box; set `SW_ADMIN_SERVER=` empty to keep the host closed. The host [...]
| - | - | host | Binding IP of the admin HTTP host. [...]
| - | - | port | Binding port of the admin HTTP host. [...]
| - | - | contextPath | Web context path of the admin HTTP host. [...]
@@ -365,10 +366,10 @@ It divided into several modules, each of which has its own settings. The followi
| - | - | gRPCSslCertChainPath | Server certificate chain path (PEM). [...]
| - | - | gRPCSslTrustedCAsPath | CA bundle path (PEM); used by the admin gRPC client to validate peer certs. [...]
| - | - | internalCommunicationTimeout | Default per-call timeout (ms) for admin-internal RPCs between OAP nodes — dsl-debugging install / collect / stop fan-outs and any other admin-internal RPC that doesn't override its own deadline. Workflow-specific call sites (runtime-rule Suspend, Forward) keep their own tuned [...]
-| receiver-runtime-rule | default | - | Hot-update admin endpoint for MAL / LAL rule files. REST routes mount on the `admin-server` HTTP host (port 17128); cluster Suspend/Resume/Forward RPCs ride the `admin-server` gRPC bus (port 17129). **Disabled by default**: leave the selector empty to keep the provider unloade [...]
+| receiver-runtime-rule | default | - | Hot-update admin endpoint for MAL / LAL rule files. REST routes mount on the `admin-server` HTTP host (port 17128); cluster Suspend/Resume/Forward RPCs ride the `admin-server` gRPC bus (port 17129). **Disabled by default**: leave the selector empty to keep the provider unloade [...]
| - | - | refreshRulesPeriod | Period (seconds) of the runtime-rule refresh tick on each OAP node. Every tick the node re-reads the stored rule rows from the storage DAO and reconciles its local state — catch-up for nodes that missed a Suspend/Resume/Forward broadcast (network glitch, restart, late-joining [...]
| - | - | selfHealThresholdSeconds | Time (seconds) a SUSPENDED rule waits before the self-heal backstop forces it back to RUNNING. [...]
-| dsl-debugging | default | - | DSL debug API (sampling debugger for MAL / LAL / OAL rules). REST routes mount on the `admin-server` HTTP host (port 17128) — read-only OAL listing under `/runtime/oal/*`, session control plane under `/dsl-debugging/*`; cluster install/collect/stop RPCs ride the `admin-server` [...]
+| dsl-debugging | default | - | DSL debug API (sampling debugger for MAL / LAL / OAL rules). REST routes mount on the `admin-server` HTTP host (port 17128) — read-only OAL listing under `/runtime/oal/*`, session control plane under `/dsl-debugging/*`; cluster install/collect/stop RPCs ride the `admin-server` [...]
| - | - | injectionEnabled | Boot-time switch for capture-probe emission in the OAL / MAL / LAL code generators. Default `true` — once the operator has enabled the `dsl-debugging` module (which is itself disabled by default via `SW_DSL_DEBUGGING`), probes fire and capture sessions record samples. Set `fal [...]
| ai-pipeline | default | | [...]
| - | - | uriRecognitionServerAddr | The address of the URI recognition server. [...]
diff --git a/docs/en/status/query_alarm_runtime_status.md b/docs/en/status/query_alarm_runtime_status.md
deleted file mode 100644
index 803bce1d21..0000000000
--- a/docs/en/status/query_alarm_runtime_status.md
+++ /dev/null
@@ -1,277 +0,0 @@
-# Get Alarm Runtime Status
-
-OAP calculates the alarm conditions in the memory based on the alarm rules and the metrics data.
-If the OAP cluster has multiple instances, each instance will calculate the alarm conditions independently.
-You can query from any OAP instance to get the all instances' alarm running status.
-
-The following APIs are exposed to make the alerting running kernel visible.
-
-## Get Alarm Running Rules
-
-Return the list of alarm running rules.
-
-- URL, `http://{core restHost}:{core restPort}/status/alarm/rules`
-- HTTP GET method.
-
-```json
-{
- "oapInstances": [
- {
- "address": "127.0.0.1_11800",
- "status": {
- "ruleList": [
- {
- "id": "service_percentile_rule"
- },
- {
- "id": "service_resp_time_rule"
- }
- ]
- }
- },
- {
- "address": "127.0.0.1_11801",
- "status": {
- "ruleList": [
- {
- "id": "service_percentile_rule"
- },
- {
- "id": "service_resp_time_rule"
- }
- ]
- }
- }
- ]
-}
-```
-
-## Get Alarm Running Rule Info
-
-Return the detailed information of the alarm running rule.
-
-- URL, `http://{core restHost}:{core restPort}/status/alarm/rules/{ruleId}`
-- HTTP GET method.
-
-```json
-{
- "oapInstances": [
- {
- "address": "127.0.0.1_11800",
- "status": {
- "ruleId": "service_resp_time_rule",
- "expression": "sum(service_resp_time > 1000) >= 1",
- "period": 10,
- "silencePeriod": 10,
- "recoveryObservationPeriod": 2,
- "additionalPeriod": 0,
- "includeEntityNames": [],
- "excludeEntityNames": [],
- "includeEntityNamesRegex": "",
- "excludeEntityNamesRegex": "",
- "runningEntities": [
- {
- "scope": "SERVICE",
- "name": "mock_b_service",
- "formattedMessage": "Service mock_b_service response time is more than 1000ms of last 10 minutes"
- }
- ],
- "tags": [
- {
- "key": "level",
- "value": "WARNING"
- }
- ],
- "hooks": [
- "webhook.default",
- "wechat.default"
- ],
- "includeMetrics": [
- "service_resp_time"
- ]
- }
- },
- {
- "address": "127.0.0.1_11801",
- "status": {
- "ruleId": "service_resp_time_rule",
- "expression": "sum(service_resp_time > 1000) >= 1",
- "period": 10,
- "silencePeriod": 10,
- "recoveryObservationPeriod": 2,
- "additionalPeriod": 0,
- "includeEntityNames": [],
- "excludeEntityNames": [],
- "includeEntityNamesRegex": "",
- "excludeEntityNamesRegex": "",
- "runningEntities": [
- {
- "scope": "SERVICE",
- "name": "mock_a_service",
- "formattedMessage": "Service mock_a_service response time is more than 1000ms of last 10 minutes."
- },
- {
- "scope": "SERVICE",
- "name": "mock_c_service",
- "formattedMessage": "Service mock_c_service response time is more than 1000ms of last 10 minutes."
- }
- ],
- "tags": [
- {
- "key": "level",
- "value": "WARNING"
- }
- ],
- "hooks": [
- "webhook.default",
- "wechat.default"
- ],
- "includeMetrics": [
- "service_resp_time"
- ]
- }
- }
- ]
-}
-```
-
-- `additonalPeriod` is the additional period if the expression includes the [increase/rate function](../api/metrics-query-expression.md#trend-operation).
-This additional period is used to enlarge window size for calculating the trend value.
-- `runningEntities` is the entities that have metrics data and being calculated by the alarm rule.
-- `formattedMessages` is the result message according to the message template and the affected running entities.
-
-## Get Alarm Running Context
-
-Return the running context of the alarm rule.
-
-- URL, `http://{core restHost}:{core restPort}/status/alarm/{ruleId}/{entityName}`
-- HTTP GET method.
-
-```json
-{
- "oapInstances": [
- {
- "address": "127.0.0.1_11800",
- "status": {
- "ruleId": "service_resp_time_rule",
- "expression": "sum(service_resp_time > 1000) >= 1",
- "endTime": "2025-11-19T15:20:00.000",
- "additionalPeriod": 0,
- "size": 10,
- "silencePeriod": 10,
- "recoveryObservationPeriod": 0,
- "silenceCountdown": 10,
- "recoveryObservationCountdown": 0,
- "currentState": "FIRING",
- "entityName": "mock_b_service",
- "windowValues": [
- {
- "index": 0,
- "metrics": []
- },
- {
- "index": 1,
- "metrics": []
- },
- {
- "index": 2,
- "metrics": []
- },
- {
- "index": 3,
- "metrics": []
- },
- {
- "index": 4,
- "metrics": []
- },
- {
- "index": 5,
- "metrics": []
- },
- {
- "index": 6,
- "metrics": []
- },
- {
- "index": 7,
- "metrics": []
- },
- {
- "index": 8,
- "metrics": [
- {
- "name": "service_resp_time",
- "timeBucket": 202511191519,
- "value": "6000"
- }
- ]
- },
- {
- "index": 9,
- "metrics": []
- }
- ],
- "mqeMetricsSnapshot": {
- "service_resp_time": "[{\"metric\":{\"labels\":[]},\"values\":[{\"id\":\"202511191511\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202511191512\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202511191513\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202511191514\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202511191515\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202511191516\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"2 [...]
- },
- "lastAlarmTime": "1763536823628",
- "lastAlarmMessage": "Service mock_b_service response time is more than 1000ms of last 10 minutes.",
- "lastAlarmMqeMetricsSnapshot": {
- "service_resp_time": "[{\"metric\":{\"labels\":[]},\"values\":[{\"id\":\"202511191511\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202511191512\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202511191513\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202511191514\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202511191515\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202511191516\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"2 [...]
- }
- }
- },
- {
- "address": "127.0.0.1_11801",
- "status": {
- "ruleId": "service_resp_time_rule",
- "expression": "sum(service_resp_time > 1000) >= 1",
- "additionalPeriod": 0,
- "size": 0,
- "silenceCountdown": 0,
- "recoveryObservationCountdown": 0,
- "windowValues": [],
- "lastAlarmTime": 0
- }
- }
- ]
-}
-```
-`size` is the window size. Equal to the `period + additionalPeriod`.
-`silenceCountdown` is the countdown of the silence period. -1 means silence countdown is not running.
-`recoveryObservationCountdown` is the countdown of the recovery observation period.
-`windowValues` is the original metrics data when the metrics come in. The `index` is the index of the window, starting from 0.
-`mqeMetricsSnapshot` is the current metrics data in the MQE format which is generated when executing the checking.
-These data will be calculated according to the expression.
-`lastAlarmTime` is the last time when the alarm is triggered. It will be reset to 0 when the alarm recovers.
-`lastAlarmMessage` is the last alarm message when the alarm is triggered.
-`lastAlarmMqeMetricsSnapshot` is the metrics data snapshot in the MQE format when the last alarm is triggered.
-
-## Get Errors When Querying Status from OAP Instances
-
-If some errors occur when querying the status from OAP instances, the error messages will be returned.
-
-```json
-{
- "oapInstances": [
- {
- "address": "127.0.0.1_11800",
- "status": {
- "ruleList": [
- {
- "id": "service_percentile_rule"
- },
- {
- "id": "service_resp_time_rule"
- }
- ]
- }
- },
- {
- "address": "127.0.0.1_11801",
- "errorMsg": "UNAVAILABLE: io exception"
- }
- ]
-}
-```
\ No newline at end of file
diff --git a/docs/en/status/query_cluster_nodes.md b/docs/en/status/query_cluster_nodes.md
deleted file mode 100644
index 4e700f6e0d..0000000000
--- a/docs/en/status/query_cluster_nodes.md
+++ /dev/null
@@ -1,39 +0,0 @@
-# Get Node List in the Cluster
-
-The OAP cluster is a set of OAP servers that work together to provide a scalable and reliable service. The OAP cluster
-supports [various cluster coordinator](../setup/backend/backend-cluster.md) to manage the cluster membership and the
-communication.
-This API provides capability to query the node list in the cluster from every OAP node perspective. If the cluster
-coordinator doesn't work properly, the node list may be incomplete or incorrect. So, we recommend you to check the
-node list when set up a cluster.
-
-This API is used to get the unified and effective TTL configurations.
-
-- URL, `http://{core restHost}:{core restPort}/status/cluster/nodes`
-- HTTP GET method.
-
-```json
-{
- "nodes": [
- {
- "host": "10.0.12.23",
- "port": 11800,
- "self": true
- },
- {
- "host": "10.0.12.25",
- "port": 11800,
- "self": false
- },
- {
- "host": "10.0.12.37",
- "port": 11800,
- "self": false
- }
- ]
-}
-```
-
-The `nodes` list all the nodes in the cluster. The size of the list should be exactly same as your cluster setup.
-The `host` and `port` are the address of the OAP node, which are used for OAP nodes communicating with each other. The
-`self` is a flag to indicate whether the node is the current node, others are remote nodes.
diff --git a/docs/en/status/query_ttl_setup.md b/docs/en/status/query_ttl_setup.md
deleted file mode 100644
index f5fadd7002..0000000000
--- a/docs/en/status/query_ttl_setup.md
+++ /dev/null
@@ -1,79 +0,0 @@
-# Get Effective TTL Configurations
-
-Time To Live (TTL) mechanism has different behaviors according to different storage implementations. By default, the
-core module provides two TTL configurations: [`recordDataTTL` and `metricsDataTTL`](../setup/backend/ttl.md).
-But some storage implementations could override these settings and provide its own TTL configurations, for example,
-BanyanDB provides its native TTL mechanism to support [progressive TTL](../banyandb/ttl.md) feature and [Data Lifecycle Stages(Hot/Warm/Cold)](../banyandb/stages.md) feature.
-
-
-This API is used to get the unified and effective TTL configurations.
-- URL, `http://{core restHost}:{core restPort}/status/config/ttl`
-- HTTP GET method.
-
-```shell
-> curl -X GET "http://127.0.0.1:12800/status/config/ttl"
-# Metrics TTL includes the definition of the TTL of the metrics-ish data in the storage,
-# Metrics TTL includes the definition of the TTL of the metrics-ish data in the storage,
-# e.g.
-# 1. The metadata of the service, instance, endpoint, topology map, etc.
-# 2. Generated metrics data from OAL and MAL engines.
-# 3. Banyandb storage provides Data Lifecycle Stages(Hot/Warm/Cold).
-#
-# TTLs for each granularity metrics are listed separately.
-#
-# Cover hot and warm data for BanyanDB.
-metrics.minute=7
-metrics.hour=15
-metrics.day=15
-# Cold data, '-1' represents no cold stage data.
-metrics.minute.cold=-1
-metrics.hour.cold=-1
-metrics.day.cold=-1
-
-# Records TTL includes the definition of the TTL of the records data in the storage,
-# Records include traces, logs, sampled slow SQL statements, HTTP requests(by Rover), alarms, etc.
-# Super dataset of records are traces and logs, which volume should be much larger.
-#
-# Cover hot and warm data for BanyanDB.
-records.normal=3
-records.trace=10
-records.zipkinTrace=3
-records.log=3
-records.browserErrorLog=3
-# Cold data, '-1' represents no cold stage data.
-records.normal.cold=-1
-records.trace.cold=30
-records.zipkinTrace.cold=-1
-records.log.cold=-1
-records.browserErrorLog.cold=-1
-```
-
-This API also provides the response in JSON format, which is more friendly for programmatic usage.
-
-```shell
-> curl -X GET "http://127.0.0.1:12800/status/config/ttl" \
- -H "Accept: application/json"
-
-{
- "metrics": {
- "minute": 7,
- "hour": 15,
- "day": 15,
- "coldMinute": -1,
- "coldHour": -1,
- "coldDay": -1
- },
- "records": {
- "normal": 3,
- "trace": 10,
- "zipkinTrace": 3,
- "log": 3,
- "browserErrorLog": 3,
- "coldNormal": -1,
- "coldTrace": 30,
- "coldZipkinTrace": -1,
- "coldLog": -1,
- "coldBrowserErrorLog": -1
- }
-}
-```
diff --git a/docs/en/status/status_apis.md b/docs/en/status/status_apis.md
index 67e144fae0..f72a5cf37b 100644
--- a/docs/en/status/status_apis.md
+++ b/docs/en/status/status_apis.md
@@ -1,34 +1,27 @@
# Status APIs
-Status APIs are a set of APIs that allow you to get the status of the OAP status and measurements of queries.
-They are useful for monitoring the health of the OAP server and to diagnose and troubleshoot issues caused by
-configurations and performance bottlenecks.
-
-Since v10, we begin to add status APIs to help users to understand the status of the OAP server, besides looking the raw
-logs and self-observability solutions.
-
-- [Dump Effective Initial Configurations API](../debugging/config_dump.md)
-- [Tracing Query Execution APIs](../debugging/query-tracing.md)
-- [Get Effective TTL Configurations API](query_ttl_setup.md)
-- [Query Cluster Nodes API](query_cluster_nodes.md)
-- [Get Alarm Runtime Status API](query_alarm_runtime_status.md)
-
-If you have a proposal about new status API, please don't hesitate
-to [create a discussion](https://github.com/apache/skywalking/discussions/new?category=ideas).
-The basic principles for a status API are
-
-1. It should be useful for users to understand the status of the OAP server or the reason of the performance behavior,
- rather than a function feature.
-2. It should be on-demand and not impact the performance of the OAP server too much, especially in the production
- environment.
-3. HTTP APIs are preferred unless there is a special reason to use other.
-
-## Disable Status APIs
-
-By default, this service is open for helping users to debug and diagnose. If you want to disable it, you need to disable
-the whole `status-query` module through setting `selector=-`.
-
-```yaml
-status-query:
- selector: ${SW_STATUS_QUERY:default}
-```
+> **Moved.** As of 10.5.0 the Status API documentation has consolidated under
+> [admin-api/status.md](../setup/backend/admin-api/status.md), alongside the
+> rest of the admin-server endpoints (Inspect, Runtime Rule, DSL Debug).
+>
+> Per-endpoint pages that previously lived here:
+>
+> - **Dump Effective Initial Configurations API** — see
+> [`/debugging/config/dump`](../setup/backend/admin-api/status.md#debuggingconfigdump)
+> in the new consolidated page.
+> - **Tracing Query Execution APIs** — see
+> [`/debugging/query/...`](../setup/backend/admin-api/status.md#debuggingquery)
+> in the new consolidated page.
+> - **Get Effective TTL Configurations API** — see
+> [`/status/config/ttl`](../setup/backend/admin-api/status.md#statusconfigttl).
+> - **Query Cluster Nodes API** — see
+> [`/status/cluster/nodes`](../setup/backend/admin-api/status.md#statusclusternodes).
+> - **Get Alarm Runtime Status API** — see
+> [`/status/alarm/rules`](../setup/backend/admin-api/status.md#statusalarmrules)
+> and the per-rule / per-entity drill-downs.
+>
+> The new Inspect API (10.5.0) is documented
+> at [admin-api/inspect.md](../setup/backend/admin-api/inspect.md).
+
+This stub will be removed in a future minor release. Please update any
+links pointing here.
diff --git a/docs/en/swip/SWIP-14.md b/docs/en/swip/SWIP-14.md
new file mode 100644
index 0000000000..ac3e3b919f
--- /dev/null
+++ b/docs/en/swip/SWIP-14.md
@@ -0,0 +1,764 @@
+# SWIP-14 Inspect API for Admin Server
+
+## Motivation
+
+When operators author OAL / MAL / LAL rules and turn them on (statically or via
+runtime-rule hot-update from SWIP-13), the natural follow-up question is:
+
+- "Which metrics did this rule actually produce?"
+- "For metric `service_xyz`, which entities are emitting values right now?"
+- "Once I have an entity, can I query it via MQE without guessing how to
+ reconstruct the `Entity` shape?"
+
+Today the only way to answer these is to dig through dashboards, write
+exploratory MQE expressions against guessed entity names, or read storage
+directly. There is no first-class "browse" interface.
+
+The public GraphQL surface deliberately does not expose
+"enumerate entities with values for metric X in range R", because that scan is
+unbounded and would compete with user-facing query traffic for storage
+resources. Operators still need it — for debugging rules, validating new
+monitoring features, and onboarding new agents.
+
+This SWIP proposes an **Inspect API** on the existing admin-server
+(SWIP-13's admin-host bundle, default port 17128) that exposes:
+
+1. The metric catalog with type + scope + supported downsamplings.
+2. For a given metric + time range, the set of entities (capped) that hold
+ values, decoded into MQE-ready form.
+
+The output of (2) is shaped so the operator can paste it directly into a
+follow-up MQE call against the existing `MetricsExpressionQuery` GraphQL
+mutation. This SWIP **does not** introduce a new MQE entry point — the public
+GraphQL path remains the single MQE surface.
+
+## Scope
+
+In scope:
+
+- `COMMON_VALUE` and `LABELED_VALUE` metric types — the two `dataType`s
+ that `MQEVisitor.visitMetric` actually dispatches today.
+- `Service`, `ServiceInstance`, `Endpoint`, `ServiceRelation`,
+ `ServiceInstanceRelation`, `EndpointRelation` scopes.
+- All three storage backends: BanyanDB, Elasticsearch, JDBC.
+- `/inspect/metrics` lists every registered metric regardless of type
+ (including `HEATMAP` and `SAMPLED_RECORD`) so operators can see the full
+ catalog. Only `/inspect/entities` is restricted to MQE-dispatchable types.
+- **Companion: relocate + dual-bind the Status API.** The existing
+ `status-query-plugin` (cluster nodes / alarm runtime status / TTL config
+ / debug query trace) is replaced by a new `status` feature module under
+ `server-admin/`. The module registers its handlers on **both** the
+ public REST `HTTPHandlerRegister` (preserving today's binding —
+ skywalking-ui consumes these endpoints on `core.restPort`) **and**, if
+ admin-server is enabled, the admin-server `HTTPHandlerRegister`. The
+ default state remains "status on the public REST port" so the UI keeps
+ working unchanged. No new endpoint is added — clients that need to
+ discover the OAP REST address parse the existing
+ `GET /debugging/config/dump`, which already publishes the effective
+ configuration including REST host and port.
+
+Out of scope (deferred):
+
+- `HISTOGRAM` / `HEATMAP` metrics from `/inspect/entities`. MQE has no
+ dispatch path for histogram dataType — `MQEVisitor.visitMetric` only
+ handles `COMMON_VALUE`, `LABELED_VALUE`, and `SAMPLED_RECORD`; histograms
+ flow through the legacy `MetricsQuery.readHeatMap` GraphQL resolver,
+ which is being phased out. The `mqeEntity` block this API produces would
+ not be usable for them. They remain visible in `/inspect/metrics` (with
+ `type: HEATMAP`) so operators can see they exist.
+- `SAMPLED_RECORD` metrics from `/inspect/entities`. These live in Stream
+ storage (logs, trace segments, browser errors) and are addressed via
+ `IRecordsQueryDAO` / trace-relative queries, not the metrics DAO. A
+ future Inspect-Records endpoint can mirror this design once the scoping
+ question is settled. They remain visible in `/inspect/metrics`.
+- `Process` and `ProcessRelation` scopes. `ProcessID` is a SHA-256 hash with
+ no decoder; readable rendering would require a `ProcessTraffic` join, which
+ we punt on for v1.
+- Firing MQE from admin-server. Operators take the inspect response and call
+ the existing public GraphQL `execExpression` mutation. Keeping MQE on one
+ surface preserves auth/quotas/observability there.
+
+## Architecture
+
+```
+ ┌── operator / swctl / curl ──┐ ┌── skywalking-ui ──┐
+ │ /inspect/* (admin-only) │ │ /status/* (UI) │
+ │ /status/* (admin mirror) │ │ │
+ └─────────────┬───────────────┘ └─────────┬─────────┘
+ │ admin port 17128 │ public REST port 12800
+ ▼ to kee ▼
+ ┌─────────────────────────────────┐ ┌──────────────────────────────┐
+ │ admin-server (Armeria) │ │ public REST (sharing/core) │
+ │ ┌───────────────────────────┐ │ │ GraphQL / MQE / receivers │
+ │ │ inspect feature module │ │ │ │
+ │ │ ├── /inspect/metrics │ │ │ ┌────────────────────────┐ │
+ │ │ └── /inspect/entities │ │ │ │ status feature module │ │
+ │ └───────────────────────────┘ │ │ │ ├── /status/cluster/.. │ │
+ │ │ │ │ ├── /status/alarm/.. │ │
+ │ ┌───────────────────────────┐ │ │ │ ├── /status/config/.. │ │
+ │ │ status feature module │◄─┼───┼───┤ └── /debugging/... │ │
+ │ │ (same handler instance, │ │ │ │ │ │
+ │ │ dual-bound when admin │ │ │ └────────────────────────┘ │
+ │ │ is on; this side is │ │ │ │
+ │ │ silent if admin off) │ │ └──────────────────────────────┘
+ │ └───────────────────────────┘ │
+ └─────────────────────────────────┘
+ │ │
+ ▼ ▼
+ ┌──────────────────────────┐ ┌──────────────────────────────┐
+ │ ValueColumnMetadata │ │ IMetricsQueryDAO │
+ │ StorageModels │ │ .listEntityIdsInRange(...) │ ◄── new method
+ │ MetadataQueryService │ │ (BanyanDB / ES / JDBC impl) │
+ │ DefaultScopeDefine │ └──────────────────────────────┘
+ └──────────────────────────┘
+ │
+ ▼
+ decode entity_id via IDManager.*ID.analysisId(),
+ enrich with layer(s) from MetadataQueryService cache,
+ shape into MQE-ready Entity
+
+ inspect-client flow (admin operator):
+ 1. (one-time) GET admin:17128/debugging/config/dump → parse REST host/port
+ OR rely on a known deployment-time URL.
+ 2. GET admin:17128/inspect/metrics → pick a metric
+ 3. GET admin:17128/inspect/entities?metric=… → get entityId + mqeEntity
+ 4. POST <REST URL>/graphql execExpression( → fire the actual query on
+ entity = mqeEntity, expression = …) the public GraphQL surface
+
+ ui flow (skywalking-ui, unchanged):
+ 1. GET oap:12800/status/cluster, /status/alarm, /status/ttl, …
+ (admin-server may be off — these still work via the public REST binding)
+```
+
+The inspect feature module is a sibling of `dsl-debugging` and
+`runtime-rule` under the existing admin-host. It registers its handlers in
+`start()` against the shared `HTTPHandlerRegister`. No new port, no new auth
+surface.
+
+## API
+
+### `GET /inspect/metrics`
+
+Lists all registered metrics with type, scope catalog, and the downsampling
+levels at which each metric is materialized.
+
+Query parameters:
+
+| Name | Required | Description |
+|-------------|----------|----------------------------------------------------|
+| `regex` | no | Java regex over metric name. Default `.*`. |
+| `type` | no | Filter by `REGULAR_VALUE` / `LABELED_VALUE` / `HEATMAP` / `SAMPLED_RECORD`. Repeatable. |
+| `mqeQueryable` | no | If `true`, return only metrics that `/inspect/entities` accepts (`REGULAR_VALUE`, `LABELED_VALUE`). Default `false`. |
+| `catalog` | no | Filter by catalog name (`SERVICE`, `SERVICE_INSTANCE`, `ENDPOINT`, `SERVICE_RELATION`, `SERVICE_INSTANCE_RELATION`, `ENDPOINT_RELATION`). Repeatable. |
+
+Response (`200 OK`, `application/json`):
+
+```json
+{
+ "metrics": [
+ {
+ "name": "service_cpm",
+ "type": "REGULAR_VALUE",
+ "catalog": "SERVICE",
+ "scopeId": 1,
+ "scope": "Service",
+ "valueColumnName": "value",
+ "downsamplings": ["MINUTE", "HOUR", "DAY"]
+ },
+ {
+ "name": "service_percentile",
+ "type": "LABELED_VALUE",
+ "catalog": "SERVICE",
+ "scopeId": 1,
+ "scope": "Service",
+ "valueColumnName": "value",
+ "downsamplings": ["MINUTE", "HOUR", "DAY"]
+ },
+ {
+ "name": "endpoint_response_time",
+ "type": "HEATMAP",
+ "catalog": "ENDPOINT",
+ "scopeId": 3,
+ "scope": "Endpoint",
+ "valueColumnName": "dataset",
+ "downsamplings": ["MINUTE", "HOUR", "DAY"]
+ }
+ ]
+}
+```
+
+Implementation:
+
+- Source of metric list is `ValueColumnMetadata.INSTANCE.getAllMetadata()`,
+ the same map already exposed via `MetricsMetadataQueryService.listMetrics`.
+- Entries with `Column.ValueDataType.NOT_VALUE` are **filtered out** —
+ these are persisted-but-not-queryable internal columns (topology lines,
+ service entries) per `Column.java`'s contract. They never appear in
+ `/inspect/metrics`.
+- Entries whose scope resolves to `Scope.All` are **filtered out** as well
+ (`Scope.All` is deprecated and not routable through the inspect path).
+- `type` is mapped from `Column.ValueDataType` exactly as
+ `MetricsMetadataQueryService.typeOfMetrics` does today.
+- `scope` is `Scope.Finder.valueOf(scopeId).name()`.
+- `downsamplings` is derived once at startup by scanning
+ `StorageModels.allModels()`, grouping by metric name, collecting
+ `model.getDownsampling()` values into a `Set`. Pure metadata, no storage I/O.
+- This endpoint is safe to poll; results change only when models are
+ registered or removed (boot, runtime-rule add/delete).
+
+### `GET /inspect/entities`
+
+Lists entities that hold values for a metric within a time range, decoded
+into MQE-ready form.
+
+Query parameters:
+
+| Name | Required | Description |
+|-----------|----------|--------------------------------------------------------------------------|
+| `metric` | yes | Metric name (must resolve in `ValueColumnMetadata`). |
+| `start` | yes | Time-range start. Same format as MQE `Duration.start`: `yyyy-MM-dd` (DAY), `yyyy-MM-dd HH` (HOUR), `yyyy-MM-dd HHmm` (MINUTE), `yyyy-MM-dd HHmmss` (SECOND). |
+| `end` | yes | Time-range end. Format mirrors `start`. |
+| `step` | yes | One of `MINUTE`, `HOUR`, `DAY`. Must be one of the metric's `downsamplings`. |
+| `limit` | no | Server-side cap. Default 300, hard-capped at 300. |
+
+`coldStage` is intentionally not exposed. The inspect API targets the
+operationally-active hot path; cold-stage scans bypass the same TTL window
+the operator would be debugging within.
+
+The `limit` is applied as `LIMIT N` at the storage layer — it bounds the
+total rows scanned (N = 300 ≅ 10 buckets × 30 entities), not 300 entities.
+Backends dedup on `entity_id` before returning.
+
+`step` is parsed via `DownSampling.valueOf(step)` after upper-casing and
+must appear in the metric's `supportedDownsamplings` (else 400). The
+`(start, end)` pair is converted to time buckets through the same
+`Duration.getStartTimeBucket()` / `getEndTimeBucket()` path MQE uses, so
+range semantics match exactly.
+
+Response (`200 OK`, `application/json`) — example: `service_cpm` over the
+last 10 minutes, where service `payment` is registered under both `MESH` and
+`GENERAL` layers:
+
+```json
+{
+ "metric": "service_cpm",
+ "scope": "Service",
+ "step": "MINUTE",
+ "start": "2026-05-10 1230",
+ "end": "2026-05-10 1240",
+ "rows": [
+ {
+ "entityId": "cGF5bWVudA==.1",
+ "decoded": { "serviceName": "payment", "isReal": true },
+ "layer": "MESH",
+ "mqeEntity": {
+ "scope": "Service",
+ "serviceName": "payment",
+ "normal": true
+ }
+ },
+ {
+ "entityId": "cGF5bWVudA==.1",
+ "decoded": { "serviceName": "payment", "isReal": true },
+ "layer": "GENERAL",
+ "mqeEntity": {
+ "scope": "Service",
+ "serviceName": "payment",
+ "normal": true
+ }
+ },
+ {
+ "entityId": "Y2hlY2tvdXQ=.1",
+ "decoded": { "serviceName": "checkout", "isReal": true },
+ "layer": "GENERAL",
+ "mqeEntity": {
+ "scope": "Service",
+ "serviceName": "checkout",
+ "normal": true
+ }
+ },
+ {
+ "entityId": "bXlzcWw=.0",
+ "decoded": { "serviceName": "mysql", "isReal": false },
+ "layer": "VIRTUAL_DATABASE",
+ "mqeEntity": {
+ "scope": "Service",
+ "serviceName": "mysql",
+ "normal": false
+ }
+ }
+ ]
+}
+```
+
+An empty result is not an error — `{ "metric": …, "rows": [] }` simply
+means no entity wrote a value in the requested window.
+
+Multi-layer rule: one logical entity emits **one row per layer** it is
+registered under. `payment` appears twice above because
+`ServiceTraffic` has two rows (`payment`/`MESH` and `payment`/`GENERAL`),
+and the inspect API does not collapse them — the operator may want to
+investigate one layer at a time, and `Layer` is meaningful for downstream
+calls such as `listInstances(serviceId)`. The `mqeEntity` block, however, is
+identical across layer rows because MQE itself does not take a layer
+parameter — including it lets the operator copy-paste without thinking.
+
+ServiceInstance example (`service_instance_cpm`):
+
+```json
+{
+ "rows": [
+ {
+ "entityId": "cGF5bWVudA==.1_cG9kLTAx",
+ "decoded": {
+ "serviceName": "payment",
+ "isReal": true,
+ "serviceInstanceName": "pod-01"
+ },
+ "layer": "GENERAL",
+ "mqeEntity": {
+ "scope": "ServiceInstance",
+ "serviceName": "payment",
+ "serviceInstanceName": "pod-01",
+ "normal": true
+ }
+ }
+ ]
+}
+```
+
+ServiceRelation example (`service_relation_client_cpm`):
+
+```json
+{
+ "rows": [
+ {
+ "entityId": "Y2hlY2tvdXQ=.1-cGF5bWVudA==.1",
+ "decoded": {
+ "source": { "serviceName": "checkout", "isReal": true },
+ "destination": { "serviceName": "payment", "isReal": true }
+ },
+ "layer": "GENERAL",
+ "mqeEntity": {
+ "scope": "ServiceRelation",
+ "serviceName": "checkout",
+ "normal": true,
+ "destServiceName": "payment",
+ "destNormal": true
+ }
+ }
+ ]
+}
+```
+
+ServiceInstanceRelation example
+(`service_instance_relation_client_cpm`, e.g., a `consumer` instance
+calling a `provider` instance in the java-agent provider/consumer
+showcase):
+
+```json
+{
+ "rows": [
+ {
+ "entityId": "Y29uc3VtZXI=.1_cG9kLWE=-cHJvdmlkZXI=.1_cG9kLWI=",
+ "decoded": {
+ "source": {
+ "serviceName": "consumer",
+ "isReal": true,
+ "serviceInstanceName": "pod-a"
+ },
+ "destination": {
+ "serviceName": "provider",
+ "isReal": true,
+ "serviceInstanceName": "pod-b"
+ }
+ },
+ "layer": "GENERAL",
+ "mqeEntity": {
+ "scope": "ServiceInstanceRelation",
+ "serviceName": "consumer",
+ "normal": true,
+ "serviceInstanceName": "pod-a",
+ "destServiceName": "provider",
+ "destNormal": true,
+ "destServiceInstanceName": "pod-b"
+ }
+ }
+ ]
+}
+```
+
+EndpointRelation example (`endpoint_relation_cpm`, where consumer's
+`/order` calls provider's `/charge`):
+
+```json
+{
+ "rows": [
+ {
+ "entityId": "Y29uc3VtZXI=.1-L29yZGVy-cHJvdmlkZXI=.1-L2NoYXJnZQ==",
+ "decoded": {
+ "source": {
+ "serviceName": "consumer",
+ "isReal": true,
+ "endpointName": "/order"
+ },
+ "destination": {
+ "serviceName": "provider",
+ "isReal": true,
+ "endpointName": "/charge"
+ }
+ },
+ "layer": "GENERAL",
+ "mqeEntity": {
+ "scope": "EndpointRelation",
+ "serviceName": "consumer",
+ "normal": true,
+ "endpointName": "/order",
+ "destServiceName": "provider",
+ "destNormal": true,
+ "destEndpointName": "/charge"
+ }
+ }
+ ]
+}
+```
+
+Error responses:
+
+| Status | Body | Cause |
+|--------|-------------------------------------------------------------------|------------------------------------------------------|
+| 400 | `{"error":"unknown metric: foo"}` | metric not in `ValueColumnMetadata`. |
+| 400 | `{"error":"step DAY not supported by metric foo (MINUTE,HOUR)"}` | metric not materialized at requested downsampling. |
+| 400 | `{"error":"metric type HEATMAP is not MQE-queryable; /inspect/entities only accepts REGULAR_VALUE and LABELED_VALUE"}` | metric type is `HEATMAP` (HISTOGRAM dataType). |
+| 400 | `{"error":"metric type SAMPLED_RECORD is out of scope for /inspect/entities"}` | metric type is `SAMPLED_RECORD`. |
+| 400 | `{"error":"process scope is out of scope"}` | scope is `Process` / `ProcessRelation`. |
+| 400 | `{"error":"limit must be between 1 and 300"}` | `limit` out of range. |
+
+### Status API relocation
+
+The `status-query-plugin` module currently registers its HTTP handlers
+(`AlarmStatusQueryHandler`, `ClusterStatusQueryHandler`,
+`TTLConfigQueryHandler`, `DebuggingHTTPHandler` for query trace) against
+the public `HTTPHandlerRegister` exposed by the core / sharing-server
+module — the same HTTP surface as GraphQL, on the public REST port
+(`core.restPort`, default `12800`, or the sharing-server port if
+configured). **skywalking-ui calls these endpoints on the public REST
+port today**, so the binding cannot move; it can only be supplemented.
+
+This SWIP replaces `status-query-plugin` with a new feature module
+`status` under `server-admin/` that **dual-binds**:
+
+1. **Always**: registers every handler on the public REST
+ `HTTPHandlerRegister` resolved through `CoreModule` — the same surface
+ `status-query-plugin` uses today, with the same URIs and payloads. UI
+ compatibility is preserved.
+2. **Conditionally**: if `AdminServerModule` is loaded
+ (`SW_ADMIN_SERVER=default`), also registers every handler on the
+ admin-server `HTTPHandlerRegister`. Operators that gateway-protect the
+ admin port can drive status from the same private surface as
+ `/inspect/*`, `/dsl-debugging/*`, `/runtime/rule/*`.
+
+The module is gated by `SW_STATUS=default` and is **enabled by default**
+(matching today's `status-query-plugin` posture so existing UI deployments
+keep working without an opt-in step). It does **not** require
+admin-server: when admin-server is off, status still binds on the public
+REST port — that is the entire point of the dual-bind design.
+
+**Routes hosted by the `status` feature module** — every existing route
+preserved verbatim from `status-query-plugin`. No new endpoint is added
+by this SWIP; clients that need to discover the OAP REST URL parse the
+existing `GET /debugging/config/dump` response, which already publishes
+the effective configuration including REST host and port.
+
+| URI (all `GET`) | Purpose |
+|---|---|
+| `/status/cluster/nodes` | OAP cluster peer list. |
+| `/status/alarm/rules` | Registered alarm rules. |
+| `/status/alarm/{ruleId}` | Per-rule runtime status. |
+| `/status/alarm/{ruleId}/{entityName}` | Per-rule + per-entity runtime status. |
+| `/status/config/ttl` | Effective TTL configuration. |
+| `/debugging/config/dump` | Effective configuration dump (also serves as the REST-URL discovery primitive for inspect clients). |
+| `/debugging/query/mqe` | Run an MQE expression with debug tracing. |
+| `/debugging/query/trace/queryBasicTraces` | Trace query brief, debug-traced. |
+| `/debugging/query/trace/queryTrace` | Trace query detail, debug-traced. |
+| `/debugging/query/zipkin/api/v2/traces` | Zipkin compat brief, debug-traced. |
+| `/debugging/query/zipkin/api/v2/trace` | Zipkin compat detail, debug-traced. |
+| `/debugging/query/topology/getGlobalTopology` | |
+| `/debugging/query/topology/getServicesTopology` | |
+| `/debugging/query/topology/getServiceInstanceTopology` | |
+| `/debugging/query/topology/getEndpointDependencies` | |
+| `/debugging/query/topology/getProcessTopology` | |
+| `/debugging/query/log/queryLogs` | |
+
+Every route is published on both binding surfaces (public REST + admin
+when admin-server is enabled). URI paths, query parameters, and response
+payloads do not change — only the host:port the routes bind on.
+
+## Storage layer
+
+A new abstract method on `IMetricsQueryDAO`:
+
+```java
+List<String> listEntityIdsInRange(String metricName,
+ String valueColumnName,
+ Duration duration,
+ int limit) throws IOException;
+```
+
+Abstract on purpose so any 3rd party storage backend that implements
+`IMetricsQueryDAO` MUST provide this override — a default body would
+let a missing override slip through compilation and surface as silent
+"no entities" or 500 the first time the inspect API hit that backend.
+
+Returns distinct `entity_id`s, ordered by most recent timestamp
+descending, capped at `limit`. Each backend must enforce that ordering
+explicitly — without an explicit sort directive, scoring / index-internal
+order can drop a hot entity that ingested late before the cap is reached.
+Implementations:
+
+- **BanyanDB** (`BanyanDBMetricsQueryDAO`):
+ `MeasureQuery` against the resolved measure (downsampling chosen via
+ `MetadataRegistry.formatName(metricName, downSampling)`), timestamp range
+ filter, tag projection limited to `ENTITY_ID`, value-column field
+ projection, **`setOrderBy(new AbstractQuery.OrderBy(Sort.DESC))`** for
+ timestamp-DESC ordering, `.limit(limit)`. Client-side `LinkedHashSet`
+ dedup preserves the server-side ordering.
+- **Elasticsearch** (`MetricsQueryEsDAO`):
+ range filter on `time_bucket`, **`search.sort(Metrics.TIME_BUCKET, Sort.Order.DESC)`**
+ for timestamp-DESC ordering, `Search.size(limit)`. The downsampling-suffixed
+ index name is resolved through the existing `IndexController` /
+ `MetricsModelInstaller` path (the same name the existing
+ `readMetricsValues` uses for the requested step). Client-side
+ `LinkedHashSet` dedup on the returned hits.
+- **JDBC** (`JDBCMetricsQueryDAO`):
+ `getTablesForRead(metricName, startTimeBucket, endTimeBucket)` resolves
+ the day-partitioned tables. Per table:
+ `SELECT entity_id, MAX(time_bucket) AS latest_time_bucket FROM <table>
+ WHERE table_column = ? AND time_bucket BETWEEN ? AND ? GROUP BY entity_id
+ ORDER BY latest_time_bucket DESC LIMIT ?`. (PostgreSQL rejects
+ `ORDER BY time_bucket` when it isn't in a `SELECT DISTINCT` list, so the
+ GROUP BY shape is the portable one across H2 / MySQL / PostgreSQL.) Per-table
+ results are merged into a `Map<entityId, maxTimeBucket>` with `Math::max`,
+ then sorted by the value DESC and capped at the global `limit` — so a
+ multi-day range cannot fill the result with stale entities from older
+ partitions before newer partitions are queried.
+
+This shape mirrors the existing
+`readLabeledMetricsValuesWithoutEntity(metricName, valueColumnName, labels,
+duration)` (limit hard-coded to 10, projects value as well) — we reuse the
+range-scan and downsampling-resolution scaffolding from those
+implementations.
+
+## Decode & enrich
+
+Per row, the handler:
+
+1. Calls `Scope.Finder.valueOf(scopeId)` to resolve the scope from the
+ metric's metadata.
+2. Switches on the scope to call the correct
+ `IDManager.{ServiceID,ServiceInstanceID,EndpointID}.analysisId(...)` /
+ `analysisRelationId(...)`.
+3. For Service-bearing rows, reads `MetadataQueryService.getService(serviceId)`
+ from the in-memory cache and emits one row per layer in
+ `service.getLayers()`.
+4. Builds the `mqeEntity` block by inverting the decode — same fields
+ `Entity.buildId()` would consume to reconstruct the same `entity_id`.
+
+If a service is missing from the metadata cache (e.g., it was just deleted
+but a metric row still exists in the storage TTL window), the row is still
+emitted with `layer: null` and the decoded names; the operator can still
+call MQE.
+
+## Module layout
+
+Two new feature modules under the existing admin-server bundle:
+
+```
+oap-server/server-admin/inspect/
+├── pom.xml
+├── src/main/java/org/apache/skywalking/oap/server/admin/inspect/
+│ ├── InspectModule.java # ModuleDefine
+│ ├── InspectModuleProvider.java # ModuleProvider
+│ ├── InspectModuleConfig.java
+│ ├── handler/InspectRestHandler.java # Armeria handler
+│ ├── decoder/EntityDecoder.java # scope → IDManager dispatch
+│ └── response/{MetricRow, EntityRow, MqeEntity}.java
+└── src/main/resources/META-INF/services/
+ └── org.apache.skywalking.oap.server.library.module.ModuleDefine
+
+oap-server/server-admin/status/ ◄── relocated from
+ server-query-plugin/status-query-plugin
+├── pom.xml
+├── src/main/java/org/apache/skywalking/oap/server/admin/status/
+│ ├── StatusModule.java
+│ ├── StatusModuleProvider.java
+│ ├── StatusModuleConfig.java
+│ ├── handler/
+│ │ ├── ClusterStatusHandler.java # moved
+│ │ ├── AlarmStatusHandler.java # moved
+│ │ ├── TTLConfigHandler.java # moved
+│ │ └── DebuggingHTTPHandler.java # moved (query trace)
+│ └── service/{AlarmStatusQueryService, …}.java # moved
+└── src/main/resources/META-INF/services/
+ └── org.apache.skywalking.oap.server.library.module.ModuleDefine
+```
+
+`requiredModules()`:
+
+- `inspect`: `CoreModule.NAME`, `StorageModule.NAME`, `AdminServerModule.NAME`.
+ Inspect is admin-only; it has no public-REST mirror and therefore
+ **does** require admin-server.
+- `status`: `CoreModule.NAME` only. **`AdminServerModule.NAME` is
+ consulted reflectively** via `moduleManager.has(...)` for the
+ conditional second handler registration. Declaring it as required
+ would force every status-using deployment to also enable admin-server,
+ which contradicts the "UI keeps working with admin off" goal.
+
+Handler registration in `status`'s `start()`:
+
+```java
+// Always — public REST surface, where the UI calls today.
+HTTPHandlerRegister publicRegister = manager.find(CoreModule.NAME)
+ .provider().getService(HTTPHandlerRegister.class);
+registerAll(publicRegister);
+
+// Additionally, when admin-server is enabled.
+if (manager.has(AdminServerModule.NAME)) {
+ HTTPHandlerRegister adminRegister = manager.find(AdminServerModule.NAME)
+ .provider().getService(HTTPHandlerRegister.class);
+ registerAll(adminRegister);
+}
+```
+
+Inspect's `start()` registers only on the admin-server register
+(mirroring `dsl-debugging` and `runtime-rule`). The old
+`status-query-plugin` module is removed.
+
+### Maven module / pom changes
+
+One module dropped, two added, three existing poms edited. Net: same number of distribution jars, just relocated under `server-admin`.
+
+| Pom | Edit |
+|-----|------|
+| `oap-server/server-admin/pom.xml` | `<modules>` adds `inspect` and `status` (already lists `admin-server`, `runtime-rule`, `dsl-debugging`). |
+| `oap-server/server-query-plugin/pom.xml` | `<modules>` drops `status-query-plugin`. |
+| `oap-server/server-starter/pom.xml` | Removes `<dependency>status-query-plugin</dependency>`; adds `<dependency>status</dependency>` and `<dependency>inspect</dependency>` next to the existing `admin-server` / `runtime-rule` / `dsl-debugging` block. |
+| `oap-server/server-admin/inspect/pom.xml` | **New.** `parent=server-admin`, `packaging=jar`, depends on `server-core`, `admin-server`, `library-server`. |
+| `oap-server/server-admin/status/pom.xml` | **New.** Same parent / packaging; carries the deps the relocated handlers already used (notably `library-server` for the Armeria handler glue). |
+| `oap-server/server-query-plugin/status-query-plugin/` | **Directory deleted** along with the pom. Sources move under `oap-server/server-admin/status/src/main/java/...` (package rename `org.apache.skywalking.oap.query.debug` → `org.apache.skywalking.oap.server.admin.status`). |
+
+## Cost & safety
+
+- `/inspect/metrics` is in-process metadata only — O(N) over registered
+ metrics, no I/O.
+- `/inspect/entities` issues exactly one storage call per request bounded by
+ `LIMIT 300`. The 300 ceiling is enforced server-side and cannot be raised
+ via parameter. This is the same shape as today's
+ `readLabeledMetricsValuesWithoutEntity` (limit 10) just with a higher cap;
+ no new query class is introduced.
+- The endpoint is bound only on the admin port (default 17128), which is
+ not the agent or query port; existing operator-network controls apply.
+- No caching layer in v1. If poll-storms become a problem we can add a
+ short-TTL in-process cache keyed on `(metric,start,end,step,limit)`, but
+ given operators are humans and the cap is 300 rows, premature.
+
+## Compatibility
+
+The dual-bind design keeps the Status API backwards compatible for the
+UI and for any scripted consumer of `http://oap:12800/status/...`.
+
+| Change | Impact |
+|--------|--------|
+| `status-query-plugin` module is removed; replaced by the new `status` feature module under `server-admin/`. The selector renames from the QUERY-plugin form (`SW_QUERY=…,status-query-plugin`) to a top-level `SW_STATUS=default` (on by default). | **Mild.** Custom `application.yml` overrides that referenced `status-query-plugin` need to be repointed; default deployments do not need to change anything. URIs, payloads, and the public REST binding are preserved. |
+| Status API docs move from `docs/en/status/` → `docs/en/setup/backend/admin-api/status.md` (new consolidated page). The `docs/en/status/` directory is removed and `docs/menu.yml` updated; `docs/en/status/status_apis.md` becomes a redirect stub for one minor cycle. | **Doc move only.** No URI or payload churn. |
+| Additive: second handler binding on the admin port (when admin-server is enabled), `/inspect/metrics`, `/inspect/entities`, new `IMetricsQueryDAO.listEntityIdsInRange` method, new modules `inspect` and `status`. | **No impact** on existing deployments. UI continues to call status on the public REST port; admin-port consumers are opt-in via `SW_ADMIN_SERVER=default`. |
+
+The admin-port binding for status is intentionally **additional**, not a
+replacement, so that admin-server may stay off (the default) without
+breaking the UI. A future SWIP can drop the public-REST binding once
+skywalking-ui has migrated to the admin-port path; until then, both
+surfaces co-exist.
+
+## Test plan
+
+The inspect surface is e2e-validated against the existing **java-agent
+e2e** (`test/e2e-v2/cases/simple/...`) — that suite already runs across
+every supported storage backend (BanyanDB, Elasticsearch, JDBC), so
+piggy-backing keeps the matrix free.
+
+The agent showcase boots a `provider` and a `consumer` Java service; this
+gives us live metrics across every scope inspect supports. The flow uses
+`curl` + `jq` directly (no swctl wiring) so the test is portable across
+the existing e2e runners:
+
+1. Wait for the showcase to ingest at least one minute window.
+2. (one-time) `curl http://oap:17128/debugging/config/dump` and parse the
+ response to learn the OAP REST host:port the harness will fire MQE
+ against. Existing endpoint; no new discovery primitive needed.
+3. `curl http://oap:17128/inspect/metrics?regex=service_cpm` →
+ assert `service_cpm` appears with `scope=Service`, `type=REGULAR_VALUE`,
+ `downsamplings` includes `MINUTE`. Repeat for `instance_jvm_memory_*`
+ (ServiceInstance scope) and `instance_jvm_class_loaded_count`
+ (ServiceInstance, REGULAR_VALUE).
+4. `curl http://oap:17128/inspect/entities?metric=service_cpm&start=…&end=…&step=MINUTE`
+ → expect rows for both `provider` and `consumer`. `jq` out one row's
+ `mqeEntity` block.
+5. `POST` MQE expression `service_cpm` against `<discovered MQE URL>`
+ with the extracted `mqeEntity` → assert non-empty time-series result.
+6. Repeat (4)–(5) with `instance_jvm_memory_heap_used` (ServiceInstance
+ scope) — confirm both provider and consumer instances surface, and
+ MQE for the picked instance returns data.
+7. Relation coverage: `/inspect/entities?metric=service_relation_client_cpm`
+ → assert a `consumer→provider` row, then MQE
+ `service_relation_client_cpm` against its `mqeEntity` returns data.
+ Same pattern for `service_instance_relation_client_cpm` and
+ `endpoint_relation_cpm` (when endpoint relations are present in the
+ showcase).
+8. Negative cases: HEATMAP metric (`endpoint_response_time`) → expect
+ 400 from `/inspect/entities`; `step=DAY` against a metric registered
+ only at MINUTE → expect 400 with the supported-downsamplings list.
+
+Module-level unit tests live alongside each new source: `EntityDecoder`
+for every supported scope (covering `isReal=true` and `isReal=false`
+service IDs, multi-layer service merge, missing-from-cache fallback),
+the `/inspect/metrics` filter (NOT_VALUE / Scope.All exclusion),
+and the new DAO method per backend mocked at the storage interface.
+
+## Deliverables
+
+| Item | Location |
+|------|----------|
+| Module sources | `oap-server/server-admin/inspect/`, `oap-server/server-admin/status/` |
+| Storage DAO method | `IMetricsQueryDAO.listEntityIdsInRange` + 3 implementations |
+| Operator doc | `docs/en/setup/backend/admin-api/inspect.md` (new), `admin-api/status.md` (new, consolidates the relocated `docs/en/status/*` pages) |
+| Doc index updates | `docs/en/setup/backend/admin-api/readme.md` (add inspect + status sections), `docs/menu.yml` (drop `docs/en/status/`, add `admin-api/{inspect,status}.md`) |
+| Redirect stub | `docs/en/status/status_apis.md` → one-cycle redirect pointing to `admin-api/status.md` |
+| Changelog | `docs/en/changes/changes-X.X.X.md` — entries for: new `/inspect/*`, status-plugin → status-feature-module relocation + selector rename, new `IMetricsQueryDAO.listEntityIdsInRange` |
+| E2E updates | `test/e2e-v2/cases/simple/...` — add an inspect verification step to the existing java-agent showcase case; runs unchanged across BanyanDB / ES / JDBC matrix |
+| Unit tests | per-module under each new source root |
+
+## Implementation order
+
+The status-relocation work and the inspect-API work share zero code paths
+and can be staged independently:
+
+1. **Phase 1 — status relocation.** Create `server-admin/status` module,
+ move sources, set up dual-bind, delete `status-query-plugin`. UI and
+ existing operator scripts continue to work; URI paths and payloads
+ are unchanged.
+2. **Phase 2 — inspect.** Add `IMetricsQueryDAO.listEntityIdsInRange`
+ across the three backends, then `server-admin/inspect` with handlers
+ and decoder. Independent of Phase 1 — inspect clients that need to
+ discover the OAP REST URL parse `/debugging/config/dump`, an
+ already-existing endpoint preserved by Phase 1.
+
+Each phase is a self-contained PR.
+
+## Future work
+
+- Inspect-Records (`/inspect/records`) for `SAMPLED_RECORD` metrics, backed
+ by `IRecordsQueryDAO`.
+- Inspect-Process for `Process` / `ProcessRelation`, contingent on a
+ decoder for `ProcessID` (likely a `ProcessTraffic` join).
+- Optional `?attribute=k=v` filter pushdown to leverage the same attribute
+ conditions `TopNCondition` accepts.
diff --git a/docs/en/swip/readme.md b/docs/en/swip/readme.md
index b0344ee83e..952bcf8af3 100644
--- a/docs/en/swip/readme.md
+++ b/docs/en/swip/readme.md
@@ -68,10 +68,11 @@ All accepted and proposed SWIPs can be found in [here](https://github.com/apache
## Known SWIPs
-Next SWIP Number: 14
+Next SWIP Number: 15
### Proposed SWIPs
+- [SWIP-14 Inspect API for Admin Server](SWIP-14.md)
- [SWIP-13 Live Debugger for MAL / LAL / OAL](SWIP-13.md)
### Accepted SWIPs
diff --git a/docs/menu.yml b/docs/menu.yml
index 1ad605a84c..494260a3cc 100644
--- a/docs/menu.yml
+++ b/docs/menu.yml
@@ -366,20 +366,6 @@ catalog:
path: "/en/api/traceql-service"
- name: "Health Check API"
path: "/en/api/health-check"
- - name: "Status APIs"
- catalog:
- - name: "Introduction"
- path: "/en/status/status_apis"
- - name: "Dump Effective Initial Configurations"
- path: "/en/debugging/config_dump"
- - name: "Tracing Query Execution"
- path: "/en/debugging/query-tracing"
- - name: "Get Effective TTL Configurations"
- path: "/en/status/query_ttl_setup"
- - name: "Get Node List in the Cluster"
- path: "/en/status/query_cluster_nodes"
- - name: "Get Alarm Runtime Status"
- path: "/en/status/query_alarm_runtime_status"
- name: "Customization"
catalog:
- name: "Overview"
@@ -418,6 +404,18 @@ catalog:
path: "/en/setup/backend/admin-api/dsl-debugging-oal"
- name: "LAL"
path: "/en/setup/backend/admin-api/dsl-debugging-lal"
+ - name: "Status API"
+ catalog:
+ - name: "Overview"
+ path: "/en/setup/backend/admin-api/status"
+ - name: "Dump Effective Initial Configurations"
+ path: "/en/debugging/config_dump"
+ - name: "Tracing Query Execution"
+ path: "/en/debugging/query-tracing"
+ - name: "Inspect API"
+ path: "/en/setup/backend/admin-api/inspect"
+ - name: "Admin UI (Community)"
+ path: "/en/setup/backend/admin-api/admin-ui"
- name: "Security Notice"
path: "/en/security/readme"
- name: "Academy"
diff --git a/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/rest/DSLDebuggingRestHandler.java b/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/rest/DSLDebuggingRestHandler.java
index 62e7fe1928..d0f35b5614 100644
--- a/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/rest/DSLDebuggingRestHandler.java
+++ b/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/rest/DSLDebuggingRestHandler.java
@@ -399,7 +399,6 @@ public class DSLDebuggingRestHandler {
public HttpResponse status() {
final JsonObject body = new JsonObject();
body.addProperty("module", "dsl-debugging");
- body.addProperty("phase", "phase-4");
body.addProperty("nodeId", selfNodeId);
body.addProperty("injectionEnabled", moduleConfig.isInjectionEnabled());
body.addProperty("activeSessions", sessionRegistry.snapshotActive().size());
diff --git a/oap-server/server-query-plugin/status-query-plugin/pom.xml b/oap-server/server-admin/inspect/pom.xml
similarity index 67%
copy from oap-server/server-query-plugin/status-query-plugin/pom.xml
copy to oap-server/server-admin/inspect/pom.xml
index 89be571bf5..05da1ae654 100644
--- a/oap-server/server-query-plugin/status-query-plugin/pom.xml
+++ b/oap-server/server-admin/inspect/pom.xml
@@ -19,35 +19,40 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
- <artifactId>server-query-plugin</artifactId>
+ <artifactId>server-admin</artifactId>
<groupId>org.apache.skywalking</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
- <artifactId>status-query-plugin</artifactId>
+ <artifactId>inspect</artifactId>
<packaging>jar</packaging>
<dependencies>
+ <!--
+ admin-server hosts the HTTP surface inspect binds to (port 17128).
+ Required at compile time for the AdminServerModule constant lookup
+ and at runtime for the HTTPHandlerRegister service.
+ -->
<dependency>
<groupId>org.apache.skywalking</groupId>
- <artifactId>server-core</artifactId>
+ <artifactId>admin-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
- <groupId>org.apache.skywalking</groupId>
- <artifactId>query-graphql-plugin</artifactId>
- <version>${project.version}</version>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter</artifactId>
+ <scope>test</scope>
</dependency>
<dependency>
- <groupId>org.apache.skywalking</groupId>
- <artifactId>zipkin-query-plugin</artifactId>
- <version>${project.version}</version>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
</dependency>
<dependency>
- <groupId>org.apache.skywalking</groupId>
- <artifactId>server-alarm-plugin</artifactId>
- <version>${project.version}</version>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-junit-jupiter</artifactId>
+ <scope>test</scope>
</dependency>
</dependencies>
</project>
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryModule.java b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/InspectModule.java
similarity index 57%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryModule.java
copy to oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/InspectModule.java
index 1fd3494cb4..74b98f367d 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryModule.java
+++ b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/InspectModule.java
@@ -16,18 +16,29 @@
*
*/
-package org.apache.skywalking.oap.query.debug;
+package org.apache.skywalking.oap.server.admin.inspect;
import org.apache.skywalking.oap.server.library.module.ModuleDefine;
-public class StatusQueryModule extends ModuleDefine {
- public static final String NAME = "status-query";
+/**
+ * Inspect feature module — admin-only.
+ *
+ * <p>Hosts {@code GET /inspect/metrics} (the metric catalog) and
+ * {@code GET /inspect/entities} (entities with values for a metric in a time
+ * range, decoded into MQE-ready form). The handlers register on
+ * admin-server's HTTP register; there is no public-REST mirror because
+ * inspect's entity-enumeration scan competes with user-facing query traffic
+ * for storage and is intentionally gated behind the private admin port.
+ */
+public class InspectModule extends ModuleDefine {
+ public static final String NAME = "inspect";
- public StatusQueryModule() {
+ public InspectModule() {
super(NAME);
}
+ @Override
public Class[] services() {
- return new Class[] {AlarmStatusQueryService.class};
+ return new Class[0];
}
}
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryConfig.java b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/InspectModuleConfig.java
similarity index 71%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryConfig.java
copy to oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/InspectModuleConfig.java
index 4be0a9c8a4..af655f3150 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryConfig.java
+++ b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/InspectModuleConfig.java
@@ -16,17 +16,13 @@
*
*/
-package org.apache.skywalking.oap.query.debug;
+package org.apache.skywalking.oap.server.admin.inspect;
import lombok.Getter;
+import lombok.Setter;
import org.apache.skywalking.oap.server.library.module.ModuleConfig;
@Getter
-public class StatusQueryConfig extends ModuleConfig {
- /**
- * Include the list of keywords to filter configurations including secrets. Separate keywords by a comma.
- *
- * @since 9.7.0
- */
- private String keywords4MaskingSecretsOfConfig = "user,password,token,accessKey,secretKey,authentication";
+@Setter
+public class InspectModuleConfig extends ModuleConfig {
}
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryProvider.java b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/InspectModuleProvider.java
similarity index 57%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryProvider.java
rename to oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/InspectModuleProvider.java
index 1527e3fc36..d09e502873 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryProvider.java
+++ b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/InspectModuleProvider.java
@@ -16,76 +16,76 @@
*
*/
-package org.apache.skywalking.oap.query.debug;
+package org.apache.skywalking.oap.server.admin.inspect;
import com.linecorp.armeria.common.HttpMethod;
import java.util.Collections;
+import org.apache.skywalking.oap.server.admin.inspect.handler.InspectRestHandler;
+import org.apache.skywalking.oap.server.admin.server.module.AdminServerModule;
import org.apache.skywalking.oap.server.core.CoreModule;
import org.apache.skywalking.oap.server.core.server.HTTPHandlerRegister;
+import org.apache.skywalking.oap.server.core.storage.StorageModule;
import org.apache.skywalking.oap.server.library.module.ModuleConfig;
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;
-public class StatusQueryProvider extends ModuleProvider {
+public class InspectModuleProvider extends ModuleProvider {
public static final String NAME = "default";
- private StatusQueryConfig config;
+ private InspectModuleConfig config;
+ @Override
public String name() {
return NAME;
}
+ @Override
public Class<? extends ModuleDefine> module() {
- return StatusQueryModule.class;
+ return InspectModule.class;
}
+ @Override
public ConfigCreator<? extends ModuleConfig> newConfigCreator() {
- return new ConfigCreator<StatusQueryConfig>() {
+ return new ConfigCreator<InspectModuleConfig>() {
@Override
- public Class<StatusQueryConfig> type() {
- return StatusQueryConfig.class;
+ public Class<InspectModuleConfig> type() {
+ return InspectModuleConfig.class;
}
@Override
- public void onInitialized(final StatusQueryConfig initialized) {
+ public void onInitialized(final InspectModuleConfig initialized) {
config = initialized;
}
};
}
- public void prepare() throws ServiceNotProvidedException {
- this.registerServiceImplementation(AlarmStatusQueryService.class, new AlarmStatusQueryService(getManager()));
+ @Override
+ public void prepare() throws ServiceNotProvidedException, ModuleStartException {
}
- public void start() throws ServiceNotProvidedException {
- HTTPHandlerRegister service = getManager().find(CoreModule.NAME)
- .provider()
- .getService(HTTPHandlerRegister.class);
- service.addHandler(
- new DebuggingHTTPHandler(getManager(), config),
- Collections.singletonList(HttpMethod.GET)
- );
- service.addHandler(
- new TTLConfigQueryHandler(getManager()),
- Collections.singletonList(HttpMethod.GET)
- );
- service.addHandler(
- new ClusterStatusQueryHandler(getManager()),
- Collections.singletonList(HttpMethod.GET)
- );
- service.addHandler(
- new AlarmStatusQueryHandler(getManager()),
+ @Override
+ public void start() throws ServiceNotProvidedException, ModuleStartException {
+ final HTTPHandlerRegister adminRegister = getManager().find(AdminServerModule.NAME)
+ .provider()
+ .getService(HTTPHandlerRegister.class);
+ adminRegister.addHandler(
+ new InspectRestHandler(getManager()),
Collections.singletonList(HttpMethod.GET)
);
}
+ @Override
public void notifyAfterCompleted() throws ServiceNotProvidedException, ModuleStartException {
-
}
+ @Override
public String[] requiredModules() {
- return new String[] {CoreModule.NAME};
+ return new String[] {
+ CoreModule.NAME,
+ StorageModule.NAME,
+ AdminServerModule.NAME,
+ };
}
}
diff --git a/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/decoder/EntityDecoder.java b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/decoder/EntityDecoder.java
new file mode 100644
index 0000000000..db8ea609e9
--- /dev/null
+++ b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/decoder/EntityDecoder.java
@@ -0,0 +1,232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.admin.inspect.decoder;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.apache.skywalking.oap.server.admin.inspect.response.MqeEntity;
+import org.apache.skywalking.oap.server.core.analysis.IDManager;
+import org.apache.skywalking.oap.server.core.query.enumeration.Scope;
+
+/**
+ * Decodes a stored {@code entity_id} into the named-attribute shape the
+ * inspect API publishes as {@code decoded}, plus the matching
+ * {@link MqeEntity} payload an inspect-API client pastes into the public
+ * GraphQL {@code execExpression} mutation.
+ *
+ * <p>Switches on {@link Scope} to pick the right
+ * {@link IDManager}.{@code *.analysisId(...)} or
+ * {@code *.analysisRelationId(...)} entry point. Scopes that the inspect
+ * API explicitly skips ({@link Scope#All}, {@link Scope#Process},
+ * {@link Scope#ProcessRelation}) throw {@link IllegalArgumentException} —
+ * the caller is expected to filter those before reaching this decoder.
+ */
+public final class EntityDecoder {
+
+ private EntityDecoder() {
+ }
+
+ /**
+ * Decoded payload for a single row plus the MQE-ready entity. The
+ * {@code decoded} map shape is scope-specific:
+ * <ul>
+ * <li>Service / ServiceInstance / Endpoint — single-entity, flat
+ * fields ({@code serviceName}, {@code isReal}, optional
+ * {@code serviceInstanceName} / {@code endpointName}).</li>
+ * <li>ServiceRelation / ServiceInstanceRelation / EndpointRelation —
+ * paired, nested {@code source} and {@code destination} maps.</li>
+ * </ul>
+ */
+ public static final class Decoded {
+ public final Map<String, Object> decodedFields;
+ public final MqeEntity mqeEntity;
+ /**
+ * The serviceId of the row's source entity (or the only entity for
+ * non-relation scopes), used as the cache key for layer enrichment.
+ * {@code null} for scopes with no service-id structure.
+ */
+ public final String serviceIdForLayer;
+
+ Decoded(final Map<String, Object> decodedFields,
+ final MqeEntity mqeEntity,
+ final String serviceIdForLayer) {
+ this.decodedFields = decodedFields;
+ this.mqeEntity = mqeEntity;
+ this.serviceIdForLayer = serviceIdForLayer;
+ }
+ }
+
+ public static Decoded decode(final Scope scope, final String entityId) {
+ switch (scope) {
+ case Service:
+ return decodeService(entityId);
+ case ServiceInstance:
+ return decodeServiceInstance(entityId);
+ case Endpoint:
+ return decodeEndpoint(entityId);
+ case ServiceRelation:
+ return decodeServiceRelation(entityId);
+ case ServiceInstanceRelation:
+ return decodeServiceInstanceRelation(entityId);
+ case EndpointRelation:
+ return decodeEndpointRelation(entityId);
+ default:
+ throw new IllegalArgumentException(
+ "scope " + scope + " is not supported by /inspect/entities");
+ }
+ }
+
+ private static Decoded decodeService(final String entityId) {
+ final IDManager.ServiceID.ServiceIDDefinition def = IDManager.ServiceID.analysisId(entityId);
+ final Map<String, Object> decoded = new LinkedHashMap<>();
+ decoded.put("serviceName", def.getName());
+ decoded.put("isReal", def.isReal());
+ final MqeEntity mqe = MqeEntity.builder()
+ .scope("Service")
+ .serviceName(def.getName())
+ .normal(def.isReal())
+ .build();
+ return new Decoded(decoded, mqe, entityId);
+ }
+
+ private static Decoded decodeServiceInstance(final String entityId) {
+ final IDManager.ServiceInstanceID.InstanceIDDefinition def =
+ IDManager.ServiceInstanceID.analysisId(entityId);
+ final IDManager.ServiceID.ServiceIDDefinition svc =
+ IDManager.ServiceID.analysisId(def.getServiceId());
+ final Map<String, Object> decoded = new LinkedHashMap<>();
+ decoded.put("serviceName", svc.getName());
+ decoded.put("isReal", svc.isReal());
+ decoded.put("serviceInstanceName", def.getName());
+ final MqeEntity mqe = MqeEntity.builder()
+ .scope("ServiceInstance")
+ .serviceName(svc.getName())
+ .normal(svc.isReal())
+ .serviceInstanceName(def.getName())
+ .build();
+ return new Decoded(decoded, mqe, def.getServiceId());
+ }
+
+ private static Decoded decodeEndpoint(final String entityId) {
+ final IDManager.EndpointID.EndpointIDDefinition def =
+ IDManager.EndpointID.analysisId(entityId);
+ final IDManager.ServiceID.ServiceIDDefinition svc =
+ IDManager.ServiceID.analysisId(def.getServiceId());
+ final Map<String, Object> decoded = new LinkedHashMap<>();
+ decoded.put("serviceName", svc.getName());
+ decoded.put("isReal", svc.isReal());
+ decoded.put("endpointName", def.getEndpointName());
+ final MqeEntity mqe = MqeEntity.builder()
+ .scope("Endpoint")
+ .serviceName(svc.getName())
+ .normal(svc.isReal())
+ .endpointName(def.getEndpointName())
+ .build();
+ return new Decoded(decoded, mqe, def.getServiceId());
+ }
+
+ private static Decoded decodeServiceRelation(final String entityId) {
+ final IDManager.ServiceID.ServiceRelationDefine rel =
+ IDManager.ServiceID.analysisRelationId(entityId);
+ final IDManager.ServiceID.ServiceIDDefinition src =
+ IDManager.ServiceID.analysisId(rel.getSourceId());
+ final IDManager.ServiceID.ServiceIDDefinition dst =
+ IDManager.ServiceID.analysisId(rel.getDestId());
+ final Map<String, Object> decoded = new LinkedHashMap<>();
+ decoded.put("source", toServiceMap(src));
+ decoded.put("destination", toServiceMap(dst));
+ final MqeEntity mqe = MqeEntity.builder()
+ .scope("ServiceRelation")
+ .serviceName(src.getName())
+ .normal(src.isReal())
+ .destServiceName(dst.getName())
+ .destNormal(dst.isReal())
+ .build();
+ return new Decoded(decoded, mqe, rel.getSourceId());
+ }
+
+ private static Decoded decodeServiceInstanceRelation(final String entityId) {
+ final IDManager.ServiceInstanceID.ServiceInstanceRelationDefine rel =
+ IDManager.ServiceInstanceID.analysisRelationId(entityId);
+ final IDManager.ServiceInstanceID.InstanceIDDefinition srcInst =
+ IDManager.ServiceInstanceID.analysisId(rel.getSourceId());
+ final IDManager.ServiceInstanceID.InstanceIDDefinition dstInst =
+ IDManager.ServiceInstanceID.analysisId(rel.getDestId());
+ final IDManager.ServiceID.ServiceIDDefinition srcSvc =
+ IDManager.ServiceID.analysisId(srcInst.getServiceId());
+ final IDManager.ServiceID.ServiceIDDefinition dstSvc =
+ IDManager.ServiceID.analysisId(dstInst.getServiceId());
+ final Map<String, Object> decoded = new LinkedHashMap<>();
+ decoded.put("source", toInstanceMap(srcSvc, srcInst));
+ decoded.put("destination", toInstanceMap(dstSvc, dstInst));
+ final MqeEntity mqe = MqeEntity.builder()
+ .scope("ServiceInstanceRelation")
+ .serviceName(srcSvc.getName())
+ .normal(srcSvc.isReal())
+ .serviceInstanceName(srcInst.getName())
+ .destServiceName(dstSvc.getName())
+ .destNormal(dstSvc.isReal())
+ .destServiceInstanceName(dstInst.getName())
+ .build();
+ return new Decoded(decoded, mqe, srcInst.getServiceId());
+ }
+
+ private static Decoded decodeEndpointRelation(final String entityId) {
+ final IDManager.EndpointID.EndpointRelationDefine rel =
+ IDManager.EndpointID.analysisRelationId(entityId);
+ final IDManager.ServiceID.ServiceIDDefinition srcSvc =
+ IDManager.ServiceID.analysisId(rel.getSourceServiceId());
+ final IDManager.ServiceID.ServiceIDDefinition dstSvc =
+ IDManager.ServiceID.analysisId(rel.getDestServiceId());
+ final Map<String, Object> decoded = new LinkedHashMap<>();
+ decoded.put("source", toEndpointMap(srcSvc, rel.getSource()));
+ decoded.put("destination", toEndpointMap(dstSvc, rel.getDest()));
+ final MqeEntity mqe = MqeEntity.builder()
+ .scope("EndpointRelation")
+ .serviceName(srcSvc.getName())
+ .normal(srcSvc.isReal())
+ .endpointName(rel.getSource())
+ .destServiceName(dstSvc.getName())
+ .destNormal(dstSvc.isReal())
+ .destEndpointName(rel.getDest())
+ .build();
+ return new Decoded(decoded, mqe, rel.getSourceServiceId());
+ }
+
+ private static Map<String, Object> toServiceMap(final IDManager.ServiceID.ServiceIDDefinition svc) {
+ final Map<String, Object> map = new LinkedHashMap<>();
+ map.put("serviceName", svc.getName());
+ map.put("isReal", svc.isReal());
+ return map;
+ }
+
+ private static Map<String, Object> toInstanceMap(final IDManager.ServiceID.ServiceIDDefinition svc,
+ final IDManager.ServiceInstanceID.InstanceIDDefinition inst) {
+ final Map<String, Object> map = toServiceMap(svc);
+ map.put("serviceInstanceName", inst.getName());
+ return map;
+ }
+
+ private static Map<String, Object> toEndpointMap(final IDManager.ServiceID.ServiceIDDefinition svc,
+ final String endpointName) {
+ final Map<String, Object> map = toServiceMap(svc);
+ map.put("endpointName", endpointName);
+ return map;
+ }
+}
diff --git a/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/handler/InspectRestHandler.java b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/handler/InspectRestHandler.java
new file mode 100644
index 0000000000..48f29c3737
--- /dev/null
+++ b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/handler/InspectRestHandler.java
@@ -0,0 +1,376 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.admin.inspect.handler;
+
+import com.linecorp.armeria.common.HttpResponse;
+import com.linecorp.armeria.common.HttpStatus;
+import com.linecorp.armeria.common.MediaType;
+import com.linecorp.armeria.server.annotation.Get;
+import com.linecorp.armeria.server.annotation.Param;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import java.util.stream.Collectors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.server.admin.inspect.decoder.EntityDecoder;
+import org.apache.skywalking.oap.server.admin.inspect.response.EntitiesResponse;
+import org.apache.skywalking.oap.server.admin.inspect.response.EntityRow;
+import org.apache.skywalking.oap.server.admin.inspect.response.ErrorResponse;
+import org.apache.skywalking.oap.server.admin.inspect.response.MetricRow;
+import org.apache.skywalking.oap.server.admin.inspect.response.MetricsResponse;
+import org.apache.skywalking.oap.server.core.CoreModule;
+import org.apache.skywalking.oap.server.core.UnexpectedException;
+import org.apache.skywalking.oap.server.core.analysis.DownSampling;
+import org.apache.skywalking.oap.server.core.query.MetadataQueryService;
+import org.apache.skywalking.oap.server.core.query.MetricsMetadataQueryService;
+import org.apache.skywalking.oap.server.core.query.enumeration.MetricsType;
+import org.apache.skywalking.oap.server.core.query.enumeration.Scope;
+import org.apache.skywalking.oap.server.core.query.enumeration.Step;
+import org.apache.skywalking.oap.server.core.query.input.Duration;
+import org.apache.skywalking.oap.server.core.query.type.Service;
+import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
+import org.apache.skywalking.oap.server.core.storage.StorageModule;
+import org.apache.skywalking.oap.server.core.storage.annotation.Column;
+import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
+import org.apache.skywalking.oap.server.core.storage.model.IModelManager;
+import org.apache.skywalking.oap.server.core.storage.model.Model;
+import org.apache.skywalking.oap.server.core.storage.query.IMetricsQueryDAO;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+
+/**
+ * REST handler for the inspect API.
+ *
+ * <p>{@code GET /inspect/metrics} surfaces the metric catalog (filtered to
+ * remove internal NOT_VALUE columns and {@link Scope#All} entries).
+ * {@code GET /inspect/entities} enumerates entities holding values for a
+ * metric in a time range, decoded into MQE-ready form.
+ */
+@Slf4j
+public class InspectRestHandler {
+
+ private static final int LIMIT_DEFAULT = 300;
+ private static final int LIMIT_MAX = 300;
+
+ private final ModuleManager moduleManager;
+
+ public InspectRestHandler(final ModuleManager moduleManager) {
+ this.moduleManager = moduleManager;
+ }
+
+ @Get("/inspect/metrics")
+ public HttpResponse listMetrics(@Param("regex") final Optional<String> regex,
+ @Param("type") final Optional<List<String>> types,
+ @Param("catalog") final Optional<List<String>> catalogs,
+ @Param("mqeQueryable") final Optional<Boolean> mqeQueryableOpt) {
+ final Pattern namePattern;
+ try {
+ namePattern = regex.map(Pattern::compile).orElse(null);
+ } catch (final PatternSyntaxException e) {
+ // Bad operator input: surface as the inspect ErrorResponse shape (400),
+ // matching every other client-input validation in this handler. Without
+ // this catch the framework would render a generic 500.
+ return error(HttpStatus.BAD_REQUEST,
+ "regex is not a valid Java regular expression: " + e.getDescription());
+ }
+ final Set<String> typeFilter = toSet(types);
+ final Set<String> catalogFilter = toSet(catalogs);
+ final boolean mqeQueryable = mqeQueryableOpt.orElse(false);
+
+ // Group all registered Models by metric name → set of downsamplings. One pass.
+ final Map<String, Set<DownSampling>> downsamplingsByMetric =
+ modelManager().allModels().stream()
+ .collect(Collectors.groupingBy(
+ Model::getName,
+ Collectors.mapping(Model::getDownsampling, Collectors.toCollection(HashSet::new))));
+
+ final List<MetricRow> rows = new ArrayList<>();
+ for (final Map.Entry<String, ValueColumnMetadata.ValueColumn> entry
+ : ValueColumnMetadata.INSTANCE.getAllMetadata().entrySet()) {
+ final String name = entry.getKey();
+ final ValueColumnMetadata.ValueColumn vc = entry.getValue();
+
+ // NOT_VALUE: persisted-but-not-queryable internal columns (topology lines,
+ // service entries) per Column.java:93-97. Never appears in inspect.
+ if (vc.getDataType() == Column.ValueDataType.NOT_VALUE) {
+ continue;
+ }
+
+ final Scope scope = Scope.Finder.valueOf(vc.getScopeId());
+ // Scope.All is deprecated and not routable through MQE; skip silently.
+ if (scope == Scope.All) {
+ continue;
+ }
+
+ if (namePattern != null && !namePattern.matcher(name).matches()) {
+ continue;
+ }
+
+ final MetricsType type = MetricsMetadataQueryService.typeOfMetrics(name);
+ final String typeName = type.name();
+ if (!typeFilter.isEmpty() && !typeFilter.contains(typeName)) {
+ continue;
+ }
+ // `mqeQueryable=true` is a strict alias for "row would be accepted by
+ // /inspect/entities". Filter MUST mirror that handler's reject set exactly:
+ // type ∈ {REGULAR_VALUE, LABELED_VALUE} AND scope ∈ {Service, ServiceInstance,
+ // Endpoint, ServiceRelation, ServiceInstanceRelation, EndpointRelation}.
+ // Without the scope check, a Process-scope REGULAR_VALUE (e.g., a continuous-
+ // profiling MAL metric) would survive this filter and then 400 at the entities
+ // endpoint.
+ if (mqeQueryable
+ && (!isMqeDispatchableType(type) || !isSupportedScope(scope))) {
+ continue;
+ }
+
+ final String catalog = DefaultScopeDefine.catalogOf(vc.getScopeId());
+ if (!catalogFilter.isEmpty() && !catalogFilter.contains(catalog)) {
+ continue;
+ }
+
+ final List<String> downs = sortedDownsamplingNames(
+ downsamplingsByMetric.getOrDefault(name, Collections.emptySet()));
+
+ rows.add(new MetricRow(
+ name, typeName, catalog, vc.getScopeId(), scope.name(),
+ vc.getValueCName(), downs));
+ }
+ return HttpResponse.ofJson(MediaType.JSON_UTF_8, new MetricsResponse(rows));
+ }
+
+ @Get("/inspect/entities")
+ public HttpResponse listEntities(@Param("metric") final String metric,
+ @Param("start") final String start,
+ @Param("end") final String end,
+ @Param("step") final String stepStr,
+ @Param("limit") final Optional<Integer> limitOpt) {
+ // Resolve metadata.
+ final Optional<ValueColumnMetadata.ValueColumn> vcOpt =
+ ValueColumnMetadata.INSTANCE.readValueColumnDefinition(metric);
+ if (vcOpt.isEmpty()) {
+ return error(HttpStatus.BAD_REQUEST, "unknown metric: " + metric);
+ }
+ final ValueColumnMetadata.ValueColumn vc = vcOpt.get();
+
+ if (vc.getDataType() == Column.ValueDataType.NOT_VALUE) {
+ return error(HttpStatus.BAD_REQUEST, "unknown metric: " + metric);
+ }
+
+ final MetricsType type = MetricsMetadataQueryService.typeOfMetrics(metric);
+ if (type == MetricsType.HEATMAP) {
+ return error(HttpStatus.BAD_REQUEST,
+ "metric type HEATMAP is not MQE-queryable; /inspect/entities only "
+ + "accepts REGULAR_VALUE and LABELED_VALUE");
+ }
+ if (type == MetricsType.SAMPLED_RECORD) {
+ return error(HttpStatus.BAD_REQUEST,
+ "metric type SAMPLED_RECORD is out of scope for /inspect/entities");
+ }
+
+ final Scope scope = Scope.Finder.valueOf(vc.getScopeId());
+ if (!isSupportedScope(scope)) {
+ if (scope == Scope.Process || scope == Scope.ProcessRelation) {
+ return error(HttpStatus.BAD_REQUEST, "process scope is out of scope");
+ }
+ return error(HttpStatus.BAD_REQUEST, "scope " + scope + " is out of scope");
+ }
+
+ // Validate step. SECOND is rejected explicitly even though Step.valueOf("SECOND")
+ // would parse, because second-precision is not in the inspect API contract — the
+ // docs and error message advertise MINUTE / HOUR / DAY only.
+ final Step step;
+ try {
+ step = Step.valueOf(stepStr.toUpperCase());
+ } catch (Exception e) {
+ return error(HttpStatus.BAD_REQUEST,
+ "step must be one of MINUTE / HOUR / DAY (got " + stepStr + ")");
+ }
+ if (step == Step.SECOND) {
+ return error(HttpStatus.BAD_REQUEST,
+ "step must be one of MINUTE / HOUR / DAY (got SECOND)");
+ }
+ final Set<DownSampling> supported = supportedDownsamplings(metric);
+ if (!supported.contains(stepToDownsampling(step))) {
+ return error(HttpStatus.BAD_REQUEST,
+ "step " + step + " not supported by metric " + metric
+ + " (" + sortedDownsamplingNames(supported) + ")");
+ }
+
+ // Validate limit.
+ final int limit = limitOpt.orElse(LIMIT_DEFAULT);
+ if (limit < 1 || limit > LIMIT_MAX) {
+ return error(HttpStatus.BAD_REQUEST, "limit must be between 1 and " + LIMIT_MAX);
+ }
+
+ final Duration duration = new Duration();
+ duration.setStart(start);
+ duration.setEnd(end);
+ duration.setStep(step);
+
+ // Validate the date strings up front by triggering the same parse the storage path will
+ // run. Joda throws IllegalArgumentException on bad format; the framework's
+ // verifyDateTimeString throws UnexpectedException on unsupported steps. Catch both here
+ // so a malformed `start`/`end` returns 400 instead of bubbling out as a 500.
+ try {
+ duration.getStartTimeBucket();
+ duration.getEndTimeBucket();
+ } catch (IllegalArgumentException | UnexpectedException e) {
+ return error(HttpStatus.BAD_REQUEST,
+ "start / end must follow the step's date format (DAY: yyyy-MM-dd, HOUR: "
+ + "yyyy-MM-dd HH, MINUTE: yyyy-MM-dd HHmm, SECOND: yyyy-MM-dd HHmmss): "
+ + e.getMessage());
+ }
+
+ final List<String> entityIds;
+ try {
+ entityIds = metricsQueryDAO()
+ .listEntityIdsInRange(metric, vc.getValueCName(), duration, limit);
+ } catch (IOException e) {
+ log.warn("listEntityIdsInRange failed for metric={} step={}", metric, step, e);
+ return error(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
+ }
+
+ final List<EntityRow> rows = new ArrayList<>();
+ for (final String entityId : entityIds) {
+ final EntityDecoder.Decoded decoded;
+ try {
+ decoded = EntityDecoder.decode(scope, entityId);
+ } catch (Exception e) {
+ // Malformed id from storage — skip but keep the response well-formed.
+ log.warn("Failed to decode entity_id={} for scope={}", entityId, scope, e);
+ continue;
+ }
+ final List<String> layers = lookupLayers(decoded.serviceIdForLayer);
+ if (layers.isEmpty()) {
+ rows.add(new EntityRow(entityId, decoded.decodedFields, null, decoded.mqeEntity));
+ } else {
+ for (final String layer : layers) {
+ rows.add(new EntityRow(entityId, decoded.decodedFields, layer, decoded.mqeEntity));
+ }
+ }
+ }
+
+ final EntitiesResponse body = new EntitiesResponse(metric, scope.name(),
+ step.name(), start, end, rows);
+ return HttpResponse.ofJson(MediaType.JSON_UTF_8, body);
+ }
+
+ /**
+ * Mirror of the {@code /inspect/entities} type acceptance set. Kept in one place so
+ * the {@code mqeQueryable=true} filter on {@code /inspect/metrics} and the actual
+ * rejection in {@code /inspect/entities} cannot drift.
+ */
+ private static boolean isMqeDispatchableType(final MetricsType type) {
+ return type == MetricsType.REGULAR_VALUE || type == MetricsType.LABELED_VALUE;
+ }
+
+ private boolean isSupportedScope(final Scope scope) {
+ switch (scope) {
+ case Service:
+ case ServiceInstance:
+ case Endpoint:
+ case ServiceRelation:
+ case ServiceInstanceRelation:
+ case EndpointRelation:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private List<String> lookupLayers(final String serviceId) {
+ if (serviceId == null) {
+ return Collections.emptyList();
+ }
+ try {
+ final Service svc = metadataQueryService().getService(serviceId);
+ if (svc == null || svc.getLayers() == null || svc.getLayers().isEmpty()) {
+ return Collections.emptyList();
+ }
+ // Stable ordering across rows so multi-layer responses don't shuffle.
+ return new ArrayList<>(new TreeSet<>(svc.getLayers()));
+ } catch (Exception e) {
+ log.debug("Layer lookup failed for serviceId={}", serviceId, e);
+ return Collections.emptyList();
+ }
+ }
+
+ private static DownSampling stepToDownsampling(final Step step) {
+ switch (step) {
+ case DAY:
+ return DownSampling.Day;
+ case HOUR:
+ return DownSampling.Hour;
+ case MINUTE:
+ return DownSampling.Minute;
+ case SECOND:
+ return DownSampling.Second;
+ default:
+ throw new IllegalArgumentException("unsupported step: " + step);
+ }
+ }
+
+ private Set<DownSampling> supportedDownsamplings(final String metricName) {
+ final Set<DownSampling> result = new HashSet<>();
+ for (final Model m : modelManager().allModels()) {
+ if (metricName.equals(m.getName())) {
+ result.add(m.getDownsampling());
+ }
+ }
+ return result;
+ }
+
+ private static List<String> sortedDownsamplingNames(final Set<DownSampling> set) {
+ // None / Second / Minute / Hour / Day — sort by ordinal so MINUTE precedes HOUR precedes DAY
+ // in the response, matching the API contract example shape.
+ final List<DownSampling> list = new ArrayList<>(set);
+ list.sort((a, b) -> Integer.compare(a.ordinal(), b.ordinal()));
+ return list.stream().map(DownSampling::name).map(String::toUpperCase).collect(Collectors.toList());
+ }
+
+ private static Set<String> toSet(final Optional<List<String>> opt) {
+ if (opt.isEmpty()) {
+ return Collections.emptySet();
+ }
+ return new HashSet<>(opt.get());
+ }
+
+ private static HttpResponse error(final HttpStatus status, final String message) {
+ return HttpResponse.ofJson(status, MediaType.JSON_UTF_8, new ErrorResponse(message));
+ }
+
+ private IModelManager modelManager() {
+ return moduleManager.find(CoreModule.NAME).provider().getService(IModelManager.class);
+ }
+
+ private IMetricsQueryDAO metricsQueryDAO() {
+ return moduleManager.find(StorageModule.NAME).provider().getService(IMetricsQueryDAO.class);
+ }
+
+ private MetadataQueryService metadataQueryService() {
+ return moduleManager.find(CoreModule.NAME).provider().getService(MetadataQueryService.class);
+ }
+}
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/trace/DebuggingQueryTraceRsp.java b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/EntitiesResponse.java
similarity index 70%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/trace/DebuggingQueryTraceRsp.java
copy to oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/EntitiesResponse.java
index 0da174650f..888cd8de73 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/trace/DebuggingQueryTraceRsp.java
+++ b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/EntitiesResponse.java
@@ -16,17 +16,19 @@
*
*/
-package org.apache.skywalking.oap.query.debug.trace;
+package org.apache.skywalking.oap.server.admin.inspect.response;
import java.util.List;
+import lombok.AllArgsConstructor;
import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.oap.query.debug.DebuggingTraceRsp;
-import org.apache.skywalking.oap.server.core.query.type.Span;
-@RequiredArgsConstructor
@Getter
-public class DebuggingQueryTraceRsp {
- private final List<Span> spans;
- private final DebuggingTraceRsp debuggingTrace;
+@AllArgsConstructor
+public class EntitiesResponse {
+ private final String metric;
+ private final String scope;
+ private final String step;
+ private final String start;
+ private final String end;
+ private final List<EntityRow> rows;
}
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/DebuggingTraceRsp.java b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/EntityRow.java
similarity index 52%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/DebuggingTraceRsp.java
copy to oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/EntityRow.java
index aa77970913..544af684be 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/DebuggingTraceRsp.java
+++ b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/EntityRow.java
@@ -16,18 +16,26 @@
*
*/
-package org.apache.skywalking.oap.query.debug;
+package org.apache.skywalking.oap.server.admin.inspect.response;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import java.util.Map;
+import lombok.AllArgsConstructor;
import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-@RequiredArgsConstructor
+/**
+ * One row of {@code GET /inspect/entities}. {@code decoded} carries the
+ * scope-specific human-readable shape (single entity or source/destination
+ * pair); {@code layer} surfaces the layer of the source service for the
+ * row's scope (multi-layer services emit one row per layer);
+ * {@code mqeEntity} is the MQE-ready payload.
+ */
@Getter
-public class DebuggingTraceRsp {
- private final String traceId;
- private final String condition;
- private final long startTime;
- private final long endTime;
- private final long duration;
- private final DebuggingSpanRsp rootSpan;
+@AllArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class EntityRow {
+ private final String entityId;
+ private final Map<String, Object> decoded;
+ private final String layer;
+ private final MqeEntity mqeEntity;
}
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryModule.java b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/ErrorResponse.java
similarity index 67%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryModule.java
copy to oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/ErrorResponse.java
index 1fd3494cb4..76a70123f1 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryModule.java
+++ b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/ErrorResponse.java
@@ -16,18 +16,13 @@
*
*/
-package org.apache.skywalking.oap.query.debug;
+package org.apache.skywalking.oap.server.admin.inspect.response;
-import org.apache.skywalking.oap.server.library.module.ModuleDefine;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
-public class StatusQueryModule extends ModuleDefine {
- public static final String NAME = "status-query";
-
- public StatusQueryModule() {
- super(NAME);
- }
-
- public Class[] services() {
- return new Class[] {AlarmStatusQueryService.class};
- }
+@Getter
+@AllArgsConstructor
+public class ErrorResponse {
+ private final String error;
}
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/mqe/DebuggingMQERsp.java b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/MetricRow.java
similarity index 56%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/mqe/DebuggingMQERsp.java
copy to oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/MetricRow.java
index cafa1752b2..ef5e184451 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/mqe/DebuggingMQERsp.java
+++ b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/MetricRow.java
@@ -16,20 +16,26 @@
*
*/
-package org.apache.skywalking.oap.query.debug.mqe;
+package org.apache.skywalking.oap.server.admin.inspect.response;
import java.util.List;
+import lombok.AllArgsConstructor;
import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.oap.server.core.query.mqe.ExpressionResultType;
-import org.apache.skywalking.oap.server.core.query.mqe.MQEValues;
-import org.apache.skywalking.oap.query.debug.DebuggingTraceRsp;
-@RequiredArgsConstructor
+/**
+ * One row of {@code GET /inspect/metrics}. Each registered metric in
+ * {@code ValueColumnMetadata} surfaces as one row, except entries that are
+ * filtered out (NOT_VALUE persistent-but-not-queryable columns, and
+ * {@code Scope.All} deprecated entries).
+ */
@Getter
-public class DebuggingMQERsp {
- private final ExpressionResultType type;
- private final List<MQEValues> results;
- private final String error;
- private final DebuggingTraceRsp debuggingTrace;
+@AllArgsConstructor
+public class MetricRow {
+ private final String name;
+ private final String type;
+ private final String catalog;
+ private final int scopeId;
+ private final String scope;
+ private final String valueColumnName;
+ private final List<String> downsamplings;
}
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/DebuggingTraceRsp.java b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/MetricsResponse.java
similarity index 70%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/DebuggingTraceRsp.java
copy to oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/MetricsResponse.java
index aa77970913..f2b4c7d2d8 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/DebuggingTraceRsp.java
+++ b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/MetricsResponse.java
@@ -16,18 +16,14 @@
*
*/
-package org.apache.skywalking.oap.query.debug;
+package org.apache.skywalking.oap.server.admin.inspect.response;
+import java.util.List;
+import lombok.AllArgsConstructor;
import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-@RequiredArgsConstructor
@Getter
-public class DebuggingTraceRsp {
- private final String traceId;
- private final String condition;
- private final long startTime;
- private final long endTime;
- private final long duration;
- private final DebuggingSpanRsp rootSpan;
+@AllArgsConstructor
+public class MetricsResponse {
+ private final List<MetricRow> metrics;
}
diff --git a/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/MqeEntity.java b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/MqeEntity.java
new file mode 100644
index 0000000000..6c089e0e5c
--- /dev/null
+++ b/oap-server/server-admin/inspect/src/main/java/org/apache/skywalking/oap/server/admin/inspect/response/MqeEntity.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.admin.inspect.response;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+/**
+ * The MQE Entity payload an inspect-API client pastes into the public
+ * GraphQL {@code execExpression} mutation. Mirrors
+ * {@link org.apache.skywalking.oap.server.core.query.input.Entity}'s field
+ * set; {@code null} fields are omitted from the JSON so each scope's row
+ * includes only the fields {@code Entity.isValid()} requires.
+ */
+@Getter
+@Builder
+@AllArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class MqeEntity {
+ private final String scope;
+ private final String serviceName;
+ private final Boolean normal;
+ private final String serviceInstanceName;
+ private final String endpointName;
+ private final String destServiceName;
+ private final Boolean destNormal;
+ private final String destServiceInstanceName;
+ private final String destEndpointName;
+}
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider b/oap-server/server-admin/inspect/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
similarity index 92%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
copy to oap-server/server-admin/inspect/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
index 94780edbbf..0e657588e7 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
+++ b/oap-server/server-admin/inspect/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
@@ -16,4 +16,4 @@
#
#
-org.apache.skywalking.oap.query.debug.StatusQueryProvider
\ No newline at end of file
+org.apache.skywalking.oap.server.admin.inspect.InspectModule
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider b/oap-server/server-admin/inspect/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
similarity index 91%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
copy to oap-server/server-admin/inspect/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
index 94780edbbf..06763571ed 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
+++ b/oap-server/server-admin/inspect/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
@@ -16,4 +16,4 @@
#
#
-org.apache.skywalking.oap.query.debug.StatusQueryProvider
\ No newline at end of file
+org.apache.skywalking.oap.server.admin.inspect.InspectModuleProvider
diff --git a/oap-server/server-admin/inspect/src/test/java/org/apache/skywalking/oap/server/admin/inspect/decoder/EntityDecoderTest.java b/oap-server/server-admin/inspect/src/test/java/org/apache/skywalking/oap/server/admin/inspect/decoder/EntityDecoderTest.java
new file mode 100644
index 0000000000..890c8bafba
--- /dev/null
+++ b/oap-server/server-admin/inspect/src/test/java/org/apache/skywalking/oap/server/admin/inspect/decoder/EntityDecoderTest.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.admin.inspect.decoder;
+
+import java.util.Map;
+import org.apache.skywalking.oap.server.admin.inspect.response.MqeEntity;
+import org.apache.skywalking.oap.server.core.analysis.IDManager;
+import org.apache.skywalking.oap.server.core.query.enumeration.Scope;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Round-trip tests: build an entity_id with the same {@link IDManager} entry points
+ * the framework uses, decode it back through {@link EntityDecoder}, and verify the
+ * decoded names + isReal flag + the MQE-ready entity payload match.
+ */
+class EntityDecoderTest {
+
+ @Test
+ void serviceNormal() {
+ final String id = IDManager.ServiceID.buildId("payment", true);
+ final EntityDecoder.Decoded d = EntityDecoder.decode(Scope.Service, id);
+
+ assertEquals("payment", d.decodedFields.get("serviceName"));
+ assertEquals(Boolean.TRUE, d.decodedFields.get("isReal"));
+ final MqeEntity e = d.mqeEntity;
+ assertEquals("Service", e.getScope());
+ assertEquals("payment", e.getServiceName());
+ assertEquals(Boolean.TRUE, e.getNormal());
+ assertEquals(id, d.serviceIdForLayer);
+ }
+
+ @Test
+ void serviceConjectured() {
+ // "isNormal=false" — virtual / conjectured service (e.g., a database we detected
+ // from a span tag instead of an agent installation).
+ final String id = IDManager.ServiceID.buildId("mysql", false);
+ final EntityDecoder.Decoded d = EntityDecoder.decode(Scope.Service, id);
+
+ assertEquals("mysql", d.decodedFields.get("serviceName"));
+ assertEquals(Boolean.FALSE, d.decodedFields.get("isReal"));
+ assertEquals(Boolean.FALSE, d.mqeEntity.getNormal());
+ }
+
+ @Test
+ void serviceInstance() {
+ final String svcId = IDManager.ServiceID.buildId("payment", true);
+ final String id = IDManager.ServiceInstanceID.buildId(svcId, "pod-01");
+ final EntityDecoder.Decoded d = EntityDecoder.decode(Scope.ServiceInstance, id);
+
+ assertEquals("payment", d.decodedFields.get("serviceName"));
+ assertEquals(Boolean.TRUE, d.decodedFields.get("isReal"));
+ assertEquals("pod-01", d.decodedFields.get("serviceInstanceName"));
+ assertEquals("ServiceInstance", d.mqeEntity.getScope());
+ assertEquals("payment", d.mqeEntity.getServiceName());
+ assertEquals("pod-01", d.mqeEntity.getServiceInstanceName());
+ assertEquals(svcId, d.serviceIdForLayer);
+ }
+
+ @Test
+ void endpoint() {
+ final String svcId = IDManager.ServiceID.buildId("payment", true);
+ final String id = IDManager.EndpointID.buildId(svcId, "POST:/charge");
+ final EntityDecoder.Decoded d = EntityDecoder.decode(Scope.Endpoint, id);
+
+ assertEquals("payment", d.decodedFields.get("serviceName"));
+ assertEquals("POST:/charge", d.decodedFields.get("endpointName"));
+ assertEquals("Endpoint", d.mqeEntity.getScope());
+ assertEquals("POST:/charge", d.mqeEntity.getEndpointName());
+ assertEquals(svcId, d.serviceIdForLayer);
+ }
+
+ @Test
+ void serviceRelation() {
+ final String src = IDManager.ServiceID.buildId("checkout", true);
+ final String dst = IDManager.ServiceID.buildId("payment", true);
+ final String id = IDManager.ServiceID.buildRelationId(
+ new IDManager.ServiceID.ServiceRelationDefine(src, dst));
+ final EntityDecoder.Decoded d = EntityDecoder.decode(Scope.ServiceRelation, id);
+
+ @SuppressWarnings("unchecked") final Map<String, Object> srcMap =
+ (Map<String, Object>) d.decodedFields.get("source");
+ @SuppressWarnings("unchecked") final Map<String, Object> dstMap =
+ (Map<String, Object>) d.decodedFields.get("destination");
+ assertEquals("checkout", srcMap.get("serviceName"));
+ assertEquals(Boolean.TRUE, srcMap.get("isReal"));
+ assertEquals("payment", dstMap.get("serviceName"));
+
+ assertEquals("ServiceRelation", d.mqeEntity.getScope());
+ assertEquals("checkout", d.mqeEntity.getServiceName());
+ assertEquals("payment", d.mqeEntity.getDestServiceName());
+ assertEquals(src, d.serviceIdForLayer);
+ }
+
+ @Test
+ void serviceInstanceRelation() {
+ final String srcSvc = IDManager.ServiceID.buildId("consumer", true);
+ final String dstSvc = IDManager.ServiceID.buildId("provider", true);
+ final String srcInst = IDManager.ServiceInstanceID.buildId(srcSvc, "pod-a");
+ final String dstInst = IDManager.ServiceInstanceID.buildId(dstSvc, "pod-b");
+ final String id = IDManager.ServiceInstanceID.buildRelationId(
+ new IDManager.ServiceInstanceID.ServiceInstanceRelationDefine(srcInst, dstInst));
+ final EntityDecoder.Decoded d = EntityDecoder.decode(Scope.ServiceInstanceRelation, id);
+
+ @SuppressWarnings("unchecked") final Map<String, Object> srcMap =
+ (Map<String, Object>) d.decodedFields.get("source");
+ @SuppressWarnings("unchecked") final Map<String, Object> dstMap =
+ (Map<String, Object>) d.decodedFields.get("destination");
+ assertEquals("consumer", srcMap.get("serviceName"));
+ assertEquals("pod-a", srcMap.get("serviceInstanceName"));
+ assertEquals("provider", dstMap.get("serviceName"));
+ assertEquals("pod-b", dstMap.get("serviceInstanceName"));
+
+ assertEquals("consumer", d.mqeEntity.getServiceName());
+ assertEquals("pod-a", d.mqeEntity.getServiceInstanceName());
+ assertEquals("provider", d.mqeEntity.getDestServiceName());
+ assertEquals("pod-b", d.mqeEntity.getDestServiceInstanceName());
+ assertEquals(srcSvc, d.serviceIdForLayer);
+ }
+
+ @Test
+ void endpointRelation() {
+ final String srcSvc = IDManager.ServiceID.buildId("consumer", true);
+ final String dstSvc = IDManager.ServiceID.buildId("provider", true);
+ final String id = IDManager.EndpointID.buildRelationId(
+ new IDManager.EndpointID.EndpointRelationDefine(srcSvc, "/order", dstSvc, "/charge"));
+ final EntityDecoder.Decoded d = EntityDecoder.decode(Scope.EndpointRelation, id);
+
+ @SuppressWarnings("unchecked") final Map<String, Object> srcMap =
+ (Map<String, Object>) d.decodedFields.get("source");
+ @SuppressWarnings("unchecked") final Map<String, Object> dstMap =
+ (Map<String, Object>) d.decodedFields.get("destination");
+ assertEquals("consumer", srcMap.get("serviceName"));
+ assertEquals("/order", srcMap.get("endpointName"));
+ assertEquals("provider", dstMap.get("serviceName"));
+ assertEquals("/charge", dstMap.get("endpointName"));
+
+ assertEquals("consumer", d.mqeEntity.getServiceName());
+ assertEquals("/order", d.mqeEntity.getEndpointName());
+ assertEquals("provider", d.mqeEntity.getDestServiceName());
+ assertEquals("/charge", d.mqeEntity.getDestEndpointName());
+ assertEquals(srcSvc, d.serviceIdForLayer);
+ }
+
+ @Test
+ void unsupportedScopesReject() {
+ // Process / ProcessRelation / All are explicitly out of v1 scope.
+ assertThrows(IllegalArgumentException.class,
+ () -> EntityDecoder.decode(Scope.Process, "deadbeef"));
+ assertThrows(IllegalArgumentException.class,
+ () -> EntityDecoder.decode(Scope.ProcessRelation, "deadbeef-cafebabe"));
+ assertThrows(IllegalArgumentException.class,
+ () -> EntityDecoder.decode(Scope.All, "anything"));
+ }
+
+ @Test
+ void serviceMqeEntityOmitsRelationFields() {
+ // Sanity check the @JsonInclude(NON_NULL) annotation on MqeEntity does what we expect:
+ // a Service-scope payload should not carry destServiceName / destNormal / instance /
+ // endpoint fields. We assert by checking the field accessors directly.
+ final EntityDecoder.Decoded d = EntityDecoder.decode(
+ Scope.Service, IDManager.ServiceID.buildId("a", true));
+ assertNull(d.mqeEntity.getDestServiceName());
+ assertNull(d.mqeEntity.getDestNormal());
+ assertNull(d.mqeEntity.getServiceInstanceName());
+ assertNull(d.mqeEntity.getEndpointName());
+ assertTrue(d.mqeEntity.getNormal());
+ }
+}
diff --git a/oap-server/server-admin/pom.xml b/oap-server/server-admin/pom.xml
index bad66392f4..796b07444d 100644
--- a/oap-server/server-admin/pom.xml
+++ b/oap-server/server-admin/pom.xml
@@ -31,6 +31,8 @@
<module>admin-server</module>
<module>runtime-rule</module>
<module>dsl-debugging</module>
+ <module>status</module>
+ <module>inspect</module>
</modules>
<dependencies>
diff --git a/oap-server/server-query-plugin/status-query-plugin/pom.xml b/oap-server/server-admin/status/pom.xml
similarity index 55%
rename from oap-server/server-query-plugin/status-query-plugin/pom.xml
rename to oap-server/server-admin/status/pom.xml
index 89be571bf5..fe835aa1f6 100644
--- a/oap-server/server-query-plugin/status-query-plugin/pom.xml
+++ b/oap-server/server-admin/status/pom.xml
@@ -19,35 +19,71 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
- <artifactId>server-query-plugin</artifactId>
+ <artifactId>server-admin</artifactId>
<groupId>org.apache.skywalking</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
- <artifactId>status-query-plugin</artifactId>
+ <artifactId>status</artifactId>
<packaging>jar</packaging>
<dependencies>
+ <!--
+ admin-server is consulted reflectively at start() time via
+ moduleManager.has(AdminServerModule.NAME). The dep is required at
+ compile-time for the constant lookup; the runtime binding is
+ conditional so a deployment can run status without admin-server
+ enabled (e.g., the UI continues to call /status/* on the public
+ REST port even when SW_ADMIN_SERVER is empty).
+ -->
<dependency>
<groupId>org.apache.skywalking</groupId>
- <artifactId>server-core</artifactId>
+ <artifactId>admin-server</artifactId>
<version>${project.version}</version>
</dependency>
+ <!--
+ query-graphql-plugin: status reuses the GraphQL resolvers' query
+ services to render trace / topology / log / mqe debugging payloads
+ on the HTTP debug-trace endpoints.
+ -->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>query-graphql-plugin</artifactId>
<version>${project.version}</version>
</dependency>
+ <!--
+ zipkin-query-plugin: zipkin-compat debug-trace endpoints route
+ through the same handlers Zipkin queries do.
+ -->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>zipkin-query-plugin</artifactId>
<version>${project.version}</version>
</dependency>
+ <!--
+ server-alarm-plugin: alarm runtime status queries inspect the
+ alarm provider's in-memory rule + window state.
+ -->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>server-alarm-plugin</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-junit-jupiter</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/AlarmStatusQueryHandler.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/AlarmStatusQueryHandler.java
similarity index 95%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/AlarmStatusQueryHandler.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/AlarmStatusQueryHandler.java
index 4a87fff324..d858940167 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/AlarmStatusQueryHandler.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/AlarmStatusQueryHandler.java
@@ -16,7 +16,7 @@
*
*/
-package org.apache.skywalking.oap.query.debug;
+package org.apache.skywalking.oap.server.admin.status;
import com.google.gson.Gson;
import com.linecorp.armeria.common.HttpResponse;
@@ -40,7 +40,7 @@ public class AlarmStatusQueryHandler {
private AlarmStatusQueryService getQueryService() {
if (queryService == null) {
- queryService = moduleManager.find(StatusQueryModule.NAME)
+ queryService = moduleManager.find(StatusModule.NAME)
.provider().getService(AlarmStatusQueryService.class);
}
return queryService;
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/AlarmStatusQueryService.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/AlarmStatusQueryService.java
similarity index 99%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/AlarmStatusQueryService.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/AlarmStatusQueryService.java
index f467af3505..6e8a0fea29 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/AlarmStatusQueryService.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/AlarmStatusQueryService.java
@@ -16,7 +16,7 @@
*
*/
-package org.apache.skywalking.oap.query.debug;
+package org.apache.skywalking.oap.server.admin.status;
import com.google.gson.Gson;
import java.util.List;
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/ClusterStatusQueryHandler.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/ClusterStatusQueryHandler.java
similarity index 97%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/ClusterStatusQueryHandler.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/ClusterStatusQueryHandler.java
index 3beaa4a46b..651754caab 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/ClusterStatusQueryHandler.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/ClusterStatusQueryHandler.java
@@ -16,7 +16,7 @@
*
*/
-package org.apache.skywalking.oap.query.debug;
+package org.apache.skywalking.oap.server.admin.status;
import com.linecorp.armeria.common.HttpRequest;
import com.linecorp.armeria.server.annotation.ExceptionHandler;
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/DebuggingHTTPHandler.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/DebuggingHTTPHandler.java
similarity index 96%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/DebuggingHTTPHandler.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/DebuggingHTTPHandler.java
index 5c78e0e4a9..e6510c5d68 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/DebuggingHTTPHandler.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/DebuggingHTTPHandler.java
@@ -16,7 +16,7 @@
*
*/
-package org.apache.skywalking.oap.query.debug;
+package org.apache.skywalking.oap.server.admin.status;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
@@ -42,16 +42,16 @@ import java.util.Optional;
import java.util.stream.Collectors;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
-import org.apache.skywalking.oap.query.debug.log.DebuggingQueryLogsRsp;
-import org.apache.skywalking.oap.query.debug.mqe.DebuggingMQERsp;
-import org.apache.skywalking.oap.query.debug.topology.DebuggingQueryEndpointTopologyRsp;
-import org.apache.skywalking.oap.query.debug.topology.DebuggingQueryInstanceTopologyRsp;
-import org.apache.skywalking.oap.query.debug.topology.DebuggingQueryProcessTopologyRsp;
-import org.apache.skywalking.oap.query.debug.topology.DebuggingQueryServiceTopologyRsp;
-import org.apache.skywalking.oap.query.debug.trace.DebuggingQueryTraceBriefRsp;
-import org.apache.skywalking.oap.query.debug.trace.DebuggingQueryTraceRsp;
-import org.apache.skywalking.oap.query.debug.trace.zipkin.DebuggingZipkinQueryTraceRsp;
-import org.apache.skywalking.oap.query.debug.trace.zipkin.DebuggingZipkinQueryTracesRsp;
+import org.apache.skywalking.oap.server.admin.status.log.DebuggingQueryLogsRsp;
+import org.apache.skywalking.oap.server.admin.status.mqe.DebuggingMQERsp;
+import org.apache.skywalking.oap.server.admin.status.topology.DebuggingQueryEndpointTopologyRsp;
+import org.apache.skywalking.oap.server.admin.status.topology.DebuggingQueryInstanceTopologyRsp;
+import org.apache.skywalking.oap.server.admin.status.topology.DebuggingQueryProcessTopologyRsp;
+import org.apache.skywalking.oap.server.admin.status.topology.DebuggingQueryServiceTopologyRsp;
+import org.apache.skywalking.oap.server.admin.status.trace.DebuggingQueryTraceBriefRsp;
+import org.apache.skywalking.oap.server.admin.status.trace.DebuggingQueryTraceRsp;
+import org.apache.skywalking.oap.server.admin.status.trace.zipkin.DebuggingZipkinQueryTraceRsp;
+import org.apache.skywalking.oap.server.admin.status.trace.zipkin.DebuggingZipkinQueryTracesRsp;
import org.apache.skywalking.oap.query.graphql.resolver.LogQuery;
import org.apache.skywalking.oap.query.graphql.resolver.MetricsExpressionQuery;
import org.apache.skywalking.oap.query.graphql.resolver.TopologyQuery;
@@ -98,9 +98,9 @@ public class DebuggingHTTPHandler {
private final ZipkinQueryHandler zipkinQueryHandler;
private final TopologyQuery topologyQuery;
private final LogQuery logQuery;
- final StatusQueryConfig config;
+ final StatusModuleConfig config;
- public DebuggingHTTPHandler(final ModuleManager manager, final StatusQueryConfig config) {
+ public DebuggingHTTPHandler(final ModuleManager manager, final StatusModuleConfig config) {
serverStatusService = manager.find(CoreModule.NAME)
.provider()
.getService(ServerStatusService.class);
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/DebuggingSpanRsp.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/DebuggingSpanRsp.java
similarity index 96%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/DebuggingSpanRsp.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/DebuggingSpanRsp.java
index 0d62d84063..0ec9b45d12 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/DebuggingSpanRsp.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/DebuggingSpanRsp.java
@@ -16,7 +16,7 @@
*
*/
-package org.apache.skywalking.oap.query.debug;
+package org.apache.skywalking.oap.server.admin.status;
import java.util.ArrayList;
import java.util.List;
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/DebuggingTraceRsp.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/DebuggingTraceRsp.java
similarity index 95%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/DebuggingTraceRsp.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/DebuggingTraceRsp.java
index aa77970913..762300d0b7 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/DebuggingTraceRsp.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/DebuggingTraceRsp.java
@@ -16,7 +16,7 @@
*
*/
-package org.apache.skywalking.oap.query.debug;
+package org.apache.skywalking.oap.server.admin.status;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryModule.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/StatusModule.java
similarity index 51%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryModule.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/StatusModule.java
index 1fd3494cb4..c9d1029804 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryModule.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/StatusModule.java
@@ -16,17 +16,35 @@
*
*/
-package org.apache.skywalking.oap.query.debug;
+package org.apache.skywalking.oap.server.admin.status;
import org.apache.skywalking.oap.server.library.module.ModuleDefine;
-public class StatusQueryModule extends ModuleDefine {
- public static final String NAME = "status-query";
+/**
+ * Status feature module — replaces the legacy status-query-plugin.
+ *
+ * <p>Hosts the cluster / alarm / TTL status endpoints plus the per-query
+ * debugging trace endpoints. URI paths and response payloads are
+ * preserved verbatim from the legacy plugin.
+ *
+ * <p>Dual-binds at start time: every handler is registered on the
+ * public REST {@link org.apache.skywalking.oap.server.core.server.HTTPHandlerRegister}
+ * exposed by core (preserving the binding skywalking-ui already
+ * consumes) and, when admin-server is enabled, additionally on the
+ * admin-server's own register.
+ *
+ * <p>Designed to function with admin-server off — the public REST
+ * binding is unconditional so existing UI deployments keep working
+ * after the relocation.
+ */
+public class StatusModule extends ModuleDefine {
+ public static final String NAME = "status";
- public StatusQueryModule() {
+ public StatusModule() {
super(NAME);
}
+ @Override
public Class[] services() {
return new Class[] {AlarmStatusQueryService.class};
}
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryConfig.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/StatusModuleConfig.java
similarity index 89%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryConfig.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/StatusModuleConfig.java
index 4be0a9c8a4..b941e5dfb4 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryConfig.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/StatusModuleConfig.java
@@ -16,13 +16,15 @@
*
*/
-package org.apache.skywalking.oap.query.debug;
+package org.apache.skywalking.oap.server.admin.status;
import lombok.Getter;
+import lombok.Setter;
import org.apache.skywalking.oap.server.library.module.ModuleConfig;
@Getter
-public class StatusQueryConfig extends ModuleConfig {
+@Setter
+public class StatusModuleConfig extends ModuleConfig {
/**
* Include the list of keywords to filter configurations including secrets. Separate keywords by a comma.
*
diff --git a/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/StatusModuleProvider.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/StatusModuleProvider.java
new file mode 100644
index 0000000000..c63ad4fac6
--- /dev/null
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/StatusModuleProvider.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.admin.status;
+
+import com.linecorp.armeria.common.HttpMethod;
+import java.util.Collections;
+import org.apache.skywalking.oap.server.admin.server.module.AdminServerModule;
+import org.apache.skywalking.oap.server.core.CoreModule;
+import org.apache.skywalking.oap.server.core.server.HTTPHandlerRegister;
+import org.apache.skywalking.oap.server.library.module.ModuleConfig;
+import org.apache.skywalking.oap.server.library.module.ModuleDefine;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+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;
+
+/**
+ * Provider for the {@link StatusModule}. Skeleton in this commit — handler
+ * relocation and dual-bind logic land in the next commits.
+ */
+public class StatusModuleProvider extends ModuleProvider {
+ public static final String NAME = "default";
+
+ private StatusModuleConfig config;
+
+ @Override
+ public String name() {
+ return NAME;
+ }
+
+ @Override
+ public Class<? extends ModuleDefine> module() {
+ return StatusModule.class;
+ }
+
+ @Override
+ public ConfigCreator<? extends ModuleConfig> newConfigCreator() {
+ return new ConfigCreator<StatusModuleConfig>() {
+ @Override
+ public Class<StatusModuleConfig> type() {
+ return StatusModuleConfig.class;
+ }
+
+ @Override
+ public void onInitialized(final StatusModuleConfig initialized) {
+ config = initialized;
+ }
+ };
+ }
+
+ @Override
+ public void prepare() throws ServiceNotProvidedException, ModuleStartException {
+ registerServiceImplementation(AlarmStatusQueryService.class, new AlarmStatusQueryService(getManager()));
+ }
+
+ @Override
+ public void start() throws ServiceNotProvidedException, ModuleStartException {
+ registerHandlers(publicRestRegister());
+ final HTTPHandlerRegister adminRegister = adminRestRegisterOrNull();
+ if (adminRegister != null) {
+ registerHandlers(adminRegister);
+ }
+ }
+
+ @Override
+ public void notifyAfterCompleted() throws ServiceNotProvidedException, ModuleStartException {
+ }
+
+ @Override
+ public String[] requiredModules() {
+ return new String[] {
+ CoreModule.NAME,
+ };
+ }
+
+ /**
+ * Register all status / debug HTTP handlers on the supplied
+ * {@link HTTPHandlerRegister}. Same set the legacy
+ * {@code status-query-plugin} hosted, in the same order. Called for each
+ * surface the module dual-binds onto:
+ * <ul>
+ * <li>the public REST register exposed by {@link CoreModule} — always,
+ * so skywalking-ui (which calls {@code /status/cluster/nodes},
+ * {@code /status/config/ttl}, {@code /debugging/config/dump} on
+ * {@code core.restPort}) keeps working;</li>
+ * <li>the admin-server register, when admin-server is enabled, so
+ * operators driving admin-host see the same surface alongside
+ * {@code /inspect/*}, {@code /dsl-debugging/*}, and
+ * {@code /runtime/rule/*}.</li>
+ * </ul>
+ */
+ void registerHandlers(final HTTPHandlerRegister register) {
+ final ModuleManager manager = getManager();
+ register.addHandler(
+ new DebuggingHTTPHandler(manager, config),
+ Collections.singletonList(HttpMethod.GET)
+ );
+ register.addHandler(
+ new TTLConfigQueryHandler(manager),
+ Collections.singletonList(HttpMethod.GET)
+ );
+ register.addHandler(
+ new ClusterStatusQueryHandler(manager),
+ Collections.singletonList(HttpMethod.GET)
+ );
+ register.addHandler(
+ new AlarmStatusQueryHandler(manager),
+ Collections.singletonList(HttpMethod.GET)
+ );
+ }
+
+ /**
+ * Resolve the public REST {@link HTTPHandlerRegister} via {@link CoreModule}.
+ * This binding is unconditional — the public surface preserves the URI
+ * binding {@code skywalking-ui} consumes today.
+ */
+ HTTPHandlerRegister publicRestRegister() {
+ return getManager().find(CoreModule.NAME)
+ .provider()
+ .getService(HTTPHandlerRegister.class);
+ }
+
+ /**
+ * Resolve the admin-server {@link HTTPHandlerRegister} when admin-server
+ * is loaded; return {@code null} otherwise. {@link AdminServerModule} is
+ * deliberately NOT in {@link #requiredModules()} so that admin-server
+ * may stay off without breaking the public-REST status binding.
+ * Admin-server registers its {@link HTTPHandlerRegister} service in
+ * {@code prepare()} (admin-server/.../AdminServerModuleProvider.java:116),
+ * which the framework guarantees has run before any provider's
+ * {@code start()}, so this lookup is safe at start-time.
+ */
+ HTTPHandlerRegister adminRestRegisterOrNull() {
+ final ModuleManager manager = getManager();
+ if (!manager.has(AdminServerModule.NAME)) {
+ return null;
+ }
+ return manager.find(AdminServerModule.NAME)
+ .provider()
+ .getService(HTTPHandlerRegister.class);
+ }
+}
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryExceptionHandler.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/StatusQueryExceptionHandler.java
similarity index 97%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryExceptionHandler.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/StatusQueryExceptionHandler.java
index 28d4037a54..c180fd77ff 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryExceptionHandler.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/StatusQueryExceptionHandler.java
@@ -16,7 +16,7 @@
*
*/
-package org.apache.skywalking.oap.query.debug;
+package org.apache.skywalking.oap.server.admin.status;
import com.linecorp.armeria.common.HttpRequest;
import com.linecorp.armeria.common.HttpResponse;
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/TTLConfigQueryHandler.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/TTLConfigQueryHandler.java
similarity index 97%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/TTLConfigQueryHandler.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/TTLConfigQueryHandler.java
index 8a87a3e372..40e25673b2 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/TTLConfigQueryHandler.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/TTLConfigQueryHandler.java
@@ -16,7 +16,7 @@
*
*/
-package org.apache.skywalking.oap.query.debug;
+package org.apache.skywalking.oap.server.admin.status;
import com.linecorp.armeria.server.annotation.ExceptionHandler;
import com.linecorp.armeria.server.annotation.Get;
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/log/DebuggingQueryLogsRsp.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/log/DebuggingQueryLogsRsp.java
similarity index 89%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/log/DebuggingQueryLogsRsp.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/log/DebuggingQueryLogsRsp.java
index e869342309..789c149b08 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/log/DebuggingQueryLogsRsp.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/log/DebuggingQueryLogsRsp.java
@@ -16,12 +16,12 @@
*
*/
-package org.apache.skywalking.oap.query.debug.log;
+package org.apache.skywalking.oap.server.admin.status.log;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.oap.query.debug.DebuggingTraceRsp;
+import org.apache.skywalking.oap.server.admin.status.DebuggingTraceRsp;
import org.apache.skywalking.oap.server.core.query.type.Log;
@RequiredArgsConstructor
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/mqe/DebuggingMQERsp.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/mqe/DebuggingMQERsp.java
similarity index 90%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/mqe/DebuggingMQERsp.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/mqe/DebuggingMQERsp.java
index cafa1752b2..fa9c8592e2 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/mqe/DebuggingMQERsp.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/mqe/DebuggingMQERsp.java
@@ -16,14 +16,14 @@
*
*/
-package org.apache.skywalking.oap.query.debug.mqe;
+package org.apache.skywalking.oap.server.admin.status.mqe;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.skywalking.oap.server.core.query.mqe.ExpressionResultType;
import org.apache.skywalking.oap.server.core.query.mqe.MQEValues;
-import org.apache.skywalking.oap.query.debug.DebuggingTraceRsp;
+import org.apache.skywalking.oap.server.admin.status.DebuggingTraceRsp;
@RequiredArgsConstructor
@Getter
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/topology/DebuggingQueryEndpointTopologyRsp.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/topology/DebuggingQueryEndpointTopologyRsp.java
similarity index 90%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/topology/DebuggingQueryEndpointTopologyRsp.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/topology/DebuggingQueryEndpointTopologyRsp.java
index 483ca7fbf3..e09f215f0a 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/topology/DebuggingQueryEndpointTopologyRsp.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/topology/DebuggingQueryEndpointTopologyRsp.java
@@ -16,12 +16,12 @@
*
*/
-package org.apache.skywalking.oap.query.debug.topology;
+package org.apache.skywalking.oap.server.admin.status.topology;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.oap.query.debug.DebuggingTraceRsp;
+import org.apache.skywalking.oap.server.admin.status.DebuggingTraceRsp;
import org.apache.skywalking.oap.server.core.query.type.Call;
import org.apache.skywalking.oap.server.core.query.type.EndpointNode;
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/topology/DebuggingQueryInstanceTopologyRsp.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/topology/DebuggingQueryInstanceTopologyRsp.java
similarity index 90%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/topology/DebuggingQueryInstanceTopologyRsp.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/topology/DebuggingQueryInstanceTopologyRsp.java
index a7d1c1db90..61580668fa 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/topology/DebuggingQueryInstanceTopologyRsp.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/topology/DebuggingQueryInstanceTopologyRsp.java
@@ -16,12 +16,12 @@
*
*/
-package org.apache.skywalking.oap.query.debug.topology;
+package org.apache.skywalking.oap.server.admin.status.topology;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.oap.query.debug.DebuggingTraceRsp;
+import org.apache.skywalking.oap.server.admin.status.DebuggingTraceRsp;
import org.apache.skywalking.oap.server.core.query.type.Call;
import org.apache.skywalking.oap.server.core.query.type.ServiceInstanceNode;
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/topology/DebuggingQueryProcessTopologyRsp.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/topology/DebuggingQueryProcessTopologyRsp.java
similarity index 90%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/topology/DebuggingQueryProcessTopologyRsp.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/topology/DebuggingQueryProcessTopologyRsp.java
index eb0e5bf8be..01c5b83849 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/topology/DebuggingQueryProcessTopologyRsp.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/topology/DebuggingQueryProcessTopologyRsp.java
@@ -16,12 +16,12 @@
*
*/
-package org.apache.skywalking.oap.query.debug.topology;
+package org.apache.skywalking.oap.server.admin.status.topology;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.oap.query.debug.DebuggingTraceRsp;
+import org.apache.skywalking.oap.server.admin.status.DebuggingTraceRsp;
import org.apache.skywalking.oap.server.core.query.type.Call;
import org.apache.skywalking.oap.server.core.query.type.ProcessNode;
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/topology/DebuggingQueryServiceTopologyRsp.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/topology/DebuggingQueryServiceTopologyRsp.java
similarity index 89%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/topology/DebuggingQueryServiceTopologyRsp.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/topology/DebuggingQueryServiceTopologyRsp.java
index c421787c38..b031c5a480 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/topology/DebuggingQueryServiceTopologyRsp.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/topology/DebuggingQueryServiceTopologyRsp.java
@@ -16,12 +16,12 @@
*
*/
-package org.apache.skywalking.oap.query.debug.topology;
+package org.apache.skywalking.oap.server.admin.status.topology;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.oap.query.debug.DebuggingTraceRsp;
+import org.apache.skywalking.oap.server.admin.status.DebuggingTraceRsp;
import org.apache.skywalking.oap.server.core.query.type.Call;
import org.apache.skywalking.oap.server.core.query.type.Node;
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/trace/DebuggingQueryTraceBriefRsp.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/trace/DebuggingQueryTraceBriefRsp.java
similarity index 89%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/trace/DebuggingQueryTraceBriefRsp.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/trace/DebuggingQueryTraceBriefRsp.java
index 4872eae34b..6296fe309c 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/trace/DebuggingQueryTraceBriefRsp.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/trace/DebuggingQueryTraceBriefRsp.java
@@ -16,12 +16,12 @@
*
*/
-package org.apache.skywalking.oap.query.debug.trace;
+package org.apache.skywalking.oap.server.admin.status.trace;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.oap.query.debug.DebuggingTraceRsp;
+import org.apache.skywalking.oap.server.admin.status.DebuggingTraceRsp;
import org.apache.skywalking.oap.server.core.query.type.BasicTrace;
@RequiredArgsConstructor
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/trace/DebuggingQueryTraceRsp.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/trace/DebuggingQueryTraceRsp.java
similarity index 89%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/trace/DebuggingQueryTraceRsp.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/trace/DebuggingQueryTraceRsp.java
index 0da174650f..009dd7a081 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/trace/DebuggingQueryTraceRsp.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/trace/DebuggingQueryTraceRsp.java
@@ -16,12 +16,12 @@
*
*/
-package org.apache.skywalking.oap.query.debug.trace;
+package org.apache.skywalking.oap.server.admin.status.trace;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.oap.query.debug.DebuggingTraceRsp;
+import org.apache.skywalking.oap.server.admin.status.DebuggingTraceRsp;
import org.apache.skywalking.oap.server.core.query.type.Span;
@RequiredArgsConstructor
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/trace/zipkin/DebuggingZipkinQueryTraceRsp.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/trace/zipkin/DebuggingZipkinQueryTraceRsp.java
similarity index 88%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/trace/zipkin/DebuggingZipkinQueryTraceRsp.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/trace/zipkin/DebuggingZipkinQueryTraceRsp.java
index da17e16d98..82ec3a21fc 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/trace/zipkin/DebuggingZipkinQueryTraceRsp.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/trace/zipkin/DebuggingZipkinQueryTraceRsp.java
@@ -16,12 +16,12 @@
*
*/
-package org.apache.skywalking.oap.query.debug.trace.zipkin;
+package org.apache.skywalking.oap.server.admin.status.trace.zipkin;
import java.util.List;
import lombok.Data;
import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.oap.query.debug.DebuggingTraceRsp;
+import org.apache.skywalking.oap.server.admin.status.DebuggingTraceRsp;
import zipkin2.Span;
@RequiredArgsConstructor
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/trace/zipkin/DebuggingZipkinQueryTracesRsp.java b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/trace/zipkin/DebuggingZipkinQueryTracesRsp.java
similarity index 88%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/trace/zipkin/DebuggingZipkinQueryTracesRsp.java
rename to oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/trace/zipkin/DebuggingZipkinQueryTracesRsp.java
index 02589796f2..3a20a23803 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/trace/zipkin/DebuggingZipkinQueryTracesRsp.java
+++ b/oap-server/server-admin/status/src/main/java/org/apache/skywalking/oap/server/admin/status/trace/zipkin/DebuggingZipkinQueryTracesRsp.java
@@ -16,12 +16,12 @@
*
*/
-package org.apache.skywalking.oap.query.debug.trace.zipkin;
+package org.apache.skywalking.oap.server.admin.status.trace.zipkin;
import java.util.List;
import lombok.Data;
import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.oap.query.debug.DebuggingTraceRsp;
+import org.apache.skywalking.oap.server.admin.status.DebuggingTraceRsp;
import zipkin2.Span;
@RequiredArgsConstructor
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine b/oap-server/server-admin/status/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
similarity index 93%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
copy to oap-server/server-admin/status/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
index e63b6e2066..b70a98f1ab 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
+++ b/oap-server/server-admin/status/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
@@ -16,4 +16,4 @@
#
#
-org.apache.skywalking.oap.query.debug.StatusQueryModule
+org.apache.skywalking.oap.server.admin.status.StatusModule
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider b/oap-server/server-admin/status/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
similarity index 92%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
copy to oap-server/server-admin/status/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
index 94780edbbf..a8b89a7342 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
+++ b/oap-server/server-admin/status/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
@@ -16,4 +16,4 @@
#
#
-org.apache.skywalking.oap.query.debug.StatusQueryProvider
\ No newline at end of file
+org.apache.skywalking.oap.server.admin.status.StatusModuleProvider
diff --git a/oap-server/server-admin/status/src/test/java/org/apache/skywalking/oap/server/admin/status/StatusModuleProviderTest.java b/oap-server/server-admin/status/src/test/java/org/apache/skywalking/oap/server/admin/status/StatusModuleProviderTest.java
new file mode 100644
index 0000000000..6aea435f4d
--- /dev/null
+++ b/oap-server/server-admin/status/src/test/java/org/apache/skywalking/oap/server/admin/status/StatusModuleProviderTest.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.admin.status;
+
+import org.apache.skywalking.oap.server.admin.server.module.AdminServerModule;
+import org.apache.skywalking.oap.server.core.CoreModule;
+import org.apache.skywalking.oap.server.core.server.HTTPHandlerRegister;
+import org.apache.skywalking.oap.server.library.module.ModuleConfig;
+import org.apache.skywalking.oap.server.library.module.ModuleDefine;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.library.module.ModuleProvider;
+import org.apache.skywalking.oap.server.library.module.ModuleProviderHolder;
+import org.apache.skywalking.oap.server.library.module.Service;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Verifies the dual-bind register-resolution helpers on StatusModuleProvider.
+ * Handler instantiation itself is exercised end-to-end by the e2e suite —
+ * unit-mocking every {@code manager.find(...)} chain inside each handler's
+ * constructor would test the upstream services more than the registration logic.
+ */
+@ExtendWith(MockitoExtension.class)
+class StatusModuleProviderTest {
+
+ @Mock
+ private ModuleManager moduleManager;
+
+ @Mock
+ private ModuleProviderHolder coreHolder;
+
+ @Mock
+ private ModuleProviderHolder adminHolder;
+
+ @Mock
+ private HTTPHandlerRegister publicRegister;
+
+ @Mock
+ private HTTPHandlerRegister adminRegister;
+
+ private StatusModuleProvider provider;
+
+ @BeforeEach
+ void setUp() {
+ provider = new StatusModuleProvider();
+ provider.setManager(moduleManager);
+ }
+
+ @Test
+ void publicRestRegisterResolvesViaCoreModule() {
+ when(moduleManager.find(CoreModule.NAME)).thenReturn(coreHolder);
+ when(coreHolder.provider()).thenReturn(stubProviderExposing(publicRegister));
+
+ final HTTPHandlerRegister resolved = provider.publicRestRegister();
+
+ assertSame(publicRegister, resolved);
+ verify(moduleManager).find(CoreModule.NAME);
+ }
+
+ @Test
+ void adminRestRegisterReturnsNullWhenAdminServerNotLoaded() {
+ when(moduleManager.has(AdminServerModule.NAME)).thenReturn(false);
+
+ assertNull(provider.adminRestRegisterOrNull());
+ // Critical: must NOT touch find() if has() said no. AdminServerModule is
+ // intentionally absent from requiredModules() so admin-server can stay
+ // off without breaking status's public-REST binding for skywalking-ui.
+ verify(moduleManager, never()).find(AdminServerModule.NAME);
+ }
+
+ @Test
+ void adminRestRegisterResolvesAdminRegisterWhenAdminServerLoaded() {
+ when(moduleManager.has(AdminServerModule.NAME)).thenReturn(true);
+ when(moduleManager.find(AdminServerModule.NAME)).thenReturn(adminHolder);
+ when(adminHolder.provider()).thenReturn(stubProviderExposing(adminRegister));
+
+ final HTTPHandlerRegister resolved = provider.adminRestRegisterOrNull();
+
+ assertNotNull(resolved);
+ assertSame(adminRegister, resolved);
+ }
+
+ /**
+ * Tiny adapter so {@code holder.provider().getService(HTTPHandlerRegister.class)}
+ * resolves to the supplied register without us having to mock every step
+ * of the chain in each test.
+ */
+ private static ModuleProvider stubProviderExposing(final HTTPHandlerRegister register) {
+ return new ModuleProvider() {
+ @Override
+ public String name() {
+ return "test";
+ }
+
+ @Override
+ public Class<? extends ModuleDefine> module() {
+ return null;
+ }
+
+ @Override
+ public ConfigCreator<? extends ModuleConfig> newConfigCreator() {
+ return null;
+ }
+
+ @Override
+ public void prepare() {
+ }
+
+ @Override
+ public void start() {
+ }
+
+ @Override
+ public void notifyAfterCompleted() {
+ }
+
+ @Override
+ public String[] requiredModules() {
+ return new String[0];
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T extends Service> T getService(final Class<T> serviceType) {
+ if (HTTPHandlerRegister.class.equals(serviceType)) {
+ return (T) register;
+ }
+ return null;
+ }
+ };
+ }
+}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IMetricsQueryDAO.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IMetricsQueryDAO.java
index 3c190307d7..6b5b49426a 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IMetricsQueryDAO.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IMetricsQueryDAO.java
@@ -115,6 +115,32 @@ public interface IMetricsQueryDAO extends DAO {
List<KeyValue> labels,
Duration duration) throws IOException;
+ /**
+ * List distinct {@code entity_id}s that have at least one row for the given metric in the
+ * given time range, capped at {@code limit}. Used by the {@code /inspect/entities}
+ * admin-server endpoint to enumerate the entities currently emitting values for a metric —
+ * feeds the decoded {@code mqeEntity} payload the inspect API hands back to operators so
+ * they can follow up with a public-GraphQL MQE query.
+ *
+ * <p>Order: most recent timestamp first within the range so callers see live entities ahead
+ * of stale ones. Backends dedup by {@code entity_id} before returning. The {@code limit}
+ * argument is a server-side cap on the rows scanned, not a guarantee on distinct entities
+ * (300 rows ≈ 10 buckets × 30 entities).
+ *
+ * <p>Abstract on purpose — any 3rd party storage backend that implements
+ * {@code IMetricsQueryDAO} after 10.5.0 MUST provide this override. A default
+ * (empty list or thrown exception) would let a missing override slip through
+ * compilation and surface as a runtime "no entities" or 500 the first time the
+ * inspect API hit that backend; the breaking-at-compile signal is the safer
+ * contract for the inspect storage path.
+ *
+ * @since 10.5.0
+ */
+ List<String> listEntityIdsInRange(String metricName,
+ String valueColumnName,
+ Duration duration,
+ int limit) throws IOException;
+
HeatMap readHeatMap(MetricsCondition condition, String valueColumnName, Duration duration) throws IOException;
class Util {
diff --git a/oap-server/server-query-plugin/pom.xml b/oap-server/server-query-plugin/pom.xml
index 54144773c0..f58ff1ba89 100644
--- a/oap-server/server-query-plugin/pom.xml
+++ b/oap-server/server-query-plugin/pom.xml
@@ -46,7 +46,6 @@
<module>zipkin-query-plugin</module>
<module>promql-plugin</module>
<module>logql-plugin</module>
- <module>status-query-plugin</module>
<module>traceql-plugin</module>
</modules>
</project>
diff --git a/oap-server/server-starter/pom.xml b/oap-server/server-starter/pom.xml
index 6c8313dbc5..01a62951d1 100644
--- a/oap-server/server-starter/pom.xml
+++ b/oap-server/server-starter/pom.xml
@@ -124,11 +124,6 @@
<artifactId>zipkin-query-plugin</artifactId>
<version>${project.version}</version>
</dependency>
- <dependency>
- <groupId>org.apache.skywalking</groupId>
- <artifactId>status-query-plugin</artifactId>
- <version>${project.version}</version>
- </dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>envoy-metrics-receiver-plugin</artifactId>
@@ -209,6 +204,16 @@
<artifactId>dsl-debugging</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.skywalking</groupId>
+ <artifactId>status</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.skywalking</groupId>
+ <artifactId>inspect</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>skywalking-telegraf-receiver-plugin</artifactId>
diff --git a/oap-server/server-starter/src/main/resources/application.yml b/oap-server/server-starter/src/main/resources/application.yml
index 5b32df2fd8..95818d2af4 100644
--- a/oap-server/server-starter/src/main/resources/application.yml
+++ b/oap-server/server-starter/src/main/resources/application.yml
@@ -608,12 +608,6 @@ health-checker:
default:
checkIntervalSeconds: ${SW_HEALTH_CHECKER_INTERVAL_SECONDS:30}
-status-query:
- selector: ${SW_STATUS_QUERY:default}
- default:
- # Include the list of keywords to filter configurations including secrets. Separate keywords by a comma.
- keywords4MaskingSecretsOfConfig: ${SW_DEBUGGING_QUERY_KEYWORDS_FOR_MASKING_SECRETS:user,password,token,accessKey,secretKey,authentication}
-
configuration-discovery:
selector: ${SW_CONFIGURATION_DISCOVERY:default}
default:
@@ -682,11 +676,14 @@ ai-pipeline:
# *****************************************************************************
# admin-server is the shared HTTP host for admin / on-demand write APIs. Feature
-# modules (receiver-runtime-rule, dsl-debugging) register their REST routes onto
-# this single port. The empty selector below keeps the host closed by default;
-# enable with SW_ADMIN_SERVER=default.
+# modules (receiver-runtime-rule, dsl-debugging, inspect, status) register their
+# REST routes onto this single port. Enabled by default so the status feature
+# module is reachable out-of-the-box; the host binds to 0.0.0.0:17128 by
+# default and has NO built-in authentication — operators MUST gateway-protect
+# it with an IP allow-list and authenticating reverse proxy, or bind it to a
+# private interface only. Set SW_ADMIN_SERVER empty to disable.
admin-server:
- selector: ${SW_ADMIN_SERVER:-}
+ selector: ${SW_ADMIN_SERVER:default}
default:
host: ${SW_ADMIN_SERVER_HOST:0.0.0.0}
port: ${SW_ADMIN_SERVER_PORT:17128}
@@ -740,3 +737,23 @@ dsl-debugging:
selector: ${SW_DSL_DEBUGGING:-}
default:
injectionEnabled: ${SW_DSL_DEBUGGING_INJECTION_ENABLED:true}
+
+# Status feature module — cluster nodes / alarm runtime status / TTL config / debug
+# query trace endpoints. Dual-binds: always on the public REST register (preserving
+# the binding skywalking-ui consumes today) and additionally on admin-server's
+# register when admin-server is enabled. Enabled by default; set SW_STATUS empty to
+# disable. AdminServerModule is consulted reflectively, so this module survives
+# admin-server being off.
+status:
+ selector: ${SW_STATUS:default}
+ default:
+ # Include the list of keywords to filter configurations including secrets. Separate keywords by a comma.
+ keywords4MaskingSecretsOfConfig: ${SW_DEBUGGING_QUERY_KEYWORDS_FOR_MASKING_SECRETS:user,password,token,accessKey,secretKey,authentication}
+
+# Inspect API — admin-only metric catalog + per-metric entity enumeration.
+# Routes live under /inspect/*. Enabled by default; mounts onto admin-server
+# (also on by default). Set SW_INSPECT= empty to disable. Operator reference:
+# docs/en/setup/backend/admin-api/inspect.md.
+inspect:
+ selector: ${SW_INSPECT:default}
+ default:
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/measure/BanyanDBMetricsQueryDAO.java b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/measure/BanyanDBMetricsQueryDAO.java
index a6d384b10b..1c139df0be 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/measure/BanyanDBMetricsQueryDAO.java
+++ b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/measure/BanyanDBMetricsQueryDAO.java
@@ -23,10 +23,12 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.library.banyandb.v1.client.DataPoint;
+import org.apache.skywalking.library.banyandb.v1.client.AbstractQuery;
import org.apache.skywalking.library.banyandb.v1.client.MeasureQuery;
import org.apache.skywalking.library.banyandb.v1.client.MeasureQueryResponse;
import org.apache.skywalking.oap.server.core.analysis.TimeBucket;
@@ -164,6 +166,44 @@ public class BanyanDBMetricsQueryDAO extends AbstractBanyanDBDAO implements IMet
);
}
+ @Override
+ public List<String> listEntityIdsInRange(final String metricName,
+ final String valueColumnName,
+ final Duration duration,
+ final int limit) throws IOException {
+ final boolean isColdStage = duration != null && duration.isColdStage();
+ final MetadataRegistry.Schema schema = MetadataRegistry.INSTANCE.findMetricMetadata(metricName, duration.getStep());
+ if (schema == null) {
+ throw new IOException("schema is not registered");
+ }
+ final MeasureQueryResponse resp = query(
+ isColdStage, schema,
+ ImmutableSet.of(Metrics.ENTITY_ID), ImmutableSet.of(valueColumnName),
+ getTimestampRange(duration),
+ new QueryBuilder<MeasureQuery>() {
+ @Override
+ protected void apply(final MeasureQuery query) {
+ // Most-recent-first ordering must be explicit — without it BanyanDB returns
+ // arbitrary order and a hot entity that ingested late can be dropped before
+ // the LIMIT cap is reached.
+ query.setOrderBy(new AbstractQuery.OrderBy(AbstractQuery.Sort.DESC));
+ query.limit(limit);
+ }
+ }
+ );
+ if (resp.size() == 0) {
+ return Collections.emptyList();
+ }
+ // BanyanDB returns one DataPoint per (timestamp, entity_id); if the same entity has rows
+ // in multiple time buckets within the range, we'd see it more than once. LinkedHashSet
+ // dedups while preserving the server-side timestamp-DESC ordering.
+ final LinkedHashSet<String> entityIds = new LinkedHashSet<>();
+ for (final DataPoint dp : resp.getDataPoints()) {
+ entityIds.add(dp.getTagValue(Metrics.ENTITY_ID));
+ }
+ return new ArrayList<>(entityIds);
+ }
+
@Override
public HeatMap readHeatMap(MetricsCondition condition, String valueColumnName, Duration duration) throws IOException {
Map<Long, DataPoint> idMap = queryByEntityID(condition, valueColumnName, duration);
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/MetricsQueryEsDAO.java b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/MetricsQueryEsDAO.java
index 8b139a323c..82123c83c1 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/MetricsQueryEsDAO.java
+++ b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/MetricsQueryEsDAO.java
@@ -20,6 +20,7 @@ package org.apache.skywalking.oap.server.storage.plugin.elasticsearch.query;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -29,6 +30,7 @@ import org.apache.skywalking.library.elasticsearch.requests.search.Query;
import org.apache.skywalking.library.elasticsearch.requests.search.RangeQueryBuilder;
import org.apache.skywalking.library.elasticsearch.requests.search.Search;
import org.apache.skywalking.library.elasticsearch.requests.search.SearchBuilder;
+import org.apache.skywalking.library.elasticsearch.requests.search.Sort;
import org.apache.skywalking.library.elasticsearch.response.Document;
import org.apache.skywalking.library.elasticsearch.response.Documents;
import org.apache.skywalking.library.elasticsearch.response.search.SearchHit;
@@ -192,6 +194,42 @@ public class MetricsQueryEsDAO extends EsDAO implements IMetricsQueryDAO {
);
}
+ @Override
+ public List<String> listEntityIdsInRange(final String metricName,
+ final String valueColumnName,
+ final Duration duration,
+ final int limit) {
+ final SearchBuilder search = Search.builder().size(limit);
+ // Most-recent-first ordering must be explicit — without sort the hit set is
+ // score / index-internal ordered, so a hot entity that ingested late can be dropped
+ // before the limit is reached.
+ search.sort(Metrics.TIME_BUCKET, Sort.Order.DESC);
+ final BoolQueryBuilder query = Query.bool().must(Query.range(Metrics.TIME_BUCKET)
+ .lte(duration.getEndTimeBucket())
+ .gte(duration.getStartTimeBucket()));
+ if (IndexController.LogicIndicesRegister.isMergedTable(metricName)) {
+ query.must(Query.term(
+ IndexController.LogicIndicesRegister.METRIC_TABLE_NAME,
+ metricName
+ ));
+ }
+ search.query(query);
+ final SearchResponse response = getClient().search(new TimeRangeIndexNameGenerator(
+ IndexController.LogicIndicesRegister.getPhysicalTableName(metricName),
+ duration.getStartTimeBucketInSec(),
+ duration.getEndTimeBucketInSec()), search.build());
+ // Top-N hits across the time range, dedup client-side on entity_id. LinkedHashSet
+ // preserves the time_bucket-DESC ordering ES returned.
+ final LinkedHashSet<String> entityIds = new LinkedHashSet<>();
+ for (final SearchHit searchHit : response.getHits()) {
+ final Object eid = searchHit.getSource().get(Metrics.ENTITY_ID);
+ if (eid instanceof String) {
+ entityIds.add((String) eid);
+ }
+ }
+ return new ArrayList<>(entityIds);
+ }
+
@Override
public HeatMap readHeatMap(final MetricsCondition condition,
final String valueColumnName,
diff --git a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCMetricsQueryDAO.java b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCMetricsQueryDAO.java
index 09fee64d3d..2ded45d408 100644
--- a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCMetricsQueryDAO.java
+++ b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCMetricsQueryDAO.java
@@ -199,6 +199,60 @@ public class JDBCMetricsQueryDAO extends JDBCSQLExecutor implements IMetricsQuer
);
}
+ @Override
+ @SneakyThrows
+ public List<String> listEntityIdsInRange(final String metricName,
+ final String valueColumnName,
+ final Duration duration,
+ final int limit) {
+ final var tables = tableHelper.getTablesForRead(
+ metricName,
+ duration.getStartTimeBucket(),
+ duration.getEndTimeBucket()
+ );
+ // For each entity_id, track the latest time_bucket seen across every day-partitioned
+ // table the range touches. Per-table query shape is GROUP BY entity_id with MAX(time_bucket)
+ // and ORDER BY that max — portable across H2 / MySQL / PostgreSQL (Postgres rejects
+ // ORDER BY on a column that is not in a SELECT DISTINCT list, so the GROUP BY shape is
+ // the right one). Each table is independently capped at `limit`; we then merge by latest
+ // time_bucket globally before applying the global `limit`, so a range that spans many
+ // day partitions cannot fill the result with stale entities from older partitions before
+ // newer partitions are queried.
+ final var latestByEntity = new HashMap<String, Long>();
+ for (final var table : tables) {
+ final var sql = new StringBuilder("select ")
+ .append(Metrics.ENTITY_ID).append(", max(").append(Metrics.TIME_BUCKET)
+ .append(") as latest_time_bucket")
+ .append(" from ").append(table)
+ .append(" where ").append(JDBCTableInstaller.TABLE_COLUMN).append(" = ? ")
+ .append(" and ").append(Metrics.TIME_BUCKET).append(" >= ? ")
+ .append(" and ").append(Metrics.TIME_BUCKET).append(" <= ? ")
+ .append(" group by ").append(Metrics.ENTITY_ID)
+ .append(" order by latest_time_bucket desc ")
+ .append(" limit ").append(limit);
+
+ jdbcClient.executeQuery(
+ sql.toString(),
+ resultSet -> {
+ while (resultSet.next()) {
+ final String eid = resultSet.getString(Metrics.ENTITY_ID);
+ if (eid == null) {
+ continue;
+ }
+ final long latest = resultSet.getLong("latest_time_bucket");
+ latestByEntity.merge(eid, latest, Math::max);
+ }
+ return null;
+ },
+ metricName, duration.getStartTimeBucket(), duration.getEndTimeBucket());
+ }
+ return latestByEntity.entrySet().stream()
+ .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
+ .limit(limit)
+ .map(Map.Entry::getKey)
+ .collect(Collectors.toList());
+ }
+
@Override
@SneakyThrows
public HeatMap readHeatMap(final MetricsCondition condition,
diff --git a/test/e2e-v2/CLAUDE.md b/test/e2e-v2/CLAUDE.md
index a35db555be..894e527832 100644
--- a/test/e2e-v2/CLAUDE.md
+++ b/test/e2e-v2/CLAUDE.md
@@ -22,7 +22,7 @@ with actual query results as context, then structurally compared via `go-cmp`.
| Protocol | Port | Query Tool | Schema/Spec |
|----------|------|------------|-------------|
| GraphQL / MQE | `${oap_12800}` | `swctl --display yaml` | `query-protocol/*.graphqls` |
-| Status / Debug | `${oap_12800}` | `curl` | `status-query-plugin` handlers |
+| Status / Debug | `${oap_12800}` | `curl` | `server-admin/status` handlers |
| PromQL | `${oap_9090}` | `curl` | Prometheus API v1 |
| LogQL | `${oap_3100}` | `curl` | Loki API v1 |
| TraceQL | `${oap_3200}` | `curl` | Tempo API |
diff --git a/test/e2e-v2/cases/storage/banyandb/docker-compose.yml b/test/e2e-v2/cases/storage/banyandb/docker-compose.yml
index dc3e84d38e..882ea46033 100644
--- a/test/e2e-v2/cases/storage/banyandb/docker-compose.yml
+++ b/test/e2e-v2/cases/storage/banyandb/docker-compose.yml
@@ -34,6 +34,8 @@ services:
volumes:
- ./config/bydb-topn.yml:/skywalking/config/bydb-topn.yml
environment:
+ SW_ADMIN_SERVER: default
+ SW_INSPECT: default
SW_STORAGE: banyandb
SW_STORAGE_BANYANDB_USER: admin
SW_STORAGE_BANYANDB_PASSWORD: admin
@@ -42,6 +44,7 @@ services:
SW_STORAGE_BANYANDB_METRICS_MINUTE_ENABLE_COLD_STAGE: "true"
ports:
- 12800
+ - 17128
depends_on:
banyandb:
condition: service_healthy
diff --git a/test/e2e-v2/cases/storage/banyandb/stages/docker-compose.yml b/test/e2e-v2/cases/storage/banyandb/stages/docker-compose.yml
index f156df2696..3dda536f6d 100644
--- a/test/e2e-v2/cases/storage/banyandb/stages/docker-compose.yml
+++ b/test/e2e-v2/cases/storage/banyandb/stages/docker-compose.yml
@@ -67,6 +67,8 @@ services:
file: ../../../../script/docker-compose/base-compose.yml
service: oap
environment:
+ SW_ADMIN_SERVER: default
+ SW_INSPECT: default
SW_STORAGE: banyandb
SW_STORAGE_BANYANDB_TARGETS: "liaison:17912"
SW_STORAGE_BANYANDB_METRICS_MINUTE_ENABLE_WARM_STAGE: "true"
@@ -82,6 +84,7 @@ services:
ports:
- 12800:12800
- 11800:11800
+ - 17128:17128
depends_on:
liaison:
condition: service_healthy
diff --git a/test/e2e-v2/cases/storage/banyandb/tls/docker-compose.yml b/test/e2e-v2/cases/storage/banyandb/tls/docker-compose.yml
index 2ed16d3124..0c3ad80a7c 100644
--- a/test/e2e-v2/cases/storage/banyandb/tls/docker-compose.yml
+++ b/test/e2e-v2/cases/storage/banyandb/tls/docker-compose.yml
@@ -31,10 +31,13 @@ services:
volumes:
- ./tls/cert.crt:/tls/cert.crt
environment:
+ SW_ADMIN_SERVER: default
+ SW_INSPECT: default
SW_STORAGE: banyandb
SW_STORAGE_BANYANDB_SSL_TRUST_CA_PATH: /tls/cert.crt
ports:
- 12800
+ - 17128
depends_on:
banyandb:
condition: service_healthy
diff --git a/test/e2e-v2/cases/storage/es/docker-compose.yml b/test/e2e-v2/cases/storage/es/docker-compose.yml
index dbe36ebb80..2d87b79dd6 100644
--- a/test/e2e-v2/cases/storage/es/docker-compose.yml
+++ b/test/e2e-v2/cases/storage/es/docker-compose.yml
@@ -35,10 +35,13 @@ services:
file: ../../../script/docker-compose/base-compose.yml
service: oap
environment:
+ SW_ADMIN_SERVER: default
+ SW_INSPECT: default
SW_STORAGE: elasticsearch
SW_STORAGE_ES_CLUSTER_NODES: es:9200
ports:
- 12800
+ - 17128
depends_on:
es:
condition: service_healthy
diff --git a/test/e2e-v2/cases/storage/es/es-sharding/docker-compose.yml b/test/e2e-v2/cases/storage/es/es-sharding/docker-compose.yml
index c0f03ebb2a..dfacace5da 100644
--- a/test/e2e-v2/cases/storage/es/es-sharding/docker-compose.yml
+++ b/test/e2e-v2/cases/storage/es/es-sharding/docker-compose.yml
@@ -35,11 +35,14 @@ services:
file: ../../../../script/docker-compose/base-compose.yml
service: oap
environment:
+ SW_ADMIN_SERVER: default
+ SW_INSPECT: default
SW_STORAGE: elasticsearch
SW_STORAGE_ES_CLUSTER_NODES: es:9200
SW_STORAGE_ES_LOGIC_SHARDING: "true"
ports:
- 12800
+ - 17128
depends_on:
es:
condition: service_healthy
diff --git a/test/e2e-v2/cases/storage/expected/config-dump.yml b/test/e2e-v2/cases/storage/expected/config-dump.yml
index 9306de8724..3c0f5e01c3 100644
--- a/test/e2e-v2/cases/storage/expected/config-dump.yml
+++ b/test/e2e-v2/cases/storage/expected/config-dump.yml
@@ -13,24 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-core.default.autocompleteTagKeysQueryMaxSize=100
-receiver-sharing-server.default.gRPCPort=0
aws-firehose.default.port=12801
-core.default.restPort=12800
-receiver-sharing-server.default.gRPCSslCertChainPath=
agent-analyzer.default.meterAnalyzerActiveFiles=datasource,threadpool,satellite,go-runtime,python-runtime,continuous-profiling,java-agent,go-agent,ruby-runtime
-receiver-pprof.default.memoryParserEnabled=true
-agent-analyzer.default.traceSamplingPolicySettingsFile=trace-sampling-policy-settings.yml
-core.default.gRPCSslTrustedCAPath=
configuration-discovery.default.disableMessageDigest=false
core.default.serviceNameMaxLength=70
-aws-firehose.default.tlsCertChainPath=
envoy-metric.default.k8sServiceNameRule=${pod.metadata.labels.(service.istio.io/canonical-name)}.${pod.metadata.namespace}
-ai-pipeline.provider=default
promql.default.buildInfoVersion=2.45.0
-receiver-ebpf.default.maxConcurrentCallsPerConnection=0
-aws-firehose.provider=default
-receiver-event.provider=default
core.default.searchableAlarmTags=level
core.default.prepareThreads=2
core.default.topNReportPeriod=10
@@ -38,168 +26,198 @@ core.default.enableDataKeeperExecutor=true
agent-analyzer.default.slowCacheReadThreshold=default:20,redis:10
receiver-ebpf.default.continuousPolicyCacheTimeout=60
receiver-ebpf.default.gRPCSslKeyPath=
+agent-analyzer.default.segmentStatusAnalysisStrategy=FROM_SPAN_STATUS
+admin-server.default.gRPCSslEnabled=false
+receiver-clr.provider=default
+storage.provider=mysql
+receiver-sharing-server.default.gRPCSslEnabled=false
+telemetry.prometheus.sslEnabled=false
+storage.mysql.properties.dataSource.prepStmtCacheSize=250
+core.default.gRPCSslKeyPath=
+receiver-ebpf.provider=default
+admin-server.default.gRPCSslTrustedCAsPath=
+promql.default.restContextPath=/
+envoy-metric.default.maxMessageSize=0
+envoy-metric.default.gRPCSslCertChainPath=
+promql.default.buildInfoRevision=
+receiver-otel.default.enabledOtelMetricsRules=apisix,nginx/*,k8s/*,istio-controlplane,vm,mysql/*,postgresql/*,oap,aws-eks/*,windows,aws-s3/*,aws-dynamodb/*,aws-gateway/*,redis/*,elasticsearch/*,rabbitmq/*,mongodb/*,kafka/*,pulsar/*,bookkeeper/*,rocketmq/*,clickhouse/*,activemq/*,kong/*,flink/*,banyandb/*,envoy-ai-gateway/*,ios/*,miniprogram/*
+status.default.keywords4MaskingSecretsOfConfig=user,password,token,accessKey,secretKey,authentication
+event-analyzer.provider=default
+admin-server.default.gRPCSslKeyPath=
+receiver-ebpf.default.gRPCHost=0.0.0.0
+receiver-browser.default.sampleRate=10000
+core.default.searchableTracesTags=http.method,http.status_code,rpc.status_code,db.type,db.instance,mq.queue,mq.topic,mq.broker
+core.default.instanceNameMaxLength=70
+receiver-sharing-server.default.gRPCHost=0.0.0.0
+storage.mysql.properties.dataSource.password=******
+core.default.restIdleTimeOut=30000
+admin-server.default.gRPCMaxConcurrentCallsPerConnection=0
+agent-analyzer.provider=default
+core.default.gRPCHost=0.0.0.0
+admin-server.default.contextPath=/
+aws-firehose.default.maxRequestHeaderSize=8192
+core.default.endpointNameMaxLength=150
+core.default.metricsDataTTL=7
+promql.provider=default
+core.default.maxConcurrentCallsPerConnection=0
+ai-pipeline.default.baselineServerAddr=
+admin-server.default.gRPCThreadPoolSize=0
+receiver-sharing-server.default.restIdleTimeOut=30000
+configuration.provider=none
+logql.provider=default
+core.default.persistentPeriod=25
+storage.mysql.properties.jdbcUrl=jdbc:mysql://mysql:3306/swtest?allowMultiQueries=true
+receiver-sharing-server.default.gRPCThreadPoolSize=0
+receiver-register.provider=default
+core.default.storageSessionTimeout=70000
+receiver-trace.provider=default
+query.graphql.enableOnDemandPodLog=false
+logql.default.restContextPath=/
+receiver-profile.provider=default
+receiver-sharing-server.default.httpMaxRequestHeaderSize=8192
+telemetry.prometheus.port=1234
+core.default.maxDirectMemoryUsage=-1
+agent-analyzer.default.slowDBAccessThreshold=default:200,mongodb:100
+receiver-log.provider=default
+receiver-async-profiler.default.memoryParserEnabled=true
+envoy-metric.default.istioServiceNameRule=${serviceEntry.metadata.name}.${serviceEntry.metadata.namespace}
+envoy-metric.default.istioServiceEntryIgnoredNamespaces=
+receiver-sharing-server.default.gRPCSslTrustedCAsPath=
+agent-analyzer.default.noUpstreamRealAddressAgents=6000,9000
+receiver-sharing-server.default.gRPCSslKeyPath=
+admin-server.default.internalCommunicationTimeout=5000
+envoy-metric.default.gRPCThreadPoolSize=0
+aws-firehose.default.host=0.0.0.0
+promql.default.restHost=0.0.0.0
+envoy-metric.default.gRPCSslEnabled=false
+core.default.gRPCSslCertChainPath=
+receiver-meter.provider=default
+receiver-ebpf.default.gRPCSslTrustedCAsPath=
+receiver-jvm.provider=default
+logql.default.restPort=3100
+core.default.l1FlushPeriod=500
+admin-server.default.gRPCPort=17129
+envoy-metric.default.alsTCPAnalysis=
+envoy-metric.default.acceptMetricsService=true
+envoy-metric.default.gRPCSslTrustedCAsPath=
+storage.mysql.properties.dataSource.prepStmtCacheSqlLimit=2048
+promql.default.buildInfoBranch=
+admin-server.default.host=0.0.0.0
+core.default.dataKeeperExecutePeriod=5
+log-analyzer.default.lalFiles=envoy-als,mesh-dp,mysql-slowsql,pgsql-slowsql,redis-slowsql,k8s-service,nginx,envoy-ai-gateway,miniprogram,default
+receiver-otel.provider=default
+core.default.autocompleteTagKeysQueryMaxSize=100
+receiver-sharing-server.default.gRPCPort=0
+core.default.restPort=12800
+receiver-sharing-server.default.gRPCSslCertChainPath=
+receiver-pprof.default.memoryParserEnabled=true
+agent-analyzer.default.traceSamplingPolicySettingsFile=trace-sampling-policy-settings.yml
+core.default.gRPCSslTrustedCAPath=
+aws-firehose.default.tlsCertChainPath=
+admin-server.default.gRPCSslCertChainPath=
+ai-pipeline.provider=default
+receiver-ebpf.default.maxConcurrentCallsPerConnection=0
+aws-firehose.provider=default
+receiver-event.provider=default
gen-ai-analyzer.provider=default
receiver-browser.provider=default
-agent-analyzer.default.segmentStatusAnalysisStrategy=FROM_SPAN_STATUS
envoy-metric.default.maxConcurrentCallsPerConnection=0
+admin-server.default.gRPCHost=0.0.0.0
health-checker.default.checkIntervalSeconds=30
core.default.gRPCSslEnabled=false
logql.default.restAcceptQueueSize=0
-receiver-clr.provider=default
+admin-server.default.port=17128
receiver-async-profiler.provider=default
-storage.provider=mysql
query.graphql.maxQueryComplexity=3000
core.default.httpMaxRequestHeaderSize=8192
-receiver-sharing-server.default.gRPCSslEnabled=false
-telemetry.prometheus.sslEnabled=false
receiver-sharing-server.default.authentication=******
aws-firehose.default.acceptQueueSize=0
-storage.mysql.properties.dataSource.prepStmtCacheSize=250
core.default.serviceCacheRefreshInterval=10
-core.default.gRPCSslKeyPath=
-receiver-ebpf.provider=default
telemetry.prometheus.sslCertChainPath=
receiver-sharing-server.default.maxConcurrentCallsPerConnection=0
+status.provider=default
storage.mysql.asyncBatchPersistentPoolSize=4
telemetry.provider=prometheus
core.default.trainingPeriodHttpUriRecognitionPattern=60
-promql.default.restContextPath=/
core.default.maxHeapMemoryUsagePercent=96
aws-firehose.default.contextPath=/
agent-analyzer.default.slowCacheWriteThreshold=default:20,redis:10
-envoy-metric.default.maxMessageSize=0
promql.default.buildInfoGoVersion=
storage.mysql.properties.dataSource.cachePrepStmts=true
receiver-ebpf.default.gRPCThreadPoolSize=0
query.graphql.enableLogTestTool=false
-envoy-metric.default.gRPCSslCertChainPath=
receiver-ebpf.default.gRPCPort=0
-promql.default.buildInfoRevision=
-receiver-otel.default.enabledOtelMetricsRules=apisix,nginx/*,k8s/*,istio-controlplane,vm,mysql/*,postgresql/*,oap,aws-eks/*,windows,aws-s3/*,aws-dynamodb/*,aws-gateway/*,redis/*,elasticsearch/*,rabbitmq/*,mongodb/*,kafka/*,pulsar/*,bookkeeper/*,rocketmq/*,clickhouse/*,activemq/*,kong/*,flink/*,banyandb/*,envoy-ai-gateway/*,ios/*,miniprogram/*
core.default.syncPeriodHttpUriRecognitionPattern=10
core.default.enableHierarchy=true
-event-analyzer.provider=default
receiver-otel.default.enabledHandlers=otlp-traces,otlp-metrics,otlp-logs
core.default.enableEndpointNameGroupingByOpenapi=true
-receiver-ebpf.default.gRPCHost=0.0.0.0
-receiver-browser.default.sampleRate=10000
-core.default.searchableTracesTags=http.method,http.status_code,rpc.status_code,db.type,db.instance,mq.queue,mq.topic,mq.broker
-core.default.instanceNameMaxLength=70
+admin-server.provider=default
core.default.restAcceptQueueSize=0
-receiver-sharing-server.default.gRPCHost=0.0.0.0
telemetry.prometheus.sslKeyPath=
log-analyzer.provider=default
envoy-metric.default.gRPCPort=0
-storage.mysql.properties.dataSource.password=******
+admin-server.default.acceptQueueSize=0
agent-analyzer.default.forceSampleErrorSegment=true
ai-pipeline.default.uriRecognitionServerPort=17128
-core.default.restIdleTimeOut=30000
+admin-server.default.httpMaxRequestHeaderSize=8192
receiver-sharing-server.default.restHost=0.0.0.0
receiver-sharing-server.provider=default
cluster.provider=standalone
receiver-sharing-server.default.restAcceptQueueSize=0
-agent-analyzer.provider=default
receiver-telegraf.provider=default
aws-firehose.default.tlsKeyPath=
receiver-sharing-server.default.maxMessageSize=52428800
-core.default.gRPCHost=0.0.0.0
envoy-metric.default.gRPCHost=0.0.0.0
logql.default.restIdleTimeOut=30000
service-mesh.provider=default
query.provider=graphql
receiver-telegraf.default.activeFiles=vm
-aws-firehose.default.maxRequestHeaderSize=8192
-core.default.endpointNameMaxLength=150
-core.default.metricsDataTTL=7
storage.mysql.properties.dataSource.user=******
-promql.provider=default
-core.default.maxConcurrentCallsPerConnection=0
configuration-discovery.provider=default
-ai-pipeline.default.baselineServerAddr=
core.provider=default
receiver-pprof.default.pprofMaxSize=31457280
health-checker.provider=default
logql.default.restHost=0.0.0.0
-receiver-sharing-server.default.restIdleTimeOut=30000
-configuration.provider=none
-logql.provider=default
-core.default.persistentPeriod=25
storage.mysql.metadataQueryMaxSize=5000
-storage.mysql.properties.jdbcUrl=jdbc:mysql://mysql:3306/swtest?allowMultiQueries=true
-receiver-sharing-server.default.gRPCThreadPoolSize=0
-receiver-register.provider=default
-core.default.storageSessionTimeout=70000
-receiver-trace.provider=default
envoy-metric.provider=default
-query.graphql.enableOnDemandPodLog=false
+admin-server.default.idleTimeOut=30000
receiver-sharing-server.default.restPort=0
-logql.default.restContextPath=/
promql.default.restAcceptQueueSize=0
telemetry.prometheus.host=0.0.0.0
receiver-ebpf.default.maxMessageSize=0
-receiver-profile.provider=default
-receiver-sharing-server.default.httpMaxRequestHeaderSize=8192
storage.mysql.properties.dataSource.useServerPrepStmts=true
core.default.gRPCThreadPoolSize=-1
-status-query.default.keywords4MaskingSecretsOfConfig=user,password,token,accessKey,secretKey,authentication
storage.mysql.maxSizeOfBatchSql=2000
log-analyzer.default.malFiles=nginx,miniprogram-wechat,miniprogram-alipay
-telemetry.prometheus.port=1234
+admin-server.default.gRPCMaxMessageSize=52428800
core.default.recordDataTTL=3
core.default.maxHttpUrisNumberPerService=3000
core.default.downsampling=[Hour, Day]
aws-firehose.default.idleTimeOut=30000
-core.default.maxDirectMemoryUsage=-1
-agent-analyzer.default.slowDBAccessThreshold=default:200,mongodb:100
-receiver-log.provider=default
receiver-sharing-server.default.restContextPath=/
-receiver-async-profiler.default.memoryParserEnabled=true
-envoy-metric.default.istioServiceNameRule=${serviceEntry.metadata.name}.${serviceEntry.metadata.namespace}
-envoy-metric.default.istioServiceEntryIgnoredNamespaces=
alarm.provider=default
-receiver-sharing-server.default.gRPCSslTrustedCAsPath=
aws-firehose.default.firehoseAccessKey=******
+inspect.provider=default
core.default.autocompleteTagValuesQueryMaxSize=100
-agent-analyzer.default.noUpstreamRealAddressAgents=6000,9000
core.default.searchableLogsTags=level,http.status_code,ai_route_type
core.default.role=Mixed
-receiver-sharing-server.default.gRPCSslKeyPath=
receiver-pprof.provider=default
receiver-ebpf.default.gRPCSslEnabled=false
-envoy-metric.default.gRPCThreadPoolSize=0
-aws-firehose.default.host=0.0.0.0
-promql.default.restHost=0.0.0.0
-envoy-metric.default.gRPCSslEnabled=false
-core.default.gRPCSslCertChainPath=
envoy-metric.default.enabledEnvoyMetricsRules=envoy,envoy-svc-relation
-receiver-meter.provider=default
ai-pipeline.default.uriRecognitionServerAddr=
promql.default.restIdleTimeOut=30000
-status-query.provider=default
envoy-metric.default.gRPCSslKeyPath=
core.default.gRPCPort=11800
-receiver-ebpf.default.gRPCSslTrustedCAsPath=
-receiver-jvm.provider=default
receiver-ebpf.default.gRPCSslCertChainPath=
promql.default.buildInfoBuildDate=
-logql.default.restPort=3100
-core.default.l1FlushPeriod=500
promql.default.restPort=9090
receiver-async-profiler.default.jfrMaxSize=31457280
-envoy-metric.default.alsTCPAnalysis=
core.default.restHost=0.0.0.0
-envoy-metric.default.acceptMetricsService=true
core.default.restContextPath=/
-envoy-metric.default.gRPCSslTrustedCAsPath=
-storage.mysql.properties.dataSource.prepStmtCacheSqlLimit=2048
-promql.default.buildInfoBranch=
envoy-metric.default.alsHTTPAnalysis=
core.default.maxMessageSize=52428800
-core.default.dataKeeperExecutePeriod=5
-log-analyzer.default.lalFiles=envoy-als,mesh-dp,mysql-slowsql,pgsql-slowsql,redis-slowsql,k8s-service,nginx,envoy-ai-gateway,miniprogram,default
promql.default.buildInfoBuildUser=******
aws-firehose.default.enableTLS=false
ai-pipeline.default.baselineServerPort=18080
-receiver-otel.provider=default
core.default.activeExtraModelColumns=false
-query.graphql.enableUpdateUITemplate=false
\ No newline at end of file
+query.graphql.enableUpdateUITemplate=false
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine b/test/e2e-v2/cases/storage/expected/inspect-entities-endpoint-cpm.yml
similarity index 60%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
copy to test/e2e-v2/cases/storage/expected/inspect-entities-endpoint-cpm.yml
index e63b6e2066..ef096c68fd 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
+++ b/test/e2e-v2/cases/storage/expected/inspect-entities-endpoint-cpm.yml
@@ -1,4 +1,3 @@
-#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -13,7 +12,22 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-#
-
-org.apache.skywalking.oap.query.debug.StatusQueryModule
+metric: endpoint_cpm
+scope: Endpoint
+step: DAY
+start: {{ notEmpty .start }}
+end: {{ notEmpty .end }}
+rows:
+ {{- contains .rows }}
+ - entityId: {{ notEmpty .entityId }}
+ decoded:
+ serviceName: {{ notEmpty .decoded.serviceName }}
+ isReal: true
+ endpointName: {{ notEmpty .decoded.endpointName }}
+ layer: GENERAL
+ mqeEntity:
+ scope: Endpoint
+ serviceName: {{ notEmpty .mqeEntity.serviceName }}
+ normal: true
+ endpointName: {{ notEmpty .mqeEntity.endpointName }}
+ {{- end }}
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider b/test/e2e-v2/cases/storage/expected/inspect-entities-service-cpm-hour.yml
similarity index 53%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
rename to test/e2e-v2/cases/storage/expected/inspect-entities-service-cpm-hour.yml
index 94780edbbf..dd997d5f50 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
+++ b/test/e2e-v2/cases/storage/expected/inspect-entities-service-cpm-hour.yml
@@ -1,4 +1,3 @@
-#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -13,7 +12,30 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-#
-
-org.apache.skywalking.oap.query.debug.StatusQueryProvider
\ No newline at end of file
+# 2-hour / step=HOUR window. Same expected shape as the DAY case.
+metric: service_cpm
+scope: Service
+step: HOUR
+start: {{ notEmpty .start }}
+end: {{ notEmpty .end }}
+rows:
+ {{- contains .rows }}
+ - entityId: {{ b64enc "e2e-service-provider" }}.1
+ decoded:
+ serviceName: e2e-service-provider
+ isReal: true
+ layer: GENERAL
+ mqeEntity:
+ scope: Service
+ serviceName: e2e-service-provider
+ normal: true
+ - entityId: {{ b64enc "e2e-service-consumer" }}.1
+ decoded:
+ serviceName: e2e-service-consumer
+ isReal: true
+ layer: GENERAL
+ mqeEntity:
+ scope: Service
+ serviceName: e2e-service-consumer
+ normal: true
+ {{- end }}
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine b/test/e2e-v2/cases/storage/expected/inspect-entities-service-cpm-minute.yml
similarity index 53%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
copy to test/e2e-v2/cases/storage/expected/inspect-entities-service-cpm-minute.yml
index e63b6e2066..90739d41d4 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
+++ b/test/e2e-v2/cases/storage/expected/inspect-entities-service-cpm-minute.yml
@@ -1,4 +1,3 @@
-#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -13,7 +12,30 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-#
-
-org.apache.skywalking.oap.query.debug.StatusQueryModule
+# 20-minute / step=MINUTE window. Same expected shape as the DAY case.
+metric: service_cpm
+scope: Service
+step: MINUTE
+start: {{ notEmpty .start }}
+end: {{ notEmpty .end }}
+rows:
+ {{- contains .rows }}
+ - entityId: {{ b64enc "e2e-service-provider" }}.1
+ decoded:
+ serviceName: e2e-service-provider
+ isReal: true
+ layer: GENERAL
+ mqeEntity:
+ scope: Service
+ serviceName: e2e-service-provider
+ normal: true
+ - entityId: {{ b64enc "e2e-service-consumer" }}.1
+ decoded:
+ serviceName: e2e-service-consumer
+ isReal: true
+ layer: GENERAL
+ mqeEntity:
+ scope: Service
+ serviceName: e2e-service-consumer
+ normal: true
+ {{- end }}
diff --git a/test/e2e-v2/cases/storage/expected/inspect-entities-service-cpm.yml b/test/e2e-v2/cases/storage/expected/inspect-entities-service-cpm.yml
new file mode 100644
index 0000000000..882bd146e1
--- /dev/null
+++ b/test/e2e-v2/cases/storage/expected/inspect-entities-service-cpm.yml
@@ -0,0 +1,44 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# /inspect/entities?metric=service_cpm&start=…&end=…&step=MINUTE — expect provider
+# and consumer rows (the java-agent showcase deploys both). Date strings are
+# dynamic per run; only metric / scope / step / rows are asserted.
+metric: service_cpm
+scope: Service
+step: DAY
+start: {{ notEmpty .start }}
+end: {{ notEmpty .end }}
+rows:
+ {{- contains .rows }}
+ - entityId: {{ b64enc "e2e-service-provider" }}.1
+ decoded:
+ serviceName: e2e-service-provider
+ isReal: true
+ layer: GENERAL
+ mqeEntity:
+ scope: Service
+ serviceName: e2e-service-provider
+ normal: true
+ - entityId: {{ b64enc "e2e-service-consumer" }}.1
+ decoded:
+ serviceName: e2e-service-consumer
+ isReal: true
+ layer: GENERAL
+ mqeEntity:
+ scope: Service
+ serviceName: e2e-service-consumer
+ normal: true
+ {{- end }}
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine b/test/e2e-v2/cases/storage/expected/inspect-entities-service-instance-cpm.yml
similarity index 57%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
copy to test/e2e-v2/cases/storage/expected/inspect-entities-service-instance-cpm.yml
index e63b6e2066..449288221d 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
+++ b/test/e2e-v2/cases/storage/expected/inspect-entities-service-instance-cpm.yml
@@ -1,4 +1,3 @@
-#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -13,7 +12,22 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-#
-
-org.apache.skywalking.oap.query.debug.StatusQueryModule
+metric: service_instance_cpm
+scope: ServiceInstance
+step: DAY
+start: {{ notEmpty .start }}
+end: {{ notEmpty .end }}
+rows:
+ {{- contains .rows }}
+ - entityId: {{ notEmpty .entityId }}
+ decoded:
+ serviceName: {{ notEmpty .decoded.serviceName }}
+ isReal: true
+ serviceInstanceName: {{ notEmpty .decoded.serviceInstanceName }}
+ layer: GENERAL
+ mqeEntity:
+ scope: ServiceInstance
+ serviceName: {{ notEmpty .mqeEntity.serviceName }}
+ normal: true
+ serviceInstanceName: {{ notEmpty .mqeEntity.serviceInstanceName }}
+ {{- end }}
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine b/test/e2e-v2/cases/storage/expected/inspect-entities-service-percentile.yml
similarity index 63%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
copy to test/e2e-v2/cases/storage/expected/inspect-entities-service-percentile.yml
index e63b6e2066..db7b565dbb 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
+++ b/test/e2e-v2/cases/storage/expected/inspect-entities-service-percentile.yml
@@ -1,4 +1,3 @@
-#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -13,7 +12,21 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-#
-
-org.apache.skywalking.oap.query.debug.StatusQueryModule
+# LABELED_VALUE metric — same Service-scope rows as service_cpm.
+metric: service_percentile
+scope: Service
+step: DAY
+start: {{ notEmpty .start }}
+end: {{ notEmpty .end }}
+rows:
+ {{- contains .rows }}
+ - entityId: {{ b64enc "e2e-service-provider" }}.1
+ decoded:
+ serviceName: e2e-service-provider
+ isReal: true
+ layer: GENERAL
+ mqeEntity:
+ scope: Service
+ serviceName: e2e-service-provider
+ normal: true
+ {{- end }}
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine b/test/e2e-v2/cases/storage/expected/inspect-entities-service-relation-cpm.yml
similarity index 54%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
copy to test/e2e-v2/cases/storage/expected/inspect-entities-service-relation-cpm.yml
index e63b6e2066..e1ec6a936d 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
+++ b/test/e2e-v2/cases/storage/expected/inspect-entities-service-relation-cpm.yml
@@ -1,4 +1,3 @@
-#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -13,7 +12,26 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-#
-
-org.apache.skywalking.oap.query.debug.StatusQueryModule
+metric: service_relation_client_cpm
+scope: ServiceRelation
+step: DAY
+start: {{ notEmpty .start }}
+end: {{ notEmpty .end }}
+rows:
+ {{- contains .rows }}
+ - entityId: {{ notEmpty .entityId }}
+ decoded:
+ source:
+ serviceName: {{ notEmpty .decoded.source.serviceName }}
+ isReal: true
+ destination:
+ serviceName: {{ notEmpty .decoded.destination.serviceName }}
+ isReal: true
+ layer: {{ notEmpty .layer }}
+ mqeEntity:
+ scope: ServiceRelation
+ serviceName: {{ notEmpty .mqeEntity.serviceName }}
+ normal: true
+ destServiceName: {{ notEmpty .mqeEntity.destServiceName }}
+ destNormal: true
+ {{- end }}
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine b/test/e2e-v2/cases/storage/expected/inspect-entities-unknown-metric.yml
similarity index 79%
copy from oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
copy to test/e2e-v2/cases/storage/expected/inspect-entities-unknown-metric.yml
index e63b6e2066..80d0b6f0e3 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
+++ b/test/e2e-v2/cases/storage/expected/inspect-entities-unknown-metric.yml
@@ -1,4 +1,3 @@
-#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -13,7 +12,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-#
-
-org.apache.skywalking.oap.query.debug.StatusQueryModule
+# /inspect/entities for a metric that is not in ValueColumnMetadata — 400
+# error JSON shape (curl -s strips status but the body is the ErrorResponse).
+error: 'unknown metric: nonexistent_metric_xyz'
diff --git a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine b/test/e2e-v2/cases/storage/expected/inspect-metrics.yml
similarity index 69%
rename from oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
rename to test/e2e-v2/cases/storage/expected/inspect-metrics.yml
index e63b6e2066..d43597024c 100644
--- a/oap-server/server-query-plugin/status-query-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
+++ b/test/e2e-v2/cases/storage/expected/inspect-metrics.yml
@@ -1,4 +1,3 @@
-#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -14,6 +13,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-#
-
-org.apache.skywalking.oap.query.debug.StatusQueryModule
+# /inspect/metrics?regex=service_cpm — one row.
+metrics:
+ {{- contains .metrics }}
+ - name: service_cpm
+ type: REGULAR_VALUE
+ catalog: SERVICE
+ scopeId: 1
+ scope: Service
+ valueColumnName: value
+ downsamplings:
+ {{- contains .downsamplings }}
+ - MINUTE
+ - HOUR
+ - DAY
+ {{- end }}
+ {{- end }}
diff --git a/test/e2e-v2/cases/storage/mysql/docker-compose.yml b/test/e2e-v2/cases/storage/mysql/docker-compose.yml
index 2533e454b9..002a48d37f 100644
--- a/test/e2e-v2/cases/storage/mysql/docker-compose.yml
+++ b/test/e2e-v2/cases/storage/mysql/docker-compose.yml
@@ -35,10 +35,13 @@ services:
file: ../../../script/docker-compose/base-compose.yml
service: oap
environment:
+ SW_ADMIN_SERVER: default
+ SW_INSPECT: default
SW_STORAGE: mysql
SW_JDBC_URL: jdbc:mysql://mysql:3306/swtest?allowMultiQueries=true
ports:
- 12800
+ - 17128
entrypoint: ['sh', '-c', '/download-mysql.sh /skywalking/oap-libs && /skywalking/docker-entrypoint.sh']
networks:
- e2e
diff --git a/test/e2e-v2/cases/storage/opensearch/docker-compose.yml b/test/e2e-v2/cases/storage/opensearch/docker-compose.yml
index b95023e3d8..8fd7934e51 100644
--- a/test/e2e-v2/cases/storage/opensearch/docker-compose.yml
+++ b/test/e2e-v2/cases/storage/opensearch/docker-compose.yml
@@ -103,6 +103,8 @@ services:
file: ../../../script/docker-compose/base-compose.yml
service: oap
environment:
+ SW_ADMIN_SERVER: default
+ SW_INSPECT: default
SW_STORAGE: elasticsearch
SW_STORAGE_ES_CLUSTER_NODES: opensearch:9200
SW_ES_USER: admin
@@ -114,6 +116,7 @@ services:
SW_STORAGE_ES_SSL_KEY_STORE_PASS: changeit
ports:
- 12800
+ - 17128
volumes:
- ./certs:/etc/skywalking/certs:ro
depends_on:
diff --git a/test/e2e-v2/cases/storage/postgres/docker-compose.yml b/test/e2e-v2/cases/storage/postgres/docker-compose.yml
index 506b338f8d..6c2f10abe9 100644
--- a/test/e2e-v2/cases/storage/postgres/docker-compose.yml
+++ b/test/e2e-v2/cases/storage/postgres/docker-compose.yml
@@ -34,10 +34,13 @@ services:
file: ../../../script/docker-compose/base-compose.yml
service: oap
environment:
+ SW_ADMIN_SERVER: default
+ SW_INSPECT: default
SW_STORAGE: postgresql
SW_JDBC_URL: "jdbc:postgresql://postgres:5432/skywalking"
ports:
- 12800
+ - 17128
depends_on:
postgres:
condition: service_healthy
diff --git a/test/e2e-v2/cases/storage/storage-cases.yaml b/test/e2e-v2/cases/storage/storage-cases.yaml
index fe0d7407b3..36203e5677 100644
--- a/test/e2e-v2/cases/storage/storage-cases.yaml
+++ b/test/e2e-v2/cases/storage/storage-cases.yaml
@@ -154,3 +154,71 @@ cases:
expected: expected/topN-OP-endpoint.yml
- query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression="top_n(endpoint_resp_time,3,des,attr0!='Not_GENERAL')"
expected: expected/topN-OP-endpoint.yml
+
+ # =====================================================================
+ # Inspect API validation
+ #
+ # Admin-server endpoints (port 17128) for the metric catalog and
+ # per-metric entity enumeration. Ordered after the basic functional
+ # validation above so any breakage of the underlying GraphQL / storage
+ # path shows up first, and the inspect-specific surface is exercised
+ # against a known-good OAP. Runs across every storage backend that
+ # includes this file (BanyanDB, Elasticsearch, MySQL, OpenSearch,
+ # PostgreSQL).
+ # =====================================================================
+
+ # /inspect/metrics — catalog endpoint, asserts service_cpm shows the
+ # expected type / scope / catalog / supported downsamplings.
+ - query: curl -s "http://${oap_host}:${oap_17128}/inspect/metrics?regex=service_cpm" | yq -P
+ expected: expected/inspect-metrics.yml
+ # /inspect/entities — Service scope, step=DAY (single bucket = today UTC).
+ # Avoids the framework's MAX_TIME_RANGE=500 cap and works on any host shell
+ # (BSD date on macOS, GNU date in CI; both accept `date -u +%Y-%m-%d`).
+ - query: |
+ DAY=$(date -u +"%Y-%m-%d")
+ curl -s "http://${oap_host}:${oap_17128}/inspect/entities?metric=service_cpm&start=${DAY}&end=${DAY}&step=DAY" | yq -P
+ expected: expected/inspect-entities-service-cpm.yml
+ # ServiceInstance scope — instance-id decode + parent-service layer lookup.
+ - query: |
+ DAY=$(date -u +"%Y-%m-%d")
+ curl -s "http://${oap_host}:${oap_17128}/inspect/entities?metric=service_instance_cpm&start=${DAY}&end=${DAY}&step=DAY" | yq -P
+ expected: expected/inspect-entities-service-instance-cpm.yml
+ # Endpoint scope — endpoint-id decode.
+ - query: |
+ DAY=$(date -u +"%Y-%m-%d")
+ curl -s "http://${oap_host}:${oap_17128}/inspect/entities?metric=endpoint_cpm&start=${DAY}&end=${DAY}&step=DAY" | yq -P
+ expected: expected/inspect-entities-endpoint-cpm.yml
+ # ServiceRelation scope — analysisRelationId decode + paired source/destination
+ # shape + MqeEntity dest* fields.
+ - query: |
+ DAY=$(date -u +"%Y-%m-%d")
+ curl -s "http://${oap_host}:${oap_17128}/inspect/entities?metric=service_relation_client_cpm&start=${DAY}&end=${DAY}&step=DAY" | yq -P
+ expected: expected/inspect-entities-service-relation-cpm.yml
+ # LABELED_VALUE metric — Service-scope entity decode is identical but the
+ # storage value column shape differs (BanyanDB DataTable, ES JSON, JDBC
+ # VARCHAR).
+ - query: |
+ DAY=$(date -u +"%Y-%m-%d")
+ curl -s "http://${oap_host}:${oap_17128}/inspect/entities?metric=service_percentile&start=${DAY}&end=${DAY}&step=DAY" | yq -P
+ expected: expected/inspect-entities-service-percentile.yml
+ # step=MINUTE over a 20-minute window. Exercises minute-precision range.
+ # python3 keeps the date math portable across BSD / GNU date.
+ - query: |
+ START=$(python3 -c "import datetime as d; print((d.datetime.utcnow() - d.timedelta(minutes=20)).strftime('%Y-%m-%d %H%M'))" | sed 's/ /%20/')
+ END=$(python3 -c "import datetime as d; print(d.datetime.utcnow().strftime('%Y-%m-%d %H%M'))" | sed 's/ /%20/')
+ curl -s "http://${oap_host}:${oap_17128}/inspect/entities?metric=service_cpm&start=${START}&end=${END}&step=MINUTE" | yq -P
+ expected: expected/inspect-entities-service-cpm-minute.yml
+ # step=HOUR over a full-day window (00 → 23 UTC). Avoids the flakiness of
+ # a sliding 2-hour window — postgres / slow downsamplers may not have
+ # populated the *current* HOUR bucket yet, but at least one earlier HOUR
+ # bucket in today is guaranteed once the MINUTE case has data.
+ - query: |
+ START=$(date -u +"%Y-%m-%d")%2000
+ END=$(date -u +"%Y-%m-%d")%2023
+ curl -s "http://${oap_host}:${oap_17128}/inspect/entities?metric=service_cpm&start=${START}&end=${END}&step=HOUR" | yq -P
+ expected: expected/inspect-entities-service-cpm-hour.yml
+ # Negative — unknown metric returns a 400-shaped error JSON.
+ - query: |
+ DAY=$(date -u +"%Y-%m-%d")
+ curl -s "http://${oap_host}:${oap_17128}/inspect/entities?metric=nonexistent_metric_xyz&start=${DAY}&end=${DAY}&step=DAY" | yq -P
+ expected: expected/inspect-entities-unknown-metric.yml