227 lines
4.7 KiB
Go
227 lines
4.7 KiB
Go
package mqe
|
||
|
||
import (
|
||
"database/sql"
|
||
"fmt"
|
||
"time"
|
||
)
|
||
|
||
type CurrentValue struct {
|
||
MetricID uint32
|
||
Time int64 `json:"t"`
|
||
Value float64 `json:"v"`
|
||
}
|
||
|
||
func (s *MeasureQueryEngine) ListCurrentValues(metricIDs []uint32) (result []CurrentValue, err error) {
|
||
driver := s.db.Driver()
|
||
|
||
for _, metricID := range metricIDs {
|
||
var rows *sql.Rows
|
||
rows, err = driver.Query(`
|
||
SELECT tm, value
|
||
FROM f64
|
||
WHERE metricID=?
|
||
ORDER BY tm DESC
|
||
LIMIT 1`,
|
||
metricID)
|
||
if err != nil {
|
||
if err == sql.ErrNoRows {
|
||
err = nil
|
||
}
|
||
return
|
||
}
|
||
|
||
for rows.Next() {
|
||
var (
|
||
tm int64
|
||
value float64
|
||
)
|
||
err = rows.Scan(&tm, &value)
|
||
if err != nil {
|
||
return
|
||
}
|
||
result = append(result, CurrentValue{
|
||
MetricID: metricID,
|
||
Time: tm,
|
||
Value: value,
|
||
})
|
||
}
|
||
|
||
if err = rows.Err(); err != nil {
|
||
return
|
||
}
|
||
rows.Close()
|
||
}
|
||
return
|
||
}
|
||
|
||
// ListInstantMeasures - cписок показаний мгновенных метрик (Температура, Давление, Расход)
|
||
// за за интервал без группировки
|
||
func (s *MeasureQueryEngine) listInstantMeasures(req MetricMeasuresFilter) (_ []Measure, err error) {
|
||
rows, err := s.db.Driver().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()
|
||
|
||
var result []Measure
|
||
|
||
for rows.Next() {
|
||
var (
|
||
tm int64
|
||
value float64
|
||
)
|
||
err = rows.Scan(&tm, &value)
|
||
if err != nil {
|
||
return
|
||
}
|
||
result = append(result, Measure{
|
||
Time: tm,
|
||
Values: []float64{
|
||
value,
|
||
},
|
||
})
|
||
}
|
||
|
||
if err = rows.Err(); err != nil {
|
||
return
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
type AggregatedInstantMeasuresIn struct {
|
||
MetricID int64 `json:"metricID"`
|
||
Since int64 `json:"since"`
|
||
Until int64 `json:"until"`
|
||
GroupBy GroupBy `json:"groupBy"`
|
||
Flags byte `json:"flags"` // из настроек по умолчанию либо из запроса
|
||
LastDayOfMonth int `json:"lastDayOfMonth"` // например, конец месяца 25 число
|
||
FirstHourOfDay int `json:"firstHourOfDay"` // например день начинается в 6:00
|
||
}
|
||
|
||
// Заполняет поля Period и Values (от 1 до 3 значений) в структуре AggregatedMeasure
|
||
func (s *MeasureQueryEngine) listAggregatedInstantMeasures(req AggregatedInstantMeasuresIn) (_ []AggregatedMeasure, err error) {
|
||
var (
|
||
groupByFormat string
|
||
parsePeriodLayout string
|
||
|
||
result []AggregatedMeasure
|
||
)
|
||
|
||
switch req.GroupBy {
|
||
case ByHour:
|
||
groupByFormat = "%Y%m%d%H"
|
||
parsePeriodLayout = hourPeriodLayout
|
||
|
||
case ByDay:
|
||
groupByFormat = "%Y%m%d"
|
||
parsePeriodLayout = dayPeriodLayout
|
||
|
||
case ByMonth:
|
||
groupByFormat = "%Y%m"
|
||
parsePeriodLayout = monthPeriodLayout
|
||
|
||
default:
|
||
// Защита от дурака
|
||
err = fmt.Errorf("unknown GroupBy: %s", req.GroupBy)
|
||
return
|
||
}
|
||
|
||
valuesQty := 0
|
||
values := ""
|
||
|
||
if (req.Flags & AggregateMin) == AggregateMin {
|
||
values += " MIN(value),"
|
||
valuesQty++
|
||
}
|
||
|
||
if (req.Flags & AggregateMax) == AggregateMax {
|
||
values += " MAX(value),"
|
||
valuesQty++
|
||
}
|
||
|
||
if (req.Flags & AggregateAvg) == AggregateAvg {
|
||
values += " AVG(value),"
|
||
valuesQty++
|
||
}
|
||
|
||
dt, err := getPeriodDateTime(req.GroupBy, req.LastDayOfMonth, req.FirstHourOfDay)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
query := fmt.Sprintf(`
|
||
SELECT%s DATE_FORMAT(%s, '%s') as periodStr
|
||
FROM f64
|
||
WHERE metricID=? AND tm BETWEEN ? AND ?
|
||
GROUP BY periodStr
|
||
ORDER BY periodStr ASC`,
|
||
values, dt, groupByFormat)
|
||
|
||
rows, err := s.db.Driver().Query(query, req.MetricID, req.Since, req.Until)
|
||
if err != nil {
|
||
if err == sql.ErrNoRows {
|
||
err = nil
|
||
}
|
||
return
|
||
}
|
||
|
||
for rows.Next() {
|
||
var (
|
||
m = AggregatedMeasure{
|
||
Values: make([]float64, valuesQty),
|
||
}
|
||
|
||
periodStr string
|
||
period time.Time
|
||
|
||
valueIdx int
|
||
dest []interface{}
|
||
)
|
||
|
||
if (req.Flags & AggregateMin) == AggregateMin {
|
||
dest = append(dest, &m.Values[valueIdx])
|
||
valueIdx++
|
||
}
|
||
|
||
if (req.Flags & AggregateMax) == AggregateMax {
|
||
dest = append(dest, &m.Values[valueIdx])
|
||
valueIdx++
|
||
}
|
||
|
||
if (req.Flags & AggregateAvg) == AggregateAvg {
|
||
dest = append(dest, &m.Values[valueIdx])
|
||
}
|
||
|
||
dest = append(dest, &periodStr)
|
||
|
||
err = rows.Scan(dest...)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
period, err = time.ParseInLocation(parsePeriodLayout, periodStr, s.location)
|
||
if err != nil {
|
||
err = fmt.Errorf("time.ParseInLocation: %s; layout=%q; str=%q",
|
||
err, parsePeriodLayout, periodStr)
|
||
return
|
||
}
|
||
m.Period = period.Unix()
|
||
|
||
result = append(result, m)
|
||
}
|
||
|
||
if err = rows.Err(); err != nil {
|
||
return
|
||
}
|
||
return result, nil
|
||
}
|