package influxdb import ( "context" "sync" "time" client "github.com/influxdata/influxdb1-client/v2" "github.com/sirupsen/logrus" "src.dualinventive.com/go/dinet/rpc" "src.dualinventive.com/go/influxdb-logger/internal/device" "src.dualinventive.com/go/lib/kv" ) const ( measurementEvent = "event" measurementDevice = "device" measurementDeviceError = "device_error" ) // Influx writes measurements to an influx database type Influx struct { c chan *rpc.Msg devCache *device.Cache writer Writer } // New creates a new Influx connection func New(uri, username, password, db string, bufferSize int, redis kv.KV, cacheSize int, cacheExpiry time.Duration) (*Influx, error) { c, err := client.NewHTTPClient(client.HTTPConfig{Addr: uri, Username: username, Password: password}) if err != nil { return nil, err } return &Influx{ c: make(chan *rpc.Msg), devCache: device.New(redis, cacheSize, cacheExpiry), writer: NewWriter(c, db, bufferSize), }, nil } // Send sends a message to influx. It is processed internally by another go routine. func (i *Influx) Send(m *rpc.Msg) error { i.c <- m return nil } // Process processes all incomming write messages. This method blocks until the context is done. func (i *Influx) Process(ctx context.Context, writeInterval time.Duration) { var wg sync.WaitGroup defer wg.Wait() wg.Add(1) go func() { defer wg.Done() i.writer.Dispatch(ctx, writeInterval) }() for { select { case <-ctx.Done(): return case m := <-i.c: if err := i.process(m); err != nil { logrus.Error("Unable to process RPC message:", err) } } } } // process processes a single rpc message func (i *Influx) process(m *rpc.Msg) error { errCode := rpc.Ok if m.Error != nil { errCode = m.Error.Code } // Drop request messages if m.Type == rpc.MsgTypeRequest { return nil } var deviceType string d, err := i.devCache.Device(m.DeviceUID) if err != nil { logrus.Warnf("cannot get device '%s' from cache: %v", m.DeviceUID, err) } if d != nil { deviceType = d.Type } rpcPt, err := client.NewPoint( measurementEvent, map[string]string{ "device:uid": m.DeviceUID, "device:group": m.DeviceUID[0:2], "device:type": deviceType, "class:method": string(m.ClassMethod), "type": string(m.Type), }, map[string]interface{}{ "errno": uint32(errCode), }, m.Time.ToTime(), ) if err != nil { logrus.Errorf("error while creating new samplepoint: %v", err) return err } i.writer.Add(rpcPt) // Ignore error messages if m.Error != nil { return nil } return i.routeMessage(m) } func (i *Influx) routeMessage(m *rpc.Msg) error { // Route the right class methods switch m.ClassMethod { case rpc.ClassMethodDeviceData: return i.processDeviceData(m) case rpc.ClassMethodSensorData: return i.processResultValueItems(m, rpc.ClassSensor) case rpc.ClassMethodNotifyData: return i.processResultValueItems(m, rpc.ClassNotify) } return nil }