query/common/time_bucketizer.go (94 lines of code) (raw):
// Copyright (c) 2017-2018 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package common
import (
"fmt"
"github.com/uber/aresdb/utils"
"strconv"
"strings"
"time"
)
const (
parseErrorString = "failed to parse time bucketizer: %s"
// SecondsPerMinute is number of seconds per minute
SecondsPerMinute = 60
// SecondsPerHour is number of seconds per hour
SecondsPerHour = SecondsPerMinute * 60
// SecondsPerDay is number of secods per day
SecondsPerDay = SecondsPerHour * 24
// SecondsPer4Day is number of seconds per 4 days
SecondsPer4Day = SecondsPerDay * 4
// DaysPerWeek is number of days per week
DaysPerWeek = 7
// WeekdayOffset is to compensate 1970-01-01 being a Thursday
WeekdayOffset = 4
// SecondsPerWeek is number of seconds per week
SecondsPerWeek = SecondsPerDay * DaysPerWeek
)
// BucketSizeToseconds is the map from normalized bucket unit to number of seconds
var BucketSizeToseconds = map[string]int{
"m": SecondsPerMinute,
"h": SecondsPerHour,
"d": SecondsPerDay,
}
// TimeDimensionMeta is the aggregation of meta data needed to format time dimensions
type TimeDimensionMeta struct {
TimeBucketizer string
TimeUnit string
IsTimezoneTable bool
TimeZone *time.Location
DSTSwitchTs int64
FromOffset int
ToOffset int
}
// TimeSeriesBucketizer is the helper struct to express parsed time bucketizer, see comment below
type TimeSeriesBucketizer struct {
Size int
Unit string
}
// used to convert supported time units (string) to single char format
var bucketSizeToNormalized = map[string]string{
"minutes": "m",
"minute": "m",
"day": "d",
"hours": "h",
"hour": "h",
}
// ParseRegularTimeBucketizer tries to convert a regular time bucketizer(anything below month) input string to a (Size,
// Unit) pair, reports error if input is invalid/unsupported.
// e.g. "3m" -> (3, "m") "4 hours" -> (4, "h")
func ParseRegularTimeBucketizer(timeBucketizerString string) (TimeSeriesBucketizer, error) {
// hack to support quarter-hour
if timeBucketizerString == "quarter-hour" {
timeBucketizerString = "15m"
}
result := TimeSeriesBucketizer{}
timeBucketizerString = strings.ToLower(timeBucketizerString)
segments := strings.SplitN(timeBucketizerString, " ", 2)
if len(segments) == 2 { // "N minutes" "N hours"
if unit, ok := bucketSizeToNormalized[segments[1]]; ok {
result.Unit = unit
size, err := parseSize(segments[0], unit)
if err != nil {
return result, utils.StackError(err, fmt.Sprintf(parseErrorString, timeBucketizerString))
}
result.Size = size
} else {
return result, utils.StackError(nil, fmt.Sprintf(parseErrorString, timeBucketizerString))
}
} else {
if normalized, ok := bucketSizeToNormalized[timeBucketizerString]; ok { // "day", "minute", "hour"
timeBucketizerString = normalized
}
// "3m", "2h"
unit := timeBucketizerString[len(timeBucketizerString)-1:]
if _, ok := BucketSizeToseconds[unit]; !ok {
return result, utils.StackError(nil, fmt.Sprintf(parseErrorString, timeBucketizerString))
}
result.Unit = unit
if len(timeBucketizerString) > 1 {
size, err := parseSize(timeBucketizerString[:len(timeBucketizerString)-1], unit)
if err != nil {
return result, utils.StackError(err, fmt.Sprintf(parseErrorString, timeBucketizerString))
}
result.Size = size
} else {
result.Size = 1
}
}
return result, nil
}
// parseSize parses input string into integer time bucketizer Size, and validates it with given Unit
func parseSize(s, unit string) (int, error) {
if unit == "m" || unit == "h" {
size, err := strconv.Atoi(s)
if err != nil {
return 0, utils.StackError(err, fmt.Sprintf(parseErrorString, s))
}
if size > 0 && size < 60 && ((unit == "m" && (60%size) == 0) || (unit == "h" && (24%size) == 0)) {
return size, nil
}
}
return 0, utils.StackError(nil, fmt.Sprintf(parseErrorString, s), fmt.Sprintf("invalid bucket Size for %s", unit))
}