github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/policy/resolution.go (about) 1 // Copyright (c) 2016 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package policy 22 23 import ( 24 "errors" 25 "fmt" 26 "strings" 27 "time" 28 29 "github.com/m3db/m3/src/metrics/generated/proto/policypb" 30 xtime "github.com/m3db/m3/src/x/time" 31 ) 32 33 const ( 34 windowPrecisionSeparator = "@" 35 ) 36 37 var ( 38 emptyResolution Resolution 39 emptyResolutionProto = policypb.Resolution{} 40 errNilResolutionProto = errors.New("empty resolution proto message") 41 ) 42 43 // Resolution is the sampling resolution for datapoints. 44 type Resolution struct { 45 // Window is the bucket size represented by the resolution. 46 Window time.Duration 47 48 // Precision is the precision of datapoints stored at this resolution. 49 Precision xtime.Unit 50 } 51 52 // ToProto converts the resolution to a protobuf message in place. 53 func (r Resolution) ToProto(pb *policypb.Resolution) error { 54 precision, err := r.Precision.Value() 55 if err != nil { 56 return err 57 } 58 pb.WindowSize = r.Window.Nanoseconds() 59 pb.Precision = precision.Nanoseconds() 60 return nil 61 } 62 63 // FromProto converts the protobuf message to a resolution in place. 64 func (r *Resolution) FromProto(pb policypb.Resolution) error { 65 if pb == emptyResolutionProto { 66 return errNilResolutionProto 67 } 68 precision, err := xtime.UnitFromDuration(time.Duration(pb.Precision)) 69 if err != nil { 70 return err 71 } 72 r.Window = time.Duration(pb.WindowSize) 73 r.Precision = precision 74 return nil 75 } 76 77 // String is the string representation of a resolution. 78 func (r Resolution) String() string { 79 _, maxUnit := xtime.MaxUnitForDuration(r.Window) 80 if maxUnit == r.Precision { 81 // If the precision is the default value, do not write it for better readability. 82 return xtime.ToExtendedString(r.Window) 83 } 84 return fmt.Sprintf("%s%s1%s", xtime.ToExtendedString(r.Window), windowPrecisionSeparator, r.Precision.String()) 85 } 86 87 // ParseResolution parses a resolution. 88 func ParseResolution(str string) (Resolution, error) { 89 separatorIdx := strings.Index(str, windowPrecisionSeparator) 90 91 // If there is no separator, the precision unit is the maximum time unit 92 // for which the window size is a multiple of. 93 if separatorIdx == -1 { 94 windowSize, err := xtime.ParseExtendedDuration(str) 95 if err != nil { 96 return emptyResolution, err 97 } 98 _, precision := xtime.MaxUnitForDuration(windowSize) 99 return Resolution{Window: windowSize, Precision: precision}, nil 100 } 101 102 // Otherwise the window and the precision are determined by the input. 103 windowSize, err := xtime.ParseExtendedDuration(str[:separatorIdx]) 104 if err != nil { 105 return emptyResolution, err 106 } 107 precisionDuration, err := xtime.ParseExtendedDuration(str[separatorIdx+1:]) 108 if err != nil { 109 return emptyResolution, err 110 } 111 precision, err := xtime.UnitFromDuration(precisionDuration) 112 if err != nil { 113 return emptyResolution, err 114 } 115 return Resolution{Window: windowSize, Precision: precision}, nil 116 } 117 118 // MustParseResolution parses a resolution in the form of window@precision, 119 // and panics if the input string is invalid. 120 func MustParseResolution(str string) Resolution { 121 resolution, err := ParseResolution(str) 122 if err != nil { 123 panic(fmt.Errorf("invalid resolution string %s: %v", str, err)) 124 } 125 return resolution 126 } 127 128 // ResolutionValue is the resolution value. 129 type ResolutionValue int 130 131 // List of known resolution values. 132 const ( 133 UnknownResolutionValue ResolutionValue = iota 134 OneSecond 135 TenSeconds 136 OneMinute 137 FiveMinutes 138 TenMinutes 139 ) 140 141 var ( 142 errUnknownResolution = errors.New("unknown resolution") 143 errUnknownResolutionValue = errors.New("unknown resolution value") 144 145 // EmptyResolution is an empty resolution. 146 EmptyResolution Resolution 147 ) 148 149 // Resolution returns the resolution associated with a value. 150 func (v ResolutionValue) Resolution() (Resolution, error) { 151 resolution, exists := valuesToResolution[v] 152 if !exists { 153 return EmptyResolution, errUnknownResolutionValue 154 } 155 return resolution, nil 156 } 157 158 // IsValid returns whether the resolution value is valid. 159 func (v ResolutionValue) IsValid() bool { 160 _, valid := valuesToResolution[v] 161 return valid 162 } 163 164 // ValueFromResolution returns the value given a resolution. 165 func ValueFromResolution(resolution Resolution) (ResolutionValue, error) { 166 value, exists := resolutionToValues[resolution] 167 if exists { 168 return value, nil 169 } 170 return UnknownResolutionValue, errUnknownResolution 171 } 172 173 var ( 174 valuesToResolution = map[ResolutionValue]Resolution{ 175 OneSecond: Resolution{Window: time.Second, Precision: xtime.Second}, 176 TenSeconds: Resolution{Window: 10 * time.Second, Precision: xtime.Second}, 177 OneMinute: Resolution{Window: time.Minute, Precision: xtime.Minute}, 178 FiveMinutes: Resolution{Window: 5 * time.Minute, Precision: xtime.Minute}, 179 TenMinutes: Resolution{Window: 10 * time.Minute, Precision: xtime.Minute}, 180 } 181 182 resolutionToValues = make(map[Resolution]ResolutionValue) 183 ) 184 185 func init() { 186 for value, resolution := range valuesToResolution { 187 resolutionToValues[resolution] = value 188 } 189 }