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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-rule); and `admin dsl-debug status|sessions|session start|get|stop` plus `admin oal files|file|rules|rule` (dsl-debugging).
* 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

Expand Down
95 changes: 95 additions & 0 deletions internal/commands/admin/inspect/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package inspect

import (
"fmt"
"strings"

"github.com/urfave/cli/v2"

Expand All @@ -37,6 +38,7 @@ var Command = &cli.Command{
Subcommands: []*cli.Command{
metricsCommand,
entitiesCommand,
valuesCommand,
},
}

Expand Down Expand Up @@ -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})
},
}
83 changes: 83 additions & 0 deletions pkg/admin/inspect/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ package inspect

import (
"context"
"net/http"
"net/url"
"strconv"

Expand Down Expand Up @@ -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
}
Loading