From 4bf77662d35789b92a2bf04bfec3c802caba3473 Mon Sep 17 00:00:00 2001
From: Cyril Tovena <cyril.tovena@gmail.com>
Date: Thu, 18 Jul 2019 08:49:58 -0400
Subject: [PATCH] rounds nanoseconds boundaries to milliseconds (#771)

* rounds nanoseconds boundaries to milliseconds

* convert also store query
---
 pkg/ingester/flush.go |  7 ++---
 pkg/storage/store.go  |  3 ++-
 pkg/util/conv.go      |  9 +++++++
 pkg/util/conv_test.go | 59 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 74 insertions(+), 4 deletions(-)
 create mode 100644 pkg/util/conv_test.go

diff --git a/pkg/ingester/flush.go b/pkg/ingester/flush.go
index 29280297..d45c11be 100644
--- a/pkg/ingester/flush.go
+++ b/pkg/ingester/flush.go
@@ -18,6 +18,7 @@ import (
 	"github.com/cortexproject/cortex/pkg/ingester/client"
 	"github.com/cortexproject/cortex/pkg/util"
 	"github.com/grafana/loki/pkg/chunkenc"
+	loki_util "github.com/grafana/loki/pkg/util"
 )
 
 var (
@@ -261,12 +262,12 @@ func (i *Ingester) flushChunks(ctx context.Context, fp model.Fingerprint, labelP
 
 	wireChunks := make([]chunk.Chunk, 0, len(cs))
 	for _, c := range cs {
-		firstTime, lastTime := c.chunk.Bounds()
+		firstTime, lastTime := loki_util.RoundToMilliseconds(c.chunk.Bounds())
 		c := chunk.NewChunk(
 			userID, fp, metric,
 			chunkenc.NewFacade(c.chunk),
-			model.TimeFromUnixNano(firstTime.UnixNano()),
-			model.TimeFromUnixNano(lastTime.UnixNano()),
+			firstTime,
+			lastTime,
 		)
 
 		start := time.Now()
diff --git a/pkg/storage/store.go b/pkg/storage/store.go
index 0ff8fcca..7e156637 100644
--- a/pkg/storage/store.go
+++ b/pkg/storage/store.go
@@ -11,6 +11,7 @@ import (
 	"github.com/grafana/loki/pkg/iter"
 	"github.com/grafana/loki/pkg/logproto"
 	"github.com/grafana/loki/pkg/logql"
+	"github.com/grafana/loki/pkg/util"
 	"github.com/opentracing/opentracing-go"
 	"github.com/prometheus/common/model"
 	"github.com/prometheus/prometheus/pkg/labels"
@@ -56,7 +57,7 @@ func (s *store) LazyQuery(ctx context.Context, req *logproto.QueryRequest) (iter
 		}
 
 		matchers = append(matchers, nameLabelMatcher)
-		from, through := model.TimeFromUnixNano(req.Start.UnixNano()), model.TimeFromUnixNano(req.End.UnixNano())
+		from, through := util.RoundToMilliseconds(req.Start, req.End)
 		chks, fetchers, err := s.GetChunkRefs(ctx, from, through, matchers...)
 		if err != nil {
 			return nil, err
diff --git a/pkg/util/conv.go b/pkg/util/conv.go
index bc95638c..54e31dcd 100644
--- a/pkg/util/conv.go
+++ b/pkg/util/conv.go
@@ -1,8 +1,10 @@
 package util
 
 import (
+	"math"
 	"sort"
 	"strings"
+	"time"
 
 	"github.com/cortexproject/cortex/pkg/ingester/client"
 	"github.com/grafana/loki/pkg/logql"
@@ -41,3 +43,10 @@ func ModelLabelSetToMap(m model.LabelSet) map[string]string {
 	}
 	return result
 }
+
+// RoundToMilliseconds returns milliseconds precision time from nanoseconds.
+// from will be rounded down to the nearest milliseconds while through is rounded up.
+func RoundToMilliseconds(from, through time.Time) (model.Time, model.Time) {
+	return model.Time(int64(math.Floor(float64(from.UnixNano()) / float64(time.Millisecond)))),
+		model.Time(int64(math.Ceil(float64(through.UnixNano()) / float64(time.Millisecond))))
+}
diff --git a/pkg/util/conv_test.go b/pkg/util/conv_test.go
new file mode 100644
index 00000000..ab27d230
--- /dev/null
+++ b/pkg/util/conv_test.go
@@ -0,0 +1,59 @@
+package util
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/prometheus/common/model"
+)
+
+func TestRoundToMilliseconds(t *testing.T) {
+	tests := []struct {
+		name        string
+		from        time.Time
+		through     time.Time
+		wantFrom    model.Time
+		wantThrough model.Time
+	}{
+		{
+			"0",
+			time.Unix(0, 0),
+			time.Unix(0, 1),
+			model.Time(0),
+			model.Time(1),
+		},
+		{
+			"equal",
+			time.Unix(0, time.Millisecond.Nanoseconds()),
+			time.Unix(0, time.Millisecond.Nanoseconds()),
+			model.Time(1),
+			model.Time(1),
+		},
+		{
+			"exact",
+			time.Unix(0, time.Millisecond.Nanoseconds()),
+			time.Unix(0, 2*time.Millisecond.Nanoseconds()),
+			model.Time(1),
+			model.Time(2),
+		},
+		{
+			"rounding",
+			time.Unix(0, time.Millisecond.Nanoseconds()+10),
+			time.Unix(0, 2*time.Millisecond.Nanoseconds()+10),
+			model.Time(1),
+			model.Time(3),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			from, through := RoundToMilliseconds(tt.from, tt.through)
+			if !reflect.DeepEqual(from, tt.wantFrom) {
+				t.Errorf("RoundToMilliseconds() from = %v, want %v", from, tt.wantFrom)
+			}
+			if !reflect.DeepEqual(through, tt.wantThrough) {
+				t.Errorf("RoundToMilliseconds() through = %v, want %v", through, tt.wantThrough)
+			}
+		})
+	}
+}
-- 
GitLab