go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/cmd/statsd-to-tsmon/statsd.go (about) 1 // Copyright 2020 The LUCI Authors. 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 main 16 17 import ( 18 "go.chromium.org/luci/common/errors" 19 ) 20 21 var ( 22 // ErrMalformedStatsdLine is returned by ParseStatsdMetric if it receives 23 // an input that doesn't look like a statsd metric line. 24 ErrMalformedStatsdLine = errors.New("statsd line has wrong format") 25 26 // ErrUnsupportedType is returned by ParseStatsdMetric if the statsd metric 27 // has a type we don't support. 28 ErrUnsupportedType = errors.New("unsupported metric type") 29 ) 30 31 // StatsdMetricType enumerates supports statsd metric types. 32 type StatsdMetricType int 33 34 const ( 35 StatsdMetricUnknown StatsdMetricType = 0 36 StatsdMetricGauge StatsdMetricType = 1 // 'g' 37 StatsdMetricCounter StatsdMetricType = 2 // 'c' 38 StatsdMetricTimer StatsdMetricType = 3 // 'ms' 39 ) 40 41 // StatsdMetric holds some parsed statsd metric. 42 // 43 // It retains pointers to the buffer it was parsed from. 44 // 45 // A metric line "a.b.c:1234|c" is represented by 46 // 47 // m := StatsdMetric{ 48 // Name: [][]byte{ 49 // {'a'}, 50 // {'b'}, 51 // {'c'}, 52 // }, 53 // Type: StatsdMetricCounter, 54 // Value: []byte("1234"), 55 // } 56 type StatsdMetric struct { 57 Name [][]byte 58 Type StatsdMetricType 59 Value []byte 60 } 61 62 // ParseStatsdMetric parses one statsd metric line from the buffer. 63 // 64 // Returns the number of bytes read and the parsed metric. The parsed metric 65 // retains pointers to the `buf`. Make sure `buf` is not modified as long as 66 // there are StatsdMetric that point to it. 67 func ParseStatsdMetric(buf []byte, metric *StatsdMetric) (read int, err error) { 68 // Reset the state (but retain Name buffer). 69 metric.Name = metric.Name[:0] 70 metric.Type = StatsdMetricUnknown 71 metric.Value = nil 72 73 // Buf contains one or more lines like: 74 // 75 // xxx.yyy.zzz:12345|c 76 // xxx.yyy.zzz:12345|c|<ignore> 77 // xxx.yyy.zzz:12345|c#ignore 78 // 79 // We parse only the first line using a simple state machine. That way we can 80 // avoid unnecessary memory allocations on a hot code path. 81 const ( 82 S_NAME = iota // in 'xxx' 83 S_VALUE // in '1234' 84 S_TYPE // in 'c' 85 S_SKIP // waiting for \n or EOF 86 S_SKIP_BROKEN // waiting for \n or EOF 87 ) 88 state := S_NAME 89 90 nameIdx := 0 // index where the name component started 91 valueIdx := 0 // index where the value portion started 92 typIdx := 0 // index where the type portion started 93 typ := []byte(nil) // the type portion 94 95 idx := 0 // the current scanning pointer 96 chr := byte(0) // the current character 97 98 for idx, chr = range buf { 99 switch { 100 case state == S_NAME && (chr == '.' || chr == ':'): 101 // This happens when parsing e.g. "abc..def". 102 if nameIdx == idx { 103 state = S_SKIP_BROKEN 104 continue 105 } 106 metric.Name = append(metric.Name, buf[nameIdx:idx]) 107 if chr == '.' { 108 nameIdx = idx + 1 109 state = S_NAME // keep reading the name 110 } else { // ':' 111 valueIdx = idx + 1 112 state = S_VALUE // reading the value now 113 } 114 115 case state == S_VALUE && chr == '|': 116 metric.Value = buf[valueIdx:idx] 117 typIdx = idx + 1 118 state = S_TYPE 119 120 case state == S_TYPE && (chr == '|' || chr == '#' || chr == '\n'): 121 typ = buf[typIdx:idx] 122 state = S_SKIP 123 } 124 125 if chr == '\n' { 126 break 127 } 128 } 129 read = idx + 1 130 131 // S_TYPE transitions into S_SKIP on EOF/EOL. 132 if state == S_TYPE { 133 typ = buf[typIdx : idx+1] 134 state = S_SKIP 135 } 136 137 // S_SKIP is the only valid state on EOF/EOL. 138 if state != S_SKIP { 139 err = ErrMalformedStatsdLine 140 } else { 141 // Parse `typ`. 142 switch { 143 case len(typ) == 1 && typ[0] == 'g': 144 metric.Type = StatsdMetricGauge 145 case len(typ) == 1 && typ[0] == 'c': 146 metric.Type = StatsdMetricCounter 147 case len(typ) == 2 && typ[0] == 'm' && typ[1] == 's': 148 metric.Type = StatsdMetricTimer 149 default: 150 err = ErrUnsupportedType 151 } 152 } 153 154 return 155 }