github.com/timstclair/heapster@v0.20.0-alpha1/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 eventToPoint(event *kube_api.Event) (*influxdb.Point, error) { 71 value, err := getEventValue(event) 72 if err != nil { 73 return nil, err 74 } 75 76 point := influxdb.Point{ 77 Measurement: eventMeasurementName, 78 Time: event.LastTimestamp.Time.UTC(), 79 Fields: map[string]interface{}{ 80 valueField: value, 81 }, 82 Tags: map[string]string{ 83 eventUID: string(event.UID), 84 }, 85 } 86 if event.InvolvedObject.Kind == "Pod" { 87 point.Tags[metrics_core.LabelPodId.Key] = string(event.InvolvedObject.UID) 88 point.Tags[metrics_core.LabelPodName.Key] = event.InvolvedObject.Name 89 } 90 point.Tags[metrics_core.LabelHostname.Key] = event.Source.Host 91 return &point, nil 92 } 93 94 func (sink *influxdbSink) ExportEvents(eventBatch *core.EventBatch) { 95 sink.Lock() 96 defer sink.Unlock() 97 98 dataPoints := make([]influxdb.Point, 0, 10) 99 for _, event := range eventBatch.Events { 100 point, err := eventToPoint(event) 101 if err != nil { 102 glog.Warningf("Failed to convert event to point: %v", err) 103 } 104 dataPoints = append(dataPoints, *point) 105 if len(dataPoints) >= maxSendBatchSize { 106 sink.sendData(dataPoints) 107 dataPoints = make([]influxdb.Point, 0, 1) 108 } 109 } 110 if len(dataPoints) >= 0 { 111 sink.sendData(dataPoints) 112 } 113 } 114 115 func (sink *influxdbSink) sendData(dataPoints []influxdb.Point) { 116 if err := sink.createDatabase(); err != nil { 117 glog.Errorf("Failed to create infuxdb: %v", err) 118 } 119 bp := influxdb.BatchPoints{ 120 Points: dataPoints, 121 Database: sink.c.DbName, 122 RetentionPolicy: "default", 123 } 124 125 start := time.Now() 126 if _, err := sink.client.Write(bp); err != nil { 127 if strings.Contains(err.Error(), dbNotFoundError) { 128 sink.resetConnection() 129 } else if _, _, err := sink.client.Ping(); err != nil { 130 glog.Errorf("InfluxDB ping failed: %v", err) 131 sink.resetConnection() 132 } 133 } 134 end := time.Now() 135 glog.V(4).Info("Exported %d data to influxDB in %s", len(dataPoints), end.Sub(start)) 136 } 137 138 func (sink *influxdbSink) Name() string { 139 return "InfluxDB Sink" 140 } 141 142 func (sink *influxdbSink) Stop() { 143 // nothing needs to be done. 144 } 145 146 func (sink *influxdbSink) createDatabase() error { 147 if sink.client == nil { 148 client, err := influxdb_common.NewClient(sink.c) 149 if err != nil { 150 return err 151 } 152 sink.client = client 153 } 154 155 if sink.dbExists { 156 return nil 157 } 158 q := influxdb.Query{ 159 Command: fmt.Sprintf("CREATE DATABASE %s", sink.c.DbName), 160 } 161 if resp, err := sink.client.Query(q); err != nil { 162 // We want to return error only if it is not "already exists" error. 163 if !(resp != nil && resp.Err != nil && strings.Contains(resp.Err.Error(), "already exists")) { 164 return fmt.Errorf("Database creation failed: %v", err) 165 } 166 } 167 sink.dbExists = true 168 glog.Infof("Created database %q on influxDB server at %q", sink.c.DbName, sink.c.Host) 169 return nil 170 } 171 172 // Returns a thread-safe implementation of core.EventSink for InfluxDB. 173 func new(c influxdb_common.InfluxdbConfig) core.EventSink { 174 client, err := influxdb_common.NewClient(c) 175 if err != nil { 176 glog.Errorf("issues while creating an InfluxDB sink: %v, will retry on use", err) 177 } 178 return &influxdbSink{ 179 client: client, // can be nil 180 c: c, 181 } 182 } 183 184 func CreateInfluxdbSink(uri *url.URL) (core.EventSink, error) { 185 config, err := influxdb_common.BuildConfig(uri) 186 if err != nil { 187 return nil, err 188 } 189 sink := new(*config) 190 glog.Infof("created influxdb sink with options: host:%s user:%s db:%s", config.Host, config.User, config.DbName) 191 return sink, nil 192 }