commit
c97003bc4f
@ -1,261 +0,0 @@ |
||||
package atree |
||||
|
||||
import ( |
||||
"fmt" |
||||
"time" |
||||
|
||||
"gordenko.dev/dima/diploma" |
||||
"gordenko.dev/dima/diploma/timeutil" |
||||
) |
||||
|
||||
// AGGREGATE
|
||||
|
||||
type InstantAggregator struct { |
||||
firstHourOfDay int |
||||
time2period func(uint32) uint32 |
||||
currentPeriod uint32 |
||||
since uint32 |
||||
until uint32 |
||||
min float64 |
||||
max float64 |
||||
total float64 |
||||
entries int |
||||
} |
||||
|
||||
type InstantAggregatorOptions struct { |
||||
GroupBy diploma.GroupBy |
||||
FirstHourOfDay int |
||||
} |
||||
|
||||
func NewInstantAggregator(opt InstantAggregatorOptions) (*InstantAggregator, error) { |
||||
s := &InstantAggregator{ |
||||
firstHourOfDay: opt.FirstHourOfDay, |
||||
} |
||||
|
||||
switch opt.GroupBy { |
||||
case diploma.GroupByHour: |
||||
s.time2period = groupByHour |
||||
|
||||
case diploma.GroupByDay: |
||||
if s.firstHourOfDay > 0 { |
||||
s.time2period = s.groupByDayUsingFHD |
||||
} else { |
||||
s.time2period = groupByDay |
||||
} |
||||
|
||||
case diploma.GroupByMonth: |
||||
if s.firstHourOfDay > 0 { |
||||
s.time2period = s.groupByMonthUsingFHD |
||||
} else { |
||||
s.time2period = groupByMonth |
||||
} |
||||
|
||||
default: |
||||
return nil, fmt.Errorf("unknown groupBy %d option", opt.GroupBy) |
||||
} |
||||
|
||||
return s, nil |
||||
} |
||||
|
||||
// Приходят данные от свежих к старым, тоесть сперва получаю Until.
|
||||
// return period complete flag
|
||||
func (s *InstantAggregator) Feed(timestamp uint32, value float64, p *InstantPeriod) bool { |
||||
period := s.time2period(timestamp) |
||||
//fmt.Printf("feed: %s %v, period: %s\n", time.Unix(int64(timestamp), 0), value, time.Unix(int64(period), 0))
|
||||
if s.entries == 0 { |
||||
s.currentPeriod = period |
||||
s.since = timestamp |
||||
s.until = timestamp |
||||
s.min = value |
||||
s.max = value |
||||
s.total = value |
||||
s.entries = 1 |
||||
return false |
||||
} |
||||
|
||||
if period != s.currentPeriod { |
||||
// готовый период
|
||||
s.FillPeriod(timestamp, p) |
||||
s.currentPeriod = period |
||||
s.since = timestamp |
||||
s.until = timestamp |
||||
s.min = value |
||||
s.max = value |
||||
s.total = value |
||||
s.entries = 1 |
||||
return true |
||||
} |
||||
|
||||
if value < s.min { |
||||
s.min = value |
||||
} else if value > s.max { |
||||
s.max = value |
||||
} |
||||
// для подсчета AVG
|
||||
s.total += value |
||||
s.entries++ |
||||
// начало периода
|
||||
s.since = timestamp |
||||
return false |
||||
} |
||||
|
||||
func (s *InstantAggregator) FillPeriod(prevTimestamp uint32, p *InstantPeriod) bool { |
||||
if s.entries == 0 { |
||||
return false |
||||
} |
||||
|
||||
//fmt.Printf("FillPeriod: %s, prevTimestamp: %s\n", time.Unix(int64(s.currentPeriod), 0), time.Unix(int64(prevTimestamp), 0))
|
||||
p.Period = s.currentPeriod |
||||
if prevTimestamp > 0 { |
||||
p.Since = prevTimestamp |
||||
} else { |
||||
p.Since = s.since |
||||
} |
||||
p.Until = s.until |
||||
p.Min = s.min |
||||
p.Max = s.max |
||||
p.Avg = s.total / float64(s.entries) |
||||
return true |
||||
} |
||||
|
||||
func (s *InstantAggregator) groupByDayUsingFHD(timestamp uint32) uint32 { |
||||
tm := timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "d") |
||||
if tm.Hour() < s.firstHourOfDay { |
||||
tm = tm.AddDate(0, 0, -1) |
||||
} |
||||
return uint32(tm.Unix()) |
||||
} |
||||
|
||||
func (s *InstantAggregator) groupByMonthUsingFHD(timestamp uint32) uint32 { |
||||
tm := timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "m") |
||||
if tm.Hour() < s.firstHourOfDay { |
||||
tm = tm.AddDate(0, 0, -1) |
||||
} |
||||
return uint32(tm.Unix()) |
||||
} |
||||
|
||||
// CUMULATIVE
|
||||
|
||||
type CumulativeAggregator struct { |
||||
firstHourOfDay int |
||||
time2period func(uint32) uint32 |
||||
currentPeriod uint32 |
||||
since uint32 |
||||
until uint32 |
||||
sinceValue float64 |
||||
untilValue float64 |
||||
entries int |
||||
} |
||||
|
||||
type CumulativeAggregatorOptions struct { |
||||
GroupBy diploma.GroupBy |
||||
FirstHourOfDay int |
||||
} |
||||
|
||||
func NewCumulativeAggregator(opt CumulativeAggregatorOptions) (*CumulativeAggregator, error) { |
||||
s := &CumulativeAggregator{ |
||||
firstHourOfDay: opt.FirstHourOfDay, |
||||
} |
||||
|
||||
switch opt.GroupBy { |
||||
case diploma.GroupByHour: |
||||
s.time2period = groupByHour |
||||
|
||||
case diploma.GroupByDay: |
||||
if s.firstHourOfDay > 0 { |
||||
s.time2period = s.groupByDayUsingFHD |
||||
} else { |
||||
s.time2period = groupByDay |
||||
} |
||||
|
||||
case diploma.GroupByMonth: |
||||
if s.firstHourOfDay > 0 { |
||||
s.time2period = s.groupByMonthUsingFHD |
||||
} else { |
||||
s.time2period = groupByMonth |
||||
} |
||||
|
||||
default: |
||||
return nil, fmt.Errorf("unknown groupBy %d option", opt.GroupBy) |
||||
} |
||||
|
||||
return s, nil |
||||
} |
||||
|
||||
// return period complete flag
|
||||
func (s *CumulativeAggregator) Feed(timestamp uint32, value float64, p *CumulativePeriod) bool { |
||||
period := s.time2period(timestamp) |
||||
if s.entries == 0 { |
||||
s.currentPeriod = period |
||||
s.since = timestamp |
||||
s.until = timestamp |
||||
s.sinceValue = value |
||||
s.untilValue = value |
||||
s.entries = 1 |
||||
return false |
||||
} |
||||
|
||||
if period != s.currentPeriod { |
||||
// готовый период
|
||||
s.FillPeriod(timestamp, value, p) |
||||
s.currentPeriod = period |
||||
s.since = timestamp |
||||
s.until = timestamp |
||||
s.sinceValue = value |
||||
s.untilValue = value |
||||
s.entries = 1 |
||||
return true |
||||
} |
||||
|
||||
// начало периода
|
||||
s.since = timestamp |
||||
s.sinceValue = value |
||||
s.entries++ |
||||
return false |
||||
} |
||||
|
||||
func (s *CumulativeAggregator) FillPeriod(prevTimestamp uint32, value float64, p *CumulativePeriod) bool { |
||||
if s.entries == 0 { |
||||
return false |
||||
} |
||||
p.Period = s.currentPeriod |
||||
if prevTimestamp > 0 { |
||||
p.Since = prevTimestamp |
||||
p.Total = s.untilValue - value |
||||
} else { |
||||
p.Since = s.since |
||||
p.Total = s.untilValue - s.sinceValue |
||||
} |
||||
p.Until = s.until |
||||
p.EndValue = s.untilValue |
||||
|
||||
return true |
||||
} |
||||
|
||||
func (s *CumulativeAggregator) groupByDayUsingFHD(timestamp uint32) uint32 { |
||||
tm := timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "d") |
||||
if tm.Hour() < s.firstHourOfDay { |
||||
tm = tm.AddDate(0, 0, -1) |
||||
} |
||||
return uint32(tm.Unix()) |
||||
} |
||||
|
||||
func (s *CumulativeAggregator) groupByMonthUsingFHD(timestamp uint32) uint32 { |
||||
tm := timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "m") |
||||
if tm.Hour() < s.firstHourOfDay { |
||||
tm = tm.AddDate(0, 0, -1) |
||||
} |
||||
return uint32(tm.Unix()) |
||||
} |
||||
|
||||
func groupByHour(timestamp uint32) uint32 { |
||||
return uint32(timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "h").Unix()) |
||||
} |
||||
|
||||
func groupByDay(timestamp uint32) uint32 { |
||||
return uint32(timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "d").Unix()) |
||||
} |
||||
|
||||
func groupByMonth(timestamp uint32) uint32 { |
||||
return uint32(timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "m").Unix()) |
||||
} |
@ -1,306 +0,0 @@ |
||||
package atree |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"io" |
||||
|
||||
octopus "gordenko.dev/dima/diploma" |
||||
"gordenko.dev/dima/diploma/bin" |
||||
"gordenko.dev/dima/diploma/proto" |
||||
) |
||||
|
||||
// CURRENT VALUE WRITER
|
||||
|
||||
type CurrentValue struct { |
||||
MetricID uint32 |
||||
Timestamp uint32 |
||||
Value float64 |
||||
} |
||||
|
||||
type CurrentValueWriter struct { |
||||
arr []byte |
||||
responder *ChunkedResponder |
||||
} |
||||
|
||||
func NewCurrentValueWriter(dst io.Writer) *CurrentValueWriter { |
||||
return &CurrentValueWriter{ |
||||
arr: make([]byte, 16), |
||||
responder: NewChunkedResponder(dst), |
||||
} |
||||
} |
||||
|
||||
func (s *CurrentValueWriter) BufferValue(m CurrentValue) { |
||||
bin.PutUint32(s.arr[0:], m.MetricID) |
||||
bin.PutUint32(s.arr[4:], m.Timestamp) |
||||
bin.PutFloat64(s.arr[8:], m.Value) |
||||
s.responder.BufferRecord(s.arr) |
||||
} |
||||
|
||||
func (s *CurrentValueWriter) Close() error { |
||||
return s.responder.Flush() |
||||
} |
||||
|
||||
// INSTANT MEASURE WRITER
|
||||
|
||||
type InstantMeasure struct { |
||||
Timestamp uint32 |
||||
Value float64 |
||||
} |
||||
|
||||
type InstantMeasureWriter struct { |
||||
arr []byte |
||||
responder *ChunkedResponder |
||||
} |
||||
|
||||
func NewInstantMeasureWriter(dst io.Writer) *InstantMeasureWriter { |
||||
return &InstantMeasureWriter{ |
||||
arr: make([]byte, 12), |
||||
responder: NewChunkedResponder(dst), |
||||
} |
||||
} |
||||
|
||||
func (s *InstantMeasureWriter) BufferMeasure(m InstantMeasure) { |
||||
bin.PutUint32(s.arr[0:], m.Timestamp) |
||||
bin.PutFloat64(s.arr[4:], m.Value) |
||||
s.responder.BufferRecord(s.arr) |
||||
} |
||||
|
||||
func (s *InstantMeasureWriter) WriteMeasure(m InstantMeasure) error { |
||||
bin.PutUint32(s.arr[0:], m.Timestamp) |
||||
bin.PutFloat64(s.arr[4:], m.Value) |
||||
return s.responder.AppendRecord(s.arr) |
||||
} |
||||
|
||||
func (s *InstantMeasureWriter) Close() error { |
||||
return s.responder.Flush() |
||||
} |
||||
|
||||
// CUMULATIVE MEASURE WRITER
|
||||
|
||||
type CumulativeMeasure struct { |
||||
Timestamp uint32 |
||||
Value float64 |
||||
Total float64 |
||||
} |
||||
|
||||
type CumulativeMeasureWriter struct { |
||||
arr []byte |
||||
responder *ChunkedResponder |
||||
} |
||||
|
||||
func NewCumulativeMeasureWriter(dst io.Writer) *CumulativeMeasureWriter { |
||||
return &CumulativeMeasureWriter{ |
||||
arr: make([]byte, 20), |
||||
responder: NewChunkedResponder(dst), |
||||
} |
||||
} |
||||
|
||||
func (s *CumulativeMeasureWriter) BufferMeasure(m CumulativeMeasure) { |
||||
bin.PutUint32(s.arr[0:], m.Timestamp) |
||||
bin.PutFloat64(s.arr[4:], m.Value) |
||||
bin.PutFloat64(s.arr[12:], m.Total) |
||||
s.responder.BufferRecord(s.arr) |
||||
} |
||||
|
||||
func (s *CumulativeMeasureWriter) WriteMeasure(m CumulativeMeasure) error { |
||||
bin.PutUint32(s.arr[0:], m.Timestamp) |
||||
bin.PutFloat64(s.arr[4:], m.Value) |
||||
bin.PutFloat64(s.arr[12:], m.Total) |
||||
return s.responder.AppendRecord(s.arr) |
||||
} |
||||
|
||||
func (s *CumulativeMeasureWriter) Close() error { |
||||
return s.responder.Flush() |
||||
} |
||||
|
||||
// INSTANT AGGREGATE WRITER
|
||||
|
||||
type InstantPeriodsWriter struct { |
||||
aggregateFuncs byte |
||||
arr []byte |
||||
responder *ChunkedResponder |
||||
} |
||||
|
||||
func NewInstantPeriodsWriter(dst io.Writer, aggregateFuncs byte) *InstantPeriodsWriter { |
||||
var q int |
||||
if (aggregateFuncs & octopus.AggregateMin) == octopus.AggregateMin { |
||||
q++ |
||||
} |
||||
if (aggregateFuncs & octopus.AggregateMax) == octopus.AggregateMax { |
||||
q++ |
||||
} |
||||
if (aggregateFuncs & octopus.AggregateAvg) == octopus.AggregateAvg { |
||||
q++ |
||||
} |
||||
return &InstantPeriodsWriter{ |
||||
aggregateFuncs: aggregateFuncs, |
||||
arr: make([]byte, 12+q*8), |
||||
responder: NewChunkedResponder(dst), |
||||
} |
||||
} |
||||
|
||||
type InstantPeriod struct { |
||||
Period uint32 |
||||
Since uint32 |
||||
Until uint32 |
||||
Min float64 |
||||
Max float64 |
||||
Avg float64 |
||||
} |
||||
|
||||
func (s *InstantPeriodsWriter) BufferMeasure(p InstantPeriod) { |
||||
s.pack(p) |
||||
s.responder.BufferRecord(s.arr) |
||||
} |
||||
|
||||
func (s *InstantPeriodsWriter) WritePeriod(p InstantPeriod) error { |
||||
s.pack(p) |
||||
return s.responder.AppendRecord(s.arr) |
||||
} |
||||
|
||||
func (s *InstantPeriodsWriter) Close() error { |
||||
return s.responder.Flush() |
||||
} |
||||
|
||||
func (s *InstantPeriodsWriter) pack(p InstantPeriod) { |
||||
bin.PutUint32(s.arr[0:], p.Period) |
||||
bin.PutUint32(s.arr[4:], p.Since) |
||||
bin.PutUint32(s.arr[8:], p.Until) |
||||
|
||||
pos := 12 |
||||
if (s.aggregateFuncs & octopus.AggregateMin) == octopus.AggregateMin { |
||||
bin.PutFloat64(s.arr[pos:], p.Min) |
||||
pos += 8 |
||||
} |
||||
if (s.aggregateFuncs & octopus.AggregateMax) == octopus.AggregateMax { |
||||
bin.PutFloat64(s.arr[pos:], p.Max) |
||||
pos += 8 |
||||
} |
||||
if (s.aggregateFuncs & octopus.AggregateAvg) == octopus.AggregateAvg { |
||||
bin.PutFloat64(s.arr[pos:], p.Avg) |
||||
} |
||||
} |
||||
|
||||
// CUMULATIVE AGGREGATE WRITER
|
||||
|
||||
type CumulativePeriodsWriter struct { |
||||
arr []byte |
||||
responder *ChunkedResponder |
||||
} |
||||
|
||||
func NewCumulativePeriodsWriter(dst io.Writer) *CumulativePeriodsWriter { |
||||
return &CumulativePeriodsWriter{ |
||||
arr: make([]byte, 28), |
||||
responder: NewChunkedResponder(dst), |
||||
} |
||||
} |
||||
|
||||
type CumulativePeriod struct { |
||||
Period uint32 |
||||
Since uint32 |
||||
Until uint32 |
||||
EndValue float64 |
||||
Total float64 |
||||
} |
||||
|
||||
func (s *CumulativePeriodsWriter) BufferMeasure(p CumulativePeriod) { |
||||
s.pack(p) |
||||
s.responder.BufferRecord(s.arr) |
||||
} |
||||
|
||||
func (s *CumulativePeriodsWriter) WritePeriod(p CumulativePeriod) error { |
||||
s.pack(p) |
||||
return s.responder.AppendRecord(s.arr) |
||||
} |
||||
|
||||
func (s *CumulativePeriodsWriter) Close() error { |
||||
return s.responder.Flush() |
||||
} |
||||
|
||||
func (s *CumulativePeriodsWriter) pack(p CumulativePeriod) { |
||||
bin.PutUint32(s.arr[0:], p.Period) |
||||
bin.PutUint32(s.arr[4:], p.Since) |
||||
bin.PutUint32(s.arr[8:], p.Until) |
||||
bin.PutFloat64(s.arr[12:], p.EndValue) |
||||
bin.PutFloat64(s.arr[20:], p.Total) |
||||
} |
||||
|
||||
// CHUNKED RESPONDER
|
||||
|
||||
//const headerSize = 3
|
||||
|
||||
var endMsg = []byte{ |
||||
proto.RespEndOfValue, // end of stream
|
||||
} |
||||
|
||||
type ChunkedResponder struct { |
||||
recordsQty int |
||||
buf *bytes.Buffer |
||||
dst io.Writer |
||||
} |
||||
|
||||
func NewChunkedResponder(dst io.Writer) *ChunkedResponder { |
||||
s := &ChunkedResponder{ |
||||
recordsQty: 0, |
||||
buf: bytes.NewBuffer(nil), |
||||
dst: dst, |
||||
} |
||||
|
||||
s.buf.Write([]byte{ |
||||
proto.RespPartOfValue, // message type
|
||||
0, 0, 0, 0, // records qty
|
||||
}) |
||||
return s |
||||
} |
||||
|
||||
func (s *ChunkedResponder) BufferRecord(rec []byte) { |
||||
s.buf.Write(rec) |
||||
s.recordsQty++ |
||||
} |
||||
|
||||
func (s *ChunkedResponder) AppendRecord(rec []byte) error { |
||||
s.buf.Write(rec) |
||||
s.recordsQty++ |
||||
|
||||
if s.buf.Len() < 1500 { |
||||
return nil |
||||
} |
||||
|
||||
if err := s.sendBuffered(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
s.buf.Write([]byte{ |
||||
proto.RespPartOfValue, // message type
|
||||
0, 0, 0, 0, // records qty
|
||||
}) |
||||
s.recordsQty = 0 |
||||
return nil |
||||
} |
||||
|
||||
func (s *ChunkedResponder) Flush() error { |
||||
if s.recordsQty > 0 { |
||||
if err := s.sendBuffered(); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
if _, err := s.dst.Write(endMsg); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (s *ChunkedResponder) sendBuffered() (err error) { |
||||
msg := s.buf.Bytes() |
||||
bin.PutUint32(msg[1:], uint32(s.recordsQty)) |
||||
n, err := s.dst.Write(msg) |
||||
if err != nil { |
||||
return |
||||
} |
||||
if n != len(msg) { |
||||
return fmt.Errorf("incomplete write %d bytes instead of %d", n, len(msg)) |
||||
} |
||||
s.buf.Reset() |
||||
return |
||||
} |
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,372 @@ |
||||
package transform |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"time" |
||||
|
||||
"gordenko.dev/dima/diploma" |
||||
"gordenko.dev/dima/diploma/bin" |
||||
"gordenko.dev/dima/diploma/timeutil" |
||||
) |
||||
|
||||
// INSTANT
|
||||
|
||||
type InstantPeriodsWriterOptions struct { |
||||
Dst io.Writer |
||||
GroupBy diploma.GroupBy |
||||
Since uint32 |
||||
AggregateFuncs byte |
||||
FirstHourOfDay int |
||||
} |
||||
|
||||
type InstantPeriodsWriter struct { |
||||
aggregateFuncs byte |
||||
arr []byte |
||||
responder *ChunkedResponder |
||||
groupBy diploma.GroupBy |
||||
since uint32 |
||||
firstHourOfDay int |
||||
time2period func(uint32) time.Time |
||||
currentPeriod time.Time |
||||
lastTimestamp uint32 |
||||
endTimestamp uint32 |
||||
min float64 |
||||
max float64 |
||||
total float64 |
||||
entries int |
||||
} |
||||
|
||||
func NewInstantPeriodsWriter(opt InstantPeriodsWriterOptions) (*InstantPeriodsWriter, error) { |
||||
if opt.Dst == nil { |
||||
return nil, errors.New("Dst option is required") |
||||
} |
||||
if opt.FirstHourOfDay < 0 || opt.FirstHourOfDay > 23 { |
||||
return nil, fmt.Errorf("wrong FirstHourOfDay option: %d", opt.FirstHourOfDay) |
||||
} |
||||
var q int |
||||
if (opt.AggregateFuncs & diploma.AggregateMin) == diploma.AggregateMin { |
||||
q++ |
||||
} |
||||
if (opt.AggregateFuncs & diploma.AggregateMax) == diploma.AggregateMax { |
||||
q++ |
||||
} |
||||
if (opt.AggregateFuncs & diploma.AggregateAvg) == diploma.AggregateAvg { |
||||
q++ |
||||
} |
||||
|
||||
if q == 0 { |
||||
return nil, errors.New("AggregateFuncs option is required") |
||||
} |
||||
|
||||
s := &InstantPeriodsWriter{ |
||||
aggregateFuncs: opt.AggregateFuncs, |
||||
arr: make([]byte, 12+q*8), |
||||
responder: NewChunkedResponder(opt.Dst), |
||||
groupBy: opt.GroupBy, |
||||
since: opt.Since, |
||||
firstHourOfDay: opt.FirstHourOfDay, |
||||
} |
||||
|
||||
switch opt.GroupBy { |
||||
case diploma.GroupByHour: |
||||
s.time2period = groupByHour |
||||
|
||||
case diploma.GroupByDay: |
||||
if s.firstHourOfDay > 0 { |
||||
s.time2period = s.groupByDayUsingFHD |
||||
} else { |
||||
s.time2period = groupByDay |
||||
} |
||||
|
||||
case diploma.GroupByMonth: |
||||
if s.firstHourOfDay > 0 { |
||||
s.time2period = s.groupByMonthUsingFHD |
||||
} else { |
||||
s.time2period = groupByMonth |
||||
} |
||||
|
||||
default: |
||||
return nil, fmt.Errorf("unknown groupBy %d option", opt.GroupBy) |
||||
} |
||||
return s, nil |
||||
} |
||||
|
||||
func (s *InstantPeriodsWriter) groupByDayUsingFHD(timestamp uint32) time.Time { |
||||
tm := timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "d") |
||||
if tm.Hour() < s.firstHourOfDay { |
||||
tm = tm.AddDate(0, 0, -1) |
||||
} |
||||
return tm |
||||
} |
||||
|
||||
func (s *InstantPeriodsWriter) groupByMonthUsingFHD(timestamp uint32) time.Time { |
||||
tm := timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "m") |
||||
if tm.Hour() < s.firstHourOfDay { |
||||
tm = tm.AddDate(0, 0, -1) |
||||
} |
||||
return tm |
||||
} |
||||
|
||||
func (s *InstantPeriodsWriter) Feed(timestamp uint32, value float64) { |
||||
s.feed(timestamp, value, false) |
||||
} |
||||
|
||||
func (s *InstantPeriodsWriter) FeedNoSend(timestamp uint32, value float64) { |
||||
s.feed(timestamp, value, true) |
||||
} |
||||
|
||||
func (s *InstantPeriodsWriter) feed(timestamp uint32, value float64, isBuffer bool) { |
||||
if s.entries > 0 { |
||||
period := s.time2period(timestamp) |
||||
if period != s.currentPeriod { |
||||
s.packPeriod(timestamp) |
||||
if isBuffer { |
||||
s.responder.BufferRecord(s.arr) |
||||
} else { |
||||
s.responder.AppendRecord(s.arr) |
||||
} |
||||
s.decrementPeriod() |
||||
for period.Before(s.currentPeriod) { |
||||
s.packBlankPeriod() |
||||
if isBuffer { |
||||
s.responder.BufferRecord(s.arr) |
||||
} else { |
||||
s.responder.AppendRecord(s.arr) |
||||
} |
||||
s.decrementPeriod() |
||||
} |
||||
s.endTimestamp = timestamp |
||||
s.min = value |
||||
s.max = value |
||||
s.total = value |
||||
s.entries = 1 |
||||
} else { |
||||
if value < s.min { |
||||
s.min = value |
||||
} else if value > s.max { |
||||
s.max = value |
||||
} |
||||
s.total += value |
||||
s.entries++ |
||||
} |
||||
} else { |
||||
s.endTimestamp = timestamp |
||||
s.min = value |
||||
s.max = value |
||||
s.total = value |
||||
s.entries = 1 |
||||
s.currentPeriod = s.time2period(timestamp) |
||||
} |
||||
s.lastTimestamp = timestamp |
||||
} |
||||
|
||||
func (s *InstantPeriodsWriter) decrementPeriod() { |
||||
switch s.groupBy { |
||||
case diploma.GroupByHour: |
||||
s.currentPeriod = s.currentPeriod.Add(-1 * time.Hour) |
||||
case diploma.GroupByDay: |
||||
s.currentPeriod = s.currentPeriod.AddDate(0, 0, -1) |
||||
case diploma.GroupByMonth: |
||||
s.currentPeriod = s.currentPeriod.AddDate(0, -1, 0) |
||||
} |
||||
} |
||||
|
||||
func (s *InstantPeriodsWriter) packBlankPeriod() { |
||||
bin.PutUint32(s.arr[0:], uint32(s.currentPeriod.Unix())) |
||||
for i := 4; i < len(s.arr); i++ { |
||||
s.arr[i] = 0 |
||||
} |
||||
} |
||||
|
||||
func (s *InstantPeriodsWriter) Close() (err error) { |
||||
if s.entries > 0 { |
||||
if s.lastTimestamp >= s.since { |
||||
s.packPeriod(s.lastTimestamp) |
||||
s.responder.AppendRecord(s.arr) |
||||
} |
||||
} |
||||
return s.responder.Flush() |
||||
} |
||||
|
||||
func (s *InstantPeriodsWriter) packPeriod(timestamp uint32) { |
||||
bin.PutUint32(s.arr[0:], uint32(s.currentPeriod.Unix())) |
||||
bin.PutUint32(s.arr[4:], timestamp) |
||||
bin.PutUint32(s.arr[8:], s.endTimestamp) |
||||
|
||||
pos := 12 |
||||
if (s.aggregateFuncs & diploma.AggregateMin) == diploma.AggregateMin { |
||||
bin.PutFloat64(s.arr[pos:], s.min) |
||||
pos += 8 |
||||
} |
||||
if (s.aggregateFuncs & diploma.AggregateMax) == diploma.AggregateMax { |
||||
bin.PutFloat64(s.arr[pos:], s.max) |
||||
pos += 8 |
||||
} |
||||
if (s.aggregateFuncs & diploma.AggregateAvg) == diploma.AggregateAvg { |
||||
bin.PutFloat64(s.arr[pos:], s.total/float64(s.entries)) |
||||
} |
||||
} |
||||
|
||||
type CumulativePeriodsWriter struct { |
||||
arr []byte |
||||
responder *ChunkedResponder |
||||
since uint32 |
||||
firstHourOfDay int |
||||
currentPeriod time.Time |
||||
groupBy diploma.GroupBy |
||||
time2period func(uint32) time.Time |
||||
endTimestamp uint32 |
||||
endValue float64 |
||||
lastTimestamp uint32 |
||||
lastValue float64 |
||||
} |
||||
|
||||
type CumulativePeriodsWriterOptions struct { |
||||
Dst io.Writer |
||||
GroupBy diploma.GroupBy |
||||
Since uint32 |
||||
FirstHourOfDay int |
||||
} |
||||
|
||||
func NewCumulativePeriodsWriter(opt CumulativePeriodsWriterOptions) (*CumulativePeriodsWriter, error) { |
||||
if opt.Dst == nil { |
||||
return nil, errors.New("Dst option is required") |
||||
} |
||||
if opt.FirstHourOfDay < 0 || opt.FirstHourOfDay > 23 { |
||||
return nil, fmt.Errorf("wrong firstHourOfDay option: %d", opt.FirstHourOfDay) |
||||
} |
||||
|
||||
s := &CumulativePeriodsWriter{ |
||||
arr: make([]byte, 28), |
||||
responder: NewChunkedResponder(opt.Dst), |
||||
since: opt.Since, |
||||
firstHourOfDay: opt.FirstHourOfDay, |
||||
groupBy: opt.GroupBy, |
||||
} |
||||
|
||||
s.time2period = func(timestamp uint32) time.Time { |
||||
return timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "h") |
||||
} |
||||
|
||||
switch opt.GroupBy { |
||||
case diploma.GroupByHour: |
||||
s.time2period = groupByHour |
||||
|
||||
case diploma.GroupByDay: |
||||
if s.firstHourOfDay > 0 { |
||||
s.time2period = s.groupByDayUsingFHD |
||||
} else { |
||||
s.time2period = groupByDay |
||||
} |
||||
|
||||
case diploma.GroupByMonth: |
||||
if s.firstHourOfDay > 0 { |
||||
s.time2period = s.groupByMonthUsingFHD |
||||
} else { |
||||
s.time2period = groupByMonth |
||||
} |
||||
|
||||
default: |
||||
return nil, fmt.Errorf("unknown groupBy %d option", opt.GroupBy) |
||||
} |
||||
return s, nil |
||||
} |
||||
|
||||
func (s *CumulativePeriodsWriter) groupByDayUsingFHD(timestamp uint32) time.Time { |
||||
tm := timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "d") |
||||
if tm.Hour() < s.firstHourOfDay { |
||||
tm = tm.AddDate(0, 0, -1) |
||||
} |
||||
return tm |
||||
} |
||||
|
||||
func (s *CumulativePeriodsWriter) groupByMonthUsingFHD(timestamp uint32) time.Time { |
||||
tm := timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "m") |
||||
if tm.Hour() < s.firstHourOfDay { |
||||
tm = tm.AddDate(0, 0, -1) |
||||
} |
||||
return tm |
||||
} |
||||
|
||||
func (s *CumulativePeriodsWriter) Feed(timestamp uint32, value float64) { |
||||
s.feed(timestamp, value, false) |
||||
} |
||||
|
||||
func (s *CumulativePeriodsWriter) FeedNoSend(timestamp uint32, value float64) { |
||||
s.feed(timestamp, value, true) |
||||
} |
||||
|
||||
func (s *CumulativePeriodsWriter) feed(timestamp uint32, value float64, isBuffer bool) { |
||||
if s.endTimestamp > 0 { |
||||
period := s.time2period(timestamp) |
||||
if period != s.currentPeriod { |
||||
s.packPeriod(timestamp, value) |
||||
if isBuffer { |
||||
s.responder.BufferRecord(s.arr) |
||||
} else { |
||||
s.responder.AppendRecord(s.arr) |
||||
} |
||||
s.decrementPeriod() |
||||
for period.Before(s.currentPeriod) { |
||||
// вставляю пустышку
|
||||
s.packBlankPeriod() |
||||
if isBuffer { |
||||
s.responder.BufferRecord(s.arr) |
||||
} else { |
||||
s.responder.AppendRecord(s.arr) |
||||
} |
||||
s.decrementPeriod() |
||||
} |
||||
s.endTimestamp = timestamp |
||||
s.endValue = value |
||||
} |
||||
} else { |
||||
s.endTimestamp = timestamp |
||||
s.endValue = value |
||||
s.currentPeriod = s.time2period(timestamp) |
||||
} |
||||
s.lastTimestamp = timestamp |
||||
s.lastValue = value |
||||
} |
||||
|
||||
func (s *CumulativePeriodsWriter) decrementPeriod() { |
||||
switch s.groupBy { |
||||
case diploma.GroupByHour: |
||||
s.currentPeriod = s.currentPeriod.Add(-1 * time.Hour) |
||||
case diploma.GroupByDay: |
||||
s.currentPeriod = s.currentPeriod.AddDate(0, 0, -1) |
||||
case diploma.GroupByMonth: |
||||
s.currentPeriod = s.currentPeriod.AddDate(0, -1, 0) |
||||
} |
||||
} |
||||
|
||||
func (s *CumulativePeriodsWriter) packBlankPeriod() { |
||||
bin.PutUint32(s.arr[0:], uint32(s.currentPeriod.Unix())) |
||||
for i := 4; i < len(s.arr); i++ { |
||||
s.arr[i] = 0 |
||||
} |
||||
} |
||||
|
||||
func (s *CumulativePeriodsWriter) packPeriod(start uint32, startValue float64) { |
||||
bin.PutUint32(s.arr[0:], uint32(s.currentPeriod.Unix())) |
||||
bin.PutUint32(s.arr[4:], start) |
||||
bin.PutUint32(s.arr[8:], s.endTimestamp) |
||||
bin.PutFloat64(s.arr[12:], startValue) |
||||
bin.PutFloat64(s.arr[20:], s.endValue) |
||||
} |
||||
|
||||
func (s *CumulativePeriodsWriter) Close() error { |
||||
if s.endTimestamp > 0 { |
||||
if s.endTimestamp >= s.since { |
||||
if s.lastTimestamp != s.endTimestamp { |
||||
s.packPeriod(s.lastTimestamp, s.lastValue) |
||||
} else { |
||||
s.packPeriod(s.endTimestamp, s.endValue) |
||||
} |
||||
s.responder.AppendRecord(s.arr) |
||||
} |
||||
} |
||||
return s.responder.Flush() |
||||
} |
@ -0,0 +1,143 @@ |
||||
package transform |
||||
|
||||
import ( |
||||
"io" |
||||
|
||||
"gordenko.dev/dima/diploma/bin" |
||||
) |
||||
|
||||
// CURRENT VALUE WRITER
|
||||
|
||||
type CurrentValue struct { |
||||
MetricID uint32 |
||||
Timestamp uint32 |
||||
Value float64 |
||||
} |
||||
|
||||
type CurrentValueWriter struct { |
||||
arr []byte |
||||
responder *ChunkedResponder |
||||
} |
||||
|
||||
func NewCurrentValueWriter(dst io.Writer) *CurrentValueWriter { |
||||
return &CurrentValueWriter{ |
||||
arr: make([]byte, 16), |
||||
responder: NewChunkedResponder(dst), |
||||
} |
||||
} |
||||
|
||||
func (s *CurrentValueWriter) BufferValue(m CurrentValue) { |
||||
bin.PutUint32(s.arr[0:], m.MetricID) |
||||
bin.PutUint32(s.arr[4:], m.Timestamp) |
||||
bin.PutFloat64(s.arr[8:], m.Value) |
||||
s.responder.BufferRecord(s.arr) |
||||
} |
||||
|
||||
func (s *CurrentValueWriter) Close() error { |
||||
return s.responder.Flush() |
||||
} |
||||
|
||||
// INSTANT MEASURE WRITER
|
||||
|
||||
type InstantMeasure struct { |
||||
Timestamp uint32 |
||||
Value float64 |
||||
} |
||||
|
||||
type InstantMeasureWriter struct { |
||||
arr []byte |
||||
responder *ChunkedResponder |
||||
since uint32 |
||||
} |
||||
|
||||
func NewInstantMeasureWriter(dst io.Writer, since uint32) *InstantMeasureWriter { |
||||
return &InstantMeasureWriter{ |
||||
arr: make([]byte, 12), |
||||
responder: NewChunkedResponder(dst), |
||||
since: since, |
||||
} |
||||
} |
||||
|
||||
func (s *InstantMeasureWriter) Feed(timestamp uint32, value float64) { |
||||
s.feed(timestamp, value, false) |
||||
} |
||||
|
||||
func (s *InstantMeasureWriter) FeedNoSend(timestamp uint32, value float64) { |
||||
s.feed(timestamp, value, true) |
||||
} |
||||
|
||||
func (s *InstantMeasureWriter) feed(timestamp uint32, value float64, isBuffer bool) { |
||||
if timestamp < s.since { |
||||
return |
||||
} |
||||
bin.PutUint32(s.arr[0:], timestamp) |
||||
bin.PutFloat64(s.arr[4:], value) |
||||
if isBuffer { |
||||
s.responder.BufferRecord(s.arr) |
||||
} else { |
||||
s.responder.AppendRecord(s.arr) |
||||
} |
||||
} |
||||
|
||||
func (s *InstantMeasureWriter) Close() error { |
||||
return s.responder.Flush() |
||||
} |
||||
|
||||
// CUMULATIVE MEASURE WRITER
|
||||
|
||||
type CumulativeMeasure struct { |
||||
Timestamp uint32 |
||||
Value float64 |
||||
Total float64 |
||||
} |
||||
|
||||
type CumulativeMeasureWriter struct { |
||||
arr []byte |
||||
responder *ChunkedResponder |
||||
since uint32 |
||||
endTimestamp uint32 |
||||
endValue float64 |
||||
} |
||||
|
||||
func NewCumulativeMeasureWriter(dst io.Writer, since uint32) *CumulativeMeasureWriter { |
||||
return &CumulativeMeasureWriter{ |
||||
arr: make([]byte, 20), |
||||
responder: NewChunkedResponder(dst), |
||||
since: since, |
||||
} |
||||
} |
||||
|
||||
func (s *CumulativeMeasureWriter) Feed(timestamp uint32, value float64) { |
||||
s.feed(timestamp, value, false) |
||||
} |
||||
|
||||
func (s *CumulativeMeasureWriter) FeedNoSend(timestamp uint32, value float64) { |
||||
s.feed(timestamp, value, true) |
||||
} |
||||
|
||||
func (s *CumulativeMeasureWriter) feed(timestamp uint32, value float64, isBuffer bool) { |
||||
if s.endTimestamp > 0 { |
||||
s.pack(s.endValue - value) |
||||
if isBuffer { |
||||
s.responder.BufferRecord(s.arr) |
||||
} else { |
||||
s.responder.AppendRecord(s.arr) |
||||
} |
||||
} |
||||
s.endTimestamp = timestamp |
||||
s.endValue = value |
||||
} |
||||
|
||||
func (s *CumulativeMeasureWriter) pack(total float64) { |
||||
bin.PutUint32(s.arr[0:], s.endTimestamp) |
||||
bin.PutFloat64(s.arr[4:], s.endValue) |
||||
bin.PutFloat64(s.arr[12:], total) |
||||
} |
||||
|
||||
func (s *CumulativeMeasureWriter) Close() error { |
||||
if s.endTimestamp >= s.since { |
||||
s.pack(0) |
||||
s.responder.BufferRecord(s.arr) |
||||
} |
||||
return s.responder.Flush() |
||||
} |
@ -0,0 +1,89 @@ |
||||
package transform |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"io" |
||||
|
||||
"gordenko.dev/dima/diploma/bin" |
||||
"gordenko.dev/dima/diploma/proto" |
||||
) |
||||
|
||||
// CHUNKED RESPONDER
|
||||
|
||||
var endMsg = []byte{ |
||||
proto.RespEndOfValue, // end of stream
|
||||
} |
||||
|
||||
type ChunkedResponder struct { |
||||
recordsQty int |
||||
buf *bytes.Buffer |
||||
dst io.Writer |
||||
} |
||||
|
||||
func NewChunkedResponder(dst io.Writer) *ChunkedResponder { |
||||
s := &ChunkedResponder{ |
||||
recordsQty: 0, |
||||
buf: bytes.NewBuffer(nil), |
||||
dst: dst, |
||||
} |
||||
|
||||
s.buf.Write([]byte{ |
||||
proto.RespPartOfValue, // message type
|
||||
0, 0, 0, 0, // records qty
|
||||
}) |
||||
return s |
||||
} |
||||
|
||||
func (s *ChunkedResponder) BufferRecord(rec []byte) { |
||||
s.buf.Write(rec) |
||||
s.recordsQty++ |
||||
} |
||||
|
||||
func (s *ChunkedResponder) AppendRecord(rec []byte) error { |
||||
s.buf.Write(rec) |
||||
s.recordsQty++ |
||||
|
||||
if s.buf.Len() < 1500 { |
||||
return nil |
||||
} |
||||
|
||||
if err := s.sendBuffered(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
s.buf.Write([]byte{ |
||||
proto.RespPartOfValue, // message type
|
||||
0, 0, 0, 0, // records qty
|
||||
}) |
||||
s.recordsQty = 0 |
||||
return nil |
||||
} |
||||
|
||||
func (s *ChunkedResponder) Flush() error { |
||||
if s.recordsQty > 0 { |
||||
if err := s.sendBuffered(); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
if _, err := s.dst.Write(endMsg); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (s *ChunkedResponder) sendBuffered() (err error) { |
||||
msg := s.buf.Bytes() |
||||
bin.PutUint32(msg[1:], uint32(s.recordsQty)) |
||||
|
||||
n, err := s.dst.Write(msg) |
||||
if err != nil { |
||||
return |
||||
} |
||||
if n != len(msg) { |
||||
return fmt.Errorf("incomplete write %d bytes instead of %d", n, len(msg)) |
||||
} |
||||
s.buf.Reset() |
||||
return |
||||
} |
@ -0,0 +1,19 @@ |
||||
package transform |
||||
|
||||
import ( |
||||
"time" |
||||
|
||||
"gordenko.dev/dima/diploma/timeutil" |
||||
) |
||||
|
||||
func groupByHour(timestamp uint32) time.Time { |
||||
return timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "h") |
||||
} |
||||
|
||||
func groupByDay(timestamp uint32) time.Time { |
||||
return timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "d") |
||||
} |
||||
|
||||
func groupByMonth(timestamp uint32) time.Time { |
||||
return timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "m") |
||||
} |
Loading…
Reference in new issue