This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-cli.git


The following commit(s) were added to refs/heads/master by this push:
     new 85e5afd  Add `admin inspect values` to read foreign metric values 
(#232)
85e5afd is described below

commit 85e5afdb3d55c6e5af66a472c3fe8ac024d11690
Author: 吴晟 Wu Sheng <[email protected]>
AuthorDate: Wed Jun 24 21:39:21 2026 +0800

    Add `admin inspect values` to read foreign metric values (#232)
    
    The new sub-command POSTs to the OAP admin API `POST /inspect/values` to
    evaluate an MQE expression over metric(s) the target OAP does not define
    locally (foreign metrics), supplying each metric's {valueColumn, valueType}.
    The entity scope is inferred from which name flag is set (--service-name /
    --service-instance-name / --endpoint-name). Returns the native MQE result.
---
 CHANGES.md                                 |  1 +
 internal/commands/admin/inspect/inspect.go | 95 ++++++++++++++++++++++++++++++
 pkg/admin/inspect/inspect.go               | 83 ++++++++++++++++++++++++++
 3 files changed, 179 insertions(+)

diff --git a/CHANGES.md b/CHANGES.md
index 6de747b..a401f8a 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -17,6 +17,7 @@ Release Notes.
 * Remove the oldest `queryTraceFromColdStage` query call in the `trace list` 
command by @mrproliu in https://github.com/apache/skywalking-cli/pull/225
 * Add the sub-command `profiling pprof` for pprof query API by @JophieQu in 
https://github.com/apache/skywalking-cli/pull/226
 * Add the `admin` command group for the OAP admin-server REST host (default 
port `17128`), with a new global `--admin-url` flag (derived from `--base-url` 
when unset). Covers every admin feature module: `admin preflight`; `admin 
cluster nodes`, `admin config dump|ttl`, `admin alarm rules|rule` (status); 
`admin inspect metrics|entities` (inspect); `admin ui-template 
list|get|create|update|disable` (ui-management); `admin runtime-rule 
list|bundled|get|add|inactivate|delete|dump` (runtime-r [...]
+* Add the sub-command `admin inspect values` to read the VALUES of metric(s) 
an OAP does not define locally (foreign metrics) by supplying their 
`{valueColumn, valueType}` metadata, via the new `POST /inspect/values` admin 
API; returns the native MQE result.
 
 ### Bug Fixes
 
diff --git a/internal/commands/admin/inspect/inspect.go 
b/internal/commands/admin/inspect/inspect.go
index 385807a..df9f1c3 100644
--- a/internal/commands/admin/inspect/inspect.go
+++ b/internal/commands/admin/inspect/inspect.go
@@ -19,6 +19,7 @@ package inspect
 
 import (
        "fmt"
+       "strings"
 
        "github.com/urfave/cli/v2"
 
@@ -37,6 +38,7 @@ var Command = &cli.Command{
        Subcommands: []*cli.Command{
                metricsCommand,
                entitiesCommand,
+               valuesCommand,
        },
 }
 
@@ -144,3 +146,96 @@ $ swctl admin inspect entities --metric meter_foo 
--value-column value --value-t
                return display.Display(ctx.Context, 
&displayable.Displayable{Data: entities})
        },
 }
+
+var valuesCommand = &cli.Command{
+       Name:  "values",
+       Usage: "Read the VALUES of metric(s) persisted by another OAP (POST 
/inspect/values)",
+       UsageText: `Evaluate an MQE expression over metric(s) the target OAP 
does not define locally, by
+supplying each foreign metric's metadata. Returns the native MQE result (the 
same shape the UI
+renders for catalog metrics). The entity scope is inferred from which name 
flags are set.
+
+Example — read a foreign service metric's value series:
+$ swctl admin inspect values --expression meter_foo --service-name my-svc \
+    --foreign-metric meter_foo,value,LONG`,
+       Flags: flags.Flags(
+               flags.DurationFlags,
+               []cli.Flag{
+                       &cli.StringFlag{
+                               Name:     "expression",
+                               Usage:    "the MQE `expression` to evaluate",
+                               Required: true,
+                       },
+                       &cli.StringFlag{
+                               Name:     "service-name",
+                               Usage:    "the `service` entity to query",
+                               Required: true,
+                       },
+                       &cli.StringFlag{
+                               Name:  "service-instance-name",
+                               Usage: "the `instance` to query (selects 
ServiceInstance scope)",
+                       },
+                       &cli.StringFlag{
+                               Name:  "endpoint-name",
+                               Usage: "the `endpoint` to query (selects 
Endpoint scope)",
+                       },
+                       &cli.BoolFlag{
+                               Name:  "normal",
+                               Usage: "whether the service is normal 
(agent-reported)",
+                               Value: true,
+                       },
+                       &cli.StringSliceFlag{
+                               Name: "foreign-metric",
+                               Usage: "metadata for one foreign metric as 
`name,valueColumn,valueType` " +
+                                       "(repeatable — supply one per foreign 
metric in the expression)",
+                               Required: true,
+                       },
+               },
+       ),
+       Before: interceptor.BeforeChain(
+               interceptor.DurationInterceptor,
+       ),
+       Action: func(ctx *cli.Context) error {
+               step := ctx.Generic("step").(*model.StepEnumValue).Selected
+
+               entity := inspect.QueryEntity{
+                       ServiceName: ctx.String("service-name"),
+                       Normal:      ctx.Bool("normal"),
+               }
+               switch {
+               case ctx.String("endpoint-name") != "":
+                       entity.Scope = "Endpoint"
+                       entity.EndpointName = ctx.String("endpoint-name")
+               case ctx.String("service-instance-name") != "":
+                       entity.Scope = "ServiceInstance"
+                       entity.ServiceInstanceName = 
ctx.String("service-instance-name")
+               default:
+                       entity.Scope = "Service"
+               }
+
+               var foreign []inspect.ForeignMetricInput
+               for _, fm := range ctx.StringSlice("foreign-metric") {
+                       parts := strings.Split(fm, ",")
+                       if len(parts) != 3 {
+                               return fmt.Errorf("--foreign-metric must be 
name,valueColumn,valueType (got %q)", fm)
+                       }
+                       foreign = append(foreign, inspect.ForeignMetricInput{
+                               Name:        parts[0],
+                               ValueColumn: parts[1],
+                               ValueType:   parts[2],
+                       })
+               }
+
+               result, err := inspect.ListValues(ctx.Context, 
&inspect.ValuesOptions{
+                       Expression:     ctx.String("expression"),
+                       Entity:         entity,
+                       Start:          ctx.String("start"),
+                       End:            ctx.String("end"),
+                       Step:           string(step),
+                       ForeignMetrics: foreign,
+               })
+               if err != nil {
+                       return preflight.Explain(ctx.Context, err, 
preflight.ModuleInspect, "SW_INSPECT")
+               }
+               return display.Display(ctx.Context, 
&displayable.Displayable{Data: result})
+       },
+}
diff --git a/pkg/admin/inspect/inspect.go b/pkg/admin/inspect/inspect.go
index ef3ee20..8d80295 100644
--- a/pkg/admin/inspect/inspect.go
+++ b/pkg/admin/inspect/inspect.go
@@ -23,6 +23,7 @@ package inspect
 
 import (
        "context"
+       "net/http"
        "net/url"
        "strconv"
 
@@ -132,3 +133,85 @@ func ListEntities(ctx context.Context, opts 
*EntitiesOptions) (*Entities, error)
        err := client.GetJSON(ctx, "/inspect/entities", query, &out)
        return &out, err
 }
