File: stat.go

package info (click to toggle)
golang-github-cactus-go-statsd-client 5.0.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 204 kB
  • sloc: makefile: 5
file content (139 lines) | stat: -rw-r--r-- 3,017 bytes parent folder | download | duplicates (2)
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
package statsdtest

import (
	"bytes"
	"fmt"
	"strings"
)

// Stat contains the raw and extracted stat information from a stat that was
// sent by the RecordingSender. Raw will always have the content that was
// consumed for this specific stat and Parsed will be set if no errors were hit
// pulling information out of it.
type Stat struct {
	Raw    []byte
	Stat   string
	Value  string
	Tag    string
	Rate   string
	Parsed bool
}

// String fulfils the stringer interface
func (s *Stat) String() string {
	return fmt.Sprintf("%s %s %s", s.Stat, s.Value, s.Rate)
}

// ParseStats takes a sequence of bytes destined for a Statsd server and parses
// it out into one or more Stat structs. Each struct includes both the raw
// bytes (copied, so the src []byte may be reused if desired) as well as each
// component it was able to parse out. If parsing was incomplete Stat.Parsed
// will be set to false but no error is returned / kept.
func ParseStats(src []byte) Stats {
	d := make([]byte, len(src))
	copy(d, src)

	// standard protocol indicates one stat per line
	entries := bytes.Split(d, []byte{'\n'})

	result := make(Stats, len(entries))

	for i, e := range entries {
		result[i] = Stat{Raw: e}
		ss := &result[i]

		// : deliniates the stat name from the stat data
		marker := bytes.IndexByte(e, ':')
		if marker == -1 {
			continue
		}
		ss.Stat = string(e[0:marker])

		// stat data folows ':' with the form {value}|{type tag}[|@{sample rate}]
		e = e[marker+1:]
		marker = bytes.IndexByte(e, '|')
		if marker == -1 {
			continue
		}

		ss.Value = string(e[:marker])

		e = e[marker+1:]
		marker = bytes.IndexByte(e, '|')
		if marker == -1 {
			// no sample rate
			ss.Tag = string(e)
		} else {
			ss.Tag = string(e[:marker])
			e = e[marker+1:]
			if len(e) == 0 || e[0] != '@' {
				// sample rate should be prefixed with '@'; bail otherwise
				continue
			}
			ss.Rate = string(e[1:])
		}

		ss.Parsed = true
	}

	return result
}

// Stats is a slice of Stat
type Stats []Stat

// Unparsed returns any stats that were unable to be completely parsed.
func (s Stats) Unparsed() Stats {
	var r Stats
	for _, e := range s {
		if !e.Parsed {
			r = append(r, e)
		}
	}

	return r
}

// CollectNamed returns all data sent for a given stat name.
func (s Stats) CollectNamed(statName string) Stats {
	return s.Collect(func(e Stat) bool {
		return e.Stat == statName
	})
}

// Collect gathers all stats that make some predicate true.
func (s Stats) Collect(pred func(Stat) bool) Stats {
	var r Stats
	for _, e := range s {
		if pred(e) {
			r = append(r, e)
		}
	}
	return r
}

// Values returns the values associated with this Stats object.
func (s Stats) Values() []string {
	if len(s) == 0 {
		return nil
	}

	r := make([]string, len(s))
	for i, e := range s {
		r[i] = e.Value
	}
	return r
}

// String fulfils the stringer interface
func (s Stats) String() string {
	if len(s) == 0 {
		return ""
	}

	r := make([]string, len(s))
	for i, e := range s {
		r[i] = e.String()
	}
	return strings.Join(r, "\n")
}