github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/admin-prometheus-metrics.go (about)

     1  // Copyright (c) 2015-2022 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"errors"
    22  	"io"
    23  	"net/http"
    24  	"os"
    25  	"time"
    26  
    27  	"github.com/minio/cli"
    28  	json "github.com/minio/colorjson"
    29  	"github.com/minio/madmin-go/v3"
    30  	"github.com/minio/mc/pkg/probe"
    31  )
    32  
    33  var adminPrometheusMetricsCmd = cli.Command{
    34  	Name:         "metrics",
    35  	Usage:        "print cluster wide prometheus metrics",
    36  	OnUsageError: onUsageError,
    37  	Action:       mainSupportMetrics,
    38  	Before:       setGlobalsFromContext,
    39  	Flags:        globalFlags,
    40  	CustomHelpTemplate: `NAME:
    41    {{.HelpName}} - {{.Usage}}
    42  USAGE:
    43    {{.HelpName}} TARGET [METRIC-TYPE]
    44  
    45  METRIC-TYPE:
    46    valid values are ['cluster', 'node', 'bucket', 'resource']. Defaults to 'cluster' if not specified.
    47  
    48  FLAGS:
    49    {{range .VisibleFlags}}{{.}}
    50    {{end}}
    51  EXAMPLES:
    52    1. List of metrics reported cluster wide.
    53       {{.Prompt}} {{.HelpName}} play
    54  
    55    2. List of metrics reported at node level.
    56       {{.Prompt}} {{.HelpName}} play node
    57  
    58    3. List of metrics reported at bucket level.
    59       {{.Prompt}} {{.HelpName}} play bucket
    60  
    61    4. List of resource metrics.
    62       {{.Prompt}} {{.HelpName}} play resource
    63  `,
    64  }
    65  
    66  const metricsEndPointRoot = "/minio/v2/metrics/"
    67  
    68  // checkSupportMetricsSyntax - validate arguments passed by a user
    69  func checkSupportMetricsSyntax(ctx *cli.Context) {
    70  	if len(ctx.Args()) == 0 || len(ctx.Args()) > 2 {
    71  		showCommandHelpAndExit(ctx, 1) // last argument is exit code
    72  	}
    73  }
    74  
    75  func printPrometheusMetrics(ctx *cli.Context) error {
    76  	// Get the alias parameter from cli
    77  	args := ctx.Args()
    78  	alias := cleanAlias(args.Get(0))
    79  
    80  	if !isValidAlias(alias) {
    81  		fatalIf(errInvalidAlias(alias), "Invalid alias.")
    82  	}
    83  	hostConfig := mustGetHostConfig(alias)
    84  	if hostConfig == nil {
    85  		fatalIf(errInvalidAliasedURL(alias), "No such alias `"+alias+"` found.")
    86  		return nil
    87  	}
    88  
    89  	token, e := getPrometheusToken(hostConfig)
    90  	if e != nil {
    91  		return e
    92  	}
    93  	metricsSubSystem := args.Get(1)
    94  	switch metricsSubSystem {
    95  	case "node", "bucket", "cluster", "resource":
    96  	case "":
    97  		metricsSubSystem = "cluster"
    98  	default:
    99  		fatalIf(errInvalidArgument().Trace(), "invalid metric type '%v'", metricsSubSystem)
   100  	}
   101  
   102  	req, e := http.NewRequest(http.MethodGet, hostConfig.URL+metricsEndPointRoot+metricsSubSystem, nil)
   103  	if e != nil {
   104  		return e
   105  	}
   106  
   107  	if token != "" {
   108  		req.Header.Add("Authorization", "Bearer "+token)
   109  	}
   110  
   111  	client := httpClient(60 * time.Second)
   112  	resp, e := client.Do(req)
   113  	if e != nil {
   114  		return e
   115  	}
   116  
   117  	defer resp.Body.Close()
   118  
   119  	if resp.StatusCode == http.StatusOK {
   120  		printMsg(prometheusMetricsReader{Reader: resp.Body})
   121  		return nil
   122  	}
   123  
   124  	return errors.New(resp.Status)
   125  }
   126  
   127  // JSON returns jsonified message
   128  func (pm prometheusMetricsReader) JSON() string {
   129  	results, e := madmin.ParsePrometheusResults(pm.Reader)
   130  	fatalIf(probe.NewError(e), "Unable to parse Prometheus metrics.")
   131  
   132  	jsonMessageBytes, e := json.MarshalIndent(results, "", " ")
   133  	fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
   134  	return string(jsonMessageBytes)
   135  }
   136  
   137  // String - returns the string representation of the prometheus metrics
   138  func (pm prometheusMetricsReader) String() string {
   139  	_, e := io.Copy(os.Stdout, pm.Reader)
   140  
   141  	fatalIf(probe.NewError(e), "Unable to read Prometheus metrics.")
   142  
   143  	return ""
   144  }
   145  
   146  // prometheusMetricsReader mirrors the MetricFamily proto message.
   147  type prometheusMetricsReader struct {
   148  	Reader io.Reader
   149  }
   150  
   151  func mainSupportMetrics(ctx *cli.Context) error {
   152  	checkSupportMetricsSyntax(ctx)
   153  
   154  	fatalIf(probe.NewError(printPrometheusMetrics(ctx)), "Unable to list prometheus metrics.")
   155  
   156  	return nil
   157  }