Skip to content

Commit

Permalink
Allow to store Date in UTC (#114)
Browse files Browse the repository at this point in the history
Allow to store Date in UTC
  • Loading branch information
msaf1980 authored Nov 13, 2022
1 parent f96b832 commit 1a33775
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 8 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,34 @@ jobs:
env:
CGO_ENABLED: 1

- name: Test
run: make test
env:
CGO_ENABLED: 1

- name: Test (with GMT+5)
run: |
go clean -testcache
TZ=Etc/GMT+5 make test
env:
CGO_ENABLED: 1

# Some tests are broen in GMT-5
# --- FAIL: TestProm1UnpackFast (1.34s)
# prometheus_test.go:74:
# Error Trace: prometheus_test.go:74
# Error: Not equal:
# expected:
# Test: TestProm1UnpackFast
# FAIL
# FAIL github.com/lomik/carbon-clickhouse/receiver 2.515s
# - name: Test (with GMT-5)
# run: |
# go clean -testcache
# TZ=Etc/GMT-5 make test
# env:
# CGO_ENABLED: 1

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ WORKDIR /go/src/github.com/lomik/carbon-clickhouse

COPY . .

RUN apk --no-cache add make git
RUN apk --no-cache add make git tzdata

RUN make

Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ Usage of carbon-clickhouse:
-version=false: Print version
```

Date are broken by default (not always in UTC), but this used from start of project, and can produce some bugs.
Change to UTC requires points/index/tags tables rebuild (Date recalc to true UTC) or queries with wide Date range.
Set `data.utc-date = true` for this.
Without UTC date is required to run carbon-clickhouse and graphite-clickhouse in one timezone.

```toml
[common]
# Prefix for store all internal carbon-clickhouse graphs. Supported macroses: {host}
Expand Down Expand Up @@ -115,6 +120,9 @@ compression = "none"
# For "lz4" 0 means use normal LZ4, >=1 use LZ4HC with this depth (the higher - the better compression, but slower)
compression-level = 0

# Date are broken by default (not always in UTC)
#utc-date = false

[upload.graphite]
type = "points"
table = "graphite"
Expand Down
6 changes: 6 additions & 0 deletions carbon/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/BurntSushi/toml"
rb "github.com/lomik/carbon-clickhouse/helper/RowBinary"
"github.com/lomik/carbon-clickhouse/helper/config"
"github.com/lomik/carbon-clickhouse/helper/tags"
"github.com/lomik/carbon-clickhouse/uploader"
Expand Down Expand Up @@ -93,6 +94,7 @@ type dataConfig struct {
AutoInterval *config.ChunkAutoInterval `toml:"chunk-auto-interval"`
CompAlgo *config.Compression `toml:"compression"`
CompLevel int `toml:"compression-level"`
UTCDate bool `toml:"utc-date"`
}

// Config ...
Expand Down Expand Up @@ -286,5 +288,9 @@ func ReadConfig(filename string) (*Config, error) {
}
}

if cfg.Data.UTCDate {
rb.SetUTCDate()
}

return cfg, nil
}
41 changes: 35 additions & 6 deletions helper/RowBinary/date.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@ package RowBinary

import "time"

var TimestampToDays func(timestamp uint32) uint16

// UTCTimestampToDays is always UTC, but mismatch SlowTimestampToDays and need points/index/tags table rebuild (with Date recalc)
func SetUTCDate() {
TimestampToDays = UTCTimestampToDays
}

// PrecalcTimestampToDays is broken, not always UTC, like SlowTimestampToDays, but used from start of project
func SetDefaultDate() {
TimestampToDays = PrecalcTimestampToDays
}

var daysTimestampStart []int64

func init() {
Expand All @@ -14,21 +26,18 @@ func init() {
daysTimestampStart = append(daysTimestampStart, ts)
t = t.Add(24 * time.Hour)
}
SetDefaultDate()
}

func TimestampToDays(timestamp uint32) uint16 {
if int64(timestamp) < daysTimestampStart[0] {
return 0
}

// PrecalcTimestampToDays is broken, not always UTC, like SlowTimestampToDays
func PrecalcTimestampToDays(timestamp uint32) uint16 {
i := int(timestamp / 86400)
ts := int64(timestamp)

if i < 10 || i > len(daysTimestampStart)-10 {
// fallback to slow method
return SlowTimestampToDays(timestamp)
}

FindLoop:
for {
if ts < daysTimestampStart[i] {
Expand All @@ -43,7 +52,27 @@ FindLoop:
}
}

// SlowTimestampToDays is broken, not always UTC
func SlowTimestampToDays(timestamp uint32) uint16 {
t := time.Unix(int64(timestamp), 0)
return uint16(time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC).Unix() / 86400)
}

// TimestampToDaysFormat is pair for SlowTimestampToDays and broken also for symmetric
func TimestampToDaysFormat(timestamp int64) string {
t := time.Unix(timestamp, 0)
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC).Format("2006-01-02")
}

