parent
5e49c66e15
commit
84ed171fdf
@ -1,325 +0,0 @@ |
|||||||
package atree |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"time" |
|
||||||
|
|
||||||
"gordenko.dev/dima/diploma" |
|
||||||
"gordenko.dev/dima/diploma/timeutil" |
|
||||||
) |
|
||||||
|
|
||||||
// AGGREGATE
|
|
||||||
|
|
||||||
type InstantAggregator struct { |
|
||||||
firstHourOfDay int |
|
||||||
lastDayOfMonth 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 |
|
||||||
LastDayOfMonth int |
|
||||||
} |
|
||||||
|
|
||||||
func NewInstantAggregator(opt InstantAggregatorOptions) (*InstantAggregator, error) { |
|
||||||
s := &InstantAggregator{ |
|
||||||
firstHourOfDay: opt.FirstHourOfDay, |
|
||||||
lastDayOfMonth: opt.LastDayOfMonth, |
|
||||||
} |
|
||||||
|
|
||||||
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 { |
|
||||||
if s.lastDayOfMonth > 0 { |
|
||||||
s.time2period = s.groupByMonthUsingFHDAndLDM |
|
||||||
} else { |
|
||||||
s.time2period = s.groupByMonthUsingFHD |
|
||||||
} |
|
||||||
} else { |
|
||||||
if s.lastDayOfMonth > 0 { |
|
||||||
s.time2period = s.groupByMonthUsingLDM |
|
||||||
} 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()) |
|
||||||
} |
|
||||||
|
|
||||||
func (s *InstantAggregator) groupByMonthUsingLDM(timestamp uint32) uint32 { |
|
||||||
tm := timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "m") |
|
||||||
if tm.Day() > s.lastDayOfMonth { |
|
||||||
tm = tm.AddDate(0, 1, 0) |
|
||||||
} |
|
||||||
return uint32(tm.Unix()) |
|
||||||
} |
|
||||||
|
|
||||||
func (s *InstantAggregator) groupByMonthUsingFHDAndLDM(timestamp uint32) uint32 { |
|
||||||
// ВАЖНО!
|
|
||||||
// Сперва проверяю время.
|
|
||||||
tm := timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "m") |
|
||||||
if tm.Hour() < s.firstHourOfDay { |
|
||||||
tm = tm.AddDate(0, 0, -1) |
|
||||||
} |
|
||||||
if tm.Day() > s.lastDayOfMonth { |
|
||||||
tm = tm.AddDate(0, 1, 0) |
|
||||||
} |
|
||||||
return uint32(tm.Unix()) |
|
||||||
} |
|
||||||
|
|
||||||
// CUMULATIVE
|
|
||||||
|
|
||||||
type CumulativeAggregator struct { |
|
||||||
firstHourOfDay int |
|
||||||
lastDayOfMonth 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 |
|
||||||
LastDayOfMonth int |
|
||||||
} |
|
||||||
|
|
||||||
func NewCumulativeAggregator(opt CumulativeAggregatorOptions) (*CumulativeAggregator, error) { |
|
||||||
s := &CumulativeAggregator{ |
|
||||||
firstHourOfDay: opt.FirstHourOfDay, |
|
||||||
lastDayOfMonth: opt.LastDayOfMonth, |
|
||||||
} |
|
||||||
|
|
||||||
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 { |
|
||||||
if s.lastDayOfMonth > 0 { |
|
||||||
s.time2period = s.groupByMonthUsingFHDAndLDM |
|
||||||
} else { |
|
||||||
s.time2period = s.groupByMonthUsingFHD |
|
||||||
} |
|
||||||
} else { |
|
||||||
if s.lastDayOfMonth > 0 { |
|
||||||
s.time2period = s.groupByMonthUsingLDM |
|
||||||
} 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 (s *CumulativeAggregator) groupByMonthUsingLDM(timestamp uint32) uint32 { |
|
||||||
tm := timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "m") |
|
||||||
if tm.Day() > s.lastDayOfMonth { |
|
||||||
tm = tm.AddDate(0, 1, 0) |
|
||||||
} |
|
||||||
return uint32(tm.Unix()) |
|
||||||
} |
|
||||||
|
|
||||||
func (s *CumulativeAggregator) groupByMonthUsingFHDAndLDM(timestamp uint32) uint32 { |
|
||||||
// ВАЖНО!
|
|
||||||
// Сперва проверяю время.
|
|
||||||
tm := timeutil.FirstSecondInPeriod(time.Unix(int64(timestamp), 0), "m") |
|
||||||
if tm.Hour() < s.firstHourOfDay { |
|
||||||
tm = tm.AddDate(0, 0, -1) |
|
||||||
} |
|
||||||
if tm.Day() > s.lastDayOfMonth { |
|
||||||
tm = tm.AddDate(0, 1, 0) |
|
||||||
} |
|
||||||
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 +1,15 @@ |
|||||||
package atree |
package atree |
||||||
|
|
||||||
import ( |
type PeriodsWriter interface { |
||||||
"bytes" |
Feed(uint32, float64) |
||||||
"fmt" |
FeedNoSend(uint32, float64) |
||||||
"io" |
Close() error |
||||||
|
|
||||||
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 WorkerMeasureConsumer interface { |
||||||
|
FeedNoSend(uint32, float64) |
||||||
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 { |
type AtreeMeasureConsumer interface { |
||||||
var q int |
Feed(uint32, float64) |
||||||
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
@ -0,0 +1,408 @@ |
|||||||
|
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 |
||||||
|
AggregateFuncs byte |
||||||
|
FirstHourOfDay int |
||||||
|
} |
||||||
|
|
||||||
|
type InstantPeriodsWriter struct { |
||||||
|
aggregateFuncs byte |
||||||
|
arr []byte |
||||||
|
responder *ChunkedResponder |
||||||
|
groupBy diploma.GroupBy |
||||||
|
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) |
||||||
|
} |
||||||
|
// Считаю q, чтобы заранее выделить массив для упаковки периодов
|
||||||
|
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") |
||||||
|
} |
||||||
|
|
||||||
|
// 12 - это period, since, until
|
||||||
|
// 8 - это размер float64
|
||||||
|
s := &InstantPeriodsWriter{ |
||||||
|
aggregateFuncs: opt.AggregateFuncs, |
||||||
|
arr: make([]byte, 12+q*8), |
||||||
|
responder: NewChunkedResponder(opt.Dst), |
||||||
|
groupBy: opt.GroupBy, |
||||||
|
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() |
||||||
|
//fmt.Println(" period: ", period.Format("2006-01-02 15:04:05"))
|
||||||
|
//fmt.Println("current period: ", s.currentPeriod.Format("2006-01-02 15:04:05"))
|
||||||
|
for period.Before(s.currentPeriod) { |
||||||
|
// вставляю пустышку
|
||||||
|
s.packBlankPeriod() |
||||||
|
if isBuffer { |
||||||
|
s.responder.BufferRecord(s.arr) |
||||||
|
} else { |
||||||
|
s.responder.AppendRecord(s.arr) |
||||||
|
} |
||||||
|
s.decrementPeriod() |
||||||
|
//fmt.Println(" period: ", period.Format("2006-01-02 15:04:05"))
|
||||||
|
//fmt.Println("current period: ", s.currentPeriod.Format("2006-01-02 15:04:05"))
|
||||||
|
//return
|
||||||
|
} |
||||||
|
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 |
||||||
|
} |
||||||
|
// для подсчета AVG
|
||||||
|
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) |
||||||
|
//fmt.Println("decrement")
|
||||||
|
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() { |
||||||
|
//period := s.currentPeriod.Format("2006-01-02 15:04:05")
|
||||||
|
//since := "0"
|
||||||
|
//until := "0"
|
||||||
|
//fmt.Printf("%s: %s - %s, %.0f - %.0f\n", period, since, until, 0.0, 0.0)
|
||||||
|
// until - это endTimestamp всегда
|
||||||
|
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 { |
||||||
|
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 |
||||||
|
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 |
||||||
|
FirstHourOfDay int |
||||||
|
} |
||||||
|
|
||||||
|
func NewCumulativePeriodsWriter(opt CumulativePeriodsWriterOptions) (*CumulativePeriodsWriter, error) { |
||||||
|
if opt.Dst == nil { |
||||||
|
return nil, errors.New("Dst option is required") |
||||||
|
} |
||||||
|
// Считаю q, чтобы заранее выделить массив для упаковки периодов
|
||||||
|
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), |
||||||
|
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() |
||||||
|
//fmt.Println(" period: ", period.Format("2006-01-02 15:04:05"))
|
||||||
|
//fmt.Println("current period: ", s.currentPeriod.Format("2006-01-02 15:04:05"))
|
||||||
|
for period.Before(s.currentPeriod) { |
||||||
|
// вставляю пустышку
|
||||||
|
s.packBlankPeriod() |
||||||
|
if isBuffer { |
||||||
|
s.responder.BufferRecord(s.arr) |
||||||
|
} else { |
||||||
|
s.responder.AppendRecord(s.arr) |
||||||
|
} |
||||||
|
s.decrementPeriod() |
||||||
|
//fmt.Println(" period: ", period.Format("2006-01-02 15:04:05"))
|
||||||
|
//fmt.Println("current period: ", s.currentPeriod.Format("2006-01-02 15:04:05"))
|
||||||
|
//return
|
||||||
|
} |
||||||
|
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) |
||||||
|
//fmt.Println("decrement")
|
||||||
|
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() { |
||||||
|
//period := s.currentPeriod.Format("2006-01-02 15:04:05")
|
||||||
|
//since := "0"
|
||||||
|
//until := "0"
|
||||||
|
//fmt.Printf("%s: %s - %s, %.0f - %.0f\n", period, since, until, 0.0, 0.0)
|
||||||
|
// until - это endTimestamp всегда
|
||||||
|
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) { |
||||||
|
//period := s.currentPeriod.Format("2006-01-02 15:04:05")
|
||||||
|
//since := time.Unix(int64(start), 0).Format("2006-01-02 15:04:05")
|
||||||
|
//until := time.Unix(int64(s.endTimestamp), 0).Format("2006-01-02 15:04:05")
|
||||||
|
//fmt.Printf("%s: %s - %s, %.0f - %.0f\n", period, since, until, startValue, s.endValue)
|
||||||
|
// until - это endTimestamp всегда
|
||||||
|
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.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,147 @@ |
|||||||
|
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 { |
||||||
|
// 12 - это timestamp, value
|
||||||
|
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 { |
||||||
|
// 20 - это timestamp, value, total
|
||||||
|
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 { |
||||||
|
// endTimestamp внутри заданного периода. Других показаний нет,
|
||||||
|
// поэтому время добавляю, но накопленную сумму ставлю 0.
|
||||||
|
s.pack(0) |
||||||
|
// Если < since - ничего делать не нужно, ибо накопленная сумма уже добавлена
|
||||||
|
} |
||||||
|
return s.responder.Flush() |
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
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 |
||||||
|
} |
||||||
|
//fmt.Printf("sent endMsg %d\n", endMsg)
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *ChunkedResponder) sendBuffered() (err error) { |
||||||
|
msg := s.buf.Bytes() |
||||||
|
bin.PutUint32(msg[1:], uint32(s.recordsQty)) |
||||||
|
//fmt.Printf("put uint16: %d\n", msg[:3])
|
||||||
|
|
||||||
|
//fmt.Printf("send %d records\n", s.recordsQty)
|
||||||
|
|
||||||
|
//fmt.Printf("send buffered: %d, qty: %d\n", msg, 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 |
||||||
|
} |
||||||
|
|
||||||
|
// Для Aggregation пишем функцию определения периода и пуляем фактические периоды
|
||||||
|
//
|
||||||
|
|
||||||
|
// By default net/http.Server uses 4KB buffers, which are flushed to client with chunked responses.
|
||||||
|
// These buffers may result in visible overhead for responses exceeding a few megabytes.
|
||||||
|
// So allocate 64Kb buffers.
|
||||||
|
// bw: bufio.NewWriterSize(w, 64*1024),
|
@ -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