This commit is contained in:
2026-06-19 07:25:46 +03:00
parent 06823ee547
commit 2d73ce1727
52 changed files with 5173 additions and 547 deletions

141
mqe/total.go Normal file
View File

@@ -0,0 +1,141 @@
package mqe
import (
"fmt"
"strings"
"sync"
"time"
"gordenko.dev/dima/qb/timeutil"
)
// TOTALS
type TotalMetricSpec struct {
MetricID int64
LastDayOfMonth int
FirstHourOfDay int
}
// LastDayOfMonth и FirstHourOfDay нет, ибо у каждой метрики свои настройки
type RangeTotalsFilter struct {
Since string
Until string
// Если учитывать не нужно - программа уровнем выше может передать нули.
// FIX у каждой метрики свои настройки
// искать ли конец периода в следующих периодах, если в последние 3 минуты периода
// не было показаний
CumulativeMetrics []TotalMetricSpec // metricID
}
// SelectRangeTotalsInParallel - метод рассчитывает накопленный объем от Since до Until
// по каждой метрике из массива CumulativeMetricIDs. Учитываются настройки объекта
// (LastDayOfMonth, FirstHourOfDay).
// ВАЖНО!
// Запросы по каждой метрике отправляются в БД в отдельной горутине, что позволяет
// в разы ускорить получение данных по объекту.
func (s *MeasureQueryEngine) GetRangeTotalsInParallel(req RangeTotalsFilter) (resultMap map[int64]RangeTotal, err error) {
if req.Since == "" {
err = fix.Field(EmptyValue, "since")
return
}
if req.Until == "" {
err = fix.Field(EmptyValue, "until")
return
}
// ВАЖНО!
// Если в строке нет таймзоны - Parse функция возвращает время в UTC.
// Поэтому нужно использовать ParseInLocation!
since, err := time.ParseInLocation("2006-01-02", req.Since, s.location)
if err != nil {
err = fix.Field(WrongValue, "since")
return
}
until, err := time.ParseInLocation("2006-01-02", req.Until, s.location)
if err != nil {
err = fix.Field(WrongValue, "until")
return
}
// Корректируем, чтобы указывало на последнюю секунду суток
until = timeutil.LastSecondInPeriod(until, string(ByDay))
var metrics []string
for _, metric := range req.CumulativeMetrics {
metrics = append(metrics, fmt.Sprintf(` {
MetricID: %d
LastDayOfMonth: %d
FirstHourOfDay: %d
}`, metric.MetricID, metric.LastDayOfMonth, metric.FirstHourOfDay))
}
fmt.Printf(`GetRangeTotalsInParallel: {
Since: %s
Until: %s
CumulativeMetrics [
%s
]
}
`, since, until, strings.Join(metrics, "\n"))
if since.Unix() >= until.Unix() {
err = fix.Error(WrongDateRange)
return
}
var (
tasks []*CumulativeTotalTask
wg = new(sync.WaitGroup)
)
resultMap = make(map[int64]RangeTotal)
// CUMULATIVE METRIC TOTALS
if len(req.CumulativeMetrics) > 0 {
for _, metric := range req.CumulativeMetrics {
// У каждой метрики индивидуальные настройки,
// ибо метрики принадлежат разным объектам!!!
metricSince := since
metricUntil := until
if metric.FirstHourOfDay > 0 {
metricSince = since.Add(time.Duration(metric.FirstHourOfDay) * time.Hour)
metricUntil = until.Add(time.Duration(metric.FirstHourOfDay) * time.Hour)
}
task := &CumulativeTotalTask{
In: getCumulativeTotalFilter{
MetricID: metric.MetricID,
Since: metricSince.Unix(),
Until: metricUntil.Unix(),
LastDayOfMonth: metric.LastDayOfMonth,
FirstHourOfDay: metric.FirstHourOfDay,
},
WaitGroup: wg,
}
wg.Add(1)
s.cumulativeTotalCh <- task
tasks = append(tasks, task)
}
}
wg.Wait()
for _, task := range tasks {
if task.Err != nil {
err = fmt.Errorf("get data for the metric %d: %s", task.In.MetricID, task.Err)
return
}
if task.Result != nil {
resultMap[task.In.MetricID] = *task.Result
} else {
resultMap[task.In.MetricID] = RangeTotal{}
}
}
return
}