Files
cloudpods/pkg/monitor/models/datasource.go
Jian Qiu 63ef1dad78 fix: unhandled errors (#25383)
Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
2026-08-20 17:30:29 +08:00

954 lines
28 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.

// 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 models
import (
"bytes"
"context"
"fmt"
"math"
"regexp"
"sort"
"strings"
"time"
"github.com/influxdata/promql/v2/pkg/labels"
"github.com/zexi/influxql-to-metricsql/converter/translator"
"golang.org/x/sync/errgroup"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/tristate"
"yunion.io/x/pkg/util/sets"
"yunion.io/x/pkg/utils"
"yunion.io/x/onecloud/pkg/apis/monitor"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"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/mcclient/auth"
"yunion.io/x/onecloud/pkg/monitor/datasource"
merrors "yunion.io/x/onecloud/pkg/monitor/errors"
"yunion.io/x/onecloud/pkg/monitor/tsdb"
"yunion.io/x/onecloud/pkg/monitor/validators"
"yunion.io/x/onecloud/pkg/util/influxdb"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
const (
VICTORIA_METRICS_DB_TAG_KEY = "db"
VICTORIA_METRICS_DB_TAG_VAL_TELEGRAF = "telegraf"
)
var (
DataSourceManager *SDataSourceManager
compile = regexp.MustCompile(`\w{8}(-\w{4}){3}-\w{12}`)
)
func init() {
DataSourceManager = &SDataSourceManager{
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
SDataSource{},
"datasources_tbl",
"datasource",
"datasources",
),
}
DataSourceManager.SetVirtualObject(DataSourceManager)
}
// +onecloud:swagger-gen-model-singular=datasource
// +onecloud:swagger-gen-model-plural=datasources
type SDataSourceManager struct {
db.SStandaloneResourceBaseManager
}
type SDataSource struct {
db.SStandaloneResourceBase
Type string `nullable:"false" list:"user"`
Url string `nullable:"false" list:"user"`
User string `width:"64" charset:"utf8" nullable:"true"`
Password string `width:"64" charset:"utf8" nullable:"true"`
Database string `width:"64" charset:"utf8" nullable:"true"`
IsDefault tristate.TriState `default:"false" create:"optional"`
/*
TimeInterval string
BasicAuth bool
BasicAuthUser string
BasicAuthPassword string
*/
}
func (m *SDataSourceManager) GetSource(id string) (*SDataSource, error) {
ret, err := m.FetchById(id)
if err != nil {
return nil, err
}
return ret.(*SDataSource), nil
}
func (m *SDataSourceManager) GetDatabases() (jsonutils.JSONObject, error) {
ret := jsonutils.NewDict()
dataSource, err := datasource.GetDefaultSource("")
if err != nil {
return jsonutils.JSONNull, errors.Wrap(err, "s.GetDefaultSource")
}
db := influxdb.NewInfluxdb(dataSource.Url)
//db.SetDatabase("telegraf")
databases, err := db.GetDatabases()
if err != nil {
return jsonutils.JSONNull, errors.Wrap(err, "GetDatabases")
}
ret.Add(jsonutils.NewStringArray(databases), "databases")
return ret, nil
}
func (m *SDataSourceManager) GetMeasurements(query jsonutils.JSONObject,
measurementFilter, tagFilter string) (jsonutils.JSONObject,
error) {
ret := jsonutils.NewDict()
measurements, err := m.getMeasurementQueryInfluxdb(query, measurementFilter, tagFilter)
if err != nil {
return jsonutils.JSONNull, err
}
ret.Add(jsonutils.Marshal(&measurements), "measurements")
return ret, nil
}
func (m *SDataSourceManager) getMeasurementQueryInfluxdb(query jsonutils.JSONObject,
measurementFilter, tagFilter string) (rtnMeasurements []monitor.InfluxMeasurement, err error) {
database, _ := query.GetString("database")
if database == "" {
return rtnMeasurements, merrors.NewArgIsEmptyErr("database")
}
dataSource, err := datasource.GetDefaultSource("")
if err != nil {
return rtnMeasurements, errors.Wrap(err, "s.GetDefaultSource")
}
db := influxdb.NewInfluxdb(dataSource.Url)
db.SetDatabase(database)
var buffer bytes.Buffer
buffer.WriteString(" SHOW MEASUREMENTS ON ")
buffer.WriteString(database)
if len(measurementFilter) != 0 {
buffer.WriteString(" WITH ")
buffer.WriteString(measurementFilter)
}
if len(tagFilter) != 0 {
buffer.WriteString(" WHERE ")
buffer.WriteString(tagFilter)
}
dbRtn, err := db.Query(buffer.String())
if err != nil {
return rtnMeasurements, errors.Wrap(err, "SHOW MEASUREMENTS")
}
if len(dbRtn) > 0 && len(dbRtn[0]) > 0 {
res := dbRtn[0][0]
measurements := make([]monitor.InfluxMeasurement, len(res.Values))
for i := range res.Values {
tmpDict := jsonutils.NewDict()
tmpDict.Add(res.Values[i][0], "measurement")
err = tmpDict.Unmarshal(&measurements[i])
if err != nil {
return rtnMeasurements, errors.Wrap(err, "measurement unmarshal error")
}
}
rtnMeasurements = append(rtnMeasurements, measurements...)
}
return
}
func (m *SDataSourceManager) GetMeasurementsWithDescriptionInfos(query jsonutils.JSONObject, tagFilter *monitor.MetricQueryTag) (jsonutils.JSONObject, error) {
ret := jsonutils.NewDict()
rtnMeasurements := make([]monitor.InfluxMeasurement, 0)
measurements, err := MetricMeasurementManager.getMeasurementsFromDB()
if err != nil {
return jsonutils.JSONNull, errors.Wrap(err, "getMeasurementsFromDB")
}
/*filterMeasurements, err := m.filterMeasurementsByTime(measurements, query, tagFilter)
if err != nil {
return jsonutils.JSONNull, errors.Wrap(err, "filterMeasurementsByTime error")
}*/
filterMeasurements := m.getMetricDescriptions(measurements)
if len(filterMeasurements) != 0 {
rtnMeasurements = append(rtnMeasurements, filterMeasurements...)
}
ret.Add(jsonutils.Marshal(&rtnMeasurements), "measurements")
resTypeMap := make(map[string][]monitor.InfluxMeasurement, 0)
resTypes := make([]string, 0)
for _, measurement := range rtnMeasurements {
if typeMeasurements, ok := resTypeMap[measurement.ResType]; ok {
resTypeMap[measurement.ResType] = append(typeMeasurements, measurement)
continue
}
resTypes = append(resTypes, measurement.ResType)
resTypeMap[measurement.ResType] = []monitor.InfluxMeasurement{measurement}
}
sort.Slice(resTypes, func(i, j int) bool {
r1 := resTypes[i]
r2 := resTypes[j]
return monitor.ResTypeScoreMap[r1] < monitor.ResTypeScoreMap[r2]
})
for _, measures := range resTypeMap {
sort.Slice(measures, func(i, j int) bool {
return measures[i].Score < measures[j].Score
})
}
ret.Add(jsonutils.Marshal(&resTypes), "res_types")
ret.Add(jsonutils.Marshal(&resTypeMap), "res_type_measurements")
return ret, nil
}
func (m *SDataSourceManager) GetMeasurementsWithOutTimeFilter(query jsonutils.JSONObject,
measurementFilter, tagFilter string) (jsonutils.JSONObject,
error) {
ret := jsonutils.NewDict()
database, _ := query.GetString("database")
if database == "" {
return jsonutils.JSONNull, httperrors.NewInputParameterError("not support database")
}
dataSource, err := datasource.GetDefaultSource("")
if err != nil {
return jsonutils.JSONNull, errors.Wrap(err, "s.GetDefaultSource")
}
db := influxdb.NewInfluxdb(dataSource.Url)
db.SetDatabase(database)
var buffer bytes.Buffer
buffer.WriteString(" SHOW MEASUREMENTS ON ")
buffer.WriteString(database)
if len(measurementFilter) != 0 {
buffer.WriteString(" WITH ")
buffer.WriteString(measurementFilter)
}
if len(tagFilter) != 0 {
buffer.WriteString(" WHERE ")
buffer.WriteString(tagFilter)
}
dbRtn, err := db.Query(buffer.String())
if err != nil {
return jsonutils.JSONNull, errors.Wrap(err, "SHOW MEASUREMENTS")
}
if len(dbRtn) > 0 && len(dbRtn[0]) > 0 {
res := dbRtn[0][0]
measurements := make([]monitor.InfluxMeasurement, len(res.Values))
for i := range res.Values {
tmpDict := jsonutils.NewDict()
tmpDict.Add(res.Values[i][0], "measurement")
err := tmpDict.Unmarshal(&measurements[i])
if err != nil {
return jsonutils.JSONNull, errors.Wrap(err, "measurement unmarshal error")
}
}
ret.Add(jsonutils.Marshal(&measurements), "measurements")
}
return ret, nil
}
func (m *SDataSourceManager) getMetricDescriptions(influxdbMeasurements []monitor.InfluxMeasurement) (
descMeasurements []monitor.InfluxMeasurement) {
userCred := auth.AdminCredential()
listInput := new(monitor.MetricListInput)
for _, measurement := range influxdbMeasurements {
listInput.Measurement.Names = append(listInput.Measurement.Names, measurement.Measurement)
}
query, err := MetricMeasurementManager.ListItemFilter(context.Background(), MetricMeasurementManager.Query(), userCred,
*listInput)
if err != nil {
log.Errorln(errors.Wrap(err, "DataSourceManager getMetricDescriptions error"))
}
descriMeasurements, err := MetricMeasurementManager.getMeasurement(query)
if err != nil {
log.Errorln(errors.Wrap(err, "DataSourceManager getMeasurement error"))
}
if len(descriMeasurements) != 0 {
measurementsIns := make([]interface{}, len(descriMeasurements))
for i, _ := range descriMeasurements {
measurementsIns[i] = &descriMeasurements[i]
}
details := MetricMeasurementManager.FetchCustomizeColumns(context.Background(), userCred, jsonutils.NewDict(), measurementsIns,
stringutils2.NewSortedStrings([]string{}), true)
for i, measureDes := range descriMeasurements {
for j, _ := range influxdbMeasurements {
if measureDes.Name == influxdbMeasurements[j].Measurement {
if len(measureDes.DisplayName) != 0 {
influxdbMeasurements[j].MeasurementDisplayName = measureDes.DisplayName
}
if len(measureDes.ResType) != 0 {
influxdbMeasurements[j].ResType = measureDes.ResType
}
if measureDes.Score != 0 {
influxdbMeasurements[j].Score = measureDes.Score
}
fieldDesMap := make(map[string]monitor.MetricFieldDetail, 0)
fields := make([]string, 0)
fieldKeys := stringutils2.NewSortedStrings(influxdbMeasurements[j].FieldKey)
for fieldIndex, fieldDes := range details[i].MetricFields {
if len(fieldDes.DisplayName) != 0 {
fieldDesMap[fieldDes.Name] = details[i].MetricFields[fieldIndex]
}
if fieldKeys.Contains(fieldDes.Name) {
fields = append(fields, fieldDes.Name)
}
}
influxdbMeasurements[j].FieldDescriptions = fieldDesMap
influxdbMeasurements[j].Database = measureDes.Database
influxdbMeasurements[j].FieldKey = fields
descMeasurements = append(descMeasurements, influxdbMeasurements[j])
}
}
}
}
return
}
func (m *SDataSourceManager) filterMeasurementsByTime(
measurements []monitor.InfluxMeasurement, query jsonutils.JSONObject, tagFilter *monitor.MetricQueryTag) ([]monitor.InfluxMeasurement, error) {
timeF, err := m.getFromAndToFromParam(query)
if err != nil {
return nil, err
}
filterMeasurements, err := m.getFilterMeasurementsParallel(timeF.From, timeF.To, measurements, tagFilter)
if err != nil {
return nil, err
}
return filterMeasurements, nil
}
type timeFilter struct {
From string
To string
}
func (m *SDataSourceManager) getFromAndToFromParam(query jsonutils.JSONObject) (timeFilter, error) {
timeF := timeFilter{}
from, _ := query.GetString("from")
if len(from) == 0 {
from = "6h"
}
to, _ := query.GetString("to")
if len(to) == 0 {
to = "now"
}
timeFilter := monitor.AlertQuery{
From: from,
To: to,
}
err := validators.ValidateFromAndToValue(timeFilter)
if err != nil {
return timeF, err
}
timeF.From = from
timeF.To = to
return timeF, nil
}
func (m *SDataSourceManager) getFilterMeasurementsParallel(from, to string,
measurements []monitor.InfluxMeasurement, tagFilter *monitor.MetricQueryTag) ([]monitor.InfluxMeasurement, error) {
filterMeasurements := make([]monitor.InfluxMeasurement, len(measurements))
ctx, cancel := context.WithTimeout(context.Background(), time.Second*60)
defer cancel()
measurementQueryGroup, _ := errgroup.WithContext(ctx)
for i := range measurements {
index := i
tmp := measurements[index]
measurementQueryGroup.Go(func() error {
errCh := make(chan error)
go func() {
ret, err := m.getFilterMeasurement(from, to, tmp, tagFilter)
if err != nil {
errCh <- errors.Wrapf(err, "getFilterMeasurement %d", index)
return
}
filterMeasurements[index] = *ret
errCh <- nil
}()
for {
select {
case <-ctx.Done():
return errors.Wrap(ctx.Err(), "filter measurement from TSDB")
case err := <-errCh:
if err != nil {
return err
}
return nil
}
}
})
}
if err := measurementQueryGroup.Wait(); err != nil {
return nil, errors.Wrap(err, "measuremetnQueryGroup.Wait()")
}
ret := make([]monitor.InfluxMeasurement, 0)
for _, fm := range filterMeasurements {
if len(fm.Measurement) != 0 {
tmp := fm
ret = append(ret, tmp)
}
}
return ret, nil
}
func (m *SDataSourceManager) GetTSDBDriver() (tsdb.TsdbQueryEndpoint, error) {
ep, err := datasource.GetDefaultQueryEndpoint()
if err != nil {
return nil, errors.Wrap(err, "GetDefaultQueryEndpoint")
}
return ep, nil
}
func (m *SDataSourceManager) getFilterMeasurement(from, to string, measurement monitor.InfluxMeasurement, tagFilter *monitor.MetricQueryTag) (*monitor.InfluxMeasurement, error) {
dds, _ := datasource.GetDefaultSource("")
ep, err := m.GetTSDBDriver()
if err != nil {
return nil, errors.Wrap(err, "GetDefaultQueryEndpoint")
}
retMs, err := ep.FilterMeasurement(context.Background(), dds, from, to, &measurement, tagFilter)
if err != nil {
return nil, errors.Wrap(err, "Get endpoint filtered measurement")
}
return retMs, nil
}
func renderTimeFilter(from, to string) string {
if strings.Contains(from, "now-") {
from = "now() - " + strings.Replace(from, "now-", "", 1)
} else {
from = "now() - " + from
}
tmp := ""
if to != "now" && to != "" {
tmp = " and time < now() - " + strings.Replace(to, "now-", "", 1)
}
return fmt.Sprintf("time > %s%s", from, tmp)
}
func (m *SDataSourceManager) GetMetricMeasurement(userCred mcclient.TokenCredential, query jsonutils.JSONObject, tagFilter *monitor.MetricQueryTag) (*monitor.InfluxMeasurement, error) {
database, _ := query.GetString("database")
if database == "" {
return nil, merrors.NewArgIsEmptyErr("database")
}
measurement, _ := query.GetString("measurement")
if measurement == "" {
return nil, merrors.NewArgIsEmptyErr("measurement")
}
field, _ := query.GetString("field")
if field == "" {
return nil, merrors.NewArgIsEmptyErr("field")
}
from, _ := query.GetString("from")
if len(from) == 0 {
return nil, merrors.NewArgIsEmptyErr("from")
}
timeF, err := m.getFromAndToFromParam(query)
if err != nil {
return nil, errors.Wrap(err, "getFromAndToFromParam")
}
//skipCheckSeries := jsonutils.QueryBoolean(query, "skip_check_series", false)
output := new(monitor.InfluxMeasurement)
output.Measurement = measurement
output.Database = database
output.TagValue = make(map[string][]string, 0)
output.TagNameIdValueMap = make(map[string]map[string]string)
if measureDes, ok := MetricMeasurementManager.GetCache().Get(measurement); ok && len(measureDes.ResType) != 0 {
output.ResType = measureDes.ResType
}
nameTag := monitor.MEASUREMENT_TAG_KEYWORD[output.ResType]
idTag := monitor.MEASUREMENT_TAG_ID[output.ResType]
output.FieldKey = []string{field}
// 只查询过去 30m 的指标
if timeF.To == "now" {
timeF.From = "30m"
}
if err := getTagValues(userCred, output, timeF, tagFilter, true, nameTag, idTag); err != nil {
return nil, errors.Wrap(err, "getTagValues error")
}
m.filterRtnTags(output)
return output, nil
}
func (m *SDataSourceManager) filterRtnTags(output *monitor.InfluxMeasurement) {
for _, tag := range []string{hostconsts.TELEGRAF_TAG_KEY_BRAND, hostconsts.TELEGRAF_TAG_KEY_PLATFORM,
hostconsts.TELEGRAF_TAG_KEY_HYPERVISOR} {
if val, ok := output.TagValue[tag]; ok {
output.TagValue[hostconsts.TELEGRAF_TAG_KEY_BRAND] = val
break
}
}
for _, tag := range []string{
"source", hostconsts.TELEGRAF_TAG_KEY_HOST_TYPE, hostconsts.TELEGRAF_TAG_KEY_RES_TYPE,
"is_vm", "os_type", hostconsts.TELEGRAF_TAG_KEY_PLATFORM, hostconsts.TELEGRAF_TAG_KEY_HYPERVISOR,
"domain_name", "region", "ips", "vip", "vip_eip", "eip", "eip_mode",
labels.MetricName, translator.UNION_RESULT_NAME,
} {
if _, ok := output.TagValue[tag]; ok {
delete(output.TagValue, tag)
}
}
// hide VictoriaMetrics telegraf db tag
if val, ok := output.TagValue[VICTORIA_METRICS_DB_TAG_KEY]; ok {
if len(val) == 1 && val[0] == VICTORIA_METRICS_DB_TAG_VAL_TELEGRAF {
delete(output.TagValue, VICTORIA_METRICS_DB_TAG_KEY)
}
}
repTag := make([]string, 0)
for tag, _ := range output.TagValue {
repTag = append(repTag, tag)
}
output.TagKey = repTag
}
func (m *SDataSourceManager) filterTagValue(measurement monitor.InfluxMeasurement, timeF timeFilter,
db *influxdb.SInfluxdb, tagValChan *influxdbTagValueChan, tagFilter string) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
defer cancel()
tagValGroup2, _ := errgroup.WithContext(ctx)
tagValChan2 := influxdbTagValueChan{
rtnChan: make(chan map[string][]string, len(measurement.TagKey)),
count: len(measurement.TagKey),
}
for i, _ := range measurement.TagKey {
tmpkey := measurement.TagKey[i]
tagValGroup2.Go(func() error {
return m.getFilterMeasurementTagValue(&tagValChan2, timeF.From, timeF.To, measurement.FieldKey[0],
tmpkey, measurement, db, tagFilter)
})
}
tagValGroup2.Go(func() error {
valMaps := make(map[string][]string, 0)
for i := 0; i < tagValChan2.count; i++ {
select {
case valMap := <-tagValChan2.rtnChan:
for key, val := range valMap {
if _, ok := valMaps[key]; ok {
valMaps[key] = append(valMaps[key], val...)
continue
}
valMaps[key] = val
}
case <-ctx.Done():
return fmt.Errorf("filter getFilterMeasurementTagValue time out")
}
}
tagValChan.rtnChan <- valMaps
close(tagValChan2.rtnChan)
return nil
})
return tagValGroup2.Wait()
}
func tagValUnion(measurement *monitor.InfluxMeasurement, rtn map[string][]string) {
keys := make([]string, 0)
for _, tag := range measurement.TagKey {
if rtnTagVal, ok := rtn[tag]; ok {
keys = append(keys, tag)
if _, ok := measurement.TagValue[tag]; !ok {
measurement.TagValue[tag] = rtnTagVal
continue
}
measurement.TagValue[tag] = union(measurement.TagValue[tag], rtnTagVal)
}
}
measurement.TagKey = keys
}
func union(slice1, slice2 []string) []string {
m := make(map[string]int)
for _, v := range slice1 {
m[v]++
}
for _, v := range slice2 {
times, _ := m[v]
if times == 0 {
slice1 = append(slice1, v)
}
}
return slice1
}
type InfluxdbSubscription struct {
SubName string
DataBase string
//retention policy
Rc string
Url string
}
func (m *SDataSourceManager) AddSubscription(subscription InfluxdbSubscription) error {
query := fmt.Sprintf("CREATE SUBSCRIPTION %s ON %s.%s DESTINATIONS ALL %s",
jsonutils.NewString(subscription.SubName).String(),
jsonutils.NewString(subscription.DataBase).String(),
jsonutils.NewString(subscription.Rc).String(),
strings.ReplaceAll(jsonutils.NewString(subscription.Url).String(), "\"", "'"),
)
dataSource, err := datasource.GetDefaultSource("")
if err != nil {
return errors.Wrap(err, "s.GetDefaultSource")
}
db := influxdb.NewInfluxdbWithDebug(dataSource.Url, true)
db.SetDatabase(subscription.DataBase)
rtn, err := db.GetQuery(query)
if err != nil {
return err
}
for _, result := range rtn {
for _, obj := range result {
objJson := jsonutils.Marshal(&obj)
log.Errorln(objJson.String())
}
}
return nil
}
func (m *SDataSourceManager) DropSubscription(subscription InfluxdbSubscription) error {
query := fmt.Sprintf("DROP SUBSCRIPTION %s ON %s.%s", jsonutils.NewString(subscription.SubName).String(),
jsonutils.NewString(subscription.DataBase).String(),
jsonutils.NewString(subscription.Rc).String(),
)
dataSource, err := datasource.GetDefaultSource("")
if err != nil {
return errors.Wrap(err, "s.GetDefaultSource")
}
db := influxdb.NewInfluxdb(dataSource.Url)
db.SetDatabase(subscription.DataBase)
rtn, err := db.Query(query)
if err != nil {
return err
}
for _, result := range rtn {
for _, obj := range result {
objJson := jsonutils.Marshal(&obj)
log.Errorln(objJson.String())
}
}
return nil
}
/*func getAttributesOnMeasurement(database, tp string, output *monitor.InfluxMeasurement, db *influxdb.SInfluxdb) error {
query := fmt.Sprintf("SHOW %s KEYS ON %s FROM %s", tp, database, output.Measurement)
dbRtn, err := db.Query(query)
if err != nil {
return errors.Wrapf(err, "SHOW MEASUREMENTS: %s", query)
}
if len(dbRtn) == 0 || len(dbRtn[0]) == 0 {
return nil
}
res := dbRtn[0][0]
tmpDict := jsonutils.NewDict()
tmpArr := jsonutils.NewArray()
for i := range res.Values {
v, _ := res.Values[i][0].(*jsonutils.JSONString).GetString()
if filterTagKey(v) {
continue
}
tmpArr.Add(res.Values[i][0])
}
tmpDict.Add(tmpArr, res.Columns[0])
err = tmpDict.Unmarshal(output)
if err != nil {
return errors.Wrap(err, "measurement unmarshal error")
}
return nil
}*/
func inferNameIdTagsFromSeries(series monitor.TimeSeriesSlice) (nameTag, idTag string) {
if len(series) == 0 {
return "", ""
}
tagKeys := sets.NewString()
for _, s := range series {
for k := range s.Tags {
tagKeys.Insert(k)
}
}
for resType, nameKey := range monitor.MEASUREMENT_TAG_KEYWORD {
idKey := monitor.MEASUREMENT_TAG_ID[resType]
if nameKey != "" && idKey != "" && tagKeys.Has(nameKey) && tagKeys.Has(idKey) {
return nameKey, idKey
}
}
return "", ""
}
func buildTagNameIdValueMap(series monitor.TimeSeriesSlice, nameTag, idTag string) map[string]map[string]string {
if nameTag == "" || idTag == "" {
nameTag, idTag = inferNameIdTagsFromSeries(series)
}
if nameTag == "" || idTag == "" {
return nil
}
result := make(map[string]map[string]string)
for _, s := range series {
nameVal := renderTagVal(s.Tags[nameTag])
idVal := renderTagVal(s.Tags[idTag])
// id 值通常为 UUID不能用 filterTagValue 过滤
if len(nameVal) == 0 || len(idVal) == 0 || nameVal == "null" || idVal == "null" || filterTagValue(nameVal) {
continue
}
if result[nameTag] == nil {
result[nameTag] = make(map[string]string)
}
if _, exists := result[nameTag][nameVal]; !exists {
result[nameTag][nameVal] = idVal
}
}
if len(result) == 0 {
return nil
}
return result
}
func getTagValues(userCred mcclient.TokenCredential, output *monitor.InfluxMeasurement, timeF timeFilter, tagFilter *monitor.MetricQueryTag, skipCheckSeries bool, nameTag, idTag string) error {
mq := monitor.MetricQuery{
Database: output.Database,
Measurement: output.Measurement,
Selects: []monitor.MetricQuerySelect{
{
{
Type: "field",
Params: []string{output.FieldKey[0]},
},
{
Type: "last",
},
},
},
GroupBy: []monitor.MetricQueryPart{
{
Type: "field",
Params: []string{"*"},
},
},
}
if tagFilter != nil {
mq.Tags = []monitor.MetricQueryTag{
{
Key: tagFilter.Key,
Operator: tagFilter.Operator,
Value: tagFilter.Value,
},
}
}
aq := &monitor.AlertQuery{
Model: mq,
From: timeF.From,
To: timeF.To,
}
q := monitor.MetricQueryInput{
From: timeF.From,
To: timeF.To,
Interval: "3m",
MetricQuery: []*monitor.AlertQuery{
aq,
},
SkipCheckSeries: skipCheckSeries,
}
ret, err := doQuery(userCred, q)
if err != nil {
return errors.Wrapf(err, "getTagValues query error %s", jsonutils.Marshal(q))
}
// 2. group tag and values
tagValMap := make(map[string][]string)
tagKeys := make([]string, 0)
if len(ret.Series) == 0 {
return nil
}
if nameTag == "" || idTag == "" {
nameTag, idTag = inferNameIdTagsFromSeries(ret.Series)
}
if len(output.ResType) == 0 && nameTag != "" && idTag != "" {
for resType, keyword := range monitor.MEASUREMENT_TAG_KEYWORD {
if keyword == nameTag && monitor.MEASUREMENT_TAG_ID[resType] == idTag {
output.ResType = resType
break
}
}
}
for _, s := range ret.Series {
tagMap := s.Tags
for key, valStr := range tagMap {
valStr = renderTagVal(valStr)
if len(valStr) == 0 || valStr == "null" || filterTagValue(valStr) {
continue
}
if filterTagKey(key) {
continue
}
if valArr, ok := tagValMap[key]; ok {
if !utils.IsInStringArray(valStr, valArr) {
tagValMap[key] = append(valArr, valStr)
}
continue
}
tagValMap[key] = []string{valStr}
tagKeys = append(tagKeys, key)
}
}
output.TagValue = tagValMap
sort.Strings(tagKeys)
output.TagKey = tagKeys
output.TagNameIdValueMap = buildTagNameIdValueMap(ret.Series, nameTag, idTag)
return nil
}
func getTagValue(database string, output *monitor.InfluxMeasurement, db *influxdb.SInfluxdb) error {
if len(output.TagKey) == 0 {
return nil
}
tagKeyStr := jsonutils.NewStringArray(output.TagKey).String()
tagKeyStr = tagKeyStr[1 : len(tagKeyStr)-1]
dbRtn, err := db.Query(fmt.Sprintf("SHOW TAG VALUES ON %s FROM %s WITH KEY IN (%s)", database, output.Measurement, tagKeyStr))
if err != nil {
return err
}
res := dbRtn[0][0]
tagValue := make(map[string][]string, 0)
keys := strings.Join(output.TagKey, ",")
for i := range res.Values {
val, _ := res.Values[i][0].(*jsonutils.JSONString).GetString()
if !strings.Contains(keys, val) {
continue
}
if _, ok := tagValue[val]; !ok {
tagValue[val] = make([]string, 0)
}
tag, _ := res.Values[i][1].(*jsonutils.JSONString).GetString()
if filterTagValue(tag) {
delete(tagValue, val)
continue
}
tagValue[val] = append(tagValue[val], tag)
}
output.TagValue = tagValue
//TagKey == TagValue.keys
tagK := make([]string, 0)
for tag, _ := range output.TagValue {
tagK = append(tagK, tag)
}
output.TagKey = tagK
return nil
}
type influxdbTagValueChan struct {
rtnChan chan map[string][]string
count int
}
func (m *SDataSourceManager) getFilterMeasurementTagValue(tagValueChan *influxdbTagValueChan, from string,
to string, field string, tagKey string,
measurement monitor.InfluxMeasurement, db *influxdb.SInfluxdb, tagFilter string) error {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf(`SELECT last("%s") FROM "%s" WHERE %s `, field, measurement.Measurement,
renderTimeFilter(from, to)))
if len(tagFilter) != 0 {
buffer.WriteString(fmt.Sprintf(` AND %s `, tagFilter))
}
buffer.WriteString(fmt.Sprintf(` GROUP BY %q`, tagKey))
log.Errorln(buffer.String())
rtn, err := db.Query(buffer.String())
if err != nil {
return errors.Wrap(err, "getFilterMeasurementTagValue query error")
}
tagValMap := make(map[string][]string)
if len(rtn) != 0 && len(rtn[0]) != 0 {
for rtnIndex, _ := range rtn {
for serieIndex, _ := range rtn[rtnIndex] {
tagMap, _ := rtn[rtnIndex][serieIndex].Tags.GetMap()
for key, valObj := range tagMap {
valStr, _ := valObj.GetString()
valStr = renderTagVal(valStr)
if len(valStr) == 0 || valStr == "null" || filterTagValue(valStr) {
continue
}
if !utils.IsInStringArray(key, measurement.TagKey) {
//measurement.TagKey = append(measurement.TagKey, key)
continue
}
if valArr, ok := tagValMap[key]; ok {
if !utils.IsInStringArray(valStr, valArr) {
tagValMap[key] = append(valArr, valStr)
}
continue
}
tagValMap[key] = []string{valStr}
}
}
}
measurement.TagValue = tagValMap
}
tagValueChan.rtnChan <- tagValMap
return nil
}
func renderTagVal(val string) string {
return strings.ReplaceAll(val, "+", " ")
}
func floatEquals(a, b float64) bool {
eps := 0.000000001
if math.Abs(a-b) < eps {
return true
}
return false
}
var filterKey = []string{"perf_instance", "res_type", "status", "cloudregion", "os_type", "is_vm"}
func filterTagKey(key string) bool {
whiteListIdKeys := sets.NewString("dev_id", "die_id")
if whiteListIdKeys.Has(key) {
return false
}
if strings.Contains(key, "_id") {
return true
}
if key == "perf_instance" {
return true
}
return false
}
func filterTagValue(val string) bool {
if compile.MatchString(val) {
return true
}
return false
}