1// Copyright 2019 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Package vault provides helper functions to improve the go-metrics to stackdriver metric
16// conversions specific to HashiCorp Vault.
17package vault
18
19import "github.com/armon/go-metrics"
20
21// Extractor extracts known patterns from the key into metrics.Label for better metric grouping
22// and to help avoid the limit of 500 custom metric descriptors per project
23// (https://cloud.google.com/monitoring/quotas).
24func Extractor(key []string, kind string) ([]string, []metrics.Label, error) {
25	// Metrics documented at https://www.vaultproject.io/docs/internals/telemetry.html should be
26	// extracted here into a base metric name with appropriate labels extracted from the 'key'.
27	switch len(key) {
28	case 3: // metrics of format: *.*.*
29		// vault.database.<method>
30		if key[0] == "vault" && key[1] == "database" {
31			return typeWrap(key[:2], kind), []metrics.Label{
32				{
33					Name:  "method",
34					Value: key[2],
35				},
36			}, nil
37		}
38		// vault.token.create_root
39		if key[0] == "vault" && key[1] == "token" && key[2] == "create_root" {
40			return typeWrap(key, kind), nil, nil
41		}
42
43		// vault.barrier.<method>
44		// vault.token.<method>
45		// vault.policy.<method>
46		if key[0] == "vault" && (key[1] == "barrier" || key[1] == "token" || key[1] == "policy") {
47			return typeWrap(key[:2], kind), []metrics.Label{
48				{
49					Name:  "method",
50					Value: key[2],
51				},
52			}, nil
53		}
54		// vault.<backend>.<method>
55		if key[0] == "vault" && (key[2] == "put" || key[2] == "get" || key[2] == "delete" || key[2] == "list") {
56			return typeWrap(key[:2], kind), []metrics.Label{
57				{
58					Name:  "method",
59					Value: key[2],
60				},
61			}, nil
62		}
63	case 4: // metrics of format: *.*.*.*
64		// vault.route.<method>.<mount>
65		if key[0] == "vault" && key[1] == "route" {
66			return typeWrap(key[:2], kind), []metrics.Label{
67				{
68					Name:  "method",
69					Value: key[2],
70				},
71				{
72					Name:  "mount",
73					Value: key[3],
74				},
75			}, nil
76		}
77		// vault.audit.<type>.*
78		if key[0] == "vault" && key[1] == "audit" {
79			return typeWrap([]string{"vault", "audit", key[3]}, kind), []metrics.Label{
80				{
81					Name:  "type",
82					Value: key[2],
83				},
84			}, nil
85		}
86		// vault.rollback.attempt.<mount>
87		if key[0] == "vault" && key[1] == "rollback" && key[2] == "attempt" {
88			return typeWrap(key[:3], kind), []metrics.Label{
89				{
90					Name:  "mount",
91					Value: key[3],
92				},
93			}, nil
94		}
95		// vault.<backend>.lock.<method>
96		if key[0] == "vault" && key[2] == "lock" {
97			return typeWrap(key[:3], kind), []metrics.Label{
98				{
99					Name:  "method",
100					Value: key[3],
101				},
102			}, nil
103		}
104		// vault.database.<name>.<method>
105		// note: there are vault.database.<method>.error counters. Those are handled separately.
106		if key[0] == "vault" && key[1] == "database" && key[3] != "error" {
107			return typeWrap(key[:2], kind), []metrics.Label{
108				{
109					Name:  "name",
110					Value: key[2],
111				},
112				{
113					Name:  "method",
114					Value: key[3],
115				},
116			}, nil
117		}
118		//ivault.database.<method>.error
119		if key[0] == "vault" && key[1] == "database" && key[3] == "error" {
120			return typeWrap([]string{"vault", "database", "error"}, kind), []metrics.Label{
121				{
122					Name:  "method",
123					Value: key[2],
124				},
125			}, nil
126		}
127	case 5:
128		// vault.database.<name>.<method>.error
129		if key[0] == "vault" && key[1] == "database" && key[4] == "error" {
130			return typeWrap([]string{key[0], key[1], key[4]}, kind), []metrics.Label{
131				{
132					Name:  "name",
133					Value: key[2],
134				},
135				{
136					Name:  "method",
137					Value: key[3],
138				},
139			}, nil
140		}
141	default:
142		// unknown key pattern, keep it as-is.
143	}
144	return typeWrap(key, kind), nil, nil
145}
146
147func typeWrap(key []string, kind string) []string {
148	out := []string{}
149	for _, a := range key {
150		out = append(out, a)
151	}
152	switch kind {
153	case "counter":
154		return append(out, kind)
155	case "gauge":
156		return append(out, kind)
157	default:
158		return out
159	}
160}
161
162// Bucketer specifies the bucket boundaries that should be used for the given metric key.
163func Bucketer(key []string) []float64 {
164	// These were chosen to give some reasonable boundaires for RPC times in the 10-100ms range and
165	// then rough values for 1-5 seconds.
166	// TODO: investigate better boundaires for different metrics.
167	return []float64{10.0, 25.0, 50.0, 100.0, 150.0, 200.0, 250.0, 300.0, 500.0, 1000.0, 1500.0, 2000.0, 3000.0, 4000.0, 5000.0}
168}
169