bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/metadata/metadata.go (about) 1 // Package metadata provides metadata information between bosun and OpenTSDB. 2 package metadata // import "bosun.org/metadata" 3 4 import ( 5 "bytes" 6 "encoding/json" 7 "io" 8 "io/ioutil" 9 "net/http" 10 "net/url" 11 "reflect" 12 "strings" 13 "sync" 14 "time" 15 16 "bosun.org/opentsdb" 17 "bosun.org/slog" 18 "bosun.org/util" 19 ) 20 21 var ( 22 // AuthToken is an optional string that sets the X-Access-Token HTTP header 23 // which is used to authenticate against Bosun 24 AuthToken string 25 ) 26 27 // RateType is the type of rate for a metric: gauge, counter, or rate. 28 type RateType string 29 30 const ( 31 // Unknown is a not-yet documented rate type. 32 Unknown RateType = "" 33 // Gauge rate type. 34 Gauge = "gauge" 35 // Counter rate type. 36 Counter = "counter" 37 // Rate rate type. 38 Rate = "rate" 39 ) 40 41 // Unit is the unit for a metric. 42 type Unit string 43 44 const ( 45 // None is a not-yet documented unit. 46 None Unit = "" 47 A = "A" // Amps 48 ActiveUsers = "active users" // Google Analytics 49 Alert = "alerts" 50 Abort = "aborts" 51 Bool = "bool" 52 BitsPerSecond = "bits per second" 53 Bytes = "bytes" 54 BytesPerSecond = "bytes per second" 55 C = "C" // Celsius 56 CacheHit = "cache hits" 57 CacheMiss = "cache misses" 58 Change = "changes" 59 Channel = "channels" 60 Check = "checks" 61 CHz = "CentiHertz" 62 Client = "clients" 63 Command = "commands" 64 Connection = "connections" 65 Consumer = "consumers" 66 Context = "contexts" 67 ContextSwitch = "context switches" 68 Count = "" 69 Document = "documents" 70 Enabled = "enabled" 71 Entropy = "entropy" 72 Error = "errors" 73 Event = "" 74 Eviction = "evictions" 75 Exchange = "exchanges" 76 Fault = "faults" 77 Flush = "flushes" 78 Files = "files" 79 Frame = "frames" 80 Fraction = "fraction" 81 Get = "gets" 82 GetExists = "get exists" 83 Group = "groups" 84 Incident = "incidents" 85 Interupt = "interupts" 86 InProgress = "in progress" 87 Item = "items" 88 KBytes = "kbytes" 89 Key = "keys" 90 Load = "load" 91 EMail = "emails" 92 MHz = "MHz" // MegaHertz 93 Megabit = "Mbit" 94 Merge = "merges" 95 Message = "messages" 96 MilliSecond = "milliseconds" 97 Nanosecond = "nanoseconds" 98 Node = "nodes" 99 Ok = "ok" // "OK" or not status, 0 = ok, 1 = not ok 100 Operation = "Operations" 101 Packet = "packets" 102 Page = "pages" 103 Pct = "percent" // Range of 0-100. 104 PerSecond = "per second" 105 Pool = "pools" 106 Process = "processes" 107 Priority = "priority" 108 Query = "queries" 109 Queue = "queues" 110 Ratio = "ratio" 111 Redispatch = "redispatches" 112 Refresh = "refreshes" 113 Replica = "replicas" 114 Retry = "retries" 115 Response = "responses" 116 Request = "requests" 117 RPM = "RPM" // Rotations per minute. 118 Scheduled = "scheduled" 119 Score = "score" 120 Second = "seconds" 121 Sector = "sectors" 122 Segment = "segments" 123 Server = "servers" 124 Session = "sessions" 125 Shard = "shards" 126 Slave = "slaves" 127 Socket = "sockets" 128 Suggest = "suggests" 129 StatusCode = "status code" 130 Resync = "resynchronizations" 131 Syscall = "system calls" 132 Thread = "threads" 133 Timestamp = "timestamp" 134 Transition = "transitions" 135 USD = "US dollars" 136 V = "V" // Volts 137 V10 = "tenth-Volts" 138 Vulnerabilities = "vulnerabilities" 139 Watt = "Watts" 140 Weight = "weight" 141 Yield = "yields" 142 ) 143 144 // Metakey uniquely identifies a metadata entry. 145 type Metakey struct { 146 Metric string 147 Tags string 148 Name string 149 } 150 151 // TagSet returns m's tags. 152 func (m Metakey) TagSet() opentsdb.TagSet { 153 tags, err := opentsdb.ParseTags(m.Tags) 154 if err != nil { 155 return nil 156 } 157 return tags 158 } 159 160 var ( 161 metadata = make(map[Metakey]interface{}) 162 metalock sync.Mutex 163 metahost string 164 metafuncs []func() 165 metadebug bool 166 ) 167 168 // AddMeta adds a metadata entry to memory, which is queued for later sending. 169 func AddMeta(metric string, tags opentsdb.TagSet, name string, value interface{}, setHost bool) { 170 if tags == nil { 171 tags = make(opentsdb.TagSet) 172 } 173 if _, present := tags["host"]; setHost && !present { 174 tags["host"] = util.GetHostManager().GetHostName() 175 } 176 if err := tags.Clean(); err != nil { 177 slog.Error(err) 178 return 179 } 180 ts := tags.Tags() 181 metalock.Lock() 182 defer metalock.Unlock() 183 prev, present := metadata[Metakey{metric, ts, name}] 184 if present && !reflect.DeepEqual(prev, value) { 185 slog.Infof("metadata changed for %s/%s/%s: %v to %v", metric, ts, name, prev, value) 186 go sendMetadata([]Metasend{{ 187 Metric: metric, 188 Tags: tags, 189 Name: name, 190 Value: value, 191 }}) 192 } else if metadebug { 193 slog.Infof("AddMeta for %s/%s/%s: %v", metric, ts, name, value) 194 } 195 metadata[Metakey{metric, ts, name}] = value 196 } 197 198 // AddMetricMeta is a convenience function to set the main metadata fields for a 199 // metric. Those fields are rate, unit, and description. If you need to document 200 // tag keys then use AddMeta. 201 func AddMetricMeta(metric string, rate RateType, unit Unit, desc string) { 202 AddMeta(metric, nil, "rate", rate, false) 203 AddMeta(metric, nil, "unit", unit, false) 204 AddMeta(metric, nil, "desc", desc, false) 205 } 206 207 // Init initializes the metadata send queue. 208 func Init(u *url.URL, debug bool) error { 209 mh, err := u.Parse("/api/metadata/put") 210 if err != nil { 211 return err 212 } 213 if strings.HasPrefix(mh.Host, ":") { 214 mh.Host = "localhost" + mh.Host 215 } 216 metahost = mh.String() 217 metadebug = debug 218 go collectMetadata() 219 return nil 220 } 221 222 var putFunction func(k Metakey, v interface{}) error 223 224 func InitF(debug bool, f func(k Metakey, v interface{}) error) error { 225 putFunction = f 226 metadebug = debug 227 go collectMetadata() 228 return nil 229 } 230 231 func collectMetadata() { 232 // Wait a bit so hopefully our collectors have run once and populated the 233 // metadata. 234 time.Sleep(time.Minute) 235 for { 236 FlushMetadata() 237 time.Sleep(time.Hour) 238 } 239 } 240 241 func FlushMetadata() { 242 for _, f := range metafuncs { 243 f() 244 } 245 if len(metadata) == 0 { 246 return 247 } 248 metalock.Lock() 249 ms := make([]Metasend, len(metadata)) 250 i := 0 251 for k, v := range metadata { 252 ms[i] = Metasend{ 253 Metric: k.Metric, 254 Tags: k.TagSet(), 255 Name: k.Name, 256 Value: v, 257 } 258 i++ 259 } 260 metalock.Unlock() 261 sendMetadata(ms) 262 } 263 264 // Metasend is the struct for sending metadata to bosun. 265 type Metasend struct { 266 Metric string `json:",omitempty"` 267 Tags opentsdb.TagSet `json:",omitempty"` 268 Name string `json:",omitempty"` 269 Value interface{} 270 Time *time.Time `json:",omitempty"` 271 } 272 273 func sendMetadata(ms []Metasend) { 274 if putFunction != nil { 275 for _, m := range ms { 276 key := Metakey{ 277 Metric: m.Metric, 278 Name: m.Name, 279 Tags: m.Tags.Tags(), 280 } 281 err := putFunction(key, m.Value) 282 if err != nil { 283 slog.Error(err) 284 continue 285 } 286 } 287 } else { 288 postMetadata(ms) 289 } 290 } 291 func postMetadata(ms []Metasend) { 292 b, err := json.Marshal(&ms) 293 if err != nil { 294 slog.Error(err) 295 return 296 } 297 req, err := http.NewRequest(http.MethodPost, metahost, bytes.NewBuffer(b)) 298 if err != nil { 299 slog.Error(err) 300 return 301 } 302 req.Header.Set("Content-Type", "application/json") 303 if AuthToken != "" { 304 req.Header.Set("X-Access-Token", AuthToken) 305 } 306 client := http.DefaultClient 307 resp, err := client.Do(req) 308 if err != nil { 309 slog.Error(err) 310 return 311 } 312 defer resp.Body.Close() 313 // Drain up to 512 bytes and close the body to let the Transport reuse the connection 314 io.CopyN(ioutil.Discard, resp.Body, 512) 315 if resp.StatusCode != 204 { 316 slog.Errorln("bad metadata return:", resp.Status) 317 return 318 } 319 }