428 lines
6.7 KiB
Go
428 lines
6.7 KiB
Go
|
package sentry
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"hash/crc32"
|
||
|
"math"
|
||
|
"regexp"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
type (
|
||
|
NumberOrString interface {
|
||
|
int | string
|
||
|
}
|
||
|
|
||
|
void struct{}
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
member void
|
||
|
keyRegex = regexp.MustCompile(`[^a-zA-Z0-9_/.-]+`)
|
||
|
valueRegex = regexp.MustCompile(`[^\w\d\s_:/@\.{}\[\]$-]+`)
|
||
|
unitRegex = regexp.MustCompile(`[^a-z]+`)
|
||
|
)
|
||
|
|
||
|
type MetricUnit struct {
|
||
|
unit string
|
||
|
}
|
||
|
|
||
|
func (m MetricUnit) toString() string {
|
||
|
return m.unit
|
||
|
}
|
||
|
|
||
|
func NanoSecond() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"nanosecond",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func MicroSecond() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"microsecond",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func MilliSecond() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"millisecond",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Second() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"second",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Minute() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"minute",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Hour() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"hour",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Day() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"day",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Week() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"week",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Bit() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"bit",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Byte() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"byte",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func KiloByte() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"kilobyte",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func KibiByte() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"kibibyte",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func MegaByte() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"megabyte",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func MebiByte() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"mebibyte",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func GigaByte() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"gigabyte",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func GibiByte() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"gibibyte",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TeraByte() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"terabyte",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TebiByte() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"tebibyte",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func PetaByte() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"petabyte",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func PebiByte() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"pebibyte",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func ExaByte() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"exabyte",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func ExbiByte() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"exbibyte",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Ratio() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"ratio",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Percent() MetricUnit {
|
||
|
return MetricUnit{
|
||
|
"percent",
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func CustomUnit(unit string) MetricUnit {
|
||
|
return MetricUnit{
|
||
|
unitRegex.ReplaceAllString(unit, ""),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type Metric interface {
|
||
|
GetType() string
|
||
|
GetTags() map[string]string
|
||
|
GetKey() string
|
||
|
GetUnit() string
|
||
|
GetTimestamp() int64
|
||
|
SerializeValue() string
|
||
|
SerializeTags() string
|
||
|
}
|
||
|
|
||
|
type abstractMetric struct {
|
||
|
key string
|
||
|
unit MetricUnit
|
||
|
tags map[string]string
|
||
|
// A unix timestamp (full seconds elapsed since 1970-01-01 00:00 UTC).
|
||
|
timestamp int64
|
||
|
}
|
||
|
|
||
|
func (am abstractMetric) GetTags() map[string]string {
|
||
|
return am.tags
|
||
|
}
|
||
|
|
||
|
func (am abstractMetric) GetKey() string {
|
||
|
return am.key
|
||
|
}
|
||
|
|
||
|
func (am abstractMetric) GetUnit() string {
|
||
|
return am.unit.toString()
|
||
|
}
|
||
|
|
||
|
func (am abstractMetric) GetTimestamp() int64 {
|
||
|
return am.timestamp
|
||
|
}
|
||
|
|
||
|
func (am abstractMetric) SerializeTags() string {
|
||
|
var sb strings.Builder
|
||
|
|
||
|
values := make([]string, 0, len(am.tags))
|
||
|
for k := range am.tags {
|
||
|
values = append(values, k)
|
||
|
}
|
||
|
sortSlice(values)
|
||
|
|
||
|
for _, key := range values {
|
||
|
val := sanitizeValue(am.tags[key])
|
||
|
key = sanitizeKey(key)
|
||
|
sb.WriteString(fmt.Sprintf("%s:%s,", key, val))
|
||
|
}
|
||
|
s := sb.String()
|
||
|
if len(s) > 0 {
|
||
|
s = s[:len(s)-1]
|
||
|
}
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// Counter Metric.
|
||
|
type CounterMetric struct {
|
||
|
value float64
|
||
|
abstractMetric
|
||
|
}
|
||
|
|
||
|
func (c *CounterMetric) Add(value float64) {
|
||
|
c.value += value
|
||
|
}
|
||
|
|
||
|
func (c CounterMetric) GetType() string {
|
||
|
return "c"
|
||
|
}
|
||
|
|
||
|
func (c CounterMetric) SerializeValue() string {
|
||
|
return fmt.Sprintf(":%v", c.value)
|
||
|
}
|
||
|
|
||
|
// timestamp: A unix timestamp (full seconds elapsed since 1970-01-01 00:00 UTC).
|
||
|
func NewCounterMetric(key string, unit MetricUnit, tags map[string]string, timestamp int64, value float64) CounterMetric {
|
||
|
am := abstractMetric{
|
||
|
key,
|
||
|
unit,
|
||
|
tags,
|
||
|
timestamp,
|
||
|
}
|
||
|
|
||
|
return CounterMetric{
|
||
|
value,
|
||
|
am,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Distribution Metric.
|
||
|
type DistributionMetric struct {
|
||
|
values []float64
|
||
|
abstractMetric
|
||
|
}
|
||
|
|
||
|
func (d *DistributionMetric) Add(value float64) {
|
||
|
d.values = append(d.values, value)
|
||
|
}
|
||
|
|
||
|
func (d DistributionMetric) GetType() string {
|
||
|
return "d"
|
||
|
}
|
||
|
|
||
|
func (d DistributionMetric) SerializeValue() string {
|
||
|
var sb strings.Builder
|
||
|
for _, el := range d.values {
|
||
|
sb.WriteString(fmt.Sprintf(":%v", el))
|
||
|
}
|
||
|
return sb.String()
|
||
|
}
|
||
|
|
||
|
// timestamp: A unix timestamp (full seconds elapsed since 1970-01-01 00:00 UTC).
|
||
|
func NewDistributionMetric(key string, unit MetricUnit, tags map[string]string, timestamp int64, value float64) DistributionMetric {
|
||
|
am := abstractMetric{
|
||
|
key,
|
||
|
unit,
|
||
|
tags,
|
||
|
timestamp,
|
||
|
}
|
||
|
|
||
|
return DistributionMetric{
|
||
|
[]float64{value},
|
||
|
am,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Gauge Metric.
|
||
|
type GaugeMetric struct {
|
||
|
last float64
|
||
|
min float64
|
||
|
max float64
|
||
|
sum float64
|
||
|
count float64
|
||
|
abstractMetric
|
||
|
}
|
||
|
|
||
|
func (g *GaugeMetric) Add(value float64) {
|
||
|
g.last = value
|
||
|
g.min = math.Min(g.min, value)
|
||
|
g.max = math.Max(g.max, value)
|
||
|
g.sum += value
|
||
|
g.count++
|
||
|
}
|
||
|
|
||
|
func (g GaugeMetric) GetType() string {
|
||
|
return "g"
|
||
|
}
|
||
|
|
||
|
func (g GaugeMetric) SerializeValue() string {
|
||
|
return fmt.Sprintf(":%v:%v:%v:%v:%v", g.last, g.min, g.max, g.sum, g.count)
|
||
|
}
|
||
|
|
||
|
// timestamp: A unix timestamp (full seconds elapsed since 1970-01-01 00:00 UTC).
|
||
|
func NewGaugeMetric(key string, unit MetricUnit, tags map[string]string, timestamp int64, value float64) GaugeMetric {
|
||
|
am := abstractMetric{
|
||
|
key,
|
||
|
unit,
|
||
|
tags,
|
||
|
timestamp,
|
||
|
}
|
||
|
|
||
|
return GaugeMetric{
|
||
|
value, // last
|
||
|
value, // min
|
||
|
value, // max
|
||
|
value, // sum
|
||
|
value, // count
|
||
|
am,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Set Metric.
|
||
|
type SetMetric[T NumberOrString] struct {
|
||
|
values map[T]void
|
||
|
abstractMetric
|
||
|
}
|
||
|
|
||
|
func (s *SetMetric[T]) Add(value T) {
|
||
|
s.values[value] = member
|
||
|
}
|
||
|
|
||
|
func (s SetMetric[T]) GetType() string {
|
||
|
return "s"
|
||
|
}
|
||
|
|
||
|
func (s SetMetric[T]) SerializeValue() string {
|
||
|
_hash := func(s string) uint32 {
|
||
|
return crc32.ChecksumIEEE([]byte(s))
|
||
|
}
|
||
|
|
||
|
values := make([]T, 0, len(s.values))
|
||
|
for k := range s.values {
|
||
|
values = append(values, k)
|
||
|
}
|
||
|
sortSlice(values)
|
||
|
|
||
|
var sb strings.Builder
|
||
|
for _, el := range values {
|
||
|
switch any(el).(type) {
|
||
|
case int:
|
||
|
sb.WriteString(fmt.Sprintf(":%v", el))
|
||
|
case string:
|
||
|
s := fmt.Sprintf("%v", el)
|
||
|
sb.WriteString(fmt.Sprintf(":%d", _hash(s)))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return sb.String()
|
||
|
}
|
||
|
|
||
|
// timestamp: A unix timestamp (full seconds elapsed since 1970-01-01 00:00 UTC).
|
||
|
func NewSetMetric[T NumberOrString](key string, unit MetricUnit, tags map[string]string, timestamp int64, value T) SetMetric[T] {
|
||
|
am := abstractMetric{
|
||
|
key,
|
||
|
unit,
|
||
|
tags,
|
||
|
timestamp,
|
||
|
}
|
||
|
|
||
|
return SetMetric[T]{
|
||
|
map[T]void{
|
||
|
value: member,
|
||
|
},
|
||
|
am,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func sanitizeKey(s string) string {
|
||
|
return keyRegex.ReplaceAllString(s, "_")
|
||
|
}
|
||
|
|
||
|
func sanitizeValue(s string) string {
|
||
|
return valueRegex.ReplaceAllString(s, "")
|
||
|
}
|
||
|
|
||
|
type Ordered interface {
|
||
|
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string
|
||
|
}
|
||
|
|
||
|
func sortSlice[T Ordered](s []T) {
|
||
|
sort.Slice(s, func(i, j int) bool {
|
||
|
return s[i] < s[j]
|
||
|
})
|
||
|
}
|