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 }