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  }