Skip to content

Commit faebde8

Browse files
authored
Merge pull request #18 from raintank/metricpoint-etc
import changes from latest metrictank
2 parents 3ef30a7 + 3660fad commit faebde8

25 files changed

+1694
-362
lines changed

archive.go

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package schema
2+
3+
import (
4+
"errors"
5+
"strconv"
6+
)
7+
8+
//go:generate stringer -type=Method -linecomment
9+
10+
// Archive represents a metric archive
11+
// the zero value represents a raw metric
12+
// any non-zero value represents a certain
13+
// aggregation method (lower 8 bits) and
14+
// aggregation span (higher 8 bits)
15+
type Archive uint16
16+
17+
// important: caller must make sure to call IsSpanValid first
18+
func NewArchive(method Method, span uint32) Archive {
19+
code := uint16(spanHumanToCode[span])
20+
return Archive(uint16(method) | code<<8)
21+
}
22+
23+
// String returns the traditional key suffix like sum_600 etc
24+
// (invalid to call this for raw archives)
25+
func (a Archive) String() string {
26+
return a.Method().String() + "_" + strconv.FormatInt(int64(a.Span()), 10)
27+
}
28+
29+
func (a Archive) Method() Method {
30+
return Method(a & 0x0F)
31+
}
32+
33+
func (a Archive) Span() uint32 {
34+
return spanCodeToHuman[uint8(a>>8)]
35+
}
36+
37+
func IsSpanValid(span uint32) bool {
38+
_, ok := spanHumanToCode[span]
39+
return ok
40+
}
41+
42+
type Method uint8
43+
44+
const (
45+
Avg Method = iota + 1 // avg
46+
Sum // sum
47+
Lst // lst
48+
Max // max
49+
Min // min
50+
Cnt // cnt
51+
)
52+
53+
func MethodFromString(input string) (Method, error) {
54+
switch input {
55+
case "avg":
56+
return Avg, nil
57+
case "sum":
58+
return Sum, nil
59+
case "lst":
60+
return Lst, nil
61+
case "max":
62+
return Max, nil
63+
case "min":
64+
return Min, nil
65+
case "cnt":
66+
return Cnt, nil
67+
}
68+
return 0, errors.New("no such method")
69+
}
70+
71+
// maps human friendly span numbers (in seconds) to optimized code form
72+
var spanHumanToCode map[uint32]uint8
73+
74+
// maps span codes to human friendly span numbers in seconds
75+
var spanCodeToHuman map[uint8]uint32
76+
77+
func init() {
78+
// all the aggregation spans we support, their index position in this slice is their code
79+
spans := []uint32{2, 5, 10, 15, 30, 60, 90, 120, 150, 300, 600, 900, 1200, 1800, 45 * 60, 3600, 3600 + 30*60, 2 * 3600, 3 * 3600, 4 * 3600, 5 * 3600, 6 * 3600, 8 * 3600, 12 * 3600, 24 * 3600}
80+
81+
spanHumanToCode = make(map[uint32]uint8)
82+
spanCodeToHuman = make(map[uint8]uint32)
83+
84+
for i, human := range spans {
85+
code := uint8(i)
86+
spanHumanToCode[human] = code
87+
spanCodeToHuman[code] = human
88+
}
89+
90+
}

archive_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package schema
2+
3+
import "testing"
4+
5+
func TestArchive(t *testing.T) {
6+
type c struct {
7+
method Method
8+
span uint32
9+
expArchive Archive
10+
expStr string
11+
}
12+
cases := []c{
13+
{Avg, 15, 0x301, "avg_15"},
14+
{Cnt, 120, 0x706, "cnt_120"},
15+
{Cnt, 3600, 0xF06, "cnt_3600"},
16+
{Lst, 7200, 0x1103, "lst_7200"},
17+
{Min, 6 * 3600, 0x1505, "min_21600"},
18+
{Cnt, 2, 0x6, "cnt_2"},
19+
{Avg, 5, 0x101, "avg_5"},
20+
{Cnt, 3600 + 30*60, 0x1006, "cnt_5400"},
21+
}
22+
for i, cas := range cases {
23+
arch := NewArchive(cas.method, cas.span)
24+
if arch != cas.expArchive {
25+
t.Fatalf("case %d: expected archive %d, got %d", i, cas.expArchive, arch)
26+
}
27+
str := arch.String()
28+
if str != cas.expStr {
29+
t.Fatalf("case %d: expected string %q, got %q", i, cas.expStr, str)
30+
}
31+
method := arch.Method()
32+
if method != cas.method {
33+
t.Fatalf("case %d: expected method %v, got %v", i, cas.method, method)
34+
}
35+
span := arch.Span()
36+
if span != cas.span {
37+
t.Fatalf("case %d: expected span %v, got %v", i, cas.span, span)
38+
}
39+
}
40+
}

