github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/scrape/model/time.go (about) 1 // Copyright 2022 The Pyroscope Authors 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package model 15 16 import ( 17 "encoding/json" 18 "errors" 19 "fmt" 20 "math" 21 "regexp" 22 "strconv" 23 "strings" 24 "time" 25 ) 26 27 const ( 28 // MinimumTick is the minimum supported time resolution. This has to be 29 // at least time.Second in order for the code below to work. 30 minimumTick = time.Millisecond 31 // second is the Time duration equivalent to one second. 32 second = int64(time.Second / minimumTick) 33 // The number of nanoseconds per minimum tick. 34 nanosPerTick = int64(minimumTick / time.Nanosecond) 35 36 // Earliest is the earliest Time representable. Handy for 37 // initializing a high watermark. 38 Earliest = Time(math.MinInt64) 39 // Latest is the latest Time representable. Handy for initializing 40 // a low watermark. 41 Latest = Time(math.MaxInt64) 42 ) 43 44 // Time is the number of milliseconds since the epoch 45 // (1970-01-01 00:00 UTC) excluding leap seconds. 46 type Time int64 47 48 // Interval describes an interval between two timestamps. 49 type Interval struct { 50 Start, End Time 51 } 52 53 // Now returns the current time as a Time. 54 func Now() Time { 55 return TimeFromUnixNano(time.Now().UnixNano()) 56 } 57 58 // TimeFromUnix returns the Time equivalent to the Unix Time t 59 // provided in seconds. 60 func TimeFromUnix(t int64) Time { 61 return Time(t * second) 62 } 63 64 // TimeFromUnixNano returns the Time equivalent to the Unix Time 65 // t provided in nanoseconds. 66 func TimeFromUnixNano(t int64) Time { 67 return Time(t / nanosPerTick) 68 } 69 70 // Equal reports whether two Times represent the same instant. 71 func (t Time) Equal(o Time) bool { 72 return t == o 73 } 74 75 // Before reports whether the Time t is before o. 76 func (t Time) Before(o Time) bool { 77 return t < o 78 } 79 80 // After reports whether the Time t is after o. 81 func (t Time) After(o Time) bool { 82 return t > o 83 } 84 85 // Add returns the Time t + d. 86 func (t Time) Add(d time.Duration) Time { 87 return t + Time(d/minimumTick) 88 } 89 90 // Sub returns the Duration t - o. 91 func (t Time) Sub(o Time) time.Duration { 92 return time.Duration(t-o) * minimumTick 93 } 94 95 // Time returns the time.Time representation of t. 96 func (t Time) Time() time.Time { 97 return time.Unix(int64(t)/second, (int64(t)%second)*nanosPerTick) 98 } 99 100 // Unix returns t as a Unix time, the number of seconds elapsed 101 // since January 1, 1970 UTC. 102 func (t Time) Unix() int64 { 103 return int64(t) / second 104 } 105 106 // UnixNano returns t as a Unix time, the number of nanoseconds elapsed 107 // since January 1, 1970 UTC. 108 func (t Time) UnixNano() int64 { 109 return int64(t) * nanosPerTick 110 } 111 112 // The number of digits after the dot. 113 var dotPrecision = int(math.Log10(float64(second))) 114 115 // String returns a string representation of the Time. 116 func (t Time) String() string { 117 return strconv.FormatFloat(float64(t)/float64(second), 'f', -1, 64) 118 } 119 120 // MarshalJSON implements the json.Marshaler interface. 121 func (t Time) MarshalJSON() ([]byte, error) { 122 return []byte(t.String()), nil 123 } 124 125 // UnmarshalJSON implements the json.Unmarshaler interface. 126 func (t *Time) UnmarshalJSON(b []byte) error { 127 p := strings.Split(string(b), ".") 128 switch len(p) { 129 case 1: 130 v, err := strconv.ParseInt(string(p[0]), 10, 64) 131 if err != nil { 132 return err 133 } 134 *t = Time(v * second) 135 136 case 2: 137 v, err := strconv.ParseInt(string(p[0]), 10, 64) 138 if err != nil { 139 return err 140 } 141 v *= second 142 143 prec := dotPrecision - len(p[1]) 144 if prec < 0 { 145 p[1] = p[1][:dotPrecision] 146 } else if prec > 0 { 147 p[1] = p[1] + strings.Repeat("0", prec) 148 } 149 150 va, err := strconv.ParseInt(p[1], 10, 32) 151 if err != nil { 152 return err 153 } 154 155 // If the value was something like -0.1 the negative is lost in the 156 // parsing because of the leading zero, this ensures that we capture it. 157 if len(p[0]) > 0 && p[0][0] == '-' && v+va > 0 { 158 *t = Time(v+va) * -1 159 } else { 160 *t = Time(v + va) 161 } 162 163 default: 164 return fmt.Errorf("invalid time %q", string(b)) 165 } 166 return nil 167 } 168 169 // Duration wraps time.Duration. It is used to parse the custom duration format 170 // from YAML. 171 // This type should not propagate beyond the scope of input/output processing. 172 type Duration time.Duration 173 174 // Set implements pflag/flag.Value 175 func (d *Duration) Set(s string) error { 176 var err error 177 *d, err = ParseDuration(s) 178 return err 179 } 180 181 // Type implements pflag.Value 182 func (*Duration) Type() string { 183 return "duration" 184 } 185 186 var durationRE = regexp.MustCompile("^(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$") 187 188 // ParseDuration parses a string into a time.Duration, assuming that a year 189 // always has 365d, a week always has 7d, and a day always has 24h. 190 func ParseDuration(durationStr string) (Duration, error) { 191 switch durationStr { 192 case "0": 193 // Allow 0 without a unit. 194 return 0, nil 195 case "": 196 return 0, fmt.Errorf("empty duration string") 197 } 198 matches := durationRE.FindStringSubmatch(durationStr) 199 if matches == nil { 200 return 0, fmt.Errorf("not a valid duration string: %q", durationStr) 201 } 202 var dur time.Duration 203 204 // Parse the match at pos `pos` in the regex and use `mult` to turn that 205 // into ms, then add that value to the total parsed duration. 206 var overflowErr error 207 m := func(pos int, mult time.Duration) { 208 if matches[pos] == "" { 209 return 210 } 211 n, _ := strconv.Atoi(matches[pos]) 212 213 // Check if the provided duration overflows time.Duration (> ~ 290years). 214 if n > int((1<<63-1)/mult/time.Millisecond) { 215 overflowErr = errors.New("duration out of range") 216 } 217 d := time.Duration(n) * time.Millisecond 218 dur += d * mult 219 220 if dur < 0 { 221 overflowErr = errors.New("duration out of range") 222 } 223 } 224 225 m(2, 1000*60*60*24*365) // y 226 m(4, 1000*60*60*24*7) // w 227 m(6, 1000*60*60*24) // d 228 m(8, 1000*60*60) // h 229 m(10, 1000*60) // m 230 m(12, 1000) // s 231 m(14, 1) // ms 232 233 return Duration(dur), overflowErr 234 } 235 236 func (d Duration) String() string { 237 var ( 238 ms = int64(time.Duration(d) / time.Millisecond) 239 r = "" 240 ) 241 if ms == 0 { 242 return "0s" 243 } 244 245 f := func(unit string, mult int64, exact bool) { 246 if exact && ms%mult != 0 { 247 return 248 } 249 if v := ms / mult; v > 0 { 250 r += fmt.Sprintf("%d%s", v, unit) 251 ms -= v * mult 252 } 253 } 254 255 // Only format years and weeks if the remainder is zero, as it is often 256 // easier to read 90d than 12w6d. 257 f("y", 1000*60*60*24*365, true) 258 f("w", 1000*60*60*24*7, true) 259 260 f("d", 1000*60*60*24, false) 261 f("h", 1000*60*60, false) 262 f("m", 1000*60, false) 263 f("s", 1000, false) 264 f("ms", 1, false) 265 266 return r 267 } 268 269 // MarshalJSON implements the json.Marshaler interface. 270 func (d Duration) MarshalJSON() ([]byte, error) { 271 return json.Marshal(d.String()) 272 } 273 274 // UnmarshalJSON implements the json.Unmarshaler interface. 275 func (d *Duration) UnmarshalJSON(bytes []byte) error { 276 var s string 277 if err := json.Unmarshal(bytes, &s); err != nil { 278 return err 279 } 280 dur, err := ParseDuration(s) 281 if err != nil { 282 return err 283 } 284 *d = dur 285 return nil 286 } 287 288 // MarshalText implements the encoding.TextMarshaler interface. 289 func (d *Duration) MarshalText() ([]byte, error) { 290 return []byte(d.String()), nil 291 } 292 293 // UnmarshalText implements the encoding.TextUnmarshaler interface. 294 func (d *Duration) UnmarshalText(text []byte) error { 295 var err error 296 *d, err = ParseDuration(string(text)) 297 return err 298 } 299 300 // MarshalYAML implements the yaml.Marshaler interface. 301 func (d Duration) MarshalYAML() (interface{}, error) { 302 return d.String(), nil 303 } 304 305 // UnmarshalYAML implements the yaml.Unmarshaler interface. 306 func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error { 307 var s string 308 if err := unmarshal(&s); err != nil { 309 return err 310 } 311 dur, err := ParseDuration(s) 312 if err != nil { 313 return err 314 } 315 *d = dur 316 return nil 317 }