github.com/aclisp/heapster@v0.19.2-0.20160613100040-51756f899a96/events/sinks/influxdb/influxdb.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 influxdb 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "net/url" 21 "strings" 22 "sync" 23 "time" 24 25 influxdb_common "k8s.io/heapster/common/influxdb" 26 "k8s.io/heapster/events/core" 27 metrics_core "k8s.io/heapster/metrics/core" 28 kube_api "k8s.io/kubernetes/pkg/api" 29 30 "github.com/golang/glog" 31 influxdb "github.com/influxdb/influxdb/client" 32 ) 33 34 type influxdbSink struct { 35 client influxdb_common.InfluxdbClient 36 sync.RWMutex 37 c influxdb_common.InfluxdbConfig 38 dbExists bool 39 } 40 41 const ( 42 eventMeasurementName = "log/events" 43 // Event special tags 44 eventUID = "uid" 45 // Value Field name 46 valueField = "value" 47 // Event special tags 48 dbNotFoundError = "database not found" 49 50 // Maximum number of influxdb Points to be sent in one batch. 51 maxSendBatchSize = 1000 52 ) 53 54 func (sink *influxdbSink) resetConnection() { 55 glog.Infof("Influxdb connection reset") 56 sink.dbExists = false 57 sink.client = nil 58 } 59 60 // Generate point value for event 61 func getEventValue(event *kube_api.Event) (string, error) { 62 // TODO: check whether indenting is required. 63 bytes, err := json.MarshalIndent(event, "", " ") 64 if err != nil { 65 return "", err 66 } 67 return string(bytes), nil 68 } 69 70 func eventToPointWithFields(event *kube_api.Event) (*influxdb.Point, error) { 71 point := influxdb.Point{ 72 Measurement: "events", 73 Time: event.LastTimestamp.Time.UTC(), 74 Fields: map[string]interface{}{ 75 "message": event.Message, 76 }, 77 Tags: map[string]string{ 78 eventUID: string(event.UID), 79 }, 80 } 81 if event.InvolvedObject.Kind == "Pod" { 82 point.Tags[metrics_core.LabelPodId.Key] = string(event.InvolvedObject.UID) 83 } 84 point.Tags["object_name"] = event.InvolvedObject.Name 85 point.Tags["type"] = event.Type 86 point.Tags["kind"] = event.InvolvedObject.Kind 87 point.Tags["component"] = event.Source.Component 88 point.Tags["reason"] = event.Reason 89 point.Tags[metrics_core.LabelNamespaceName.Key] = event.Namespace 90 point.Tags[metrics_core.LabelHostname.Key] = event.Source.Host 91 return &point, nil 92 } 93 94 func eventToPoint(event *kube_api.Event) (*influxdb.Point, error) { 95 value, err := getEventValue(event) 96 if err != nil { 97 return nil, err 98 } 99 100 point := influxdb.Point{ 101 Measurement: eventMeasurementName, 102 Time: event.LastTimestamp.Time.UTC(), 103 Fields: map[string]interface{}{ 104 valueField: value, 105 }, 106 Tags: map[string]string{ 107 eventUID: string(event.UID), 108 }, 109 } 110 if event.InvolvedObject.Kind == "Pod" { 111 point.Tags[metrics_core.LabelPodId.Key] = string(event.InvolvedObject.UID) 112 point.Tags[metrics_core.LabelPodName.Key] = event.InvolvedObject.Name 113 } 114 point.Tags[metrics_core.LabelHostname.Key] = event.Source.Host 115 return &point, nil 116 } 117 118 func (sink *influxdbSink) ExportEvents(eventBatch *core.EventBatch) { 119 sink.Lock() 120 defer sink.Unlock() 121 122 dataPoints := make([]influxdb.Point, 0, 10) 123 for _, event := range eventBatch.Events { 124 var point *influxdb.Point 125 var err error 126 if sink.c.WithFields { 127 point, err = eventToPointWithFields(event) 128 } else { 129 point, err = eventToPoint(event) 130 } 131 if err != nil { 132 glog.Warningf("Failed to convert event to point: %v", err) 133 } 134 dataPoints = append(dataPoints, *point) 135 if len(dataPoints) >= maxSendBatchSize { 136 sink.sendData(dataPoints) 137 dataPoints = make([]influxdb.Point, 0, 1) 138 } 139 } 140 if len(dataPoints) >= 0 { 141 sink.sendData(dataPoints) 142 } 143 } 144 145 func (sink *influxdbSink) sendData(dataPoints []influxdb.Point) { 146 if err := sink.createDatabase(); err != nil { 147 glog.Errorf("Failed to create infuxdb: %v", err) 148 return 149 } 150 bp := influxdb.BatchPoints{ 151 Points: dataPoints, 152 Database: sink.c.DbName, 153 RetentionPolicy: "default", 154 } 155 156 start := time.Now() 157 if _, err := sink.client.Write(bp); err != nil { 158 if strings.Contains(err.Error(), dbNotFoundError) { 159 sink.resetConnection() 160 } else if _, _, err := sink.client.Ping(); err != nil { 161 glog.Errorf("InfluxDB ping failed: %v", err) 162 sink.resetConnection() 163 } 164 } 165 end := time.Now() 166 glog.V(4).Infof("Exported %d data to influxDB in %s", len(dataPoints), end.Sub(start)) 167 } 168 169 func (sink *influxdbSink) Name() string { 170 return "InfluxDB Sink" 171 } 172 173 func (sink *influxdbSink) Stop() { 174 // nothing needs to be done. 175 } 176 177 func (sink *influxdbSink) createDatabase() error { 178 if sink.client == nil { 179 client, err := influxdb_common.NewClient(sink.c) 180 if err != nil { 181 return err 182 } 183 sink.client = client 184 } 185 186 if sink.dbExists { 187 return nil 188 } 189 q := influxdb.Query{ 190 Command: fmt.Sprintf("CREATE DATABASE %s", sink.c.DbName), 191 } 192 if resp, err := sink.client.Query(q); err != nil { 193 // We want to return error only if it is not "already exists" error. 194 if !(resp != nil && resp.Err != nil && strings.Contains(resp.Err.Error(), "already exists")) { 195 return fmt.Errorf("Database creation failed: %v", err) 196 } 197 } 198 sink.dbExists = true 199 glog.Infof("Created database %q on influxDB server at %q", sink.c.DbName, sink.c.Host) 200 return nil 201 } 202 203 // Returns a thread-safe implementation of core.EventSink for InfluxDB. 204 func new(c influxdb_common.InfluxdbConfig) core.EventSink { 205 client, err := influxdb_common.NewClient(c) 206 if err != nil { 207 glog.Errorf("issues while creating an InfluxDB sink: %v, will retry on use", err) 208 } 209 return &influxdbSink{ 210 client: client, // can be nil 211 c: c, 212 } 213 } 214 215 func CreateInfluxdbSink(uri *url.URL) (core.EventSink, error) { 216 config, err := influxdb_common.BuildConfig(uri) 217 if err != nil { 218 return nil, err 219 } 220 sink := new(*config) 221 glog.Infof("created influxdb sink with options: host:%s user:%s db:%s", config.Host, config.User, config.DbName) 222 return sink, nil 223 }