blob: df9f1c351015b003c6e78ee8b2f64db6991702e4 [file]
// Licensed to 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. Apache Software Foundation (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 inspect
import (
"fmt"
"strings"
"github.com/urfave/cli/v2"
"github.com/apache/skywalking-cli/internal/commands/interceptor"
"github.com/apache/skywalking-cli/internal/flags"
"github.com/apache/skywalking-cli/internal/model"
"github.com/apache/skywalking-cli/pkg/admin/inspect"
"github.com/apache/skywalking-cli/pkg/admin/preflight"
"github.com/apache/skywalking-cli/pkg/display"
"github.com/apache/skywalking-cli/pkg/display/displayable"
)
var Command = &cli.Command{
Name: "inspect",
Usage: "Browse the metric catalog and entities from the admin-server `inspect` module",
Subcommands: []*cli.Command{
metricsCommand,
entitiesCommand,
valuesCommand,
},
}
var metricsCommand = &cli.Command{
Name: "metrics",
Usage: "List the registered metric catalog (GET /inspect/metrics)",
UsageText: `List the registered metrics with their type, scope and supported downsamplings.
Examples:
1. List every metric:
$ swctl admin inspect metrics
2. List service metrics that /inspect/entities accepts:
$ swctl admin inspect metrics --catalog SERVICE --mqe-queryable`,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "regex",
Usage: "filter metric names by a Java `regex` (default matches all)",
},
&cli.StringSliceFlag{
Name: "type",
Usage: "filter by metric `type` (REGULAR_VALUE / LABELED_VALUE / HEATMAP / SAMPLED_RECORD); repeatable",
},
&cli.StringSliceFlag{
Name: "catalog",
Usage: "filter by `catalog` (SERVICE / SERVICE_INSTANCE / ENDPOINT / *_RELATION); repeatable",
},
&cli.BoolFlag{
Name: "mqe-queryable",
Usage: "return only metrics that /inspect/entities accepts (REGULAR_VALUE, LABELED_VALUE)",
},
},
Action: func(ctx *cli.Context) error {
metrics, err := inspect.ListMetrics(ctx.Context, inspect.MetricsOptions{
Regex: ctx.String("regex"),
Types: ctx.StringSlice("type"),
Catalogs: ctx.StringSlice("catalog"),
MQEQueryable: ctx.Bool("mqe-queryable"),
})
if err != nil {
return preflight.Explain(ctx.Context, err, preflight.ModuleInspect, "SW_INSPECT")
}
return display.Display(ctx.Context, &displayable.Displayable{Data: metrics})
},
}
var entitiesCommand = &cli.Command{
Name: "entities",
Usage: "Enumerate the entities holding values for a metric (GET /inspect/entities)",
UsageText: `Enumerate the entities currently holding values for a metric over a time range.
Each row carries an MQE-ready entity to paste into a follow-up "swctl metrics exec"
(execExpression) query. Only REGULAR_VALUE / LABELED_VALUE metrics are accepted.
Examples:
1. Entities reporting service_cpm in the last 30 minutes:
$ swctl admin inspect entities --metric service_cpm
2. Inspect a metric persisted by ANOTHER OAP (not defined on this OAP) — supply its value
column + type so the OAP resolves it from storage without its local registry:
$ swctl admin inspect entities --metric meter_foo --value-column value --value-type LONG`,
Flags: flags.Flags(
flags.DurationFlags,
[]cli.Flag{
&cli.StringFlag{
Name: "metric",
Usage: "the `metric` name to enumerate entities for",
Required: true,
},
&cli.IntFlag{
Name: "limit",
Usage: fmt.Sprintf("max rows scanned at the storage layer (1-%d, server default 300)", inspect.MaxLimit),
},
&cli.StringFlag{
Name: "value-column",
Usage: "the metric's value `column` (e.g. value, value_, double_value); REQUIRED when the metric is not defined on the target OAP",
},
&cli.StringFlag{
Name: "value-type",
Usage: "value data `type` (LONG / INT / DOUBLE / LABELED); REQUIRED when the metric is not defined on the target OAP",
},
},
),
Before: interceptor.BeforeChain(
interceptor.DurationInterceptor,
),
Action: func(ctx *cli.Context) error {
limit := ctx.Int("limit")
if limit < 0 || limit > inspect.MaxLimit {
return fmt.Errorf("--limit must be between 1 and %d", inspect.MaxLimit)
}
step := ctx.Generic("step").(*model.StepEnumValue).Selected
entities, err := inspect.ListEntities(ctx.Context, &inspect.EntitiesOptions{
Metric: ctx.String("metric"),
Start: ctx.String("start"),
End: ctx.String("end"),
Step: string(step),
Limit: limit,
ValueColumn: ctx.String("value-column"),
ValueType: ctx.String("value-type"),
})
if err != nil {
return preflight.Explain(ctx.Context, err, preflight.ModuleInspect, "SW_INSPECT")
}
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})
},
}