Files
qb/mqe/readings.go
2026-06-19 07:25:46 +03:00

180 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package mqe
import (
"database/sql"
"fmt"
"sync"
"time"
"gordenko.dev/dima/pretty"
"gordenko.dev/dima/qb/timeutil"
)
////////////////////////////
type MetricMeasure struct {
Time int64 `json:"t"`
Value float64 `json:"v"`
}
type MetricError struct {
Time int64 `json:"time"`
Code int64 `json:"code"`
}
type ReadingsFilter struct {
Since string `json:"since"`
Until string `json:"until"`
MetricIDs []int64
}
type MetricReadings struct {
MetricID int64
Measures []MetricMeasure
Errors []MetricError
}
// SelectReadingsInParallel - метод достает из БД данные всех метрик, которые связаны
// с объектом. Учитываются настройки объекта (LastDayOfMonth, FirstHourOfDay), а также
// фильтры выбранные пользователем на странице объекта (Since, Until, GroupBy).
// ВАЖНО!
// Запросы по каждой метрике отправляются в БД в отдельной горутине, что позволяет
// в разы ускорить получение данных по объекту.
func (s *MeasureQueryEngine) SelectReadingsInParallel(req ReadingsFilter) (result []MetricReadings, err error) {
pretty.PPrintln("SelectReadingsInParallel", req)
if len(req.MetricIDs) == 0 {
return
}
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))
fmt.Printf(`SelectReadingsInParallel: {
Since: %s
Until: %s
MetricIDs: %v
}
`, since, until, req.MetricIDs)
// FIX проверить как добавляются часы в DST часовых поясах
// Для req.FirstHourOfDay=4 since должен быть 4:00:00, until должен быть
// 3:59:59 следующего дня
sinceUnixtime := since.Unix()
untilUnixtime := until.Unix()
if sinceUnixtime >= untilUnixtime {
err = fix.Error(WrongDateRange)
return
}
// Важно вирутальная метрика зависит от реальных, которые к объекту могут быть
// привязаны или нет. Если нет - не возвращать.
var (
tasks []*MetricReadingsTask
wg = new(sync.WaitGroup)
)
for _, metricID := range req.MetricIDs {
task := &MetricReadingsTask{
In: MetricMeasuresFilter{
MetricID: metricID,
Since: sinceUnixtime,
Until: untilUnixtime,
},
WaitGroup: wg,
}
wg.Add(1)
s.metricReadingsCh <- 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
}
result = append(result, task.Result)
}
return
}
// ListInstantMeasures - cписок показаний для юнитов без накопительного итога
// (например, Давление)
func (s *MeasureQueryEngine) listMetricReadings(req MetricMeasuresFilter) (result MetricReadings, err error) {
result.MetricID = req.MetricID
// buf, _ := json.MarshalIndent(req, "", " ")
// fmt.Printf("listInstantMeasures: %s\n\n", buf)
tx, err := s.db.Driver().Begin()
if err != nil {
return
}
defer tx.Rollback()
rows, err := tx.Query(`
SELECT tm, value
FROM f64
WHERE metricID=? AND tm BETWEEN ? AND ?
ORDER BY tm ASC`,
req.MetricID, req.Since, req.Until)
if err != nil {
if err == sql.ErrNoRows {
err = nil
}
return
}
defer rows.Close()
for rows.Next() {
var measure MetricMeasure
err = rows.Scan(&measure.Time, &measure.Value)
if err != nil {
return
}
// fix - для cumulative метрик учет поправок
result.Measures = append(result.Measures, measure)
}
if err = rows.Err(); err != nil {
return
}
//for _, m := range result {
// fmt.Printf("raw: %d\t%f\n", m.Time, m.Value)
//}
//fmt.Printf("listInstantMeasures (qty): %d\n", len(result))
return
}