github.com/grafana/pyroscope@v1.18.0/pkg/frontend/vcs/client/metrics.go (about)

     1  package client
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"regexp"
     7  	"time"
     8  
     9  	"github.com/go-kit/log"
    10  	"github.com/go-kit/log/level"
    11  	"github.com/prometheus/client_golang/prometheus"
    12  
    13  	"github.com/grafana/pyroscope/pkg/util"
    14  )
    15  
    16  var (
    17  	githubRouteMatchers = map[string]*regexp.Regexp{
    18  		// Get repository contents.
    19  		// https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#get-repository-content
    20  		"/repos/{owner}/{repo}/contents/{path}": regexp.MustCompile(`^\/repos\/\S+\/\S+\/contents\/\S+$`),
    21  
    22  		// Get a commit.
    23  		// https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#get-a-commit
    24  		"/repos/{owner}/{repo}/commits/{ref}": regexp.MustCompile(`^\/repos\/\S+\/\S+\/commits\/\S+$`),
    25  
    26  		// Refresh auth token.
    27  		// https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/refreshing-user-access-tokens#refreshing-a-user-access-token-with-a-refresh-token
    28  		"/login/oauth/access_token": regexp.MustCompile(`^\/login\/oauth\/access_token$`),
    29  	}
    30  )
    31  
    32  func InstrumentedHTTPClient(logger log.Logger, reg prometheus.Registerer) *http.Client {
    33  	apiDuration := util.RegisterOrGet(reg, prometheus.NewHistogramVec(
    34  		prometheus.HistogramOpts{
    35  			Namespace: "pyroscope",
    36  			Name:      "vcs_github_request_duration",
    37  			Help:      "Duration of GitHub API requests in seconds",
    38  			Buckets:   prometheus.ExponentialBucketsRange(0.1, 10, 8),
    39  		},
    40  		[]string{"method", "route", "status_code"},
    41  	))
    42  
    43  	defaultClient := &http.Client{
    44  		Timeout:   10 * time.Second,
    45  		Transport: http.DefaultTransport,
    46  	}
    47  	client := util.InstrumentedHTTPClient(defaultClient, withGitHubMetricsTransport(logger, apiDuration))
    48  	return client
    49  }
    50  
    51  // withGitHubMetricsTransport wraps a transport with a client to track GitHub
    52  // API usage.
    53  func withGitHubMetricsTransport(logger log.Logger, hv *prometheus.HistogramVec) util.RoundTripperInstrumentFunc {
    54  	return func(next http.RoundTripper) http.RoundTripper {
    55  		return util.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
    56  			route := matchGitHubAPIRoute(req.URL.Path)
    57  			statusCode := ""
    58  			start := time.Now()
    59  
    60  			res, err := next.RoundTrip(req)
    61  			if err == nil {
    62  				statusCode = fmt.Sprintf("%d", res.StatusCode)
    63  			}
    64  
    65  			if route == "unknown_route" {
    66  				level.Warn(logger).Log("path", req.URL.Path, "msg", "unknown GitHub API route")
    67  			}
    68  			hv.WithLabelValues(req.Method, route, statusCode).Observe(time.Since(start).Seconds())
    69  
    70  			return res, err
    71  		})
    72  	}
    73  }
    74  
    75  func matchGitHubAPIRoute(path string) string {
    76  	for route, regex := range githubRouteMatchers {
    77  		if regex.MatchString(path) {
    78  			return route
    79  		}
    80  	}
    81  
    82  	return "unknown_route"
    83  }