github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/db/metric_bucket.go (about)

     1  // This file is part of the Smart Home
     2  // Program complex distribution https://github.com/e154/smart-home
     3  // Copyright (C) 2016-2023, Filippov Alex
     4  //
     5  // This library is free software: you can redistribute it and/or
     6  // modify it under the terms of the GNU Lesser General Public
     7  // License as published by the Free Software Foundation; either
     8  // version 3 of the License, or (at your option) any later version.
     9  //
    10  // This library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13  // Library General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public
    16  // License along with this library.  If not, see
    17  // <https://www.gnu.org/licenses/>.
    18  
    19  package db
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"fmt"
    25  	"time"
    26  
    27  	"github.com/pkg/errors"
    28  	"gorm.io/gorm"
    29  
    30  	"github.com/e154/smart-home/common"
    31  	"github.com/e154/smart-home/common/apperr"
    32  )
    33  
    34  // MetricBuckets ...
    35  type MetricBuckets struct {
    36  	Db        *gorm.DB
    37  	Timescale bool
    38  }
    39  
    40  // MetricBucket ...
    41  type MetricBucket struct {
    42  	Value    json.RawMessage `gorm:"type:jsonb;not null"`
    43  	Metric   *Metric
    44  	Mins     time.Time `gorm:"->"`
    45  	MetricId int64
    46  	Time     time.Time
    47  }
    48  
    49  // TableName ...
    50  func (d *MetricBucket) TableName() string {
    51  	return "metric_bucket"
    52  }
    53  
    54  // Add ...
    55  func (n MetricBuckets) Add(ctx context.Context, metric *MetricBucket) (err error) {
    56  	if err = n.Db.WithContext(ctx).Create(&metric).Error; err != nil {
    57  		err = errors.Wrap(apperr.ErrMetricBucketAdd, err.Error())
    58  	}
    59  	return
    60  }
    61  
    62  // List ...
    63  func (n *MetricBuckets) List(ctx context.Context, metricId int64, optionItems []string, rFrom, rTo *time.Time, metricRange *common.MetricRange) (list []*MetricBucket, err error) {
    64  
    65  	var str string
    66  	for i, item := range optionItems {
    67  		str += fmt.Sprintf(" '%s', trunc(avg((value ->> '%[1]s')::numeric), 2)", item)
    68  		if i+1 < len(optionItems) {
    69  			str += ","
    70  		}
    71  	}
    72  
    73  	var interval string
    74  	var from time.Time
    75  	var to = time.Now()
    76  
    77  	if rTo != nil {
    78  		to = common.TimeValue(rTo)
    79  	}
    80  
    81  	if metricRange != nil {
    82  		switch *metricRange {
    83  		case common.MetricRange6H:
    84  			interval = "6 seconds"
    85  			from = to.Add(-6 * time.Hour)
    86  		case common.MetricRange12H:
    87  			interval = "12 seconds"
    88  			from = to.Add(-12 * time.Hour)
    89  		case common.MetricRange24H:
    90  			interval = "24 seconds"
    91  			from = to.Add(-24 * time.Hour)
    92  		case common.MetricRange7d:
    93  			interval = "150 seconds"
    94  			from = to.Add(-24 * 7 * time.Hour)
    95  		case common.MetricRange30d, common.MetricRange1m:
    96  			interval = "7 minutes"
    97  			from = to.Add(-24 * 30 * time.Hour)
    98  		default:
    99  			err = errors.Wrap(apperr.ErrMetricBucketGet, fmt.Sprintf("unknown filter %s", metricRange))
   100  			return
   101  		}
   102  	}
   103  
   104  	if rFrom != nil {
   105  		from = common.TimeValue(rFrom)
   106  	}
   107  
   108  	var num int64 = 1
   109  	t := from.Sub(to).Seconds()
   110  	if t > 3600 {
   111  		num = int64(t / 3600)
   112  	}
   113  
   114  	if metricRange == nil {
   115  		interval = fmt.Sprintf("%d seconds", num)
   116  	}
   117  
   118  	list = make([]*MetricBucket, 0)
   119  
   120  	// c.time between ? and ?
   121  	if n.Timescale {
   122  		var q = `SELECT time_bucket('%s', time) AS mins, json_build_object(%s) as value
   123  FROM metric_bucket c
   124  WHERE c.metric_id = ? and c.time > ? and c.time < ? 
   125  GROUP BY mins
   126  ORDER BY mins ASC
   127  LIMIT 3600`
   128  		if err = n.Db.WithContext(ctx).Raw(fmt.Sprintf(q, interval, str), metricId, from.UTC().Format(time.RFC3339), to.UTC().Format(time.RFC3339)).Scan(&list).Error; err != nil {
   129  			err = errors.Wrap(apperr.ErrMetricBucketGet, err.Error())
   130  		}
   131  
   132  		for _, metric := range list {
   133  			metric.Time = metric.Mins
   134  		}
   135  
   136  		return
   137  	}
   138  
   139  	q := `SELECT TIMESTAMP WITH TIME ZONE 'epoch' +
   140         INTERVAL '1 second' * round(extract('epoch' from time) / %[1]d) * %[1]d as time, json_build_object(%s) as value
   141  FROM metric_bucket c
   142  WHERE c.metric_id = ? and c.time > ? and c.time < ? 
   143  GROUP BY round(extract('epoch' from c.time) / %[1]d)
   144  order by time asc
   145  LIMIT 3600`
   146  	if err = n.Db.WithContext(ctx).Raw(fmt.Sprintf(q, num, str), metricId, from.UTC().Format(time.RFC3339), to.UTC().Format(time.RFC3339)).Scan(&list).Error; err != nil {
   147  		err = errors.Wrap(apperr.ErrMetricBucketGet, err.Error())
   148  	}
   149  
   150  	return
   151  }
   152  
   153  // DeleteOldest ...
   154  func (n *MetricBuckets) DeleteOldest(ctx context.Context, days int) (err error) {
   155  	bucket := &MetricBucket{}
   156  	if err = n.Db.WithContext(ctx).Last(&bucket).Error; err != nil {
   157  		err = errors.Wrap(apperr.ErrLogDelete, err.Error())
   158  		return
   159  	}
   160  	err = n.Db.WithContext(ctx).Delete(&MetricBucket{},
   161  		fmt.Sprintf(`time < CAST('%s' AS DATE) - interval '%d days'`,
   162  			bucket.Time.UTC().Format("2006-01-02 15:04:05"), days)).Error
   163  	if err != nil {
   164  		err = errors.Wrap(apperr.ErrMetricBucketDelete, err.Error())
   165  	}
   166  	return
   167  }
   168  
   169  // DeleteByMetricId ...
   170  func (n MetricBuckets) DeleteByMetricId(ctx context.Context, metricId int64) (err error) {
   171  	if err = n.Db.WithContext(ctx).Delete(&MetricBucket{}, "metric_id = ?", metricId).Error; err != nil {
   172  		err = errors.Wrap(apperr.ErrMetricBucketDelete, err.Error())
   173  	}
   174  	return
   175  }
   176  
   177  // DeleteById ...
   178  func (n MetricBuckets) DeleteById(ctx context.Context, id int64) (err error) {
   179  	if err = n.Db.WithContext(ctx).Delete(&MetricBucket{}, "id = ?", id).Error; err != nil {
   180  		err = errors.Wrap(apperr.ErrMetricBucketDelete, err.Error())
   181  	}
   182  	return
   183  }
   184  
   185  // AddMultiple ...
   186  func (n *MetricBuckets) AddMultiple(ctx context.Context, buckets []*MetricBucket) (err error) {
   187  	if err = n.Db.WithContext(ctx).Create(&buckets).Error; err != nil {
   188  		err = errors.Wrap(apperr.ErrMetricBucketAdd, err.Error())
   189  	}
   190  	return
   191  }