mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			139 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			139 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
| package sqleng
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/grafana/grafana-plugin-sdk-go/data"
 | |
| )
 | |
| 
 | |
| // getRowFillValues populates a slice of values corresponding to the provided data.Frame fields.
 | |
| // Uses data.FillMissing settings to fill in values that are missing. Values are normally missing
 | |
| // due to that the selected query interval doesn't match the intervals of the data returned from
 | |
| // the query and therefore needs to be resampled.
 | |
| func getRowFillValues(f *data.Frame, tsSchema data.TimeSeriesSchema, currentTime time.Time,
 | |
| 	fillMissing *data.FillMissing, intermediateRows []int, lastSeenRowIdx int) []interface{} {
 | |
| 	vals := make([]interface{}, 0, len(f.Fields))
 | |
| 	for i, field := range f.Fields {
 | |
| 		// if the current field is the time index of the series
 | |
| 		// set the new value to be added to the new timestamp
 | |
| 		if i == tsSchema.TimeIndex {
 | |
| 			switch f.Fields[tsSchema.TimeIndex].Type() {
 | |
| 			case data.FieldTypeTime:
 | |
| 				vals = append(vals, currentTime)
 | |
| 			default:
 | |
| 				vals = append(vals, ¤tTime)
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		isValueField := false
 | |
| 		for _, idx := range tsSchema.ValueIndices {
 | |
| 			if i == idx {
 | |
| 				isValueField = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// if the current field is value Field
 | |
| 		// set the new value to the last seen field value (if such exists)
 | |
| 		// otherwise set the appropriate value according to the fillMissing mode
 | |
| 		// if the current field is string field)
 | |
| 		// set the new value to be added to the last seen value (if such exists)
 | |
| 		// if the Frame is wide then there should not be any string fields
 | |
| 		var newVal interface{}
 | |
| 		if isValueField {
 | |
| 			if len(intermediateRows) > 0 {
 | |
| 				// instead of setting the last seen
 | |
| 				// we could set avg, sum, min or max
 | |
| 				// of the intermediate values for each field
 | |
| 				newVal = f.At(i, intermediateRows[len(intermediateRows)-1])
 | |
| 			} else {
 | |
| 				val, err := data.GetMissing(fillMissing, field, lastSeenRowIdx)
 | |
| 				if err == nil {
 | |
| 					newVal = val
 | |
| 				}
 | |
| 			}
 | |
| 		} else if lastSeenRowIdx >= 0 {
 | |
| 			newVal = f.At(i, lastSeenRowIdx)
 | |
| 		}
 | |
| 		vals = append(vals, newVal)
 | |
| 	}
 | |
| 	return vals
 | |
| }
 | |
| 
 | |
| // resample resample provided time-series data.Frame.
 | |
| // This is needed in the case of the selected query interval doesn't
 | |
| // match the intervals of the time-series field in the data.Frame and
 | |
| // therefore needs to be resampled.
 | |
| func resample(f *data.Frame, qm dataQueryModel) (*data.Frame, error) {
 | |
| 	tsSchema := f.TimeSeriesSchema()
 | |
| 	if tsSchema.Type == data.TimeSeriesTypeNot {
 | |
| 		return f, fmt.Errorf("can not fill missing, not timeseries frame")
 | |
| 	}
 | |
| 
 | |
| 	if qm.Interval == 0 {
 | |
| 		return f, nil
 | |
| 	}
 | |
| 
 | |
| 	newFields := make([]*data.Field, 0, len(f.Fields))
 | |
| 	for _, field := range f.Fields {
 | |
| 		newField := data.NewFieldFromFieldType(field.Type(), 0)
 | |
| 		newField.Name = field.Name
 | |
| 		newField.Labels = field.Labels
 | |
| 		newFields = append(newFields, newField)
 | |
| 	}
 | |
| 	resampledFrame := data.NewFrame(f.Name, newFields...)
 | |
| 	resampledFrame.Meta = f.Meta
 | |
| 
 | |
| 	resampledRowidx := 0
 | |
| 	lastSeenRowIdx := -1
 | |
| 	timeField := f.Fields[tsSchema.TimeIndex]
 | |
| 
 | |
| 	startUnixTime := qm.TimeRange.From.Unix() / int64(qm.Interval.Seconds()) * int64(qm.Interval.Seconds())
 | |
| 	startTime := time.Unix(startUnixTime, 0)
 | |
| 
 | |
| 	for currentTime := startTime; !currentTime.After(qm.TimeRange.To); currentTime = currentTime.Add(qm.Interval) {
 | |
| 		initialRowIdx := 0
 | |
| 		if lastSeenRowIdx > 0 {
 | |
| 			initialRowIdx = lastSeenRowIdx + 1
 | |
| 		}
 | |
| 		intermediateRows := make([]int, 0)
 | |
| 		for {
 | |
| 			rowLen, err := f.RowLen()
 | |
| 			if err != nil {
 | |
| 				return f, err
 | |
| 			}
 | |
| 			if initialRowIdx == rowLen {
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			t, ok := timeField.ConcreteAt(initialRowIdx)
 | |
| 			if !ok {
 | |
| 				return f, fmt.Errorf("time point is nil")
 | |
| 			}
 | |
| 
 | |
| 			// take the last element of the period current - interval <-> current, use it as value for current data point value
 | |
| 			previousTime := currentTime.Add(-qm.Interval)
 | |
| 			if t.(time.Time).After(previousTime) {
 | |
| 				if !t.(time.Time).After(currentTime) {
 | |
| 					intermediateRows = append(intermediateRows, initialRowIdx)
 | |
| 				} else {
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			lastSeenRowIdx = initialRowIdx
 | |
| 			initialRowIdx++
 | |
| 		}
 | |
| 
 | |
| 		// no intermediate points; set values following fill missing mode
 | |
| 		fieldVals := getRowFillValues(f, tsSchema, currentTime, qm.FillMissing, intermediateRows, lastSeenRowIdx)
 | |
| 
 | |
| 		resampledFrame.InsertRow(resampledRowidx, fieldVals...)
 | |
| 		resampledRowidx++
 | |
| 	}
 | |
| 
 | |
| 	return resampledFrame, nil
 | |
| }
 |