key.go

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package schema
2+
3+
import (
4+
"encoding/hex"
5+
"errors"
6+
"fmt"
7+
"strconv"
8+
"strings"
9+
)
10+
11+
//go:generate msgp
12+
//msgp:ignore AMKey
13+
// don't ignore Key, MKey because it's used for MetricDefinition
14+
15+
var ErrStringTooShort = errors.New("string to short")
16+
var ErrInvalidFormat = errors.New("invalid format")
17+
18+
// Key identifies a metric
19+
type Key [16]byte
20+
21+
// MKey uniquely identifies a metric in a multi-tenant context
22+
type MKey struct {
23+
Key Key
24+
Org uint32
25+
}
26+
27+
// KeyFromString parses a string id to an MKey
28+
// string id must be of form orgid.<hexadecimal 128bit hash>
29+
func MKeyFromString(s string) (MKey, error) {
30+
l := len(s)
31+
32+
// shortest an orgid can be is single digit
33+
if l < 34 {
34+
return MKey{}, ErrStringTooShort
35+
}
36+
37+
hashStr := s[l-32:]
38+
orgStr := s[0 : l-33]
39+
40+
hash, err := hex.DecodeString(hashStr)
41+
if err != nil {
42+
return MKey{}, err
43+
}
44+
45+
org, err := strconv.ParseUint(orgStr, 10, 32)
46+
if err != nil {
47+
return MKey{}, err
48+
}
49+
50+
k := MKey{
51+
Org: uint32(org),
52+
}
53+
54+
copy(k.Key[:], hash)
55+
return k, nil
56+
}
57+
58+
func (m MKey) String() string {
59+
return fmt.Sprintf("%d.%x", m.Org, m.Key)
60+
61+
}
62+
63+
// AMKey is a multi-tenant key with archive extension
64+
// so you can refer to rollup archives
65+
type AMKey struct {
66+
MKey MKey
67+
Archive Archive
68+
}
69+
70+
func (a AMKey) String() string {
71+
if a.Archive == 0 {
72+
return a.MKey.String()
73+
}
74+
return a.MKey.String() + "_" + a.Archive.String()
75+
}
76+
77+
// GetAMKey helps to easily get an AMKey from a given MKey
78+
func GetAMKey(m MKey, method Method, span uint32) AMKey {
79+
return AMKey{
80+
MKey: m,
81+
Archive: NewArchive(method, span),
82+
}
83+
}
84+
85+
func AMKeyFromString(s string) (AMKey, error) {
86+
underscores := strings.Count(s, "_")
87+
amk := AMKey{}
88+
switch underscores {
89+
case 0:
90+
mk, err := MKeyFromString(s)
91+
amk.MKey = mk
92+
return amk, err
93+
case 2:
94+
splits := strings.Split(s, "_")
95+
mk, err := MKeyFromString(splits[0])
96+
if err != nil {
97+
return amk, err
98+
}
99+
amk.MKey = mk
100+
method, err := MethodFromString(splits[1])
101+
if err != nil {
102+
return amk, err
103+
}
104+
span, err := strconv.ParseInt(splits[2], 10, 32)
105+
if err == nil {
106+
return amk, err
107+
}
108+
if !IsSpanValid(uint32(span)) {
109+
return amk, fmt.Errorf("invalid span %d", span)
110+
}
111+
amk.Archive = NewArchive(method, uint32(span))
112+
return amk, nil
113+
}
114+
return amk, ErrInvalidFormat
115+
}

0 commit comments

Comments
 (0)