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()) }