mirror of
https://github.com/Oxalide/vsphere-influxdb-go.git
synced 2023-10-10 13:36:51 +02:00
226 lines
5.3 KiB
Go
226 lines
5.3 KiB
Go
package influxql
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/influxdata/influxdb/models"
|
|
)
|
|
|
|
// Emitter groups values together by name, tags, and time.
|
|
type Emitter struct {
|
|
buf []Point
|
|
itrs []Iterator
|
|
ascending bool
|
|
chunkSize int
|
|
|
|
tags Tags
|
|
row *models.Row
|
|
|
|
// The columns to attach to each row.
|
|
Columns []string
|
|
|
|
// The time zone location.
|
|
Location *time.Location
|
|
|
|
// Removes the "time" column from output.
|
|
// Used for meta queries where time does not apply.
|
|
OmitTime bool
|
|
}
|
|
|
|
// NewEmitter returns a new instance of Emitter that pulls from itrs.
|
|
func NewEmitter(itrs []Iterator, ascending bool, chunkSize int) *Emitter {
|
|
return &Emitter{
|
|
buf: make([]Point, len(itrs)),
|
|
itrs: itrs,
|
|
ascending: ascending,
|
|
chunkSize: chunkSize,
|
|
Location: time.UTC,
|
|
}
|
|
}
|
|
|
|
// Close closes the underlying iterators.
|
|
func (e *Emitter) Close() error {
|
|
return Iterators(e.itrs).Close()
|
|
}
|
|
|
|
// Emit returns the next row from the iterators.
|
|
func (e *Emitter) Emit() (*models.Row, bool, error) {
|
|
// Immediately end emission if there are no iterators.
|
|
if len(e.itrs) == 0 {
|
|
return nil, false, nil
|
|
}
|
|
|
|
// Continually read from iterators until they are exhausted.
|
|
for {
|
|
// Fill buffer. Return row if no more points remain.
|
|
t, name, tags, err := e.loadBuf()
|
|
if err != nil {
|
|
return nil, false, err
|
|
} else if t == ZeroTime {
|
|
row := e.row
|
|
e.row = nil
|
|
return row, false, nil
|
|
}
|
|
|
|
// Read next set of values from all iterators at a given time/name/tags.
|
|
// If no values are returned then return row.
|
|
values := e.readAt(t, name, tags)
|
|
if values == nil {
|
|
row := e.row
|
|
e.row = nil
|
|
return row, false, nil
|
|
}
|
|
|
|
// If there's no row yet then create one.
|
|
// If the name and tags match the existing row, append to that row if
|
|
// the number of values doesn't exceed the chunk size.
|
|
// Otherwise return existing row and add values to next emitted row.
|
|
if e.row == nil {
|
|
e.createRow(name, tags, values)
|
|
} else if e.row.Name == name && e.tags.Equals(&tags) {
|
|
if e.chunkSize > 0 && len(e.row.Values) >= e.chunkSize {
|
|
row := e.row
|
|
row.Partial = true
|
|
e.createRow(name, tags, values)
|
|
return row, true, nil
|
|
}
|
|
e.row.Values = append(e.row.Values, values)
|
|
} else {
|
|
row := e.row
|
|
e.createRow(name, tags, values)
|
|
return row, true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// loadBuf reads in points into empty buffer slots.
|
|
// Returns the next time/name/tags to emit for.
|
|
func (e *Emitter) loadBuf() (t int64, name string, tags Tags, err error) {
|
|
t = ZeroTime
|
|
|
|
for i := range e.itrs {
|
|
// Load buffer, if empty.
|
|
if e.buf[i] == nil {
|
|
e.buf[i], err = e.readIterator(e.itrs[i])
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Skip if buffer is empty.
|
|
p := e.buf[i]
|
|
if p == nil {
|
|
continue
|
|
}
|
|
itrTime, itrName, itrTags := p.time(), p.name(), p.tags()
|
|
|
|
// Initialize range values if not set.
|
|
if t == ZeroTime {
|
|
t, name, tags = itrTime, itrName, itrTags
|
|
continue
|
|
}
|
|
|
|
// Update range values if lower and emitter is in time ascending order.
|
|
if e.ascending {
|
|
if (itrName < name) || (itrName == name && itrTags.ID() < tags.ID()) || (itrName == name && itrTags.ID() == tags.ID() && itrTime < t) {
|
|
t, name, tags = itrTime, itrName, itrTags
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Update range values if higher and emitter is in time descending order.
|
|
if (itrName > name) || (itrName == name && itrTags.ID() > tags.ID()) || (itrName == name && itrTags.ID() == tags.ID() && itrTime > t) {
|
|
t, name, tags = itrTime, itrName, itrTags
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// createRow creates a new row attached to the emitter.
|
|
func (e *Emitter) createRow(name string, tags Tags, values []interface{}) {
|
|
e.tags = tags
|
|
e.row = &models.Row{
|
|
Name: name,
|
|
Tags: tags.KeyValues(),
|
|
Columns: e.Columns,
|
|
Values: [][]interface{}{values},
|
|
}
|
|
}
|
|
|
|
// readAt returns the next slice of values from the iterators at time/name/tags.
|
|
// Returns nil values once the iterators are exhausted.
|
|
func (e *Emitter) readAt(t int64, name string, tags Tags) []interface{} {
|
|
offset := 1
|
|
if e.OmitTime {
|
|
offset = 0
|
|
}
|
|
|
|
values := make([]interface{}, len(e.itrs)+offset)
|
|
if !e.OmitTime {
|
|
values[0] = time.Unix(0, t).In(e.Location)
|
|
}
|
|
e.readInto(t, name, tags, values[offset:])
|
|
return values
|
|
}
|
|
|
|
func (e *Emitter) readInto(t int64, name string, tags Tags, values []interface{}) {
|
|
for i, p := range e.buf {
|
|
// Skip if buffer is empty.
|
|
if p == nil {
|
|
values[i] = nil
|
|
continue
|
|
}
|
|
|
|
// Skip point if it doesn't match time/name/tags.
|
|
pTags := p.tags()
|
|
if p.time() != t || p.name() != name || !pTags.Equals(&tags) {
|
|
values[i] = nil
|
|
continue
|
|
}
|
|
|
|
// Read point value.
|
|
values[i] = p.value()
|
|
|
|
// Clear buffer.
|
|
e.buf[i] = nil
|
|
}
|
|
}
|
|
|
|
// readIterator reads the next point from itr.
|
|
func (e *Emitter) readIterator(itr Iterator) (Point, error) {
|
|
if itr == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
switch itr := itr.(type) {
|
|
case FloatIterator:
|
|
if p, err := itr.Next(); err != nil {
|
|
return nil, err
|
|
} else if p != nil {
|
|
return p, nil
|
|
}
|
|
case IntegerIterator:
|
|
if p, err := itr.Next(); err != nil {
|
|
return nil, err
|
|
} else if p != nil {
|
|
return p, nil
|
|
}
|
|
case StringIterator:
|
|
if p, err := itr.Next(); err != nil {
|
|
return nil, err
|
|
} else if p != nil {
|
|
return p, nil
|
|
}
|
|
case BooleanIterator:
|
|
if p, err := itr.Next(); err != nil {
|
|
return nil, err
|
|
} else if p != nil {
|
|
return p, nil
|
|
}
|
|
default:
|
|
panic(fmt.Sprintf("unsupported iterator: %T", itr))
|
|
}
|
|
return nil, nil
|
|
}
|