fix(monitor): make simple-query API adapt to victoriametrics

This commit is contained in:
Zexi Li
2023-12-07 18:01:31 +08:00
parent 8fa8266009
commit 92b35fa44a
29 changed files with 590 additions and 446 deletions
+60 -51
View File
@@ -14,50 +14,12 @@
package monitor
type DataSourceConfig struct {
Id string
Name string
Driver string
Config interface{}
}
import "fmt"
type MetricResource struct {
// Type is the metric resource type. e.g: host, vm, lbinstance
Type string `json:"type"`
// ConfigId is the data source config id
ConfigId string `json:"config_id"`
}
type Metric struct {
Resource MetricResource `json:"resource"`
Measurement string `json:"measurement"`
Field string `json:"field"`
DisplayName string `json:"displayname"`
}
type TimeSeries struct {
Results []TimeSeriesResult `json:"results"`
}
type TimeSeriesResult struct {
Series []TimeSeriesRow `json:"series"`
}
type TimeSeriesRow struct {
Metric Metric `json:"metric"`
Tags map[string]string `json:"tags,omitempty"`
Columns []string `json:"columns,omitempty"`
// Value item is a point with timestamp and value
Values [][]interface{} `json:"values,omitempty"`
}
type MetricRequest struct {
// The start time for the query
From string `json:"from"`
// An end time for the query
To string `json:"to"`
Queries []*MetricQuery `json:"queries"`
Debug bool `json:"debug"`
type Condition struct {
Type string `json:"type"`
Params []float64 `json:"params"`
Operators []string `json:"operators"`
}
type MetricQueryTag struct {
@@ -72,6 +34,61 @@ type MetricQueryPart struct {
Params []string `json:"params"`
}
func NewMetricQueryPartField(fieldName string) MetricQueryPart {
return MetricQueryPart{
Type: "field",
Params: []string{fieldName},
}
}
func NewMetricQueryPartAS(alias string) MetricQueryPart {
return MetricQueryPart{
Type: "alias",
Params: []string{alias},
}
}
func NewMetricQueryPartMath(op string, val string) MetricQueryPart {
return MetricQueryPart{
Type: "math",
Params: []string{fmt.Sprintf("%s %s", op, val)},
}
}
func NewMetricQueryPartFunc(funcName string) MetricQueryPart {
return MetricQueryPart{
Type: funcName,
}
}
func NewMetricQueryPartMean() MetricQueryPart {
return NewMetricQueryPartFunc("mean")
}
func NewMetricQueryPartCount() MetricQueryPart {
return NewMetricQueryPartFunc("count")
}
func NewMetricQueryPartDistinct() MetricQueryPart {
return NewMetricQueryPartFunc("distinct")
}
func NewMetricQueryPartSum() MetricQueryPart {
return NewMetricQueryPartFunc("sum")
}
func NewMetricQueryPartMin() MetricQueryPart {
return NewMetricQueryPartFunc("min")
}
func NewMetricQueryPartMax() MetricQueryPart {
return NewMetricQueryPartFunc("max")
}
func NewMetricQueryPartLast() MetricQueryPart {
return NewMetricQueryPartFunc("last")
}
type MetricQuerySelect []MetricQueryPart
func NewMetricQuerySelect(parts ...MetricQueryPart) MetricQuerySelect {
@@ -90,11 +107,3 @@ type MetricQuery struct {
Policy string `json:"policy"`
ResultFormat string `json:"result_format"`
}
type AlertConditionCombiner string
type Condition struct {
Type string `json:"type"`
Params []float64 `json:"params"`
Operators []string `json:"operators"`
}
+15
View File
@@ -0,0 +1,15 @@
package monitor
import (
"testing"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
)
func TestMetricQueryInput_AddMetric(t *testing.T) {
q := NewMetricQuery("cpu")
q.Select("usage_active").MEAN()
q.Select("usage_active_per_core")
log.Infof("%s", jsonutils.Marshal(q))
}
@@ -14,7 +14,10 @@
package monitor
import "time"
import (
"strconv"
"time"
)
var (
UNIFIED_MONITOR_FIELD_OPT_TYPE = []string{"Aggregations", "Selectors"}
@@ -70,23 +73,6 @@ type MetricFunc struct {
GroupOptValue map[string][]string `json:"group_opt_value"`
}
type MetricInputQuery struct {
From string `json:"from"`
To string `json:"to"`
Scope string `json:"scope"`
Slimit string `json:"slimit"`
Soffset string `json:"soffset"`
//default group by
Unit bool `json:"unit"`
Interval string `json:"interval"`
DomainId string `json:"domain_id"`
ProjectId string `json:"project_id"`
MetricQuery []*AlertQuery `json:"metric_query"`
Signature string `json:"signature"`
ShowMeta bool `json:"show_meta"`
SkipCheckSeries bool `json:"skip_check_series"`
}
type SimpleQueryInput struct {
// 资源Id, 可以不填, 代表查询指定监控的所有监控数据
Id string `json:"id"`
@@ -108,3 +94,103 @@ type SimpleQueryOutput struct {
Time time.Time `json:"time"`
Value float64 `json:"value"`
}
type MetricsQueryResult struct {
SeriesTotal int64
Series TimeSeriesSlice
Metas []QueryResultMeta
}
type TimeSeriesPoints []TimePoint
type TimeSeriesSlice []*TimeSeries
type TimeSeries struct {
// RawName is used to frontend displaying the curve name
RawName string `json:"raw_name"`
Columns []string `json:"columns"`
Name string `json:"name"`
Points TimeSeriesPoints `json:"points"`
Tags map[string]string `json:"tags,omitempty"`
}
type TimePoint []interface{}
func NewTimePoint(value *float64, timestamp float64) TimePoint {
return TimePoint{value, timestamp}
}
func NewTimePointByVal(value float64, timestamp float64) TimePoint {
return NewTimePoint(&value, timestamp)
}
func (p TimePoint) IsValid() bool {
if val, ok := p[0].(*float64); ok && val != nil {
return true
}
return false
//return p[0].(*float64) != nil
}
func (p TimePoint) IsValids() bool {
for i := 0; i < len(p)-1; i++ {
if p[i] == nil {
return false
}
if p[i].(*float64) == nil {
return false
}
}
return true
}
func (p TimePoint) Value() float64 {
return *(p[0].(*float64))
}
func (p TimePoint) Timestamp() float64 {
return p[len(p)-1].(float64)
}
func (p TimePoint) Values() []float64 {
values := make([]float64, 0)
for i := 0; i < len(p)-1; i++ {
values = append(values, *(p[i].(*float64)))
}
return values
}
func (p TimePoint) PointValueStr() []string {
arrStr := make([]string, 0)
for i := 0; i < len(p)-1; i++ {
if p[i] == nil {
arrStr = append(arrStr, "")
}
if fval, ok := p[i].(*float64); ok {
arrStr = append(arrStr, strconv.FormatFloat((*fval), 'f', -1, 64))
continue
}
if ival, ok := p[i].(*int64); ok {
arrStr = append(arrStr, strconv.FormatInt((*ival), 64))
continue
}
arrStr = append(arrStr, p[i].(string))
}
return arrStr
}
func NewTimeSeriesPointsFromArgs(values ...float64) TimeSeriesPoints {
points := make(TimeSeriesPoints, 0)
for i := 0; i < len(values); i += 2 {
points = append(points, NewTimePoint(&values[i], values[i+1]))
}
return points
}
type QueryResultMeta struct {
RawQuery string `json:"raw_query"`
}
const ConditionTypeMetricQuery = "metricquery"
@@ -0,0 +1,47 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package monitor
import (
"crypto/sha256"
"fmt"
"yunion.io/x/jsonutils"
)
type MetricQueryInput struct {
From string `json:"from"`
To string `json:"to"`
Scope string `json:"scope"`
Slimit string `json:"slimit"`
Soffset string `json:"soffset"`
Unit bool `json:"unit"`
Interval string `json:"interval"`
DomainId string `json:"domain_id"`
ProjectId string `json:"project_id"`
MetricQuery []*AlertQuery `json:"metric_query"`
Signature string `json:"signature"`
ShowMeta bool `json:"show_meta"`
SkipCheckSeries bool `json:"skip_check_series"`
}
const (
QUERY_SIGNATURE_KEY = "signature"
)
func DigestQuerySignature(data *jsonutils.JSONDict) string {
data.Remove(QUERY_SIGNATURE_KEY)
return fmt.Sprintf("%x", sha256.Sum256([]byte(data.String())))
}
@@ -0,0 +1 @@
package monitor
+100 -25
View File
@@ -19,6 +19,7 @@ import (
"strings"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/utils"
"yunion.io/x/onecloud/pkg/apis/monitor"
@@ -407,10 +408,7 @@ func (s *AlertQuerySelects) Select(fieldName string) *AlertQuerySelect {
s.parts = make([]*AlertQuerySelect, 0)
}
sel := make([]monitor.MetricQueryPart, 0)
sel = append(sel, monitor.MetricQueryPart{
Type: "field",
Params: []string{fieldName},
})
sel = append(sel, monitor.NewMetricQueryPartField(fieldName))
part := &AlertQuerySelect{sel}
s.parts = append(s.parts, part)
return part
@@ -424,58 +422,48 @@ func (s *AlertQuerySelects) ToSelects() []monitor.MetricQuerySelect {
return ret
}
func (s *AlertQuerySelect) addFunc(funcName string) *AlertQuerySelect {
s.MetricQuerySelect = append(s.MetricQuerySelect, monitor.MetricQueryPart{
Type: funcName,
})
func (s *AlertQuerySelect) addPart(p monitor.MetricQueryPart) *AlertQuerySelect {
s.MetricQuerySelect = append(s.MetricQuerySelect, p)
return s
}
// Aggregations method
func (s *AlertQuerySelect) MEAN() *AlertQuerySelect {
return s.addFunc("mean")
return s.addPart(monitor.NewMetricQueryPartMean())
}
func (s *AlertQuerySelect) COUNT() *AlertQuerySelect {
return s.addFunc("count")
return s.addPart(monitor.NewMetricQueryPartCount())
}
func (s *AlertQuerySelect) DISTINCT() *AlertQuerySelect {
return s.addFunc("distinct")
return s.addPart(monitor.NewMetricQueryPartDistinct())
}
func (s *AlertQuerySelect) SUM() *AlertQuerySelect {
return s.addFunc("sum")
return s.addPart(monitor.NewMetricQueryPartSum())
}
func (s *AlertQuerySelect) MIN() *AlertQuerySelect {
return s.addFunc("min")
return s.addPart(monitor.NewMetricQueryPartMin())
}
func (s *AlertQuerySelect) MAX() *AlertQuerySelect {
return s.addFunc("max")
return s.addPart(monitor.NewMetricQueryPartMax())
}
func (s *AlertQuerySelect) LAST() *AlertQuerySelect {
return s.addFunc("last")
return s.addPart(monitor.NewMetricQueryPartLast())
}
// AS is alias method
func (s *AlertQuerySelect) AS(alias string) *AlertQuerySelect {
s.MetricQuerySelect = append(s.MetricQuerySelect, monitor.MetricQueryPart{
Type: "alias",
Params: []string{alias},
})
return s
return s.addPart(monitor.NewMetricQueryPartAS(alias))
}
// MATH method
func (s *AlertQuerySelect) MATH(op string, val string) *AlertQuerySelect {
s.MetricQuerySelect = append(s.MetricQuerySelect, monitor.MetricQueryPart{
Type: "math",
Params: []string{fmt.Sprintf("%s %s", op, val)},
})
return s
return s.addPart(monitor.NewMetricQueryPartMath(op, val))
}
type AlertQueryWhere struct {
@@ -595,3 +583,90 @@ func (g *AlertQueryGroupBy) FILL(val string) *AlertQueryGroupBy {
func (g *AlertQueryGroupBy) ToGroupBy() []monitor.MetricQueryPart {
return g.parts
}
type MetricQueryInput struct {
from string `json:"from"`
to string `json:"to"`
scope string `json:"scope"`
slimit string `json:"slimit"`
soffset string `json:"soffset"`
unit bool `json:"unit"`
interval string `json:"interval"`
domainId string `json:"domain_id"`
projectId string `json:"project_id"`
showMeta bool `json:"show_meta"`
skipCheckSeries bool `json:"skip_check_series"`
query *AlertQuery
}
func NewMetricQueryInput(measurement string) *MetricQueryInput {
return NewMetricQueryInputWithDB("", measurement)
}
func NewMetricQueryInputWithDB(db string, measurement string) *MetricQueryInput {
return &MetricQueryInput{
query: NewAlertQuery(db, measurement),
}
}
func (input *MetricQueryInput) Interval(interval string) *MetricQueryInput {
input.interval = interval
return input
}
func (input *MetricQueryInput) From(from time.Time) *MetricQueryInput {
input.from = fmt.Sprintf("%d", from.UnixMilli())
return input
}
func (input *MetricQueryInput) To(to time.Time) *MetricQueryInput {
input.to = fmt.Sprintf("%d", to.UnixMilli())
return input
}
func (input *MetricQueryInput) Scope(scope string) *MetricQueryInput {
input.scope = scope
return input
}
func (input *MetricQueryInput) SkipCheckSeries(skip bool) *MetricQueryInput {
input.skipCheckSeries = skip
return input
}
func (input *MetricQueryInput) Selects() *AlertQuerySelects {
return input.query.Selects()
}
func (input *MetricQueryInput) Where() *AlertQueryWhere {
return input.query.Where()
}
func (input *MetricQueryInput) GroupBy() *AlertQueryGroupBy {
return input.query.GroupBy()
}
func (input *MetricQueryInput) ToQueryData() *monitor.MetricQueryInput {
data := &monitor.MetricQueryInput{
From: input.from,
To: input.to,
Scope: input.scope,
Slimit: input.slimit,
Soffset: input.soffset,
Unit: input.unit,
Interval: input.interval,
DomainId: input.domainId,
ProjectId: input.projectId,
ShowMeta: input.showMeta,
SkipCheckSeries: input.skipCheckSeries,
}
data.MetricQuery = []*monitor.AlertQuery{
{Model: input.query.ToMetricQuery()},
}
jsonData := jsonutils.Marshal(data).(*jsonutils.JSONDict)
digest := monitor.DigestQuerySignature(jsonData)
data.Signature = digest
return data
}
@@ -15,6 +15,8 @@
package monitor
import (
"yunion.io/x/onecloud/pkg/apis/monitor"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
"yunion.io/x/onecloud/pkg/mcclient/modules"
)
@@ -44,3 +46,6 @@ func NewUnifiedMonitorManager() *SUnifiedMonitorManager {
ResourceManager: &man,
}
}
func (m *SUnifiedMonitorManager) PerformQuery(s *mcclient.ClientSession, input *monitor.MetricQueryInput) {
}
@@ -21,7 +21,6 @@ import (
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/apis/monitor"
"yunion.io/x/onecloud/pkg/monitor/tsdb"
)
type mathReducer struct {
@@ -40,7 +39,7 @@ func (s *mathReducer) GetType() string {
return s.Type
}
func (s *mathReducer) Reduce(series *tsdb.TimeSeries) (*float64, []string) {
func (s *mathReducer) Reduce(series *monitor.TimeSeries) (*float64, []string) {
if len(series.Points) == 0 {
return nil, nil
}
+24 -23
View File
@@ -38,7 +38,7 @@ import (
)
func init() {
mq.RegisterMetricQuery("metricquery", func(model []*monitor.AlertCondition) (mq.MetricQuery, error) {
mq.RegisterMetricQuery(monitor.ConditionTypeMetricQuery, func(model []*monitor.AlertCondition) (mq.MetricQuery, error) {
return NewMetricQueryCondition(model)
})
}
@@ -60,11 +60,11 @@ func NewMetricQueryCondition(models []*monitor.AlertCondition) (*MetricQueryCond
qc.Query.From = q.From
qc.Query.To = q.To
if err := validators.ValidateFromValue(qc.Query.From); err != nil {
return nil, errors.Wrapf(err, "from value %q", qc.Query.From)
return nil, errors.Wrapf(err, "validate from value %q", qc.Query.From)
}
if err := validators.ValidateToValue(qc.Query.To); err != nil {
return nil, errors.Wrapf(err, "to value %q", qc.Query.To)
return nil, errors.Wrapf(err, "validate to value %q", qc.Query.To)
}
qc.setResType()
cond.QueryCons = append(cond.QueryCons, *qc)
@@ -73,7 +73,7 @@ func NewMetricQueryCondition(models []*monitor.AlertCondition) (*MetricQueryCond
return cond, nil
}
func (query *MetricQueryCondition) ExecuteQuery(userCred mcclient.TokenCredential, skipCheckSeries bool) (*mq.Metrics, error) {
func (query *MetricQueryCondition) ExecuteQuery(userCred mcclient.TokenCredential, skipCheckSeries bool) (*monitor.MetricsQueryResult, error) {
firstCond := query.QueryCons[0]
timeRange := tsdb.NewTimeRange(firstCond.Query.From, firstCond.Query.To)
ctx := gocontext.Background()
@@ -87,23 +87,29 @@ func (query *MetricQueryCondition) ExecuteQuery(userCred mcclient.TokenCredentia
var qr *queryResult
var err error
queryInfluxdb := func() (*queryResult, error) {
ds, err := datasource.GetDefaultSource("")
if err != nil {
return nil, errors.Wrapf(err, "Can't find default datasource")
}
queryTSDB := func() (*queryResult, error) {
startTime := time.Now()
queryResult, err := query.executeQuery(&evalContext, timeRange)
queryResult, err := query.executeQuery(ds, &evalContext, timeRange)
if err != nil {
return nil, errors.Wrap(err, "query.executeQuery")
return nil, errors.Wrapf(err, "query.executeQuery from %s", ds.Type)
}
log.Debugf("query metrics from influxdb elapsed: %s", time.Since(startTime))
log.Debugf("query metrics from TSDB %q elapsed: %s", ds.Type, time.Since(startTime))
return queryResult, nil
}
if query.noCheckSeries(skipCheckSeries) {
qr, err = queryInfluxdb()
qr, err = queryTSDB()
if err != nil {
return nil, err
return nil, errors.Wrap(err, "queryTSDB")
}
metrics := mq.Metrics{
Series: make(tsdb.TimeSeriesSlice, 0),
metrics := monitor.MetricsQueryResult{
Series: make(monitor.TimeSeriesSlice, 0),
Metas: qr.metas,
}
metrics.Series = qr.series
@@ -114,7 +120,7 @@ func (query *MetricQueryCondition) ExecuteQuery(userCred mcclient.TokenCredentia
qRegionCh := make(chan bool, 0)
go func() {
qr, err = queryInfluxdb()
qr, err = queryTSDB()
qInfluxdbCh <- true
}()
@@ -163,8 +169,8 @@ func (query *MetricQueryCondition) ExecuteQuery(userCred mcclient.TokenCredentia
// firstCond.FillSerieByResourceField(resource, serie)
// metrics.Series = append(metrics.Series, serie)
//}
metrics := mq.Metrics{
Series: make(tsdb.TimeSeriesSlice, 0),
metrics := monitor.MetricsQueryResult{
Series: make(monitor.TimeSeriesSlice, 0),
Metas: qr.metas,
}
mtx := sync.Mutex{}
@@ -217,15 +223,10 @@ func (query *MetricQueryCondition) noCheckSeries(skipCheckSeries bool) bool {
return true
}
func (c *MetricQueryCondition) executeQuery(context *alerting.EvalContext, timeRange *tsdb.TimeRange) (*queryResult, error) {
ds, err := datasource.GetDefaultSource("")
if err != nil {
return nil, errors.Wrapf(err, "Can't find default datasource")
}
func (c *MetricQueryCondition) executeQuery(ds *tsdb.DataSource, context *alerting.EvalContext, timeRange *tsdb.TimeRange) (*queryResult, error) {
req := c.getRequestQuery(ds, timeRange, context.IsDebug)
result := make(tsdb.TimeSeriesSlice, 0)
metas := make([]tsdb.QueryResultMeta, 0)
result := make(monitor.TimeSeriesSlice, 0)
metas := make([]monitor.QueryResultMeta, 0)
if context.IsDebug {
setContextLog(context, req)
+12 -12
View File
@@ -66,7 +66,7 @@ type AlertQuery struct {
}
type FormatCond struct {
QueryMeta *tsdb.QueryResultMeta
QueryMeta *monitor.QueryResultMeta
QueryKeyInfo string
Reducer string
Evaluator AlertEvaluator
@@ -93,7 +93,7 @@ func GetFetchImpByDb(db string) iEvalMatchFetch {
return iFetchImp[db]
}
func (c *QueryCondition) GenerateFormatCond(meta *tsdb.QueryResultMeta, metric string) *FormatCond {
func (c *QueryCondition) GenerateFormatCond(meta *monitor.QueryResultMeta, metric string) *FormatCond {
return &FormatCond{
QueryMeta: meta,
QueryKeyInfo: metric,
@@ -186,7 +186,7 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.Conditio
if evalMatch {
evalMatchCount++
}
var meta *tsdb.QueryResultMeta
var meta *monitor.QueryResultMeta
if len(metas) > 0 {
//the relation metas with series is 1 to more
meta = &metas[0]
@@ -239,7 +239,7 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.Conditio
}, nil
}
func (c *QueryCondition) serieIsLatestResource(resources map[string]jsonutils.JSONObject, series *tsdb.TimeSeries) (bool, jsonutils.JSONObject) {
func (c *QueryCondition) serieIsLatestResource(resources map[string]jsonutils.JSONObject, series *monitor.TimeSeries) (bool, jsonutils.JSONObject) {
tagId := monitor.MEASUREMENT_TAG_ID[c.ResType]
if len(tagId) == 0 {
tagId = "host_id"
@@ -263,7 +263,7 @@ func (c *QueryCondition) serieIsLatestResource(resources map[string]jsonutils.JS
}
func (c *QueryCondition) FillSerieByResourceField(resource jsonutils.JSONObject,
series *tsdb.TimeSeries) {
series *monitor.TimeSeries) {
//startTime := time.Now()
//defer func() {
// log.Debugf("--FillSerieByResourceField: %s", time.Since(startTime))
@@ -278,8 +278,8 @@ func (c *QueryCondition) FillSerieByResourceField(resource jsonutils.JSONObject,
}
}
func (c *QueryCondition) NewEvalMatch(context *alerting.EvalContext, series tsdb.TimeSeries,
meta *tsdb.QueryResultMeta, value *float64, valStrArr []string) (*monitor.EvalMatch, error) {
func (c *QueryCondition) NewEvalMatch(context *alerting.EvalContext, series monitor.TimeSeries,
meta *monitor.QueryResultMeta, value *float64, valStrArr []string) (*monitor.EvalMatch, error) {
evalMatch := new(monitor.EvalMatch)
alertDetails, err := c.GetCommonAlertDetails(context)
if err != nil {
@@ -351,7 +351,7 @@ func (c *QueryCondition) GetCommonAlertDetails(context *alerting.EvalContext) (*
return alertDetails, nil
}
func (c *QueryCondition) jointPointStr(series tsdb.TimeSeries, value string, valStrArr []string) string {
func (c *QueryCondition) jointPointStr(series monitor.TimeSeries, value string, valStrArr []string) string {
str := ""
for i := 0; i < len(valStrArr); i++ {
if i == 0 {
@@ -364,14 +364,14 @@ func (c *QueryCondition) jointPointStr(series tsdb.TimeSeries, value string, val
}
type queryResult struct {
series tsdb.TimeSeriesSlice
metas []tsdb.QueryResultMeta
series monitor.TimeSeriesSlice
metas []monitor.QueryResultMeta
}
func (c *QueryCondition) executeQuery(evalCtx *alerting.EvalContext, timeRange *tsdb.TimeRange) (*queryResult, error) {
req := c.getRequestForAlertRule(timeRange, evalCtx.IsDebug)
result := make(tsdb.TimeSeriesSlice, 0)
metas := make([]tsdb.QueryResultMeta, 0)
result := make(monitor.TimeSeriesSlice, 0)
metas := make([]monitor.QueryResultMeta, 0)
if evalCtx.IsDebug {
data := jsonutils.NewDict()
+3 -4
View File
@@ -22,12 +22,11 @@ import (
"yunion.io/x/pkg/utils"
"yunion.io/x/onecloud/pkg/apis/monitor"
"yunion.io/x/onecloud/pkg/monitor/tsdb"
"yunion.io/x/onecloud/pkg/monitor/validators"
)
type Reducer interface {
Reduce(series *tsdb.TimeSeries) (*float64, []string)
Reduce(series *monitor.TimeSeries) (*float64, []string)
GetType() string
GetParams() []float64
}
@@ -48,7 +47,7 @@ func (s *queryReducer) GetType() string {
return s.Type
}
func (s *queryReducer) Reduce(series *tsdb.TimeSeries) (*float64, []string) {
func (s *queryReducer) Reduce(series *monitor.TimeSeries) (*float64, []string) {
if len(series.Points) == 0 {
return nil, nil
}
@@ -181,7 +180,7 @@ func newSimpleReducerByType(typ string) *queryReducer {
}
}
func calculateDiff(series *tsdb.TimeSeries, allNull bool, value float64, fn func(float64, float64) float64) (bool, float64) {
func calculateDiff(series *monitor.TimeSeries, allNull bool, value float64, fn func(float64, float64) float64) (bool, float64) {
var (
points = series.Points
first float64
+29 -29
View File
@@ -19,7 +19,7 @@ import (
. "github.com/smartystreets/goconvey/convey"
"yunion.io/x/onecloud/pkg/monitor/tsdb"
"yunion.io/x/onecloud/pkg/apis/monitor"
)
func TestSimpleReducer(t *testing.T) {
@@ -67,16 +67,16 @@ func TestSimpleReducer(t *testing.T) {
Convey("median should ignore null values", func() {
reducer := newSimpleReducerByType("median")
series := &tsdb.TimeSeries{
series := &monitor.TimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(nil, 1))
series.Points = append(series.Points, tsdb.NewTimePoint(nil, 2))
series.Points = append(series.Points, tsdb.NewTimePoint(nil, 3))
series.Points = append(series.Points, tsdb.NewTimePointByVal(1, 4))
series.Points = append(series.Points, tsdb.NewTimePointByVal(2, 5))
series.Points = append(series.Points, tsdb.NewTimePointByVal(3, 6))
series.Points = append(series.Points, monitor.NewTimePoint(nil, 1))
series.Points = append(series.Points, monitor.NewTimePoint(nil, 2))
series.Points = append(series.Points, monitor.NewTimePoint(nil, 3))
series.Points = append(series.Points, monitor.NewTimePointByVal(1, 4))
series.Points = append(series.Points, monitor.NewTimePointByVal(2, 5))
series.Points = append(series.Points, monitor.NewTimePointByVal(3, 6))
result, _ := reducer.Reduce(series)
So(result, ShouldNotBeNil)
@@ -91,14 +91,14 @@ func TestSimpleReducer(t *testing.T) {
Convey("count_non_null", func() {
Convey("with null values and real values", func() {
reducer := newSimpleReducerByType("count_non_null")
series := &tsdb.TimeSeries{
series := &monitor.TimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(nil, 1))
series.Points = append(series.Points, tsdb.NewTimePoint(nil, 2))
series.Points = append(series.Points, tsdb.NewTimePointByVal(3, 3))
series.Points = append(series.Points, tsdb.NewTimePointByVal(4, 4))
series.Points = append(series.Points, monitor.NewTimePoint(nil, 1))
series.Points = append(series.Points, monitor.NewTimePoint(nil, 2))
series.Points = append(series.Points, monitor.NewTimePointByVal(3, 3))
series.Points = append(series.Points, monitor.NewTimePointByVal(4, 4))
reduce, _ := reducer.Reduce(series)
So(reduce, ShouldNotBeNil)
So(*reduce, ShouldEqual, 2)
@@ -106,12 +106,12 @@ func TestSimpleReducer(t *testing.T) {
Convey("with null values", func() {
reducer := newSimpleReducerByType("count_non_null")
series := &tsdb.TimeSeries{
series := &monitor.TimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(nil, 1))
series.Points = append(series.Points, tsdb.NewTimePoint(nil, 2))
series.Points = append(series.Points, monitor.NewTimePoint(nil, 1))
series.Points = append(series.Points, monitor.NewTimePoint(nil, 2))
reduce, _ := reducer.Reduce(series)
So(reduce, ShouldBeNil)
})
@@ -119,14 +119,14 @@ func TestSimpleReducer(t *testing.T) {
Convey("avg of number values and null values should ignore nulls", func() {
reduer := newSimpleReducerByType("avg")
series := &tsdb.TimeSeries{
series := &monitor.TimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(nil, 1))
series.Points = append(series.Points, tsdb.NewTimePoint(nil, 2))
series.Points = append(series.Points, tsdb.NewTimePoint(nil, 3))
series.Points = append(series.Points, tsdb.NewTimePointByVal(3, 4))
series.Points = append(series.Points, monitor.NewTimePoint(nil, 1))
series.Points = append(series.Points, monitor.NewTimePoint(nil, 2))
series.Points = append(series.Points, monitor.NewTimePoint(nil, 3))
series.Points = append(series.Points, monitor.NewTimePointByVal(3, 4))
reduce, _ := reduer.Reduce(series)
So(*reduce, ShouldEqual, 3)
})
@@ -148,12 +148,12 @@ func TestSimpleReducer(t *testing.T) {
Convey("diff with only nulls", func() {
reducer := newSimpleReducerByType("diff")
series := &tsdb.TimeSeries{
series := &monitor.TimeSeries{
Name: "test time serie",
}
series.Points = append(series.Points, tsdb.NewTimePoint(nil, 1))
series.Points = append(series.Points, tsdb.NewTimePoint(nil, 2))
series.Points = append(series.Points, monitor.NewTimePoint(nil, 1))
series.Points = append(series.Points, monitor.NewTimePoint(nil, 2))
reduce, _ := reducer.Reduce(series)
So(reduce, ShouldBeNil)
})
@@ -175,12 +175,12 @@ func TestSimpleReducer(t *testing.T) {
Convey("percent_diff with only nulls", func() {
reducer := newSimpleReducerByType("percent_diff")
series := &tsdb.TimeSeries{
series := &monitor.TimeSeries{
Name: "test time serie",
}
series.Points = append(series.Points, tsdb.NewTimePoint(nil, 1))
series.Points = append(series.Points, tsdb.NewTimePoint(nil, 2))
series.Points = append(series.Points, monitor.NewTimePoint(nil, 1))
series.Points = append(series.Points, monitor.NewTimePoint(nil, 2))
reduce, _ := reducer.Reduce(series)
So(reduce, ShouldBeNil)
})
@@ -189,13 +189,13 @@ func TestSimpleReducer(t *testing.T) {
func testReducer(reducerType string, datapoints ...float64) float64 {
reducer := newSimpleReducerByType(reducerType)
serires := &tsdb.TimeSeries{
serires := &monitor.TimeSeries{
Name: "test time series",
}
for idx := range datapoints {
val := datapoints[idx]
serires.Points = append(serires.Points, tsdb.NewTimePoint(&val, 1234134))
serires.Points = append(serires.Points, monitor.NewTimePoint(&val, 1234134))
}
reduce, _ := reducer.Reduce(serires)
return *reduce
@@ -17,7 +17,7 @@ package conditions
import (
"time"
"yunion.io/x/onecloud/pkg/monitor/tsdb"
"yunion.io/x/onecloud/pkg/apis/monitor"
)
type suggestRuleReducer struct {
@@ -32,7 +32,7 @@ func NewSuggestRuleReducer(t string, duration time.Duration) Reducer {
}
}
func (s *suggestRuleReducer) Reduce(series *tsdb.TimeSeries) (*float64, []string) {
func (s *suggestRuleReducer) Reduce(series *monitor.TimeSeries) (*float64, []string) {
/*if int(s.duration.Seconds()) > len(series.Points) {
return nil, nil
}*/
+1 -8
View File
@@ -17,17 +17,10 @@ package metricquery
import (
"yunion.io/x/onecloud/pkg/apis/monitor"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/monitor/tsdb"
)
type Metrics struct {
SeriesTotal int64
Series tsdb.TimeSeriesSlice
Metas []tsdb.QueryResultMeta
}
type MetricQuery interface {
ExecuteQuery(userCred mcclient.TokenCredential, forceCheckSeries bool) (*Metrics, error)
ExecuteQuery(userCred mcclient.TokenCredential, forceCheckSeries bool) (*monitor.MetricsQueryResult, error)
}
type QueryFactory func(model []*monitor.AlertCondition) (MetricQuery, error)
+2 -2
View File
@@ -292,13 +292,13 @@ func (man *SCommonAlertManager) genName(ctx context.Context, ownerId mcclient.II
func (man *SCommonAlertManager) ValidateMetricQuery(metricRequest *monitor.CommonMetricInputQuery, scope string, ownerId mcclient.IIdentityProvider) error {
for _, q := range metricRequest.MetricQuery {
metriInputQuery := monitor.MetricInputQuery{
metriInputQuery := monitor.MetricQueryInput{
From: metricRequest.From,
To: metricRequest.To,
Interval: metricRequest.Interval,
}
setDefaultValue(q.AlertQuery, &metriInputQuery, scope, ownerId)
err := UnifiedMonitorManager.ValidateInputQuery(q.AlertQuery)
err := UnifiedMonitorManager.ValidateInputQuery(q.AlertQuery, &metriInputQuery)
if err != nil {
return err
}
+7 -12
View File
@@ -177,12 +177,7 @@ func (self *SDataSourceManager) GetMeasurementsWithDescriptionInfos(query jsonut
if err != nil {
return jsonutils.JSONNull, errors.Wrap(err, "getInfluxdbMeasurements")
}
dataSource, err := datasource.GetDefaultSource("")
if err != nil {
return jsonutils.JSONNull, errors.Wrap(err, "s.GetDefaultSource")
}
db := influxdb.NewInfluxdb(dataSource.Url)
filterMeasurements, err := self.filterMeasurementsByTime(*db, measurements, query, tagFilter)
filterMeasurements, err := self.filterMeasurementsByTime(measurements, query, tagFilter)
if err != nil {
return jsonutils.JSONNull, errors.Wrap(err, "filterMeasurementsByTime error")
}
@@ -325,13 +320,13 @@ type influxdbQueryChan struct {
count int
}
func (self *SDataSourceManager) filterMeasurementsByTime(db influxdb.SInfluxdb,
func (self *SDataSourceManager) filterMeasurementsByTime(
measurements []monitor.InfluxMeasurement, query jsonutils.JSONObject, tagFilter *monitor.MetricQueryTag) ([]monitor.InfluxMeasurement, error) {
timeF, err := self.getFromAndToFromParam(query)
if err != nil {
return nil, err
}
filterMeasurements, err := self.getFilterMeasurementsAsync(timeF.From, timeF.To, measurements, db, tagFilter)
filterMeasurements, err := self.getFilterMeasurementsAsync(timeF.From, timeF.To, measurements, tagFilter)
if err != nil {
return nil, err
}
@@ -367,7 +362,7 @@ func (self *SDataSourceManager) getFromAndToFromParam(query jsonutils.JSONObject
}
func (self *SDataSourceManager) getFilterMeasurementsAsync(from, to string,
measurements []monitor.InfluxMeasurement, db influxdb.SInfluxdb, tagFilter *monitor.MetricQueryTag) ([]monitor.InfluxMeasurement, error) {
measurements []monitor.InfluxMeasurement, tagFilter *monitor.MetricQueryTag) ([]monitor.InfluxMeasurement, error) {
filterMeasurements := make([]monitor.InfluxMeasurement, 0)
queryChan := new(influxdbQueryChan)
queryChan.queryRtnChan = make(chan monitor.InfluxMeasurement, len(measurements))
@@ -379,7 +374,7 @@ func (self *SDataSourceManager) getFilterMeasurementsAsync(from, to string,
for i, _ := range measurements {
tmp := measurements[i]
measurementQueryGroup.Go(func() error {
return self.getFilterMeasurement(queryChan, from, to, tmp, db, tagFilter)
return self.getFilterMeasurement(queryChan, from, to, tmp, tagFilter)
})
}
measurementQueryGroup.Go(func() error {
@@ -399,7 +394,7 @@ func (self *SDataSourceManager) getFilterMeasurementsAsync(from, to string,
return filterMeasurements, err
}
func (self *SDataSourceManager) getFilterMeasurement(queryChan *influxdbQueryChan, from, to string, measurement monitor.InfluxMeasurement, db influxdb.SInfluxdb, tagFilter *monitor.MetricQueryTag) error {
func (self *SDataSourceManager) getFilterMeasurement(queryChan *influxdbQueryChan, from, to string, measurement monitor.InfluxMeasurement, tagFilter *monitor.MetricQueryTag) error {
dds, _ := datasource.GetDefaultSource("")
ep, err := datasource.GetDefaultQueryEndpoint()
if err != nil {
@@ -730,7 +725,7 @@ func getTagValues(userCred mcclient.TokenCredential, output *monitor.InfluxMeasu
To: timeF.To,
}
q := monitor.MetricInputQuery{
q := monitor.MetricQueryInput{
From: timeF.From,
To: timeF.To,
MetricQuery: []*monitor.AlertQuery{
+3 -15
View File
@@ -15,38 +15,26 @@
package models
import (
"crypto/sha256"
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/apis/monitor"
"yunion.io/x/onecloud/pkg/httperrors"
)
const (
QUERY_SIGNATURE_KEY = "signature"
)
func digestQuerySignature(data *jsonutils.JSONDict) string {
data.Remove(QUERY_SIGNATURE_KEY)
return fmt.Sprintf("%x", sha256.Sum256([]byte(data.String())))
}
func ValidateQuerySignature(input jsonutils.JSONObject) error {
data, ok := input.(*jsonutils.JSONDict)
if !ok {
return httperrors.NewInputParameterError("input not json dict")
}
signature, err := data.GetString(QUERY_SIGNATURE_KEY)
signature, err := data.GetString(monitor.QUERY_SIGNATURE_KEY)
if err != nil {
if errors.Cause(err) == jsonutils.ErrJsonDictKeyNotFound {
return httperrors.NewNotFoundError("not found signature")
}
return errors.Wrap(err, "get signature")
}
if signature != digestQuerySignature(data) {
if signature != monitor.DigestQuerySignature(data) {
return httperrors.NewBadRequestError("signature error")
}
return nil
+3 -1
View File
@@ -18,6 +18,8 @@ import (
"testing"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/apis/monitor"
)
func Test_digestQuerySignature(t *testing.T) {
@@ -42,7 +44,7 @@ func Test_digestQuerySignature(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := digestQuerySignature(tt.data); got != tt.want {
if got := monitor.DigestQuerySignature(tt.data); got != tt.want {
t.Errorf("sumQuerySignature() = %v, want %v", got, tt.want)
}
})
+80 -77
View File
@@ -25,7 +25,6 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/gotypes"
"yunion.io/x/pkg/util/rbacscope"
"yunion.io/x/onecloud/pkg/apis/monitor"
@@ -34,13 +33,11 @@ import (
"yunion.io/x/onecloud/pkg/hostman/hostinfo/hostconsts"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/monitor/datasource"
mod "yunion.io/x/onecloud/pkg/mcclient/modules/monitor"
merrors "yunion.io/x/onecloud/pkg/monitor/errors"
mq "yunion.io/x/onecloud/pkg/monitor/metricquery"
"yunion.io/x/onecloud/pkg/monitor/options"
"yunion.io/x/onecloud/pkg/monitor/tsdb"
"yunion.io/x/onecloud/pkg/monitor/validators"
"yunion.io/x/onecloud/pkg/util/influxdb"
)
const (
@@ -200,7 +197,7 @@ func (self *SUnifiedMonitorManager) SetHandlerProcessTimeout(info *appsrv.SHandl
return 5 * time.Minute
}
func (self *SUnifiedMonitorManager) PerformQuery(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
func (self *SUnifiedMonitorManager) PerformQuery(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (*monitor.MetricsQueryResult, error) {
tmp := jsonutils.DeepCopy(data)
self.handleDataPreSignature(ctx, tmp)
if !options.Options.DisableQuerySignatureCheck {
@@ -208,10 +205,10 @@ func (self *SUnifiedMonitorManager) PerformQuery(ctx context.Context, userCred m
return nil, errors.Wrap(err, "ValidateQuerySignature")
}
}
inputQuery := new(monitor.MetricInputQuery)
inputQuery := new(monitor.MetricQueryInput)
err := data.Unmarshal(inputQuery)
if err != nil {
return jsonutils.NewDict(), err
return nil, err
}
if len(inputQuery.MetricQuery) == 0 {
return nil, merrors.NewArgIsEmptyErr("metric_query")
@@ -223,8 +220,8 @@ func (self *SUnifiedMonitorManager) PerformQuery(ctx context.Context, userCred m
ownId = userCred
}
setDefaultValue(q, inputQuery, scope, ownId)
if err := self.ValidateInputQuery(q); err != nil {
return jsonutils.NewDict(), errors.Wrapf(err, "ValidateInputQuery")
if err := self.ValidateInputQuery(q, inputQuery); err != nil {
return nil, errors.Wrapf(err, "ValidateInputQuery")
}
}
@@ -237,9 +234,13 @@ func (self *SUnifiedMonitorManager) PerformQuery(ctx context.Context, userCred m
}
}
return self.performQuery(ctx, userCred, inputQuery)
}
func (self *SUnifiedMonitorManager) performQuery(ctx context.Context, userCred mcclient.TokenCredential, inputQuery *monitor.MetricQueryInput) (*monitor.MetricsQueryResult, error) {
rtn, err := doQuery(userCred, *inputQuery)
if err != nil {
return jsonutils.NewDict(), errors.Wrapf(err, "doQuery with input %s", data)
return nil, errors.Wrapf(err, "doQuery with input %s", jsonutils.Marshal(inputQuery))
}
if len(inputQuery.Soffset) != 0 && len(inputQuery.Slimit) != 0 {
@@ -268,14 +269,14 @@ func (self *SUnifiedMonitorManager) PerformQuery(ctx context.Context, userCred m
}
fillSerieTags(&rtn.Series)
return jsonutils.Marshal(rtn), nil
return rtn, nil
}
func (self *SUnifiedMonitorManager) fillSearchSeriesTotalQuery(userCred mcclient.TokenCredential, fork monitor.AlertQuery) int64 {
newGroupByPart := make([]monitor.MetricQueryPart, 0)
newGroupByPart = append(newGroupByPart, fork.Model.GroupBy[0])
fork.Model.GroupBy = newGroupByPart
forkInputQury := new(monitor.MetricInputQuery)
forkInputQury := new(monitor.MetricQueryInput)
forkInputQury.MetricQuery = []*monitor.AlertQuery{&fork}
rtn, err := doQuery(userCred, *forkInputQury)
if err != nil {
@@ -314,17 +315,26 @@ func (self *SUnifiedMonitorManager) handleDataPreSignature(ctx context.Context,
}
}
func doQuery(userCred mcclient.TokenCredential, query monitor.MetricInputQuery) (*mq.Metrics, error) {
conditions := make([]*monitor.AlertCondition, 0)
func doQuery(userCred mcclient.TokenCredential, query monitor.MetricQueryInput) (*monitor.MetricsQueryResult, error) {
conds := make([]*monitor.AlertCondition, 0)
for _, q := range query.MetricQuery {
if q.To == "" {
q.To = query.To
}
if q.From == "" {
q.From = query.From
}
if q.Model.Interval == "" {
q.Model.Interval = query.Interval
}
condition := monitor.AlertCondition{
Type: "metricquery",
Type: monitor.ConditionTypeMetricQuery,
Query: *q,
}
conditions = append(conditions, &condition)
conds = append(conds, &condition)
}
factory := mq.GetQueryFactories()["metricquery"]
metricQ, err := factory(conditions)
factory := mq.GetQueryFactories()[monitor.ConditionTypeMetricQuery]
metricQ, err := factory(conds)
if err != nil {
return nil, errors.Wrap(err, "factory")
}
@@ -340,15 +350,25 @@ func doQuery(userCred mcclient.TokenCredential, query monitor.MetricInputQuery)
return metrics, nil
}
func (self *SUnifiedMonitorManager) ValidateInputQuery(query *monitor.AlertQuery) error {
func (self *SUnifiedMonitorManager) ValidateInputQuery(query *monitor.AlertQuery, input *monitor.MetricQueryInput) error {
if input.From == "" {
input.From = "1h"
}
if input.To == "" {
input.To = "now"
}
if input.Interval == "" {
input.Interval = "5m"
}
if query.From == "" {
query.From = "1h"
query.From = input.From
}
if query.Model.Interval == "" {
query.Model.Interval = "5m"
query.Model.Interval = input.Interval
}
if query.To == "" {
query.To = "now"
query.To = input.To
}
if _, err := time.ParseDuration(query.Model.Interval); err != nil {
return httperrors.NewInputParameterError("Invalid interval format: %s", query.Model.Interval)
@@ -356,7 +376,7 @@ func (self *SUnifiedMonitorManager) ValidateInputQuery(query *monitor.AlertQuery
return validators.ValidateSelectOfMetricQuery(*query)
}
func setDefaultValue(query *monitor.AlertQuery, inputQuery *monitor.MetricInputQuery,
func setDefaultValue(query *monitor.AlertQuery, inputQuery *monitor.MetricQueryInput,
scope string, ownerId mcclient.IIdentityProvider) {
query.From = inputQuery.From
query.To = inputQuery.To
@@ -457,7 +477,7 @@ func setDefaultValue(query *monitor.AlertQuery, inputQuery *monitor.MetricInputQ
}
}
func checkQueryGroupBy(query *monitor.AlertQuery, inputQuery *monitor.MetricInputQuery) {
func checkQueryGroupBy(query *monitor.AlertQuery, inputQuery *monitor.MetricQueryInput) {
if len(query.Model.GroupBy) != 0 {
return
}
@@ -479,7 +499,7 @@ func checkQueryGroupBy(query *monitor.AlertQuery, inputQuery *monitor.MetricInpu
})
}
func fillSerieTags(series *tsdb.TimeSeriesSlice) {
func fillSerieTags(series *monitor.TimeSeriesSlice) {
for i, serie := range *series {
for _, tag := range []string{"brand", "platform", "hypervisor"} {
if val, ok := serie.Tags[tag]; ok {
@@ -497,8 +517,7 @@ func fillSerieTags(series *tsdb.TimeSeriesSlice) {
}
}
func (self *SUnifiedMonitorManager) GetPropertySimpleQuery(ctx context.Context, userCred mcclient.TokenCredential,
input *monitor.SimpleQueryInput) (jsonutils.JSONObject, error) {
func (self *SUnifiedMonitorManager) GetPropertySimpleQuery(ctx context.Context, userCred mcclient.TokenCredential, input *monitor.SimpleQueryInput) (jsonutils.JSONObject, error) {
if len(input.Database) == 0 {
input.Database = "telegraf"
}
@@ -510,12 +529,13 @@ func (self *SUnifiedMonitorManager) GetPropertySimpleQuery(ctx context.Context,
return nil, httperrors.NewInputParameterError("invalid metric_name %s", input.MetricName)
}
measurement, field := metric[0], metric[1]
sqlstr := "select id, " + field + " from " + measurement + " where "
if input.Tags == nil {
input.Tags = map[string]string{}
}
data := mod.NewMetricQueryInputWithDB(input.Database, measurement).SkipCheckSeries(true)
data.Selects().Select(field)
where := data.Where()
if len(input.Id) > 0 {
input.Tags["id"] = input.Id
where.Equal("id", input.Id)
}
if input.EndTime.IsZero() {
input.EndTime = time.Now()
@@ -526,57 +546,40 @@ func (self *SUnifiedMonitorManager) GetPropertySimpleQuery(ctx context.Context,
if input.EndTime.Sub(input.StartTime).Hours() > 1 {
return nil, httperrors.NewInputParameterError("The query interval is greater than one hour")
}
st := input.StartTime.Format(time.RFC3339)
et := input.EndTime.Format(time.RFC3339)
conditions := []string{}
for k, v := range input.Tags {
conditions = append(conditions, fmt.Sprintf("%s = '%s'", k, v))
where.Equal(k, v)
}
conditions = append(conditions, fmt.Sprintf("time >= '%s'", st))
conditions = append(conditions, fmt.Sprintf("time <= '%s'", et))
sqlstr += strings.Join(conditions, " and ")
sqlstr += " limit 2000"
dataSource, err := datasource.GetDefaultSource(input.Database)
data.From(input.StartTime).To(input.EndTime).Interval("5m")
queryData := data.ToQueryData()
dbRtn, err := self.performQuery(ctx, userCred, queryData)
if err != nil {
return nil, errors.Wrap(err, "s.GetDefaultSource")
}
db := influxdb.NewInfluxdb(dataSource.Url)
err = db.SetDatabase(input.Database)
if err != nil {
return nil, httperrors.NewInputParameterError("not support database %s", input.Database)
}
dbRtn, err := db.Query(sqlstr)
if err != nil {
return nil, errors.Wrap(err, "sql selected error")
return nil, errors.Wrapf(err, "performQuery with data: %s", queryData)
}
ret := []monitor.SimpleQueryOutput{}
for _, dbResults := range dbRtn {
for _, dbResult := range dbResults {
for _, value := range dbResult.Values {
if len(value) != 3 ||
gotypes.IsNil(value[0]) ||
gotypes.IsNil(value[1]) ||
gotypes.IsNil(value[2]) {
continue
}
timestamp, err := value[0].Int()
if err != nil {
continue
}
id, err := value[1].GetString()
if err != nil {
continue
}
v, err := value[2].Float()
if err != nil {
continue
}
ret = append(ret, monitor.SimpleQueryOutput{
Id: id,
Time: time.UnixMilli(timestamp),
Value: v,
})
for _, s := range dbRtn.Series {
id, ok := s.Tags["id"]
if !ok {
log.Warningf("Not found id from series: %s", jsonutils.Marshal(s))
continue
}
for _, point := range s.Points {
if len(point) != 2 {
log.Warningf("invalid series: %s", jsonutils.Marshal(s))
break
}
timestamp := point[len(point)-1]
valPtr, ok := point[0].(*float64)
if !ok || valPtr == nil {
log.Warningf("invalid series point: %#v", point)
break
}
ret = append(ret, monitor.SimpleQueryOutput{
Id: id,
Time: time.UnixMilli(int64(timestamp.(float64))),
Value: *valPtr,
})
}
}
return jsonutils.Marshal(map[string]interface{}{"values": ret}), nil
+5 -6
View File
@@ -39,7 +39,6 @@ import (
sub "yunion.io/x/onecloud/pkg/monitor/influxdbsubscribe"
"yunion.io/x/onecloud/pkg/monitor/models"
"yunion.io/x/onecloud/pkg/monitor/registry"
"yunion.io/x/onecloud/pkg/monitor/tsdb"
)
var (
@@ -250,13 +249,13 @@ func getQueryEvalType(evalType string) string {
}
func (self *SSubscriptionManager) getPointsByAlertDetail(details monitor.CommonAlertMetricDetails, alert models.SCommonAlert,
points []sub.Point) *tsdb.TimeSeries {
points []sub.Point) *monitor.TimeSeries {
metricPoints := make([]sub.Point, 0)
serie := tsdb.TimeSeries{
serie := monitor.TimeSeries{
RawName: "",
Name: "",
Points: make(tsdb.TimeSeriesPoints, 0),
Points: make(monitor.TimeSeriesPoints, 0),
Tags: nil,
}
@@ -297,7 +296,7 @@ func (self *SSubscriptionManager) getPointsByAlertDetail(details monitor.CommonA
for _, metricPoint := range metricPoints {
if len(model.Selects) > 1 {
fieldMap := metricPoint.Fields()
point := make(tsdb.TimePoint, 0)
point := make(monitor.TimePoint, 0)
for _, sel := range model.Selects {
point = append(point, parseValue(fieldMap[sel[0].Params[0]]))
}
@@ -311,7 +310,7 @@ func (self *SSubscriptionManager) getPointsByAlertDetail(details monitor.CommonA
for fieldPoint.Next() {
if string(fieldPoint.FieldKey()) == details.Field && isValid(fieldPoint) {
val := fieldPoint.FloatValue()
timePoint := tsdb.NewTimePoint(&val, float64(metricPoint.UnixNano()))
timePoint := monitor.NewTimePoint(&val, float64(metricPoint.UnixNano()))
serie.Points = append(serie.Points, timePoint)
}
}
+1 -1
View File
@@ -141,7 +141,7 @@ func (e *InfluxdbExecutor) Query(ctx context.Context, dsInfo *tsdb.DataSource, t
}
for i, query := range tsdbQuery.Queries {
ret := e.ResponseParser.Parse(&response, influxQ[i])
ret.Meta = tsdb.QueryResultMeta{
ret.Meta = monitor.QueryResultMeta{
RawQuery: rawQuery,
}
result.Results[query.RefId] = ret
+10 -2
View File
@@ -101,12 +101,20 @@ func (query *Query) renderTimeFilter(queryCtx *tsdb.TsdbQuery) string {
if strings.Contains(queryCtx.TimeRange.From, "now-") {
from = "now() - " + strings.Replace(queryCtx.TimeRange.From, "now-", "", 1)
} else {
from = "now() - " + queryCtx.TimeRange.From
if _, ok := tsdb.TryParseUnixMsEpoch(queryCtx.TimeRange.From); ok {
from = fmt.Sprintf("%sms", queryCtx.TimeRange.From)
} else {
from = "now() - " + queryCtx.TimeRange.From
}
}
to := ""
if queryCtx.TimeRange.To != "now" && queryCtx.TimeRange.To != "" {
to = " and time < now() - " + strings.Replace(queryCtx.TimeRange.To, "now-", "", 1)
if _, ok := tsdb.TryParseUnixMsEpoch(queryCtx.TimeRange.To); ok {
to = fmt.Sprintf(" and time < %sms", queryCtx.TimeRange.To)
} else {
to = " and time < now() - " + strings.Replace(queryCtx.TimeRange.To, "now-", "", 1)
}
}
return fmt.Sprintf("time > %s%s", from, to)
@@ -15,6 +15,7 @@
package influxdb
import (
"fmt"
"strings"
"testing"
"time"
@@ -159,6 +160,14 @@ func TestInfluxdbQueryBuilder(t *testing.T) {
queryContext := &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("10m", "now")}
So(query.renderTimeFilter(queryContext), ShouldEqual, "time > now() - 10m")
})
Convey("render from: 1701957983540 to 1701961583540", func() {
query := Query{}
start := "1701957983540"
end := "1701961583540"
queryContext := &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange(start, end)}
So(query.renderTimeFilter(queryContext), ShouldEqual, fmt.Sprintf("time > %sms and time < %sms", start, end))
})
})
Convey("can render normal tags without operator", func() {
@@ -24,6 +24,7 @@ import (
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/apis/monitor"
"yunion.io/x/onecloud/pkg/monitor/tsdb"
)
@@ -47,22 +48,22 @@ func (rp *ResponseParser) Parse(response *Response, query *Query) *tsdb.QueryRes
return queryRes
}
func (rp *ResponseParser) transformRows(rows []Row, queryResult *tsdb.QueryResult, query *Query) tsdb.TimeSeriesSlice {
var result tsdb.TimeSeriesSlice
func (rp *ResponseParser) transformRows(rows []Row, queryResult *tsdb.QueryResult, query *Query) monitor.TimeSeriesSlice {
var result monitor.TimeSeriesSlice
for _, row := range rows {
for columnIndex, column := range row.Columns {
if column == "time" {
continue
}
var points tsdb.TimeSeriesPoints
var points monitor.TimeSeriesPoints
for _, valuePair := range row.Values {
point, err := rp.parseTimepoint(valuePair, columnIndex)
if err == nil {
points = append(points, point)
}
}
result = append(result, &tsdb.TimeSeries{
result = append(result, &monitor.TimeSeries{
Name: rp.formatSerieName(row, column, query),
Points: points,
Tags: row.Tags,
@@ -73,8 +74,8 @@ func (rp *ResponseParser) transformRows(rows []Row, queryResult *tsdb.QueryResul
return result
}
func (rp *ResponseParser) transformRowsV2(rows []Row, queryResult *tsdb.QueryResult, query *Query) tsdb.TimeSeriesSlice {
var result tsdb.TimeSeriesSlice
func (rp *ResponseParser) transformRowsV2(rows []Row, queryResult *tsdb.QueryResult, query *Query) monitor.TimeSeriesSlice {
var result monitor.TimeSeriesSlice
for idx, row := range rows {
col := ""
columns := make([]string, 0)
@@ -90,7 +91,7 @@ func (rp *ResponseParser) transformRowsV2(rows []Row, queryResult *tsdb.QueryRes
col = fmt.Sprintf("%s-%s", col, column)
}
columns = append(columns, "time")
var points tsdb.TimeSeriesPoints
var points monitor.TimeSeriesPoints
for _, valuePair := range row.Values {
point, err := rp.parseTimepointV2(valuePair)
if err == nil {
@@ -192,27 +193,27 @@ func (rp *ResponseParser) buildSerieNameFromQuery(row Row, column string) string
return fmt.Sprintf("%s.%s", row.Name, column)
}
func (rp *ResponseParser) parseTimepoint(valuePair []interface{}, valuePosition int) (tsdb.TimePoint, error) {
func (rp *ResponseParser) parseTimepoint(valuePair []interface{}, valuePosition int) (monitor.TimePoint, error) {
var value *float64 = rp.parseValue(valuePair[valuePosition])
timestampNumber, _ := valuePair[0].(json.Number)
timestamp, err := timestampNumber.Float64()
if err != nil {
return tsdb.TimePoint{}, err
return monitor.TimePoint{}, err
}
return tsdb.NewTimePoint(value, timestamp), nil
return monitor.NewTimePoint(value, timestamp), nil
}
func (rp *ResponseParser) parseTimepointV2(valuePair []interface{}) (tsdb.TimePoint, error) {
timepoint := make(tsdb.TimePoint, 0)
func (rp *ResponseParser) parseTimepointV2(valuePair []interface{}) (monitor.TimePoint, error) {
timepoint := make(monitor.TimePoint, 0)
for i := 1; i < len(valuePair); i++ {
timepoint = append(timepoint, rp.parseValueV2(valuePair[i]))
}
timestampNumber, _ := valuePair[0].(json.Number)
timestamp, err := timestampNumber.Float64()
if err != nil {
return tsdb.TimePoint{}, errors.Wrapf(err, "timestampNumber.Float64 of %#v", timestampNumber)
return monitor.TimePoint{}, errors.Wrapf(err, "timestampNumber.Float64 of %#v", timestampNumber)
}
timepoint = append(timepoint, timestamp)
return timepoint, nil
@@ -27,6 +27,7 @@ import (
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/sets"
"yunion.io/x/onecloud/pkg/apis/monitor"
"yunion.io/x/onecloud/pkg/monitor/tsdb"
)
@@ -149,16 +150,16 @@ func newPointsByResults(results []ResponseDataResult) ([]*points, error) {
return ret, nil
}
func transPointsToSeries(points []*points, query *tsdb.Query) tsdb.TimeSeriesSlice {
var result tsdb.TimeSeriesSlice
func transPointsToSeries(points []*points, query *tsdb.Query) monitor.TimeSeriesSlice {
var result monitor.TimeSeriesSlice
for _, point := range points {
result = append(result, transPointToSeries(point, query)...)
}
return result
}
func transValuesToTSDBPoints(vals []ResponseDataResultValue) tsdb.TimeSeriesPoints {
var points tsdb.TimeSeriesPoints
func transValuesToTSDBPoints(vals []ResponseDataResultValue) monitor.TimeSeriesPoints {
var points monitor.TimeSeriesPoints
for _, val := range vals {
point, err := parseTimepoint(val)
if err != nil {
@@ -179,8 +180,8 @@ func reviseTags(tags map[string]string) map[string]string {
return ret
}
func transPointToSeries(p *points, query *tsdb.Query) tsdb.TimeSeriesSlice {
var result tsdb.TimeSeriesSlice
func transPointToSeries(p *points, query *tsdb.Query) monitor.TimeSeriesSlice {
var result monitor.TimeSeriesSlice
points := transValuesToTSDBPoints(p.values)
tags := reviseTags(p.tags)
@@ -124,7 +124,7 @@ func convertVMResponse(rawQuery string, tsdbQuery *tsdb.TsdbQuery, resp *Respons
if err != nil {
return nil, errors.Wrap(err, "translate response")
}
ret.Meta = tsdb.QueryResultMeta{
ret.Meta = monitor.QueryResultMeta{
RawQuery: rawQuery,
}
result.Results[query.RefId] = ret
@@ -160,8 +160,8 @@ func translateResponse(resp *Response, query *tsdb.Query) (*tsdb.QueryResult, er
}
// Check VictoriaMetrics response at: https://docs.victoriametrics.com/keyConcepts.html#range-query
func transformSeries(vmResult ResponseDataResult, query *tsdb.Query) tsdb.TimeSeriesSlice {
var result tsdb.TimeSeriesSlice
func transformSeries(vmResult ResponseDataResult, query *tsdb.Query) monitor.TimeSeriesSlice {
var result monitor.TimeSeriesSlice
metric := vmResult.Metric
points := transValuesToTSDBPoints(vmResult.Values)
@@ -200,13 +200,13 @@ func formatRawName(idx int, name string, query *tsdb.Query, tags map[string]stri
return tsdb.FormatRawName(idx, name, groupByTags, tags)
}
func parseTimepoint(val ResponseDataResultValue) (tsdb.TimePoint, error) {
timepoint := make(tsdb.TimePoint, 0)
func parseTimepoint(val ResponseDataResultValue) (monitor.TimePoint, error) {
timepoint := make(monitor.TimePoint, 0)
// parse timestamp
timestampNumber, _ := val[0].(json.Number)
timestamp, err := timestampNumber.Float64()
if err != nil {
return tsdb.TimePoint{}, errors.Wrapf(err, "parse timestampNumber")
return monitor.TimePoint{}, errors.Wrapf(err, "parse timestampNumber")
}
// to influxdb timestamp format, millisecond ?
timestamp *= 1000
+11 -101
View File
@@ -17,7 +17,6 @@ package tsdb
import (
"fmt"
"sort"
"strconv"
"strings"
"yunion.io/x/pkg/util/sets"
@@ -44,37 +43,24 @@ type Response struct {
Message string `json:"message,omitempty"`
}
type QueryResultMeta struct {
RawQuery string `json:"raw_query"`
}
type QueryResult struct {
Error error `json:"-"`
ErrorString string `json:"error,omitempty"`
RefId string `json:"ref_id"`
Meta QueryResultMeta `json:"meta"`
Series TimeSeriesSlice `json:"series"`
Tables []*Table `json:"tables"`
Dataframes [][]byte `json:"dataframes"`
}
type TimeSeries struct {
// RawName is used to frontend displaying the curve name
RawName string `json:"raw_name"`
Columns []string `json:"columns"`
Name string `json:"name"`
Points TimeSeriesPoints `json:"points"`
Tags map[string]string `json:"tags,omitempty"`
Error error `json:"-"`
ErrorString string `json:"error,omitempty"`
RefId string `json:"ref_id"`
Meta api.QueryResultMeta `json:"meta"`
Series api.TimeSeriesSlice `json:"series"`
Tables []*Table `json:"tables"`
Dataframes [][]byte `json:"dataframes"`
}
func NewTimeSeries(
name string,
rawName string,
columns []string,
points TimeSeriesPoints,
points api.TimeSeriesPoints,
tags map[string]string,
) *TimeSeries {
return &TimeSeries{
) *api.TimeSeries {
return &api.TimeSeries{
RawName: rawName,
Columns: columns,
Name: name,
@@ -93,89 +79,13 @@ type TableColumn struct {
}
type RowValues []interface{}
type TimePoint []interface{}
type TimeSeriesPoints []TimePoint
type TimeSeriesSlice []*TimeSeries
func NewQueryResult() *QueryResult {
return &QueryResult{
Series: make(TimeSeriesSlice, 0),
Series: make(api.TimeSeriesSlice, 0),
}
}
func NewTimePoint(value *float64, timestamp float64) TimePoint {
return TimePoint{value, timestamp}
}
func NewTimePointByVal(value float64, timestamp float64) TimePoint {
return NewTimePoint(&value, timestamp)
}
func (p TimePoint) IsValid() bool {
if val, ok := p[0].(*float64); ok && val != nil {
return true
}
return false
//return p[0].(*float64) != nil
}
func (p TimePoint) IsValids() bool {
for i := 0; i < len(p)-1; i++ {
if p[i] == nil {
return false
}
if p[i].(*float64) == nil {
return false
}
}
return true
}
func (p TimePoint) Value() float64 {
return *(p[0].(*float64))
}
func (p TimePoint) Timestamp() float64 {
return p[len(p)-1].(float64)
}
func (p TimePoint) Values() []float64 {
values := make([]float64, 0)
for i := 0; i < len(p)-1; i++ {
values = append(values, *(p[i].(*float64)))
}
return values
}
func (p TimePoint) PointValueStr() []string {
arrStr := make([]string, 0)
for i := 0; i < len(p)-1; i++ {
if p[i] == nil {
arrStr = append(arrStr, "")
}
if fval, ok := p[i].(*float64); ok {
arrStr = append(arrStr, strconv.FormatFloat((*fval), 'f', -1, 64))
continue
}
if ival, ok := p[i].(*int64); ok {
arrStr = append(arrStr, strconv.FormatInt((*ival), 64))
continue
}
arrStr = append(arrStr, p[i].(string))
}
return arrStr
}
func NewTimeSeriesPointsFromArgs(values ...float64) TimeSeriesPoints {
points := make(TimeSeriesPoints, 0)
for i := 0; i < len(values); i += 2 {
points = append(points, NewTimePoint(&values[i], values[i+1]))
}
return points
}
func FormatRawName(idx int, name string, groupByTags []string, tags map[string]string) string {
// when group by tag specified
if len(groupByTags) != 0 {
+4 -3
View File
@@ -35,7 +35,7 @@ func NewTimeRange(from, to string) *TimeRange {
}
}
func tryParseUnixMsEpoch(val string) (time.Time, bool) {
func TryParseUnixMsEpoch(val string) (time.Time, bool) {
if val, err := strconv.ParseInt(val, 10, 64); err == nil {
seconds := val / 1000
nano := (val - seconds*1000) * 1000000
@@ -45,7 +45,8 @@ func tryParseUnixMsEpoch(val string) (time.Time, bool) {
}
func (tr *TimeRange) ParseFrom() (time.Time, error) {
if res, ok := tryParseUnixMsEpoch(tr.From); ok {
res, ok := TryParseUnixMsEpoch(tr.From)
if ok {
return res, nil
}
@@ -71,7 +72,7 @@ func (tr *TimeRange) ParseTo() (time.Time, error) {
return tr.now.Add(diff), nil
}
if res, ok := tryParseUnixMsEpoch(tr.To); ok {
if res, ok := TryParseUnixMsEpoch(tr.To); ok {
return res, nil
}
+24 -27
View File
@@ -24,6 +24,7 @@ import (
"yunion.io/x/onecloud/pkg/apis/monitor"
"yunion.io/x/onecloud/pkg/httperrors"
merrors "yunion.io/x/onecloud/pkg/monitor/errors"
"yunion.io/x/onecloud/pkg/monitor/tsdb"
)
const (
@@ -112,11 +113,11 @@ func ValidateAlertQueryModel(input monitor.MetricQuery) error {
func ValidateSelectOfMetricQuery(input monitor.AlertQuery) error {
if err := ValidateFromAndToValue(input); err != nil {
return err
return errors.Wrap(err, "ValidateFromAndToValue")
}
if err := ValidateAlertQueryModel(input.Model); err != nil {
return err
return errors.Wrap(err, "ValidateAlertQueryModel")
}
for _, sel := range input.Model.Selects {
@@ -128,28 +129,13 @@ func ValidateSelectOfMetricQuery(input monitor.AlertQuery) error {
}
func ValidateFromAndToValue(input monitor.AlertQuery) error {
fromRaw := strings.Replace(input.From, "now-", "", 1)
fromDur, err := time.ParseDuration("-" + fromRaw)
if err != nil {
return err
if err := ValidateFromValue(input.From); err != nil {
return errors.Wrap(err, "ValidateFromValue")
}
if input.To == "now" {
return nil
} else if strings.HasPrefix(input.To, "now-") {
withoutNow := strings.Replace(input.To, "now-", "", 1)
toDur, err := time.ParseDuration("-" + withoutNow)
if err == nil {
if toDur >= fromDur {
return nil
}
return httperrors.NewInputParameterError("query duration err: from: %s, to:%s", input.From, input.To)
}
return err
if err := ValidateToValue(input.To); err != nil {
return errors.Wrap(err, "ValidateToValue")
}
return httperrors.NewInputParameterError("query duration `to` err: %s", input.To)
return nil
}
func ValidateAlertConditionReducer(input monitor.Condition) error {
@@ -217,10 +203,18 @@ func HumanThresholdType(typ string) string {
}
func ValidateFromValue(from string) error {
fromRaw := strings.Replace(from, "now-", "", 1)
_, ok := tsdb.TryParseUnixMsEpoch(from)
if ok {
return nil
}
_, err := time.ParseDuration("-" + fromRaw)
return err
fromRaw := strings.Replace(from, "now-", "", 1)
fromRawStr := "-" + fromRaw
_, err := time.ParseDuration(fromRawStr)
if err != nil {
return errors.Wrapf(err, "parse duration: %s", fromRawStr)
}
return nil
}
func ValidateToValue(to string) error {
@@ -228,13 +222,16 @@ func ValidateToValue(to string) error {
return nil
} else if strings.HasPrefix(to, "now-") {
withoutNow := strings.Replace(to, "now-", "", 1)
_, err := time.ParseDuration("-" + withoutNow)
if err == nil {
return nil
}
}
_, ok := tsdb.TryParseUnixMsEpoch(to)
if ok {
return nil
}
_, err := time.ParseDuration(to)
return err
return errors.Wrapf(err, "parse to: %s", to)
}