wp
This commit is contained in:
570
mqe/split.go
Normal file
570
mqe/split.go
Normal file
@@ -0,0 +1,570 @@
|
||||
package mqe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SplitByPeriods(producer periodProducer, calc PeriodCalculator) (result []AggregatedMeasure, err error) {
|
||||
var (
|
||||
isOpened bool
|
||||
openedPeriod time.Time
|
||||
since int64
|
||||
sinceValue float64
|
||||
until int64
|
||||
untilValue float64
|
||||
|
||||
//
|
||||
period time.Time
|
||||
first, last PeriodBound
|
||||
found bool
|
||||
)
|
||||
|
||||
for {
|
||||
period, first, last, found, err = producer.Next()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
|
||||
if isOpened {
|
||||
if calc.IsExtendedSincePeriod(period) {
|
||||
err = fmt.Errorf("extended period while already opened")
|
||||
return
|
||||
} else if calc.IsExtendedUntilPeriod(period) {
|
||||
result = append(result, AggregatedMeasure{
|
||||
Period: openedPeriod.Unix(),
|
||||
Since: since,
|
||||
Until: first.Time,
|
||||
Values: []float64{
|
||||
first.Value,
|
||||
first.Value - sinceValue,
|
||||
},
|
||||
})
|
||||
// Последний период успешно закрыли данными из Extended Until, поэтому выходим
|
||||
return
|
||||
} else {
|
||||
if period.Equal(openedPeriod) {
|
||||
// +
|
||||
// Данная ситуация будет когда предыдущий период был корректно
|
||||
// закрыт за 3 минуты до конца. После этого сразу открылся
|
||||
// новый период. А запись для нового периода получена только
|
||||
// сейчас.
|
||||
if calc.IsPeriodCorrectEnds(period, last.Time) {
|
||||
// CLOSE
|
||||
result = append(result, AggregatedMeasure{
|
||||
Period: openedPeriod.Unix(),
|
||||
Since: since,
|
||||
Until: last.Time,
|
||||
Values: []float64{
|
||||
last.Value,
|
||||
last.Value - sinceValue,
|
||||
},
|
||||
})
|
||||
|
||||
// OPEN PERIOD (extended since, last 3 min closed)
|
||||
openedPeriod = calc.NextPeriod(period)
|
||||
if calc.IsExtendedUntilPeriod(openedPeriod) {
|
||||
return
|
||||
}
|
||||
since = last.Time
|
||||
sinceValue = last.Value
|
||||
until = 0
|
||||
untilValue = 0
|
||||
} else {
|
||||
// Период закроем где-то в будущем, на всякий случай запомнили последнее
|
||||
// показание внутри периода
|
||||
until = last.Time
|
||||
untilValue = last.Value
|
||||
}
|
||||
} else {
|
||||
// +
|
||||
// Периоды не совпадают - смело закрываем
|
||||
// Возможные ситуации:
|
||||
// - был открыт мартовский период, а текущий период - апрель
|
||||
// -
|
||||
result = append(result, AggregatedMeasure{
|
||||
Period: openedPeriod.Unix(),
|
||||
Since: since,
|
||||
Until: first.Time,
|
||||
Values: []float64{
|
||||
first.Value,
|
||||
first.Value - sinceValue,
|
||||
},
|
||||
})
|
||||
|
||||
if first.Time != last.Time {
|
||||
// +
|
||||
openedPeriod = period
|
||||
since = first.Time
|
||||
sinceValue = first.Value
|
||||
until = 0
|
||||
untilValue = 0
|
||||
|
||||
if calc.IsPeriodCorrectEnds(period, last.Time) {
|
||||
// Закрываем только что открытый период
|
||||
result = append(result, AggregatedMeasure{
|
||||
Period: openedPeriod.Unix(),
|
||||
Since: since,
|
||||
Until: last.Time,
|
||||
Values: []float64{
|
||||
last.Value,
|
||||
last.Value - sinceValue,
|
||||
},
|
||||
})
|
||||
|
||||
openedPeriod = calc.NextPeriod(period)
|
||||
if calc.IsExtendedUntilPeriod(openedPeriod) {
|
||||
return
|
||||
}
|
||||
since = last.Time
|
||||
sinceValue = last.Value
|
||||
until = 0
|
||||
untilValue = 0
|
||||
} else {
|
||||
// Период закроем где-то в будущем, на всякий случай запомнили последнее
|
||||
// показание внутри периода
|
||||
until = last.Time
|
||||
untilValue = last.Value
|
||||
}
|
||||
} else {
|
||||
// +
|
||||
// first == last
|
||||
if calc.IsPeriodCorrectEnds(period, last.Time) {
|
||||
// Допустим закрыли предыдущий период 30 апреля в 23:58, новый стартуем за май.
|
||||
openedPeriod = calc.NextPeriod(period)
|
||||
if calc.IsExtendedUntilPeriod(openedPeriod) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Допустим закрыли предыдущий период 15 апреля, новый стартуем за апрель.
|
||||
openedPeriod = period
|
||||
}
|
||||
since = last.Time
|
||||
sinceValue = last.Value
|
||||
until = 0
|
||||
untilValue = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if calc.IsExtendedSincePeriod(period) {
|
||||
// Если последние 3 минуты
|
||||
if calc.IsPeriodCorrectEnds(period, last.Time) {
|
||||
// OPEN PERIOD (extended since, last 3 min closed)
|
||||
isOpened = true
|
||||
openedPeriod = calc.NextPeriod(period)
|
||||
since = last.Time
|
||||
sinceValue = last.Value
|
||||
until = 0
|
||||
untilValue = 0
|
||||
}
|
||||
} else if calc.IsExtendedUntilPeriod(period) {
|
||||
// Ничего не нашли, пустой результат
|
||||
return
|
||||
} else {
|
||||
// OPEN PERIOD (regular first)
|
||||
isOpened = true
|
||||
openedPeriod = period
|
||||
since = first.Time
|
||||
sinceValue = first.Value
|
||||
until = last.Time
|
||||
untilValue = last.Value
|
||||
|
||||
if calc.IsPeriodCorrectEnds(openedPeriod, last.Time) {
|
||||
// Закрываем только что открытый период
|
||||
result = append(result, AggregatedMeasure{
|
||||
Period: openedPeriod.Unix(),
|
||||
Since: since,
|
||||
Until: last.Time,
|
||||
Values: []float64{
|
||||
last.Value,
|
||||
last.Value - sinceValue,
|
||||
},
|
||||
})
|
||||
|
||||
openedPeriod = calc.NextPeriod(period)
|
||||
if calc.IsExtendedUntilPeriod(openedPeriod) {
|
||||
return
|
||||
}
|
||||
since = last.Time
|
||||
sinceValue = last.Value
|
||||
until = 0
|
||||
untilValue = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Доп запрос.
|
||||
if openedPeriod.IsZero() {
|
||||
// Открытого периода не будет если вообще не нашли записей
|
||||
return
|
||||
}
|
||||
|
||||
next, found, err := producer.Last()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if found {
|
||||
result = append(result, AggregatedMeasure{
|
||||
Period: openedPeriod.Unix(),
|
||||
Since: since,
|
||||
Until: next.Time,
|
||||
Values: []float64{
|
||||
next.Value,
|
||||
next.Value - sinceValue,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
if until > 0 {
|
||||
result = append(result, AggregatedMeasure{
|
||||
Period: openedPeriod.Unix(),
|
||||
Since: since,
|
||||
Until: until,
|
||||
Values: []float64{
|
||||
untilValue,
|
||||
untilValue - sinceValue,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Period - это начало периода, например для дней - это 0ч 0м 0с
|
||||
// lastTm предыдущего периода всегда будет меньше current period
|
||||
// since - это всегда сдвинутая метка времени (+ FirstHourOfDay * 3600)
|
||||
|
||||
// При FirstHourOfDay = 3 и группировке по дням, будет такая ситуация.
|
||||
// Period будет указывать на 1 марта 00:00:00, а since на 1 марта 02:00:00,
|
||||
// а lastTm предыдущего периода на 1 марта 01:59:00.
|
||||
// Поэтому нельзя сравнивать текущий period и lastTm предыдущего периода.
|
||||
// Преобразовать lastTm к period непонятно как если нужно учитывать LastDayOfMonth.
|
||||
// Пример: 28 марта 00ч стало 1 апреля 00ч, а 27 марта 23:59 так и осталось.
|
||||
// Можно вычислить endPeriodTime для previousPeriodEndTime, но текущий период может
|
||||
// быть не строго следующим после предыдущего (например, июнь после марта).
|
||||
// Правильное решение - берем firstTm текущего периода и вычисляем его реальное
|
||||
// календарное начало. Например, FirstHourOfDay = 3, LastDayOfMonth = 28,
|
||||
// firstTm = 1 мая. Календарное начало - 28 апреля 03:00:00. И уже с этим значением
|
||||
// сравниваем previousPeriodEndTime.
|
||||
// Но можно сравнивать since c lastTm и until c firstTm для определения
|
||||
// extended периодов.
|
||||
/*
|
||||
func periodToCalendarTime(period time.Time, groupBy string, lastDayOfMonth, firstHourOfDay int) time.Time {
|
||||
switch groupBy {
|
||||
case "d":
|
||||
if firstHourOfDay == 0 {
|
||||
return period
|
||||
}
|
||||
// Было 0ч, стало - 2ч
|
||||
return period.Add(time.Duration(firstHourOfDay) * time.Hour)
|
||||
|
||||
case "m":
|
||||
if lastDayOfMonth == 0 {
|
||||
if firstHourOfDay == 0 {
|
||||
return period
|
||||
}
|
||||
// Было 0ч, стало - 2ч
|
||||
return period.Add(time.Duration(firstHourOfDay) * time.Hour)
|
||||
} else {
|
||||
// текущий период начинается в прошлом месяце
|
||||
tm := period.AddDate(0, -1, 0)
|
||||
return time.Date(
|
||||
tm.Year(),
|
||||
tm.Month(),
|
||||
lastDayOfMonth+1,
|
||||
firstHourOfDay,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
tm.Location(),
|
||||
)
|
||||
}
|
||||
default:
|
||||
return period
|
||||
}
|
||||
}
|
||||
*/
|
||||
//var threeMinutes int64 = 3 * 60
|
||||
|
||||
/*
|
||||
type readOptions struct {
|
||||
MetricID int64 // только для readAggregatedMeasuresCanEndsInFuture
|
||||
LastDayOfMonth int
|
||||
FirstHourOfDay int
|
||||
GroupBy string
|
||||
Since int64
|
||||
Until int64
|
||||
ParsePeriodLayout string
|
||||
}
|
||||
*/
|
||||
// Читает из sql.Rows показания и строит периоды
|
||||
/*
|
||||
func (s *MeasureQueryEngine) readAggregatedMeasuresCanEndsInFuture(tx *sql.Tx, rows *sql.Rows, opt readOptions) (result []CalculatedAggregatedMeasure, err error) {
|
||||
var (
|
||||
openPeriod time.Time
|
||||
since int64
|
||||
sinceValue float64
|
||||
until int64
|
||||
untilValue float64
|
||||
//
|
||||
frameStr string
|
||||
firstTm int64
|
||||
firstValue float64
|
||||
lastTm int64
|
||||
lastValue float64
|
||||
period time.Time
|
||||
)
|
||||
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&frameStr, &firstTm, &firstValue, &lastTm, &lastValue)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
period, err = time.ParseInLocation(opt.ParsePeriodLayout, frameStr, s.location)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("time.ParseInLocation: %s; layout=%q; str=%q",
|
||||
err, opt.ParsePeriodLayout, frameStr)
|
||||
return
|
||||
}
|
||||
|
||||
//fmt.Printf("%s s: %s, %v; u: %s, %v\n", frameStr, time.Unix(firstTm, 0), firstValue, time.Unix(lastTm, 0), lastValue)
|
||||
|
||||
// Читаем записи из БД. Одна запись - один период. Возможны 3 вида периодов:
|
||||
// 1. extended since - период длиной до 3 минут перед since. Оптимизация, чтобы
|
||||
// не отправлять дополнительный запрос.
|
||||
// 2. обычный период.
|
||||
// 3. extended until - период длиной до 1 часа после until. Оптимизация, чтобы
|
||||
// не отправлять дополнительный запрос (в большинстве случаев).
|
||||
//
|
||||
// ВАЖНО!
|
||||
// Любой период (1, 2 или 3) может встретится первым. Определить что это
|
||||
// первая запись модно простой проверкой openPeriod.IsZero()
|
||||
|
||||
if lastTm < opt.Since {
|
||||
// 1. extended since period
|
||||
|
||||
if isPeriodCorrectEnds(period, opt.GroupBy, opt.LastDayOfMonth, opt.FirstHourOfDay, lastTm) {
|
||||
openPeriod = nextPeriod(period, opt.GroupBy)
|
||||
since = lastTm
|
||||
sinceValue = lastValue
|
||||
}
|
||||
continue
|
||||
} else if firstTm > opt.Until {
|
||||
// 3. extended until period
|
||||
|
||||
if openPeriod.IsZero() {
|
||||
// Нет показаний кроме extended until, поэтому выходим
|
||||
return
|
||||
}
|
||||
|
||||
// закрываем открытый период
|
||||
result = append(result, CalculatedAggregatedMeasure{
|
||||
Period: openPeriod.Unix(),
|
||||
Since: since,
|
||||
Until: firstTm,
|
||||
Value: firstValue,
|
||||
Total: firstValue - sinceValue,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 2. обычный период
|
||||
|
||||
if openPeriod.IsZero() {
|
||||
// это первая запись (extended since period не найден)
|
||||
openPeriod = period
|
||||
since = firstTm
|
||||
sinceValue = firstValue
|
||||
} else {
|
||||
if !period.Equal(openPeriod) {
|
||||
//fmt.Printf("\nperiod %s != openPeriod %s\n", period, openPeriod)
|
||||
// Пример: открытый период - март. В period - апрель.
|
||||
// закрываем открытый период
|
||||
result = append(result, CalculatedAggregatedMeasure{
|
||||
Period: openPeriod.Unix(),
|
||||
Since: since,
|
||||
Until: firstTm,
|
||||
Value: firstValue,
|
||||
Total: firstValue - sinceValue,
|
||||
})
|
||||
|
||||
// И сразу открываем новый период
|
||||
openPeriod = period
|
||||
since = firstTm
|
||||
sinceValue = firstValue
|
||||
}
|
||||
// Периоды совпадают. Когда такая ситуация возможна?
|
||||
// Например, на предыдущей итерации был корректно закрыт (57-59 минуты)
|
||||
// прошлый период и сразу открыт новый (текущий).
|
||||
}
|
||||
|
||||
if isPeriodCorrectEnds(openPeriod, opt.GroupBy, opt.LastDayOfMonth, opt.FirstHourOfDay, lastTm) {
|
||||
// fmt.Printf("\nCORRECT END period %s\n", openPeriod)
|
||||
// закрываем открытый период
|
||||
result = append(result, CalculatedAggregatedMeasure{
|
||||
Period: openPeriod.Unix(),
|
||||
Since: since,
|
||||
Until: lastTm,
|
||||
Value: lastValue,
|
||||
Total: lastValue - sinceValue,
|
||||
})
|
||||
|
||||
// Открываем новый период
|
||||
openPeriod = nextPeriod(openPeriod, opt.GroupBy)
|
||||
since = lastTm
|
||||
sinceValue = lastValue
|
||||
} else {
|
||||
// На последних минутах периода (57-59) не было показания, поэтому
|
||||
// запоминаем конец периода какой есть. Если конец периода в будущем
|
||||
// не будет найден - закроем период по текущему последнему показанию.
|
||||
until = lastTm
|
||||
untilValue = lastValue
|
||||
}
|
||||
|
||||
// Валидируем новый период.
|
||||
// Зачем?
|
||||
// Пример: юзер задал opt.Until - 31 марта 23:59:59. Только что корректно
|
||||
// закрылся март показанием от 31 марта 23:57:00. И сразу открылся новый
|
||||
// период за апрель.
|
||||
// Понять что закрыли последний период из запрошенных юзером просто -
|
||||
// вычисляем календарное начало нового периода и сравниваем с opt.Until.
|
||||
mustLessThanUntil := periodToCalendarTime(openPeriod, opt.GroupBy,
|
||||
opt.LastDayOfMonth, opt.FirstHourOfDay)
|
||||
|
||||
if mustLessThanUntil.Unix() > opt.Until {
|
||||
// Новый период вышел за opt.Until, а это означает что мы корректно
|
||||
// закрыли последний период из запрошенных юзером. Bыходим.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Цикл завершен, а из метода мы не вышли, а это означает одно - есть незакрытый
|
||||
// период. Extended until не найден. Значит нужно отправить дополнительный запрос
|
||||
// к БД.
|
||||
var (
|
||||
isNextValueFound bool
|
||||
nextTm int64
|
||||
nextValue float64
|
||||
)
|
||||
|
||||
// Находим следующее показание метрики.
|
||||
err = tx.QueryRow(`
|
||||
SELECT tm, value, true
|
||||
FROM f64
|
||||
WHERE metricID=? AND tm > ?
|
||||
ORDER BY tm ASC
|
||||
LIMIT 1`,
|
||||
opt.MetricID, opt.Until).Scan(&nextTm, &nextValue, &isNextValueFound)
|
||||
|
||||
if err != nil {
|
||||
if err != sql.ErrNoRows {
|
||||
return
|
||||
}
|
||||
// Нет записей
|
||||
err = nil
|
||||
}
|
||||
|
||||
if isNextValueFound {
|
||||
// Найдено какое-то показание в будущем.
|
||||
result = append(result, CalculatedAggregatedMeasure{
|
||||
Period: openPeriod.Unix(),
|
||||
Since: since,
|
||||
Until: nextTm,
|
||||
Value: nextValue,
|
||||
Total: nextValue - sinceValue,
|
||||
})
|
||||
} else {
|
||||
// Закрываем период самым последним показанием внутри периода
|
||||
// (НЕ 57-59 минуты).
|
||||
result = append(result, CalculatedAggregatedMeasure{
|
||||
Period: openPeriod.Unix(),
|
||||
Since: since,
|
||||
Until: until,
|
||||
Value: untilValue,
|
||||
Total: untilValue - sinceValue,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
// Читает из sql.Rows показания и строит периоды
|
||||
func (s *MeasureQueryEngine) readAggregatedMeasuresEndsInsidePeriod(tx *sql.Tx, rows *sql.Rows, opt readOptions) (result []CalculatedAggregatedMeasure, err error) {
|
||||
var (
|
||||
previousPeriodEndTime int64
|
||||
previousPeriodEndValue float64
|
||||
//
|
||||
frameStr string
|
||||
firstTm int64
|
||||
firstValue float64
|
||||
lastTm int64
|
||||
lastValue float64
|
||||
period time.Time
|
||||
)
|
||||
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&frameStr, &firstTm, &firstValue, &lastTm, &lastValue)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
period, err = time.ParseInLocation(opt.ParsePeriodLayout, frameStr, s.location)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("time.ParseInLocation: %s; layout=%q; str=%q",
|
||||
err, opt.ParsePeriodLayout, frameStr)
|
||||
return
|
||||
}
|
||||
|
||||
//fmt.Printf("%s s: %s, %v; u: %s, %v\n", frameStr, time.Unix(firstTm, 0), firstValue, time.Unix(lastTm, 0), lastValue)
|
||||
|
||||
if lastTm < opt.Since {
|
||||
// extended since period
|
||||
previousPeriodEndTime = lastTm
|
||||
previousPeriodEndValue = lastValue
|
||||
continue
|
||||
} else if firstTm > opt.Until {
|
||||
// extended until period
|
||||
return
|
||||
}
|
||||
|
||||
// целевой период. В будущем конец периода не ищем, поэтому закрываем сразу.
|
||||
|
||||
// Вычисляем реальное календарное начало периода. Например, period - 1 мая 00:00:00,
|
||||
// FirstHourOfDay = 3, LastDayOfMonth = 28. Календарное начало - 29 апреля 03:00:00.
|
||||
periodCalendarTime := periodToCalendarTime(period, opt.GroupBy, opt.LastDayOfMonth, opt.FirstHourOfDay)
|
||||
|
||||
minAge := periodCalendarTime.Add(-3 * time.Minute).Unix()
|
||||
|
||||
if previousPeriodEndTime > 0 && previousPeriodEndTime >= minAge {
|
||||
result = append(result, CalculatedAggregatedMeasure{
|
||||
Period: period.Unix(),
|
||||
Since: previousPeriodEndTime,
|
||||
Until: lastTm,
|
||||
Value: lastValue,
|
||||
Total: lastValue - previousPeriodEndValue,
|
||||
})
|
||||
} else {
|
||||
result = append(result, CalculatedAggregatedMeasure{
|
||||
Period: period.Unix(),
|
||||
Since: firstTm,
|
||||
Until: lastTm,
|
||||
Value: lastValue,
|
||||
Total: lastValue - firstValue,
|
||||
})
|
||||
}
|
||||
|
||||
previousPeriodEndTime = lastTm
|
||||
previousPeriodEndValue = lastValue
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
*/
|
||||
Reference in New Issue
Block a user