Skip to content

Commit

Permalink
[release-0.3] feat(metrics): support HTTP metrics for RPC style descr…
Browse files Browse the repository at this point in the history
…iptors (#397)

* feat(metrics): support HTTP metrics for RPC style descriptors

* vendor

* fix lint

Co-authored-by: Chuan Li <[email protected]>
  • Loading branch information
caicloud-bot and lichuan0620 authored Nov 24, 2020
1 parent 2bee466 commit 56c677b
Show file tree
Hide file tree
Showing 201 changed files with 11,804 additions and 12,363 deletions.
11 changes: 4 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,19 @@ module github.com/caicloud/nirvana
go 1.13

require (
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-openapi/jsonreference v0.19.3 // indirect
github.com/go-openapi/spec v0.19.7
github.com/go-openapi/swag v0.19.9 // indirect
github.com/go-playground/locales v0.12.1 // indirect
github.com/go-playground/universal-translator v0.16.0 // indirect
github.com/golang/protobuf v1.4.2 // indirect
github.com/google/go-cmp v0.5.2 // indirect
github.com/kr/pretty v0.2.0 // indirect
github.com/mailru/easyjson v0.7.0 // indirect
github.com/opentracing/opentracing-go v1.1.0
github.com/pelletier/go-toml v1.7.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.5.1
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.8.0
github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/cast v1.3.1
github.com/spf13/cobra v0.0.5
Expand All @@ -28,11 +26,10 @@ require (
github.com/uber/jaeger-lib v2.2.0+incompatible // indirect
go.uber.org/atomic v1.6.0 // indirect
golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 // indirect
golang.org/x/text v0.3.3
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114
google.golang.org/protobuf v1.24.0 // indirect
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/go-playground/validator.v9 v9.17.1
gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v2 v2.3.0
)
265 changes: 256 additions & 9 deletions go.sum

Large diffs are not rendered by default.

136 changes: 103 additions & 33 deletions metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,49 +17,70 @@ limitations under the License.
package metrics

import (
"regexp"
"strconv"
"strings"
"sync"
"time"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"

"github.com/caicloud/nirvana/service"
)

var (
once sync.Once
requestCounter *prometheus.CounterVec
requestCount *prometheus.CounterVec
requestDuration *prometheus.HistogramVec
responseSize *prometheus.HistogramVec
startTime prometheus.Gauge
)

// Install registers the metrics under the given namespace. This take effect only once; subsequent calls
// has no effect.
func Install(namespace string) {
// Options provide a way to configure the name of the metrics (by setting Namespace and Subsystem) and
// control if and how these value should be used as label values.
// If you are unsure about these options, just use nil or empty Options.
type Options struct {
NamespaceLabel string `desc:"label name for the metrics namespace"`
NamespaceValue string `desc:"metrics namespace; also used as the value of the namespace label (if provided)"`
SubsystemLabel string `desc:"label name for the metrics subsystem"`
SubsystemValue string `desc:"metrics subsystem; also used as the value of the subsystem value (if provided)"`
}

const defaultNamespace = "nirvana"

// DefaultOptions builds an Options object with default values; it is used automatically when a nil
// Options is provided.
func DefaultOptions() *Options {
return &Options{
NamespaceValue: defaultNamespace,
}
}

// Install registers the metrics under the given namespace and subsystem. This take effect only
// once; subsequent calls has no effect.
func Install(options *Options) {
once.Do(func() {
if namespace == "" {
namespace = "nirvana"
if options == nil {
options = DefaultOptions()
}
constLabel := prometheus.Labels{"component": namespace}
httpLabels := []string{"verb", "path"}

startTime = promauto.NewGauge(
prometheus.GaugeOpts{
Namespace: namespace,
Name: "start_time_seconds",
Help: "Start time of the service in unix timestamp",
ConstLabels: constLabel,
},
)
startTime.Set(float64(time.Now().Unix()))
if len(options.NamespaceValue) == 0 {
options.NamespaceValue = defaultNamespace
}
namespace := normalizeLabelName(options.NamespaceValue)
subsystem := normalizeLabelName(options.SubsystemValue)
constLabel := prometheus.Labels{}
if labelKey := normalizeLabelName(options.NamespaceLabel); len(labelKey) > 0 {
constLabel[labelKey] = namespace
}
if labelKey := normalizeLabelName(options.SubsystemLabel); len(labelKey) > 0 && len(subsystem) > 0 {
constLabel[labelKey] = subsystem
}
httpLabels := []string{"verb", "path", "action", "version"}

requestCounter = promauto.NewCounterVec(
requestCount = promauto.NewCounterVec(
prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "request_total",
Help: "Counter of server requests for each verb, API resource, and HTTP response code.",
Help: "Counter of server requests.",
ConstLabels: constLabel,
},
append(httpLabels, "code"),
Expand All @@ -68,8 +89,9 @@ func Install(namespace string) {
requestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "request_duration_seconds",
Help: "Request duration distribution in seconds for each verb and path.",
Help: "Request duration distribution in seconds.",
ConstLabels: constLabel,
Buckets: prometheus.DefBuckets,
},
Expand All @@ -79,8 +101,9 @@ func Install(namespace string) {
responseSize = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "response_sizes",
Help: "Response content length distribution in bytes for each verb and path.",
Help: "Response content length distribution in bytes.",
ConstLabels: constLabel,
Buckets: []float64{1e04, 1e05, 1e06, 1e07, 1e08, 1e09},
},
Expand All @@ -89,12 +112,59 @@ func Install(namespace string) {
})
}

// RecordRequest can be used at the end of each request to record its metric values.
func RecordRequest(start time.Time, ctx service.HTTPContext) {
req := ctx.Request()
resp := ctx.ResponseWriter()
path := ctx.RoutePath()
requestCounter.WithLabelValues(req.Method, path, strconv.Itoa(resp.StatusCode())).Inc()
responseSize.WithLabelValues(req.Method, path).Observe(float64(resp.ContentLength()))
requestDuration.WithLabelValues(req.Method, path).Observe(time.Since(start).Seconds())
// RecordRestfulRequest gathers the metric values of a Restful HTTP request. It should be called at
// the end of a request session.
func RecordRestfulRequest(path, verb string, code int, contentLength int, duration time.Duration) {
verb = strings.ToUpper(verb)
labels := prometheus.Labels{
"verb": verb,
"path": path,
"action": "",
"version": "",
}
labelsWithCode := prometheus.Labels{
"verb": verb,
"path": path,
"action": "",
"version": "",
"code": strconv.Itoa(code),
}
requestCount.With(labelsWithCode).Inc()
responseSize.With(labels).Observe(float64(contentLength))
requestDuration.With(labels).Observe(duration.Seconds())
}

// RecordRPCRequest gathers the metric values of a RPC HTTP request. It should be called at
// the end of a request session.
func RecordRPCRequest(action, version string, code int, contentLength int, duration time.Duration) {
labels := prometheus.Labels{
"verb": "",
"path": "",
"action": action,
"version": version,
}
labelsWithCode := prometheus.Labels{
"verb": "",
"path": "",
"action": action,
"version": version,
"code": strconv.Itoa(code),
}
requestCount.With(labelsWithCode).Inc()
responseSize.With(labels).Observe(float64(contentLength))
requestDuration.With(labels).Observe(duration.Seconds())
}

var labelRegex = regexp.MustCompile("[^a-z0-9_]+")

// normalizeLabelName convert the given string into a valid label name (or any part of one)
func normalizeLabelName(label string) string {
if len(label) == 0 {
return ""
}
lower := strings.ToLower(label)
noSpace := strings.ReplaceAll(lower, " ", "_")
alphaNumeric := labelRegex.ReplaceAllString(noSpace, "")
trimmed := strings.Trim(alphaNumeric, "_")
return trimmed
}
Loading

0 comments on commit 56c677b

Please sign in to comment.