+
+// QueryEntity is the MQE query entity for a value query; its scope binds 
every foreign metric.
+type QueryEntity struct {
+       Scope               string `json:"scope,omitempty"`
+       ServiceName         string `json:"serviceName,omitempty"`
+       Normal              bool   `json:"normal"`
+       ServiceInstanceName string `json:"serviceInstanceName,omitempty"`
+       EndpointName        string `json:"endpointName,omitempty"`
+}
+
+// ForeignMetricInput is caller-supplied metadata for one metric the target 
OAP does not define.
+type ForeignMetricInput struct {
+       Name        string `json:"name"`
+       ValueColumn string `json:"valueColumn"`
+       ValueType   string `json:"valueType"`
+}
+
+// ValuesOptions holds the parameters of POST /inspect/values.
+type ValuesOptions struct {
+       Expression     string
+       Entity         QueryEntity
+       Start          string
+       End            string
+       Step           string
+       ForeignMetrics []ForeignMetricInput
+}
+
+// ExpressionResult is the native MQE result returned by POST /inspect/values, 
mirroring the
+// GraphQL execExpression shape.
+type ExpressionResult struct {
+       Type    string      `json:"type"`
+       Error   string      `json:"error,omitempty"`
+       Results []MQEValues `json:"results"`
+}
+
+// MQEValues is one (optionally labeled) series of an ExpressionResult.
+type MQEValues struct {
+       Metric MQEMetric  `json:"metric"`
+       Values []MQEValue `json:"values"`
+}
+
+// MQEMetric carries the label set of an MQEValues series.
+type MQEMetric struct {
+       Labels []KeyValue `json:"labels"`
+}
+
+// MQEValue is one time-bucket point.
+type MQEValue struct {
+       ID    string `json:"id"`
+       Value string `json:"value"`
+}
+
+// KeyValue is a metric label.
+type KeyValue struct {
+       Key   string `json:"key"`
+       Value string `json:"value"`
+}
+
+type valuesRequest struct {
+       Expression     string               `json:"expression"`
+       Entity         QueryEntity          `json:"entity"`
+       Start          string               `json:"start"`
+       End            string               `json:"end"`
+       Step           string               `json:"step"`
+       ForeignMetrics []ForeignMetricInput `json:"foreignMetrics"`
+}
+
+// ListValues evaluates an MQE expression over foreign metric(s) — metrics the 
target OAP does not
+// define locally — by supplying their metadata (POST /inspect/values). 
Returns the native MQE result.
+func ListValues(ctx context.Context, opts *ValuesOptions) (*ExpressionResult, 
error) {
+       body := valuesRequest{
+               Expression:     opts.Expression,
+               Entity:         opts.Entity,
+               Start:          opts.Start,
+               End:            opts.End,
+               Step:           opts.Step,
+               ForeignMetrics: opts.ForeignMetrics,
+       }
+       var out ExpressionResult
+       err := client.SendJSON(ctx, http.MethodPost, "/inspect/values", nil, 
body, &out)
+       return &out, err
+}

Reply via email to