// TimeToDaysFormat like TimestampDaysFormat, but for time.Time
func TimeToDaysFormat(t time.Time) string {
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC).Format("2006-01-02")
}

func UTCTimestampToDays(timestamp uint32) uint16 {
return uint16(timestamp / 86400)
}

func UTCTimestampToDaysFormat(timestamp uint32) string {
return time.Unix(int64(timestamp), 0).UTC().Format("2006-01-02")
}
165 changes: 164 additions & 1 deletion helper/RowBinary/date_test.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,131 @@
package RowBinary

import (
"os"
"strconv"
"testing"
"time"
)

var runBroken bool

func init() {
if os.Getenv("RUN_BRKEN_TESTS") == "1" {
runBroken = true
}
}

// SlowTimestampToDays is broken on some cases
//
// TZ=Etc/GMT-5 RUN_BRKEN_TESTS=1 make test
// --- FAIL: TestSlowTimestampToDays (0.00s)
//
// --- FAIL: TestSlowTimestampToDays/1668106870_2022-11-11T00:01:10+05:00,_UTC_2022-11-10T19:01:10Z_[0] (0.00s)
// date_test.go:62: TimestampDaysFormat() = 19307 (2022-11-11), want 19306 (2022-11-10)
// --- FAIL: TestSlowTimestampToDays/1668193200_2022-11-12T00:00:00+05:00,_UTC_2022-11-11T19:00:00Z_[1] (0.00s)
// date_test.go:62: TimestampDaysFormat() = 19308 (2022-11-12), want 19307 (2022-11-11)
//
// FAIL
// FAIL github.com/lomik/carbon-clickhouse/helper/RowBinary 3.328s
//
// $ TZ=Etc/GMT+5 RUN_BRKEN_TESTS=1 make test
// go test -race ./...
// --- FAIL: TestSlowTimestampToDays (0.00s)
//
// --- FAIL: TestSlowTimestampToDays/1668106870_2022-11-10T14:01:10-05:00,_UTC_2022-11-10T19:01:10Z_[0] (0.00s)
// date_test.go:62: TimestampDaysFormat() = 19306 (2022-11-09), want 19306 (2022-11-10)
// --- FAIL: TestSlowTimestampToDays/1668193200_2022-11-11T14:00:00-05:00,_UTC_2022-11-11T19:00:00Z_[1] (0.00s)
// date_test.go:62: TimestampDaysFormat() = 19307 (2022-11-10), want 19307 (2022-11-11)
// --- FAIL: TestSlowTimestampToDays/1668124800_2022-11-10T19:00:00-05:00,_UTC_2022-11-11T00:00:00Z_[2] (0.00s)
// date_test.go:62: TimestampDaysFormat() = 19306 (2022-11-09), want 19307 (2022-11-11)
// --- FAIL: TestSlowTimestampToDays/1668142799_2022-11-10T23:59:59-05:00,_UTC_2022-11-11T04:59:59Z_[3] (0.00s)
// date_test.go:62: TimestampDaysFormat() = 19306 (2022-11-09), want 19307 (2022-11-11)
// --- FAIL: TestSlowTimestampToDays/1650776160_2022-04-23T23:56:00-05:00,_UTC_2022-04-24T04:56:00Z_[4] (0.00s)
// date_test.go:62: TimestampDaysFormat() = 19105 (2022-04-22), want 19106 (2022-04-24)
//
// --- FAIL: TestTimestampToDays (0.00s)
func TestSlowTimestampToDays(t *testing.T) {
if !runBroken {
t.Log("skip broken test, set RUN_BRKEN_TESTS=1 for run")
return
}
tests := []struct {
ts uint32
want uint16
wantStr string
}{
{
ts: 1668106870, // 2022-11-11 00:01:10 +05:00 ; 2022-11-10 19:01:10 UTC
// select toDate(1650776160,'UTC')
// 2022-11-10
want: 19306,
wantStr: "2022-11-10",
},
{
ts: 1668193200, // 2022-11-12 00:00:00 +05:00 ; 2022-11-11 19:00:00 UTC
// SELECT Date(19307)
// 2022-11-11
want: 19307,
wantStr: "2022-11-11",
},
{
ts: 1668124800, // 2022-11-11 00:00:00 UTC
want: 19307,
wantStr: "2022-11-11",
},
{
ts: 1668142799, // 2022-11-10 23:59:59 -05:00; 2022-11-11 04:59:59 UTC
want: 19307,
wantStr: "2022-11-11",
},
{
ts: 1650776160, // graphite-clickhouse issue #184, graphite-clickhouse in UTC, clickhouse in PDT(UTC-7)
// 2022-04-24 4:56:00
// select toDate(1650776160,'UTC')
// 2022-04-24
// select toDate(1650776160,'Etc/GMT+7')
// 2022-04-23
want: 19106,
wantStr: "2022-04-24",
},
}
for i, tt := range tests {
t.Run(strconv.FormatInt(int64(tt.ts), 10)+" "+time.Unix(int64(tt.ts), 0).Format(time.RFC3339)+", UTC "+time.Unix(int64(tt.ts), 0).UTC().Format(time.RFC3339)+" ["+strconv.Itoa(i)+"]", func(t *testing.T) {
got := SlowTimestampToDays(tt.ts)
// gotStr := dayStart.Add(time.Duration(int64(got) * 24 * 3600 * 1e9)).Format("2006-01-02")
gotStr := time.Unix(int64(got)*24*3600, 0).Format("2006-01-02")
if gotStr != tt.wantStr || got != tt.want {
t.Errorf("TimestampDaysFormat() = %v (%s), want %v (%s)", got, gotStr, tt.want, tt.wantStr)
}
convStr := UTCTimestampToDaysFormat(uint32(tt.want) * 24 * 3600)
if convStr != tt.wantStr {
t.Errorf("conversion got %s, want %s", convStr, tt.wantStr)
}
})
}
}

