1
0
mirror of https://github.com/minio/mc.git synced 2025-04-18 10:04:03 +03:00
mc/cmd/admin-prometheus-metrics.go
Shireesh Anjal ddfd44cb35
Support metrics-v3 api in admin prometheus generate (#4985)
Add a flag --api-version with possible values of v2 and v3, with default
being v2.

So when this flag is not passed, the command will work exactly as it did
before.

When using v3, there is a different set of metric types supported. It
also supports a new flag:

bucket: the bucket for which the metrics are to be fetched. it is
applicable only for the metric types that are available at bucket level,
which currently are 'api' and 'replication'
2024-07-11 11:00:59 -07:00

249 lines
6.7 KiB
Go

// Copyright (c) 2015-2024 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"errors"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/minio/cli"
json "github.com/minio/colorjson"
"github.com/minio/madmin-go/v3"
"github.com/minio/mc/pkg/probe"
"github.com/minio/minio-go/v7/pkg/set"
)
var metricsFlags = append(metricsV3Flags,
cli.StringFlag{
Name: "api-version",
Usage: "version of metrics api to use. valid values are ['v2', 'v3']. defaults to 'v2' if not specified.",
Value: "v2",
})
var metricsV2SubSystems = set.CreateStringSet("node", "bucket", "cluster", "resource")
var adminPrometheusMetricsCmd = cli.Command{
Name: "metrics",
Usage: "print prometheus metrics",
OnUsageError: onUsageError,
Action: mainSupportMetrics,
Before: setGlobalsFromContext,
Flags: append(globalFlags, metricsFlags...),
CustomHelpTemplate: `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}} TARGET [METRIC-TYPE]
METRIC-TYPE:
valid values are
api-version v2 ['cluster', 'node', 'bucket', 'resource']. defaults to 'cluster' if not specified.
api-version v3 ["api", "system", "debug", "cluster", "ilm", "audit", "logger", "replication", "notification", "scanner"]. defaults to all if not specified.
FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}
EXAMPLES (v3):
1. API metrics
{{.Prompt}} {{.HelpName}} play api --api-version v3
2. API metrics for the bucket 'mybucket'
{{.Prompt}} {{.HelpName}} play api --bucket mybucket --api-version v3
3. System metrics
{{.Prompt}} {{.HelpName}} play system --api-version v3
4. Debug metrics
{{.Prompt}} {{.HelpName}} play debug --api-version v3
5. Cluster metrics
{{.Prompt}} {{.HelpName}} play cluster --api-version v3
6. ILM metrics
{{.Prompt}} {{.HelpName}} play ilm --api-version v3
7. Audit metrics
{{.Prompt}} {{.HelpName}} play audit --api-version v3
8. Logger metrics
{{.Prompt}} {{.HelpName}} play logger --api-version v3
9. Replication metrics
{{.Prompt}} {{.HelpName}} play replication --api-version v3
10. Replication metrics for the bucket 'mybucket'
{{.Prompt}} {{.HelpName}} play replication --bucket mybucket --api-version v3
11. Notification metrics
{{.Prompt}} {{.HelpName}} play notification --api-version v3
12. Scanner metrics
{{.Prompt}} {{.HelpName}} play scanner --api-version v3
EXAMPLES (v2):
1. Metrics reported cluster wide.
{{.Prompt}} {{.HelpName}} play
2. Metrics reported at node level.
{{.Prompt}} {{.HelpName}} play node
3. Metrics reported at bucket level.
{{.Prompt}} {{.HelpName}} play bucket
4. Resource metrics.
{{.Prompt}} {{.HelpName}} play resource
`,
}
const metricsEndPointRoot = "/minio/v2/metrics/"
type prometheusMetricsReq struct {
aliasURL string
token string
subsystem string
}
// checkSupportMetricsSyntax - validate arguments passed by a user
func checkSupportMetricsSyntax(ctx *cli.Context) {
if len(ctx.Args()) == 0 || len(ctx.Args()) > 2 {
showCommandHelpAndExit(ctx, 1) // last argument is exit code
}
}
func fetchMetrics(metricsURL string, token string) (*http.Response, error) {
req, e := http.NewRequest(http.MethodGet, metricsURL, nil)
if e != nil {
return nil, e
}
if token != "" {
req.Header.Add("Authorization", "Bearer "+token)
}
client := httpClient(60 * time.Second)
return client.Do(req)
}
func validateV2Args(ctx *cli.Context, subsys string) {
for _, flag := range metricsV3Flags {
flagName := flag.GetName()
if ctx.IsSet(flagName) {
fatalIf(errInvalidArgument().Trace(), "Flag `"+flagName+"` is not supported with v2 metrics")
}
}
if !metricsV2SubSystems.Contains(subsys) {
fatalIf(errInvalidArgument().Trace(),
"invalid metric type `"+subsys+"`. valid values are `"+
strings.Join(metricsV2SubSystems.ToSlice(), ", ")+"`")
}
}
func printPrometheusMetricsV2(ctx *cli.Context, req prometheusMetricsReq) error {
subsys := req.subsystem
if subsys == "" {
subsys = "cluster"
}
validateV2Args(ctx, subsys)
resp, e := fetchMetrics(req.aliasURL+metricsEndPointRoot+subsys, req.token)
if e != nil {
return e
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
printMsg(prometheusMetricsReader{Reader: resp.Body})
return nil
}
return errors.New(resp.Status)
}
// JSON returns jsonified message
func (pm prometheusMetricsReader) JSON() string {
results, e := madmin.ParsePrometheusResults(pm.Reader)
fatalIf(probe.NewError(e), "Unable to parse Prometheus metrics.")
jsonMessageBytes, e := json.MarshalIndent(results, "", " ")
fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
return string(jsonMessageBytes)
}
// String - returns the string representation of the prometheus metrics
func (pm prometheusMetricsReader) String() string {
_, e := io.Copy(os.Stdout, pm.Reader)
fatalIf(probe.NewError(e), "Unable to read Prometheus metrics.")
return ""
}
// prometheusMetricsReader mirrors the MetricFamily proto message.
type prometheusMetricsReader struct {
Reader io.Reader
}
func mainSupportMetrics(ctx *cli.Context) error {
checkSupportMetricsSyntax(ctx)
// Get the alias parameter from cli
args := ctx.Args()
alias := cleanAlias(args.Get(0))
if !isValidAlias(alias) {
fatalIf(errInvalidAlias(alias), "Invalid alias.")
}
hostConfig := mustGetHostConfig(alias)
if hostConfig == nil {
fatalIf(errInvalidAliasedURL(alias), "No such alias `"+alias+"` found.")
return nil
}
token, e := getPrometheusToken(hostConfig)
if e != nil {
return e
}
metricsSubSystem := args.Get(1)
apiVer := ctx.String("api-version")
metricsReq := prometheusMetricsReq{
aliasURL: hostConfig.URL,
token: token,
subsystem: metricsSubSystem,
}
switch apiVer {
case "v2":
err := printPrometheusMetricsV2(ctx, metricsReq)
fatalIf(probe.NewError(err), "Unable to list prometheus metrics with api-version v2.")
case "v3":
err := printPrometheusMetricsV3(ctx, metricsReq)
fatalIf(probe.NewError(err), "Unable to list prometheus metrics with api-version v3.")
default:
fatalIf(errInvalidArgument().Trace(), "Invalid api version `"+apiVer+"`")
}
return nil
}