wp
This commit is contained in:
@@ -84,7 +84,7 @@ func (s *Database) processRequest(conn net.Conn, r *bufreader.BufferedReader) (e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("proto.ReadAddMetricReq: %s", err)
|
return fmt.Errorf("proto.ReadAddMetricReq: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Println("ReadAddMetricReq:", req)
|
//fmt.Println("ReadAddMetricReq:", req)
|
||||||
reply(conn, s.AddMetric(req))
|
reply(conn, s.AddMetric(req))
|
||||||
|
|
||||||
case proto.TypeDeleteMetric:
|
case proto.TypeDeleteMetric:
|
||||||
@@ -534,8 +534,11 @@ func (s *Database) rangeScan(req rangeScanReq) error {
|
|||||||
LastPageNo: result.LastPageNo,
|
LastPageNo: result.LastPageNo,
|
||||||
Since: req.Since,
|
Since: req.Since,
|
||||||
})
|
})
|
||||||
//s.metricRUnlock(req.MetricID) fix release unlock
|
s.workerInbox.Push(worker.ReleaseRLock{
|
||||||
|
MetricID: req.MetricID,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
s.logger.Printf("ContinueRangeScan: %s\n", err)
|
||||||
reply(req.Conn, proto.ErrUnexpected)
|
reply(req.Conn, proto.ErrUnexpected)
|
||||||
} else {
|
} else {
|
||||||
req.ResponseWriter.Close()
|
req.ResponseWriter.Close()
|
||||||
@@ -550,8 +553,11 @@ func (s *Database) rangeScan(req rangeScanReq) error {
|
|||||||
LastPageNo: result.LastPageNo,
|
LastPageNo: result.LastPageNo,
|
||||||
IsDataPage: result.IsDataPage,
|
IsDataPage: result.IsDataPage,
|
||||||
})
|
})
|
||||||
//s.metricRUnlock(req.MetricID) // fix release
|
s.workerInbox.Push(worker.ReleaseRLock{
|
||||||
|
MetricID: req.MetricID,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
s.logger.Printf("RangeScan: %s\n", err)
|
||||||
reply(req.Conn, proto.ErrUnexpected)
|
reply(req.Conn, proto.ErrUnexpected)
|
||||||
} else {
|
} else {
|
||||||
req.ResponseWriter.Close()
|
req.ResponseWriter.Close()
|
||||||
@@ -595,8 +601,11 @@ func (s *Database) fullScan(req fullScanReq) error {
|
|||||||
ResponseWriter: req.ResponseWriter,
|
ResponseWriter: req.ResponseWriter,
|
||||||
LastPageNo: result.LastPageNo,
|
LastPageNo: result.LastPageNo,
|
||||||
})
|
})
|
||||||
//s.worker.AddJobToQueue(req.MetricID) // FIX release rlock
|
s.workerInbox.Push(worker.ReleaseRLock{
|
||||||
|
MetricID: req.MetricID,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
s.logger.Printf("ContinueFullScan: %s\n", err)
|
||||||
reply(req.Conn, proto.ErrUnexpected)
|
reply(req.Conn, proto.ErrUnexpected)
|
||||||
} else {
|
} else {
|
||||||
req.ResponseWriter.Close()
|
req.ResponseWriter.Close()
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ func New(opt Options) (_ *Database, err error) {
|
|||||||
return nil, fmt.Errorf("recovery.Recovery: %s", err)
|
return nil, fmt.Errorf("recovery.Recovery: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%#v\n", recoveryReport)
|
//fmt.Printf("%#v\n", recoveryReport)
|
||||||
|
|
||||||
storageInbox := inbox.New()
|
storageInbox := inbox.New()
|
||||||
s.workerInbox = inbox.New()
|
s.workerInbox = inbox.New()
|
||||||
@@ -180,6 +180,7 @@ type ContinueFullScanReq struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Database) ContinueFullScan(req ContinueFullScanReq) error {
|
func (s *Database) ContinueFullScan(req ContinueFullScanReq) error {
|
||||||
|
//fmt.Printf("ContinueFullScan from page %d\n", req.LastPageNo)
|
||||||
buf, err := s.dataCache.FetchPage(req.LastPageNo)
|
buf, err := s.dataCache.FetchPage(req.LastPageNo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("dataCache.FetchPage(%d): %s", req.LastPageNo, err)
|
return fmt.Errorf("dataCache.FetchPage(%d): %s", req.LastPageNo, err)
|
||||||
@@ -196,6 +197,7 @@ func (s *Database) ContinueFullScan(req ContinueFullScanReq) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer treeCursor.Close()
|
defer treeCursor.Close()
|
||||||
|
idx := 0
|
||||||
for {
|
for {
|
||||||
timestamp, value, done, err := treeCursor.Prev()
|
timestamp, value, done, err := treeCursor.Prev()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -204,7 +206,9 @@ func (s *Database) ContinueFullScan(req ContinueFullScanReq) error {
|
|||||||
if done {
|
if done {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
//fmt.Printf(" %d: %s => %.2f\n", idx, formatTime(timestamp), value)
|
||||||
req.ResponseWriter.Feed(timestamp, value)
|
req.ResponseWriter.Feed(timestamp, value)
|
||||||
|
idx++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,15 +330,15 @@ func (s *Database) findDataPage(foundPageNo uint32, timestamp uint32) (uint32, [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// type PathLeg struct {
|
type PathLeg struct {
|
||||||
// PageNo uint32
|
PageNo uint32
|
||||||
// Data []byte
|
Data []byte
|
||||||
// }
|
}
|
||||||
|
|
||||||
// type PathToDataPage struct {
|
type PathToDataPage struct {
|
||||||
// Legs []PathLeg
|
Legs []PathLeg
|
||||||
// LastPageNo uint32
|
LastPageNo uint32
|
||||||
// }
|
}
|
||||||
|
|
||||||
// func (s *Database) FindPathToLastPage(rootPageNo uint32) (_ PathToDataPage, err error) {
|
// func (s *Database) FindPathToLastPage(rootPageNo uint32) (_ PathToDataPage, err error) {
|
||||||
// // var (
|
// // var (
|
||||||
@@ -381,82 +385,215 @@ func (s *Database) findDataPage(foundPageNo uint32, timestamp uint32) (uint32, [
|
|||||||
// s.mutex.Unlock()
|
// s.mutex.Unlock()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
type Level struct {
|
||||||
|
Idx int
|
||||||
|
PageNumbers []uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Database) GetAllPages(pageNumbers []uint32) ([]uint32, []uint32, error) {
|
||||||
|
levels := []*Level{
|
||||||
|
{
|
||||||
|
PageNumbers: pageNumbers,
|
||||||
|
Idx: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return s.collectPages(levels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Database) collectPages(levels []*Level) (indexPageNumbers []uint32, dataPageNumbers []uint32, err error) {
|
||||||
|
var (
|
||||||
|
buf []byte
|
||||||
|
pageNumbers []uint32
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
if len(levels) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
lastIdx = len(levels) - 1
|
||||||
|
level = levels[lastIdx]
|
||||||
|
pageNo = level.PageNumbers[level.Idx]
|
||||||
|
)
|
||||||
|
if level.Idx < len(level.PageNumbers) {
|
||||||
|
buf, err = s.indexCache.FetchPage(pageNo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("indexCache.FetchPage(%d): %s", pageNo, err)
|
||||||
|
}
|
||||||
|
pageNumbers = storage.ListPageNumbers(buf)
|
||||||
|
s.indexCache.ReleasePage(pageNo)
|
||||||
|
|
||||||
|
if storage.IsZeroLevelPage(buf) {
|
||||||
|
dataPageNumbers = append(dataPageNumbers, pageNumbers...)
|
||||||
|
} else {
|
||||||
|
levels = append(levels, &Level{
|
||||||
|
PageNumbers: pageNumbers,
|
||||||
|
})
|
||||||
|
indexPageNumbers = append(indexPageNumbers, pageNumbers...)
|
||||||
|
}
|
||||||
|
level.Idx++
|
||||||
|
} else {
|
||||||
|
levels = levels[:lastIdx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteSinceReport struct {
|
||||||
|
IndexPageNumbers []uint32
|
||||||
|
DataPageNumbers []uint32
|
||||||
|
DataPage []byte
|
||||||
|
IndexLevelTails []storage.IndexLevelTail
|
||||||
|
}
|
||||||
|
|
||||||
// type Level struct {
|
// type Level struct {
|
||||||
// PageNo uint32
|
// Idx int
|
||||||
// PageData []byte
|
// PageNumbers []uint32
|
||||||
// Idx int
|
|
||||||
// ChildQty int
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// func (s *Atree) GetAllPages(rootPageNo uint32) (_ []uint32, err error) {
|
// fix - find by (since - 1) ?
|
||||||
// // var (
|
|
||||||
// // pageNumbers []uint32
|
|
||||||
// // levels []*Level
|
|
||||||
// // )
|
|
||||||
|
|
||||||
// // buf, err := s.fetchIndexPage(rootPageNo)
|
// [since, ...]
|
||||||
// // if err != nil {
|
func (s *Database) DeleteSince(since uint32, indexLevelTail storage.IndexLevelTail) (_ DeleteSinceReport, err error) {
|
||||||
// // return nil, fmt.Errorf("fetchIndexPage(%d): %s", rootPageNo, err)
|
var (
|
||||||
// // }
|
indexPageNumbers []uint32
|
||||||
// // pageNumbers = append(pageNumbers, rootPageNo)
|
dataPageNumbers []uint32
|
||||||
|
dataPage []byte
|
||||||
|
buf []byte
|
||||||
|
indexLevelTails []storage.IndexLevelTail
|
||||||
|
foundPageNo uint32
|
||||||
|
)
|
||||||
|
|
||||||
// // // if buf[isDataPageNumbersIdx] == 1 {
|
result := storage.DeleteSinceOnIndexTail(indexLevelTail, since)
|
||||||
// // // pageNumbers := listPageNumbers(buf)
|
//
|
||||||
// // // dataPages = append(dataPages, pageNumbers...)
|
indexLevelTails = append(indexLevelTails, storage.IndexLevelTail{
|
||||||
|
Buffer: indexLevelTail.Buffer,
|
||||||
|
RecordsCount: result.RecordsCount,
|
||||||
|
})
|
||||||
|
levels := []*Level{
|
||||||
|
{
|
||||||
|
PageNumbers: result.PageNumbers,
|
||||||
|
Idx: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
foundPageNo = result.PageNumbers[0]
|
||||||
|
for {
|
||||||
|
buf, err = s.indexCache.FetchPage(foundPageNo)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("indexCache.FetchPage(%d): %s", foundPageNo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
toReleaseIndexPageNo := foundPageNo
|
||||||
|
result := storage.DeleteSinceOnIndexPage(buf, since)
|
||||||
|
s.indexCache.ReleasePage(toReleaseIndexPageNo)
|
||||||
|
//
|
||||||
|
foundPageNo = result.PageNumbers[0]
|
||||||
|
|
||||||
// // // s.releasePage(rootPageNo)
|
levels = append(levels, &Level{
|
||||||
|
PageNumbers: result.PageNumbers,
|
||||||
|
Idx: 1, // 1st - found pageNo
|
||||||
|
})
|
||||||
|
|
||||||
// // // return PageLists{
|
indexLevelTails = append(indexLevelTails, storage.IndexLevelTail{
|
||||||
// // // DataPages: dataPages,
|
Buffer: result.Buffer,
|
||||||
// // // IndexPages: indexPages,
|
RecordsCount: result.RecordsCount,
|
||||||
// // // }, nil
|
})
|
||||||
// // // }
|
|
||||||
|
|
||||||
// // childQty, _ := bin.GetUint16(buf[indexRecordsQtyIdx:])
|
if storage.IsZeroLevelPage(buf) {
|
||||||
|
dataPage, err = s.dataCache.FetchPage(foundPageNo)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("dataCache.FetchPage(%d): %s", foundPageNo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dataPageNumbers = append(dataPageNumbers, result.PageNumbers...)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
indexPageNumbers = append(indexPageNumbers, result.PageNumbers...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
indexPageNumbers, dataPageNumbers, err = s.collectPages(levels)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return DeleteSinceReport{
|
||||||
|
IndexPageNumbers: indexPageNumbers,
|
||||||
|
DataPageNumbers: dataPageNumbers,
|
||||||
|
DataPage: dataPage,
|
||||||
|
IndexLevelTails: indexLevelTails,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// // levels = append(levels, &Level{
|
// [..., until]
|
||||||
// // PageNo: rootPageNo,
|
func (s *Database) DeleteUntil(since uint32, indexLevelTail storage.IndexLevelTail) (_ DeleteSinceReport, err error) {
|
||||||
// // PageData: buf,
|
var (
|
||||||
// // Idx: 0,
|
indexPageNumbers []uint32
|
||||||
// // ChildQty: int(childQty),
|
dataPageNumbers []uint32
|
||||||
// // })
|
dataPage []byte
|
||||||
|
buf []byte
|
||||||
|
indexLevelTails []storage.IndexLevelTail
|
||||||
|
foundPageNo uint32
|
||||||
|
)
|
||||||
|
|
||||||
// // for {
|
result := storage.DeleteSinceOnIndexTail(indexLevelTail, since)
|
||||||
// // if len(levels) == 0 {
|
//
|
||||||
// // return pageNumbers, nil
|
indexLevelTails = append(indexLevelTails, storage.IndexLevelTail{
|
||||||
// // }
|
Buffer: indexLevelTail.Buffer,
|
||||||
|
RecordsCount: result.RecordsCount,
|
||||||
|
})
|
||||||
|
levels := []*Level{
|
||||||
|
{
|
||||||
|
PageNumbers: result.PageNumbers,
|
||||||
|
Idx: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
foundPageNo = result.PageNumbers[0]
|
||||||
|
for {
|
||||||
|
buf, err = s.indexCache.FetchPage(foundPageNo)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("indexCache.FetchPage(%d): %s", foundPageNo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
toReleaseIndexPageNo := foundPageNo
|
||||||
|
result := storage.DeleteSinceOnIndexPage(buf, since)
|
||||||
|
s.indexCache.ReleasePage(toReleaseIndexPageNo)
|
||||||
|
//
|
||||||
|
foundPageNo = result.PageNumbers[0]
|
||||||
|
|
||||||
// // lastIdx := len(levels) - 1
|
levels = append(levels, &Level{
|
||||||
// // level := levels[lastIdx]
|
PageNumbers: result.PageNumbers,
|
||||||
|
Idx: 1, // 1st - found pageNo
|
||||||
|
})
|
||||||
|
|
||||||
// // if level.Idx < level.ChildQty {
|
indexLevelTails = append(indexLevelTails, storage.IndexLevelTail{
|
||||||
// // pageNo := getPageNo(level.PageData, level.Idx)
|
Buffer: result.Buffer,
|
||||||
// // level.Idx++
|
RecordsCount: result.RecordsCount,
|
||||||
|
})
|
||||||
|
|
||||||
// // var buf []byte
|
if storage.IsZeroLevelPage(buf) {
|
||||||
// // buf, err = s.fetchPage(pageNo)
|
dataPage, err = s.dataCache.FetchPage(foundPageNo)
|
||||||
// // if err != nil {
|
if err != nil {
|
||||||
// // return nil, fmt.Errorf("fetchPage(%d): %s", pageNo, err)
|
err = fmt.Errorf("dataCache.FetchPage(%d): %s", foundPageNo, err)
|
||||||
// // }
|
return
|
||||||
// // pageNumbers = append(pageNumbers, pageNo)
|
}
|
||||||
|
dataPageNumbers = append(dataPageNumbers, result.PageNumbers...)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
indexPageNumbers = append(indexPageNumbers, result.PageNumbers...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
indexPageNumbers, dataPageNumbers, err = s.collectPages(levels)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return DeleteSinceReport{
|
||||||
|
IndexPageNumbers: indexPageNumbers,
|
||||||
|
DataPageNumbers: dataPageNumbers,
|
||||||
|
DataPage: dataPage,
|
||||||
|
IndexLevelTails: indexLevelTails,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// // if buf[pageTypeIdx] == PageTypeData {
|
const datetimeLayout = "2006-01-02 15:04:05"
|
||||||
// // //pageNumbers := listPageNumbers(buf)
|
|
||||||
// // //dataPages = append(dataPages, pageNumbers...)
|
func formatTime(timestamp uint32) string {
|
||||||
// // s.releasePage(pageNo)
|
tm := time.Unix(int64(timestamp), 0)
|
||||||
// // } else {
|
return tm.Format(datetimeLayout)
|
||||||
// // childQty, _ = bin.GetUint16(buf[indexRecordsQtyIdx:])
|
}
|
||||||
// // levels = append(levels, &Level{
|
|
||||||
// // PageNo: pageNo,
|
|
||||||
// // PageData: buf,
|
|
||||||
// // Idx: 0,
|
|
||||||
// // ChildQty: int(childQty),
|
|
||||||
// // })
|
|
||||||
// // }
|
|
||||||
// // } else {
|
|
||||||
// // s.releasePage(level.PageNo)
|
|
||||||
// // levels = levels[:lastIdx]
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|||||||
421
enc/enc_test.go
421
enc/enc_test.go
@@ -6,9 +6,11 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
bin "gordenko.dev/dima/bin/little"
|
bin "gordenko.dev/dima/bin/little"
|
||||||
"gordenko.dev/dima/qb"
|
"gordenko.dev/dima/qb"
|
||||||
|
"gordenko.dev/dima/textutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func equalFloatSlices(a, b []float64, epsilon float64) bool {
|
func equalFloatSlices(a, b []float64, epsilon float64) bool {
|
||||||
@@ -21,23 +23,23 @@ func equalFloatSlices(a, b []float64, epsilon float64) bool {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
cumulativeTestCases = []struct {
|
cumulativeTestCases = []struct {
|
||||||
Nums []float64
|
Nums []float64
|
||||||
Name string
|
Name string
|
||||||
Offset int
|
RewindOffset int
|
||||||
ChangeSize int
|
ChangeSize int
|
||||||
Buf []byte
|
Buf []byte
|
||||||
BaseValue float64
|
BaseValue float64
|
||||||
LastDelta uint64
|
LastDelta uint64
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Nums: []float64{
|
Nums: []float64{
|
||||||
0,
|
0,
|
||||||
},
|
},
|
||||||
Name: "add 1st value zero",
|
Name: "add 1st value zero",
|
||||||
Offset: 0,
|
RewindOffset: 0,
|
||||||
ChangeSize: 3,
|
ChangeSize: 3,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x8f, // base value
|
0x80, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
0x80, // literal (len = 1)
|
0x80, // literal (len = 1)
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@@ -49,9 +51,9 @@ var (
|
|||||||
Nums: []float64{
|
Nums: []float64{
|
||||||
1.5,
|
1.5,
|
||||||
},
|
},
|
||||||
Name: "add 1st value non zero",
|
Name: "add 1st value non zero",
|
||||||
Offset: 0,
|
RewindOffset: 0,
|
||||||
ChangeSize: 3,
|
ChangeSize: 3,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x8f, // base value
|
0x8f, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -66,9 +68,9 @@ var (
|
|||||||
1.5,
|
1.5,
|
||||||
1.5,
|
1.5,
|
||||||
},
|
},
|
||||||
Name: "literal switch to run",
|
Name: "literal switch to run",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 1,
|
ChangeSize: 1,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x8f, // base value
|
0x8f, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -84,9 +86,9 @@ var (
|
|||||||
1.6,
|
1.6,
|
||||||
1.6,
|
1.6,
|
||||||
},
|
},
|
||||||
Name: "literal decrease by 1 and switch to run",
|
Name: "literal decrease by 1 and switch to run",
|
||||||
Offset: 2,
|
RewindOffset: 2,
|
||||||
ChangeSize: 3,
|
ChangeSize: 3,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x8f, // base value
|
0x8f, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -104,9 +106,9 @@ var (
|
|||||||
1.5,
|
1.5,
|
||||||
1.5,
|
1.5,
|
||||||
},
|
},
|
||||||
Name: "increment run",
|
Name: "increment run",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 1,
|
ChangeSize: 1,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x8f, // base value
|
0x8f, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -123,9 +125,9 @@ var (
|
|||||||
1.5,
|
1.5,
|
||||||
1.6,
|
1.6,
|
||||||
},
|
},
|
||||||
Name: "run switch to literal",
|
Name: "run switch to literal",
|
||||||
Offset: 0,
|
RewindOffset: 0,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x8f, // base value
|
0x8f, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -138,10 +140,10 @@ var (
|
|||||||
LastDelta: 1,
|
LastDelta: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Nums: repeatFloat64(1.5, 129),
|
Nums: repeatFloat64(1.5, 129),
|
||||||
Name: "increment run to full fill h-byte",
|
Name: "increment run to full fill h-byte",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 1,
|
ChangeSize: 1,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x8f, // base value
|
0x8f, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -152,10 +154,10 @@ var (
|
|||||||
LastDelta: 0,
|
LastDelta: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Nums: repeatFloat64(1.5, 130),
|
Nums: repeatFloat64(1.5, 130),
|
||||||
Name: "run switch to literal after h-byte overflow",
|
Name: "run switch to literal after h-byte overflow",
|
||||||
Offset: 0,
|
RewindOffset: 0,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x8f, // base value
|
0x8f, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -173,9 +175,9 @@ var (
|
|||||||
1.6,
|
1.6,
|
||||||
1.7,
|
1.7,
|
||||||
},
|
},
|
||||||
Name: "increment literal",
|
Name: "increment literal",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x8f, // base value
|
0x8f, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -203,11 +205,11 @@ func TestCumulativeDeltaCompressor(t *testing.T) {
|
|||||||
c := NewValueDeltaCompressor(metricType, fracDigits, buf, payloadSize)
|
c := NewValueDeltaCompressor(metricType, fracDigits, buf, payloadSize)
|
||||||
for _, num := range testCase.Nums {
|
for _, num := range testCase.Nums {
|
||||||
report = c.Evaluate(tmp, num)
|
report = c.Evaluate(tmp, num)
|
||||||
c.Append(report.Offset, tmp[:report.ChangeSize], num, report.Delta)
|
c.Append(report.RewindOffset, tmp[:report.ChangeSize], num, report.Delta)
|
||||||
}
|
}
|
||||||
if report.Offset != testCase.Offset {
|
if report.RewindOffset != testCase.RewindOffset {
|
||||||
t.Fatalf("%s: got offset %d are not equal expected %d",
|
t.Fatalf("%s: got offset %d are not equal expected %d",
|
||||||
testCase.Name, report.Offset, testCase.Offset)
|
testCase.Name, report.RewindOffset, testCase.RewindOffset)
|
||||||
}
|
}
|
||||||
if report.ChangeSize != testCase.ChangeSize {
|
if report.ChangeSize != testCase.ChangeSize {
|
||||||
t.Fatalf("%s: got changeSize %d are not equal expected %d",
|
t.Fatalf("%s: got changeSize %d are not equal expected %d",
|
||||||
@@ -241,7 +243,7 @@ func TestRestoreCumulativeDeltaCompressor(t *testing.T) {
|
|||||||
c := NewValueDeltaCompressor(metricType, fracDigits, buf, payloadSize)
|
c := NewValueDeltaCompressor(metricType, fracDigits, buf, payloadSize)
|
||||||
for _, num := range testCase.Nums {
|
for _, num := range testCase.Nums {
|
||||||
report = c.Evaluate(tmp, num)
|
report = c.Evaluate(tmp, num)
|
||||||
c.Append(report.Offset, tmp[:report.ChangeSize], num, report.Delta)
|
c.Append(report.RewindOffset, tmp[:report.ChangeSize], num, report.Delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
restored := NewValueDeltaCompressor(metricType, fracDigits, buf, c.Size())
|
restored := NewValueDeltaCompressor(metricType, fracDigits, buf, c.Size())
|
||||||
@@ -270,7 +272,7 @@ func TestCumulativeDeltaDecompressorFromState(t *testing.T) {
|
|||||||
c := NewValueDeltaCompressor(metricType, fracDigits, buf, payloadSize)
|
c := NewValueDeltaCompressor(metricType, fracDigits, buf, payloadSize)
|
||||||
for _, num := range testCase.Nums {
|
for _, num := range testCase.Nums {
|
||||||
report := c.Evaluate(tmp, num)
|
report := c.Evaluate(tmp, num)
|
||||||
c.Append(report.Offset, tmp[:report.ChangeSize], num, report.Delta)
|
c.Append(report.RewindOffset, tmp[:report.ChangeSize], num, report.Delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
d := c.CreateDecompressor(metricType, fracDigits)
|
d := c.CreateDecompressor(metricType, fracDigits)
|
||||||
@@ -304,7 +306,7 @@ func TestCumulativeDeltaDecompressorFromEnd(t *testing.T) {
|
|||||||
c := NewValueDeltaCompressor(metricType, fracDigits, buf, payloadSize)
|
c := NewValueDeltaCompressor(metricType, fracDigits, buf, payloadSize)
|
||||||
for _, num := range testCase.Nums {
|
for _, num := range testCase.Nums {
|
||||||
report := c.Evaluate(tmp, num)
|
report := c.Evaluate(tmp, num)
|
||||||
c.Append(report.Offset, tmp[:report.ChangeSize], num, report.Delta)
|
c.Append(report.RewindOffset, tmp[:report.ChangeSize], num, report.Delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
d := NewValueDeltaDecompressor(metricType, fracDigits)
|
d := NewValueDeltaDecompressor(metricType, fracDigits)
|
||||||
@@ -330,21 +332,21 @@ func TestCumulativeDeltaDecompressorFromEnd(t *testing.T) {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
instantTestCases = []struct {
|
instantTestCases = []struct {
|
||||||
Nums []float64
|
Nums []float64
|
||||||
Name string
|
Name string
|
||||||
Offset int
|
RewindOffset int
|
||||||
ChangeSize int
|
ChangeSize int
|
||||||
Buf []byte
|
Buf []byte
|
||||||
BaseValue float64
|
BaseValue float64
|
||||||
LastDelta uint64
|
LastDelta uint64
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Nums: []float64{
|
Nums: []float64{
|
||||||
1.5,
|
1.5,
|
||||||
},
|
},
|
||||||
Name: "add 1st value",
|
Name: "add 1st value",
|
||||||
Offset: 0,
|
RewindOffset: 0,
|
||||||
ChangeSize: 3,
|
ChangeSize: 3,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9e, // base value
|
0x9e, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -358,9 +360,9 @@ var (
|
|||||||
Nums: []float64{
|
Nums: []float64{
|
||||||
-1.5,
|
-1.5,
|
||||||
},
|
},
|
||||||
Name: "add 1st value (negative)",
|
Name: "add 1st value (negative)",
|
||||||
Offset: 0,
|
RewindOffset: 0,
|
||||||
ChangeSize: 3,
|
ChangeSize: 3,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9d, // base value
|
0x9d, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -375,9 +377,9 @@ var (
|
|||||||
1.5,
|
1.5,
|
||||||
1.5,
|
1.5,
|
||||||
},
|
},
|
||||||
Name: "literal switch to run",
|
Name: "literal switch to run",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 1,
|
ChangeSize: 1,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9e, // base value
|
0x9e, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -392,9 +394,9 @@ var (
|
|||||||
-1.5,
|
-1.5,
|
||||||
-1.5,
|
-1.5,
|
||||||
},
|
},
|
||||||
Name: "literal switch to run (negative)",
|
Name: "literal switch to run (negative)",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 1,
|
ChangeSize: 1,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9d, // base value
|
0x9d, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -410,9 +412,9 @@ var (
|
|||||||
1.6,
|
1.6,
|
||||||
1.6,
|
1.6,
|
||||||
},
|
},
|
||||||
Name: "literal decrease by 1 and switch to run",
|
Name: "literal decrease by 1 and switch to run",
|
||||||
Offset: 2,
|
RewindOffset: 2,
|
||||||
ChangeSize: 3,
|
ChangeSize: 3,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9e, // base value
|
0x9e, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -430,9 +432,9 @@ var (
|
|||||||
-1.6,
|
-1.6,
|
||||||
-1.6,
|
-1.6,
|
||||||
},
|
},
|
||||||
Name: "literal decrease by 1 and switch to run (negative)",
|
Name: "literal decrease by 1 and switch to run (negative)",
|
||||||
Offset: 2,
|
RewindOffset: 2,
|
||||||
ChangeSize: 3,
|
ChangeSize: 3,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9d, // base value
|
0x9d, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -450,9 +452,9 @@ var (
|
|||||||
1.4,
|
1.4,
|
||||||
1.4,
|
1.4,
|
||||||
},
|
},
|
||||||
Name: "literal decrease by 1 and switch to run (negative delta)",
|
Name: "literal decrease by 1 and switch to run (negative delta)",
|
||||||
Offset: 2,
|
RewindOffset: 2,
|
||||||
ChangeSize: 3,
|
ChangeSize: 3,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9e, // base value
|
0x9e, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -470,9 +472,9 @@ var (
|
|||||||
-1.4,
|
-1.4,
|
||||||
-1.4,
|
-1.4,
|
||||||
},
|
},
|
||||||
Name: "literal decrease by 1 and switch to run (positive delta, negative)",
|
Name: "literal decrease by 1 and switch to run (positive delta, negative)",
|
||||||
Offset: 2,
|
RewindOffset: 2,
|
||||||
ChangeSize: 3,
|
ChangeSize: 3,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9d, // base value
|
0x9d, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -490,9 +492,9 @@ var (
|
|||||||
1.5,
|
1.5,
|
||||||
1.5,
|
1.5,
|
||||||
},
|
},
|
||||||
Name: "increment run",
|
Name: "increment run",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 1,
|
ChangeSize: 1,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9e, // base value
|
0x9e, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -508,9 +510,9 @@ var (
|
|||||||
-1.5,
|
-1.5,
|
||||||
-1.5,
|
-1.5,
|
||||||
},
|
},
|
||||||
Name: "increment run (negative)",
|
Name: "increment run (negative)",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 1,
|
ChangeSize: 1,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9d, // base value
|
0x9d, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -527,9 +529,9 @@ var (
|
|||||||
1.5,
|
1.5,
|
||||||
1.6,
|
1.6,
|
||||||
},
|
},
|
||||||
Name: "run switch to literal",
|
Name: "run switch to literal",
|
||||||
Offset: 0,
|
RewindOffset: 0,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9e, // base value
|
0x9e, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -548,9 +550,9 @@ var (
|
|||||||
-1.5,
|
-1.5,
|
||||||
-1.6,
|
-1.6,
|
||||||
},
|
},
|
||||||
Name: "run switch to literal (negative)",
|
Name: "run switch to literal (negative)",
|
||||||
Offset: 0,
|
RewindOffset: 0,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9d, // base value
|
0x9d, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -569,9 +571,9 @@ var (
|
|||||||
1.5,
|
1.5,
|
||||||
1.4,
|
1.4,
|
||||||
},
|
},
|
||||||
Name: "run switch to literal (negative delta)",
|
Name: "run switch to literal (negative delta)",
|
||||||
Offset: 0,
|
RewindOffset: 0,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9e, // base value
|
0x9e, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -590,9 +592,9 @@ var (
|
|||||||
-1.5,
|
-1.5,
|
||||||
-1.4,
|
-1.4,
|
||||||
},
|
},
|
||||||
Name: "run switch to literal (positive delta, negative version)",
|
Name: "run switch to literal (positive delta, negative version)",
|
||||||
Offset: 0,
|
RewindOffset: 0,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9d, // base value
|
0x9d, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -605,10 +607,10 @@ var (
|
|||||||
LastDelta: bin.EncodeZigZag(1),
|
LastDelta: bin.EncodeZigZag(1),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Nums: repeatFloat64(1.5, 129),
|
Nums: repeatFloat64(1.5, 129),
|
||||||
Name: "increment run to full fill h-byte",
|
Name: "increment run to full fill h-byte",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 1,
|
ChangeSize: 1,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9e, // base value
|
0x9e, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -619,10 +621,10 @@ var (
|
|||||||
LastDelta: 0,
|
LastDelta: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Nums: repeatFloat64(-1.5, 129),
|
Nums: repeatFloat64(-1.5, 129),
|
||||||
Name: "increment run to full fill h-byte (negative version)",
|
Name: "increment run to full fill h-byte (negative version)",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 1,
|
ChangeSize: 1,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9d, // base value
|
0x9d, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -633,10 +635,10 @@ var (
|
|||||||
LastDelta: 0,
|
LastDelta: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Nums: repeatFloat64(1.5, 130),
|
Nums: repeatFloat64(1.5, 130),
|
||||||
Name: "run switch to literal after h-byte overflow",
|
Name: "run switch to literal after h-byte overflow",
|
||||||
Offset: 0,
|
RewindOffset: 0,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9e, // base value
|
0x9e, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -649,10 +651,10 @@ var (
|
|||||||
LastDelta: 0,
|
LastDelta: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Nums: repeatFloat64(-1.5, 130),
|
Nums: repeatFloat64(-1.5, 130),
|
||||||
Name: "run switch to literal after h-byte overflow (negative version)",
|
Name: "run switch to literal after h-byte overflow (negative version)",
|
||||||
Offset: 0,
|
RewindOffset: 0,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9d, // base value
|
0x9d, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -670,9 +672,9 @@ var (
|
|||||||
1.6,
|
1.6,
|
||||||
1.7,
|
1.7,
|
||||||
},
|
},
|
||||||
Name: "increment literal",
|
Name: "increment literal",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9e, // base value
|
0x9e, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -690,9 +692,9 @@ var (
|
|||||||
-1.6,
|
-1.6,
|
||||||
-1.7,
|
-1.7,
|
||||||
},
|
},
|
||||||
Name: "increment literal (negative version)",
|
Name: "increment literal (negative version)",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9d, // base value
|
0x9d, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -710,9 +712,9 @@ var (
|
|||||||
1.4,
|
1.4,
|
||||||
1.3,
|
1.3,
|
||||||
},
|
},
|
||||||
Name: "increment literal (negative delta)",
|
Name: "increment literal (negative delta)",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9e, // base value
|
0x9e, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -730,9 +732,9 @@ var (
|
|||||||
-1.4,
|
-1.4,
|
||||||
-1.3,
|
-1.3,
|
||||||
},
|
},
|
||||||
Name: "increment literal (positive delta, negative version)",
|
Name: "increment literal (positive delta, negative version)",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x9d, // base value
|
0x9d, // base value
|
||||||
0x80, // delta 0
|
0x80, // delta 0
|
||||||
@@ -760,11 +762,11 @@ func TestInstantDeltaCompressor(t *testing.T) {
|
|||||||
c := NewValueDeltaCompressor(metricType, fracDigits, buf, payloadSize)
|
c := NewValueDeltaCompressor(metricType, fracDigits, buf, payloadSize)
|
||||||
for _, num := range testCase.Nums {
|
for _, num := range testCase.Nums {
|
||||||
report = c.Evaluate(tmp, num)
|
report = c.Evaluate(tmp, num)
|
||||||
c.Append(report.Offset, tmp[:report.ChangeSize], num, report.Delta)
|
c.Append(report.RewindOffset, tmp[:report.ChangeSize], num, report.Delta)
|
||||||
}
|
}
|
||||||
if report.Offset != testCase.Offset {
|
if report.RewindOffset != testCase.RewindOffset {
|
||||||
t.Fatalf("%s: got offset %d are not equal expected %d",
|
t.Fatalf("%s: got rewindOffset %d are not equal expected %d",
|
||||||
testCase.Name, report.Offset, testCase.Offset)
|
testCase.Name, report.RewindOffset, testCase.RewindOffset)
|
||||||
}
|
}
|
||||||
if report.ChangeSize != testCase.ChangeSize {
|
if report.ChangeSize != testCase.ChangeSize {
|
||||||
t.Fatalf("%s: got changeSize %d are not equal expected %d",
|
t.Fatalf("%s: got changeSize %d are not equal expected %d",
|
||||||
@@ -798,7 +800,7 @@ func TestRestoreInstantDeltaCompressor(t *testing.T) {
|
|||||||
c := NewValueDeltaCompressor(metricType, fracDigits, buf, payloadSize)
|
c := NewValueDeltaCompressor(metricType, fracDigits, buf, payloadSize)
|
||||||
for _, num := range testCase.Nums {
|
for _, num := range testCase.Nums {
|
||||||
report = c.Evaluate(tmp, num)
|
report = c.Evaluate(tmp, num)
|
||||||
c.Append(report.Offset, tmp[:report.ChangeSize], num, report.Delta)
|
c.Append(report.RewindOffset, tmp[:report.ChangeSize], num, report.Delta)
|
||||||
}
|
}
|
||||||
restored := NewValueDeltaCompressor(metricType, fracDigits, buf, c.Size())
|
restored := NewValueDeltaCompressor(metricType, fracDigits, buf, c.Size())
|
||||||
if restored.baseValue != testCase.BaseValue {
|
if restored.baseValue != testCase.BaseValue {
|
||||||
@@ -825,7 +827,7 @@ func TestInstantDeltaDecompressorFromState(t *testing.T) {
|
|||||||
c := NewValueDeltaCompressor(metricType, fracDigits, buf, payloadSize)
|
c := NewValueDeltaCompressor(metricType, fracDigits, buf, payloadSize)
|
||||||
for _, num := range testCase.Nums {
|
for _, num := range testCase.Nums {
|
||||||
report := c.Evaluate(tmp, num)
|
report := c.Evaluate(tmp, num)
|
||||||
c.Append(report.Offset, tmp[:report.ChangeSize], num, report.Delta)
|
c.Append(report.RewindOffset, tmp[:report.ChangeSize], num, report.Delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
d := c.CreateDecompressor(metricType, fracDigits)
|
d := c.CreateDecompressor(metricType, fracDigits)
|
||||||
@@ -859,7 +861,7 @@ func TestInstantDeltaDecompressorFromEnd(t *testing.T) {
|
|||||||
c := NewValueDeltaCompressor(metricType, fracDigits, buf, payloadSize)
|
c := NewValueDeltaCompressor(metricType, fracDigits, buf, payloadSize)
|
||||||
for _, num := range testCase.Nums {
|
for _, num := range testCase.Nums {
|
||||||
report := c.Evaluate(tmp, num)
|
report := c.Evaluate(tmp, num)
|
||||||
c.Append(report.Offset, tmp[:report.ChangeSize], num, report.Delta)
|
c.Append(report.RewindOffset, tmp[:report.ChangeSize], num, report.Delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
d := NewValueDeltaDecompressor(metricType, fracDigits)
|
d := NewValueDeltaDecompressor(metricType, fracDigits)
|
||||||
@@ -890,16 +892,16 @@ var (
|
|||||||
Buf []byte
|
Buf []byte
|
||||||
LastUnixtime uint32
|
LastUnixtime uint32
|
||||||
LastDelta uint32
|
LastDelta uint32
|
||||||
Offset int
|
RewindOffset int
|
||||||
ChangeSize int
|
ChangeSize int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Nums: []uint32{
|
Nums: []uint32{
|
||||||
1780777000,
|
1780777000,
|
||||||
},
|
},
|
||||||
Name: "add 1st value",
|
Name: "add 1st value",
|
||||||
Offset: 0,
|
RewindOffset: 0,
|
||||||
ChangeSize: 4,
|
ChangeSize: 4,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00,
|
||||||
0x28, 0x80, 0x24, 0x6a, // since
|
0x28, 0x80, 0x24, 0x6a, // since
|
||||||
@@ -912,9 +914,9 @@ var (
|
|||||||
1780777000,
|
1780777000,
|
||||||
1780777060, // +60
|
1780777060, // +60
|
||||||
},
|
},
|
||||||
Name: "add 1st delta",
|
Name: "add 1st delta",
|
||||||
Offset: 0,
|
RewindOffset: 0,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x00, 0x00,
|
0x00, 0x00,
|
||||||
0x80, // h-byte (literal, len=1)
|
0x80, // h-byte (literal, len=1)
|
||||||
@@ -930,9 +932,9 @@ var (
|
|||||||
1780777060, // +60
|
1780777060, // +60
|
||||||
1780777120, // +60
|
1780777120, // +60
|
||||||
},
|
},
|
||||||
Name: "literal changed to run",
|
Name: "literal changed to run",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 1,
|
ChangeSize: 1,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x00, 0x00,
|
0x00, 0x00,
|
||||||
0x00, // h-byte (run, len=2)
|
0x00, // h-byte (run, len=2)
|
||||||
@@ -949,9 +951,9 @@ var (
|
|||||||
1780777130, // +70
|
1780777130, // +70
|
||||||
1780777200, // +70
|
1780777200, // +70
|
||||||
},
|
},
|
||||||
Name: "literal decrease by 1 and switch to run",
|
Name: "literal decrease by 1 and switch to run",
|
||||||
Offset: 2,
|
RewindOffset: 2,
|
||||||
ChangeSize: 3,
|
ChangeSize: 3,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x00, // h-byte (run, len=2)
|
0x00, // h-byte (run, len=2)
|
||||||
0xc6, // delta 70
|
0xc6, // delta 70
|
||||||
@@ -969,9 +971,9 @@ var (
|
|||||||
1780777120, // +60
|
1780777120, // +60
|
||||||
1780777180, // +60
|
1780777180, // +60
|
||||||
},
|
},
|
||||||
Name: "increment run",
|
Name: "increment run",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 1,
|
ChangeSize: 1,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x00, 0x00,
|
0x00, 0x00,
|
||||||
0x01, // h-byte (run, len=2)
|
0x01, // h-byte (run, len=2)
|
||||||
@@ -989,9 +991,9 @@ var (
|
|||||||
1780777180, // +60
|
1780777180, // +60
|
||||||
1780777200, // +20
|
1780777200, // +20
|
||||||
},
|
},
|
||||||
Name: "switch run to literal",
|
Name: "switch run to literal",
|
||||||
Offset: 0,
|
RewindOffset: 0,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x80, // h-byte (literal, len=1)
|
0x80, // h-byte (literal, len=1)
|
||||||
0x94, // delta 20
|
0x94, // delta 20
|
||||||
@@ -1003,10 +1005,10 @@ var (
|
|||||||
LastDelta: 20,
|
LastDelta: 20,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Nums: generateProgression(1780777000, 60, 130),
|
Nums: generateProgression(1780777000, 60, 130),
|
||||||
Name: "increment run up to full filled h-byte",
|
Name: "increment run up to full filled h-byte",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 1,
|
ChangeSize: 1,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x00, 0x00,
|
0x00, 0x00,
|
||||||
0x7f, // h-byte (run, len=129)
|
0x7f, // h-byte (run, len=129)
|
||||||
@@ -1017,10 +1019,10 @@ var (
|
|||||||
LastDelta: 60,
|
LastDelta: 60,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Nums: generateProgression(1780777000, 60, 131),
|
Nums: generateProgression(1780777000, 60, 131),
|
||||||
Name: "run switch to literal after h-byte overflow",
|
Name: "run switch to literal after h-byte overflow",
|
||||||
Offset: 0,
|
RewindOffset: 0,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x80, // h-byte (literal, len=1)
|
0x80, // h-byte (literal, len=1)
|
||||||
0xbc, // delta 60
|
0xbc, // delta 60
|
||||||
@@ -1037,9 +1039,9 @@ var (
|
|||||||
1780777060, // +60
|
1780777060, // +60
|
||||||
1780777130, // +70
|
1780777130, // +70
|
||||||
},
|
},
|
||||||
Name: "increment literal",
|
Name: "increment literal",
|
||||||
Offset: 1,
|
RewindOffset: 1,
|
||||||
ChangeSize: 2,
|
ChangeSize: 2,
|
||||||
Buf: []byte{
|
Buf: []byte{
|
||||||
0x00,
|
0x00,
|
||||||
0x81, // h-byte (literal, len=2)
|
0x81, // h-byte (literal, len=2)
|
||||||
@@ -1064,11 +1066,11 @@ func TestTimeDeltaCompressor(t *testing.T) {
|
|||||||
c := NewTimeDeltaCompressor(buf, payloadSize)
|
c := NewTimeDeltaCompressor(buf, payloadSize)
|
||||||
for _, num := range testCase.Nums {
|
for _, num := range testCase.Nums {
|
||||||
report = c.Evaluate(tmp, num)
|
report = c.Evaluate(tmp, num)
|
||||||
c.Append(report.Offset, tmp[:report.ChangeSize], num)
|
c.Append(report.RewindOffset, tmp[:report.ChangeSize], num)
|
||||||
}
|
}
|
||||||
if report.Offset != testCase.Offset {
|
if report.RewindOffset != testCase.RewindOffset {
|
||||||
t.Fatalf("%s: got offset %d are not equal expected %d",
|
t.Fatalf("%s: got rewindOffset %d are not equal expected %d",
|
||||||
testCase.Name, report.Offset, testCase.Offset)
|
testCase.Name, report.RewindOffset, testCase.RewindOffset)
|
||||||
}
|
}
|
||||||
if report.ChangeSize != testCase.ChangeSize {
|
if report.ChangeSize != testCase.ChangeSize {
|
||||||
t.Fatalf("%s: got changeSize %d are not equal expected %d",
|
t.Fatalf("%s: got changeSize %d are not equal expected %d",
|
||||||
@@ -1100,7 +1102,7 @@ func TestRestoreTimeDeltaCompressor(t *testing.T) {
|
|||||||
c := NewTimeDeltaCompressor(buf, payloadSize)
|
c := NewTimeDeltaCompressor(buf, payloadSize)
|
||||||
for _, num := range testCase.Nums {
|
for _, num := range testCase.Nums {
|
||||||
report = c.Evaluate(tmp, num)
|
report = c.Evaluate(tmp, num)
|
||||||
c.Append(report.Offset, tmp[:report.ChangeSize], num)
|
c.Append(report.RewindOffset, tmp[:report.ChangeSize], num)
|
||||||
}
|
}
|
||||||
restored := NewTimeDeltaCompressor(buf, c.Size())
|
restored := NewTimeDeltaCompressor(buf, c.Size())
|
||||||
if restored.lastUnixtime != testCase.LastUnixtime {
|
if restored.lastUnixtime != testCase.LastUnixtime {
|
||||||
@@ -1118,16 +1120,17 @@ func TestTimeDeltaDecompressorFromState(t *testing.T) {
|
|||||||
for _, testCase := range timeTestCases {
|
for _, testCase := range timeTestCases {
|
||||||
var (
|
var (
|
||||||
tmp = make([]byte, tmpTimeSize)
|
tmp = make([]byte, tmpTimeSize)
|
||||||
buf = make([]byte, 8)
|
buf = make([]byte, 16)
|
||||||
payloadSize = 0
|
payloadSize = 0
|
||||||
decodedNums []uint32
|
decodedNums []uint32
|
||||||
)
|
)
|
||||||
c := NewTimeDeltaCompressor(buf, payloadSize)
|
c := NewTimeDeltaCompressor(buf, payloadSize)
|
||||||
for _, num := range testCase.Nums {
|
for _, num := range testCase.Nums {
|
||||||
report := c.Evaluate(tmp, num)
|
report := c.Evaluate(tmp, num)
|
||||||
c.Append(report.Offset, tmp[:report.ChangeSize], num)
|
c.Append(report.RewindOffset, tmp[:report.ChangeSize], num)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println("-----")
|
||||||
d := c.CreateDecompressor()
|
d := c.CreateDecompressor()
|
||||||
for {
|
for {
|
||||||
num, done := d.NextValue()
|
num, done := d.NextValue()
|
||||||
@@ -1157,7 +1160,7 @@ func TestTimeDeltaDecompressorFromEnd(t *testing.T) {
|
|||||||
c := NewTimeDeltaCompressor(buf, payloadSize)
|
c := NewTimeDeltaCompressor(buf, payloadSize)
|
||||||
for _, num := range testCase.Nums {
|
for _, num := range testCase.Nums {
|
||||||
report := c.Evaluate(tmp, num)
|
report := c.Evaluate(tmp, num)
|
||||||
c.Append(report.Offset, tmp[:report.ChangeSize], num)
|
c.Append(report.RewindOffset, tmp[:report.ChangeSize], num)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ReplaceSinceWithUntil()
|
c.ReplaceSinceWithUntil()
|
||||||
@@ -1206,3 +1209,91 @@ func generateProgression(start uint32, delta uint32, n int) []uint32 {
|
|||||||
}
|
}
|
||||||
return nums
|
return nums
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateTimestamps(days int) (timestamps []uint32) {
|
||||||
|
var (
|
||||||
|
minutes = []int{14, 29, 44, 59}
|
||||||
|
hoursPerDay = 24
|
||||||
|
totalHours = days * hoursPerDay
|
||||||
|
since = time.Now().AddDate(0, 0, -days)
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := range totalHours {
|
||||||
|
hourTime := since.Add(time.Duration(i) * time.Hour)
|
||||||
|
for _, m := range minutes {
|
||||||
|
measureTime := time.Date(
|
||||||
|
hourTime.Year(),
|
||||||
|
hourTime.Month(),
|
||||||
|
hourTime.Day(),
|
||||||
|
hourTime.Hour(),
|
||||||
|
m, // minutes
|
||||||
|
0, // seconds
|
||||||
|
0, // nanoseconds
|
||||||
|
time.Local,
|
||||||
|
)
|
||||||
|
timestamps = append(timestamps, uint32(measureTime.Unix()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXXX(t *testing.T) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
timestamps = generateTimestamps(20)[:1231]
|
||||||
|
tmp = make([]byte, tmpTimeSize)
|
||||||
|
buf = make([]byte, 64)
|
||||||
|
payloadSize = 0
|
||||||
|
decodedNums []uint32
|
||||||
|
)
|
||||||
|
c := NewTimeDeltaCompressor(buf, payloadSize)
|
||||||
|
for _, num := range timestamps {
|
||||||
|
report := c.Evaluate(tmp, num)
|
||||||
|
c.Append(report.RewindOffset, tmp[:report.ChangeSize], num)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := c.CreateDecompressor()
|
||||||
|
for {
|
||||||
|
num, done := d.NextValue()
|
||||||
|
if done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
decodedNums = append(decodedNums, num)
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.Reverse(decodedNums)
|
||||||
|
|
||||||
|
if !slices.Equal(timestamps, decodedNums) {
|
||||||
|
t.Fatalf("got nums %v not equal expected %v",
|
||||||
|
decodedNums, timestamps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYYY(t *testing.T) {
|
||||||
|
buf, err := textutil.ParseHex("50 04 87 ac 6b 05 6a")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//var decodedNums []uint32
|
||||||
|
var prev uint32
|
||||||
|
|
||||||
|
d := NewTimeDeltaDecompressor()
|
||||||
|
d.RestoreFromEnd(buf)
|
||||||
|
|
||||||
|
for {
|
||||||
|
num, done := d.NextValue()
|
||||||
|
if done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fmt.Println(num, prev-num)
|
||||||
|
prev = num
|
||||||
|
//decodedNums = append(decodedNums, num)
|
||||||
|
}
|
||||||
|
|
||||||
|
// //slices.Reverse(decodedNums)
|
||||||
|
|
||||||
|
// for _, num := range decodedNums {
|
||||||
|
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package enc
|
package enc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
@@ -92,11 +91,11 @@ func (s *TimeDeltaCompressor) restore(payloadSize int) {
|
|||||||
|
|
||||||
func (s *TimeDeltaCompressor) Evaluate(tmp []byte, timestamp uint32) qb.TimeEvaluationReport {
|
func (s *TimeDeltaCompressor) Evaluate(tmp []byte, timestamp uint32) qb.TimeEvaluationReport {
|
||||||
var (
|
var (
|
||||||
delta = timestamp - s.lastUnixtime
|
|
||||||
rewindOffset int
|
rewindOffset int
|
||||||
i int
|
i int
|
||||||
)
|
)
|
||||||
if s.pos < len(s.buf) {
|
if s.pos < len(s.buf) {
|
||||||
|
delta := timestamp - s.lastUnixtime
|
||||||
if s.lastDelta > 0 {
|
if s.lastDelta > 0 {
|
||||||
h := s.buf[s.pos]
|
h := s.buf[s.pos]
|
||||||
if h < 128 {
|
if h < 128 {
|
||||||
@@ -108,36 +107,36 @@ func (s *TimeDeltaCompressor) Evaluate(tmp []byte, timestamp uint32) qb.TimeEval
|
|||||||
rewindOffset = 1 // перезапис h
|
rewindOffset = 1 // перезапис h
|
||||||
} else {
|
} else {
|
||||||
// endSeries
|
// endSeries
|
||||||
n, _ := bin.PutVarUint64(tmp, uint64(delta))
|
|
||||||
i += n
|
|
||||||
tmp[i] = 128 // start new literal (length=1)
|
tmp[i] = 128 // start new literal (length=1)
|
||||||
i++
|
i++
|
||||||
|
n, _ := bin.PutVarUint64(tmp[i:], uint64(delta))
|
||||||
|
i += n
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// literal
|
// literal
|
||||||
if delta != s.lastDelta {
|
if delta != s.lastDelta {
|
||||||
if h < 255 {
|
if h < 255 {
|
||||||
// incrementLiteral
|
// incrementLiteral
|
||||||
n, _ := bin.PutVarUint64(tmp, uint64(delta))
|
|
||||||
i += n
|
|
||||||
tmp[i] = h + 1
|
tmp[i] = h + 1
|
||||||
i++
|
i++
|
||||||
|
n, _ := bin.PutVarUint64(tmp[i:], uint64(delta))
|
||||||
|
i += n
|
||||||
rewindOffset = 1 // перезапис h
|
rewindOffset = 1 // перезапис h
|
||||||
} else {
|
} else {
|
||||||
// endSeries
|
// endSeries
|
||||||
n, _ := bin.PutVarUint64(tmp, uint64(delta))
|
|
||||||
i += n
|
|
||||||
tmp[i] = 128 // start new literal (length=1)
|
tmp[i] = 128 // start new literal (length=1)
|
||||||
i++
|
i++
|
||||||
|
n, _ := bin.PutVarUint64(tmp[i:], uint64(delta))
|
||||||
|
i += n
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// startRun
|
// startRun
|
||||||
if h > 128 {
|
if h > 128 {
|
||||||
tmp[i] = h - 1 // зменшую довжину попередньої серії на 1
|
tmp[i] = 0 // start new run (length=2)
|
||||||
i++
|
i++
|
||||||
n, _ := bin.PutVarUint64(tmp[i:], uint64(delta))
|
n, _ := bin.PutVarUint64(tmp[i:], uint64(delta))
|
||||||
i += n
|
i += n
|
||||||
tmp[i] = 0 // start new run (length=2)
|
tmp[i] = h - 1 // зменшую довжину попередньої серії на 1
|
||||||
i++
|
i++
|
||||||
rewindOffset = 1 + n // перезапис пари delta/h
|
rewindOffset = 1 + n // перезапис пари delta/h
|
||||||
} else {
|
} else {
|
||||||
@@ -148,11 +147,10 @@ func (s *TimeDeltaCompressor) Evaluate(tmp []byte, timestamp uint32) qb.TimeEval
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// add 1st delta
|
|
||||||
n, _ := bin.PutVarUint64(tmp, uint64(delta))
|
|
||||||
i += n
|
|
||||||
tmp[i] = 128 // start new literal (length=1)
|
tmp[i] = 128 // start new literal (length=1)
|
||||||
i++
|
i++
|
||||||
|
n, _ := bin.PutVarUint64(tmp[i:], uint64(delta))
|
||||||
|
i += n
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bin.PutUint32(tmp, timestamp) // 1st timestamp (since)
|
bin.PutUint32(tmp, timestamp) // 1st timestamp (since)
|
||||||
@@ -167,16 +165,16 @@ func (s *TimeDeltaCompressor) Evaluate(tmp []byte, timestamp uint32) qb.TimeEval
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *TimeDeltaCompressor) Append(rewindOffset int, change []byte, timestamp uint32) {
|
func (s *TimeDeltaCompressor) Append(rewindOffset int, change []byte, timestamp uint32) {
|
||||||
if s.pos < len(s.buf) {
|
// fmt.Printf("----\nchange: % x\n", change)
|
||||||
i := s.pos + rewindOffset - 1 // -1, because s.pos always points to h byte
|
// fmt.Printf("rewindOffset: %d\n", rewindOffset)
|
||||||
for _, b := range change {
|
// fmt.Printf("pos: %d\n", s.pos)
|
||||||
s.buf[i] = b
|
// fmt.Printf("buf: %d\n", len(s.buf))
|
||||||
i--
|
|
||||||
}
|
if s.lastUnixtime > 0 {
|
||||||
s.lastDelta = timestamp - s.lastUnixtime
|
s.lastDelta = timestamp - s.lastUnixtime
|
||||||
} else {
|
|
||||||
copy(s.buf[len(s.buf)-4:], change) // 4b since
|
|
||||||
}
|
}
|
||||||
|
idx := s.pos - len(change) + rewindOffset
|
||||||
|
copy(s.buf[idx:], change)
|
||||||
s.pos -= len(change) - rewindOffset
|
s.pos -= len(change) - rewindOffset
|
||||||
s.lastUnixtime = timestamp
|
s.lastUnixtime = timestamp
|
||||||
}
|
}
|
||||||
@@ -199,6 +197,10 @@ func (s *TimeDeltaCompressor) getState() qb.TimeDeltaCapturedState {
|
|||||||
bound = s.pos // only since encoded
|
bound = s.pos // only since encoded
|
||||||
}
|
}
|
||||||
state.Payload = s.buf[bound:]
|
state.Payload = s.buf[bound:]
|
||||||
|
// fmt.Printf("% x\n", s.buf)
|
||||||
|
// fmt.Printf("% x\n", s.buf[bound:])
|
||||||
|
// fmt.Printf("h: % x\n", state.H)
|
||||||
|
// fmt.Printf("lastDelta: %d\n", s.lastDelta)
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
@@ -213,7 +215,7 @@ func (s *TimeDeltaCompressor) ForgetCapturedState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *TimeDeltaCompressor) Tail(offset int) []byte {
|
func (s *TimeDeltaCompressor) Tail(offset int) []byte {
|
||||||
fmt.Println("time tail:", s.pos, len(s.buf), offset)
|
//fmt.Println("time tail:", s.pos, len(s.buf), offset)
|
||||||
return s.buf[s.pos : len(s.buf)-offset]
|
return s.buf[s.pos : len(s.buf)-offset]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ func (s *ValueDeltaCompressor) Append(rewindOffset int, change []byte, value flo
|
|||||||
//fmt.Printf("buf before: % x\n", s.buf[:s.pos])
|
//fmt.Printf("buf before: % x\n", s.buf[:s.pos])
|
||||||
copy(s.buf[s.pos-rewindOffset:], change)
|
copy(s.buf[s.pos-rewindOffset:], change)
|
||||||
s.pos += len(change) - rewindOffset
|
s.pos += len(change) - rewindOffset
|
||||||
|
//fmt.Printf("v size %d\n", s.pos)
|
||||||
//fmt.Printf("buf after: % x\n", s.buf[:s.pos])
|
//fmt.Printf("buf after: % x\n", s.buf[:s.pos])
|
||||||
//fmt.Printf("buf after: % x\n", s.buf[:s.pos])
|
//fmt.Printf("buf after: % x\n", s.buf[:s.pos])
|
||||||
}
|
}
|
||||||
@@ -342,6 +343,7 @@ func (s *ValueDeltaDecompressor) readBaseValue() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ValueDeltaDecompressor) NextValue() (value float64, done bool) {
|
func (s *ValueDeltaDecompressor) NextValue() (value float64, done bool) {
|
||||||
|
//fmt.Printf("v pos: %d of %d\n", s.pos, len(s.buf))
|
||||||
if s.done {
|
if s.done {
|
||||||
return 0, true
|
return 0, true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -302,28 +302,31 @@ func sendRequests(conn *client.Connection) {
|
|||||||
// fmt.Printf("\nCumulative metric %d added\n", cumulativeMetricID)
|
// fmt.Printf("\nCumulative metric %d added\n", cumulativeMetricID)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // GET CUMULATIVE METRIC
|
// GET CUMULATIVE METRIC
|
||||||
|
|
||||||
// cMetric, err := conn.GetMetric(cumulativeMetricID)
|
cMetric, err := conn.GetMetric(cumulativeMetricID)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// log.Fatalf("conn.GetMetric: %s\n", err)
|
log.Fatalf("conn.GetMetric: %s\n", err)
|
||||||
// } else {
|
} else {
|
||||||
// fmt.Printf(`
|
fmt.Printf(`
|
||||||
// GetMetric:
|
GetMetric:
|
||||||
// metricID: %d
|
metricID: %d
|
||||||
// metricType: %s
|
metricType: %s
|
||||||
// fracDigits: %d
|
fracDigits: %d
|
||||||
// `,
|
`,
|
||||||
// cMetric.MetricID, metricTypeToName[cMetric.MetricType], cMetric.FracDigits)
|
cMetric.MetricID, metricTypeToName[cMetric.MetricType], cMetric.FracDigits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// APPEND MEASURES
|
||||||
|
|
||||||
|
// cumulativeMeasures := GenerateCumulativeMeasures(200)
|
||||||
|
// if len(cumulativeMeasures) > 65535 {
|
||||||
|
// cumulativeMeasures = cumulativeMeasures[:65535]
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // APPEND MEASURES
|
|
||||||
|
|
||||||
// cumulativeMeasures := GenerateCumulativeMeasures(62)
|
|
||||||
|
|
||||||
// result, err := conn.AppendMeasures(proto.AppendMeasuresReq{
|
// result, err := conn.AppendMeasures(proto.AppendMeasuresReq{
|
||||||
// MetricID: cumulativeMetricID,
|
// MetricID: cumulativeMetricID,
|
||||||
// Measures: cumulativeMeasures[:1000],
|
// Measures: cumulativeMeasures,
|
||||||
// })
|
// })
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// log.Fatalf("conn.AppendMeasures: %s\n", err)
|
// log.Fatalf("conn.AppendMeasures: %s\n", err)
|
||||||
@@ -344,7 +347,6 @@ func sendRequests(conn *client.Connection) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// LIST CUMULATIVE MEASURES
|
// LIST CUMULATIVE MEASURES
|
||||||
var cumulativeList []proto.CumulativeMeasure
|
|
||||||
|
|
||||||
// lastTimestamp = cumulativeMeasures[len(cumulativeMeasures)-1].Timestamp
|
// lastTimestamp = cumulativeMeasures[len(cumulativeMeasures)-1].Timestamp
|
||||||
// until = time.Unix(int64(lastTimestamp), 0)
|
// until = time.Unix(int64(lastTimestamp), 0)
|
||||||
@@ -368,14 +370,15 @@ func sendRequests(conn *client.Connection) {
|
|||||||
|
|
||||||
// LIST ALL CUMULATIVE MEASURES
|
// LIST ALL CUMULATIVE MEASURES
|
||||||
|
|
||||||
|
var cumulativeList []proto.CumulativeMeasure
|
||||||
cumulativeList, err = conn.ListAllCumulativeMeasures(cumulativeMetricID)
|
cumulativeList, err = conn.ListAllCumulativeMeasures(cumulativeMetricID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("conn.ListAllCumulativeMeasures: %s\n", err)
|
log.Fatalf("conn.ListAllCumulativeMeasures: %s\n", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\nListAllCumulativeMeasures (last 15 items):\n")
|
fmt.Printf("\nListAllCumulativeMeasures (last 15 items):\n")
|
||||||
//fmt.Printf("%#v\n", cumulativeList)
|
//fmt.Printf("%#v\n", cumulativeList)
|
||||||
for _, item := range cumulativeList[:15] {
|
for idx, item := range cumulativeList {
|
||||||
fmt.Printf(" %s => %.2f\n", formatTime(item.Timestamp), item.Value)
|
fmt.Printf(" %d: %s => %.2f\n", idx, formatTime(item.Timestamp), item.Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,6 +521,7 @@ func GenerateCumulativeMeasures(days int) []proto.Measure {
|
|||||||
totalValue float64
|
totalValue float64
|
||||||
)
|
)
|
||||||
|
|
||||||
|
idx := 0
|
||||||
for i := range totalHours {
|
for i := range totalHours {
|
||||||
hourTime := since.Add(time.Duration(i) * time.Hour)
|
hourTime := since.Add(time.Duration(i) * time.Hour)
|
||||||
for _, m := range minutes {
|
for _, m := range minutes {
|
||||||
@@ -539,6 +543,16 @@ func GenerateCumulativeMeasures(days int) []proto.Measure {
|
|||||||
measures = append(measures, measure)
|
measures = append(measures, measure)
|
||||||
|
|
||||||
totalValue += rand.Float64()
|
totalValue += rand.Float64()
|
||||||
|
|
||||||
|
if idx < 1500 {
|
||||||
|
fmt.Println(idx, measure.Timestamp)
|
||||||
|
}
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 1; i < len(measures); i++ {
|
||||||
|
if measures[i].Timestamp <= measures[i-1].Timestamp {
|
||||||
|
fmt.Println("FUCK", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return measures
|
return measures
|
||||||
@@ -580,3 +594,9 @@ func GenerateInstantMeasures(days int, baseValue float64) []proto.Measure {
|
|||||||
}
|
}
|
||||||
return measures
|
return measures
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//02 88 30 3e 88 31 1d 88 31 3e a3 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 28 68 05 6a
|
||||||
|
//02 88 30 3e 88 31 1d 88 31 3e a3 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 28 68 05 6a
|
||||||
|
|
||||||
|
//02 88 4b 4c 88 4c 06 88 4c 39 ae 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 30 1b 2c 6a
|
||||||
|
//02 88 4b 4c 88 4c 06 88 4c 39 ae 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 30 1b 2c 6a
|
||||||
|
|||||||
Binary file not shown.
@@ -80,13 +80,13 @@ func (s *FreeList) AddPageNumbers(pageNumbers []uint32) (err error) {
|
|||||||
// викидаю номери сторінок, які записав на диск
|
// викидаю номери сторінок, які записав на диск
|
||||||
copy(s.free, s.free[s.pointersOnPage:])
|
copy(s.free, s.free[s.pointersOnPage:])
|
||||||
s.free = s.free[:len(s.free)-s.pointersOnPage]
|
s.free = s.free[:len(s.free)-s.pointersOnPage]
|
||||||
fmt.Println("free:", s.free)
|
//fmt.Println("free:", s.free)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FreeList) save() error {
|
func (s *FreeList) save() error {
|
||||||
fmt.Println("save:", s.free[:s.pointersOnPage])
|
//fmt.Println("save:", s.free[:s.pointersOnPage])
|
||||||
buf := make([]byte, s.pageSize)
|
buf := make([]byte, s.pageSize)
|
||||||
i := crcSize
|
i := crcSize
|
||||||
for _, pageNo := range s.free[:s.pointersOnPage] {
|
for _, pageNo := range s.free[:s.pointersOnPage] {
|
||||||
@@ -108,7 +108,7 @@ func (s *FreeList) save() error {
|
|||||||
off = int64(s.basePagesCount * s.pageSize)
|
off = int64(s.basePagesCount * s.pageSize)
|
||||||
s.basePagesCount++
|
s.basePagesCount++
|
||||||
}
|
}
|
||||||
fmt.Println("write at:", off)
|
//fmt.Println("write at:", off)
|
||||||
n, err := file.WriteAt(buf, off)
|
n, err := file.WriteAt(buf, off)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Binary file not shown.
6
go.mod
6
go.mod
@@ -3,7 +3,13 @@ module gordenko.dev/dima/qb
|
|||||||
go 1.24.2
|
go 1.24.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/go-sql-driver/mysql v1.10.0
|
||||||
gopkg.in/ini.v1 v1.67.1
|
gopkg.in/ini.v1 v1.67.1
|
||||||
gordenko.dev/dima/bin v0.0.0-20260612161453-4ee9be3474fb
|
gordenko.dev/dima/bin v0.0.0-20260612161453-4ee9be3474fb
|
||||||
|
gordenko.dev/dima/fixme v0.0.0-20230701203753-4c2b6c1eef52
|
||||||
|
gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69
|
||||||
|
gordenko.dev/dima/qx v0.0.0-20231123051353-23dce7c0ce88
|
||||||
gordenko.dev/dima/textutil v0.0.0-20221225052909-270513dcbbbf
|
gordenko.dev/dima/textutil v0.0.0-20221225052909-270513dcbbbf
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require filippo.io/edwards25519 v1.2.0 // indirect
|
||||||
|
|||||||
10
go.sum
10
go.sum
@@ -1,6 +1,10 @@
|
|||||||
|
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||||
|
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
|
||||||
|
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@@ -20,5 +24,11 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gordenko.dev/dima/bin v0.0.0-20260612161453-4ee9be3474fb h1:XBYZbK5Z+yfpKmSR2wuPLyW14ReCRSNdOlL5MWro8ns=
|
gordenko.dev/dima/bin v0.0.0-20260612161453-4ee9be3474fb h1:XBYZbK5Z+yfpKmSR2wuPLyW14ReCRSNdOlL5MWro8ns=
|
||||||
gordenko.dev/dima/bin v0.0.0-20260612161453-4ee9be3474fb/go.mod h1:up64wpJp9xI+HqACtMDBIfPNUH6BYY/b3inKpUfrqDQ=
|
gordenko.dev/dima/bin v0.0.0-20260612161453-4ee9be3474fb/go.mod h1:up64wpJp9xI+HqACtMDBIfPNUH6BYY/b3inKpUfrqDQ=
|
||||||
|
gordenko.dev/dima/fixme v0.0.0-20230701203753-4c2b6c1eef52 h1:g3f99mV2NtLsN1/O7qkV9CwfEGhcWFS0Y5FX12On/NU=
|
||||||
|
gordenko.dev/dima/fixme v0.0.0-20230701203753-4c2b6c1eef52/go.mod h1:7tMDvA2ej8e7VwXIlcIZNtUe/Tk5jFAK498WqC+aMYE=
|
||||||
|
gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69 h1:nyJ3mzTQ46yUeMZCdLyYcs7B5JCS54c67v84miyhq2E=
|
||||||
|
gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69/go.mod h1:AxgKDktpqBVyIOhIcP+nlCpK+EsJyjN5kPdqyd8euVU=
|
||||||
|
gordenko.dev/dima/qx v0.0.0-20231123051353-23dce7c0ce88 h1:/9KsVGy3tsDR4SmSs9fZQx1hslYY89KqNpIa55kKuX0=
|
||||||
|
gordenko.dev/dima/qx v0.0.0-20231123051353-23dce7c0ce88/go.mod h1:CR5GISpjHFHZ9HeSIo3fYRyp1+kVx0R9exzBYsIuB3c=
|
||||||
gordenko.dev/dima/textutil v0.0.0-20221225052909-270513dcbbbf h1:GCL/m736iGDy3LgOm6iuvkxlGhUEyjfP7zgwZRkTYOg=
|
gordenko.dev/dima/textutil v0.0.0-20221225052909-270513dcbbbf h1:GCL/m736iGDy3LgOm6iuvkxlGhUEyjfP7zgwZRkTYOg=
|
||||||
gordenko.dev/dima/textutil v0.0.0-20221225052909-270513dcbbbf/go.mod h1:M3e8S3N1USCMYmL6Q5uDQJaxfzBlWHnTX7rxCJQ6iJs=
|
gordenko.dev/dima/textutil v0.0.0-20221225052909-270513dcbbbf/go.mod h1:M3e8S3N1USCMYmL6Q5uDQJaxfzBlWHnTX7rxCJQ6iJs=
|
||||||
|
|||||||
340
mqe/aggregated.go
Normal file
340
mqe/aggregated.go
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
package mqe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gordenko.dev/dima/qb/timeutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AggregatedMeasure struct {
|
||||||
|
Period int64 `json:"p"`
|
||||||
|
Since int64 `json:"s"`
|
||||||
|
Until int64 `json:"u"`
|
||||||
|
// для cumulative метрики 2 значения - value и total
|
||||||
|
// для instant метрики от 1 до 3 значений - min, max, avg
|
||||||
|
Values []float64 `json:"v"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AggregatedFilter struct {
|
||||||
|
Since time.Time
|
||||||
|
Until time.Time
|
||||||
|
GroupBy GroupBy
|
||||||
|
LastDayOfMonth int // например, конец месяца 25 число
|
||||||
|
FirstHourOfDay int // например день начинается в 6:00
|
||||||
|
InstantMetrics []InstantMetricAndFuncs
|
||||||
|
CumulativeMetrics []int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstantMetricAndFuncs struct {
|
||||||
|
MetricID int64
|
||||||
|
AggregateFuncs byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MeasureQueryEngine) SelectAggregatedData(req AggregatedFilter) (resultMap map[int64][]AggregatedMeasure, err error) {
|
||||||
|
since := req.Since
|
||||||
|
until := req.Until
|
||||||
|
// Валидация
|
||||||
|
switch req.GroupBy {
|
||||||
|
case ByHour, ByDay, ByMonth:
|
||||||
|
// pass
|
||||||
|
default:
|
||||||
|
err = fix.Field(WrongValue, "groupBy")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.FirstHourOfDay < 0 || req.FirstHourOfDay > 23 {
|
||||||
|
err = fix.Field(InvalidFirstHourOfDay, "firstHourOfDay")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.LastDayOfMonth < 0 || req.LastDayOfMonth > 28 {
|
||||||
|
err = fix.Field(InvalidLastDayOfMonth, "lastDayOfMonth")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Корректируем, ВСЕГДА на последнюю секунду суток
|
||||||
|
//until = timeutil.LastSecondInPeriod(until, "d")
|
||||||
|
|
||||||
|
// fmt.Printf(`SelectAggregatedData: {
|
||||||
|
// Since: %s
|
||||||
|
// Until: %s
|
||||||
|
// GroupBy: %s
|
||||||
|
// FirstHourOfDay: %d
|
||||||
|
// LastDayOfMonth: %d
|
||||||
|
// }
|
||||||
|
// `, since, until, req.GroupBy, req.FirstHourOfDay, req.LastDayOfMonth)
|
||||||
|
// FIX проверить как добавляются часы в DST часовых поясах
|
||||||
|
// Для req.FirstHourOfDay=4 since должен быть 4:00:00, until должен быть
|
||||||
|
// 3:59:59 следующего дня
|
||||||
|
if req.FirstHourOfDay > 0 {
|
||||||
|
since = since.Add(time.Duration(req.FirstHourOfDay) * time.Hour)
|
||||||
|
until = until.Add(time.Duration(req.FirstHourOfDay) * time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
sinceUnixtime := since.Unix()
|
||||||
|
untilUnixtime := until.Unix()
|
||||||
|
|
||||||
|
if sinceUnixtime >= untilUnixtime {
|
||||||
|
err = fix.Error(WrongDateRange)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cumulativeTasks []*AggregatedCumulativeTask
|
||||||
|
instantTasks []*AggregatedInstantTask
|
||||||
|
wg = new(sync.WaitGroup)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Важно вирутальная метрика зависит от реальных, которые к объекту могут быть
|
||||||
|
// привязаны или нет. Если нет - не возвращать.
|
||||||
|
resultMap = make(map[int64][]AggregatedMeasure)
|
||||||
|
|
||||||
|
if len(req.CumulativeMetrics) > 0 {
|
||||||
|
for _, metricID := range req.CumulativeMetrics {
|
||||||
|
task := &AggregatedCumulativeTask{
|
||||||
|
In: AggregatedMeasuresFilter{
|
||||||
|
MetricID: metricID,
|
||||||
|
Since: sinceUnixtime,
|
||||||
|
Until: untilUnixtime,
|
||||||
|
GroupBy: req.GroupBy,
|
||||||
|
LastDayOfMonth: req.LastDayOfMonth,
|
||||||
|
FirstHourOfDay: req.FirstHourOfDay,
|
||||||
|
},
|
||||||
|
WaitGroup: wg,
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
s.aggregatedCumulativeCh <- task
|
||||||
|
cumulativeTasks = append(cumulativeTasks, task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.InstantMetrics) > 0 {
|
||||||
|
for _, metric := range req.InstantMetrics {
|
||||||
|
task := &AggregatedInstantTask{
|
||||||
|
In: AggregatedInstantMeasuresIn{
|
||||||
|
MetricID: metric.MetricID,
|
||||||
|
Since: sinceUnixtime,
|
||||||
|
Until: untilUnixtime,
|
||||||
|
GroupBy: req.GroupBy,
|
||||||
|
LastDayOfMonth: req.LastDayOfMonth,
|
||||||
|
FirstHourOfDay: req.FirstHourOfDay,
|
||||||
|
Flags: metric.AggregateFuncs, // ФЛАГИ !!!
|
||||||
|
},
|
||||||
|
WaitGroup: wg,
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
s.aggregatedInstantCh <- task
|
||||||
|
instantTasks = append(instantTasks, task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
for _, task := range cumulativeTasks {
|
||||||
|
if task.Err != nil {
|
||||||
|
err = fmt.Errorf("get data for the metric %d: %s", task.In.MetricID, task.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resultMap[task.In.MetricID] = task.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, task := range instantTasks {
|
||||||
|
if task.Err != nil {
|
||||||
|
err = fmt.Errorf("get data for the metric %d: %s", task.In.MetricID, task.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resultMap[task.In.MetricID] = task.Result
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type IndividualAggregatedFilter struct {
|
||||||
|
Since time.Time
|
||||||
|
Until time.Time
|
||||||
|
GroupBy GroupBy
|
||||||
|
InstantMetrics []IndividualInstantMetric
|
||||||
|
CumulativeMetrics []IndividualCumulativeMetric
|
||||||
|
}
|
||||||
|
|
||||||
|
type IndividualInstantMetric struct {
|
||||||
|
MetricID int64
|
||||||
|
AggregateFuncs byte
|
||||||
|
LastDayOfMonth int // например, конец месяца 25 число
|
||||||
|
FirstHourOfDay int // например день начинается в 6:00
|
||||||
|
}
|
||||||
|
|
||||||
|
type IndividualCumulativeMetric struct {
|
||||||
|
MetricID int64
|
||||||
|
LastDayOfMonth int // например, конец месяца 25 число
|
||||||
|
FirstHourOfDay int // например день начинается в 6:00
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MeasureQueryEngine) SelectIndividualAggregatedData(req IndividualAggregatedFilter) (resultMap map[int64][]AggregatedMeasure, err error) {
|
||||||
|
if req.Since.IsZero() {
|
||||||
|
err = fmt.Errorf("zero Since")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Until.IsZero() {
|
||||||
|
err = fmt.Errorf("zero Until")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Since.After(req.Until) {
|
||||||
|
err = fmt.Errorf("wrong time range: since %s after until %s", req.Since, req.Until)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Корректируем, ВСЕГДА на первую и последнюю секунду суток
|
||||||
|
req.Since = timeutil.FirstSecondInPeriod(req.Since, "d")
|
||||||
|
req.Until = timeutil.LastSecondInPeriod(req.Until, "d")
|
||||||
|
|
||||||
|
// Валидация
|
||||||
|
switch req.GroupBy {
|
||||||
|
case ByHour, ByDay, ByMonth:
|
||||||
|
// pass
|
||||||
|
default:
|
||||||
|
err = fix.Field(WrongValue, "groupBy")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Printf(`SelectIndividualAggregatedData: {
|
||||||
|
// Since: %s
|
||||||
|
// Until: %s
|
||||||
|
// GroupBy: %s
|
||||||
|
// }
|
||||||
|
// `, req.Since, req.Until, req.GroupBy)
|
||||||
|
// Важно вирутальная метрика зависит от реальных, которые к объекту могут быть
|
||||||
|
// привязаны или нет. Если нет - не возвращать.
|
||||||
|
|
||||||
|
resultMap = make(map[int64][]AggregatedMeasure)
|
||||||
|
|
||||||
|
var (
|
||||||
|
unexpectedErrors []error
|
||||||
|
errorMutex sync.Mutex
|
||||||
|
|
||||||
|
wg sync.WaitGroup
|
||||||
|
)
|
||||||
|
|
||||||
|
wg.Add(len(req.CumulativeMetrics) + len(req.InstantMetrics))
|
||||||
|
|
||||||
|
if len(req.CumulativeMetrics) > 0 {
|
||||||
|
for _, m := range req.CumulativeMetrics {
|
||||||
|
go func(metric IndividualCumulativeMetric) {
|
||||||
|
if metric.FirstHourOfDay < 0 || metric.FirstHourOfDay > 23 {
|
||||||
|
err = fix.Field(InvalidFirstHourOfDay, "firstHourOfDay")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if metric.LastDayOfMonth < 0 || metric.LastDayOfMonth > 28 {
|
||||||
|
err = fix.Field(InvalidLastDayOfMonth, "lastDayOfMonth")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
since := req.Since
|
||||||
|
until := req.Until
|
||||||
|
// FIX проверить как добавляются часы в DST часовых поясах
|
||||||
|
// Для req.FirstHourOfDay=4 since должен быть 4:00:00, until должен быть
|
||||||
|
// 3:59:59 следующего дня
|
||||||
|
if metric.FirstHourOfDay > 0 {
|
||||||
|
since = req.Since.Add(time.Duration(metric.FirstHourOfDay) * time.Hour)
|
||||||
|
until = req.Until.Add(time.Duration(metric.FirstHourOfDay) * time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := s.listAggregatedCumulativeMeasures(AggregatedMeasuresFilter{
|
||||||
|
MetricID: metric.MetricID,
|
||||||
|
Since: since.Unix(),
|
||||||
|
Until: until.Unix(),
|
||||||
|
GroupBy: req.GroupBy,
|
||||||
|
LastDayOfMonth: metric.LastDayOfMonth,
|
||||||
|
FirstHourOfDay: metric.FirstHourOfDay,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("ListAggregatedCumulativeMeasures: %s", err)
|
||||||
|
|
||||||
|
errorMutex.Lock()
|
||||||
|
unexpectedErrors = append(unexpectedErrors, err)
|
||||||
|
errorMutex.Unlock()
|
||||||
|
} else {
|
||||||
|
errorMutex.Lock()
|
||||||
|
resultMap[metric.MetricID] = result
|
||||||
|
errorMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Done()
|
||||||
|
}(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.InstantMetrics) > 0 {
|
||||||
|
for _, m := range req.InstantMetrics {
|
||||||
|
go func(metric IndividualInstantMetric) {
|
||||||
|
if metric.FirstHourOfDay < 0 || metric.FirstHourOfDay > 23 {
|
||||||
|
err = fix.Field(InvalidFirstHourOfDay, "firstHourOfDay")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if metric.LastDayOfMonth < 0 || metric.LastDayOfMonth > 28 {
|
||||||
|
err = fix.Field(InvalidLastDayOfMonth, "lastDayOfMonth")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
since := req.Since
|
||||||
|
until := req.Until
|
||||||
|
// FIX проверить как добавляются часы в DST часовых поясах
|
||||||
|
// Для req.FirstHourOfDay=4 since должен быть 4:00:00, until должен быть
|
||||||
|
// 3:59:59 следующего дня
|
||||||
|
if metric.FirstHourOfDay > 0 {
|
||||||
|
since = req.Since.Add(time.Duration(metric.FirstHourOfDay) * time.Hour)
|
||||||
|
until = req.Until.Add(time.Duration(metric.FirstHourOfDay) * time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := s.listAggregatedInstantMeasures(AggregatedInstantMeasuresIn{
|
||||||
|
MetricID: metric.MetricID,
|
||||||
|
Since: since.Unix(),
|
||||||
|
Until: until.Unix(),
|
||||||
|
GroupBy: req.GroupBy,
|
||||||
|
LastDayOfMonth: metric.LastDayOfMonth,
|
||||||
|
FirstHourOfDay: metric.FirstHourOfDay,
|
||||||
|
Flags: metric.AggregateFuncs, // ФЛАГИ !!!
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("listAggregatedInstantMeasures: %s", err)
|
||||||
|
|
||||||
|
errorMutex.Lock()
|
||||||
|
unexpectedErrors = append(unexpectedErrors, err)
|
||||||
|
errorMutex.Unlock()
|
||||||
|
} else {
|
||||||
|
errorMutex.Lock()
|
||||||
|
resultMap[metric.MetricID] = result
|
||||||
|
errorMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Done()
|
||||||
|
}(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// Проверяем что все запросы завершились удачно
|
||||||
|
if len(unexpectedErrors) > 0 {
|
||||||
|
// Нумеруем сообщения об ошибках и склеиваем в одно большое сообщение
|
||||||
|
var errorStrings []string
|
||||||
|
|
||||||
|
for idx, err := range unexpectedErrors {
|
||||||
|
errorStrings = append(errorStrings, fmt.Sprintf("\n#%d %s", idx+1, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fmt.Errorf("%d errors occured:%s", len(unexpectedErrors),
|
||||||
|
strings.Join(errorStrings, ""))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
585
mqe/cumulative.go
Normal file
585
mqe/cumulative.go
Normal file
@@ -0,0 +1,585 @@
|
|||||||
|
package mqe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// func applyCorrections(corrections []model.F64Correction, measures []Measure) []Measure {
|
||||||
|
// for idx, measure := range measures {
|
||||||
|
// var isCorrected bool
|
||||||
|
// for _, correction := range corrections {
|
||||||
|
// if measure.Time >= correction.Time {
|
||||||
|
// measure.Value += correction.Value
|
||||||
|
// isCorrected = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if isCorrected {
|
||||||
|
// measures[idx] = measure
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return measures
|
||||||
|
// }
|
||||||
|
|
||||||
|
func applyCorrectionsToMeasure(corrections []_f64Correction, tm int64, value float64) float64 {
|
||||||
|
for _, correction := range corrections {
|
||||||
|
if tm >= correction.Time {
|
||||||
|
value += correction.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyCorrectionsToMeasures(corrections []_f64Correction, measures []_measure) {
|
||||||
|
for idx, m := range measures {
|
||||||
|
for _, correction := range corrections {
|
||||||
|
if m.Time >= correction.Time {
|
||||||
|
m.Value += correction.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
measures[idx] = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyCorrectionsToRawMeasures(corrections []_f64Correction, measures []RawMeasure) {
|
||||||
|
for idx, m := range measures {
|
||||||
|
for _, correction := range corrections {
|
||||||
|
if m.Time >= correction.Time {
|
||||||
|
m.Value += correction.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
measures[idx] = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type _measure struct {
|
||||||
|
Time int64
|
||||||
|
Value float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ВАЖНО!
|
||||||
|
// ВО ВСЕХ ЗАПРОСАХ ОБЯЗАТЕЛЬНА КОРРЕКТНАЯ СОРТИРОВКА!
|
||||||
|
|
||||||
|
type _f64Correction struct {
|
||||||
|
Time int64
|
||||||
|
Value float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func listF64CorrectionsTx(tx *sql.Tx, metricID int64) (list []_f64Correction, err error) {
|
||||||
|
rows, err := tx.Query(
|
||||||
|
"SELECT tm, value FROM metric_corrections WHERE metricID=? ORDER BY tm ASC",
|
||||||
|
metricID)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var correction _f64Correction
|
||||||
|
err = rows.Scan(&correction.Time, &correction.Value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
list = append(list, correction)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// result = append(result, Measure{
|
||||||
|
// Time: tm,
|
||||||
|
// Values: []float64{
|
||||||
|
// value,
|
||||||
|
// value - prevValue,
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
|
||||||
|
// listCumulativeMeasures - возвращает список CumulativeMeasure.
|
||||||
|
// Метод ничего не знает про FirstHourOfDay, но извне границы Since и Until могут быть
|
||||||
|
// скорректированы с учетом FirstHourOfDay. Это не поломает выборку.
|
||||||
|
func (s *MeasureQueryEngine) listCumulativeMeasures(req MetricMeasuresFilter) (_ []Measure, err error) {
|
||||||
|
tx, err := s.db.Driver().Begin()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// corrections, err := listF64CorrectionsTx(tx, req.MetricID)
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ВАЖНО!
|
||||||
|
// Оптимизация! Запрашиваем больше данных чем нужно, чтобы посчитать Total для
|
||||||
|
// самого старого измерения в result.
|
||||||
|
// Отнимаем от since еще 1 час (с запасом) чтобы зацепить extendedMeasure
|
||||||
|
extendedSince := req.Since - 3600
|
||||||
|
|
||||||
|
rows, err := tx.Query(`
|
||||||
|
SELECT tm, value
|
||||||
|
FROM f64
|
||||||
|
WHERE metricID=? AND tm BETWEEN ? AND ?
|
||||||
|
ORDER BY tm ASC`,
|
||||||
|
req.MetricID, extendedSince, req.Until)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var (
|
||||||
|
result []Measure
|
||||||
|
measures []_measure
|
||||||
|
prev *_measure
|
||||||
|
)
|
||||||
|
|
||||||
|
// Вычитываем все найденные показания
|
||||||
|
for rows.Next() {
|
||||||
|
var m _measure
|
||||||
|
err = rows.Scan(&m.Time, &m.Value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Time < req.Since {
|
||||||
|
// пока не найдено первое показание внутри диапазона обновлем extendedValue
|
||||||
|
prev = &_measure{
|
||||||
|
Time: m.Time,
|
||||||
|
Value: m.Value,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
measures = append(measures, m)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Остальные показания вычитываем без проверок
|
||||||
|
for rows.Next() {
|
||||||
|
var m _measure
|
||||||
|
err = rows.Scan(&m.Time, &m.Value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
measures = append(measures, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(measures) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if prev == nil {
|
||||||
|
// extended not found
|
||||||
|
// extended показание не найдено, а значит для первого показания не сможем показать total.
|
||||||
|
// Нужен дополнительный запрос.
|
||||||
|
// Находим максимально свежее показание метрики до extendedSince, от которого и будем
|
||||||
|
// считать total.
|
||||||
|
var (
|
||||||
|
tm int64
|
||||||
|
value float64
|
||||||
|
)
|
||||||
|
|
||||||
|
err = tx.QueryRow(`
|
||||||
|
SELECT tm, value
|
||||||
|
FROM f64
|
||||||
|
WHERE metricID=? AND tm < ?
|
||||||
|
ORDER BY tm DESC
|
||||||
|
LIMIT 1`,
|
||||||
|
req.MetricID, extendedSince).Scan(&tm, &value)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
prev = &_measure{
|
||||||
|
Time: tm,
|
||||||
|
Value: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ВАЖНО!
|
||||||
|
// Сперва применяем коррекции к показаниям, а затем рассчитываем сумму за период
|
||||||
|
|
||||||
|
// if len(corrections) > 0 {
|
||||||
|
// applyCorrectionsToMeasures(corrections, measures)
|
||||||
|
// if prev != nil {
|
||||||
|
// prev.Value = applyCorrectionsToMeasure(corrections, prev.Time, prev.Value)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
var prevValue float64
|
||||||
|
if prev != nil {
|
||||||
|
prevValue = prev.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range measures {
|
||||||
|
result = append(result, Measure{
|
||||||
|
Time: m.Time,
|
||||||
|
Values: []float64{
|
||||||
|
m.Value,
|
||||||
|
m.Value - prevValue,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
prevValue = m.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Корректируем самый первый тотал. Eсли prev показания не было -> total = 0
|
||||||
|
if prev == nil {
|
||||||
|
first := result[0]
|
||||||
|
first.Values[1] = 0
|
||||||
|
result[0] = first
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AggregatedMeasuresFilter struct {
|
||||||
|
MetricID int64 `json:"metricID"`
|
||||||
|
Since int64 `json:"since"`
|
||||||
|
Until int64 `json:"until"`
|
||||||
|
GroupBy GroupBy `json:"groupBy"`
|
||||||
|
LastDayOfMonth int `json:"lastDayOfMonth"` // например, конец месяца 25 число
|
||||||
|
FirstHourOfDay int `json:"firstHourOfDay"` // например день начинается в 6:00
|
||||||
|
}
|
||||||
|
|
||||||
|
// listAggregatedCumulativeMeasures - возвращает список AggregatedCumulativeMeasure,
|
||||||
|
// сгрупированный по какому-то периоду
|
||||||
|
func (s *MeasureQueryEngine) listAggregatedCumulativeMeasures(req AggregatedMeasuresFilter) (_ []AggregatedMeasure, err error) {
|
||||||
|
//pretty.PPrintln("listAggregatedCumulativeMeasures", req)
|
||||||
|
|
||||||
|
tx, err := s.db.Driver().Begin()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
corrections, err := listF64CorrectionsTx(tx, req.MetricID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// чтобы не экранировать символы в шаблоне строки
|
||||||
|
groupByFormat string
|
||||||
|
parsePeriodLayout string
|
||||||
|
)
|
||||||
|
|
||||||
|
switch req.GroupBy {
|
||||||
|
case ByHour:
|
||||||
|
groupByFormat = "%Y%m%d%H"
|
||||||
|
parsePeriodLayout = hourPeriodLayout
|
||||||
|
|
||||||
|
case ByDay:
|
||||||
|
groupByFormat = "%Y%m%d"
|
||||||
|
parsePeriodLayout = dayPeriodLayout
|
||||||
|
|
||||||
|
case ByMonth:
|
||||||
|
groupByFormat = "%Y%m"
|
||||||
|
parsePeriodLayout = monthPeriodLayout
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Защита от дурака
|
||||||
|
err = fmt.Errorf("unknown groupBy: %s", req.GroupBy)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ВАЖНО!
|
||||||
|
// Оптимизация! Запрашиваем больше данных чем нужно, чтобы посчитать начало
|
||||||
|
// первого и конец последнего периодов без дополнительных запросов.
|
||||||
|
//
|
||||||
|
|
||||||
|
// Отнимаем 3 минуты чтобы зацепить последнее показание предыдущего периода
|
||||||
|
extendedSince := req.Since - 3*60
|
||||||
|
// Добавляем 1 час, чтобы зацепить первое показание следующего периода
|
||||||
|
extendedUntil := req.Until + 60*60
|
||||||
|
|
||||||
|
//fmt.Printf("extendedSince: %s\n\n", extendedSince)
|
||||||
|
|
||||||
|
// Для каждого периода выбираем первое и последнее (по времени) измерение.
|
||||||
|
// Если делать выборку одним запросом без UNION, а именно сделать
|
||||||
|
// INNER JOIN ... ON f64.tm=x.maxtm OR f64.tm=x.mintm - все начинает тормозить.
|
||||||
|
// Два запроса работают достаточно быстро.
|
||||||
|
|
||||||
|
// ВАЖНО!
|
||||||
|
// Не забыть про сортировку tm DESC!
|
||||||
|
|
||||||
|
dt, err := getPeriodDateTime(req.GroupBy, req.LastDayOfMonth, req.FirstHourOfDay)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("DT: %s\n\n", dt)
|
||||||
|
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
WITH
|
||||||
|
measures AS (SELECT * FROM f64 WHERE metricID=? AND tm BETWEEN ? AND ?),
|
||||||
|
aggregated AS (
|
||||||
|
SELECT MIN(tm) as minTm, MAX(tm) as maxTm, DATE_FORMAT(%s, '%s') as periodStr
|
||||||
|
FROM measures
|
||||||
|
GROUP BY periodStr
|
||||||
|
)
|
||||||
|
SELECT aggregated.periodStr, firstMeasures.tm, firstMeasures.value, lastMeasures.tm, lastMeasures.value
|
||||||
|
FROM aggregated
|
||||||
|
INNER JOIN measures firstMeasures ON firstMeasures.tm = aggregated.minTm
|
||||||
|
INNER JOIN measures lastMeasures ON lastMeasures.tm = aggregated.maxTm
|
||||||
|
ORDER BY aggregated.periodStr ASC`,
|
||||||
|
dt, groupByFormat)
|
||||||
|
|
||||||
|
rows, err := tx.Query(query, req.MetricID, extendedSince, extendedUntil)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var result []AggregatedMeasure
|
||||||
|
|
||||||
|
periodCalculator := NewPeriodCalculator(PeriodCalculatorOptions{
|
||||||
|
GroupBy: req.GroupBy,
|
||||||
|
LastDayOfMonth: req.LastDayOfMonth,
|
||||||
|
FirstHourOfDay: req.FirstHourOfDay,
|
||||||
|
Since: time.Unix(req.Since, 0),
|
||||||
|
Until: time.Unix(req.Until, 0),
|
||||||
|
})
|
||||||
|
|
||||||
|
periodProducer := NewPeriodProducer(PeriodProducerOptions{
|
||||||
|
MetricID: req.MetricID,
|
||||||
|
ParsePeriodLayout: parsePeriodLayout,
|
||||||
|
Location: s.location,
|
||||||
|
Until: req.Until,
|
||||||
|
Corrections: corrections,
|
||||||
|
Rows: rows,
|
||||||
|
Tx: tx,
|
||||||
|
})
|
||||||
|
|
||||||
|
result, err = SplitByPeriods(periodProducer, periodCalculator)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RangeTotal struct {
|
||||||
|
Since int64 `json:"since"` // реальная граница
|
||||||
|
Until int64 `json:"until"` // реальная граница
|
||||||
|
StartValue float64 `json:"startValue"` // на конец периода
|
||||||
|
EndValue float64 `json:"endValue"` // на конец периода
|
||||||
|
}
|
||||||
|
|
||||||
|
type getCumulativeTotalFilter struct {
|
||||||
|
MetricID int64
|
||||||
|
Since int64
|
||||||
|
Until int64
|
||||||
|
LastDayOfMonth int
|
||||||
|
FirstHourOfDay int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MeasureQueryEngine) getCumulativeTotal(req getCumulativeTotalFilter) (_ *RangeTotal, err error) {
|
||||||
|
measures, err := s.listAggregatedCumulativeMeasures(AggregatedMeasuresFilter{
|
||||||
|
MetricID: req.MetricID,
|
||||||
|
Since: req.Since,
|
||||||
|
Until: req.Until,
|
||||||
|
GroupBy: ByDay,
|
||||||
|
LastDayOfMonth: req.LastDayOfMonth,
|
||||||
|
FirstHourOfDay: req.FirstHourOfDay,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(measures) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//pretty.PPrintln("TOTAL", measures)
|
||||||
|
|
||||||
|
first := measures[0]
|
||||||
|
last := measures[len(measures)-1]
|
||||||
|
|
||||||
|
return &RangeTotal{
|
||||||
|
Since: first.Since,
|
||||||
|
Until: last.Until,
|
||||||
|
StartValue: first.Values[0] - first.Values[1], // value on period end - period total
|
||||||
|
EndValue: last.Values[0],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// GetCumulativeTotal - фикс, добавить опции FirstHourInDay, IsFindPeriodEndsInFuture
|
||||||
|
func (s *MeasureQueryEngine) getCumulativeTotal(req getCumulativeTotalFilter) (_ *RangeTotal, err error) {
|
||||||
|
tx, err := s.db.Driver().Begin()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
var (
|
||||||
|
firstTm, lastTm, futureTm int64
|
||||||
|
firstValue, lastValue, futureValue float64
|
||||||
|
foundInPast, foundInFuture, foundFirstInRange, foundLastInRange bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// Поиск первого показания
|
||||||
|
|
||||||
|
// Заглядываем в прошлое на 3 минуты
|
||||||
|
firstTm, firstValue, foundInPast, err = s.findLastInRange(tx, req.MetricID, req.Since-3*60, req.Since)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundInPast {
|
||||||
|
//
|
||||||
|
firstTm, firstValue, foundFirstInRange, err = s.findFirstInRange(tx, req.MetricID, req.Since, req.Until)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundFirstInRange {
|
||||||
|
// Начало периода не найдено - значит нет показаний для указанного периода
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Поиск последнего показания
|
||||||
|
|
||||||
|
lastTm, lastValue, foundLastInRange, err = s.findLastInRange(tx, req.MetricID, req.Since, req.Until)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.CanPeriodEndsInFuture {
|
||||||
|
// Если можем искать конец в периода в будущем
|
||||||
|
if foundLastInRange {
|
||||||
|
if lastTm > (req.Until - 3*60) {
|
||||||
|
// Если lastTm в последние 3 минуты периода - период корректно завершен
|
||||||
|
return &RangeTotal{
|
||||||
|
Since: firstTm,
|
||||||
|
Until: lastTm,
|
||||||
|
Value: lastValue,
|
||||||
|
Total: lastValue - firstValue,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Период не завершен в последние 3 минуты периода, поэтому ищем в будущем
|
||||||
|
futureTm, futureValue, foundInFuture, err = s.findFirstAfter(tx, req.MetricID, req.Until)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundInFuture {
|
||||||
|
return &RangeTotal{
|
||||||
|
Since: firstTm,
|
||||||
|
Until: futureTm,
|
||||||
|
Value: futureValue,
|
||||||
|
Total: futureValue - firstValue,
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
// Супер важная проверка, ибо возможна ситуация, когда когда нашли начало
|
||||||
|
// текущего периода в конце предыдущего (foundInPast). А это показание было
|
||||||
|
// последним в БД.
|
||||||
|
if foundLastInRange {
|
||||||
|
// Последнее показание внутри периода нашли, неважно когда оно пришло - нам
|
||||||
|
// подходит.
|
||||||
|
return &RangeTotal{
|
||||||
|
Since: firstTm,
|
||||||
|
Until: lastTm,
|
||||||
|
Value: lastValue,
|
||||||
|
Total: lastValue - firstValue,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
// Последнее показание внутри периода не найдено, а значит их не было вообще.
|
||||||
|
// Просто выходим
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// В будущем искать нельзя
|
||||||
|
|
||||||
|
// Супер важная проверка, ибо возможна ситуация, когда когда нашли начало
|
||||||
|
// текущего периода в конце предыдущего (foundInPast). А это показание было
|
||||||
|
// последним в БД.
|
||||||
|
if foundLastInRange {
|
||||||
|
// Последнее показание внутри периода нашли, неважно когда оно пришло - нам
|
||||||
|
// подходит.
|
||||||
|
return &RangeTotal{
|
||||||
|
Since: firstTm,
|
||||||
|
Until: lastTm,
|
||||||
|
Value: lastValue,
|
||||||
|
Total: lastValue - firstValue,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
// Последнее показание внутри периода не найдено, а значит их не было вообще.
|
||||||
|
// Просто выходим
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (s *MeasureQueryEngine) findLastInRangeTx(tx *sql.Tx, metricID int64, since, until int64) (tm int64, value float64, found bool, err error) {
|
||||||
|
err = tx.QueryRow(`
|
||||||
|
SELECT tm, value, true
|
||||||
|
FROM f64
|
||||||
|
WHERE metricID=? AND tm >= ? AND tm < ?
|
||||||
|
ORDER BY tm DESC
|
||||||
|
LIMIT 1`,
|
||||||
|
metricID, since, until).Scan(&tm, &value, &found)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Нет записей
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MeasureQueryEngine) findFirstInRangeTx(tx *sql.Tx, metricID int64, since, until int64) (tm int64, value float64, found bool, err error) {
|
||||||
|
err = tx.QueryRow(`
|
||||||
|
SELECT tm, value, true
|
||||||
|
FROM f64
|
||||||
|
WHERE metricID=? AND tm >= ? AND tm < ?
|
||||||
|
ORDER BY tm ASC
|
||||||
|
LIMIT 1`,
|
||||||
|
metricID, since, until).Scan(&tm, &value, &found)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Нет записей
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MeasureQueryEngine) findFirstAfterTx(tx *sql.Tx, metricID int64, since int64) (tm int64, value float64, found bool, err error) {
|
||||||
|
err = tx.QueryRow(`
|
||||||
|
SELECT tm, value, true
|
||||||
|
FROM f64
|
||||||
|
WHERE metricID=? AND tm >= ?
|
||||||
|
ORDER BY tm ASC
|
||||||
|
LIMIT 1`,
|
||||||
|
metricID, since).Scan(&tm, &value, &found)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Нет записей
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
29
mqe/fix.go
Normal file
29
mqe/fix.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package mqe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gordenko.dev/dima/fixme"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EmptyValue = "EMPTY_VALUE"
|
||||||
|
BusyName = "BUSY_NAME"
|
||||||
|
WrongValue = "WRONG_VALUE"
|
||||||
|
WrongDate = "WRONG_DATE"
|
||||||
|
WrongDateRange = "WRONG_DATE_RANGE"
|
||||||
|
InvalidFirstHourOfDay = "WRONG_FIRST_HOUR_OF_DAY"
|
||||||
|
InvalidLastDayOfMonth = "WRONG_LAST_DAY_OF_MONTH"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errorMessages = map[fixme.Code]string{
|
||||||
|
EmptyValue: "порожнє значення неприпустимо",
|
||||||
|
BusyName: "имя уже используется",
|
||||||
|
WrongValue: "неправильне значення",
|
||||||
|
WrongDate: "некоректна дата",
|
||||||
|
WrongDateRange: "некоректний діапазон",
|
||||||
|
InvalidFirstHourOfDay: "корректное значение от 0 до 23",
|
||||||
|
InvalidLastDayOfMonth: "корректное значение от 0 до 28",
|
||||||
|
}
|
||||||
|
|
||||||
|
fix = fixme.New(errorMessages)
|
||||||
|
)
|
||||||
158
mqe/helpers.go
Normal file
158
mqe/helpers.go
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
package mqe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getPeriodDateTime(groupBy GroupBy, lastDayOfMonth int, firstHourOfDay int) (dt string, err error) {
|
||||||
|
//fmt.Printf("getPeriodDateTime: %s, lastDay: %d, firstHour: %d\n", groupBy, lastDayOfMonth, firstHourOfDay)
|
||||||
|
// Если FirstHourOfDay и LastDayOfMonth равны 0 - модифицировать дату не нужно
|
||||||
|
dt = `FROM_UNIXTIME(tm)`
|
||||||
|
|
||||||
|
switch groupBy {
|
||||||
|
case ByDay:
|
||||||
|
if firstHourOfDay > 0 {
|
||||||
|
dt = fmt.Sprintf(`
|
||||||
|
IF (HOUR(FROM_UNIXTIME(tm)) >= %d,
|
||||||
|
FROM_UNIXTIME(tm),
|
||||||
|
DATE_SUB(FROM_UNIXTIME(tm), INTERVAL 1 DAY)
|
||||||
|
)`,
|
||||||
|
firstHourOfDay)
|
||||||
|
}
|
||||||
|
|
||||||
|
case ByMonth:
|
||||||
|
if lastDayOfMonth > 0 {
|
||||||
|
if lastDayOfMonth > 28 {
|
||||||
|
// На всякий случай добавляем защиту от дурака, ибо модификация даты
|
||||||
|
// в запросе (+1 месяц) для 29, 30 и 31 чисел сделает запрос неверным.
|
||||||
|
err = fmt.Errorf("LastDayOfMonth: %d, max allowed value is 28", lastDayOfMonth)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstHourOfDay > 0 {
|
||||||
|
// В псевдокоде модификация времени показания по корректный период
|
||||||
|
// выглядит так:
|
||||||
|
//
|
||||||
|
// if hour >= FirstHourOfDay {
|
||||||
|
// if day <= LastDayOfMonth {
|
||||||
|
// return tm
|
||||||
|
// } else {
|
||||||
|
// return tm + 1 month
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// if (day - 1) <= LastDayOfMonth {
|
||||||
|
// return tm - 1 day
|
||||||
|
// } else {
|
||||||
|
// return tm - 1 day + 1 month
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
dt = fmt.Sprintf(`
|
||||||
|
IF (HOUR(FROM_UNIXTIME(tm)) >= %d,
|
||||||
|
IF (DAYOFMONTH(FROM_UNIXTIME(tm)) <= %d,
|
||||||
|
FROM_UNIXTIME(tm),
|
||||||
|
DATE_ADD(FROM_UNIXTIME(tm), INTERVAL 1 MONTH)
|
||||||
|
),
|
||||||
|
IF (DAYOFMONTH(DATE_SUB(FROM_UNIXTIME(tm), INTERVAL 1 DAY)) <= %d,
|
||||||
|
DATE_SUB(FROM_UNIXTIME(tm), INTERVAL 1 DAY),
|
||||||
|
DATE_ADD(DATE_SUB(FROM_UNIXTIME(tm), INTERVAL 1 DAY), INTERVAL 1 MONTH)
|
||||||
|
)
|
||||||
|
)`,
|
||||||
|
firstHourOfDay, lastDayOfMonth, lastDayOfMonth)
|
||||||
|
} else {
|
||||||
|
dt = fmt.Sprintf(`
|
||||||
|
IF (DAYOFMONTH(FROM_UNIXTIME(tm)) <= %d,
|
||||||
|
FROM_UNIXTIME(tm),
|
||||||
|
DATE_ADD(FROM_UNIXTIME(tm), INTERVAL 1 MONTH)
|
||||||
|
)`,
|
||||||
|
lastDayOfMonth)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if firstHourOfDay > 0 {
|
||||||
|
dt = fmt.Sprintf(`
|
||||||
|
IF (HOUR(FROM_UNIXTIME(tm)) >= %d,
|
||||||
|
FROM_UNIXTIME(tm),
|
||||||
|
DATE_SUB(FROM_UNIXTIME(tm), INTERVAL 1 DAY)
|
||||||
|
)`,
|
||||||
|
firstHourOfDay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDateTimeFacingFHD(firstHourOfDay int) (dt string, err error) {
|
||||||
|
if firstHourOfDay > 0 {
|
||||||
|
dt = fmt.Sprintf(`
|
||||||
|
IF (HOUR(FROM_UNIXTIME(tm)) >= %d,
|
||||||
|
FROM_UNIXTIME(tm),
|
||||||
|
DATE_SUB(FROM_UNIXTIME(tm), INTERVAL 1 DAY)
|
||||||
|
)`,
|
||||||
|
firstHourOfDay)
|
||||||
|
} else {
|
||||||
|
// Если FirstHourOfDay = 0 - модифицировать дату не нужно
|
||||||
|
dt = "FROM_UNIXTIME(tm)"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDateTimeFacingFHDAndLDM(firstHourOfDay int, lastDayOfMonth int) (dt string, err error) {
|
||||||
|
// Если FirstHourOfDay и LastDayOfMonth равны 0 - модифицировать дату не нужно
|
||||||
|
dt = "FROM_UNIXTIME(tm)"
|
||||||
|
|
||||||
|
if lastDayOfMonth > 0 {
|
||||||
|
if lastDayOfMonth > 28 {
|
||||||
|
// На всякий случай добавляем защиту от дурака, ибо модификация даты
|
||||||
|
// в запросе (+1 месяц) для 29, 30 и 31 чисел сделает запрос неверным.
|
||||||
|
err = fmt.Errorf("LastDayOfMonth: %d, max allowed value is 28", lastDayOfMonth)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstHourOfDay > 0 {
|
||||||
|
// В псевдокоде модификация времени показания по корректный период
|
||||||
|
// выглядит так:
|
||||||
|
//
|
||||||
|
// if hour >= FirstHourOfDay {
|
||||||
|
// if day <= LastDayOfMonth {
|
||||||
|
// return tm
|
||||||
|
// } else {
|
||||||
|
// return tm + 1 month
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// if (day - 1) <= LastDayOfMonth {
|
||||||
|
// return tm - 1 day
|
||||||
|
// } else {
|
||||||
|
// return tm - 1 day + 1 month
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
dt = fmt.Sprintf(`
|
||||||
|
IF (HOUR(FROM_UNIXTIME(tm)) >= %d,
|
||||||
|
IF (DAYOFMONTH(FROM_UNIXTIME(tm)) <= %d,
|
||||||
|
FROM_UNIXTIME(tm),
|
||||||
|
DATE_ADD(FROM_UNIXTIME(tm), INTERVAL 1 MONTH)
|
||||||
|
),
|
||||||
|
IF (DAYOFMONTH(DATE_SUB(FROM_UNIXTIME(tm), INTERVAL 1 DAY)) <= %d,
|
||||||
|
DATE_SUB(FROM_UNIXTIME(tm), INTERVAL 1 DAY),
|
||||||
|
DATE_ADD(DATE_SUB(FROM_UNIXTIME(tm), INTERVAL 1 DAY), INTERVAL 1 MONTH)
|
||||||
|
)
|
||||||
|
)`,
|
||||||
|
firstHourOfDay, lastDayOfMonth, lastDayOfMonth)
|
||||||
|
} else {
|
||||||
|
dt = fmt.Sprintf(`
|
||||||
|
IF (DAYOFMONTH(FROM_UNIXTIME(tm)) <= %d,
|
||||||
|
FROM_UNIXTIME(tm),
|
||||||
|
DATE_ADD(FROM_UNIXTIME(tm), INTERVAL 1 MONTH)
|
||||||
|
)`,
|
||||||
|
lastDayOfMonth)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if firstHourOfDay > 0 {
|
||||||
|
dt = fmt.Sprintf(`
|
||||||
|
IF (HOUR(FROM_UNIXTIME(tm)) >= %d,
|
||||||
|
FROM_UNIXTIME(tm),
|
||||||
|
DATE_SUB(FROM_UNIXTIME(tm), INTERVAL 1 DAY)
|
||||||
|
)`,
|
||||||
|
firstHourOfDay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
320
mqe/index.go
Normal file
320
mqe/index.go
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
package mqe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetYearIndexReq struct {
|
||||||
|
Since int64 `json:"since"` // из фильтр-формы
|
||||||
|
Until int64 `json:"until"` // из фильтр-формы
|
||||||
|
LastDayOfMonth int `json:"lastDayOfMonth"` // например, конец месяца 25 число
|
||||||
|
FirstHourOfDay int `json:"firstHourOfDay"` // например день начинается в 6:00
|
||||||
|
MetricIDs []int64 `json:"metricIDs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MeasureQueryEngine) GetYearIndex(req GetYearIndexReq) (years []int, err error) {
|
||||||
|
if len(req.MetricIDs) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
lte, gte string
|
||||||
|
|
||||||
|
args = []any{
|
||||||
|
req.MetricIDs,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if req.Since > 0 {
|
||||||
|
lte = " AND tm >= ?"
|
||||||
|
args = append(args, req.Since)
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Until > 0 {
|
||||||
|
gte = " AND tm <= ?"
|
||||||
|
args = append(args, req.Until)
|
||||||
|
}
|
||||||
|
|
||||||
|
q := fmt.Sprintf(`
|
||||||
|
SELECT YEAR(FROM_UNIXTIME(tm)) as y
|
||||||
|
FROM f64
|
||||||
|
WHERE metricID IN (?)%s%s
|
||||||
|
GROUP BY y`,
|
||||||
|
lte, gte)
|
||||||
|
|
||||||
|
// fmt.Println(q)
|
||||||
|
// fmt.Printf("%v\n", args)
|
||||||
|
|
||||||
|
err = s.db.ListQuery(&years, q, args...)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Ints(years)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetMonthIndexReq struct {
|
||||||
|
Since int64 `json:"since"` // из фильтр-формы
|
||||||
|
Until int64 `json:"until"` // из фильтр-формы
|
||||||
|
LastDayOfMonth int `json:"lastDayOfMonth"` // например, конец месяца 25 число
|
||||||
|
FirstHourOfDay int `json:"firstHourOfDay"` // например день начинается в 6:00
|
||||||
|
MetricIDs []int64 `json:"metricIDs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type YearMonth struct {
|
||||||
|
Year int `json:"year"`
|
||||||
|
Months []int `json:"months"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MeasureQueryEngine) GetMonthIndex(req GetMonthIndexReq) (years []YearMonth, err error) {
|
||||||
|
if len(req.MetricIDs) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err := getDateTimeFacingFHDAndLDM(req.FirstHourOfDay, req.LastDayOfMonth)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
lte, gte string
|
||||||
|
|
||||||
|
args = []any{
|
||||||
|
req.MetricIDs,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if req.Since > 0 {
|
||||||
|
lte = " AND tm >= ?"
|
||||||
|
args = append(args, req.Since)
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Until > 0 {
|
||||||
|
gte = " AND tm <= ?"
|
||||||
|
args = append(args, req.Until)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ВАЖНО!
|
||||||
|
// Не сортируем средствами MySQL чтобы не создавать временный файл
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
SELECT DATE_FORMAT(%s, '%s') as p
|
||||||
|
FROM f64
|
||||||
|
WHERE metricID IN (?)%s%s
|
||||||
|
GROUP BY p`,
|
||||||
|
dt, "%Y%m", lte, gte)
|
||||||
|
|
||||||
|
var (
|
||||||
|
temp []string
|
||||||
|
yms []int
|
||||||
|
)
|
||||||
|
|
||||||
|
err = s.db.ListQuery(&temp, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, str := range temp {
|
||||||
|
var ym int
|
||||||
|
ym, err = strconv.Atoi(str)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
yms = append(yms, ym)
|
||||||
|
}
|
||||||
|
return IndexYms(yms), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetDayIndexReq struct {
|
||||||
|
Since int64 `json:"since"` // из фильтр-формы
|
||||||
|
Until int64 `json:"until"` // из фильтр-формы
|
||||||
|
FirstHourOfDay int `json:"firstHourOfDay"` // например день начинается в 6:00
|
||||||
|
MetricIDs []int64 `json:"metricIDs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type YearMonthDay struct {
|
||||||
|
Year int `json:"year"`
|
||||||
|
Months []MonthDay `json:"months"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MonthDay struct {
|
||||||
|
Month int `json:"month"`
|
||||||
|
Days []int `json:"days"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MeasureQueryEngine) GetDayIndex(req GetDayIndexReq) (years []YearMonthDay, err error) {
|
||||||
|
if len(req.MetricIDs) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err := getDateTimeFacingFHD(req.FirstHourOfDay)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
lte, gte string
|
||||||
|
|
||||||
|
args = []any{
|
||||||
|
req.MetricIDs,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if req.Since > 0 {
|
||||||
|
lte = " AND tm >= ?"
|
||||||
|
args = append(args, req.Since)
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Until > 0 {
|
||||||
|
gte = " AND tm <= ?"
|
||||||
|
args = append(args, req.Until)
|
||||||
|
}
|
||||||
|
// ВАЖНО!
|
||||||
|
// Не сортируем средствами MySQL чтобы не создавать временный файл
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
SELECT DATE_FORMAT(%s, '%s') as p
|
||||||
|
FROM f64
|
||||||
|
WHERE metricID IN(?)%s%s
|
||||||
|
GROUP BY p`,
|
||||||
|
dt, "%Y%m%d", lte, gte)
|
||||||
|
|
||||||
|
//fmt.Printf("%s\n", query)
|
||||||
|
|
||||||
|
var (
|
||||||
|
temp []string
|
||||||
|
ymds []int
|
||||||
|
)
|
||||||
|
|
||||||
|
err = s.db.ListQuery(&temp, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//pretty.PPrintln("temp", temp)
|
||||||
|
|
||||||
|
for _, str := range temp {
|
||||||
|
var ymd int
|
||||||
|
ymd, err = strconv.Atoi(str)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ymds = append(ymds, ymd)
|
||||||
|
}
|
||||||
|
|
||||||
|
return IndexYmds(ymds), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func IndexYmds(ymds []int) (years []YearMonthDay) {
|
||||||
|
if len(ymds) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Ints(ymds)
|
||||||
|
|
||||||
|
var (
|
||||||
|
months []MonthDay
|
||||||
|
days []int
|
||||||
|
year, month, day int
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, ymd := range ymds {
|
||||||
|
y := ymd / 10000
|
||||||
|
m := (ymd % 10000) / 100
|
||||||
|
d := ymd % 100
|
||||||
|
|
||||||
|
if year == 0 {
|
||||||
|
// инициализация
|
||||||
|
year = y
|
||||||
|
month = m
|
||||||
|
day = d
|
||||||
|
} else {
|
||||||
|
if y != year {
|
||||||
|
days = append(days, day)
|
||||||
|
months = append(months, MonthDay{
|
||||||
|
Month: month,
|
||||||
|
Days: days,
|
||||||
|
})
|
||||||
|
years = append(years, YearMonthDay{
|
||||||
|
Year: year,
|
||||||
|
Months: months,
|
||||||
|
})
|
||||||
|
year = y
|
||||||
|
month = m
|
||||||
|
day = d
|
||||||
|
months = nil
|
||||||
|
days = nil
|
||||||
|
} else if m != month {
|
||||||
|
days = append(days, day)
|
||||||
|
months = append(months, MonthDay{
|
||||||
|
Month: month,
|
||||||
|
Days: days,
|
||||||
|
})
|
||||||
|
month = m
|
||||||
|
day = d
|
||||||
|
days = nil
|
||||||
|
} else if d != day {
|
||||||
|
days = append(days, day)
|
||||||
|
day = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
days = append(days, day)
|
||||||
|
months = append(months, MonthDay{
|
||||||
|
Month: month,
|
||||||
|
Days: days,
|
||||||
|
})
|
||||||
|
years = append(years, YearMonthDay{
|
||||||
|
Year: year,
|
||||||
|
Months: months,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func IndexYms(yms []int) (years []YearMonth) {
|
||||||
|
if len(yms) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Ints(yms)
|
||||||
|
|
||||||
|
var (
|
||||||
|
months []int
|
||||||
|
year, month int
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, ym := range yms {
|
||||||
|
y := ym / 100
|
||||||
|
m := ym % 100
|
||||||
|
|
||||||
|
if year == 0 {
|
||||||
|
// инициализация
|
||||||
|
year = y
|
||||||
|
month = m
|
||||||
|
} else {
|
||||||
|
if y != year {
|
||||||
|
months = append(months, month)
|
||||||
|
years = append(years, YearMonth{
|
||||||
|
Year: year,
|
||||||
|
Months: months,
|
||||||
|
})
|
||||||
|
year = y
|
||||||
|
month = m
|
||||||
|
months = nil
|
||||||
|
} else if m != month {
|
||||||
|
months = append(months, month)
|
||||||
|
month = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
months = append(months, month)
|
||||||
|
years = append(years, YearMonth{
|
||||||
|
Year: year,
|
||||||
|
Months: months,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//SELECT DATE_FORMAT(FROM_UNIXTIME(tm), '%Y%m%d') as p FROM f64 WHERE metricID IN (50) AND tm >= 1700863200 AND tm <= 1700949599 GROUP BY p;
|
||||||
39
mqe/index_test.go
Normal file
39
mqe/index_test.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package mqe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gordenko.dev/dima/pretty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDayIndex(t *testing.T) {
|
||||||
|
index := IndexYmds([]int{
|
||||||
|
20221201,
|
||||||
|
20221202,
|
||||||
|
20230110,
|
||||||
|
20230210,
|
||||||
|
20230330,
|
||||||
|
20230331,
|
||||||
|
20240101,
|
||||||
|
20240102,
|
||||||
|
20240103,
|
||||||
|
})
|
||||||
|
|
||||||
|
pretty.Println(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMonthIndex(t *testing.T) {
|
||||||
|
index := IndexYms([]int{
|
||||||
|
202211,
|
||||||
|
202212,
|
||||||
|
202301,
|
||||||
|
202302,
|
||||||
|
202303,
|
||||||
|
202304,
|
||||||
|
202404,
|
||||||
|
202405,
|
||||||
|
202406,
|
||||||
|
})
|
||||||
|
|
||||||
|
pretty.Println(index)
|
||||||
|
}
|
||||||
226
mqe/instant.go
Normal file
226
mqe/instant.go
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
package mqe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CurrentValue struct {
|
||||||
|
MetricID uint32
|
||||||
|
Time int64 `json:"t"`
|
||||||
|
Value float64 `json:"v"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MeasureQueryEngine) ListCurrentValues(metricIDs []uint32) (result []CurrentValue, err error) {
|
||||||
|
driver := s.db.Driver()
|
||||||
|
|
||||||
|
for _, metricID := range metricIDs {
|
||||||
|
var rows *sql.Rows
|
||||||
|
rows, err = driver.Query(`
|
||||||
|
SELECT tm, value
|
||||||
|
FROM f64
|
||||||
|
WHERE metricID=?
|
||||||
|
ORDER BY tm DESC
|
||||||
|
LIMIT 1`,
|
||||||
|
metricID)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var (
|
||||||
|
tm int64
|
||||||
|
value float64
|
||||||
|
)
|
||||||
|
err = rows.Scan(&tm, &value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result = append(result, CurrentValue{
|
||||||
|
MetricID: metricID,
|
||||||
|
Time: tm,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListInstantMeasures - cписок показаний мгновенных метрик (Температура, Давление, Расход)
|
||||||
|
// за за интервал без группировки
|
||||||
|
func (s *MeasureQueryEngine) listInstantMeasures(req MetricMeasuresFilter) (_ []Measure, err error) {
|
||||||
|
rows, err := s.db.Driver().Query(`
|
||||||
|
SELECT tm, value
|
||||||
|
FROM f64
|
||||||
|
WHERE metricID=? AND tm BETWEEN ? AND ?
|
||||||
|
ORDER BY tm ASC`,
|
||||||
|
req.MetricID, req.Since, req.Until)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var result []Measure
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var (
|
||||||
|
tm int64
|
||||||
|
value float64
|
||||||
|
)
|
||||||
|
err = rows.Scan(&tm, &value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result = append(result, Measure{
|
||||||
|
Time: tm,
|
||||||
|
Values: []float64{
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AggregatedInstantMeasuresIn struct {
|
||||||
|
MetricID int64 `json:"metricID"`
|
||||||
|
Since int64 `json:"since"`
|
||||||
|
Until int64 `json:"until"`
|
||||||
|
GroupBy GroupBy `json:"groupBy"`
|
||||||
|
Flags byte `json:"flags"` // из настроек по умолчанию либо из запроса
|
||||||
|
LastDayOfMonth int `json:"lastDayOfMonth"` // например, конец месяца 25 число
|
||||||
|
FirstHourOfDay int `json:"firstHourOfDay"` // например день начинается в 6:00
|
||||||
|
}
|
||||||
|
|
||||||
|
// Заполняет поля Period и Values (от 1 до 3 значений) в структуре AggregatedMeasure
|
||||||
|
func (s *MeasureQueryEngine) listAggregatedInstantMeasures(req AggregatedInstantMeasuresIn) (_ []AggregatedMeasure, err error) {
|
||||||
|
var (
|
||||||
|
groupByFormat string
|
||||||
|
parsePeriodLayout string
|
||||||
|
|
||||||
|
result []AggregatedMeasure
|
||||||
|
)
|
||||||
|
|
||||||
|
switch req.GroupBy {
|
||||||
|
case ByHour:
|
||||||
|
groupByFormat = "%Y%m%d%H"
|
||||||
|
parsePeriodLayout = hourPeriodLayout
|
||||||
|
|
||||||
|
case ByDay:
|
||||||
|
groupByFormat = "%Y%m%d"
|
||||||
|
parsePeriodLayout = dayPeriodLayout
|
||||||
|
|
||||||
|
case ByMonth:
|
||||||
|
groupByFormat = "%Y%m"
|
||||||
|
parsePeriodLayout = monthPeriodLayout
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Защита от дурака
|
||||||
|
err = fmt.Errorf("unknown GroupBy: %s", req.GroupBy)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
valuesQty := 0
|
||||||
|
values := ""
|
||||||
|
|
||||||
|
if (req.Flags & AggregateMin) == AggregateMin {
|
||||||
|
values += " MIN(value),"
|
||||||
|
valuesQty++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.Flags & AggregateMax) == AggregateMax {
|
||||||
|
values += " MAX(value),"
|
||||||
|
valuesQty++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.Flags & AggregateAvg) == AggregateAvg {
|
||||||
|
values += " AVG(value),"
|
||||||
|
valuesQty++
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err := getPeriodDateTime(req.GroupBy, req.LastDayOfMonth, req.FirstHourOfDay)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
SELECT%s DATE_FORMAT(%s, '%s') as periodStr
|
||||||
|
FROM f64
|
||||||
|
WHERE metricID=? AND tm BETWEEN ? AND ?
|
||||||
|
GROUP BY periodStr
|
||||||
|
ORDER BY periodStr ASC`,
|
||||||
|
values, dt, groupByFormat)
|
||||||
|
|
||||||
|
rows, err := s.db.Driver().Query(query, req.MetricID, req.Since, req.Until)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var (
|
||||||
|
m = AggregatedMeasure{
|
||||||
|
Values: make([]float64, valuesQty),
|
||||||
|
}
|
||||||
|
|
||||||
|
periodStr string
|
||||||
|
period time.Time
|
||||||
|
|
||||||
|
valueIdx int
|
||||||
|
dest []interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (req.Flags & AggregateMin) == AggregateMin {
|
||||||
|
dest = append(dest, &m.Values[valueIdx])
|
||||||
|
valueIdx++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.Flags & AggregateMax) == AggregateMax {
|
||||||
|
dest = append(dest, &m.Values[valueIdx])
|
||||||
|
valueIdx++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.Flags & AggregateAvg) == AggregateAvg {
|
||||||
|
dest = append(dest, &m.Values[valueIdx])
|
||||||
|
}
|
||||||
|
|
||||||
|
dest = append(dest, &periodStr)
|
||||||
|
|
||||||
|
err = rows.Scan(dest...)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
period, err = time.ParseInLocation(parsePeriodLayout, periodStr, s.location)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("time.ParseInLocation: %s; layout=%q; str=%q",
|
||||||
|
err, parsePeriodLayout, periodStr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Period = period.Unix()
|
||||||
|
|
||||||
|
result = append(result, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
161
mqe/mqe.go
Normal file
161
mqe/mqe.go
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
package mqe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gordenko.dev/dima/qx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GroupBy string
|
||||||
|
|
||||||
|
// Для парсинга since и until
|
||||||
|
const (
|
||||||
|
NoAggregateFunc byte = 1
|
||||||
|
AggregateMin byte = 2
|
||||||
|
AggregateMax byte = 4
|
||||||
|
AggregateAvg byte = 8
|
||||||
|
|
||||||
|
ByHour GroupBy = "h"
|
||||||
|
ByDay GroupBy = "d"
|
||||||
|
ByMonth GroupBy = "m"
|
||||||
|
|
||||||
|
hourPeriodLayout = "2006010215"
|
||||||
|
dayPeriodLayout = "20060102"
|
||||||
|
monthPeriodLayout = "200601"
|
||||||
|
|
||||||
|
dateLayout = "2006-01-02"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MetricMeasuresFilter struct {
|
||||||
|
MetricID int64 `json:"metricID"`
|
||||||
|
Since int64 `json:"since"` // уже учтен firstHourOfDay
|
||||||
|
Until int64 `json:"until"` // уже учтен firstHourOfDay
|
||||||
|
}
|
||||||
|
|
||||||
|
type MeasureQueryEngine struct {
|
||||||
|
db *qx.Db
|
||||||
|
location *time.Location
|
||||||
|
cumulativeCh chan *CumulativeTask
|
||||||
|
instantCh chan *InstantTask
|
||||||
|
aggregatedCumulativeCh chan *AggregatedCumulativeTask
|
||||||
|
aggregatedInstantCh chan *AggregatedInstantTask
|
||||||
|
cumulativeTotalCh chan *CumulativeTotalTask
|
||||||
|
metricReadingsCh chan *MetricReadingsTask
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Db *qx.Db
|
||||||
|
Location *time.Location
|
||||||
|
MinWorkers int
|
||||||
|
QueueSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(opt Options) *MeasureQueryEngine {
|
||||||
|
if opt.Db == nil {
|
||||||
|
panic("Db option is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.Location == nil {
|
||||||
|
panic("Location option is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.MinWorkers <= 0 {
|
||||||
|
panic("MinWorkers option is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.QueueSize <= 0 {
|
||||||
|
panic("QueueSize option is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
s := new(MeasureQueryEngine)
|
||||||
|
s.db = opt.Db
|
||||||
|
s.location = opt.Location
|
||||||
|
//
|
||||||
|
s.cumulativeCh = make(chan *CumulativeTask, opt.QueueSize)
|
||||||
|
s.instantCh = make(chan *InstantTask, opt.QueueSize)
|
||||||
|
s.aggregatedCumulativeCh = make(chan *AggregatedCumulativeTask, opt.QueueSize)
|
||||||
|
s.aggregatedInstantCh = make(chan *AggregatedInstantTask, opt.QueueSize)
|
||||||
|
s.cumulativeTotalCh = make(chan *CumulativeTotalTask, opt.QueueSize)
|
||||||
|
s.metricReadingsCh = make(chan *MetricReadingsTask, opt.QueueSize)
|
||||||
|
|
||||||
|
//
|
||||||
|
for i := 0; i < opt.MinWorkers; i++ {
|
||||||
|
go s.worker()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MeasureQueryEngine) worker() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case task := <-s.cumulativeCh:
|
||||||
|
task.Result, task.Err = s.listCumulativeMeasures(task.In)
|
||||||
|
task.WaitGroup.Done()
|
||||||
|
|
||||||
|
case task := <-s.instantCh:
|
||||||
|
task.Result, task.Err = s.listInstantMeasures(task.In)
|
||||||
|
task.WaitGroup.Done()
|
||||||
|
|
||||||
|
case task := <-s.aggregatedCumulativeCh:
|
||||||
|
task.Result, task.Err = s.listAggregatedCumulativeMeasures(task.In)
|
||||||
|
task.WaitGroup.Done()
|
||||||
|
|
||||||
|
case task := <-s.aggregatedInstantCh:
|
||||||
|
task.Result, task.Err = s.listAggregatedInstantMeasures(task.In)
|
||||||
|
task.WaitGroup.Done()
|
||||||
|
|
||||||
|
case task := <-s.cumulativeTotalCh:
|
||||||
|
task.Result, task.Err = s.getCumulativeTotal(task.In)
|
||||||
|
task.WaitGroup.Done()
|
||||||
|
|
||||||
|
case task := <-s.metricReadingsCh:
|
||||||
|
task.Result, task.Err = s.listMetricReadings(task.In)
|
||||||
|
task.WaitGroup.Done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
type CumulativeTask struct {
|
||||||
|
In MetricMeasuresFilter
|
||||||
|
WaitGroup *sync.WaitGroup
|
||||||
|
Result []Measure
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstantTask struct {
|
||||||
|
In MetricMeasuresFilter
|
||||||
|
WaitGroup *sync.WaitGroup
|
||||||
|
Result []Measure
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type AggregatedCumulativeTask struct {
|
||||||
|
In AggregatedMeasuresFilter
|
||||||
|
WaitGroup *sync.WaitGroup
|
||||||
|
Result []AggregatedMeasure
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type AggregatedInstantTask struct {
|
||||||
|
In AggregatedInstantMeasuresIn
|
||||||
|
WaitGroup *sync.WaitGroup
|
||||||
|
Result []AggregatedMeasure
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type MetricReadingsTask struct {
|
||||||
|
In MetricMeasuresFilter
|
||||||
|
WaitGroup *sync.WaitGroup
|
||||||
|
Result MetricReadings
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type CumulativeTotalTask struct {
|
||||||
|
In getCumulativeTotalFilter
|
||||||
|
WaitGroup *sync.WaitGroup
|
||||||
|
Result *RangeTotal
|
||||||
|
Err error
|
||||||
|
}
|
||||||
283
mqe/newreq.go
Normal file
283
mqe/newreq.go
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
package mqe
|
||||||
|
|
||||||
|
// НОВАЯ ВЕРСИЯ
|
||||||
|
|
||||||
|
// type MetricsDataFilter2 struct {
|
||||||
|
// Since time.Time `json:"since"`
|
||||||
|
// Until time.Time `json:"until"`
|
||||||
|
// GroupBy string `json:"groupBy"`
|
||||||
|
|
||||||
|
// InstantMetrics []InstantMetricFilter // virtualMetricID + aggregateFuncs
|
||||||
|
// CumulativeMetrics []CumulativeMetricFilter
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type InstantMetricFilter struct {
|
||||||
|
// MetricID int64
|
||||||
|
// AggregateFuncs byte
|
||||||
|
// LastDayOfMonth int // например, конец месяца 25 число
|
||||||
|
// FirstHourOfDay int // например день начинается в 6:00
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type CumulativeMetricFilter struct {
|
||||||
|
// MetricID int64
|
||||||
|
// LastDayOfMonth int // например, конец месяца 25 число
|
||||||
|
// FirstHourOfDay int // например день начинается в 6:00
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (s *MeasureQueryEngine) SelectMetricsDataInParallel2(req MetricsDataFilter2) (_ MetricsDataResultMaps, err error) {
|
||||||
|
// pretty.PPrintln("SelectMetricsDataInParallel2", req)
|
||||||
|
// // Строгая валидация
|
||||||
|
|
||||||
|
// if req.Since.IsZero() {
|
||||||
|
// err = fmt.Errorf("Since option is required")
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if req.Until.IsZero() {
|
||||||
|
// err = fmt.Errorf("Until option is required")
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if req.Since.After(req.Until) {
|
||||||
|
// err = fmt.Errorf("Wrong time range: Since %d after Until %d", req.Since.Unix(), req.Until.Unix())
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// switch req.GroupBy {
|
||||||
|
// case "h", "d", "m", "":
|
||||||
|
// // pass
|
||||||
|
// default:
|
||||||
|
// err = fmt.Errorf("Wrong groupBy option value: %s", req.GroupBy)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// for _, mf := range req.InstantMetrics {
|
||||||
|
// if mf.MetricID == 0 {
|
||||||
|
// err = fmt.Errorf("Empty instant metric's MetricID: %d", mf.MetricID)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if mf.FirstHourOfDay < 0 || mf.FirstHourOfDay > 23 {
|
||||||
|
// err = fmt.Errorf("Wrong instant metric's FirstHourOfDay option value: %d", mf.FirstHourOfDay)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if mf.LastDayOfMonth < 0 || mf.LastDayOfMonth > 28 {
|
||||||
|
// err = fmt.Errorf("Wrong instant metric's LastDayOfMonth option value: %d", mf.LastDayOfMonth)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// for _, mf := range req.CumulativeMetrics {
|
||||||
|
// if mf.MetricID == 0 {
|
||||||
|
// err = fmt.Errorf("Empty cumulative metric's MetricID: %d", mf.MetricID)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if mf.FirstHourOfDay < 0 || mf.FirstHourOfDay > 23 {
|
||||||
|
// err = fmt.Errorf("Wrong cumulative metric's FirstHourOfDay option value: %d", mf.FirstHourOfDay)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if mf.LastDayOfMonth < 0 || mf.LastDayOfMonth > 28 {
|
||||||
|
// err = fmt.Errorf("Wrong cumulative metric's LastDayOfMonth option value: %d", mf.LastDayOfMonth)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Корректируем, ВСЕГДА на первую и последнюю секунду суток
|
||||||
|
// since := timeutil.FirstSecondInPeriod(req.Since, "d")
|
||||||
|
// until := timeutil.LastSecondInPeriod(req.Until, "d")
|
||||||
|
|
||||||
|
// fmt.Printf(`SelectMetricsDataInParallel2: {
|
||||||
|
// Since: %s
|
||||||
|
// Until: %s
|
||||||
|
// GroupBy: %s
|
||||||
|
// }
|
||||||
|
// `, since, until, req.GroupBy)
|
||||||
|
|
||||||
|
// // Важно вирутальная метрика зависит от реальных, которые к объекту могут быть
|
||||||
|
// // привязаны или нет. Если нет - не возвращать.
|
||||||
|
|
||||||
|
// var (
|
||||||
|
// unexpectedErrors []error
|
||||||
|
// errorMutex sync.Mutex
|
||||||
|
|
||||||
|
// wg sync.WaitGroup
|
||||||
|
|
||||||
|
// resultMaps = MetricsDataResultMaps{
|
||||||
|
// AggregatedCumulative: make(map[int64][]CalculatedAggregatedMeasure),
|
||||||
|
// AggregatedByFuncInstant: make(map[int64][]AggregatedByFuncInstantMeasure),
|
||||||
|
// Cumulative: make(map[int64][]CalculatedMeasure),
|
||||||
|
// Instant: make(map[int64][]CalculatedMeasure),
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
|
// wg.Add(len(req.CumulativeMetrics) + len(req.InstantMetrics))
|
||||||
|
|
||||||
|
// if req.GroupBy != "" {
|
||||||
|
// // AGGREGATED
|
||||||
|
|
||||||
|
// // CUMULATIVE
|
||||||
|
// for _, metricFilter := range req.CumulativeMetrics {
|
||||||
|
// go func(mf CumulativeMetricFilter) {
|
||||||
|
// // У каждой метрики индивидуальные настройки,
|
||||||
|
// // ибо метрики принадлежат разным объектам!!!
|
||||||
|
// metricSince := since
|
||||||
|
// metricUntil := until
|
||||||
|
|
||||||
|
// if mf.FirstHourOfDay > 0 {
|
||||||
|
// metricSince = since.Add(time.Duration(mf.FirstHourOfDay) * time.Hour)
|
||||||
|
// metricUntil = until.Add(time.Duration(mf.FirstHourOfDay) * time.Hour)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// result, err := s.listAggregatedCumulativeMeasures(AggregatedMeasuresFilter{
|
||||||
|
// MetricID: mf.MetricID,
|
||||||
|
// Since: metricSince.Unix(),
|
||||||
|
// Until: metricUntil.Unix(),
|
||||||
|
// GroupBy: req.GroupBy,
|
||||||
|
// LastDayOfMonth: mf.LastDayOfMonth,
|
||||||
|
// FirstHourOfDay: mf.FirstHourOfDay,
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// err = fmt.Errorf("ListAggregatedCumulativeMeasures: %s", err)
|
||||||
|
|
||||||
|
// errorMutex.Lock()
|
||||||
|
// unexpectedErrors = append(unexpectedErrors, err)
|
||||||
|
// errorMutex.Unlock()
|
||||||
|
// } else {
|
||||||
|
// errorMutex.Lock()
|
||||||
|
// resultMaps.AggregatedCumulative[mf.MetricID] = result
|
||||||
|
// errorMutex.Unlock()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// wg.Done()
|
||||||
|
// }(metricFilter)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // INSTANT
|
||||||
|
// for _, metricFilter := range req.InstantMetrics {
|
||||||
|
// go func(mf InstantMetricFilter) {
|
||||||
|
// // У каждой метрики индивидуальные настройки,
|
||||||
|
// // ибо метрики принадлежат разным объектам!!!
|
||||||
|
// metricSince := since
|
||||||
|
// metricUntil := until
|
||||||
|
|
||||||
|
// if mf.FirstHourOfDay > 0 {
|
||||||
|
// metricSince = since.Add(time.Duration(mf.FirstHourOfDay) * time.Hour)
|
||||||
|
// metricUntil = until.Add(time.Duration(mf.FirstHourOfDay) * time.Hour)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// result, err := s.listAggregatedByFuncsInstantMeasures(AggregatedByFuncInstantMeasuresFilter{
|
||||||
|
// MetricID: mf.MetricID,
|
||||||
|
// Since: metricSince.Unix(),
|
||||||
|
// Until: metricUntil.Unix(),
|
||||||
|
// GroupBy: req.GroupBy,
|
||||||
|
// LastDayOfMonth: mf.LastDayOfMonth,
|
||||||
|
// FirstHourOfDay: mf.FirstHourOfDay,
|
||||||
|
// Flags: mf.AggregateFuncs, // ФЛАГИ !!!
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// err = fmt.Errorf("ListAggregatedByFuncInstantMeasures: %s", err)
|
||||||
|
|
||||||
|
// errorMutex.Lock()
|
||||||
|
// unexpectedErrors = append(unexpectedErrors, err)
|
||||||
|
// errorMutex.Unlock()
|
||||||
|
// } else {
|
||||||
|
// errorMutex.Lock()
|
||||||
|
// resultMaps.AggregatedByFuncInstant[mf.MetricID] = result
|
||||||
|
// errorMutex.Unlock()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// wg.Done()
|
||||||
|
// }(metricFilter)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// } else {
|
||||||
|
// // NON AGGREGATED
|
||||||
|
|
||||||
|
// // CUMULATIVE
|
||||||
|
// for _, metricFilter := range req.CumulativeMetrics {
|
||||||
|
// go func(mf CumulativeMetricFilter) {
|
||||||
|
// // У каждой метрики индивидуальные настройки,
|
||||||
|
// // ибо метрики принадлежат разным объектам!!!
|
||||||
|
// metricSince := since
|
||||||
|
// metricUntil := until
|
||||||
|
|
||||||
|
// if mf.FirstHourOfDay > 0 {
|
||||||
|
// metricSince = since.Add(time.Duration(mf.FirstHourOfDay) * time.Hour)
|
||||||
|
// metricUntil = until.Add(time.Duration(mf.FirstHourOfDay) * time.Hour)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// result, err := s.listCumulativeMeasures(MeasuresFilter{
|
||||||
|
// MetricID: mf.MetricID,
|
||||||
|
// Since: metricSince.Unix(),
|
||||||
|
// Until: metricUntil.Unix(),
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// err = fmt.Errorf("ListCumulativeMeasures: %s", err)
|
||||||
|
|
||||||
|
// errorMutex.Lock()
|
||||||
|
// unexpectedErrors = append(unexpectedErrors, err)
|
||||||
|
// errorMutex.Unlock()
|
||||||
|
// } else {
|
||||||
|
// errorMutex.Lock()
|
||||||
|
// resultMaps.Cumulative[mf.MetricID] = result
|
||||||
|
// errorMutex.Unlock()
|
||||||
|
// }
|
||||||
|
// wg.Done()
|
||||||
|
// }(metricFilter)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // INSTANT
|
||||||
|
// for _, metricFilter := range req.InstantMetrics {
|
||||||
|
// go func(mf InstantMetricFilter) {
|
||||||
|
// // У каждой метрики индивидуальные настройки,
|
||||||
|
// // ибо метрики принадлежат разным объектам!!!
|
||||||
|
// metricSince := since
|
||||||
|
// metricUntil := until
|
||||||
|
|
||||||
|
// if mf.FirstHourOfDay > 0 {
|
||||||
|
// metricSince = since.Add(time.Duration(mf.FirstHourOfDay) * time.Hour)
|
||||||
|
// metricUntil = until.Add(time.Duration(mf.FirstHourOfDay) * time.Hour)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// result, err := s.listInstantMeasures(MeasuresFilter{
|
||||||
|
// MetricID: mf.MetricID,
|
||||||
|
// Since: metricSince.Unix(),
|
||||||
|
// Until: metricUntil.Unix(),
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// err = fmt.Errorf("ListInstantMeasures: %s", err)
|
||||||
|
|
||||||
|
// errorMutex.Lock()
|
||||||
|
// unexpectedErrors = append(unexpectedErrors, err)
|
||||||
|
// errorMutex.Unlock()
|
||||||
|
// } else {
|
||||||
|
// errorMutex.Lock()
|
||||||
|
// resultMaps.Instant[mf.MetricID] = result
|
||||||
|
// errorMutex.Unlock()
|
||||||
|
// }
|
||||||
|
// wg.Done()
|
||||||
|
// }(metricFilter)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// wg.Wait()
|
||||||
|
|
||||||
|
// // Проверяем что все запросы завершились удачно
|
||||||
|
// if len(unexpectedErrors) > 0 {
|
||||||
|
// // Нумеруем сообщения об ошибках и склеиваем в одно большое сообщение
|
||||||
|
// var errorStrings []string
|
||||||
|
|
||||||
|
// for idx, err := range unexpectedErrors {
|
||||||
|
// errorStrings = append(errorStrings, fmt.Sprintf("\n#%d %s", idx+1, err))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// err = fmt.Errorf("%d errors occured:%s", len(unexpectedErrors),
|
||||||
|
// strings.Join(errorStrings, ""))
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return resultMaps, nil
|
||||||
|
// }
|
||||||
172
mqe/period_calculator.go
Normal file
172
mqe/period_calculator.go
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
package mqe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gordenko.dev/dima/qb/timeutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PeriodCalculatorOptions struct {
|
||||||
|
GroupBy GroupBy
|
||||||
|
LastDayOfMonth int
|
||||||
|
FirstHourOfDay int
|
||||||
|
Since time.Time
|
||||||
|
Until time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPeriodCalculator(opt PeriodCalculatorOptions) PeriodCalculator {
|
||||||
|
switch opt.GroupBy {
|
||||||
|
case ByHour, ByDay, ByMonth:
|
||||||
|
// pass
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("bug: unknown groupBy %q", opt.GroupBy))
|
||||||
|
}
|
||||||
|
|
||||||
|
s := PeriodCalculator{
|
||||||
|
groupBy: opt.GroupBy,
|
||||||
|
lastDayOfMonth: opt.LastDayOfMonth,
|
||||||
|
firstHourOfDay: opt.FirstHourOfDay,
|
||||||
|
}
|
||||||
|
s.sincePeriod = s.TimeToPeriod(opt.Since)
|
||||||
|
s.untilPeriod = s.TimeToPeriod(opt.Until)
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type PeriodCalculator struct {
|
||||||
|
groupBy GroupBy
|
||||||
|
lastDayOfMonth int
|
||||||
|
firstHourOfDay int
|
||||||
|
sincePeriod time.Time
|
||||||
|
untilPeriod time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PeriodCalculator) IsExtendedSincePeriod(period time.Time) bool {
|
||||||
|
return period.Before(s.sincePeriod)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PeriodCalculator) IsExtendedUntilPeriod(period time.Time) bool {
|
||||||
|
return period.After(s.untilPeriod)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PeriodCalculator) NextPeriod(period time.Time) time.Time {
|
||||||
|
switch s.groupBy {
|
||||||
|
case ByHour:
|
||||||
|
return period.Add(time.Hour)
|
||||||
|
|
||||||
|
case ByDay:
|
||||||
|
return period.AddDate(0, 0, 1)
|
||||||
|
|
||||||
|
case ByMonth:
|
||||||
|
return period.AddDate(0, 1, 0)
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("bug: unknown groupBy %q", s.groupBy))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PeriodCalculator) IsPeriodCorrectEnds(period time.Time, lastMeasureTime int64) bool {
|
||||||
|
// по умолчанию
|
||||||
|
tm := timeutil.LastSecondInPeriod(period, string(s.groupBy))
|
||||||
|
|
||||||
|
switch s.groupBy {
|
||||||
|
case ByDay:
|
||||||
|
if s.firstHourOfDay > 0 {
|
||||||
|
// Было 23:59, стало - 01:59
|
||||||
|
tm = tm.Add(time.Duration(s.firstHourOfDay) * time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
case ByMonth:
|
||||||
|
if s.lastDayOfMonth == 0 {
|
||||||
|
if s.firstHourOfDay > 0 {
|
||||||
|
// Было 23:59, стало - 01:59
|
||||||
|
tm = tm.Add(time.Duration(s.firstHourOfDay) * time.Hour)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if s.firstHourOfDay > 0 {
|
||||||
|
// last day 28, first hour 2 превращается в day 29 01:59:59
|
||||||
|
tm = time.Date(
|
||||||
|
tm.Year(),
|
||||||
|
tm.Month(),
|
||||||
|
s.lastDayOfMonth+1,
|
||||||
|
s.firstHourOfDay-1,
|
||||||
|
59,
|
||||||
|
59,
|
||||||
|
0,
|
||||||
|
tm.Location(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// last day 23:59:59
|
||||||
|
tm = time.Date(
|
||||||
|
tm.Year(),
|
||||||
|
tm.Month(),
|
||||||
|
s.lastDayOfMonth,
|
||||||
|
23,
|
||||||
|
59,
|
||||||
|
59,
|
||||||
|
0,
|
||||||
|
tm.Location(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := tm.Add(-179 * time.Second).Unix()
|
||||||
|
|
||||||
|
if lastMeasureTime >= timestamp && lastMeasureTime <= tm.Unix() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
//fmt.Printf("Ends: lastMeasureTime=%s, timetsamp=%s\n", time.Unix(lastMeasureTime, 0), time.Unix(timestamp, 0))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PeriodCalculator) TimeToPeriod(tm time.Time) (period time.Time) {
|
||||||
|
switch s.groupBy {
|
||||||
|
case ByDay:
|
||||||
|
if s.firstHourOfDay > 0 {
|
||||||
|
if tm.Hour() < s.firstHourOfDay {
|
||||||
|
tm = tm.AddDate(0, 0, -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case ByMonth:
|
||||||
|
if s.lastDayOfMonth > 0 {
|
||||||
|
if s.lastDayOfMonth > 28 {
|
||||||
|
// На всякий случай добавляем защиту от дурака, ибо модификация даты
|
||||||
|
// в запросе (+1 месяц) для 29, 30 и 31 чисел сделает запрос неверным.
|
||||||
|
panic(fmt.Sprintf("LastDayOfMonth: %d, max allowed value is 28", s.lastDayOfMonth))
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.firstHourOfDay > 0 {
|
||||||
|
if tm.Hour() >= s.firstHourOfDay {
|
||||||
|
if tm.Day() > s.lastDayOfMonth {
|
||||||
|
// add 1 month
|
||||||
|
tm = tm.AddDate(0, 1, 0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// firstHourOfDay показывает что текущий день еще не наступил,
|
||||||
|
// поэтому day - 1
|
||||||
|
if (tm.Day() - 1) <= s.lastDayOfMonth {
|
||||||
|
tm = tm.AddDate(0, 0, -1)
|
||||||
|
} else {
|
||||||
|
// - 1 day, + 1 month
|
||||||
|
tm = tm.AddDate(0, 0, -1).AddDate(0, 1, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if tm.Day() > s.lastDayOfMonth {
|
||||||
|
tm = tm.AddDate(0, 1, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if s.firstHourOfDay > 0 {
|
||||||
|
if tm.Hour() < s.firstHourOfDay {
|
||||||
|
tm = tm.AddDate(0, 0, -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return timeutil.FirstSecondInPeriod(tm, string(s.groupBy))
|
||||||
|
}
|
||||||
168
mqe/period_producers.go
Normal file
168
mqe/period_producers.go
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
package mqe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type periodProducer interface {
|
||||||
|
Next() (time.Time, PeriodBound, PeriodBound, bool, error)
|
||||||
|
Last() (PeriodBound, bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PeriodBound struct {
|
||||||
|
Time int64
|
||||||
|
Value float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type PeriodProducer struct {
|
||||||
|
metricID int64
|
||||||
|
parsePeriodLayout string
|
||||||
|
location *time.Location
|
||||||
|
until int64
|
||||||
|
corrections []_f64Correction
|
||||||
|
rows *sql.Rows
|
||||||
|
tx *sql.Tx
|
||||||
|
}
|
||||||
|
|
||||||
|
type PeriodProducerOptions struct {
|
||||||
|
MetricID int64
|
||||||
|
ParsePeriodLayout string
|
||||||
|
Location *time.Location
|
||||||
|
Until int64
|
||||||
|
Corrections []_f64Correction
|
||||||
|
Rows *sql.Rows
|
||||||
|
Tx *sql.Tx
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPeriodProducer(opt PeriodProducerOptions) *PeriodProducer {
|
||||||
|
if opt.Location == nil {
|
||||||
|
panic("Location option is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.MetricID == 0 {
|
||||||
|
panic("MetricID option is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.ParsePeriodLayout == "" {
|
||||||
|
panic("ParsePeriodLayout option is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.Until == 0 {
|
||||||
|
panic("Until option is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.Rows == nil {
|
||||||
|
panic("Rows option is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.Tx == nil {
|
||||||
|
panic("Tx option is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
s := new(PeriodProducer)
|
||||||
|
s.metricID = opt.MetricID
|
||||||
|
s.parsePeriodLayout = opt.ParsePeriodLayout
|
||||||
|
s.location = opt.Location
|
||||||
|
s.until = opt.Until
|
||||||
|
s.corrections = opt.Corrections
|
||||||
|
s.rows = opt.Rows
|
||||||
|
s.tx = opt.Tx
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PeriodProducer) Next() (period time.Time, first PeriodBound, last PeriodBound, found bool, err error) {
|
||||||
|
var frameStr string
|
||||||
|
|
||||||
|
hasNext := s.rows.Next()
|
||||||
|
if hasNext {
|
||||||
|
err = s.rows.Scan(&frameStr, &first.Time, &first.Value, &last.Time, &last.Value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
first.Value = applyCorrectionsToMeasure(s.corrections, first.Time, first.Value)
|
||||||
|
last.Value = applyCorrectionsToMeasure(s.corrections, last.Time, last.Value)
|
||||||
|
|
||||||
|
period, err = time.ParseInLocation(s.parsePeriodLayout, frameStr, s.location)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("time.ParseInLocation: %s; layout=%q; str=%q",
|
||||||
|
err, s.parsePeriodLayout, frameStr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
found = true
|
||||||
|
} else {
|
||||||
|
err = s.rows.Err()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PeriodProducer) Last() (next PeriodBound, found bool, err error) {
|
||||||
|
// Находим следующее показание метрики.
|
||||||
|
err = s.tx.QueryRow(`
|
||||||
|
SELECT tm, value, true
|
||||||
|
FROM f64
|
||||||
|
WHERE metricID=? AND tm > ?
|
||||||
|
ORDER BY tm ASC
|
||||||
|
LIMIT 1`,
|
||||||
|
s.metricID, s.until).Scan(&next.Time, &next.Value, &found)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Нет записей
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
next.Value = applyCorrectionsToMeasure(s.corrections, next.Time, next.Value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ДЛЯ ТЕСТИРОВАНИЯ
|
||||||
|
|
||||||
|
type stubPeriod struct {
|
||||||
|
Period time.Time
|
||||||
|
First PeriodBound
|
||||||
|
Last PeriodBound
|
||||||
|
}
|
||||||
|
|
||||||
|
type StubPeriodProducerOptions struct {
|
||||||
|
Last *PeriodBound
|
||||||
|
Periods []stubPeriod
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStubPeriodProducer(opt StubPeriodProducerOptions) *StubPeriodProducer {
|
||||||
|
s := new(StubPeriodProducer)
|
||||||
|
s.last = opt.Last
|
||||||
|
s.periods = opt.Periods
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type StubPeriodProducer struct {
|
||||||
|
last *PeriodBound // для отдельного запроса
|
||||||
|
periods []stubPeriod
|
||||||
|
idx int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StubPeriodProducer) Next() (period time.Time, first PeriodBound, last PeriodBound, found bool, err error) {
|
||||||
|
if s.idx < len(s.periods) {
|
||||||
|
p := s.periods[s.idx]
|
||||||
|
s.idx++
|
||||||
|
period = p.Period
|
||||||
|
first = p.First
|
||||||
|
last = p.Last
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StubPeriodProducer) Last() (next PeriodBound, found bool, err error) {
|
||||||
|
if s.last != nil {
|
||||||
|
next = *s.last
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
81
mqe/raw.go
Normal file
81
mqe/raw.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package mqe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"gordenko.dev/dima/qb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RawMeasure struct {
|
||||||
|
Time int64 `json:"t"`
|
||||||
|
Value float64 `json:"v"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RawMeasuresFilter struct {
|
||||||
|
MetricID int64 `json:"metricID"`
|
||||||
|
MetricType qb.MetricType `json:"metricType"`
|
||||||
|
Since int64 `json:"since"` // уже учтен firstHourOfDay
|
||||||
|
Until int64 `json:"until"` // уже учтен firstHourOfDay
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRawMeasures - cписок показаний мгновенных метрик (Температура, Давление, Расход)
|
||||||
|
// за за интервал без группировки
|
||||||
|
func (s *MeasureQueryEngine) ListRawMeasures(req RawMeasuresFilter) (_ []RawMeasure, err error) {
|
||||||
|
tx, err := s.db.Driver().Begin()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
rows, err := tx.Query(`
|
||||||
|
SELECT tm, value
|
||||||
|
FROM f64
|
||||||
|
WHERE metricID=? AND tm BETWEEN ? AND ?
|
||||||
|
ORDER BY tm ASC`,
|
||||||
|
req.MetricID, req.Since, req.Until)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var result []RawMeasure
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var (
|
||||||
|
tm int64
|
||||||
|
value float64
|
||||||
|
)
|
||||||
|
err = rows.Scan(&tm, &value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result = append(result, RawMeasure{
|
||||||
|
Time: tm,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.MetricType == qb.Cumulative {
|
||||||
|
var corrections []_f64Correction
|
||||||
|
corrections, err = listF64CorrectionsTx(tx, req.MetricID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(corrections) > 0 {
|
||||||
|
applyCorrectionsToRawMeasures(corrections, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
179
mqe/readings.go
Normal file
179
mqe/readings.go
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
package mqe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gordenko.dev/dima/pretty"
|
||||||
|
"gordenko.dev/dima/qb/timeutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
////////////////////////////
|
||||||
|
|
||||||
|
type MetricMeasure struct {
|
||||||
|
Time int64 `json:"t"`
|
||||||
|
Value float64 `json:"v"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MetricError struct {
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
Code int64 `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReadingsFilter struct {
|
||||||
|
Since string `json:"since"`
|
||||||
|
Until string `json:"until"`
|
||||||
|
MetricIDs []int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type MetricReadings struct {
|
||||||
|
MetricID int64
|
||||||
|
Measures []MetricMeasure
|
||||||
|
Errors []MetricError
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectReadingsInParallel - метод достает из БД данные всех метрик, которые связаны
|
||||||
|
// с объектом. Учитываются настройки объекта (LastDayOfMonth, FirstHourOfDay), а также
|
||||||
|
// фильтры выбранные пользователем на странице объекта (Since, Until, GroupBy).
|
||||||
|
// ВАЖНО!
|
||||||
|
// Запросы по каждой метрике отправляются в БД в отдельной горутине, что позволяет
|
||||||
|
// в разы ускорить получение данных по объекту.
|
||||||
|
func (s *MeasureQueryEngine) SelectReadingsInParallel(req ReadingsFilter) (result []MetricReadings, err error) {
|
||||||
|
pretty.PPrintln("SelectReadingsInParallel", req)
|
||||||
|
|
||||||
|
if len(req.MetricIDs) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Since == "" {
|
||||||
|
err = fix.Field(EmptyValue, "since")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Until == "" {
|
||||||
|
err = fix.Field(EmptyValue, "until")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ВАЖНО!
|
||||||
|
// Если в строке нет таймзоны - Parse функция возвращает время в UTC.
|
||||||
|
// Поэтому нужно использовать ParseInLocation!
|
||||||
|
|
||||||
|
since, err := time.ParseInLocation("2006-01-02", req.Since, s.location)
|
||||||
|
if err != nil {
|
||||||
|
err = fix.Field(WrongValue, "since")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
until, err := time.ParseInLocation("2006-01-02", req.Until, s.location)
|
||||||
|
if err != nil {
|
||||||
|
err = fix.Field(WrongValue, "until")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Корректируем, ВСЕГДА на последнюю секунду суток
|
||||||
|
until = timeutil.LastSecondInPeriod(until, string(ByDay))
|
||||||
|
|
||||||
|
fmt.Printf(`SelectReadingsInParallel: {
|
||||||
|
Since: %s
|
||||||
|
Until: %s
|
||||||
|
MetricIDs: %v
|
||||||
|
}
|
||||||
|
`, since, until, req.MetricIDs)
|
||||||
|
// FIX проверить как добавляются часы в DST часовых поясах
|
||||||
|
// Для req.FirstHourOfDay=4 since должен быть 4:00:00, until должен быть
|
||||||
|
// 3:59:59 следующего дня
|
||||||
|
|
||||||
|
sinceUnixtime := since.Unix()
|
||||||
|
untilUnixtime := until.Unix()
|
||||||
|
|
||||||
|
if sinceUnixtime >= untilUnixtime {
|
||||||
|
err = fix.Error(WrongDateRange)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Важно вирутальная метрика зависит от реальных, которые к объекту могут быть
|
||||||
|
// привязаны или нет. Если нет - не возвращать.
|
||||||
|
|
||||||
|
var (
|
||||||
|
tasks []*MetricReadingsTask
|
||||||
|
wg = new(sync.WaitGroup)
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, metricID := range req.MetricIDs {
|
||||||
|
task := &MetricReadingsTask{
|
||||||
|
In: MetricMeasuresFilter{
|
||||||
|
MetricID: metricID,
|
||||||
|
Since: sinceUnixtime,
|
||||||
|
Until: untilUnixtime,
|
||||||
|
},
|
||||||
|
WaitGroup: wg,
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
s.metricReadingsCh <- task
|
||||||
|
tasks = append(tasks, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
for _, task := range tasks {
|
||||||
|
if task.Err != nil {
|
||||||
|
err = fmt.Errorf("get data for the metric %d: %s", task.In.MetricID, task.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result = append(result, task.Result)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListInstantMeasures - cписок показаний для юнитов без накопительного итога
|
||||||
|
// (например, Давление)
|
||||||
|
func (s *MeasureQueryEngine) listMetricReadings(req MetricMeasuresFilter) (result MetricReadings, err error) {
|
||||||
|
result.MetricID = req.MetricID
|
||||||
|
// buf, _ := json.MarshalIndent(req, "", " ")
|
||||||
|
// fmt.Printf("listInstantMeasures: %s\n\n", buf)
|
||||||
|
|
||||||
|
tx, err := s.db.Driver().Begin()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
rows, err := tx.Query(`
|
||||||
|
SELECT tm, value
|
||||||
|
FROM f64
|
||||||
|
WHERE metricID=? AND tm BETWEEN ? AND ?
|
||||||
|
ORDER BY tm ASC`,
|
||||||
|
req.MetricID, req.Since, req.Until)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var measure MetricMeasure
|
||||||
|
err = rows.Scan(&measure.Time, &measure.Value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// fix - для cumulative метрик учет поправок
|
||||||
|
result.Measures = append(result.Measures, measure)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//for _, m := range result {
|
||||||
|
// fmt.Printf("raw: %d\t%f\n", m.Time, m.Value)
|
||||||
|
//}
|
||||||
|
//fmt.Printf("listInstantMeasures (qty): %d\n", len(result))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
570
mqe/split.go
Normal file
570
mqe/split.go
Normal file
@@ -0,0 +1,570 @@
|
|||||||
|
package mqe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SplitByPeriods(producer periodProducer, calc PeriodCalculator) (result []AggregatedMeasure, err error) {
|
||||||
|
var (
|
||||||
|
isOpened bool
|
||||||
|
openedPeriod time.Time
|
||||||
|
since int64
|
||||||
|
sinceValue float64
|
||||||
|
until int64
|
||||||
|
untilValue float64
|
||||||
|
|
||||||
|
//
|
||||||
|
period time.Time
|
||||||
|
first, last PeriodBound
|
||||||
|
found bool
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
period, first, last, found, err = producer.Next()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if isOpened {
|
||||||
|
if calc.IsExtendedSincePeriod(period) {
|
||||||
|
err = fmt.Errorf("extended period while already opened")
|
||||||
|
return
|
||||||
|
} else if calc.IsExtendedUntilPeriod(period) {
|
||||||
|
result = append(result, AggregatedMeasure{
|
||||||
|
Period: openedPeriod.Unix(),
|
||||||
|
Since: since,
|
||||||
|
Until: first.Time,
|
||||||
|
Values: []float64{
|
||||||
|
first.Value,
|
||||||
|
first.Value - sinceValue,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
// Последний период успешно закрыли данными из Extended Until, поэтому выходим
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if period.Equal(openedPeriod) {
|
||||||
|
// +
|
||||||
|
// Данная ситуация будет когда предыдущий период был корректно
|
||||||
|
// закрыт за 3 минуты до конца. После этого сразу открылся
|
||||||
|
// новый период. А запись для нового периода получена только
|
||||||
|
// сейчас.
|
||||||
|
if calc.IsPeriodCorrectEnds(period, last.Time) {
|
||||||
|
// CLOSE
|
||||||
|
result = append(result, AggregatedMeasure{
|
||||||
|
Period: openedPeriod.Unix(),
|
||||||
|
Since: since,
|
||||||
|
Until: last.Time,
|
||||||
|
Values: []float64{
|
||||||
|
last.Value,
|
||||||
|
last.Value - sinceValue,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// OPEN PERIOD (extended since, last 3 min closed)
|
||||||
|
openedPeriod = calc.NextPeriod(period)
|
||||||
|
if calc.IsExtendedUntilPeriod(openedPeriod) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
since = last.Time
|
||||||
|
sinceValue = last.Value
|
||||||
|
until = 0
|
||||||
|
untilValue = 0
|
||||||
|
} else {
|
||||||
|
// Период закроем где-то в будущем, на всякий случай запомнили последнее
|
||||||
|
// показание внутри периода
|
||||||
|
until = last.Time
|
||||||
|
untilValue = last.Value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// +
|
||||||
|
// Периоды не совпадают - смело закрываем
|
||||||
|
// Возможные ситуации:
|
||||||
|
// - был открыт мартовский период, а текущий период - апрель
|
||||||
|
// -
|
||||||
|
result = append(result, AggregatedMeasure{
|
||||||
|
Period: openedPeriod.Unix(),
|
||||||
|
Since: since,
|
||||||
|
Until: first.Time,
|
||||||
|
Values: []float64{
|
||||||
|
first.Value,
|
||||||
|
first.Value - sinceValue,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if first.Time != last.Time {
|
||||||
|
// +
|
||||||
|
openedPeriod = period
|
||||||
|
since = first.Time
|
||||||
|
sinceValue = first.Value
|
||||||
|
until = 0
|
||||||
|
untilValue = 0
|
||||||
|
|
||||||
|
if calc.IsPeriodCorrectEnds(period, last.Time) {
|
||||||
|
// Закрываем только что открытый период
|
||||||
|
result = append(result, AggregatedMeasure{
|
||||||
|
Period: openedPeriod.Unix(),
|
||||||
|
Since: since,
|
||||||
|
Until: last.Time,
|
||||||
|
Values: []float64{
|
||||||
|
last.Value,
|
||||||
|
last.Value - sinceValue,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
openedPeriod = calc.NextPeriod(period)
|
||||||
|
if calc.IsExtendedUntilPeriod(openedPeriod) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
since = last.Time
|
||||||
|
sinceValue = last.Value
|
||||||
|
until = 0
|
||||||
|
untilValue = 0
|
||||||
|
} else {
|
||||||
|
// Период закроем где-то в будущем, на всякий случай запомнили последнее
|
||||||
|
// показание внутри периода
|
||||||
|
until = last.Time
|
||||||
|
untilValue = last.Value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// +
|
||||||
|
// first == last
|
||||||
|
if calc.IsPeriodCorrectEnds(period, last.Time) {
|
||||||
|
// Допустим закрыли предыдущий период 30 апреля в 23:58, новый стартуем за май.
|
||||||
|
openedPeriod = calc.NextPeriod(period)
|
||||||
|
if calc.IsExtendedUntilPeriod(openedPeriod) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Допустим закрыли предыдущий период 15 апреля, новый стартуем за апрель.
|
||||||
|
openedPeriod = period
|
||||||
|
}
|
||||||
|
since = last.Time
|
||||||
|
sinceValue = last.Value
|
||||||
|
until = 0
|
||||||
|
untilValue = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if calc.IsExtendedSincePeriod(period) {
|
||||||
|
// Если последние 3 минуты
|
||||||
|
if calc.IsPeriodCorrectEnds(period, last.Time) {
|
||||||
|
// OPEN PERIOD (extended since, last 3 min closed)
|
||||||
|
isOpened = true
|
||||||
|
openedPeriod = calc.NextPeriod(period)
|
||||||
|
since = last.Time
|
||||||
|
sinceValue = last.Value
|
||||||
|
until = 0
|
||||||
|
untilValue = 0
|
||||||
|
}
|
||||||
|
} else if calc.IsExtendedUntilPeriod(period) {
|
||||||
|
// Ничего не нашли, пустой результат
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
// OPEN PERIOD (regular first)
|
||||||
|
isOpened = true
|
||||||
|
openedPeriod = period
|
||||||
|
since = first.Time
|
||||||
|
sinceValue = first.Value
|
||||||
|
until = last.Time
|
||||||
|
untilValue = last.Value
|
||||||
|
|
||||||
|
if calc.IsPeriodCorrectEnds(openedPeriod, last.Time) {
|
||||||
|
// Закрываем только что открытый период
|
||||||
|
result = append(result, AggregatedMeasure{
|
||||||
|
Period: openedPeriod.Unix(),
|
||||||
|
Since: since,
|
||||||
|
Until: last.Time,
|
||||||
|
Values: []float64{
|
||||||
|
last.Value,
|
||||||
|
last.Value - sinceValue,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
openedPeriod = calc.NextPeriod(period)
|
||||||
|
if calc.IsExtendedUntilPeriod(openedPeriod) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
since = last.Time
|
||||||
|
sinceValue = last.Value
|
||||||
|
until = 0
|
||||||
|
untilValue = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Доп запрос.
|
||||||
|
if openedPeriod.IsZero() {
|
||||||
|
// Открытого периода не будет если вообще не нашли записей
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next, found, err := producer.Last()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if found {
|
||||||
|
result = append(result, AggregatedMeasure{
|
||||||
|
Period: openedPeriod.Unix(),
|
||||||
|
Since: since,
|
||||||
|
Until: next.Time,
|
||||||
|
Values: []float64{
|
||||||
|
next.Value,
|
||||||
|
next.Value - sinceValue,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if until > 0 {
|
||||||
|
result = append(result, AggregatedMeasure{
|
||||||
|
Period: openedPeriod.Unix(),
|
||||||
|
Since: since,
|
||||||
|
Until: until,
|
||||||
|
Values: []float64{
|
||||||
|
untilValue,
|
||||||
|
untilValue - sinceValue,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Period - это начало периода, например для дней - это 0ч 0м 0с
|
||||||
|
// lastTm предыдущего периода всегда будет меньше current period
|
||||||
|
// since - это всегда сдвинутая метка времени (+ FirstHourOfDay * 3600)
|
||||||
|
|
||||||
|
// При FirstHourOfDay = 3 и группировке по дням, будет такая ситуация.
|
||||||
|
// Period будет указывать на 1 марта 00:00:00, а since на 1 марта 02:00:00,
|
||||||
|
// а lastTm предыдущего периода на 1 марта 01:59:00.
|
||||||
|
// Поэтому нельзя сравнивать текущий period и lastTm предыдущего периода.
|
||||||
|
// Преобразовать lastTm к period непонятно как если нужно учитывать LastDayOfMonth.
|
||||||
|
// Пример: 28 марта 00ч стало 1 апреля 00ч, а 27 марта 23:59 так и осталось.
|
||||||
|
// Можно вычислить endPeriodTime для previousPeriodEndTime, но текущий период может
|
||||||
|
// быть не строго следующим после предыдущего (например, июнь после марта).
|
||||||
|
// Правильное решение - берем firstTm текущего периода и вычисляем его реальное
|
||||||
|
// календарное начало. Например, FirstHourOfDay = 3, LastDayOfMonth = 28,
|
||||||
|
// firstTm = 1 мая. Календарное начало - 28 апреля 03:00:00. И уже с этим значением
|
||||||
|
// сравниваем previousPeriodEndTime.
|
||||||
|
// Но можно сравнивать since c lastTm и until c firstTm для определения
|
||||||
|
// extended периодов.
|
||||||
|
/*
|
||||||
|
func periodToCalendarTime(period time.Time, groupBy string, lastDayOfMonth, firstHourOfDay int) time.Time {
|
||||||
|
switch groupBy {
|
||||||
|
case "d":
|
||||||
|
if firstHourOfDay == 0 {
|
||||||
|
return period
|
||||||
|
}
|
||||||
|
// Было 0ч, стало - 2ч
|
||||||
|
return period.Add(time.Duration(firstHourOfDay) * time.Hour)
|
||||||
|
|
||||||
|
case "m":
|
||||||
|
if lastDayOfMonth == 0 {
|
||||||
|
if firstHourOfDay == 0 {
|
||||||
|
return period
|
||||||
|
}
|
||||||
|
// Было 0ч, стало - 2ч
|
||||||
|
return period.Add(time.Duration(firstHourOfDay) * time.Hour)
|
||||||
|
} else {
|
||||||
|
// текущий период начинается в прошлом месяце
|
||||||
|
tm := period.AddDate(0, -1, 0)
|
||||||
|
return time.Date(
|
||||||
|
tm.Year(),
|
||||||
|
tm.Month(),
|
||||||
|
lastDayOfMonth+1,
|
||||||
|
firstHourOfDay,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
tm.Location(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return period
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
//var threeMinutes int64 = 3 * 60
|
||||||
|
|
||||||
|
/*
|
||||||
|
type readOptions struct {
|
||||||
|
MetricID int64 // только для readAggregatedMeasuresCanEndsInFuture
|
||||||
|
LastDayOfMonth int
|
||||||
|
FirstHourOfDay int
|
||||||
|
GroupBy string
|
||||||
|
Since int64
|
||||||
|
Until int64
|
||||||
|
ParsePeriodLayout string
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// Читает из sql.Rows показания и строит периоды
|
||||||
|
/*
|
||||||
|
func (s *MeasureQueryEngine) readAggregatedMeasuresCanEndsInFuture(tx *sql.Tx, rows *sql.Rows, opt readOptions) (result []CalculatedAggregatedMeasure, err error) {
|
||||||
|
var (
|
||||||
|
openPeriod time.Time
|
||||||
|
since int64
|
||||||
|
sinceValue float64
|
||||||
|
until int64
|
||||||
|
untilValue float64
|
||||||
|
//
|
||||||
|
frameStr string
|
||||||
|
firstTm int64
|
||||||
|
firstValue float64
|
||||||
|
lastTm int64
|
||||||
|
lastValue float64
|
||||||
|
period time.Time
|
||||||
|
)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
err = rows.Scan(&frameStr, &firstTm, &firstValue, &lastTm, &lastValue)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
period, err = time.ParseInLocation(opt.ParsePeriodLayout, frameStr, s.location)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("time.ParseInLocation: %s; layout=%q; str=%q",
|
||||||
|
err, opt.ParsePeriodLayout, frameStr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("%s s: %s, %v; u: %s, %v\n", frameStr, time.Unix(firstTm, 0), firstValue, time.Unix(lastTm, 0), lastValue)
|
||||||
|
|
||||||
|
// Читаем записи из БД. Одна запись - один период. Возможны 3 вида периодов:
|
||||||
|
// 1. extended since - период длиной до 3 минут перед since. Оптимизация, чтобы
|
||||||
|
// не отправлять дополнительный запрос.
|
||||||
|
// 2. обычный период.
|
||||||
|
// 3. extended until - период длиной до 1 часа после until. Оптимизация, чтобы
|
||||||
|
// не отправлять дополнительный запрос (в большинстве случаев).
|
||||||
|
//
|
||||||
|
// ВАЖНО!
|
||||||
|
// Любой период (1, 2 или 3) может встретится первым. Определить что это
|
||||||
|
// первая запись модно простой проверкой openPeriod.IsZero()
|
||||||
|
|
||||||
|
if lastTm < opt.Since {
|
||||||
|
// 1. extended since period
|
||||||
|
|
||||||
|
if isPeriodCorrectEnds(period, opt.GroupBy, opt.LastDayOfMonth, opt.FirstHourOfDay, lastTm) {
|
||||||
|
openPeriod = nextPeriod(period, opt.GroupBy)
|
||||||
|
since = lastTm
|
||||||
|
sinceValue = lastValue
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else if firstTm > opt.Until {
|
||||||
|
// 3. extended until period
|
||||||
|
|
||||||
|
if openPeriod.IsZero() {
|
||||||
|
// Нет показаний кроме extended until, поэтому выходим
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// закрываем открытый период
|
||||||
|
result = append(result, CalculatedAggregatedMeasure{
|
||||||
|
Period: openPeriod.Unix(),
|
||||||
|
Since: since,
|
||||||
|
Until: firstTm,
|
||||||
|
Value: firstValue,
|
||||||
|
Total: firstValue - sinceValue,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. обычный период
|
||||||
|
|
||||||
|
if openPeriod.IsZero() {
|
||||||
|
// это первая запись (extended since period не найден)
|
||||||
|
openPeriod = period
|
||||||
|
since = firstTm
|
||||||
|
sinceValue = firstValue
|
||||||
|
} else {
|
||||||
|
if !period.Equal(openPeriod) {
|
||||||
|
//fmt.Printf("\nperiod %s != openPeriod %s\n", period, openPeriod)
|
||||||
|
// Пример: открытый период - март. В period - апрель.
|
||||||
|
// закрываем открытый период
|
||||||
|
result = append(result, CalculatedAggregatedMeasure{
|
||||||
|
Period: openPeriod.Unix(),
|
||||||
|
Since: since,
|
||||||
|
Until: firstTm,
|
||||||
|
Value: firstValue,
|
||||||
|
Total: firstValue - sinceValue,
|
||||||
|
})
|
||||||
|
|
||||||
|
// И сразу открываем новый период
|
||||||
|
openPeriod = period
|
||||||
|
since = firstTm
|
||||||
|
sinceValue = firstValue
|
||||||
|
}
|
||||||
|
// Периоды совпадают. Когда такая ситуация возможна?
|
||||||
|
// Например, на предыдущей итерации был корректно закрыт (57-59 минуты)
|
||||||
|
// прошлый период и сразу открыт новый (текущий).
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPeriodCorrectEnds(openPeriod, opt.GroupBy, opt.LastDayOfMonth, opt.FirstHourOfDay, lastTm) {
|
||||||
|
// fmt.Printf("\nCORRECT END period %s\n", openPeriod)
|
||||||
|
// закрываем открытый период
|
||||||
|
result = append(result, CalculatedAggregatedMeasure{
|
||||||
|
Period: openPeriod.Unix(),
|
||||||
|
Since: since,
|
||||||
|
Until: lastTm,
|
||||||
|
Value: lastValue,
|
||||||
|
Total: lastValue - sinceValue,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Открываем новый период
|
||||||
|
openPeriod = nextPeriod(openPeriod, opt.GroupBy)
|
||||||
|
since = lastTm
|
||||||
|
sinceValue = lastValue
|
||||||
|
} else {
|
||||||
|
// На последних минутах периода (57-59) не было показания, поэтому
|
||||||
|
// запоминаем конец периода какой есть. Если конец периода в будущем
|
||||||
|
// не будет найден - закроем период по текущему последнему показанию.
|
||||||
|
until = lastTm
|
||||||
|
untilValue = lastValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Валидируем новый период.
|
||||||
|
// Зачем?
|
||||||
|
// Пример: юзер задал opt.Until - 31 марта 23:59:59. Только что корректно
|
||||||
|
// закрылся март показанием от 31 марта 23:57:00. И сразу открылся новый
|
||||||
|
// период за апрель.
|
||||||
|
// Понять что закрыли последний период из запрошенных юзером просто -
|
||||||
|
// вычисляем календарное начало нового периода и сравниваем с opt.Until.
|
||||||
|
mustLessThanUntil := periodToCalendarTime(openPeriod, opt.GroupBy,
|
||||||
|
opt.LastDayOfMonth, opt.FirstHourOfDay)
|
||||||
|
|
||||||
|
if mustLessThanUntil.Unix() > opt.Until {
|
||||||
|
// Новый период вышел за opt.Until, а это означает что мы корректно
|
||||||
|
// закрыли последний период из запрошенных юзером. Bыходим.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Цикл завершен, а из метода мы не вышли, а это означает одно - есть незакрытый
|
||||||
|
// период. Extended until не найден. Значит нужно отправить дополнительный запрос
|
||||||
|
// к БД.
|
||||||
|
var (
|
||||||
|
isNextValueFound bool
|
||||||
|
nextTm int64
|
||||||
|
nextValue float64
|
||||||
|
)
|
||||||
|
|
||||||
|
// Находим следующее показание метрики.
|
||||||
|
err = tx.QueryRow(`
|
||||||
|
SELECT tm, value, true
|
||||||
|
FROM f64
|
||||||
|
WHERE metricID=? AND tm > ?
|
||||||
|
ORDER BY tm ASC
|
||||||
|
LIMIT 1`,
|
||||||
|
opt.MetricID, opt.Until).Scan(&nextTm, &nextValue, &isNextValueFound)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Нет записей
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if isNextValueFound {
|
||||||
|
// Найдено какое-то показание в будущем.
|
||||||
|
result = append(result, CalculatedAggregatedMeasure{
|
||||||
|
Period: openPeriod.Unix(),
|
||||||
|
Since: since,
|
||||||
|
Until: nextTm,
|
||||||
|
Value: nextValue,
|
||||||
|
Total: nextValue - sinceValue,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Закрываем период самым последним показанием внутри периода
|
||||||
|
// (НЕ 57-59 минуты).
|
||||||
|
result = append(result, CalculatedAggregatedMeasure{
|
||||||
|
Period: openPeriod.Unix(),
|
||||||
|
Since: since,
|
||||||
|
Until: until,
|
||||||
|
Value: untilValue,
|
||||||
|
Total: untilValue - sinceValue,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Читает из sql.Rows показания и строит периоды
|
||||||
|
func (s *MeasureQueryEngine) readAggregatedMeasuresEndsInsidePeriod(tx *sql.Tx, rows *sql.Rows, opt readOptions) (result []CalculatedAggregatedMeasure, err error) {
|
||||||
|
var (
|
||||||
|
previousPeriodEndTime int64
|
||||||
|
previousPeriodEndValue float64
|
||||||
|
//
|
||||||
|
frameStr string
|
||||||
|
firstTm int64
|
||||||
|
firstValue float64
|
||||||
|
lastTm int64
|
||||||
|
lastValue float64
|
||||||
|
period time.Time
|
||||||
|
)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
err = rows.Scan(&frameStr, &firstTm, &firstValue, &lastTm, &lastValue)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
period, err = time.ParseInLocation(opt.ParsePeriodLayout, frameStr, s.location)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("time.ParseInLocation: %s; layout=%q; str=%q",
|
||||||
|
err, opt.ParsePeriodLayout, frameStr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("%s s: %s, %v; u: %s, %v\n", frameStr, time.Unix(firstTm, 0), firstValue, time.Unix(lastTm, 0), lastValue)
|
||||||
|
|
||||||
|
if lastTm < opt.Since {
|
||||||
|
// extended since period
|
||||||
|
previousPeriodEndTime = lastTm
|
||||||
|
previousPeriodEndValue = lastValue
|
||||||
|
continue
|
||||||
|
} else if firstTm > opt.Until {
|
||||||
|
// extended until period
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// целевой период. В будущем конец периода не ищем, поэтому закрываем сразу.
|
||||||
|
|
||||||
|
// Вычисляем реальное календарное начало периода. Например, period - 1 мая 00:00:00,
|
||||||
|
// FirstHourOfDay = 3, LastDayOfMonth = 28. Календарное начало - 29 апреля 03:00:00.
|
||||||
|
periodCalendarTime := periodToCalendarTime(period, opt.GroupBy, opt.LastDayOfMonth, opt.FirstHourOfDay)
|
||||||
|
|
||||||
|
minAge := periodCalendarTime.Add(-3 * time.Minute).Unix()
|
||||||
|
|
||||||
|
if previousPeriodEndTime > 0 && previousPeriodEndTime >= minAge {
|
||||||
|
result = append(result, CalculatedAggregatedMeasure{
|
||||||
|
Period: period.Unix(),
|
||||||
|
Since: previousPeriodEndTime,
|
||||||
|
Until: lastTm,
|
||||||
|
Value: lastValue,
|
||||||
|
Total: lastValue - previousPeriodEndValue,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
result = append(result, CalculatedAggregatedMeasure{
|
||||||
|
Period: period.Unix(),
|
||||||
|
Since: firstTm,
|
||||||
|
Until: lastTm,
|
||||||
|
Value: lastValue,
|
||||||
|
Total: lastValue - firstValue,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
previousPeriodEndTime = lastTm
|
||||||
|
previousPeriodEndValue = lastValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*/
|
||||||
141
mqe/total.go
Normal file
141
mqe/total.go
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
package mqe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gordenko.dev/dima/qb/timeutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TOTALS
|
||||||
|
|
||||||
|
type TotalMetricSpec struct {
|
||||||
|
MetricID int64
|
||||||
|
LastDayOfMonth int
|
||||||
|
FirstHourOfDay int
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastDayOfMonth и FirstHourOfDay нет, ибо у каждой метрики свои настройки
|
||||||
|
type RangeTotalsFilter struct {
|
||||||
|
Since string
|
||||||
|
Until string
|
||||||
|
// Если учитывать не нужно - программа уровнем выше может передать нули.
|
||||||
|
// FIX у каждой метрики свои настройки
|
||||||
|
// искать ли конец периода в следующих периодах, если в последние 3 минуты периода
|
||||||
|
// не было показаний
|
||||||
|
CumulativeMetrics []TotalMetricSpec // metricID
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectRangeTotalsInParallel - метод рассчитывает накопленный объем от Since до Until
|
||||||
|
// по каждой метрике из массива CumulativeMetricIDs. Учитываются настройки объекта
|
||||||
|
// (LastDayOfMonth, FirstHourOfDay).
|
||||||
|
// ВАЖНО!
|
||||||
|
// Запросы по каждой метрике отправляются в БД в отдельной горутине, что позволяет
|
||||||
|
// в разы ускорить получение данных по объекту.
|
||||||
|
func (s *MeasureQueryEngine) GetRangeTotalsInParallel(req RangeTotalsFilter) (resultMap map[int64]RangeTotal, err error) {
|
||||||
|
if req.Since == "" {
|
||||||
|
err = fix.Field(EmptyValue, "since")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Until == "" {
|
||||||
|
err = fix.Field(EmptyValue, "until")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ВАЖНО!
|
||||||
|
// Если в строке нет таймзоны - Parse функция возвращает время в UTC.
|
||||||
|
// Поэтому нужно использовать ParseInLocation!
|
||||||
|
|
||||||
|
since, err := time.ParseInLocation("2006-01-02", req.Since, s.location)
|
||||||
|
if err != nil {
|
||||||
|
err = fix.Field(WrongValue, "since")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
until, err := time.ParseInLocation("2006-01-02", req.Until, s.location)
|
||||||
|
if err != nil {
|
||||||
|
err = fix.Field(WrongValue, "until")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Корректируем, чтобы указывало на последнюю секунду суток
|
||||||
|
until = timeutil.LastSecondInPeriod(until, string(ByDay))
|
||||||
|
|
||||||
|
var metrics []string
|
||||||
|
|
||||||
|
for _, metric := range req.CumulativeMetrics {
|
||||||
|
metrics = append(metrics, fmt.Sprintf(` {
|
||||||
|
MetricID: %d
|
||||||
|
LastDayOfMonth: %d
|
||||||
|
FirstHourOfDay: %d
|
||||||
|
}`, metric.MetricID, metric.LastDayOfMonth, metric.FirstHourOfDay))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(`GetRangeTotalsInParallel: {
|
||||||
|
Since: %s
|
||||||
|
Until: %s
|
||||||
|
CumulativeMetrics [
|
||||||
|
%s
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`, since, until, strings.Join(metrics, "\n"))
|
||||||
|
if since.Unix() >= until.Unix() {
|
||||||
|
err = fix.Error(WrongDateRange)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
tasks []*CumulativeTotalTask
|
||||||
|
wg = new(sync.WaitGroup)
|
||||||
|
)
|
||||||
|
|
||||||
|
resultMap = make(map[int64]RangeTotal)
|
||||||
|
|
||||||
|
// CUMULATIVE METRIC TOTALS
|
||||||
|
if len(req.CumulativeMetrics) > 0 {
|
||||||
|
for _, metric := range req.CumulativeMetrics {
|
||||||
|
// У каждой метрики индивидуальные настройки,
|
||||||
|
// ибо метрики принадлежат разным объектам!!!
|
||||||
|
metricSince := since
|
||||||
|
metricUntil := until
|
||||||
|
|
||||||
|
if metric.FirstHourOfDay > 0 {
|
||||||
|
metricSince = since.Add(time.Duration(metric.FirstHourOfDay) * time.Hour)
|
||||||
|
metricUntil = until.Add(time.Duration(metric.FirstHourOfDay) * time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
task := &CumulativeTotalTask{
|
||||||
|
In: getCumulativeTotalFilter{
|
||||||
|
MetricID: metric.MetricID,
|
||||||
|
Since: metricSince.Unix(),
|
||||||
|
Until: metricUntil.Unix(),
|
||||||
|
LastDayOfMonth: metric.LastDayOfMonth,
|
||||||
|
FirstHourOfDay: metric.FirstHourOfDay,
|
||||||
|
},
|
||||||
|
WaitGroup: wg,
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
s.cumulativeTotalCh <- task
|
||||||
|
tasks = append(tasks, task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
for _, task := range tasks {
|
||||||
|
if task.Err != nil {
|
||||||
|
err = fmt.Errorf("get data for the metric %d: %s", task.In.MetricID, task.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if task.Result != nil {
|
||||||
|
resultMap[task.In.MetricID] = *task.Result
|
||||||
|
} else {
|
||||||
|
resultMap[task.In.MetricID] = RangeTotal{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
138
mqe/unaggregated.go
Normal file
138
mqe/unaggregated.go
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
package mqe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Measure - рассчитанное показание реальной метрики. Рассчитанное означает
|
||||||
|
// что учтен Factor. То есть значение уже можно показывать юзеру
|
||||||
|
// Для instant - одно значение value, для cumulative - 2 значения (value, total)
|
||||||
|
type Measure struct {
|
||||||
|
Time int64 `json:"t"`
|
||||||
|
Values []float64 `json:"v"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnaggregatedFilter struct {
|
||||||
|
Since time.Time
|
||||||
|
Until time.Time
|
||||||
|
LastDayOfMonth int // например, конец месяца 25 число
|
||||||
|
FirstHourOfDay int // например день начинается в 6:00
|
||||||
|
InstantMetrics []int64
|
||||||
|
CumulativeMetrics []int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// type MetricsDataResultMaps struct {
|
||||||
|
// AggregatedCumulative map[int64][]CalculatedAggregatedMeasure
|
||||||
|
// AggregatedByFuncInstant map[int64][]AggregatedByFuncInstantMeasure
|
||||||
|
// Cumulative map[int64][]CalculatedMeasure
|
||||||
|
// Instant map[int64][]CalculatedMeasure
|
||||||
|
// }
|
||||||
|
|
||||||
|
// SelectObjectDataInParallel - метод достает из БД данные всех метрик, которые связаны
|
||||||
|
// с объектом. Учитываются настройки объекта (LastDayOfMonth, FirstHourOfDay), а также
|
||||||
|
// фильтры выбранные пользователем на странице объекта (Since, Until, GroupBy).
|
||||||
|
// ВАЖНО!
|
||||||
|
// Запросы по каждой метрике отправляются в БД в отдельной горутине, что позволяет
|
||||||
|
// в разы ускорить получение данных по объекту.
|
||||||
|
func (s *MeasureQueryEngine) SelectUnaggregatedData(req UnaggregatedFilter) (resultMap map[int64][]Measure, err error) {
|
||||||
|
since := req.Since
|
||||||
|
until := req.Until
|
||||||
|
|
||||||
|
if req.FirstHourOfDay < 0 || req.FirstHourOfDay > 23 {
|
||||||
|
err = fix.Field(InvalidFirstHourOfDay, "firstHourOfDay")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.LastDayOfMonth < 0 || req.LastDayOfMonth > 28 {
|
||||||
|
err = fix.Field(InvalidLastDayOfMonth, "lastDayOfMonth")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Printf(`SelectUnaggregatedData: {
|
||||||
|
// Since: %s
|
||||||
|
// Until: %s
|
||||||
|
// FirstHourOfDay: %d
|
||||||
|
// LastDayOfMonth: %d
|
||||||
|
// }
|
||||||
|
// `, since, until, req.FirstHourOfDay, req.LastDayOfMonth)
|
||||||
|
// FIX проверить как добавляются часы в DST часовых поясах
|
||||||
|
// Для req.FirstHourOfDay=4 since должен быть 4:00:00, until должен быть
|
||||||
|
// 3:59:59 следующего дня
|
||||||
|
if req.FirstHourOfDay > 0 {
|
||||||
|
since = since.Add(time.Duration(req.FirstHourOfDay) * time.Hour)
|
||||||
|
until = until.Add(time.Duration(req.FirstHourOfDay) * time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
sinceUnixtime := since.Unix()
|
||||||
|
untilUnixtime := until.Unix()
|
||||||
|
|
||||||
|
if sinceUnixtime >= untilUnixtime {
|
||||||
|
err = fix.Error(WrongDateRange)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cumulativeTasks []*CumulativeTask
|
||||||
|
instantTasks []*InstantTask
|
||||||
|
wg = new(sync.WaitGroup)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Важно вирутальная метрика зависит от реальных, которые к объекту могут быть
|
||||||
|
// привязаны или нет. Если нет - не возвращать.
|
||||||
|
resultMap = make(map[int64][]Measure)
|
||||||
|
|
||||||
|
if len(req.CumulativeMetrics) > 0 {
|
||||||
|
for _, metricID := range req.CumulativeMetrics {
|
||||||
|
task := &CumulativeTask{
|
||||||
|
In: MetricMeasuresFilter{
|
||||||
|
MetricID: metricID,
|
||||||
|
Since: sinceUnixtime,
|
||||||
|
Until: untilUnixtime,
|
||||||
|
},
|
||||||
|
WaitGroup: wg,
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
s.cumulativeCh <- task
|
||||||
|
cumulativeTasks = append(cumulativeTasks, task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.InstantMetrics) > 0 {
|
||||||
|
for _, metricID := range req.InstantMetrics {
|
||||||
|
task := &InstantTask{
|
||||||
|
In: MetricMeasuresFilter{
|
||||||
|
MetricID: metricID,
|
||||||
|
Since: sinceUnixtime,
|
||||||
|
Until: untilUnixtime,
|
||||||
|
},
|
||||||
|
WaitGroup: wg,
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
s.instantCh <- task
|
||||||
|
instantTasks = append(instantTasks, task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
for _, task := range cumulativeTasks {
|
||||||
|
if task.Err != nil {
|
||||||
|
err = fmt.Errorf("get data for the metric %d: %s", task.In.MetricID, task.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resultMap[task.In.MetricID] = task.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, task := range instantTasks {
|
||||||
|
if task.Err != nil {
|
||||||
|
err = fmt.Errorf("get data for the metric %d: %s", task.In.MetricID, task.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resultMap[task.In.MetricID] = task.Result
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
3
prepare.sh
Executable file
3
prepare.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
cd prepare
|
||||||
|
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o prepare
|
||||||
|
cd -
|
||||||
616
prepare/main.go
Normal file
616
prepare/main.go
Normal file
@@ -0,0 +1,616 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
bin "gordenko.dev/dima/bin/little"
|
||||||
|
"gordenko.dev/dima/qx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MetricType int
|
||||||
|
type MetricCategory int
|
||||||
|
|
||||||
|
const (
|
||||||
|
MetricTypeCumulative MetricType = 1
|
||||||
|
MetricTypeInstant MetricType = 2
|
||||||
|
// Категории метрик
|
||||||
|
CategoryVolume MetricCategory = 1
|
||||||
|
CategoryVolumeFlow MetricCategory = 2
|
||||||
|
CategoryEnergy MetricCategory = 3
|
||||||
|
CategoryMass MetricCategory = 5
|
||||||
|
CategoryMassFlow MetricCategory = 6
|
||||||
|
CategoryPower MetricCategory = 7
|
||||||
|
CategoryTemperature MetricCategory = 8
|
||||||
|
CategoryTempDiff MetricCategory = 9
|
||||||
|
CategoryLevel MetricCategory = 10
|
||||||
|
CategoryPressure MetricCategory = 11
|
||||||
|
CategoryFrequency MetricCategory = 12
|
||||||
|
CategoryRunningFrequency MetricCategory = 13
|
||||||
|
CategoryCurrent MetricCategory = 14
|
||||||
|
CategoryVoltage MetricCategory = 15
|
||||||
|
CategoryBusVoltage MetricCategory = 16
|
||||||
|
CategorySpeed MetricCategory = 17
|
||||||
|
CategoryTorque MetricCategory = 18
|
||||||
|
CategoryDuration MetricCategory = 19 // час роботи, роботи з помилкою, роботи без помилок
|
||||||
|
CategoryPressureSetting MetricCategory = 20 // уставка тиску
|
||||||
|
CategoryBatteryPower MetricCategory = 21
|
||||||
|
CategorySignalQuality MetricCategory = 22
|
||||||
|
CategoryError MetricCategory = 23
|
||||||
|
CategoryPercent MetricCategory = 24
|
||||||
|
CategoryResistance MetricCategory = 25
|
||||||
|
|
||||||
|
DSN = "root:zkx75fcy@/rrc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
MetricCategoryToMetricType = map[MetricCategory]MetricType{
|
||||||
|
CategoryVolume: MetricTypeCumulative,
|
||||||
|
CategoryVolumeFlow: MetricTypeInstant,
|
||||||
|
CategoryEnergy: MetricTypeCumulative,
|
||||||
|
CategoryMass: MetricTypeCumulative,
|
||||||
|
CategoryMassFlow: MetricTypeInstant,
|
||||||
|
CategoryPower: MetricTypeInstant,
|
||||||
|
CategoryTemperature: MetricTypeInstant,
|
||||||
|
CategoryTempDiff: MetricTypeInstant,
|
||||||
|
CategoryLevel: MetricTypeInstant,
|
||||||
|
CategoryPressure: MetricTypeInstant,
|
||||||
|
CategoryPressureSetting: MetricTypeInstant,
|
||||||
|
CategoryFrequency: MetricTypeInstant,
|
||||||
|
CategoryRunningFrequency: MetricTypeInstant,
|
||||||
|
CategoryCurrent: MetricTypeInstant,
|
||||||
|
CategoryVoltage: MetricTypeInstant,
|
||||||
|
CategoryBusVoltage: MetricTypeInstant,
|
||||||
|
CategorySpeed: MetricTypeInstant,
|
||||||
|
CategoryTorque: MetricTypeInstant,
|
||||||
|
CategoryDuration: MetricTypeCumulative,
|
||||||
|
CategoryBatteryPower: MetricTypeInstant,
|
||||||
|
CategorySignalQuality: MetricTypeInstant,
|
||||||
|
CategoryError: MetricTypeInstant,
|
||||||
|
CategoryPercent: MetricTypeInstant,
|
||||||
|
CategoryResistance: MetricTypeInstant,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func CategoryToMetricType(category MetricCategory) MetricType {
|
||||||
|
switch category {
|
||||||
|
case CategoryVolume, CategoryMass, CategoryEnergy, CategoryDuration:
|
||||||
|
return MetricTypeCumulative
|
||||||
|
}
|
||||||
|
return MetricTypeInstant
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Список метрик:
|
||||||
|
ID, тип метрики, fracDigits, начало диапазона, конец диапазона.
|
||||||
|
|
||||||
|
Есть смысл записать показания в файлы. Итерация (запись в субд) пойдет быстрее.
|
||||||
|
|
||||||
|
Получить список метрик.
|
||||||
|
В цикле получить показания и записатьв файл?
|
||||||
|
*/
|
||||||
|
|
||||||
|
type Metric struct {
|
||||||
|
MetricID int64 `json:"metricID"`
|
||||||
|
MetricType MetricType `json:"metricType"`
|
||||||
|
FracDigits int `json:"fracDigits"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func listMetrics(db *qx.Db) (list []Metric, err error) {
|
||||||
|
var tmp []struct {
|
||||||
|
MetricID int64
|
||||||
|
FracDigits int
|
||||||
|
ConfigMetricID int64
|
||||||
|
CustomCategory MetricCategory
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.ListQuery(&tmp, `
|
||||||
|
SELECT m.metricID, mp.fracDigits, mp.configMetricID, mp.customCategory
|
||||||
|
FROM metrics m
|
||||||
|
INNER JOIN metric_profiles mp ON mp.profileID = m.metricProfileID
|
||||||
|
ORDER BY m.metricID ASC`)
|
||||||
|
|
||||||
|
for _, x := range tmp {
|
||||||
|
var (
|
||||||
|
category MetricCategory
|
||||||
|
metricType MetricType
|
||||||
|
)
|
||||||
|
if x.CustomCategory > 0 {
|
||||||
|
metricType = CategoryToMetricType(x.CustomCategory)
|
||||||
|
} else {
|
||||||
|
if x.ConfigMetricID > 0 {
|
||||||
|
found, err := db.OneQuery(&category, `SELECT category FROM config_metrics WHERE metricID=?`, x.ConfigMetricID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("not found configMetric %d", x.ConfigMetricID)
|
||||||
|
}
|
||||||
|
|
||||||
|
metricType = CategoryToMetricType(category)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("customCategory not set and configMetricID = 0\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list = append(list, Metric{
|
||||||
|
MetricID: x.MetricID,
|
||||||
|
FracDigits: x.FracDigits,
|
||||||
|
MetricType: metricType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Measure struct {
|
||||||
|
Time int64 `json:"tm" `
|
||||||
|
Value float64 `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func listMeasures(db *qx.Db, metricID int64) (list []Measure, err error) {
|
||||||
|
err = db.ListQuery(&list, "SELECT tm, value FROM f64 WHERE metricID=? ORDER BY tm ASC", metricID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppendTask struct {
|
||||||
|
MetricID uint32
|
||||||
|
MetricType MetricType
|
||||||
|
FracDigits byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyMeasures(db *qx.Db) (err error) {
|
||||||
|
metrics, err := listMetrics(db)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("found %d metrics\n", len(metrics))
|
||||||
|
|
||||||
|
wg := new(sync.WaitGroup)
|
||||||
|
stopCh := make(chan struct{})
|
||||||
|
taskCh := make(chan AppendTask)
|
||||||
|
|
||||||
|
for range 10 {
|
||||||
|
go writer(db, taskCh, wg, stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, metric := range metrics {
|
||||||
|
taskCh <- AppendTask{
|
||||||
|
MetricID: uint32(metric.MetricID),
|
||||||
|
MetricType: metric.MetricType,
|
||||||
|
FracDigits: byte(metric.FracDigits),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(stopCh)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writer(db *qx.Db, taskCh chan AppendTask, wg *sync.WaitGroup, stopCh chan struct{}) {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case task := <-taskCh:
|
||||||
|
err := writeMetricIntoFile(db, task)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-stopCh:
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// const maxQtyInPack = 65535
|
||||||
|
|
||||||
|
// func writer(taskCh chan AppendTask, wg *sync.WaitGroup, stopCh chan struct{}) {
|
||||||
|
// wg.Add(1)
|
||||||
|
|
||||||
|
// c, err := client.Connect(":12345")
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalln(err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// for {
|
||||||
|
// select {
|
||||||
|
// case task := <-taskCh:
|
||||||
|
// err = writeMetricIntoOctopus(c, task)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Println(err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// case <-stopCh:
|
||||||
|
// wg.Done()
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
func correctToMonotonic(measures []Measure) []Measure {
|
||||||
|
if len(measures) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if measures[0].Value < 0 {
|
||||||
|
var (
|
||||||
|
idx int
|
||||||
|
m Measure
|
||||||
|
)
|
||||||
|
for idx, m = range measures {
|
||||||
|
if m.Value >= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
measures = measures[idx:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(measures) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
baseValue float64
|
||||||
|
prevValue = measures[0].Value // как прислал прибор
|
||||||
|
)
|
||||||
|
for idx := 1; idx < len(measures); idx++ {
|
||||||
|
measure := measures[idx]
|
||||||
|
|
||||||
|
if measure.Value < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if measure.Value < prevValue {
|
||||||
|
// произошел сброс
|
||||||
|
baseValue += prevValue
|
||||||
|
}
|
||||||
|
prevValue = measure.Value
|
||||||
|
|
||||||
|
if baseValue > 0 {
|
||||||
|
measure.Value += baseValue
|
||||||
|
measures[idx] = measure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return measures
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeMetricIntoFile(db *qx.Db, task AppendTask) (err error) {
|
||||||
|
measures, err := listMeasures(db, int64(task.MetricID))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("listMeasures(%d): %s", task.MetricID, err)
|
||||||
|
}
|
||||||
|
//fmt.Printf("metric %d has %d measures\n", metric.MetricID, len(measures))
|
||||||
|
if len(measures) < 1000 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("%d.%d", task.MetricID, task.FracDigits)
|
||||||
|
|
||||||
|
if task.MetricType == MetricTypeCumulative {
|
||||||
|
measures = correctToMonotonic(measures)
|
||||||
|
|
||||||
|
if len(measures) < 1000 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename = "cumulative/" + filename
|
||||||
|
} else {
|
||||||
|
filename = "instant/" + filename
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := bufio.NewWriter(file)
|
||||||
|
|
||||||
|
for _, measure := range measures {
|
||||||
|
err = bin.WriteUint32(dst, uint32(measure.Time))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = bin.WriteFloat64(dst, measure.Value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dst.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("%d: %d measures written\n", task.MetricID, len(measures))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func writeMetricIntoOctopus(c *client.Connection, task AppendTask) error {
|
||||||
|
// err := c.AddMetric(proto.AddMetricReq{
|
||||||
|
// MetricID: task.MetricID,
|
||||||
|
// MetricType: octopus.MetricType(task.MetricType),
|
||||||
|
// FracDigits: int(task.FracDigits),
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// } else {
|
||||||
|
// fmt.Println("metric added")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// t1 := time.Now()
|
||||||
|
// var (
|
||||||
|
// prevTime int64
|
||||||
|
// pack []proto.Measure
|
||||||
|
// )
|
||||||
|
|
||||||
|
// for _, measure := range task.Measures {
|
||||||
|
// if measure.Time <= prevTime {
|
||||||
|
// continue
|
||||||
|
// } else {
|
||||||
|
// prevTime = measure.Time
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pack = append(pack, proto.Measure{
|
||||||
|
// Timestamp: uint32(measure.Time),
|
||||||
|
// Value: measure.Value,
|
||||||
|
// })
|
||||||
|
|
||||||
|
// if len(pack) == maxQtyInPack {
|
||||||
|
// err = c.AppendMeasures(proto.AppendMeasuresReq{
|
||||||
|
// MetricID: task.MetricID,
|
||||||
|
// Measures: pack,
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// return fmt.Errorf("append measures (%d): %s", task.MetricID, err)
|
||||||
|
// } else {
|
||||||
|
// //fmt.Printf("written %d measures of %d\n", len(pack), len(task.Measures))
|
||||||
|
// pack = nil
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if len(pack) > 0 {
|
||||||
|
// err = c.AppendMeasures(proto.AppendMeasuresReq{
|
||||||
|
// MetricID: task.MetricID,
|
||||||
|
// Measures: pack,
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// return fmt.Errorf("append measures (%d): %s", task.MetricID, err)
|
||||||
|
// } else {
|
||||||
|
// //fmt.Printf("written %d measures of %d\n", len(pack), len(task.Measures))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//fmt.Printf("written %d in %.2f seconds\n", len(task.Measures), time.Since(t1).Seconds())
|
||||||
|
//return nil
|
||||||
|
//}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
db, err := qx.Open("mysql", DSN)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
copyAllMetrics(db)
|
||||||
|
//copyAllMetricsByOne()
|
||||||
|
//copyOneMetric(db, 134)
|
||||||
|
// metrics, err := listMetrics(db)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalln(err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pretty.Println(metrics)
|
||||||
|
//checkAllMetrics(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyAllMetrics(db *qx.Db) {
|
||||||
|
t1 := time.Now()
|
||||||
|
err := copyMeasures(db)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("total time: %.1f seconds\n", time.Since(t1).Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
// func copyOneMetric(db *qx.Db, metricID int64) {
|
||||||
|
// measures, err := listMeasures(db, metricID)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalln(err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// c, err := client.Connect(":12345")
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalln(err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// writeMetricIntoOctopus(c, AppendTask{
|
||||||
|
// MetricID: uint32(metricID),
|
||||||
|
// MetricType: MetricTypeInstant,
|
||||||
|
// FracDigits: 3,
|
||||||
|
// Measures: measures,
|
||||||
|
// })
|
||||||
|
|
||||||
|
// // measures = measures[:2300]
|
||||||
|
|
||||||
|
// // timestampsBuf := conbuf.New(nil)
|
||||||
|
// // timestamps := chunkenc.NewReverseTimeDeltaOfDeltaCompressor(timestampsBuf, 0)
|
||||||
|
|
||||||
|
// // valuesBuf := conbuf.New(nil)
|
||||||
|
// // values := chunkenc.NewReverseInstantDeltaCompressor(valuesBuf, 0, 3)
|
||||||
|
|
||||||
|
// // for _, measure := range measures[:10] {
|
||||||
|
// // // if measure.Time < 1686671940 {
|
||||||
|
// // // fmt.Printf("idx: %d\n", idx)
|
||||||
|
// // // continue
|
||||||
|
// // // }
|
||||||
|
|
||||||
|
// // //fmt.Printf("ts: %d, v: %.2f\n", measure.Time, measure.Value)
|
||||||
|
|
||||||
|
// // timestamps.Append(uint32(measure.Time))
|
||||||
|
// // values.Append(measure.Value)
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // timestampDecompressor := chunkenc.NewReverseTimeDeltaOfDeltaDecompressor(
|
||||||
|
// // timestampsBuf,
|
||||||
|
// // timestamps.Size(),
|
||||||
|
// // )
|
||||||
|
|
||||||
|
// // valueDecompressor := chunkenc.NewReverseInstantDeltaDecompressor(
|
||||||
|
// // valuesBuf,
|
||||||
|
// // values.Size(),
|
||||||
|
// // 3,
|
||||||
|
// // )
|
||||||
|
|
||||||
|
// // fmt.Println("----------------------------------------------------")
|
||||||
|
|
||||||
|
// // var (
|
||||||
|
// // value float64
|
||||||
|
// // timestamp uint32
|
||||||
|
// // done bool
|
||||||
|
// // )
|
||||||
|
|
||||||
|
// // for {
|
||||||
|
// // timestamp, done = timestampDecompressor.NextValue()
|
||||||
|
// // if done {
|
||||||
|
// // break
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // value, done = valueDecompressor.NextValue()
|
||||||
|
// // if done {
|
||||||
|
// // fmt.Printf("ts before crash: %d\n", timestamp)
|
||||||
|
// // panic("FUCK")
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // fmt.Printf("ts: %d, v: %.2f\n", timestamp, value)
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func checkAllMetrics(db *qx.Db) {
|
||||||
|
// metrics, err := listMetrics(db)
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// //metrics = metrics[:100]
|
||||||
|
|
||||||
|
// c, err := client.Connect(":12345")
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalln(err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var total int
|
||||||
|
|
||||||
|
// for _, metric := range metrics {
|
||||||
|
// if metric.MetricType == MetricTypeCumulative {
|
||||||
|
// list, err := c.ListAllCumulativeMeasures(uint32(metric.MetricID))
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Printf("ListAllCumulativeMeasures(%d): %s\n", metric.MetricID, err)
|
||||||
|
// } else {
|
||||||
|
// total += len(list)
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// list, err := c.ListAllInstantMeasures(uint32(metric.MetricID))
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Printf("ListAllInstantMeasures(%d): %s\n", metric.MetricID, err)
|
||||||
|
// } else {
|
||||||
|
// total += len(list)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// //fmt.Printf("#%d: %d\n", idx, metric.MetricID)
|
||||||
|
|
||||||
|
// }
|
||||||
|
// fmt.Printf("total: %d\n", total)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// копирование равномерное
|
||||||
|
type Reading struct {
|
||||||
|
MetricID int
|
||||||
|
ReadAt time.Time
|
||||||
|
Value float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyAllMetricsByOne() {
|
||||||
|
mydb, err := qx.Open("mysql", DSN)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics, err := listMetrics(mydb)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mydb.Close()
|
||||||
|
|
||||||
|
//metrics = metrics[:100]
|
||||||
|
|
||||||
|
fmt.Printf("found %d metrics\n", len(metrics))
|
||||||
|
|
||||||
|
// Підключення до MySQL
|
||||||
|
db, err := sql.Open("mysql", DSN)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Завантажити всі показники, відсортовані по часу
|
||||||
|
var (
|
||||||
|
ts = time.Now().Unix()
|
||||||
|
total int
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Рівномірна вставка: поки є хоча б один показник
|
||||||
|
tx, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
localTs := ts
|
||||||
|
|
||||||
|
for range 100 {
|
||||||
|
localTs += 60
|
||||||
|
for _, metric := range metrics {
|
||||||
|
_, err = tx.Exec("INSERT INTO readings (metricID, tm, value) VALUES (?, ?, ?)",
|
||||||
|
metric.MetricID, localTs, 1232232)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Insert error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
total++
|
||||||
|
if total > 841000000 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("inserted: %d\n", total)
|
||||||
|
|
||||||
|
if total > 841000000 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ts += 660
|
||||||
|
}
|
||||||
|
}
|
||||||
1
prepare/main_test.go
Normal file
1
prepare/main_test.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package main
|
||||||
BIN
prepare/prepare
Executable file
BIN
prepare/prepare
Executable file
Binary file not shown.
@@ -175,12 +175,12 @@ func Recovery(dir string, databaseName string) (_ RecoveryReport, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("before replay\n")
|
//fmt.Printf("before replay\n")
|
||||||
err = walReplayer.Replay()
|
err = walReplayer.Replay()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("after replay\n")
|
//fmt.Printf("after replay\n")
|
||||||
// перезаписую сторінки із останнього комміта
|
// перезаписую сторінки із останнього комміта
|
||||||
var dataFile *os.File
|
var dataFile *os.File
|
||||||
dataFile, err = os.OpenFile(qb.GetDataFilePath(dir, databaseName), os.O_CREATE|os.O_WRONLY, 0666)
|
dataFile, err = os.OpenFile(qb.GetDataFilePath(dir, databaseName), os.O_CREATE|os.O_WRONLY, 0666)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
|
|
||||||
bin "gordenko.dev/dima/bin/little"
|
bin "gordenko.dev/dima/bin/little"
|
||||||
"gordenko.dev/dima/qb"
|
"gordenko.dev/dima/qb"
|
||||||
|
"gordenko.dev/dima/qb/enc"
|
||||||
|
"gordenko.dev/dima/qb/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BackwardCursor struct {
|
type BackwardCursor struct {
|
||||||
@@ -29,6 +31,7 @@ type BackwardCursorOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewBackwardCursor(opt BackwardCursorOptions) (*BackwardCursor, error) {
|
func NewBackwardCursor(opt BackwardCursorOptions) (*BackwardCursor, error) {
|
||||||
|
fmt.Printf("footer: % x\n", opt.PageData[DataPageSize-DataPageFooterSize:])
|
||||||
switch opt.MetricType {
|
switch opt.MetricType {
|
||||||
case qb.Instant, qb.Cumulative:
|
case qb.Instant, qb.Cumulative:
|
||||||
// ok
|
// ok
|
||||||
@@ -90,6 +93,7 @@ func (s *BackwardCursor) Prev() (uint32, float64, bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prevPageNo, _ := bin.GetUint32(s.pageData[prevPageIdx:])
|
prevPageNo, _ := bin.GetUint32(s.pageData[prevPageIdx:])
|
||||||
|
fmt.Printf("CURSOR: prev pageNo %d\n", prevPageNo)
|
||||||
if prevPageNo == 0 {
|
if prevPageNo == 0 {
|
||||||
return 0, 0, true, nil
|
return 0, 0, true, nil
|
||||||
}
|
}
|
||||||
@@ -129,8 +133,51 @@ func (s *BackwardCursor) Close() {
|
|||||||
s.releasePage(s.pageNo)
|
s.releasePage(s.pageNo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HELPER
|
func CreateTimestampDecompressor(page []byte) (qb.TimestampDecompressor, error) {
|
||||||
|
size, _ := bin.GetUint16(page[timestampsSizeIdx:])
|
||||||
|
if int(size) > DataPagePayloadSize {
|
||||||
|
return nil, fmt.Errorf("bug: invalid timestamps size %d", size)
|
||||||
|
}
|
||||||
|
pos := DataPagePayloadSize - int(size)
|
||||||
|
d := enc.NewTimeDeltaDecompressor()
|
||||||
|
d.RestoreFromEnd(page[pos:DataPagePayloadSize])
|
||||||
|
|
||||||
//func (s *BackwardCursor) makeDecompressors() error {
|
payload := page[pos:DataPagePayloadSize]
|
||||||
|
|
||||||
//}
|
fmt.Printf("PAGE timestamps (CURSOR) %d:\n% x\n", len(payload), payload)
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateValueDecompressor(page []byte, metricType qb.MetricType, fracDigits byte) (qb.ValueDecompressor, error) {
|
||||||
|
size, _ := bin.GetUint16(page[valuesSizeIdx:])
|
||||||
|
if int(size) > DataPagePayloadSize {
|
||||||
|
return nil, fmt.Errorf("bug: invalid values size %d", size)
|
||||||
|
}
|
||||||
|
d := enc.NewValueDeltaDecompressor(metricType, fracDigits)
|
||||||
|
d.RestoreFromEnd(page[:size])
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func VerifyDataPageCRC32(data []byte) error {
|
||||||
|
var (
|
||||||
|
calculatedCRC = util.CalculateCRC32(data[:dataCRC32Idx])
|
||||||
|
writtenCRC, _ = bin.GetUint32(data[dataCRC32Idx:])
|
||||||
|
)
|
||||||
|
if calculatedCRC != writtenCRC {
|
||||||
|
return fmt.Errorf("calculated CRC32 %d are not equal written CRC32 %d",
|
||||||
|
calculatedCRC, writtenCRC)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func VerifyIndexPageCRC32(data []byte) error {
|
||||||
|
var (
|
||||||
|
calculatedCRC = util.CalculateCRC32(data[:indexCRC32Idx])
|
||||||
|
writtenCRC, _ = bin.GetUint32(data[indexCRC32Idx:])
|
||||||
|
)
|
||||||
|
if calculatedCRC != writtenCRC {
|
||||||
|
return fmt.Errorf("calculated CRC32 %d are not equal written CRC32 %d",
|
||||||
|
calculatedCRC, writtenCRC)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
package storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
bin "gordenko.dev/dima/bin/little"
|
|
||||||
"gordenko.dev/dima/qb"
|
|
||||||
"gordenko.dev/dima/qb/enc"
|
|
||||||
"gordenko.dev/dima/qb/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreateTimestampDecompressor(page []byte) (qb.TimestampDecompressor, error) {
|
|
||||||
size, _ := bin.GetUint16(page[timestampsSizeIdx:])
|
|
||||||
if size > DataPageFooterSize {
|
|
||||||
return nil, fmt.Errorf("bug: invalid timestamps size %d", size)
|
|
||||||
}
|
|
||||||
pos := DataPagePayloadSize - int(size)
|
|
||||||
d := enc.NewTimeDeltaDecompressor()
|
|
||||||
d.RestoreFromEnd(page[pos:DataPagePayloadSize])
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateValueDecompressor(page []byte, metricType qb.MetricType, fracDigits byte) (qb.ValueDecompressor, error) {
|
|
||||||
size, _ := bin.GetUint16(page[valuesSizeIdx:])
|
|
||||||
if size > DataPageFooterSize {
|
|
||||||
return nil, fmt.Errorf("bug: invalid timestamps size %d", size)
|
|
||||||
}
|
|
||||||
d := enc.NewValueDeltaDecompressor(metricType, fracDigits)
|
|
||||||
d.RestoreFromEnd(page[:size])
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func VerifyDataPageCRC32(data []byte) error {
|
|
||||||
var (
|
|
||||||
calculatedCRC = util.CalculateCRC32(data[:dataCRC32Idx])
|
|
||||||
writtenCRC, _ = bin.GetUint32(data[dataCRC32Idx:])
|
|
||||||
)
|
|
||||||
if calculatedCRC != writtenCRC {
|
|
||||||
return fmt.Errorf("calculated CRC32 %d are not equal written CRC32 %d",
|
|
||||||
calculatedCRC, writtenCRC)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func VerifyIndexPageCRC32(data []byte) error {
|
|
||||||
var (
|
|
||||||
calculatedCRC = util.CalculateCRC32(data[:indexCRC32Idx])
|
|
||||||
writtenCRC, _ = bin.GetUint32(data[indexCRC32Idx:])
|
|
||||||
)
|
|
||||||
if calculatedCRC != writtenCRC {
|
|
||||||
return fmt.Errorf("calculated CRC32 %d are not equal written CRC32 %d",
|
|
||||||
calculatedCRC, writtenCRC)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import bin "gordenko.dev/dima/bin/little"
|
import (
|
||||||
|
bin "gordenko.dev/dima/bin/little"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PageNoSize = 4
|
PageNoSize = 4
|
||||||
@@ -61,16 +63,6 @@ func BinarySearch(qty int, keyComparator KeyComparator) (elemIdx int, isFound bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// func getPrevPageNo(buf []byte) uint32 {
|
|
||||||
// pageNo, _ := bin.GetUint32(buf[prevPageIdx:])
|
|
||||||
// return pageNo
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func GetIndexRecordsSince(buf []byte) (pageNo uint32) {
|
|
||||||
// pageNo, _ = bin.GetUint32(buf)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
func FindPageOnIndexLevelTail(level IndexLevelTail, timestamp uint32) (pageNo uint32) {
|
func FindPageOnIndexLevelTail(level IndexLevelTail, timestamp uint32) (pageNo uint32) {
|
||||||
comparator := ValueAtComparator{
|
comparator := ValueAtComparator{
|
||||||
buf: level.Buffer,
|
buf: level.Buffer,
|
||||||
@@ -82,17 +74,6 @@ func FindPageOnIndexLevelTail(level IndexLevelTail, timestamp uint32) (pageNo ui
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// func FindPageOnRecords(records []byte, timestamp uint32) (pageNo uint32) {
|
|
||||||
// comparator := ValueAtComparator{
|
|
||||||
// buf: records,
|
|
||||||
// timestamp: timestamp,
|
|
||||||
// }
|
|
||||||
// count := len(records) / IndexRecordSize
|
|
||||||
// elemIdx, _ := BinarySearch(count, comparator)
|
|
||||||
// pageNo, _ = bin.GetUint32(records[elemIdx*IndexRecordSize:])
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
func FindPageOnIndexPage(page []byte, timestamp uint32) (pageNo uint32) {
|
func FindPageOnIndexPage(page []byte, timestamp uint32) (pageNo uint32) {
|
||||||
comparator := ValueAtComparator{
|
comparator := ValueAtComparator{
|
||||||
buf: page,
|
buf: page,
|
||||||
@@ -123,6 +104,108 @@ func FindPageOnIndexLevelTails(levels []IndexLevelTail, timestamp uint32) (pageN
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeleteSinceOnIndexPageResult struct {
|
||||||
|
Buffer []byte
|
||||||
|
RecordsCount int
|
||||||
|
PageNumbers []uint32
|
||||||
|
//Idx int
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteSinceOnIndexPage(buf []byte, since uint32) DeleteSinceOnIndexPageResult {
|
||||||
|
comparator := ValueAtComparator{
|
||||||
|
buf: buf,
|
||||||
|
timestamp: since,
|
||||||
|
}
|
||||||
|
count, _ := bin.GetUint16(buf[indexRecordsCountIdx:])
|
||||||
|
elemIdx, _ := BinarySearch(int(count), comparator)
|
||||||
|
newbuf := make([]byte, IndexPageSize)
|
||||||
|
copy(newbuf, buf[:elemIdx*IndexRecordSize]) // [0..idx)
|
||||||
|
pos := elemIdx*IndexRecordSize + 4 // timestamp size
|
||||||
|
deleteCount := int(count) - elemIdx
|
||||||
|
var pageNumbers []uint32
|
||||||
|
for range deleteCount {
|
||||||
|
pageNo, _ := bin.GetUint32(buf[pos:])
|
||||||
|
pageNumbers = append(pageNumbers, pageNo)
|
||||||
|
pos += IndexRecordSize
|
||||||
|
}
|
||||||
|
|
||||||
|
return DeleteSinceOnIndexPageResult{
|
||||||
|
Buffer: newbuf,
|
||||||
|
RecordsCount: elemIdx,
|
||||||
|
PageNumbers: pageNumbers,
|
||||||
|
//Idx: elemIdx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteOnIndexTailResult struct {
|
||||||
|
RecordsCount int
|
||||||
|
PageNumbers []uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteSinceOnIndexTail(level IndexLevelTail, since uint32) DeleteOnIndexTailResult {
|
||||||
|
comparator := ValueAtComparator{
|
||||||
|
buf: level.Buffer,
|
||||||
|
timestamp: since,
|
||||||
|
}
|
||||||
|
elemIdx, _ := BinarySearch(level.RecordsCount, comparator)
|
||||||
|
pos := elemIdx*IndexRecordSize + 4 // timestamp size
|
||||||
|
deleteCount := level.RecordsCount - elemIdx
|
||||||
|
var pageNumbers []uint32
|
||||||
|
for range deleteCount {
|
||||||
|
pageNo, _ := bin.GetUint32(level.Buffer[pos:])
|
||||||
|
pageNumbers = append(pageNumbers, pageNo)
|
||||||
|
pos += IndexRecordSize
|
||||||
|
}
|
||||||
|
return DeleteOnIndexTailResult{
|
||||||
|
RecordsCount: elemIdx,
|
||||||
|
PageNumbers: pageNumbers,
|
||||||
|
//Idx: elemIdx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteUntilOnIndexTail(level IndexLevelTail, until uint32) DeleteOnIndexTailResult {
|
||||||
|
comparator := ValueAtComparator{
|
||||||
|
buf: level.Buffer,
|
||||||
|
timestamp: until,
|
||||||
|
}
|
||||||
|
elemIdx, _ := BinarySearch(level.RecordsCount, comparator)
|
||||||
|
pos := 4 // timestamp size
|
||||||
|
deleteCount := elemIdx + 1
|
||||||
|
var pageNumbers []uint32
|
||||||
|
for range deleteCount {
|
||||||
|
pageNo, _ := bin.GetUint32(level.Buffer[pos:])
|
||||||
|
pageNumbers = append(pageNumbers, pageNo)
|
||||||
|
pos += IndexRecordSize
|
||||||
|
}
|
||||||
|
copy(level.Buffer, level.Buffer[:])
|
||||||
|
return DeleteOnIndexTailResult{
|
||||||
|
RecordsCount: elemIdx,
|
||||||
|
PageNumbers: pageNumbers,
|
||||||
|
//Idx: elemIdx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func getPrevPageNo(buf []byte) uint32 {
|
||||||
|
// pageNo, _ := bin.GetUint32(buf[prevPageIdx:])
|
||||||
|
// return pageNo
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func GetIndexRecordsSince(buf []byte) (pageNo uint32) {
|
||||||
|
// pageNo, _ = bin.GetUint32(buf)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func FindPageOnRecords(records []byte, timestamp uint32) (pageNo uint32) {
|
||||||
|
// comparator := ValueAtComparator{
|
||||||
|
// buf: records,
|
||||||
|
// timestamp: timestamp,
|
||||||
|
// }
|
||||||
|
// count := len(records) / IndexRecordSize
|
||||||
|
// elemIdx, _ := BinarySearch(count, comparator)
|
||||||
|
// pageNo, _ = bin.GetUint32(records[elemIdx*IndexRecordSize:])
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
// func findPageNoIdx(buf []byte, timestamp uint32) (idx int) {
|
// func findPageNoIdx(buf []byte, timestamp uint32) (idx int) {
|
||||||
// comparator := ValueAtComparator{
|
// comparator := ValueAtComparator{
|
||||||
// buf: buf,
|
// buf: buf,
|
||||||
@@ -167,16 +250,17 @@ func FindPageOnIndexLevelTails(levels []IndexLevelTail, timestamp uint32) (pageN
|
|||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// func listPageNumbers(buf []byte) (pageNumbers []uint32) {
|
// from index page
|
||||||
// qty, _ := bin.GetUint16(buf[indexRecordsQtyIdx:])
|
func ListPageNumbers(buf []byte) (pageNumbers []uint32) {
|
||||||
// pos := indexFooterIdx - PageNoSize
|
count, _ := bin.GetUint16(buf[indexRecordsCountIdx:])
|
||||||
// for range qty {
|
pos := timestampSize
|
||||||
// pageNo, _ := bin.GetUint32(buf[pos:])
|
for range count {
|
||||||
// pageNumbers = append(pageNumbers, pageNo)
|
pageNo, _ := bin.GetUint32(buf[pos:])
|
||||||
// pos -= PageNoSize
|
pageNumbers = append(pageNumbers, pageNo)
|
||||||
// }
|
pos += IndexRecordSize
|
||||||
// return
|
}
|
||||||
// }
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// // include since timestamp
|
// // include since timestamp
|
||||||
// func listPageNumbersSince(buf []byte, timestamp uint32) (pageNumbers []uint32) {
|
// func listPageNumbersSince(buf []byte, timestamp uint32) (pageNumbers []uint32) {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ type ReplayMeasuresAppendWithGrowResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ReplayMetric) MeasuresAppendWithGrow(rec MeasuresAppendWithGrowRecord, isLastPacket bool) (_ ReplayMeasuresAppendWithGrowResult) {
|
func (s *ReplayMetric) MeasuresAppendWithGrow(rec MeasuresAppendWithGrowRecord, isLastPacket bool) (_ ReplayMeasuresAppendWithGrowResult) {
|
||||||
|
//fmt.Printf("%#v\n", rec)
|
||||||
var (
|
var (
|
||||||
reusedIndexPages int
|
reusedIndexPages int
|
||||||
reusedDataPages int
|
reusedDataPages int
|
||||||
@@ -164,17 +165,34 @@ func (s *ReplayMetric) MeasuresAppendWithGrow(rec MeasuresAppendWithGrowRecord,
|
|||||||
// HELPERS
|
// HELPERS
|
||||||
|
|
||||||
func (s *ReplayMetric) composeHeadIndexPage(levelIdx int, head *IndexPageTail) PageToWrite {
|
func (s *ReplayMetric) composeHeadIndexPage(levelIdx int, head *IndexPageTail) PageToWrite {
|
||||||
level := s.IndexLevelTails[levelIdx]
|
var (
|
||||||
// розраховую pos, з якого буду дописувати хвіст
|
page = make([]byte, IndexPageSize)
|
||||||
pos := level.RecordsCount * IndexRecordSize
|
pos int
|
||||||
// створюю копію сторінки
|
prevCount int
|
||||||
page := make([]byte, IndexPageSize)
|
)
|
||||||
copy(page, level.Buffer[:pos]) // поточні дані
|
//fmt.Printf("composeHeadIndexPage: levels=%d\n", len(s.IndexLevelTails))
|
||||||
|
//fmt.Printf("head: % x\n", head.Records)
|
||||||
|
if levelIdx < len(s.IndexLevelTails) {
|
||||||
|
level := s.IndexLevelTails[levelIdx]
|
||||||
|
|
||||||
|
//fmt.Printf("level (%d): % x\n", level.RecordsCount, level.Buffer)
|
||||||
|
// розраховую pos, з якого буду дописувати хвіст
|
||||||
|
pos = level.RecordsCount * IndexRecordSize
|
||||||
|
// створюю копію сторінки
|
||||||
|
copy(page, level.Buffer[:pos]) // поточні дані
|
||||||
|
prevCount = level.RecordsCount
|
||||||
|
} else {
|
||||||
|
//
|
||||||
|
s.IndexLevelTails = append(s.IndexLevelTails, IndexLevelTail{
|
||||||
|
Buffer: page,
|
||||||
|
RecordsCount: len(head.Records) / IndexRecordSize,
|
||||||
|
})
|
||||||
|
}
|
||||||
copy(page[pos:], head.Records)
|
copy(page[pos:], head.Records)
|
||||||
// запечатати сторінку
|
// запечатати сторінку
|
||||||
calculatedCRC := SealIndexPage(SealIndexPageIn{
|
calculatedCRC := SealIndexPage(SealIndexPageIn{
|
||||||
Content: page,
|
Content: page,
|
||||||
RecordsCount: level.RecordsCount + len(head.Records)/IndexRecordSize,
|
RecordsCount: prevCount + len(head.Records)/IndexRecordSize,
|
||||||
ZeroLevel: levelIdx == 0,
|
ZeroLevel: levelIdx == 0,
|
||||||
})
|
})
|
||||||
// перевірка CRC
|
// перевірка CRC
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ var (
|
|||||||
prevPageIdx = DataPageSize - 12
|
prevPageIdx = DataPageSize - 12
|
||||||
|
|
||||||
// index page
|
// index page
|
||||||
IndexPageSize = 1024
|
IndexPageSize = 24 // 1024
|
||||||
|
|
||||||
IndexPagePayloadSize = IndexPageSize - IndexPageFooterSize
|
IndexPagePayloadSize = IndexPageSize - IndexPageFooterSize
|
||||||
//indexPageIncSize = IndexPageIncSize
|
//indexPageIncSize = IndexPageIncSize
|
||||||
@@ -27,7 +27,7 @@ var (
|
|||||||
|
|
||||||
maxRecordsOnIndexPage = (IndexPageSize - IndexPageFooterSize) / IndexRecordSize
|
maxRecordsOnIndexPage = (IndexPageSize - IndexPageFooterSize) / IndexRecordSize
|
||||||
|
|
||||||
// timestampSize = 4
|
timestampSize = 4
|
||||||
// pairSize = timestampSize + PageNoSize
|
// pairSize = timestampSize + PageNoSize
|
||||||
// dataFooterIdx = timestampsSizeIdx
|
// dataFooterIdx = timestampsSizeIdx
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
bin "gordenko.dev/dima/bin/little"
|
bin "gordenko.dev/dima/bin/little"
|
||||||
@@ -270,10 +269,10 @@ func finalizePacket(w *bytes.Buffer, checksum uint32) []byte {
|
|||||||
start := 9 - bin.CountVarSize(bodySize)
|
start := 9 - bin.CountVarSize(bodySize)
|
||||||
bin.PutVarSize(packet[start:], bodySize)
|
bin.PutVarSize(packet[start:], bodySize)
|
||||||
|
|
||||||
fmt.Printf("checksum: %d\n", checksum)
|
//fmt.Printf("checksum: %d\n", checksum)
|
||||||
fmt.Printf("bodySize: %d\n", bodySize)
|
//fmt.Printf("bodySize: %d\n", bodySize)
|
||||||
//fmt.Printf("start: %d\n", start)
|
//fmt.Printf("start: %d\n", start)
|
||||||
fmt.Printf("body: % x\n", packet[9:len(packet)-4])
|
//fmt.Printf("body: % x\n", packet[9:len(packet)-4])
|
||||||
|
|
||||||
return packet[start:]
|
return packet[start:]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const (
|
|||||||
|
|
||||||
filePerm = 0770
|
filePerm = 0770
|
||||||
|
|
||||||
dumpSnapshotAfterNBytes = 1024 * 1024 * 1024 // 1 GB
|
dumpSnapshotAfterNBytes = 20 * 1024 // 1024 * 1024 * 1024 // 1 GB
|
||||||
|
|
||||||
writeBufferSize = 4 * 1024 * 1024
|
writeBufferSize = 4 * 1024 * 1024
|
||||||
)
|
)
|
||||||
@@ -64,19 +64,18 @@ type Writer struct {
|
|||||||
indexPagesCount uint32
|
indexPagesCount uint32
|
||||||
dataFreeList *freelist.FreeList
|
dataFreeList *freelist.FreeList
|
||||||
indexFreeList *freelist.FreeList
|
indexFreeList *freelist.FreeList
|
||||||
//atree *atree.Atree
|
dir string
|
||||||
dir string
|
databaseName string
|
||||||
databaseName string
|
w *bytes.Buffer // wal буфер для упаковки даних перед записом на диск
|
||||||
w *bytes.Buffer // wal буфер для упаковки даних перед записом на диск
|
wal *os.File
|
||||||
wal *os.File
|
dataFile *os.File
|
||||||
dataFile *os.File
|
indexFile *os.File
|
||||||
indexFile *os.File
|
input []any
|
||||||
input []any
|
writePreparer *WritePreparer
|
||||||
writePreparer *WritePreparer
|
written int64
|
||||||
written int64
|
isExited bool
|
||||||
isExited bool
|
exitCh chan struct{}
|
||||||
exitCh chan struct{}
|
waitGroup *sync.WaitGroup
|
||||||
waitGroup *sync.WaitGroup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type WriterOptions struct {
|
type WriterOptions struct {
|
||||||
@@ -225,7 +224,7 @@ func (s *Writer) packAndWrite() (err error) {
|
|||||||
prepared := s.writePreparer.Prepare(input)
|
prepared := s.writePreparer.Prepare(input)
|
||||||
|
|
||||||
//fmt.Printf("prepared: %#v\n", prepared)
|
//fmt.Printf("prepared: %#v\n", prepared)
|
||||||
fmt.Println("prepared")
|
//fmt.Println("prepared")
|
||||||
|
|
||||||
// 3. Пишу на диск WAL (append)
|
// 3. Пишу на диск WAL (append)
|
||||||
n, err := s.wal.Write(prepared.Packet)
|
n, err := s.wal.Write(prepared.Packet)
|
||||||
@@ -239,7 +238,8 @@ func (s *Writer) packAndWrite() (err error) {
|
|||||||
if err = s.wal.Sync(); err != nil {
|
if err = s.wal.Sync(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("synced to wal")
|
s.written += int64(len(prepared.Packet))
|
||||||
|
//fmt.Println("synced to wal")
|
||||||
|
|
||||||
// 4. Пишу в atree сторінки
|
// 4. Пишу в atree сторінки
|
||||||
if len(prepared.WriteToData) > 0 {
|
if len(prepared.WriteToData) > 0 {
|
||||||
@@ -250,7 +250,7 @@ func (s *Writer) packAndWrite() (err error) {
|
|||||||
if err = s.dataFile.Sync(); err != nil {
|
if err = s.dataFile.Sync(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("written to data")
|
//fmt.Println("written to data")
|
||||||
}
|
}
|
||||||
if len(prepared.WriteToIndex) > 0 {
|
if len(prepared.WriteToIndex) > 0 {
|
||||||
err = WriteIndexPages(s.indexFile, prepared.WriteToIndex)
|
err = WriteIndexPages(s.indexFile, prepared.WriteToIndex)
|
||||||
@@ -260,7 +260,7 @@ func (s *Writer) packAndWrite() (err error) {
|
|||||||
if err = s.indexFile.Sync(); err != nil {
|
if err = s.indexFile.Sync(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("written to index")
|
//fmt.Println("written to index")
|
||||||
}
|
}
|
||||||
// 6. відправляю input - worker-у
|
// 6. відправляю input - worker-у
|
||||||
|
|
||||||
@@ -296,6 +296,8 @@ func (s *Writer) packAndWrite() (err error) {
|
|||||||
|
|
||||||
snapshotNumber := <-snapshotNumberCh
|
snapshotNumber := <-snapshotNumberCh
|
||||||
|
|
||||||
|
//fmt.Println("got snapshot number:", snapshotNumber)
|
||||||
|
|
||||||
// копіюю сторінки із delta файла в base файл і потім роблю Truncate
|
// копіюю сторінки із delta файла в base файл і потім роблю Truncate
|
||||||
err = s.indexFreeList.Merge()
|
err = s.indexFreeList.Merge()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -305,6 +307,7 @@ func (s *Writer) packAndWrite() (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
//fmt.Println("free lists merged")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
s.wal, err = os.OpenFile(
|
s.wal, err = os.OpenFile(
|
||||||
@@ -315,11 +318,12 @@ func (s *Writer) packAndWrite() (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create new changes file: %s", err)
|
return fmt.Errorf("create new changes file: %s", err)
|
||||||
}
|
}
|
||||||
|
//fmt.Println("new wal created")
|
||||||
} else {
|
} else {
|
||||||
s.workerInbox.Push(Changes{
|
s.workerInbox.Push(Changes{
|
||||||
Commits: prepared.Commits,
|
Commits: prepared.Commits,
|
||||||
})
|
})
|
||||||
fmt.Println("pushed commits")
|
//fmt.Println("pushed commits")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
BIN
testdir/test.snapshot_1
Normal file
BIN
testdir/test.snapshot_1
Normal file
Binary file not shown.
Binary file not shown.
BIN
testdir/test.wal_1
Executable file
BIN
testdir/test.wal_1
Executable file
Binary file not shown.
145
worker/metric.go
145
worker/metric.go
@@ -2,12 +2,13 @@ package worker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
bin "gordenko.dev/dima/bin/little"
|
bin "gordenko.dev/dima/bin/little"
|
||||||
"gordenko.dev/dima/qb"
|
"gordenko.dev/dima/qb"
|
||||||
"gordenko.dev/dima/qb/inbox"
|
"gordenko.dev/dima/qb/inbox"
|
||||||
|
"gordenko.dev/dima/qb/proto"
|
||||||
"gordenko.dev/dima/qb/storage"
|
"gordenko.dev/dima/qb/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -119,37 +120,53 @@ func (s *Metric) ReleaseRLock() {
|
|||||||
s.rLocks--
|
s.rLocks--
|
||||||
if s.rLocks == 0 {
|
if s.rLocks == 0 {
|
||||||
if len(s.waitQueue) > 0 {
|
if len(s.waitQueue) > 0 {
|
||||||
s.ProcessQueue()
|
//s.ProcessQueue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// суть у тому що треба запускати запити, пока не зустріну XLock
|
type GetMetricResult struct {
|
||||||
func (s *Metric) ProcessQueue(metricID uint32, tmp []byte) {
|
MetricType qb.MetricType
|
||||||
if len(s.waitQueue) == 0 {
|
FracDigits byte
|
||||||
return
|
ResultCode byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetMetricReq struct {
|
||||||
|
MetricID uint32
|
||||||
|
ResultCh chan GetMetricResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Metric) GetMetric(req GetMetricReq) {
|
||||||
|
req.ResultCh <- GetMetricResult{
|
||||||
|
ResultCode: Succeed,
|
||||||
|
MetricType: s.metricType,
|
||||||
|
FracDigits: s.fracDigits,
|
||||||
}
|
}
|
||||||
for _, untyped := range s.waitQueue {
|
}
|
||||||
switch req := untyped.(type) {
|
|
||||||
case RangeScanReq:
|
func (s *Metric) DeleteMetric(req DeleteMetricReq) {
|
||||||
s.StartRangeScan(req)
|
if s.xLock || s.rLocks > 0 || s.capturedState != nil {
|
||||||
case FullScanReq:
|
s.waitQueue = append(s.waitQueue, req)
|
||||||
s.StartFullScan(req)
|
} else {
|
||||||
case GetMetricReq:
|
s.xLock = true
|
||||||
s.GetMetric(req)
|
// fix - has pages -> do query from goroutine
|
||||||
case AppendMeasuresReq:
|
// else push to storage
|
||||||
metric.AppendMeasures(req, tmp, s.storageInbox)
|
// collect all pages, than
|
||||||
case DeleteMetricReq:
|
// s.storageInbox.Push(storage.MetricDelete{
|
||||||
s.DeleteMetric(req)
|
// MetricID: req.MetricID,
|
||||||
case DeleteMeasuresReq:
|
// FreeIndexPages: nil,
|
||||||
metric.DeleteMeasures(req)
|
// FreeDataPages: nil,
|
||||||
default:
|
// ResultCh: req.ResultCh,
|
||||||
qb.Abort(qb.UnknownMetricWaitQueueItemBug,
|
// })
|
||||||
fmt.Errorf("bug: unknown metric wait queue item type %T", req))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AppendMeasuresReq struct {
|
||||||
|
MetricID uint32
|
||||||
|
Measures []proto.Measure
|
||||||
|
ResultCh chan storage.MeasuresAppendResult
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Metric) AppendMeasures(req AppendMeasuresReq, tmp []byte, storageInbox *inbox.Inbox) {
|
func (s *Metric) AppendMeasures(req AppendMeasuresReq, tmp []byte, storageInbox *inbox.Inbox) {
|
||||||
if s.xLock || s.capturedState != nil {
|
if s.xLock || s.capturedState != nil {
|
||||||
s.waitQueue = append(s.waitQueue, req)
|
s.waitQueue = append(s.waitQueue, req)
|
||||||
@@ -182,7 +199,9 @@ func (s *Metric) AppendMeasures(req AppendMeasuresReq, tmp []byte, storageInbox
|
|||||||
s.values.CaptureState()
|
s.values.CaptureState()
|
||||||
|
|
||||||
for idx, measure := range req.Measures {
|
for idx, measure := range req.Measures {
|
||||||
|
//fmt.Println(idx, measure.Timestamp)
|
||||||
if measure.Timestamp <= s.timestamps.Until() {
|
if measure.Timestamp <= s.timestamps.Until() {
|
||||||
|
//fmt.Printf("timestamp %d <= until %d\n", measure.Timestamp, s.timestamps.Until())
|
||||||
resultCode = ExpiredMeasure
|
resultCode = ExpiredMeasure
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -191,6 +210,8 @@ func (s *Metric) AppendMeasures(req AppendMeasuresReq, tmp []byte, storageInbox
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//fmt.Printf(" %d: %s => %.2f\n", idx, formatTime(measure.Timestamp), measure.Value)
|
||||||
|
|
||||||
tReport := timestamps.Evaluate(tmp[:7], measure.Timestamp)
|
tReport := timestamps.Evaluate(tmp[:7], measure.Timestamp)
|
||||||
//fmt.Printf("tReport: %#v\n", tReport)
|
//fmt.Printf("tReport: %#v\n", tReport)
|
||||||
vReport := values.Evaluate(tmp[7:], measure.Value)
|
vReport := values.Evaluate(tmp[7:], measure.Value)
|
||||||
@@ -198,7 +219,7 @@ func (s *Metric) AppendMeasures(req AppendMeasuresReq, tmp []byte, storageInbox
|
|||||||
|
|
||||||
totalRequiredSpace := tReport.TotalSpace + vReport.TotalSpace
|
totalRequiredSpace := tReport.TotalSpace + vReport.TotalSpace
|
||||||
|
|
||||||
if totalRequiredSpace <= len(s.buffer) {
|
if totalRequiredSpace <= storage.DataPagePayloadSize {
|
||||||
// якщо на сторінці є місце
|
// якщо на сторінці є місце
|
||||||
timestamps.Append(tReport.RewindOffset, tmp[:tReport.ChangeSize], measure.Timestamp)
|
timestamps.Append(tReport.RewindOffset, tmp[:tReport.ChangeSize], measure.Timestamp)
|
||||||
values.Append(vReport.RewindOffset, tmp[7:7+vReport.ChangeSize], measure.Value, vReport.Delta)
|
values.Append(vReport.RewindOffset, tmp[7:7+vReport.ChangeSize], measure.Value, vReport.Delta)
|
||||||
@@ -210,7 +231,8 @@ func (s *Metric) AppendMeasures(req AppendMeasuresReq, tmp []byte, storageInbox
|
|||||||
valuesRewindOffset = vReport.RewindOffset
|
valuesRewindOffset = vReport.RewindOffset
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("PAGE FILLED")
|
//fmt.Println("PAGE FILLED")
|
||||||
|
//fmt.Println(timestamps.Size() + values.Size())
|
||||||
// сторінка заповнена
|
// сторінка заповнена
|
||||||
since := s.timestamps.ReplaceSinceWithUntil()
|
since := s.timestamps.ReplaceSinceWithUntil()
|
||||||
|
|
||||||
@@ -227,6 +249,9 @@ func (s *Metric) AppendMeasures(req AppendMeasuresReq, tmp []byte, storageInbox
|
|||||||
ValuesSize: values.Size(),
|
ValuesSize: values.Size(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
//xxx := timestamps.Tail(0)
|
||||||
|
//fmt.Printf("PAGE timestamps %d:\n% x\n", len(xxx), xxx)
|
||||||
|
|
||||||
buf := make([]byte, storage.DataPageSize)
|
buf := make([]byte, storage.DataPageSize)
|
||||||
databuf := buf[:storage.DataPagePayloadSize]
|
databuf := buf[:storage.DataPagePayloadSize]
|
||||||
|
|
||||||
@@ -257,10 +282,14 @@ func (s *Metric) AppendMeasures(req AppendMeasuresReq, tmp []byte, storageInbox
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//xxx := timestamps.Tail(0)
|
||||||
|
//fmt.Printf("PAGE TAIL timestamps %d:\n% x\n", len(xxx), xxx)
|
||||||
|
|
||||||
// виділити змінені байти.
|
// виділити змінені байти.
|
||||||
// скопіювати. Причому можна скопіювати зрізи chunks
|
// скопіювати. Причому можна скопіювати зрізи chunks
|
||||||
|
|
||||||
if len(pages) > 0 {
|
if len(pages) > 0 {
|
||||||
|
//fmt.Printf("WITH GROW")
|
||||||
// пишу в storage довгим шляхом через redo файл і запис в data файл
|
// пишу в storage довгим шляхом через redo файл і запис в data файл
|
||||||
storageInbox.Push(storage.MeasuresAppendWithGrow{
|
storageInbox.Push(storage.MeasuresAppendWithGrow{
|
||||||
MetricID: req.MetricID,
|
MetricID: req.MetricID,
|
||||||
@@ -278,6 +307,7 @@ func (s *Metric) AppendMeasures(req AppendMeasuresReq, tmp []byte, storageInbox
|
|||||||
ResultCh: req.ResultCh,
|
ResultCh: req.ResultCh,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
//fmt.Printf("SIMPLE")
|
||||||
// короткий шлях - запис лише в storage
|
// короткий шлях - запис лише в storage
|
||||||
storageInbox.Push(storage.MeasuresAppend{
|
storageInbox.Push(storage.MeasuresAppend{
|
||||||
MetricID: req.MetricID,
|
MetricID: req.MetricID,
|
||||||
@@ -292,6 +322,12 @@ func (s *Metric) AppendMeasures(req AppendMeasuresReq, tmp []byte, storageInbox
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeleteMeasuresReq struct {
|
||||||
|
MetricID uint32
|
||||||
|
Since uint32
|
||||||
|
ResultCh chan byte
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Metric) DeleteMeasures(req DeleteMeasuresReq) {
|
func (s *Metric) DeleteMeasures(req DeleteMeasuresReq) {
|
||||||
// if s.xLock || s.capturedState != nil {
|
// if s.xLock || s.capturedState != nil {
|
||||||
// s.waitQueue = append(s.waitQueue, req)
|
// s.waitQueue = append(s.waitQueue, req)
|
||||||
@@ -314,7 +350,23 @@ func (s *Metric) DeleteMeasures(req DeleteMeasuresReq) {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Metric) StartRangeScan(req RangeScanReq) {
|
type RangeScanResult struct {
|
||||||
|
ResultCode byte
|
||||||
|
FracDigits byte
|
||||||
|
LastPageNo uint32
|
||||||
|
IsDataPage bool // for UntilNotFound only
|
||||||
|
}
|
||||||
|
|
||||||
|
type RangeScanReq struct {
|
||||||
|
MetricID uint32
|
||||||
|
Since uint32
|
||||||
|
Until uint32
|
||||||
|
MetricType qb.MetricType
|
||||||
|
ResponseWriter qb.WorkerMeasureConsumer
|
||||||
|
ResultCh chan RangeScanResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Metric) RangeScan(req RangeScanReq) {
|
||||||
if s.xLock {
|
if s.xLock {
|
||||||
s.waitQueue = append(s.waitQueue, req)
|
s.waitQueue = append(s.waitQueue, req)
|
||||||
return
|
return
|
||||||
@@ -397,7 +449,23 @@ func (s *Metric) StartRangeScan(req RangeScanReq) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Metric) StartFullScan(req FullScanReq) {
|
type FullScanResult struct {
|
||||||
|
ResultCode byte
|
||||||
|
FracDigits byte
|
||||||
|
LastPageNo uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type FullScanReq struct {
|
||||||
|
MetricID uint32
|
||||||
|
MetricType qb.MetricType
|
||||||
|
ResponseWriter qb.WorkerMeasureConsumer
|
||||||
|
ResultCh chan FullScanResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Metric) FullScan(req FullScanReq) {
|
||||||
|
//fmt.Println("lastPageNo:", s.lastPageNo)
|
||||||
|
//fmt.Printf("index: %#v\n", s.indexLevelTails)
|
||||||
|
|
||||||
if s.xLock {
|
if s.xLock {
|
||||||
s.waitQueue = append(s.waitQueue, req)
|
s.waitQueue = append(s.waitQueue, req)
|
||||||
return
|
return
|
||||||
@@ -410,18 +478,20 @@ func (s *Metric) StartFullScan(req FullScanReq) {
|
|||||||
}
|
}
|
||||||
timestampDecompressor := s.timestamps.CreateDecompressor()
|
timestampDecompressor := s.timestamps.CreateDecompressor()
|
||||||
valueDecompressor := s.values.CreateDecompressor(s.metricType, s.fracDigits)
|
valueDecompressor := s.values.CreateDecompressor(s.metricType, s.fracDigits)
|
||||||
|
idx := 0
|
||||||
for {
|
for {
|
||||||
timestamp, done := timestampDecompressor.NextValue()
|
timestamp, done := timestampDecompressor.NextValue()
|
||||||
if done {
|
if done {
|
||||||
|
//fmt.Println("DONE")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
//fmt.Println("ts:", timestamp)
|
|
||||||
value, done := valueDecompressor.NextValue()
|
value, done := valueDecompressor.NextValue()
|
||||||
if done {
|
if done {
|
||||||
qb.Abort(qb.HasTimestampNoValueBug, ErrNoValueBug)
|
qb.Abort(qb.HasTimestampNoValueBug, ErrNoValueBug)
|
||||||
}
|
}
|
||||||
//fmt.Println("value:", value)
|
|
||||||
req.ResponseWriter.FeedNoSend(timestamp, value)
|
req.ResponseWriter.FeedNoSend(timestamp, value)
|
||||||
|
//fmt.Printf(" %d: %s => %.2f\n", idx, formatTime(timestamp), value)
|
||||||
|
idx++
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.lastPageNo > 0 {
|
if s.lastPageNo > 0 {
|
||||||
@@ -441,6 +511,7 @@ func (s *Metric) StartFullScan(req FullScanReq) {
|
|||||||
// COMMITS
|
// COMMITS
|
||||||
|
|
||||||
func (s *Metric) OnMeasuresAppendCommited(rec storage.MeasuresAppendCommited) {
|
func (s *Metric) OnMeasuresAppendCommited(rec storage.MeasuresAppendCommited) {
|
||||||
|
//fmt.Println("OnMeasuresAppendCommited: written=", rec.WrittenCount)
|
||||||
// Видаляю state. Оригінальні Timestamps і Values вже мають останню версію
|
// Видаляю state. Оригінальні Timestamps і Values вже мають останню версію
|
||||||
s.capturedState = nil
|
s.capturedState = nil
|
||||||
s.timestamps.ForgetCapturedState()
|
s.timestamps.ForgetCapturedState()
|
||||||
@@ -453,6 +524,8 @@ func (s *Metric) OnMeasuresAppendCommited(rec storage.MeasuresAppendCommited) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Metric) OnMeasuresAppendWithGrowCommited(rec storage.MeasuresAppendWithGrowCommited) {
|
func (s *Metric) OnMeasuresAppendWithGrowCommited(rec storage.MeasuresAppendWithGrowCommited) {
|
||||||
|
//fmt.Println("OnMeasuresAppendWithGrowCommited: written=", rec.WrittenCount, ", lastPageNo=", rec.LastPageNo)
|
||||||
|
//fmt.Printf("index: %#v\n", rec.Index)
|
||||||
// Видаляю state. Оригінальні Timestamps і Values вже мають останню версію
|
// Видаляю state. Оригінальні Timestamps і Values вже мають останню версію
|
||||||
s.capturedState = nil
|
s.capturedState = nil
|
||||||
s.timestamps.ForgetCapturedState()
|
s.timestamps.ForgetCapturedState()
|
||||||
@@ -480,4 +553,16 @@ func (s *Metric) OnMeasuresDeleteCommited(rec storage.MeasuresDeleteCommited) {
|
|||||||
s.indexLevelTails = nil
|
s.indexLevelTails = nil
|
||||||
s.lastPageNo = 0
|
s.lastPageNo = 0
|
||||||
s.lastValue = 0
|
s.lastValue = 0
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// const (
|
||||||
|
// free
|
||||||
|
// )
|
||||||
|
|
||||||
|
const datetimeLayout = "2006-01-02 15:04:05"
|
||||||
|
|
||||||
|
func formatTime(timestamp uint32) string {
|
||||||
|
tm := time.Unix(int64(timestamp), 0)
|
||||||
|
return tm.Format(datetimeLayout)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ func ReadSnapshot(filePath string) (out ReadSnapshotOut, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("metricID", metricID)
|
//fmt.Println("metricID", metricID)
|
||||||
metricType, err = bin.ReadByte(src)
|
metricType, err = bin.ReadByte(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -187,13 +187,13 @@ func ReadSnapshot(filePath string) (out ReadSnapshotOut, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("m.MetricType", m.MetricType)
|
//fmt.Println("m.MetricType", m.MetricType)
|
||||||
fmt.Println("m.FracDigits", m.FracDigits)
|
//fmt.Println("m.FracDigits", m.FracDigits)
|
||||||
m.LastPageNo, err = bin.ReadUint32(src)
|
m.LastPageNo, err = bin.ReadUint32(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("m.LastPageNo", m.LastPageNo)
|
//fmt.Println("m.LastPageNo", m.LastPageNo)
|
||||||
m.TimestampsSize, err = bin.ReadUint16AsInt(src)
|
m.TimestampsSize, err = bin.ReadUint16AsInt(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -202,7 +202,7 @@ func ReadSnapshot(filePath string) (out ReadSnapshotOut, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("m.TimestampsSize", m.TimestampsSize, "m.ValuesSize", m.ValuesSize)
|
//fmt.Println("m.TimestampsSize", m.TimestampsSize, "m.ValuesSize", m.ValuesSize)
|
||||||
err = bin.ReadNInto(src, buf[storage.DataPagePayloadSize-m.TimestampsSize:storage.DataPagePayloadSize])
|
err = bin.ReadNInto(src, buf[storage.DataPagePayloadSize-m.TimestampsSize:storage.DataPagePayloadSize])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -211,13 +211,13 @@ func ReadSnapshot(filePath string) (out ReadSnapshotOut, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("% x\n", buf)
|
//fmt.Printf("% x\n", buf)
|
||||||
//
|
//
|
||||||
levelsCount, err = bin.ReadVarSize(src)
|
levelsCount, err = bin.ReadVarSize(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("levels count:", levelsCount)
|
//fmt.Println("levels count:", levelsCount)
|
||||||
for range levelsCount {
|
for range levelsCount {
|
||||||
var (
|
var (
|
||||||
buf = make([]byte, storage.IndexPageSize)
|
buf = make([]byte, storage.IndexPageSize)
|
||||||
@@ -227,12 +227,12 @@ func ReadSnapshot(filePath string) (out ReadSnapshotOut, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("records count:", count)
|
//fmt.Println("records count:", count)
|
||||||
err = bin.ReadNInto(src, buf[:count*storage.IndexRecordSize])
|
err = bin.ReadNInto(src, buf[:count*storage.IndexRecordSize])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("% x\n", buf)
|
//fmt.Printf("% x\n", buf)
|
||||||
m.IndexLevelTails = append(m.IndexLevelTails, storage.IndexLevelTail{
|
m.IndexLevelTails = append(m.IndexLevelTails, storage.IndexLevelTail{
|
||||||
Buffer: buf,
|
Buffer: buf,
|
||||||
RecordsCount: count,
|
RecordsCount: count,
|
||||||
@@ -278,7 +278,7 @@ func writeFreePages(dst io.Writer, frozenPagesCount int, pageNumbers []uint32) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("write frozenPagesCount:", frozenPagesCount)
|
//fmt.Println("write frozenPagesCount:", frozenPagesCount)
|
||||||
_, err = bin.WriteVarSize(dst, len(pageNumbers))
|
_, err = bin.WriteVarSize(dst, len(pageNumbers))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -301,7 +301,7 @@ func readFreePages(src io.Reader) (_ int, _ []uint32, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("read frozenPagesCount:", frozenPagesCount)
|
//fmt.Println("read frozenPagesCount:", frozenPagesCount)
|
||||||
pageNumbersCount, err := bin.ReadVarSize(src)
|
pageNumbersCount, err := bin.ReadVarSize(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
145
worker/worker.go
145
worker/worker.go
@@ -7,7 +7,6 @@ import (
|
|||||||
qb "gordenko.dev/dima/qb"
|
qb "gordenko.dev/dima/qb"
|
||||||
"gordenko.dev/dima/qb/enc"
|
"gordenko.dev/dima/qb/enc"
|
||||||
"gordenko.dev/dima/qb/inbox"
|
"gordenko.dev/dima/qb/inbox"
|
||||||
"gordenko.dev/dima/qb/proto"
|
|
||||||
"gordenko.dev/dima/qb/storage"
|
"gordenko.dev/dima/qb/storage"
|
||||||
"gordenko.dev/dima/qb/transform"
|
"gordenko.dev/dima/qb/transform"
|
||||||
)
|
)
|
||||||
@@ -81,6 +80,8 @@ func New(opt Options) *Worker {
|
|||||||
values: values,
|
values: values,
|
||||||
indexLevelTails: x.IndexLevelTails,
|
indexLevelTails: x.IndexLevelTails,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("index: %#v\n", x.IndexLevelTails)
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
@@ -109,25 +110,25 @@ func (s *Worker) doWork() {
|
|||||||
for _, untyped := range queue {
|
for _, untyped := range queue {
|
||||||
switch req := untyped.(type) {
|
switch req := untyped.(type) {
|
||||||
case AppendMeasuresReq:
|
case AppendMeasuresReq:
|
||||||
s.AppendMeasures(req)
|
s.appendMeasures(req)
|
||||||
case ReleaseRLock:
|
case ReleaseRLock:
|
||||||
s.releaseRLock(req.MetricID)
|
s.releaseRLock(req.MetricID)
|
||||||
case storage.Changes:
|
case storage.Changes:
|
||||||
s.applyCommits(req) // all metrics only
|
s.applyCommits(req) // all metrics only
|
||||||
case ListCurrentValuesReq:
|
case ListCurrentValuesReq:
|
||||||
s.ListCurrentValues(req) // all metrics only
|
s.listCurrentValues(req) // all metrics only
|
||||||
case RangeScanReq:
|
case RangeScanReq:
|
||||||
s.RangeScan(req)
|
s.rangeScan(req)
|
||||||
case FullScanReq:
|
case FullScanReq:
|
||||||
s.FullScan(req)
|
s.fullScan(req)
|
||||||
case AddMetricReq:
|
case AddMetricReq:
|
||||||
s.AddMetric(req)
|
s.addMetric(req)
|
||||||
case DeleteMetricReq:
|
case DeleteMetricReq:
|
||||||
s.DeleteMetric(req)
|
s.deleteMetric(req)
|
||||||
case DeleteMeasuresReq:
|
case DeleteMeasuresReq:
|
||||||
s.DeleteMeasures(req)
|
s.deleteMeasures(req)
|
||||||
case GetMetricReq:
|
case GetMetricReq:
|
||||||
s.GetMetric(req)
|
s.getMetric(req)
|
||||||
default:
|
default:
|
||||||
qb.Abort(qb.UnknownWorkerQueueItemBug,
|
qb.Abort(qb.UnknownWorkerQueueItemBug,
|
||||||
fmt.Errorf("bug: unknown worker queue item type %T", req))
|
fmt.Errorf("bug: unknown worker queue item type %T", req))
|
||||||
@@ -149,7 +150,7 @@ type AddMetricReq struct {
|
|||||||
ResultCh chan byte
|
ResultCh chan byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Worker) AddMetric(req AddMetricReq) {
|
func (s *Worker) addMetric(req AddMetricReq) {
|
||||||
_, ok := s.metrics[req.MetricID]
|
_, ok := s.metrics[req.MetricID]
|
||||||
if ok {
|
if ok {
|
||||||
req.ResultCh <- MetricDuplicate
|
req.ResultCh <- MetricDuplicate
|
||||||
@@ -175,30 +176,14 @@ func (s *Worker) AddMetric(req AddMetricReq) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetMetricResult struct {
|
func (s *Worker) getMetric(req GetMetricReq) {
|
||||||
MetricType qb.MetricType
|
|
||||||
FracDigits byte
|
|
||||||
ResultCode byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetMetricReq struct {
|
|
||||||
MetricID uint32
|
|
||||||
ResultCh chan GetMetricResult
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Worker) GetMetric(req GetMetricReq) {
|
|
||||||
metric, ok := s.metrics[req.MetricID]
|
metric, ok := s.metrics[req.MetricID]
|
||||||
if ok {
|
if !ok {
|
||||||
req.ResultCh <- GetMetricResult{
|
|
||||||
ResultCode: Succeed,
|
|
||||||
MetricType: metric.MetricType(),
|
|
||||||
FracDigits: metric.FracDigits(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
req.ResultCh <- GetMetricResult{
|
req.ResultCh <- GetMetricResult{
|
||||||
ResultCode: NoMetric,
|
ResultCode: NoMetric,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
metric.GetMetric(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeleteMetricReq struct {
|
type DeleteMetricReq struct {
|
||||||
@@ -206,32 +191,16 @@ type DeleteMetricReq struct {
|
|||||||
ResultCh chan byte
|
ResultCh chan byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Worker) DeleteMetric(req DeleteMetricReq) {
|
func (s *Worker) deleteMetric(req DeleteMetricReq) {
|
||||||
metric, ok := s.metrics[req.MetricID]
|
metric, ok := s.metrics[req.MetricID]
|
||||||
if !ok {
|
if !ok {
|
||||||
req.ResultCh <- NoMetric
|
req.ResultCh <- NoMetric
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if metric.xLock {
|
metric.DeleteMetric(req)
|
||||||
metric.waitQueue = append(metric.waitQueue, req)
|
|
||||||
} else {
|
|
||||||
// collect all pages, than
|
|
||||||
s.storageInbox.Push(storage.MetricDelete{
|
|
||||||
MetricID: req.MetricID,
|
|
||||||
FreeIndexPages: nil,
|
|
||||||
FreeDataPages: nil,
|
|
||||||
ResultCh: req.ResultCh,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeleteMeasuresReq struct {
|
func (s *Worker) deleteMeasures(req DeleteMeasuresReq) {
|
||||||
MetricID uint32
|
|
||||||
Since uint32
|
|
||||||
ResultCh chan byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Worker) DeleteMeasures(req DeleteMeasuresReq) {
|
|
||||||
metric, ok := s.metrics[req.MetricID]
|
metric, ok := s.metrics[req.MetricID]
|
||||||
if !ok {
|
if !ok {
|
||||||
req.ResultCh <- NoMetric
|
req.ResultCh <- NoMetric
|
||||||
@@ -240,13 +209,7 @@ func (s *Worker) DeleteMeasures(req DeleteMeasuresReq) {
|
|||||||
metric.DeleteMeasures(req)
|
metric.DeleteMeasures(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppendMeasuresReq struct {
|
func (s *Worker) appendMeasures(req AppendMeasuresReq) {
|
||||||
MetricID uint32
|
|
||||||
Measures []proto.Measure
|
|
||||||
ResultCh chan storage.MeasuresAppendResult
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Worker) AppendMeasures(req AppendMeasuresReq) {
|
|
||||||
metric, ok := s.metrics[req.MetricID]
|
metric, ok := s.metrics[req.MetricID]
|
||||||
if !ok {
|
if !ok {
|
||||||
req.ResultCh <- storage.MeasuresAppendResult{
|
req.ResultCh <- storage.MeasuresAppendResult{
|
||||||
@@ -261,23 +224,7 @@ func (s *Worker) AppendMeasures(req AppendMeasuresReq) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RangeScanResult struct {
|
func (s *Worker) rangeScan(req RangeScanReq) {
|
||||||
ResultCode byte
|
|
||||||
FracDigits byte
|
|
||||||
LastPageNo uint32
|
|
||||||
IsDataPage bool // for UntilNotFound only
|
|
||||||
}
|
|
||||||
|
|
||||||
type RangeScanReq struct {
|
|
||||||
MetricID uint32
|
|
||||||
Since uint32
|
|
||||||
Until uint32
|
|
||||||
MetricType qb.MetricType
|
|
||||||
ResponseWriter qb.WorkerMeasureConsumer
|
|
||||||
ResultCh chan RangeScanResult
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Worker) RangeScan(req RangeScanReq) {
|
|
||||||
metric, ok := s.metrics[req.MetricID]
|
metric, ok := s.metrics[req.MetricID]
|
||||||
if !ok {
|
if !ok {
|
||||||
req.ResultCh <- RangeScanResult{
|
req.ResultCh <- RangeScanResult{
|
||||||
@@ -294,24 +241,11 @@ func (s *Worker) RangeScan(req RangeScanReq) {
|
|||||||
if metric.xLock {
|
if metric.xLock {
|
||||||
metric.waitQueue = append(metric.waitQueue, req)
|
metric.waitQueue = append(metric.waitQueue, req)
|
||||||
} else {
|
} else {
|
||||||
metric.StartRangeScan(req)
|
metric.RangeScan(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type FullScanResult struct {
|
func (s *Worker) fullScan(req FullScanReq) {
|
||||||
ResultCode byte
|
|
||||||
FracDigits byte
|
|
||||||
LastPageNo uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type FullScanReq struct {
|
|
||||||
MetricID uint32
|
|
||||||
MetricType qb.MetricType
|
|
||||||
ResponseWriter qb.WorkerMeasureConsumer
|
|
||||||
ResultCh chan FullScanResult
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Worker) FullScan(req FullScanReq) {
|
|
||||||
metric, ok := s.metrics[req.MetricID]
|
metric, ok := s.metrics[req.MetricID]
|
||||||
if !ok {
|
if !ok {
|
||||||
req.ResultCh <- FullScanResult{
|
req.ResultCh <- FullScanResult{
|
||||||
@@ -328,7 +262,7 @@ func (s *Worker) FullScan(req FullScanReq) {
|
|||||||
if metric.xLock {
|
if metric.xLock {
|
||||||
metric.waitQueue = append(metric.waitQueue, req)
|
metric.waitQueue = append(metric.waitQueue, req)
|
||||||
} else {
|
} else {
|
||||||
metric.StartFullScan(req)
|
metric.FullScan(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,7 +272,7 @@ type ListCurrentValuesReq struct {
|
|||||||
ResultCh chan struct{}
|
ResultCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Worker) ListCurrentValues(req ListCurrentValuesReq) {
|
func (s *Worker) listCurrentValues(req ListCurrentValuesReq) {
|
||||||
for _, metricID := range req.MetricIDs {
|
for _, metricID := range req.MetricIDs {
|
||||||
metric, ok := s.metrics[metricID]
|
metric, ok := s.metrics[metricID]
|
||||||
if ok {
|
if ok {
|
||||||
@@ -478,8 +412,31 @@ func (s *Worker) onMeasuresDeleteCommited(rec storage.MeasuresDeleteCommited) {
|
|||||||
//s.doAfterReleaseXLock(rec.MetricID, metric)
|
//s.doAfterReleaseXLock(rec.MetricID, metric)
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (s *Database) doAfterReleaseXLock(metricID uint32, metric *_metric) {
|
// rLock сумісний із append measures,
|
||||||
// if len(metric.WaitQueue) > 0 {
|
// xLock ні з чим
|
||||||
// s.processMetricQueue(metricID, metric)
|
// після xLock - вся черга
|
||||||
// }
|
// після rLock, якщо capturedState == nil - вся черга, оскільки rLock блокує лише xLock задачі
|
||||||
// }
|
// після capturedState = nil, якщо перша задача - appendMeasures - беру, інакше перевірка rLock
|
||||||
|
|
||||||
|
// суть у тому що треба запускати запити, пока не зустріну XLock
|
||||||
|
func (s *Worker) ProcessQueue(metric *Metric, tmp []byte, storageInbox *inbox.Inbox) {
|
||||||
|
// for _, untyped := range metric.waitQueue {
|
||||||
|
// switch req := untyped.(type) {
|
||||||
|
// case RangeScanReq:
|
||||||
|
// metric.RangeScan(req)
|
||||||
|
// case FullScanReq:
|
||||||
|
// metric.FullScan(req)
|
||||||
|
// case GetMetricReq:
|
||||||
|
// metric.GetMetric(req)
|
||||||
|
// case AppendMeasuresReq:
|
||||||
|
// metric.AppendMeasures(req, tmp, s.storageInbox)
|
||||||
|
// case DeleteMetricReq:
|
||||||
|
// metric.DeleteMetric(req)
|
||||||
|
// case DeleteMeasuresReq:
|
||||||
|
// metric.DeleteMeasures(req)
|
||||||
|
// default:
|
||||||
|
// qb.Abort(qb.UnknownMetricWaitQueueItemBug,
|
||||||
|
// fmt.Errorf("bug: unknown metric wait queue item type %T", req))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user