// Query returns datapoints for the named time series during the supplied time // span. Data is returned as a series of consecutive data points. // // Data is queried only at the Resolution supplied: if data for the named time // series is not stored at the given resolution, an empty result will be // returned. // // All data stored on the server is downsampled to some degree; the data points // returned represent the average value within a sample period. Each datapoint's // timestamp falls in the middle of the sample period it represents. // // If data for the named time series was collected from multiple sources, each // returned datapoint will represent the sum of datapoints from all sources at // the same time. The returned string slices contains a list of all sources for // the metric which were aggregated to produce the result. func (db *DB) Query(query Query, r Resolution, startNanos, endNanos int64) ([]TimeSeriesDatapoint, []string, error) { // Normalize startNanos and endNanos the nearest SampleDuration boundary. startNanos -= startNanos % r.SampleDuration() var rows []client.KeyValue if len(query.Sources) == 0 { // Based on the supplied timestamps and resolution, construct start and end // keys for a scan that will return every key with data relevant to the // query. startKey := MakeDataKey(query.Name, "" /* source */, r, startNanos) endKey := MakeDataKey(query.Name, "" /* source */, r, endNanos).PrefixEnd() var b client.Batch b.Header.ReadConsistency = roachpb.INCONSISTENT b.Scan(startKey, endKey, 0) if err := db.db.Run(&b); err != nil { return nil, nil, err } rows = b.Results[0].Rows } else { b := db.db.NewBatch() b.Header.ReadConsistency = roachpb.INCONSISTENT // Iterate over all key timestamps which may contain data for the given // sources, based on the given start/end time and the resolution. kd := r.KeyDuration() startKeyNanos := startNanos - (startNanos % kd) endKeyNanos := endNanos - (endNanos % kd) for currentTimestamp := startKeyNanos; currentTimestamp <= endKeyNanos; currentTimestamp += kd { for _, source := range query.Sources { key := MakeDataKey(query.Name, source, r, currentTimestamp) b.Get(key) } } err := db.db.Run(b) if err != nil { return nil, nil, err } for _, result := range b.Results { row := result.Rows[0] if row.Value == nil { continue } rows = append(rows, row) } } // Convert the queried source data into a set of data spans, one for each // source. sourceSpans, err := makeDataSpans(rows, startNanos) if err != nil { return nil, nil, err } // Compute a downsample function which will be used to return values from // each source for each sample period. downsampler, err := getDownsampleFunction(query.GetDownsampler()) if err != nil { return nil, nil, err } // If we are returning a derivative, iteration needs to start at offset -1 // (in order to correctly compute the rate of change at offset 0). var startOffset int32 isDerivative := query.GetDerivative() != TimeSeriesQueryDerivative_NONE if isDerivative { startOffset = -1 } // Create an interpolatingIterator for each dataSpan, adding each iterator // into a unionIterator collection. This is also where we compute a list of // all sources with data present in the query. sources := make([]string, 0, len(sourceSpans)) iters := make(unionIterator, 0, len(sourceSpans)) for name, span := range sourceSpans { sources = append(sources, name) iters = append(iters, span.newIterator(startOffset, downsampler)) } // Choose an aggregation function to use when taking values from the // unionIterator. var valueFn func() float64 switch query.GetSourceAggregator() { case TimeSeriesQueryAggregator_SUM: valueFn = iters.sum case TimeSeriesQueryAggregator_AVG: valueFn = iters.avg case TimeSeriesQueryAggregator_MAX: valueFn = iters.max case TimeSeriesQueryAggregator_MIN: valueFn = iters.min } // Iterate over all requested offsets, recording a value from the // unionIterator at each offset encountered. If the query is requesting a // derivative, a rate of change is recorded instead of the actual values. iters.init() var last TimeSeriesDatapoint if isDerivative { last = TimeSeriesDatapoint{ TimestampNanos: iters.timestamp(), Value: valueFn(), } // For derivatives, the iterator was initialized at offset -1 in order // to calculate the rate of change at offset zero. However, in some // cases (such as the very first value recorded) offset -1 is not // available. In this case, we treat the rate-of-change at the first // offset as zero. if iters.offset() < 0 { iters.advance() } } var responseData []TimeSeriesDatapoint for iters.isValid() && iters.timestamp() <= endNanos { current := TimeSeriesDatapoint{ TimestampNanos: iters.timestamp(), Value: valueFn(), } response := current if isDerivative { dTime := (current.TimestampNanos - last.TimestampNanos) / time.Second.Nanoseconds() if dTime == 0 { response.Value = 0 } else { response.Value = (current.Value - last.Value) / float64(dTime) } if response.Value < 0 && query.GetDerivative() == TimeSeriesQueryDerivative_NON_NEGATIVE_DERIVATIVE { response.Value = 0 } } responseData = append(responseData, response) last = current iters.advance() } return responseData, sources, nil }