// TimestampToDays is broken on some cases like SlowTimestampToDays
func TestTimestampToDays(t *testing.T) {
ts := uint32(0)
end := uint32(time.Now().Unix()) + 473040000 // +15 years
for ts < end {
d1 := SlowTimestampToDays(ts)
d2 := TimestampToDays(ts)
if d1 != d2 {
t.FailNow()
t.Fatalf("SlowTimestampToDays(%d)=%d, TimestampToDays(%d)=%d", ts, d1, ts, d2)
}
ts1 := time.Unix(int64(d1)*86400, 0)
ds1 := time.Date(ts1.Year(), ts1.Month(), ts1.Day(), 0, 0, 0, 0, time.UTC).Format("2006-01-02")
ds2 := TimestampToDaysFormat(int64(d1) * 86400)
if ds1 != ds2 {
t.Fatalf("SlowTimestampToDays(%d)=%d, format error %s %s", ts, d1, ds1, ds2)
}

ts += 780 // step 13 minutes
}
}

func BenchmarkTimestampToDays(b *testing.B) {
timestamp := uint32(time.Now().Unix())
x := SlowTimestampToDays(timestamp)
Expand All @@ -39,3 +147,58 @@ func BenchmarkSlowTimestampToDays(b *testing.B) {
}
}
}

func TestUTCTimestampToDays(t *testing.T) {
tests := []struct {
ts uint32
want uint16
wantStr string
}{
{
ts: 1668106870, // 2022-11-11 00:01:10 +05:00 ; 2022-11-10 19:01:10 UTC
// select toDate(1650776160,'UTC')
// 2022-11-10
want: 19306,
wantStr: "2022-11-10",
},
{
ts: 1668193200, // 2022-11-12 00:00:00 +05:00 ; 2022-11-11 19:00:00 UTC
// SELECT Date(19307)
// 2022-11-11
// SELECT toDate(1668193200, 'UTC')
// 2022-11-11
want: 19307,
wantStr: "2022-11-11",
},
{
ts: 1668124800, // 2022-11-11 00:00:00 UTC
want: 19307,
wantStr: "2022-11-11",
},
{
ts: 1668142799, // 2022-11-10 23:59:59 -05:00; 2022-11-11 04:59:59 UTC
want: 19307,
wantStr: "2022-11-11",
},
{
ts: 1650776160, // graphite-clickhouse issue #184, graphite-clickhouse in UTC, clickhouse in PDT(UTC-7)
// 2022-04-24 4:56:00
// select toDate(1650776160,'UTC')
// 2022-04-24
// select toDate(1650776160,'Etc/GMT+7')
// 2022-04-23
want: 19106,
wantStr: "2022-04-24",
},
}
for i, tt := range tests {
t.Run(strconv.FormatInt(int64(tt.ts), 10)+" "+time.Unix(int64(tt.ts), 0).Format(time.RFC3339)+", UTC "+time.Unix(int64(tt.ts), 0).UTC().Format(time.RFC3339)+" ["+strconv.Itoa(i)+"]", func(t *testing.T) {
got := UTCTimestampToDays(tt.ts)
// gotStr := dayStart.Add(time.Duration(int64(got) * 24 * 3600 * 1e9)).Format("2006-01-02")
gotStr := UTCTimestampToDaysFormat(uint32(got) * 86400)
if gotStr != tt.wantStr || got != tt.want {
t.Errorf("TimestampDaysFormat() = %v (%s), want %v (%s)", got, gotStr, tt.want, tt.wantStr)
}
})
}
}

0 comments on commit 1a33775

Please sign in to comment.