aboutsummaryrefslogtreecommitdiffstats
path: root/library/go/yandex/unistat/unistat.go
blob: e3664dac38a3e4f061ae22270cd4d7f36d028daf (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package unistat

import (
	"encoding/json"
	"errors"
	"fmt"
	"time"
)

// StructuredAggregation provides type safe API to create an Aggregation. For more
// information see: https://wiki.yandex-team.ru/golovan/aggregation-types/
type StructuredAggregation struct {
	AggregationType AggregationType
	Group           AggregationRule
	MetaGroup       AggregationRule
	Rollup          AggregationRule
}

// Aggregation defines rules how to aggregate signal on each level. For more
// information see: https://wiki.yandex-team.ru/golovan/aggregation-types/
type Aggregation interface {
	Suffix() string
}

const (
	AggregationUnknown = "<unknown>"
)

// Suffix defines signal aggregation on each level:
// 1 - Signal type: absolute (A) or delta (D).
// 2 - Group aggregation.
// 3 - Meta-group aggregation type.
// 4 - Time aggregation for roll-up.
//
// Doc: https://doc.yandex-team.ru/Search/golovan-quickstart/concepts/signal-aggregation.html#agrr-levels
func (a StructuredAggregation) Suffix() string {
	return fmt.Sprintf("%s%s%s%s", a.AggregationType, a.Group, a.MetaGroup, a.Rollup)
}

// Priority is used to order signals in unistat report.
// https://wiki.yandex-team.ru/golovan/stat-handle/#protokol
type Priority int

// AggregationType is Absolute or Delta.
type AggregationType int

// Value types
const (
	Absolute AggregationType = iota // Absolute value. Use for gauges.
	Delta                           // Delta value. Use for increasing counters.
)

func (v AggregationType) String() string {
	switch v {
	case Absolute:
		return "a"
	case Delta:
		return "d"
	default:
		return AggregationUnknown
	}
}

// AggregationRule defines aggregation rules:
//
//	https://wiki.yandex-team.ru/golovan/aggregation-types/#algoritmyagregacii
type AggregationRule int

// Aggregation rules
const (
	Hgram   AggregationRule = iota // Hgram is histogram aggregation.
	Max                            // Max value.
	Min                            // Min value.
	Sum                            // Sum with default 0.
	SumNone                        // SumNone is sum with default None.
	Last                           // Last value.
	Average                        // Average value.
)

func (r AggregationRule) String() string {
	switch r {
	case Hgram:
		return "h"
	case Max:
		return "x"
	case Min:
		return "n"
	case Sum:
		return "m"
	case SumNone:
		return "e"
	case Last:
		return "t"
	case Average:
		return "v"
	default:
		return AggregationUnknown
	}
}

func (r *AggregationRule) UnmarshalText(source []byte) error {
	text := string(source)
	switch text {
	case "h":
		*r = Hgram
	case "x":
		*r = Max
	case "n":
		*r = Min
	case "m":
		*r = Sum
	case "e":
		*r = SumNone
	case "t":
		*r = Last
	case "v":
		*r = Average
	default:
		return fmt.Errorf("unknown aggregation rule '%s'", text)
	}
	return nil
}

// ErrDuplicate is raised on duplicate metric name registration.
var ErrDuplicate = errors.New("unistat: duplicate metric")

// Metric is interface that accepted by Registry.
type Metric interface {
	Name() string
	Priority() Priority
	Aggregation() Aggregation
	MarshalJSON() ([]byte, error)
}

// Updater is interface that wraps basic Update() method.
type Updater interface {
	Update(value float64)
}

// Registry is interface for container that generates stat report
type Registry interface {
	Register(metric Metric)
	MarshalJSON() ([]byte, error)
}

var defaultRegistry = NewRegistry()

// Register metric in default registry.
func Register(metric Metric) {
	defaultRegistry.Register(metric)
}

// MarshalJSON marshals default registry to JSON.
func MarshalJSON() ([]byte, error) {
	return json.Marshal(defaultRegistry)
}

// MeasureMicrosecondsSince updates metric with duration that started
// at ts and ends now.
func MeasureMicrosecondsSince(m Updater, ts time.Time) {
	measureMicrosecondsSince(time.Since, m, ts)
}

// For unittest
type timeSinceFunc func(t time.Time) time.Duration

func measureMicrosecondsSince(sinceFunc timeSinceFunc, m Updater, ts time.Time) {
	dur := sinceFunc(ts)
	m.Update(float64(dur / time.Microsecond)) // to microseconds
}