github.com/rohankumardubey/aresdb@v0.0.2-0.20190517170215-e54e3ca06b9c/query/common/time_bucketizer.go (about)

     1  //  Copyright (c) 2017-2018 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package common
    16  
    17  import (
    18  	"fmt"
    19  	"github.com/uber/aresdb/utils"
    20  	"strconv"
    21  	"strings"
    22  	"time"
    23  )
    24  
    25  const (
    26  	parseErrorString = "failed to parse time bucketizer: %s"
    27  	// SecondsPerMinute is number of seconds per minute
    28  	SecondsPerMinute = 60
    29  	// SecondsPerHour is number of seconds per hour
    30  	SecondsPerHour = SecondsPerMinute * 60
    31  	// SecondsPerDay is number of secods per day
    32  	SecondsPerDay = SecondsPerHour * 24
    33  	// SecondsPer4Day is number of seconds per 4 days
    34  	SecondsPer4Day = SecondsPerDay * 4
    35  	// DaysPerWeek is number of days per week
    36  	DaysPerWeek = 7
    37  	// WeekdayOffset is to compensate 1970-01-01 being a Thursday
    38  	WeekdayOffset = 4
    39  	// SecondsPerWeek is number of seconds per week
    40  	SecondsPerWeek = SecondsPerDay * DaysPerWeek
    41  )
    42  
    43  // BucketSizeToseconds is the map from normalized bucket unit to number of seconds
    44  var BucketSizeToseconds = map[string]int{
    45  	"m": SecondsPerMinute,
    46  	"h": SecondsPerHour,
    47  	"d": SecondsPerDay,
    48  }
    49  
    50  // TimeDimensionMeta is the aggregation of meta data needed to format time dimensions
    51  type TimeDimensionMeta struct {
    52  	TimeBucketizer  string
    53  	TimeUnit        string
    54  	IsTimezoneTable bool
    55  	TimeZone        *time.Location
    56  	DSTSwitchTs     int64
    57  	FromOffset      int
    58  	ToOffset        int
    59  }
    60  
    61  // TimeSeriesBucketizer is the helper struct to express parsed time bucketizer, see comment below
    62  type TimeSeriesBucketizer struct {
    63  	Size int
    64  	Unit string
    65  }
    66  
    67  // used to convert supported time units (string) to single char format
    68  var bucketSizeToNormalized = map[string]string{
    69  	"minutes": "m",
    70  	"minute":  "m",
    71  	"day":     "d",
    72  	"hours":   "h",
    73  	"hour":    "h",
    74  }
    75  
    76  // ParseRegularTimeBucketizer tries to convert a regular time bucketizer(anything below month) input string to a (Size,
    77  // Unit) pair, reports error if input is invalid/unsupported.
    78  // e.g. "3m" -> (3, "m")  "4 hours" -> (4, "h")
    79  func ParseRegularTimeBucketizer(timeBucketizerString string) (TimeSeriesBucketizer, error) {
    80  	// hack to support quarter-hour
    81  	if timeBucketizerString == "quarter-hour" {
    82  		timeBucketizerString = "15m"
    83  	}
    84  
    85  	result := TimeSeriesBucketizer{}
    86  
    87  	timeBucketizerString = strings.ToLower(timeBucketizerString)
    88  	segments := strings.SplitN(timeBucketizerString, " ", 2)
    89  	if len(segments) == 2 { // "N minutes" "N hours"
    90  		if unit, ok := bucketSizeToNormalized[segments[1]]; ok {
    91  			result.Unit = unit
    92  			size, err := parseSize(segments[0], unit)
    93  			if err != nil {
    94  				return result, utils.StackError(err, fmt.Sprintf(parseErrorString, timeBucketizerString))
    95  			}
    96  			result.Size = size
    97  		} else {
    98  			return result, utils.StackError(nil, fmt.Sprintf(parseErrorString, timeBucketizerString))
    99  		}
   100  	} else {
   101  		if normalized, ok := bucketSizeToNormalized[timeBucketizerString]; ok { // "day", "minute", "hour"
   102  			timeBucketizerString = normalized
   103  		}
   104  
   105  		// "3m", "2h"
   106  		unit := timeBucketizerString[len(timeBucketizerString)-1:]
   107  		if _, ok := BucketSizeToseconds[unit]; !ok {
   108  			return result, utils.StackError(nil, fmt.Sprintf(parseErrorString, timeBucketizerString))
   109  		}
   110  		result.Unit = unit
   111  
   112  		if len(timeBucketizerString) > 1 {
   113  			size, err := parseSize(timeBucketizerString[:len(timeBucketizerString)-1], unit)
   114  			if err != nil {
   115  				return result, utils.StackError(err, fmt.Sprintf(parseErrorString, timeBucketizerString))
   116  			}
   117  			result.Size = size
   118  		} else {
   119  			result.Size = 1
   120  		}
   121  	}
   122  	return result, nil
   123  }
   124  
   125  // parseSize parses input string into integer time bucketizer Size, and validates it with given Unit
   126  func parseSize(s, unit string) (int, error) {
   127  	if unit == "m" || unit == "h" {
   128  		size, err := strconv.Atoi(s)
   129  		if err != nil {
   130  			return 0, utils.StackError(err, fmt.Sprintf(parseErrorString, s))
   131  		}
   132  		if size > 0 && size < 60 && ((unit == "m" && (60%size) == 0) || (unit == "h" && (24%size) == 0)) {
   133  			return size, nil
   134  		}
   135  	}
   136  	return 0, utils.StackError(nil, fmt.Sprintf(parseErrorString, s), fmt.Sprintf("invalid bucket Size for %s", unit))
   137  }