github.com/galamsiva2020/kubernetes-heapster-monitoring@v0.0.0-20210823134957-3c1baa7c1e70/metrics/sinks/wavefront/wavefront.go (about) 1 // Copyright 2015 Google Inc. All Rights Reserved. 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 wavefront 16 17 import ( 18 "fmt" 19 "github.com/golang/glog" 20 "k8s.io/heapster/metrics/core" 21 "net" 22 "net/url" 23 "sort" 24 "strconv" 25 "strings" 26 "time" 27 ) 28 29 const ( 30 sysSubContainerName = "system.slice/" 31 ) 32 33 var excludeTagList = [...]string{"namespace_id", "host_id", "pod_id", "hostname"} 34 35 type wavefrontSink struct { 36 Conn net.Conn 37 ProxyAddress string 38 ClusterName string 39 Prefix string 40 IncludeLabels bool 41 IncludeContainers bool 42 testMode bool 43 testReceivedLines []string 44 } 45 46 func (wfSink *wavefrontSink) Name() string { 47 return "Wavefront Sink" 48 } 49 50 func (wfSink *wavefrontSink) Stop() { 51 // Do nothing. 52 wfSink.Conn.Close() 53 } 54 55 func (wfSink *wavefrontSink) sendLine(line string) { 56 if wfSink.testMode { 57 wfSink.testReceivedLines = append(wfSink.testReceivedLines, line) 58 glog.Infoln(line) 59 return 60 } 61 //if the connection was closed or interrupted - don't cause a panic (we'll retry at next interval) 62 defer func() { 63 if r := recover(); r != nil { 64 //we couldn't write the line so something is wrong with the connection 65 wfSink.Conn = nil 66 } 67 }() 68 if wfSink.Conn != nil { 69 wfSink.Conn.Write([]byte(line)) 70 } 71 } 72 73 func (wfSink *wavefrontSink) sendPoint(metricName string, metricValStr string, ts string, source string, tagStr string) { 74 metricLine := fmt.Sprintf("%s %s %s source=\"%s\" %s\n", metricName, metricValStr, ts, source, tagStr) 75 wfSink.sendLine(metricLine) 76 } 77 78 func tagsToString(tags map[string]string) string { 79 tagStr := "" 80 for k, v := range tags { 81 // ignore tags with empty values as well so the data point doesn't fail validation 82 if excludeTag(k) == false && len(v) > 0 { 83 tagStr += k + "=\"" + v + "\" " 84 } 85 } 86 return tagStr 87 } 88 89 func excludeTag(a string) bool { 90 for _, b := range excludeTagList { 91 if b == a { 92 return true 93 } 94 } 95 return false 96 } 97 98 func (wfSink *wavefrontSink) cleanMetricName(metricType string, metricName string) string { 99 return wfSink.Prefix + metricType + "." + strings.Replace(metricName, "/", ".", -1) 100 } 101 102 func (wfSink *wavefrontSink) addLabelTags(ms *core.MetricSet, tags map[string]string) { 103 for _, labelName := range sortedLabelKeys(ms.Labels) { 104 labelValue := ms.Labels[labelName] 105 if labelName == "labels" { 106 //only parse labels if IncludeLabels == true 107 if wfSink.IncludeLabels { 108 for _, label := range strings.Split(labelValue, ",") { 109 //labels = app:webproxy,version:latest 110 tagParts := strings.SplitN(label, ":", 2) 111 if len(tagParts) == 2 { 112 tags["label."+tagParts[0]] = tagParts[1] 113 } 114 } 115 } 116 } else { 117 tags[labelName] = labelValue 118 } 119 } 120 121 } 122 123 func (wfSink *wavefrontSink) send(batch *core.DataBatch) { 124 125 metricCounter := 0 126 127 for _, key := range sortedMetricSetKeys(batch.MetricSets) { 128 ms := batch.MetricSets[key] 129 130 // Populate tag map 131 tags := make(map[string]string) 132 // Make sure all metrics are tagged with the cluster name 133 tags["cluster"] = wfSink.ClusterName 134 // Add pod labels as tags 135 wfSink.addLabelTags(ms, tags) 136 metricType := tags["type"] 137 if strings.Contains(tags["container_name"], sysSubContainerName) { 138 //don't send system subcontainers 139 continue 140 } 141 if wfSink.IncludeContainers == false && strings.Contains(metricType, "pod_container") { 142 // the user doesn't want to include container metrics (only pod and above) 143 continue 144 } 145 for _, metricName := range sortedMetricValueKeys(ms.MetricValues) { 146 var metricValStr string 147 metricValue := ms.MetricValues[metricName] 148 if core.ValueInt64 == metricValue.ValueType { 149 metricValStr = fmt.Sprintf("%d", metricValue.IntValue) 150 } else if core.ValueFloat == metricValue.ValueType { // W 151 metricValStr = fmt.Sprintf("%f", metricValue.FloatValue) 152 } else { 153 //do nothing for now 154 metricValStr = "" 155 } 156 if metricValStr != "" { 157 ts := strconv.FormatInt(batch.Timestamp.Unix(), 10) 158 source := "" 159 if metricType == "cluster" { 160 source = wfSink.ClusterName 161 } else if metricType == "ns" { 162 source = tags["namespace_name"] + "-ns" 163 } else { 164 source = tags["hostname"] 165 } 166 tagStr := tagsToString(tags) 167 wfSink.sendPoint(wfSink.cleanMetricName(metricType, metricName), metricValStr, ts, source, tagStr) 168 metricCounter = metricCounter + 1 169 } 170 } 171 for _, metric := range ms.LabeledMetrics { 172 metricName := wfSink.cleanMetricName(metricType, metric.Name) 173 metricValStr := "" 174 if core.ValueInt64 == metric.ValueType { 175 metricValStr = fmt.Sprintf("%d", metric.IntValue) 176 } else if core.ValueFloat == metric.ValueType { // W 177 metricValStr = fmt.Sprintf("%f", metric.FloatValue) 178 } else { 179 //do nothing for now 180 metricValStr = "" 181 } 182 if metricValStr != "" { 183 ts := strconv.FormatInt(batch.Timestamp.Unix(), 10) 184 source := tags["hostname"] 185 tagStr := tagsToString(tags) 186 for labelName, labelValue := range metric.Labels { 187 tagStr += labelName + "=\"" + labelValue + "\" " 188 } 189 metricCounter = metricCounter + 1 190 wfSink.sendPoint(metricName, metricValStr, ts, source, tagStr) 191 } 192 } 193 } 194 195 } 196 197 func (wfSink *wavefrontSink) ExportData(batch *core.DataBatch) { 198 199 if wfSink.testMode { 200 //clear lines from last batch 201 wfSink.testReceivedLines = wfSink.testReceivedLines[:0] 202 wfSink.send(batch) 203 return 204 } 205 206 //make sure we're Connected before sending a real batch 207 err := wfSink.connect() 208 if err != nil { 209 glog.Warning(err) 210 } 211 212 if wfSink.Conn != nil && err == nil { 213 wfSink.send(batch) 214 } 215 } 216 217 func (wfSink *wavefrontSink) connect() error { 218 var err error 219 wfSink.Conn, err = net.DialTimeout("tcp", wfSink.ProxyAddress, time.Second*10) 220 if err != nil { 221 glog.Warningf("Unable to connect to Wavefront proxy at address: %s", wfSink.ProxyAddress) 222 return err 223 } else { 224 glog.Infof("Connected to Wavefront proxy at address: %s", wfSink.ProxyAddress) 225 return nil 226 } 227 } 228 229 func NewWavefrontSink(uri *url.URL) (core.DataSink, error) { 230 231 storage := &wavefrontSink{ 232 ProxyAddress: uri.Scheme + ":" + uri.Opaque, 233 ClusterName: "k8s-cluster", 234 Prefix: "heapster.", 235 IncludeLabels: false, 236 IncludeContainers: true, 237 testMode: false, 238 } 239 240 vals := uri.Query() 241 if len(vals["clusterName"]) > 0 { 242 storage.ClusterName = vals["clusterName"][0] 243 } 244 if len(vals["prefix"]) > 0 { 245 storage.Prefix = vals["prefix"][0] 246 } 247 if len(vals["includeLabels"]) > 0 { 248 incLabels := false 249 incLabels, err := strconv.ParseBool(vals["includeLabels"][0]) 250 if err != nil { 251 glog.Warning("Unable to parse the includeLabels argument. This argument is a boolean, please pass \"true\" or \"false\"") 252 return nil, err 253 } 254 storage.IncludeLabels = incLabels 255 } 256 if len(vals["includeContainers"]) > 0 { 257 incContainers := false 258 incContainers, err := strconv.ParseBool(vals["includeContainers"][0]) 259 if err != nil { 260 glog.Warning("Unable to parse the includeContainers argument. This argument is a boolean, please pass \"true\" or \"false\"") 261 return nil, err 262 } 263 storage.IncludeContainers = incContainers 264 } 265 if len(vals["testMode"]) > 0 { 266 testMode := false 267 testMode, err := strconv.ParseBool(vals["testMode"][0]) 268 if err != nil { 269 glog.Warning("Unable to parse the testMode argument. This argument is a boolean, please pass \"true\" or \"false\"") 270 return nil, err 271 } 272 storage.testMode = testMode 273 } 274 return storage, nil 275 } 276 277 func sortedMetricSetKeys(m map[string]*core.MetricSet) []string { 278 keys := make([]string, len(m)) 279 i := 0 280 for k := range m { 281 keys[i] = k 282 i++ 283 } 284 sort.Strings(keys) 285 return keys 286 } 287 288 func sortedLabelKeys(m map[string]string) []string { 289 keys := make([]string, len(m)) 290 i := 0 291 for k := range m { 292 keys[i] = k 293 i++ 294 } 295 sort.Strings(keys) 296 return keys 297 } 298 299 func sortedMetricValueKeys(m map[string]core.MetricValue) []string { 300 keys := make([]string, len(m)) 301 i := 0 302 for k := range m { 303 keys[i] = k 304 i++ 305 } 306 sort.Strings(keys) 307 return keys 308 }