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 }