mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-23 20:37:12 +01:00
[lib/promutils, lib/httputils] fixed floating-point error when parsing time in RFC3339 format (#5801)
This commit is contained in:
parent
3170ad3f44
commit
a7a04bd4e9
@ -29,6 +29,7 @@ The sandbox cluster installation is running under the constant load generated by
|
|||||||
## tip
|
## tip
|
||||||
|
|
||||||
* FEATURE: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): expose `vm_last_partition_parts` [metrics](https://docs.victoriametrics.com/#monitoring), which show the number of [parts in the latest partition](https://docs.victoriametrics.com/#storage). These metrics may help debugging query performance slowdown related to the increased number of parts in the last partition, since usually all the ingested data is written to the last partition and all the queries are performed over the recently ingested data, e.g. the last partition.
|
* FEATURE: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): expose `vm_last_partition_parts` [metrics](https://docs.victoriametrics.com/#monitoring), which show the number of [parts in the latest partition](https://docs.victoriametrics.com/#storage). These metrics may help debugging query performance slowdown related to the increased number of parts in the last partition, since usually all the ingested data is written to the last partition and all the queries are performed over the recently ingested data, e.g. the last partition.
|
||||||
|
* BUGFIX: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): fixed floating-point error when parsing time in RFC3339 format. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5801) for details.
|
||||||
|
|
||||||
## [v1.98.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.98.0)
|
## [v1.98.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.98.0)
|
||||||
|
|
||||||
|
@ -28,11 +28,11 @@ func GetTime(r *http.Request, argKey string, defaultMs int64) (int64, error) {
|
|||||||
return maxTimeMsecs, nil
|
return maxTimeMsecs, nil
|
||||||
}
|
}
|
||||||
// Parse argValue
|
// Parse argValue
|
||||||
secs, err := promutils.ParseTime(argValue)
|
ms, err := promutils.ParseTimeMs(argValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("cannot parse %s=%s: %w", argKey, argValue, err)
|
return 0, fmt.Errorf("cannot parse %s=%s: %w", argKey, argValue, err)
|
||||||
}
|
}
|
||||||
msecs := int64(secs * 1e3)
|
msecs := int64(ms)
|
||||||
if msecs < minTimeMsecs {
|
if msecs < minTimeMsecs {
|
||||||
msecs = 0
|
msecs = 0
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@ func TestGetTimeSuccess(t *testing.T) {
|
|||||||
f("2019-02-02T01:01:00", 1549069260000)
|
f("2019-02-02T01:01:00", 1549069260000)
|
||||||
f("2019-02-02T01:01:01", 1549069261000)
|
f("2019-02-02T01:01:01", 1549069261000)
|
||||||
f("2019-07-07T20:01:02Z", 1562529662000)
|
f("2019-07-07T20:01:02Z", 1562529662000)
|
||||||
|
f("2020-02-21T16:07:49.433Z", 1582301269433)
|
||||||
f("2019-07-07T20:47:40+03:00", 1562521660000)
|
f("2019-07-07T20:47:40+03:00", 1562521660000)
|
||||||
f("-292273086-05-16T16:47:06Z", minTimeMsecs)
|
f("-292273086-05-16T16:47:06Z", minTimeMsecs)
|
||||||
f("292277025-08-18T07:12:54.999999999Z", maxTimeMsecs)
|
f("292277025-08-18T07:12:54.999999999Z", maxTimeMsecs)
|
||||||
|
@ -17,6 +17,16 @@ func ParseTime(s string) (float64, error) {
|
|||||||
return ParseTimeAt(s, currentTimestamp)
|
return ParseTimeAt(s, currentTimestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseTimeMs parses time s in different formats.
|
||||||
|
//
|
||||||
|
// See https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#timestamp-formats
|
||||||
|
//
|
||||||
|
// It returns unix timestamp in milliseconds.
|
||||||
|
func ParseTimeMs(s string) (float64, error) {
|
||||||
|
currentTimestampMs := float64(time.Now().UnixNano()) / 1e6
|
||||||
|
return ParseTimeMsAt(s, currentTimestampMs)
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// time.UnixNano can only store maxInt64, which is 2262
|
// time.UnixNano can only store maxInt64, which is 2262
|
||||||
maxValidYear = 2262
|
maxValidYear = 2262
|
||||||
@ -32,6 +42,18 @@ func ParseTimeAt(s string, currentTimestamp float64) (float64, error) {
|
|||||||
if s == "now" {
|
if s == "now" {
|
||||||
return currentTimestamp, nil
|
return currentTimestamp, nil
|
||||||
}
|
}
|
||||||
|
return ParseTimeMsAt(s, currentTimestamp*1e3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseTimeMsAt parses time s in different formats, assuming the given currentTimestamp.
|
||||||
|
//
|
||||||
|
// See https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#timestamp-formats
|
||||||
|
//
|
||||||
|
// It returns unix timestamp in milliseconds.
|
||||||
|
func ParseTimeMsAt(s string, currentTimestampMs float64) (float64, error) {
|
||||||
|
if s == "now" {
|
||||||
|
return currentTimestampMs, nil
|
||||||
|
}
|
||||||
sOrig := s
|
sOrig := s
|
||||||
tzOffset := float64(0)
|
tzOffset := float64(0)
|
||||||
if len(sOrig) > 6 {
|
if len(sOrig) > 6 {
|
||||||
@ -47,7 +69,7 @@ func ParseTimeAt(s string, currentTimestamp float64) (float64, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("cannot parse minute from timezone offset %q: %w", tz, err)
|
return 0, fmt.Errorf("cannot parse minute from timezone offset %q: %w", tz, err)
|
||||||
}
|
}
|
||||||
tzOffset = float64(hour*3600 + minute*60)
|
tzOffset = float64(1000 * (hour*3600 + minute*60))
|
||||||
if isPlus {
|
if isPlus {
|
||||||
tzOffset = -tzOffset
|
tzOffset = -tzOffset
|
||||||
}
|
}
|
||||||
@ -65,7 +87,7 @@ func ParseTimeAt(s string, currentTimestamp float64) (float64, error) {
|
|||||||
if d > 0 {
|
if d > 0 {
|
||||||
d = -d
|
d = -d
|
||||||
}
|
}
|
||||||
return currentTimestamp + float64(d)/1e9, nil
|
return currentTimestampMs + float64(d)/1e6, nil
|
||||||
}
|
}
|
||||||
if len(s) == 4 {
|
if len(s) == 4 {
|
||||||
// Parse YYYY
|
// Parse YYYY
|
||||||
@ -77,7 +99,7 @@ func ParseTimeAt(s string, currentTimestamp float64) (float64, error) {
|
|||||||
if y > maxValidYear || y < minValidYear {
|
if y > maxValidYear || y < minValidYear {
|
||||||
return 0, fmt.Errorf("cannot parse year from %q: year must in range [%d, %d]", s, minValidYear, maxValidYear)
|
return 0, fmt.Errorf("cannot parse year from %q: year must in range [%d, %d]", s, minValidYear, maxValidYear)
|
||||||
}
|
}
|
||||||
return tzOffset + float64(t.UnixNano())/1e9, nil
|
return tzOffset + float64(t.UnixNano())/1e6, nil
|
||||||
}
|
}
|
||||||
if !strings.Contains(sOrig, "-") {
|
if !strings.Contains(sOrig, "-") {
|
||||||
// Parse the timestamp in seconds or in milliseconds
|
// Parse the timestamp in seconds or in milliseconds
|
||||||
@ -85,9 +107,9 @@ func ParseTimeAt(s string, currentTimestamp float64) (float64, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if ts >= (1 << 32) {
|
if ts < (1 << 32) {
|
||||||
// The timestamp is in milliseconds. Convert it to seconds.
|
// The timestamp is in seconds. Convert it to milliseconds.
|
||||||
ts /= 1000
|
ts *= 1000
|
||||||
}
|
}
|
||||||
return ts, nil
|
return ts, nil
|
||||||
}
|
}
|
||||||
@ -97,7 +119,7 @@ func ParseTimeAt(s string, currentTimestamp float64) (float64, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return tzOffset + float64(t.UnixNano())/1e9, nil
|
return tzOffset + float64(t.UnixNano())/1e6, nil
|
||||||
}
|
}
|
||||||
if len(s) == 10 {
|
if len(s) == 10 {
|
||||||
// Parse YYYY-MM-DD
|
// Parse YYYY-MM-DD
|
||||||
@ -105,7 +127,7 @@ func ParseTimeAt(s string, currentTimestamp float64) (float64, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return tzOffset + float64(t.UnixNano())/1e9, nil
|
return tzOffset + float64(t.UnixNano())/1e6, nil
|
||||||
}
|
}
|
||||||
if len(s) == 13 {
|
if len(s) == 13 {
|
||||||
// Parse YYYY-MM-DDTHH
|
// Parse YYYY-MM-DDTHH
|
||||||
@ -113,7 +135,7 @@ func ParseTimeAt(s string, currentTimestamp float64) (float64, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return tzOffset + float64(t.UnixNano())/1e9, nil
|
return tzOffset + float64(t.UnixNano())/1e6, nil
|
||||||
}
|
}
|
||||||
if len(s) == 16 {
|
if len(s) == 16 {
|
||||||
// Parse YYYY-MM-DDTHH:MM
|
// Parse YYYY-MM-DDTHH:MM
|
||||||
@ -121,7 +143,7 @@ func ParseTimeAt(s string, currentTimestamp float64) (float64, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return tzOffset + float64(t.UnixNano())/1e9, nil
|
return tzOffset + float64(t.UnixNano())/1e6, nil
|
||||||
}
|
}
|
||||||
if len(s) == 19 {
|
if len(s) == 19 {
|
||||||
// Parse YYYY-MM-DDTHH:MM:SS
|
// Parse YYYY-MM-DDTHH:MM:SS
|
||||||
@ -129,12 +151,12 @@ func ParseTimeAt(s string, currentTimestamp float64) (float64, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return tzOffset + float64(t.UnixNano())/1e9, nil
|
return tzOffset + float64(t.UnixNano())/1e6, nil
|
||||||
}
|
}
|
||||||
// Parse RFC3339
|
// Parse RFC3339
|
||||||
t, err := time.Parse(time.RFC3339, sOrig)
|
t, err := time.Parse(time.RFC3339, sOrig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return float64(t.UnixNano()) / 1e9, nil
|
return float64(t.UnixNano()) / 1e6, nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user