You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
7.6 KiB
325 lines
7.6 KiB
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())
|
|
}
|
|
|