diff --git a/prow/jira/BUILD.bazel b/prow/jira/BUILD.bazel index b2ae6fa35c20..a18173fe9cab 100644 --- a/prow/jira/BUILD.bazel +++ b/prow/jira/BUILD.bazel @@ -2,13 +2,18 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", - srcs = ["jira.go"], + srcs = [ + "jira.go", + "metrics.go", + ], importpath = "k8s.io/test-infra/prow/jira", visibility = ["//visibility:public"], deps = [ + "//prow/simplifypath:go_default_library", "//prow/version:go_default_library", "@com_github_andygrunwald_go_jira//:go_default_library", "@com_github_hashicorp_go_retryablehttp//:go_default_library", + "@com_github_prometheus_client_golang//prometheus:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", "@io_k8s_apimachinery//pkg/util/sets:go_default_library", ], diff --git a/prow/jira/jira.go b/prow/jira/jira.go index 2e741b3ce772..caf2f2d96a15 100644 --- a/prow/jira/jira.go +++ b/prow/jira/jira.go @@ -74,6 +74,11 @@ func NewClient(endpoint string, opts ...Option) (Client, error) { retryingClient := retryablehttp.NewClient() retryingClient.Logger = &retryableHTTPLogrusWrapper{log: log} + retryingClient.HTTPClient.Transport = &metricsTransport{ + upstream: retryingClient.HTTPClient.Transport, + pathSimplifier: pathSimplifier().Simplify, + recorder: requestResults, + } retryingClient.HTTPClient.Transport = userAgentSettingTransport{ userAgent: version.UserAgent(), upstream: retryingClient.HTTPClient.Transport, diff --git a/prow/jira/metrics.go b/prow/jira/metrics.go new file mode 100644 index 000000000000..9bdcd5caaee1 --- /dev/null +++ b/prow/jira/metrics.go @@ -0,0 +1,80 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed 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 jira + +import ( + "net/http" + "strconv" + "time" + + "github.com/prometheus/client_golang/prometheus" + + "k8s.io/test-infra/prow/simplifypath" +) + +func init() { + prometheus.MustRegister(requestResults) +} + +var requestResults = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: "jira_request_duration_seconds", + Buckets: []float64{0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 20}, +}, []string{methodField, pathField, statusField}) + +func pathSimplifier() simplifypath.Simplifier { + return simplifypath.NewSimplifier(simplifypath.L("", // shadow element mimicing the root + simplifypath.L("rest", + simplifypath.L("api", + simplifypath.L("2", + simplifypath.L("project"), + simplifypath.L("issue", + simplifypath.V("issueID", + simplifypath.L("remotelink"), + ), + ), + ), + ), + ), + )) +} + +const ( + methodField = "method" + pathField = "path" + statusField = "status" +) + +type metricsTransport struct { + upstream http.RoundTripper + pathSimplifier func(string) string + recorder *prometheus.HistogramVec +} + +func (m *metricsTransport) RoundTrip(r *http.Request) (*http.Response, error) { + start := time.Now() + result, err := m.upstream.RoundTrip(r) + status := "error" + if err == nil && result != nil { + status = strconv.Itoa(result.StatusCode) + } + var path string + if r.URL != nil { + path = r.URL.Path + } + m.recorder.WithLabelValues(r.Method, m.pathSimplifier(path), status).Observe(float64(time.Since(start).Milliseconds()) / 1000) + return result, err +}