mirror of
https://github.com/ccfos/nightingale.git
synced 2026-03-03 14:38:55 +00:00
Compare commits
6 Commits
refactor-i
...
stable
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df9ba52e71 | ||
|
|
ba6d2b664d | ||
|
|
2ddcf507f9 | ||
|
|
17cc588a9d | ||
|
|
e9c7eef546 | ||
|
|
7451ad2e23 |
2
.github/workflows/n9e.yml
vendored
2
.github/workflows/n9e.yml
vendored
@@ -5,7 +5,7 @@ on:
|
||||
tags:
|
||||
- 'v*'
|
||||
env:
|
||||
GO_VERSION: 1.23
|
||||
GO_VERSION: 1.18
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
- 👉 [文档中心](https://flashcat.cloud/docs/) | [下载中心](https://flashcat.cloud/download/nightingale/)
|
||||
- ❤️ [报告 Bug](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=&projects=&template=question.yml)
|
||||
- ℹ️ 为了提供更快速的访问体验,上述文档和下载站点托管于 [FlashcatCloud](https://flashcat.cloud)
|
||||
- 💡 前后端代码分离,前端代码仓库:[https://github.com/n9e/fe](https://github.com/n9e/fe)
|
||||
|
||||
## 功能特点
|
||||
|
||||
|
||||
@@ -65,7 +65,6 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
configCvalCache := memsto.NewCvalCache(ctx, syncStats)
|
||||
|
||||
promClients := prom.NewPromClient(ctx)
|
||||
dispatch.InitRegisterQueryFunc(promClients)
|
||||
tdengineClients := tdengine.NewTdengineClient(ctx, config.Alert.Heartbeat)
|
||||
|
||||
externalProcessors := process.NewExternalProcessors()
|
||||
@@ -103,7 +102,7 @@ func Start(alertc aconf.Alert, pushgwc pconf.Pushgw, syncStats *memsto.Stats, al
|
||||
naming := naming.NewNaming(ctx, alertc.Heartbeat, alertStats)
|
||||
|
||||
writers := writer.NewWriters(pushgwc)
|
||||
record.NewScheduler(alertc, recordingRuleCache, promClients, writers, alertStats, datasourceCache)
|
||||
record.NewScheduler(alertc, recordingRuleCache, promClients, writers, alertStats)
|
||||
|
||||
eval.NewScheduler(alertc, externalProcessors, alertRuleCache, targetCache, targetsOfAlertRulesCache,
|
||||
busiGroupCache, alertMuteCache, datasourceCache, promClients, tdendgineClients, naming, ctx, alertStats)
|
||||
|
||||
@@ -38,7 +38,7 @@ func NewSyncStats() *Stats {
|
||||
Subsystem: subsystem,
|
||||
Name: "rule_eval_error_total",
|
||||
Help: "Number of rule eval error.",
|
||||
}, []string{"datasource", "stage", "busi_group", "rule_id"})
|
||||
}, []string{"datasource", "stage"})
|
||||
|
||||
CounterQueryDataErrorTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
package models
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/pkg/unit"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
type AnomalyPoint struct {
|
||||
Key string `json:"key"`
|
||||
Labels model.Metric `json:"labels"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Value float64 `json:"value"`
|
||||
Severity int `json:"severity"`
|
||||
Triggered bool `json:"triggered"`
|
||||
Query string `json:"query"`
|
||||
Values string `json:"values"`
|
||||
ValuesUnit map[string]unit.FormattedValue `json:"values_unit"`
|
||||
RecoverConfig RecoverConfig `json:"recover_config"`
|
||||
Key string `json:"key"`
|
||||
Labels model.Metric `json:"labels"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Value float64 `json:"value"`
|
||||
Severity int `json:"severity"`
|
||||
Triggered bool `json:"triggered"`
|
||||
Query string `json:"query"`
|
||||
Values string `json:"values"`
|
||||
RecoverConfig models.RecoverConfig `json:"recover_config"`
|
||||
}
|
||||
|
||||
func NewAnomalyPoint(key string, labels map[string]string, ts int64, value float64, severity int) AnomalyPoint {
|
||||
@@ -1,7 +1,6 @@
|
||||
package dispatch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -14,10 +13,8 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/poster"
|
||||
promsdk "github.com/ccfos/nightingale/v6/pkg/prom"
|
||||
"github.com/ccfos/nightingale/v6/pkg/tplx"
|
||||
"github.com/ccfos/nightingale/v6/prom"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/toolkits/pkg/concurrent/semaphore"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
@@ -30,18 +27,6 @@ type Consumer struct {
|
||||
promClients *prom.PromClientMap
|
||||
}
|
||||
|
||||
func InitRegisterQueryFunc(promClients *prom.PromClientMap) {
|
||||
tplx.RegisterQueryFunc(func(datasourceID int64, promql string) model.Value {
|
||||
if promClients.IsNil(datasourceID) {
|
||||
return nil
|
||||
}
|
||||
|
||||
readerClient := promClients.GetCli(datasourceID)
|
||||
value, _, _ := readerClient.Query(context.Background(), promql, time.Now())
|
||||
return value
|
||||
})
|
||||
}
|
||||
|
||||
// 创建一个 Consumer 实例
|
||||
func NewConsumer(alerting aconf.Alerting, ctx *ctx.Context, dispatch *Dispatch, promClients *prom.PromClientMap) *Consumer {
|
||||
return &Consumer{
|
||||
@@ -128,7 +113,7 @@ func (e *Consumer) persist(event *models.AlertCurEvent) {
|
||||
event.Id, err = poster.PostByUrlsWithResp[int64](e.ctx, "/v1/n9e/event-persist", event)
|
||||
if err != nil {
|
||||
logger.Errorf("event:%+v persist err:%v", event, err)
|
||||
e.dispatch.Astats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", event.DatasourceId), "persist_event", event.GroupName, fmt.Sprintf("%v", event.RuleId)).Inc()
|
||||
e.dispatch.Astats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", event.DatasourceId), "persist_event").Inc()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -136,7 +121,7 @@ func (e *Consumer) persist(event *models.AlertCurEvent) {
|
||||
err := models.EventPersist(e.ctx, event)
|
||||
if err != nil {
|
||||
logger.Errorf("event%+v persist err:%v", event, err)
|
||||
e.dispatch.Astats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", event.DatasourceId), "persist_event", event.GroupName, fmt.Sprintf("%v", event.RuleId)).Inc()
|
||||
e.dispatch.Astats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", event.DatasourceId), "persist_event").Inc()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +169,7 @@ func (e *Consumer) queryRecoveryVal(event *models.AlertCurEvent) {
|
||||
logger.Errorf("rule_eval:%s promql:%s, warnings:%v", getKey(event), promql, warnings)
|
||||
}
|
||||
|
||||
anomalyPoints := models.ConvertAnomalyPoints(value)
|
||||
anomalyPoints := common.ConvertAnomalyPoints(value)
|
||||
if len(anomalyPoints) == 0 {
|
||||
logger.Warningf("rule_eval:%s promql:%s, result is empty", getKey(event), promql)
|
||||
event.AnnotationsJSON["recovery_promql_error"] = fmt.Sprintf("promql:%s error:%s", promql, "result is empty")
|
||||
|
||||
@@ -139,12 +139,6 @@ func (e *Dispatch) HandleEventNotify(event *models.AlertCurEvent, isSubscribe bo
|
||||
if rule == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if e.blockEventNotify(rule, event) {
|
||||
logger.Infof("block event notify: rule_id:%d event:%+v", rule.Id, event)
|
||||
return
|
||||
}
|
||||
|
||||
fillUsers(event, e.userCache, e.userGroupCache)
|
||||
|
||||
var (
|
||||
@@ -181,25 +175,6 @@ func (e *Dispatch) HandleEventNotify(event *models.AlertCurEvent, isSubscribe bo
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Dispatch) blockEventNotify(rule *models.AlertRule, event *models.AlertCurEvent) bool {
|
||||
ruleType := rule.GetRuleType()
|
||||
|
||||
// 若为机器则先看机器是否删除
|
||||
if ruleType == models.HOST {
|
||||
host, ok := e.targetCache.Get(event.TagsMap["ident"])
|
||||
if !ok || host == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复通知,检测规则配置是否改变
|
||||
// if event.IsRecovered && event.RuleHash != rule.Hash() {
|
||||
// return true
|
||||
// }
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *Dispatch) handleSubs(event *models.AlertCurEvent) {
|
||||
// handle alert subscribes
|
||||
subscribes := make([]*models.AlertSubscribe, 0)
|
||||
@@ -291,12 +266,10 @@ func (e *Dispatch) Send(rule *models.AlertRule, event *models.AlertCurEvent, not
|
||||
e.SendCallbacks(rule, notifyTarget, event)
|
||||
|
||||
// handle global webhooks
|
||||
if !event.OverrideGlobalWebhook() {
|
||||
if e.alerting.WebhookBatchSend {
|
||||
sender.BatchSendWebhooks(e.ctx, notifyTarget.ToWebhookList(), event, e.Astats)
|
||||
} else {
|
||||
sender.SingleSendWebhooks(e.ctx, notifyTarget.ToWebhookList(), event, e.Astats)
|
||||
}
|
||||
if e.alerting.WebhookBatchSend {
|
||||
sender.BatchSendWebhooks(e.ctx, notifyTarget.ToWebhookList(), event, e.Astats)
|
||||
} else {
|
||||
sender.SingleSendWebhooks(e.ctx, notifyTarget.ToWebhookList(), event, e.Astats)
|
||||
}
|
||||
|
||||
// handle plugin call
|
||||
@@ -310,10 +283,10 @@ func (e *Dispatch) Send(rule *models.AlertRule, event *models.AlertCurEvent, not
|
||||
}
|
||||
|
||||
func (e *Dispatch) SendCallbacks(rule *models.AlertRule, notifyTarget *NotifyTarget, event *models.AlertCurEvent) {
|
||||
|
||||
uids := notifyTarget.ToUidList()
|
||||
urls := notifyTarget.ToCallbackList()
|
||||
whMap := notifyTarget.ToWebhookMap()
|
||||
ogw := event.OverrideGlobalWebhook()
|
||||
for _, urlStr := range urls {
|
||||
if len(urlStr) == 0 {
|
||||
continue
|
||||
@@ -321,7 +294,7 @@ func (e *Dispatch) SendCallbacks(rule *models.AlertRule, notifyTarget *NotifyTar
|
||||
|
||||
cbCtx := sender.BuildCallBackContext(e.ctx, urlStr, rule, []*models.AlertCurEvent{event}, uids, e.userCache, e.alerting.WebhookBatchSend, e.Astats)
|
||||
|
||||
if wh, ok := whMap[cbCtx.CallBackURL]; !ogw && ok && wh.Enable {
|
||||
if wh, ok := whMap[cbCtx.CallBackURL]; ok && wh.Enable {
|
||||
logger.Debugf("SendCallbacks: webhook[%s] is in global conf.", cbCtx.CallBackURL)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -96,7 +96,8 @@ func (s *Scheduler) syncAlertRules() {
|
||||
|
||||
ruleType := rule.GetRuleType()
|
||||
if rule.IsPrometheusRule() || rule.IsLokiRule() || rule.IsTdengineRule() {
|
||||
datasourceIds := s.datasourceCache.GetIDsByDsCateAndQueries(rule.Cate, rule.DatasourceQueries)
|
||||
datasourceIds := s.promClients.Hit(rule.DatasourceIdsJson)
|
||||
datasourceIds = append(datasourceIds, s.tdengineClients.Hit(rule.DatasourceIdsJson)...)
|
||||
for _, dsId := range datasourceIds {
|
||||
if !naming.DatasourceHashRing.IsHit(strconv.FormatInt(dsId, 10), fmt.Sprintf("%d", rule.Id), s.aconf.Heartbeat.Endpoint) {
|
||||
continue
|
||||
@@ -132,8 +133,7 @@ func (s *Scheduler) syncAlertRules() {
|
||||
} else {
|
||||
// 如果 rule 不是通过 prometheus engine 来告警的,则创建为 externalRule
|
||||
// if rule is not processed by prometheus engine, create it as externalRule
|
||||
dsIds := s.datasourceCache.GetIDsByDsCateAndQueries(rule.Cate, rule.DatasourceQueries)
|
||||
for _, dsId := range dsIds {
|
||||
for _, dsId := range rule.DatasourceIdsJson {
|
||||
ds := s.datasourceCache.GetById(dsId)
|
||||
if ds == nil {
|
||||
logger.Debugf("datasource %d not found", dsId)
|
||||
|
||||
@@ -3,14 +3,11 @@ package eval
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/common"
|
||||
@@ -20,40 +17,31 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/pkg/hash"
|
||||
"github.com/ccfos/nightingale/v6/pkg/parser"
|
||||
promsdk "github.com/ccfos/nightingale/v6/pkg/prom"
|
||||
"github.com/ccfos/nightingale/v6/pkg/unit"
|
||||
"github.com/ccfos/nightingale/v6/prom"
|
||||
"github.com/ccfos/nightingale/v6/tdengine"
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
"github.com/toolkits/pkg/str"
|
||||
)
|
||||
|
||||
type AlertRuleWorker struct {
|
||||
DatasourceId int64
|
||||
Quit chan struct{}
|
||||
Inhibit bool
|
||||
Severity int
|
||||
datasourceId int64
|
||||
quit chan struct{}
|
||||
inhibit bool
|
||||
severity int
|
||||
|
||||
Rule *models.AlertRule
|
||||
rule *models.AlertRule
|
||||
|
||||
Processor *process.Processor
|
||||
processor *process.Processor
|
||||
|
||||
PromClients *prom.PromClientMap
|
||||
TdengineClients *tdengine.TdengineClientMap
|
||||
Ctx *ctx.Context
|
||||
|
||||
Scheduler *cron.Cron
|
||||
|
||||
HostAndDeviceIdentCache sync.Map
|
||||
|
||||
DeviceIdentHook func(arw *AlertRuleWorker, paramQuery models.ParamQuery) ([]string, error)
|
||||
promClients *prom.PromClientMap
|
||||
tdengineClients *tdengine.TdengineClientMap
|
||||
ctx *ctx.Context
|
||||
}
|
||||
|
||||
const (
|
||||
GET_RULE_CONFIG = "get_rule_config"
|
||||
GET_Processor = "get_Processor"
|
||||
GET_PROCESSOR = "get_processor"
|
||||
CHECK_QUERY = "check_query_config"
|
||||
GET_CLIENT = "get_client"
|
||||
QUERY_DATA = "query_data"
|
||||
@@ -67,110 +55,90 @@ const (
|
||||
Inner JoinType = "inner"
|
||||
)
|
||||
|
||||
func NewAlertRuleWorker(rule *models.AlertRule, datasourceId int64, Processor *process.Processor, promClients *prom.PromClientMap, tdengineClients *tdengine.TdengineClientMap, ctx *ctx.Context) *AlertRuleWorker {
|
||||
func NewAlertRuleWorker(rule *models.AlertRule, datasourceId int64, processor *process.Processor, promClients *prom.PromClientMap, tdengineClients *tdengine.TdengineClientMap, ctx *ctx.Context) *AlertRuleWorker {
|
||||
arw := &AlertRuleWorker{
|
||||
DatasourceId: datasourceId,
|
||||
Quit: make(chan struct{}),
|
||||
Rule: rule,
|
||||
Processor: Processor,
|
||||
datasourceId: datasourceId,
|
||||
quit: make(chan struct{}),
|
||||
rule: rule,
|
||||
processor: processor,
|
||||
|
||||
PromClients: promClients,
|
||||
TdengineClients: tdengineClients,
|
||||
Ctx: ctx,
|
||||
HostAndDeviceIdentCache: sync.Map{},
|
||||
DeviceIdentHook: func(arw *AlertRuleWorker, paramQuery models.ParamQuery) ([]string, error) {
|
||||
return nil, nil
|
||||
},
|
||||
promClients: promClients,
|
||||
tdengineClients: tdengineClients,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
interval := rule.PromEvalInterval
|
||||
if interval <= 0 {
|
||||
interval = 10
|
||||
}
|
||||
|
||||
if rule.CronPattern == "" {
|
||||
rule.CronPattern = fmt.Sprintf("@every %ds", interval)
|
||||
}
|
||||
|
||||
arw.Scheduler = cron.New(cron.WithSeconds())
|
||||
|
||||
entryID, err := arw.Scheduler.AddFunc(rule.CronPattern, func() {
|
||||
arw.Eval()
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Errorf("alert rule %s add cron pattern error: %v", arw.Key(), err)
|
||||
}
|
||||
|
||||
Processor.ScheduleEntry = arw.Scheduler.Entry(entryID)
|
||||
|
||||
return arw
|
||||
}
|
||||
|
||||
func (arw *AlertRuleWorker) Key() string {
|
||||
return common.RuleKey(arw.DatasourceId, arw.Rule.Id)
|
||||
return common.RuleKey(arw.datasourceId, arw.rule.Id)
|
||||
}
|
||||
|
||||
func (arw *AlertRuleWorker) Hash() string {
|
||||
return str.MD5(fmt.Sprintf("%d_%s_%s_%d",
|
||||
arw.Rule.Id,
|
||||
arw.Rule.CronPattern,
|
||||
arw.Rule.RuleConfig,
|
||||
arw.DatasourceId,
|
||||
return str.MD5(fmt.Sprintf("%d_%d_%s_%d",
|
||||
arw.rule.Id,
|
||||
arw.rule.PromEvalInterval,
|
||||
arw.rule.RuleConfig,
|
||||
arw.datasourceId,
|
||||
))
|
||||
}
|
||||
|
||||
func (arw *AlertRuleWorker) Prepare() {
|
||||
arw.Processor.RecoverAlertCurEventFromDb()
|
||||
arw.processor.RecoverAlertCurEventFromDb()
|
||||
}
|
||||
|
||||
func (arw *AlertRuleWorker) Start() {
|
||||
logger.Infof("eval:%s started", arw.Key())
|
||||
arw.Scheduler.Start()
|
||||
interval := arw.rule.PromEvalInterval
|
||||
if interval <= 0 {
|
||||
interval = 10
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(time.Duration(interval) * time.Second)
|
||||
go func() {
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-arw.quit:
|
||||
return
|
||||
case <-ticker.C:
|
||||
arw.Eval()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (arw *AlertRuleWorker) Eval() {
|
||||
arw.Processor.EvalStart = time.Now().Unix()
|
||||
cachedRule := arw.Rule
|
||||
cachedRule := arw.rule
|
||||
if cachedRule == nil {
|
||||
// logger.Errorf("rule_eval:%s Rule not found", arw.Key())
|
||||
// logger.Errorf("rule_eval:%s rule not found", arw.Key())
|
||||
return
|
||||
}
|
||||
arw.Processor.Stats.CounterRuleEval.WithLabelValues().Inc()
|
||||
arw.HostAndDeviceIdentCache = sync.Map{}
|
||||
arw.processor.Stats.CounterRuleEval.WithLabelValues().Inc()
|
||||
|
||||
typ := cachedRule.GetRuleType()
|
||||
var (
|
||||
anomalyPoints []models.AnomalyPoint
|
||||
recoverPoints []models.AnomalyPoint
|
||||
err error
|
||||
)
|
||||
|
||||
var anomalyPoints []common.AnomalyPoint
|
||||
var recoverPoints []common.AnomalyPoint
|
||||
switch typ {
|
||||
case models.PROMETHEUS:
|
||||
anomalyPoints, err = arw.GetPromAnomalyPoint(cachedRule.RuleConfig)
|
||||
anomalyPoints = arw.GetPromAnomalyPoint(cachedRule.RuleConfig)
|
||||
case models.HOST:
|
||||
anomalyPoints, err = arw.GetHostAnomalyPoint(cachedRule.RuleConfig)
|
||||
anomalyPoints = arw.GetHostAnomalyPoint(cachedRule.RuleConfig)
|
||||
case models.TDENGINE:
|
||||
anomalyPoints, recoverPoints, err = arw.GetTdengineAnomalyPoint(cachedRule, arw.Processor.DatasourceId())
|
||||
anomalyPoints, recoverPoints = arw.GetTdengineAnomalyPoint(cachedRule, arw.processor.DatasourceId())
|
||||
case models.LOKI:
|
||||
anomalyPoints, err = arw.GetPromAnomalyPoint(cachedRule.RuleConfig)
|
||||
anomalyPoints = arw.GetPromAnomalyPoint(cachedRule.RuleConfig)
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Errorf("rule_eval:%s get anomaly point err:%s", arw.Key(), err.Error())
|
||||
if arw.processor == nil {
|
||||
logger.Warningf("rule_eval:%s processor is nil", arw.Key())
|
||||
return
|
||||
}
|
||||
|
||||
if arw.Processor == nil {
|
||||
logger.Warningf("rule_eval:%s Processor is nil", arw.Key())
|
||||
return
|
||||
}
|
||||
|
||||
if arw.Inhibit {
|
||||
pointsMap := make(map[string]models.AnomalyPoint)
|
||||
if arw.inhibit {
|
||||
pointsMap := make(map[string]common.AnomalyPoint)
|
||||
for _, point := range recoverPoints {
|
||||
// 对于恢复的事件,合并处理
|
||||
tagHash := process.TagHash(point)
|
||||
@@ -182,9 +150,9 @@ func (arw *AlertRuleWorker) Eval() {
|
||||
}
|
||||
|
||||
if p.Severity > point.Severity {
|
||||
hash := process.Hash(cachedRule.Id, arw.Processor.DatasourceId(), p)
|
||||
arw.Processor.DeleteProcessEvent(hash)
|
||||
models.AlertCurEventDelByHash(arw.Ctx, hash)
|
||||
hash := process.Hash(cachedRule.Id, arw.processor.DatasourceId(), p)
|
||||
arw.processor.DeleteProcessEvent(hash)
|
||||
models.AlertCurEventDelByHash(arw.ctx, hash)
|
||||
|
||||
pointsMap[tagHash] = point
|
||||
}
|
||||
@@ -193,427 +161,110 @@ func (arw *AlertRuleWorker) Eval() {
|
||||
now := time.Now().Unix()
|
||||
for _, point := range pointsMap {
|
||||
str := fmt.Sprintf("%v", point.Value)
|
||||
arw.Processor.RecoverSingle(true, process.Hash(cachedRule.Id, arw.Processor.DatasourceId(), point), now, &str)
|
||||
arw.processor.RecoverSingle(true, process.Hash(cachedRule.Id, arw.processor.DatasourceId(), point), now, &str)
|
||||
}
|
||||
} else {
|
||||
now := time.Now().Unix()
|
||||
for _, point := range recoverPoints {
|
||||
str := fmt.Sprintf("%v", point.Value)
|
||||
arw.Processor.RecoverSingle(true, process.Hash(cachedRule.Id, arw.Processor.DatasourceId(), point), now, &str)
|
||||
arw.processor.RecoverSingle(true, process.Hash(cachedRule.Id, arw.processor.DatasourceId(), point), now, &str)
|
||||
}
|
||||
}
|
||||
|
||||
arw.Processor.Handle(anomalyPoints, "inner", arw.Inhibit)
|
||||
arw.processor.Handle(anomalyPoints, "inner", arw.inhibit)
|
||||
}
|
||||
|
||||
func (arw *AlertRuleWorker) Stop() {
|
||||
logger.Infof("rule_eval %s stopped", arw.Key())
|
||||
close(arw.Quit)
|
||||
c := arw.Scheduler.Stop()
|
||||
<-c.Done()
|
||||
|
||||
close(arw.quit)
|
||||
}
|
||||
|
||||
func (arw *AlertRuleWorker) GetPromAnomalyPoint(ruleConfig string) ([]models.AnomalyPoint, error) {
|
||||
var lst []models.AnomalyPoint
|
||||
func (arw *AlertRuleWorker) GetPromAnomalyPoint(ruleConfig string) []common.AnomalyPoint {
|
||||
var lst []common.AnomalyPoint
|
||||
var severity int
|
||||
|
||||
var rule *models.PromRuleConfig
|
||||
if err := json.Unmarshal([]byte(ruleConfig), &rule); err != nil {
|
||||
logger.Errorf("rule_eval:%s rule_config:%s, error:%v", arw.Key(), ruleConfig, err)
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), GET_RULE_CONFIG, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
return lst, err
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
|
||||
return lst
|
||||
}
|
||||
|
||||
if rule == nil {
|
||||
logger.Errorf("rule_eval:%s rule_config:%s, error:rule is nil", arw.Key(), ruleConfig)
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), GET_RULE_CONFIG, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
return lst, errors.New("rule is nil")
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
|
||||
return lst
|
||||
}
|
||||
|
||||
arw.Inhibit = rule.Inhibit
|
||||
arw.inhibit = rule.Inhibit
|
||||
for _, query := range rule.Queries {
|
||||
if query.Severity < severity {
|
||||
arw.Severity = query.Severity
|
||||
arw.severity = query.Severity
|
||||
}
|
||||
|
||||
readerClient := arw.PromClients.GetCli(arw.DatasourceId)
|
||||
|
||||
if query.VarEnabled {
|
||||
anomalyPoints := arw.VarFilling(query, readerClient)
|
||||
for _, v := range anomalyPoints {
|
||||
lst = append(lst, v)
|
||||
}
|
||||
} else {
|
||||
// 无变量
|
||||
promql := strings.TrimSpace(query.PromQl)
|
||||
if promql == "" {
|
||||
logger.Warningf("rule_eval:%s promql is blank", arw.Key())
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), CHECK_QUERY, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
if arw.PromClients.IsNil(arw.DatasourceId) {
|
||||
logger.Warningf("rule_eval:%s error reader client is nil", arw.Key())
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), GET_CLIENT, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
var warnings promsdk.Warnings
|
||||
arw.Processor.Stats.CounterQueryDataTotal.WithLabelValues(fmt.Sprintf("%d", arw.DatasourceId)).Inc()
|
||||
value, warnings, err := readerClient.Query(context.Background(), promql, time.Now())
|
||||
if err != nil {
|
||||
logger.Errorf("rule_eval:%s promql:%s, error:%v", arw.Key(), promql, err)
|
||||
arw.Processor.Stats.CounterQueryDataErrorTotal.WithLabelValues(fmt.Sprintf("%d", arw.DatasourceId)).Inc()
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), QUERY_DATA, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
return lst, err
|
||||
}
|
||||
|
||||
if len(warnings) > 0 {
|
||||
logger.Errorf("rule_eval:%s promql:%s, warnings:%v", arw.Key(), promql, warnings)
|
||||
arw.Processor.Stats.CounterQueryDataErrorTotal.WithLabelValues(fmt.Sprintf("%d", arw.DatasourceId)).Inc()
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), QUERY_DATA, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
}
|
||||
|
||||
logger.Debugf("rule_eval:%s query:%+v, value:%v", arw.Key(), query, value)
|
||||
points := models.ConvertAnomalyPoints(value)
|
||||
for i := 0; i < len(points); i++ {
|
||||
points[i].Severity = query.Severity
|
||||
points[i].Query = promql
|
||||
points[i].ValuesUnit = map[string]unit.FormattedValue{
|
||||
"v": unit.ValueFormatter(query.Unit, 2, points[i].Value),
|
||||
}
|
||||
}
|
||||
|
||||
lst = append(lst, points...)
|
||||
}
|
||||
}
|
||||
return lst, nil
|
||||
}
|
||||
|
||||
type sample struct {
|
||||
Metric model.Metric `json:"metric"`
|
||||
Value model.SampleValue `json:"value"`
|
||||
Timestamp model.Time
|
||||
}
|
||||
|
||||
// VarFilling 填充变量
|
||||
// 公式: mem_used_percent{host="$host"} > $val 其中 $host 为参数变量,$val 为值变量
|
||||
// 实现步骤:
|
||||
// 广度优先遍历,保证同一参数变量的子筛选可以覆盖上一层筛选
|
||||
// 每个节点先查询无参数的 query, 即 mem_used_percent > curVal, 得到满足值变量的所有结果
|
||||
// 结果中有满足本节点参数变量的值,加入异常点列表
|
||||
// 参数变量的值不满足的组合,需要覆盖上层筛选中产生的异常点
|
||||
func (arw *AlertRuleWorker) VarFilling(query models.PromQuery, readerClient promsdk.API) map[string]models.AnomalyPoint {
|
||||
fullQuery := removeVal(query.PromQl)
|
||||
// 存储所有的异常点,key 为参数变量的组合,可以实现子筛选对上一层筛选的覆盖
|
||||
anomalyPoints := make(map[string]models.AnomalyPoint)
|
||||
// 统一变量配置格式
|
||||
VarConfigForCalc := &models.ChildVarConfig{
|
||||
ParamVal: make([]map[string]models.ParamQuery, 1),
|
||||
ChildVarConfigs: query.VarConfig.ChildVarConfigs,
|
||||
}
|
||||
VarConfigForCalc.ParamVal[0] = make(map[string]models.ParamQuery)
|
||||
for _, p := range query.VarConfig.ParamVal {
|
||||
VarConfigForCalc.ParamVal[0][p.Name] = models.ParamQuery{
|
||||
ParamType: p.ParamType,
|
||||
Query: p.Query,
|
||||
}
|
||||
}
|
||||
// 使用一个统一的参数变量顺序
|
||||
var ParamKeys []string
|
||||
for val, valQuery := range VarConfigForCalc.ParamVal[0] {
|
||||
if valQuery.ParamType == "threshold" {
|
||||
promql := strings.TrimSpace(query.PromQl)
|
||||
if promql == "" {
|
||||
logger.Warningf("rule_eval:%s promql is blank", arw.Key())
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), CHECK_QUERY).Inc()
|
||||
continue
|
||||
}
|
||||
ParamKeys = append(ParamKeys, val)
|
||||
}
|
||||
sort.Slice(ParamKeys, func(i, j int) bool {
|
||||
return ParamKeys[i] < ParamKeys[j]
|
||||
})
|
||||
// 遍历变量配置链表
|
||||
curNode := VarConfigForCalc
|
||||
for curNode != nil {
|
||||
for _, param := range curNode.ParamVal {
|
||||
// curQuery 当前节点的无参数 query,用于时序库查询
|
||||
curQuery := fullQuery
|
||||
// realQuery 当前节点产生异常点的 query,用于告警展示
|
||||
realQuery := query.PromQl
|
||||
// 取出阈值变量
|
||||
valMap := make(map[string]string)
|
||||
for val, valQuery := range param {
|
||||
if valQuery.ParamType == "threshold" {
|
||||
valMap[val] = getString(valQuery.Query)
|
||||
}
|
||||
}
|
||||
// 替换值变量
|
||||
for key, val := range valMap {
|
||||
curQuery = strings.Replace(curQuery, fmt.Sprintf("$%s", key), val, -1)
|
||||
realQuery = strings.Replace(realQuery, fmt.Sprintf("$%s", key), val, -1)
|
||||
}
|
||||
// 得到满足值变量的所有结果
|
||||
value, _, err := readerClient.Query(context.Background(), curQuery, time.Now())
|
||||
if err != nil {
|
||||
logger.Errorf("rule_eval:%s, promql:%s, error:%v", arw.Key(), curQuery, err)
|
||||
continue
|
||||
}
|
||||
seqVals := getSamples(value)
|
||||
// 得到参数变量的所有组合
|
||||
paramPermutation, err := arw.getParamPermutation(param, ParamKeys)
|
||||
if err != nil {
|
||||
logger.Errorf("rule_eval:%s, paramPermutation error:%v", arw.Key(), err)
|
||||
continue
|
||||
}
|
||||
// 判断哪些参数值符合条件
|
||||
for i := range seqVals {
|
||||
curRealQuery := realQuery
|
||||
var cur []string
|
||||
for _, paramKey := range ParamKeys {
|
||||
val := string(seqVals[i].Metric[model.LabelName(paramKey)])
|
||||
cur = append(cur, val)
|
||||
curRealQuery = strings.Replace(curRealQuery, fmt.Sprintf("$%s", paramKey), val, -1)
|
||||
}
|
||||
if _, ok := paramPermutation[strings.Join(cur, "-")]; ok {
|
||||
anomalyPoints[strings.Join(cur, "-")] = models.AnomalyPoint{
|
||||
Key: seqVals[i].Metric.String(),
|
||||
Timestamp: seqVals[i].Timestamp.Unix(),
|
||||
Value: float64(seqVals[i].Value),
|
||||
Labels: seqVals[i].Metric,
|
||||
Severity: query.Severity,
|
||||
Query: curRealQuery,
|
||||
}
|
||||
// 生成异常点后,删除该参数组合
|
||||
delete(paramPermutation, strings.Join(cur, "-"))
|
||||
}
|
||||
}
|
||||
|
||||
// 剩余的参数组合为本层筛选不产生异常点的组合,需要覆盖上层筛选中产生的异常点
|
||||
for k, _ := range paramPermutation {
|
||||
delete(anomalyPoints, k)
|
||||
}
|
||||
if arw.promClients.IsNil(arw.datasourceId) {
|
||||
logger.Warningf("rule_eval:%s error reader client is nil", arw.Key())
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_CLIENT).Inc()
|
||||
continue
|
||||
}
|
||||
curNode = curNode.ChildVarConfigs
|
||||
}
|
||||
|
||||
return anomalyPoints
|
||||
readerClient := arw.promClients.GetCli(arw.datasourceId)
|
||||
|
||||
var warnings promsdk.Warnings
|
||||
arw.processor.Stats.CounterQueryDataTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
|
||||
value, warnings, err := readerClient.Query(context.Background(), promql, time.Now())
|
||||
if err != nil {
|
||||
logger.Errorf("rule_eval:%s promql:%s, error:%v", arw.Key(), promql, err)
|
||||
arw.processor.Stats.CounterQueryDataErrorTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
if len(warnings) > 0 {
|
||||
logger.Errorf("rule_eval:%s promql:%s, warnings:%v", arw.Key(), promql, warnings)
|
||||
arw.processor.Stats.CounterQueryDataErrorTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
|
||||
}
|
||||
|
||||
logger.Debugf("rule_eval:%s query:%+v, value:%v", arw.Key(), query, value)
|
||||
points := common.ConvertAnomalyPoints(value)
|
||||
for i := 0; i < len(points); i++ {
|
||||
points[i].Severity = query.Severity
|
||||
points[i].Query = promql
|
||||
}
|
||||
lst = append(lst, points...)
|
||||
}
|
||||
return lst
|
||||
}
|
||||
|
||||
// getSamples 获取查询结果的所有样本,并转化为统一的格式
|
||||
func getSamples(value model.Value) []sample {
|
||||
var seqVals []sample
|
||||
switch value.Type() {
|
||||
case model.ValVector:
|
||||
items, ok := value.(model.Vector)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
for i := range items {
|
||||
seqVals = append(seqVals, sample{
|
||||
Metric: items[i].Metric,
|
||||
Value: items[i].Value,
|
||||
Timestamp: items[i].Timestamp,
|
||||
})
|
||||
}
|
||||
case model.ValMatrix:
|
||||
items, ok := value.(model.Matrix)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
for i := range items {
|
||||
last := items[i].Values[len(items[i].Values)-1]
|
||||
seqVals = append(seqVals, sample{
|
||||
Metric: items[i].Metric,
|
||||
Value: last.Value,
|
||||
Timestamp: last.Timestamp,
|
||||
})
|
||||
}
|
||||
default:
|
||||
}
|
||||
return seqVals
|
||||
}
|
||||
|
||||
// removeVal 去除 promql 中的参数变量
|
||||
// mem{test1=\"$test1\",test2=\"test2\"} > $val1 and mem{test3=\"test3\",test4=\"$test4\"} > $val2
|
||||
// ==> mem{test2=\"test2\"} > $val1 and mem{test3=\"test3\"} > $val2
|
||||
func removeVal(promql string) string {
|
||||
sb := strings.Builder{}
|
||||
n := len(promql)
|
||||
start := false
|
||||
lastIdx := 0
|
||||
curIdx := 0
|
||||
isVar := false
|
||||
for curIdx < n {
|
||||
if !start {
|
||||
if promql[curIdx] == '{' {
|
||||
start = true
|
||||
lastIdx = curIdx
|
||||
}
|
||||
sb.WriteRune(rune(promql[curIdx]))
|
||||
} else {
|
||||
if promql[curIdx] == '$' {
|
||||
isVar = true
|
||||
}
|
||||
if promql[curIdx] == ',' || promql[curIdx] == '}' {
|
||||
if !isVar {
|
||||
if sb.String()[sb.Len()-1] == '{' {
|
||||
lastIdx++
|
||||
}
|
||||
sb.WriteString(promql[lastIdx:curIdx])
|
||||
}
|
||||
isVar = false
|
||||
if promql[curIdx] == '}' {
|
||||
start = false
|
||||
sb.WriteRune(rune(promql[curIdx]))
|
||||
}
|
||||
lastIdx = curIdx
|
||||
}
|
||||
}
|
||||
curIdx++
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// 获取参数变量的所有组合
|
||||
func (arw *AlertRuleWorker) getParamPermutation(paramVal map[string]models.ParamQuery, paramKeys []string) (map[string]struct{}, error) {
|
||||
|
||||
// 参数变量查询,得到参数变量值
|
||||
paramMap := make(map[string][]string)
|
||||
for _, paramKey := range paramKeys {
|
||||
var params []string
|
||||
paramQuery, ok := paramVal[paramKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("param key not found: %s", paramKey)
|
||||
}
|
||||
switch paramQuery.ParamType {
|
||||
case "host":
|
||||
hostIdents, err := arw.getHostIdents(paramQuery)
|
||||
if err != nil {
|
||||
logger.Errorf("rule_eval:%s, fail to get host idents, error:%v", arw.Key(), err)
|
||||
break
|
||||
}
|
||||
params = hostIdents
|
||||
case "device":
|
||||
deviceIdents, err := arw.getDeviceIdents(paramQuery)
|
||||
if err != nil {
|
||||
logger.Errorf("rule_eval:%s, fail to get device idents, error:%v", arw.Key(), err)
|
||||
break
|
||||
}
|
||||
params = deviceIdents
|
||||
case "enum":
|
||||
q, _ := json.Marshal(paramQuery.Query)
|
||||
var query []string
|
||||
err := json.Unmarshal(q, &query)
|
||||
if err != nil {
|
||||
logger.Errorf("query:%s fail to unmarshalling into string slice, error:%v", paramQuery.Query, err)
|
||||
}
|
||||
params = query
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown param type: %s", paramQuery.ParamType)
|
||||
}
|
||||
|
||||
if len(params) == 0 {
|
||||
return nil, fmt.Errorf("param key: %s, params is empty", paramKey)
|
||||
}
|
||||
|
||||
paramMap[paramKey] = params
|
||||
}
|
||||
|
||||
// 得到以 paramKeys 为顺序的所有参数组合
|
||||
permutation := mapPermutation(paramKeys, paramMap)
|
||||
|
||||
res := make(map[string]struct{})
|
||||
for i := range permutation {
|
||||
res[strings.Join(permutation[i], "-")] = struct{}{}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (arw *AlertRuleWorker) getHostIdents(paramQuery models.ParamQuery) ([]string, error) {
|
||||
var params []string
|
||||
q, _ := json.Marshal(paramQuery.Query)
|
||||
|
||||
cacheKey := "Host_" + string(q)
|
||||
value, hit := arw.HostAndDeviceIdentCache.Load(cacheKey)
|
||||
if idents, ok := value.([]string); hit && ok {
|
||||
params = idents
|
||||
return params, nil
|
||||
}
|
||||
|
||||
var queries []models.HostQuery
|
||||
err := json.Unmarshal(q, &queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostsQuery := models.GetHostsQuery(queries)
|
||||
session := models.TargetFilterQueryBuild(arw.Ctx, hostsQuery, 0, 0)
|
||||
var lst []*models.Target
|
||||
err = session.Find(&lst).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range lst {
|
||||
params = append(params, lst[i].Ident)
|
||||
}
|
||||
arw.HostAndDeviceIdentCache.Store(cacheKey, params)
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (arw *AlertRuleWorker) getDeviceIdents(paramQuery models.ParamQuery) ([]string, error) {
|
||||
return arw.DeviceIdentHook(arw, paramQuery)
|
||||
}
|
||||
|
||||
// 生成所有排列组合
|
||||
func mapPermutation(paramKeys []string, paraMap map[string][]string) [][]string {
|
||||
var result [][]string
|
||||
current := make([]string, len(paramKeys))
|
||||
combine(paramKeys, paraMap, 0, current, &result)
|
||||
return result
|
||||
}
|
||||
|
||||
// 递归生成所有排列组合
|
||||
func combine(paramKeys []string, paraMap map[string][]string, index int, current []string, result *[][]string) {
|
||||
// 当到达最后一个 key 时,存储当前的组合
|
||||
if index == len(paramKeys) {
|
||||
combination := make([]string, len(current))
|
||||
copy(combination, current)
|
||||
*result = append(*result, combination)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前 key 对应的 value 列表
|
||||
key := paramKeys[index]
|
||||
valueList := paraMap[key]
|
||||
|
||||
// 遍历每个 value,并递归生成下一个 key 的组合
|
||||
for _, value := range valueList {
|
||||
current[index] = value
|
||||
combine(paramKeys, paraMap, index+1, current, result)
|
||||
}
|
||||
}
|
||||
|
||||
func (arw *AlertRuleWorker) GetTdengineAnomalyPoint(rule *models.AlertRule, dsId int64) ([]models.AnomalyPoint, []models.AnomalyPoint, error) {
|
||||
func (arw *AlertRuleWorker) GetTdengineAnomalyPoint(rule *models.AlertRule, dsId int64) ([]common.AnomalyPoint, []common.AnomalyPoint) {
|
||||
// 获取查询和规则判断条件
|
||||
points := []models.AnomalyPoint{}
|
||||
recoverPoints := []models.AnomalyPoint{}
|
||||
points := []common.AnomalyPoint{}
|
||||
recoverPoints := []common.AnomalyPoint{}
|
||||
ruleConfig := strings.TrimSpace(rule.RuleConfig)
|
||||
if ruleConfig == "" {
|
||||
logger.Warningf("rule_eval:%d promql is blank", rule.Id)
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), GET_RULE_CONFIG, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
return points, recoverPoints, errors.New("rule config is nil")
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
|
||||
return points, recoverPoints
|
||||
}
|
||||
|
||||
var ruleQuery models.RuleQuery
|
||||
err := json.Unmarshal([]byte(ruleConfig), &ruleQuery)
|
||||
if err != nil {
|
||||
logger.Warningf("rule_eval:%d promql parse error:%s", rule.Id, err.Error())
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), GET_RULE_CONFIG, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
return points, recoverPoints, err
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId())).Inc()
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
|
||||
return points, recoverPoints
|
||||
}
|
||||
|
||||
arw.Inhibit = ruleQuery.Inhibit
|
||||
arw.inhibit = ruleQuery.Inhibit
|
||||
if len(ruleQuery.Queries) > 0 {
|
||||
seriesStore := make(map[uint64]models.DataResp)
|
||||
// 将不同查询的 hash 索引分组存放
|
||||
@@ -622,22 +273,22 @@ func (arw *AlertRuleWorker) GetTdengineAnomalyPoint(rule *models.AlertRule, dsId
|
||||
for _, query := range ruleQuery.Queries {
|
||||
seriesTagIndex := make(map[uint64][]uint64)
|
||||
|
||||
arw.Processor.Stats.CounterQueryDataTotal.WithLabelValues(fmt.Sprintf("%d", arw.DatasourceId)).Inc()
|
||||
cli := arw.TdengineClients.GetCli(dsId)
|
||||
arw.processor.Stats.CounterQueryDataTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
|
||||
cli := arw.tdengineClients.GetCli(dsId)
|
||||
if cli == nil {
|
||||
logger.Warningf("rule_eval:%d tdengine client is nil", rule.Id)
|
||||
arw.Processor.Stats.CounterQueryDataErrorTotal.WithLabelValues(fmt.Sprintf("%d", arw.DatasourceId)).Inc()
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), GET_CLIENT, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
arw.processor.Stats.CounterQueryDataErrorTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_CLIENT).Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
series, err := cli.Query(query)
|
||||
arw.Processor.Stats.CounterQueryDataTotal.WithLabelValues(fmt.Sprintf("%d", arw.DatasourceId)).Inc()
|
||||
series, err := cli.Query(query,0)
|
||||
arw.processor.Stats.CounterQueryDataTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
|
||||
if err != nil {
|
||||
logger.Warningf("rule_eval rid:%d query data error: %v", rule.Id, err)
|
||||
arw.Processor.Stats.CounterQueryDataErrorTotal.WithLabelValues(fmt.Sprintf("%d", arw.DatasourceId)).Inc()
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), QUERY_DATA, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
return points, recoverPoints, err
|
||||
arw.processor.Stats.CounterQueryDataErrorTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
|
||||
continue
|
||||
}
|
||||
// 此条日志很重要,是告警判断的现场值
|
||||
logger.Debugf("rule_eval rid:%d req:%+v resp:%+v", rule.Id, query, series)
|
||||
@@ -645,7 +296,7 @@ func (arw *AlertRuleWorker) GetTdengineAnomalyPoint(rule *models.AlertRule, dsId
|
||||
ref, err := GetQueryRef(query)
|
||||
if err != nil {
|
||||
logger.Warningf("rule_eval rid:%d query ref error: %v query:%+v", rule.Id, err, query)
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), GET_RULE_CONFIG, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
|
||||
continue
|
||||
}
|
||||
seriesTagIndexes[ref] = seriesTagIndex
|
||||
@@ -654,31 +305,31 @@ func (arw *AlertRuleWorker) GetTdengineAnomalyPoint(rule *models.AlertRule, dsId
|
||||
points, recoverPoints = GetAnomalyPoint(rule.Id, ruleQuery, seriesTagIndexes, seriesStore)
|
||||
}
|
||||
|
||||
return points, recoverPoints, nil
|
||||
return points, recoverPoints
|
||||
}
|
||||
|
||||
func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) ([]models.AnomalyPoint, error) {
|
||||
var lst []models.AnomalyPoint
|
||||
func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) []common.AnomalyPoint {
|
||||
var lst []common.AnomalyPoint
|
||||
var severity int
|
||||
|
||||
var rule *models.HostRuleConfig
|
||||
if err := json.Unmarshal([]byte(ruleConfig), &rule); err != nil {
|
||||
logger.Errorf("rule_eval:%s rule_config:%s, error:%v", arw.Key(), ruleConfig, err)
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), GET_RULE_CONFIG, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
return lst, err
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
|
||||
return lst
|
||||
}
|
||||
|
||||
if rule == nil {
|
||||
logger.Errorf("rule_eval:%s rule_config:%s, error:rule is nil", arw.Key(), ruleConfig)
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), GET_RULE_CONFIG, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
return lst, errors.New("rule is nil")
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
|
||||
return lst
|
||||
}
|
||||
|
||||
arw.Inhibit = rule.Inhibit
|
||||
arw.inhibit = rule.Inhibit
|
||||
now := time.Now().Unix()
|
||||
for _, trigger := range rule.Triggers {
|
||||
if trigger.Severity < severity {
|
||||
arw.Severity = trigger.Severity
|
||||
arw.severity = trigger.Severity
|
||||
}
|
||||
|
||||
switch trigger.Type {
|
||||
@@ -687,20 +338,20 @@ func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) ([]models.Ano
|
||||
|
||||
var idents, engineIdents, missEngineIdents []string
|
||||
var exists bool
|
||||
if arw.Ctx.IsCenter {
|
||||
if arw.ctx.IsCenter {
|
||||
// 如果是中心节点, 将不再上报数据的主机 engineName 为空的机器,也加入到 targets 中
|
||||
missEngineIdents, exists = arw.Processor.TargetsOfAlertRuleCache.Get("", arw.Rule.Id)
|
||||
missEngineIdents, exists = arw.processor.TargetsOfAlertRuleCache.Get("", arw.rule.Id)
|
||||
if !exists {
|
||||
logger.Debugf("rule_eval:%s targets not found engineName:%s", arw.Key(), arw.Processor.EngineName)
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), QUERY_DATA, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
logger.Debugf("rule_eval:%s targets not found engineName:%s", arw.Key(), arw.processor.EngineName)
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
|
||||
}
|
||||
}
|
||||
idents = append(idents, missEngineIdents...)
|
||||
|
||||
engineIdents, exists = arw.Processor.TargetsOfAlertRuleCache.Get(arw.Processor.EngineName, arw.Rule.Id)
|
||||
engineIdents, exists = arw.processor.TargetsOfAlertRuleCache.Get(arw.processor.EngineName, arw.rule.Id)
|
||||
if !exists {
|
||||
logger.Warningf("rule_eval:%s targets not found engineName:%s", arw.Key(), arw.Processor.EngineName)
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), QUERY_DATA, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
logger.Warningf("rule_eval:%s targets not found engineName:%s", arw.Key(), arw.processor.EngineName)
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
|
||||
}
|
||||
idents = append(idents, engineIdents...)
|
||||
|
||||
@@ -709,39 +360,40 @@ func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) ([]models.Ano
|
||||
}
|
||||
|
||||
var missTargets []string
|
||||
targetUpdateTimeMap := arw.Processor.TargetCache.GetHostUpdateTime(idents)
|
||||
targetUpdateTimeMap := arw.processor.TargetCache.GetHostUpdateTime(idents)
|
||||
for ident, updateTime := range targetUpdateTimeMap {
|
||||
if updateTime < t {
|
||||
missTargets = append(missTargets, ident)
|
||||
}
|
||||
}
|
||||
logger.Debugf("rule_eval:%s missTargets:%v", arw.Key(), missTargets)
|
||||
targets := arw.Processor.TargetCache.Gets(missTargets)
|
||||
targets := arw.processor.TargetCache.Gets(missTargets)
|
||||
for _, target := range targets {
|
||||
m := make(map[string]string)
|
||||
target.FillTagsMap()
|
||||
for k, v := range target.TagsMap {
|
||||
m[k] = v
|
||||
}
|
||||
m["ident"] = target.Ident
|
||||
|
||||
lst = append(lst, models.NewAnomalyPoint(trigger.Type, m, now, float64(now-target.UpdateAt), trigger.Severity))
|
||||
lst = append(lst, common.NewAnomalyPoint(trigger.Type, m, now, float64(now-target.UpdateAt), trigger.Severity))
|
||||
}
|
||||
case "offset":
|
||||
idents, exists := arw.Processor.TargetsOfAlertRuleCache.Get(arw.Processor.EngineName, arw.Rule.Id)
|
||||
idents, exists := arw.processor.TargetsOfAlertRuleCache.Get(arw.processor.EngineName, arw.rule.Id)
|
||||
if !exists {
|
||||
logger.Warningf("rule_eval:%s targets not found", arw.Key())
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), QUERY_DATA, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
targets := arw.Processor.TargetCache.Gets(idents)
|
||||
targets := arw.processor.TargetCache.Gets(idents)
|
||||
targetMap := make(map[string]*models.Target)
|
||||
for _, target := range targets {
|
||||
targetMap[target.Ident] = target
|
||||
}
|
||||
|
||||
offsetIdents := make(map[string]int64)
|
||||
targetsMeta := arw.Processor.TargetCache.GetHostMetas(targets)
|
||||
targetsMeta := arw.processor.TargetCache.GetHostMetas(targets)
|
||||
for ident, meta := range targetsMeta {
|
||||
if meta.CpuNum <= 0 {
|
||||
// means this target is not collect by categraf, do not check offset
|
||||
@@ -763,27 +415,28 @@ func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) ([]models.Ano
|
||||
logger.Debugf("rule_eval:%s offsetIdents:%v", arw.Key(), offsetIdents)
|
||||
for host, offset := range offsetIdents {
|
||||
m := make(map[string]string)
|
||||
target, exists := arw.Processor.TargetCache.Get(host)
|
||||
target, exists := arw.processor.TargetCache.Get(host)
|
||||
if exists {
|
||||
target.FillTagsMap()
|
||||
for k, v := range target.TagsMap {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
m["ident"] = host
|
||||
|
||||
lst = append(lst, models.NewAnomalyPoint(trigger.Type, m, now, float64(offset), trigger.Severity))
|
||||
lst = append(lst, common.NewAnomalyPoint(trigger.Type, m, now, float64(offset), trigger.Severity))
|
||||
}
|
||||
case "pct_target_miss":
|
||||
t := now - int64(trigger.Duration)
|
||||
idents, exists := arw.Processor.TargetsOfAlertRuleCache.Get(arw.Processor.EngineName, arw.Rule.Id)
|
||||
idents, exists := arw.processor.TargetsOfAlertRuleCache.Get(arw.processor.EngineName, arw.rule.Id)
|
||||
if !exists {
|
||||
logger.Warningf("rule_eval:%s targets not found", arw.Key())
|
||||
arw.Processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.Processor.DatasourceId()), QUERY_DATA, arw.Processor.BusiGroupCache.GetNameByBusiGroupId(arw.Rule.GroupId), fmt.Sprintf("%v", arw.Rule.Id)).Inc()
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
var missTargets []string
|
||||
targetUpdateTimeMap := arw.Processor.TargetCache.GetHostUpdateTime(idents)
|
||||
targetUpdateTimeMap := arw.processor.TargetCache.GetHostUpdateTime(idents)
|
||||
for ident, updateTime := range targetUpdateTimeMap {
|
||||
if updateTime < t {
|
||||
missTargets = append(missTargets, ident)
|
||||
@@ -792,16 +445,16 @@ func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) ([]models.Ano
|
||||
logger.Debugf("rule_eval:%s missTargets:%v", arw.Key(), missTargets)
|
||||
pct := float64(len(missTargets)) / float64(len(idents)) * 100
|
||||
if pct >= float64(trigger.Percent) {
|
||||
lst = append(lst, models.NewAnomalyPoint(trigger.Type, nil, now, pct, trigger.Severity))
|
||||
lst = append(lst, common.NewAnomalyPoint(trigger.Type, nil, now, pct, trigger.Severity))
|
||||
}
|
||||
}
|
||||
}
|
||||
return lst, nil
|
||||
return lst
|
||||
}
|
||||
|
||||
func GetAnomalyPoint(ruleId int64, ruleQuery models.RuleQuery, seriesTagIndexes map[string]map[uint64][]uint64, seriesStore map[uint64]models.DataResp) ([]models.AnomalyPoint, []models.AnomalyPoint) {
|
||||
points := []models.AnomalyPoint{}
|
||||
recoverPoints := []models.AnomalyPoint{}
|
||||
func GetAnomalyPoint(ruleId int64, ruleQuery models.RuleQuery, seriesTagIndexes map[string]map[uint64][]uint64, seriesStore map[uint64]models.DataResp) ([]common.AnomalyPoint, []common.AnomalyPoint) {
|
||||
points := []common.AnomalyPoint{}
|
||||
recoverPoints := []common.AnomalyPoint{}
|
||||
|
||||
if len(ruleQuery.Triggers) == 0 {
|
||||
return points, recoverPoints
|
||||
@@ -811,22 +464,11 @@ func GetAnomalyPoint(ruleId int64, ruleQuery models.RuleQuery, seriesTagIndexes
|
||||
return points, recoverPoints
|
||||
}
|
||||
|
||||
unitMap := make(map[string]string)
|
||||
for _, query := range ruleQuery.Queries {
|
||||
ref, unit, err := GetQueryRefAndUnit(query)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
unitMap[ref] = unit
|
||||
}
|
||||
|
||||
for _, trigger := range ruleQuery.Triggers {
|
||||
// seriesTagIndex 的 key 仅做分组使用,value 为每组 series 的 hash
|
||||
seriesTagIndex := ProcessJoins(ruleId, trigger, seriesTagIndexes, seriesStore)
|
||||
|
||||
for _, seriesHash := range seriesTagIndex {
|
||||
valuesUnitMap := make(map[string]unit.FormattedValue)
|
||||
|
||||
sort.Slice(seriesHash, func(i, j int) bool {
|
||||
return seriesHash[i] < seriesHash[j]
|
||||
})
|
||||
@@ -852,10 +494,6 @@ func GetAnomalyPoint(ruleId int64, ruleQuery models.RuleQuery, seriesTagIndexes
|
||||
continue
|
||||
}
|
||||
|
||||
if u, exists := unitMap[series.Ref]; exists {
|
||||
valuesUnitMap[series.Ref] = unit.ValueFormatter(u, 2, v)
|
||||
}
|
||||
|
||||
m["$"+series.Ref] = v
|
||||
m["$"+series.Ref+"."+series.MetricName()] = v
|
||||
ts = int64(t)
|
||||
@@ -874,7 +512,7 @@ func GetAnomalyPoint(ruleId int64, ruleQuery models.RuleQuery, seriesTagIndexes
|
||||
values += fmt.Sprintf("%s:%v ", k, v)
|
||||
}
|
||||
|
||||
point := models.AnomalyPoint{
|
||||
point := common.AnomalyPoint{
|
||||
Key: sample.MetricName(),
|
||||
Labels: sample.Metric,
|
||||
Timestamp: int64(ts),
|
||||
@@ -884,7 +522,6 @@ func GetAnomalyPoint(ruleId int64, ruleQuery models.RuleQuery, seriesTagIndexes
|
||||
Triggered: isTriggered,
|
||||
Query: fmt.Sprintf("query:%+v trigger:%+v", ruleQuery.Queries, trigger),
|
||||
RecoverConfig: trigger.RecoverConfig,
|
||||
ValuesUnit: valuesUnitMap,
|
||||
}
|
||||
|
||||
if sample.Query != "" {
|
||||
@@ -1171,30 +808,3 @@ func GetQueryRef(query interface{}) (string, error) {
|
||||
|
||||
return refField.String(), nil
|
||||
}
|
||||
|
||||
// query 可能是 string 或是 int int64 float64 等数字,全部转为 string
|
||||
func getString(query interface{}) string {
|
||||
switch query.(type) {
|
||||
case string:
|
||||
return query.(string)
|
||||
case float64:
|
||||
return strconv.FormatFloat(query.(float64), 'f', -1, 64)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func GetQueryRefAndUnit(query interface{}) (string, string, error) {
|
||||
type Query struct {
|
||||
Ref string `json:"ref"`
|
||||
Unit string `json:"unit"`
|
||||
}
|
||||
|
||||
queryMap := Query{}
|
||||
queryBytes, err := json.Marshal(query)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
json.Unmarshal(queryBytes, &queryMap)
|
||||
return queryMap.Ref, queryMap.Unit, nil
|
||||
}
|
||||
|
||||
@@ -269,122 +269,3 @@ func allValueDeepEqual(got, want map[uint64][]uint64) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// allValueDeepEqualOmitOrder 判断两个字符串切片是否相等,不考虑顺序
|
||||
func allValueDeepEqualOmitOrder(got, want []string) bool {
|
||||
if len(got) != len(want) {
|
||||
return false
|
||||
}
|
||||
slices.Sort(got)
|
||||
slices.Sort(want)
|
||||
for i := range got {
|
||||
if got[i] != want[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func Test_removeVal(t *testing.T) {
|
||||
type args struct {
|
||||
promql string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
{
|
||||
name: "removeVal1",
|
||||
args: args{
|
||||
promql: "mem{test1=\"$test1\",test2=\"$test2\",test3=\"$test3\"} > $val",
|
||||
},
|
||||
want: "mem{} > $val",
|
||||
},
|
||||
{
|
||||
name: "removeVal2",
|
||||
args: args{
|
||||
promql: "mem{test1=\"test1\",test2=\"$test2\",test3=\"$test3\"} > $val",
|
||||
},
|
||||
want: "mem{test1=\"test1\"} > $val",
|
||||
},
|
||||
{
|
||||
name: "removeVal3",
|
||||
args: args{
|
||||
promql: "mem{test1=\"$test1\",test2=\"test2\",test3=\"$test3\"} > $val",
|
||||
},
|
||||
want: "mem{test2=\"test2\"} > $val",
|
||||
},
|
||||
{
|
||||
name: "removeVal4",
|
||||
args: args{
|
||||
promql: "mem{test1=\"$test1\",test2=\"$test2\",test3=\"test3\"} > $val",
|
||||
},
|
||||
want: "mem{test3=\"test3\"} > $val",
|
||||
},
|
||||
{
|
||||
name: "removeVal5",
|
||||
args: args{
|
||||
promql: "mem{test1=\"$test1\",test2=\"test2\",test3=\"test3\"} > $val",
|
||||
},
|
||||
want: "mem{test2=\"test2\",test3=\"test3\"} > $val",
|
||||
},
|
||||
{
|
||||
name: "removeVal6",
|
||||
args: args{
|
||||
promql: "mem{test1=\"test1\",test2=\"$test2\",test3=\"test3\"} > $val",
|
||||
},
|
||||
want: "mem{test1=\"test1\",test3=\"test3\"} > $val",
|
||||
},
|
||||
{
|
||||
name: "removeVal7",
|
||||
args: args{
|
||||
promql: "mem{test1=\"test1\",test2=\"test2\",test3=\"$test3\"} > $val",
|
||||
},
|
||||
want: "mem{test1=\"test1\",test2=\"test2\"} > $val",
|
||||
},
|
||||
{
|
||||
name: "removeVal8",
|
||||
args: args{
|
||||
promql: "mem{test1=\"test1\",test2=\"test2\",test3=\"test3\"} > $val",
|
||||
},
|
||||
want: "mem{test1=\"test1\",test2=\"test2\",test3=\"test3\"} > $val",
|
||||
},
|
||||
{
|
||||
name: "removeVal9",
|
||||
args: args{
|
||||
promql: "mem{test1=\"$test1\",test2=\"test2\"} > $val1 and mem{test3=\"test3\",test4=\"test4\"} > $val2",
|
||||
},
|
||||
want: "mem{test2=\"test2\"} > $val1 and mem{test3=\"test3\",test4=\"test4\"} > $val2",
|
||||
},
|
||||
{
|
||||
name: "removeVal10",
|
||||
args: args{
|
||||
promql: "mem{test1=\"test1\",test2=\"$test2\"} > $val1 and mem{test3=\"test3\",test4=\"test4\"} > $val2",
|
||||
},
|
||||
want: "mem{test1=\"test1\"} > $val1 and mem{test3=\"test3\",test4=\"test4\"} > $val2",
|
||||
},
|
||||
{
|
||||
name: "removeVal11",
|
||||
args: args{
|
||||
promql: "mem{test1=\"test1\",test2=\"test2\"} > $val1 and mem{test3=\"$test3\",test4=\"test4\"} > $val2",
|
||||
},
|
||||
want: "mem{test1=\"test1\",test2=\"test2\"} > $val1 and mem{test4=\"test4\"} > $val2",
|
||||
},
|
||||
{
|
||||
name: "removeVal12",
|
||||
args: args{
|
||||
promql: "mem{test1=\"test1\",test2=\"test2\"} > $val1 and mem{test3=\"test3\",test4=\"$test4\"} > $val2",
|
||||
},
|
||||
want: "mem{test1=\"test1\",test2=\"test2\"} > $val1 and mem{test3=\"test3\"} > $val2",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := removeVal(tt.args.promql); got != tt.want {
|
||||
t.Errorf("removeVal() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/pushgw/writer"
|
||||
|
||||
"github.com/prometheus/prometheus/prompb"
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
"github.com/toolkits/pkg/str"
|
||||
)
|
||||
@@ -79,9 +78,6 @@ type Processor struct {
|
||||
HandleFireEventHook HandleEventFunc
|
||||
HandleRecoverEventHook HandleEventFunc
|
||||
EventMuteHook EventMuteHookFunc
|
||||
|
||||
ScheduleEntry cron.Entry
|
||||
EvalStart int64
|
||||
}
|
||||
|
||||
func (p *Processor) Key() string {
|
||||
@@ -93,9 +89,9 @@ func (p *Processor) DatasourceId() int64 {
|
||||
}
|
||||
|
||||
func (p *Processor) Hash() string {
|
||||
return str.MD5(fmt.Sprintf("%d_%s_%s_%d",
|
||||
return str.MD5(fmt.Sprintf("%d_%d_%s_%d",
|
||||
p.rule.Id,
|
||||
p.rule.CronPattern,
|
||||
p.rule.PromEvalInterval,
|
||||
p.rule.RuleConfig,
|
||||
p.datasourceId,
|
||||
))
|
||||
@@ -130,7 +126,7 @@ func NewProcessor(engineName string, rule *models.AlertRule, datasourceId int64,
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Processor) Handle(anomalyPoints []models.AnomalyPoint, from string, inhibit bool) {
|
||||
func (p *Processor) Handle(anomalyPoints []common.AnomalyPoint, from string, inhibit bool) {
|
||||
// 有可能rule的一些配置已经发生变化,比如告警接收人、callbacks等
|
||||
// 这些信息的修改是不会引起worker restart的,但是确实会影响告警处理逻辑
|
||||
// 所以,这里直接从memsto.AlertRuleCache中获取并覆盖
|
||||
@@ -138,13 +134,10 @@ func (p *Processor) Handle(anomalyPoints []models.AnomalyPoint, from string, inh
|
||||
cachedRule := p.alertRuleCache.Get(p.rule.Id)
|
||||
if cachedRule == nil {
|
||||
logger.Errorf("rule not found %+v", anomalyPoints)
|
||||
p.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", p.DatasourceId()), "handle_event", p.BusiGroupCache.GetNameByBusiGroupId(p.rule.GroupId), fmt.Sprintf("%v", p.rule.Id)).Inc()
|
||||
p.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", p.DatasourceId()), "handle_event").Inc()
|
||||
return
|
||||
}
|
||||
|
||||
// 在 rule 变化之前取到 ruleHash
|
||||
ruleHash := p.rule.Hash()
|
||||
|
||||
p.rule = cachedRule
|
||||
now := time.Now().Unix()
|
||||
alertingKeys := map[string]struct{}{}
|
||||
@@ -152,7 +145,7 @@ func (p *Processor) Handle(anomalyPoints []models.AnomalyPoint, from string, inh
|
||||
// 根据 event 的 tag 将 events 分组,处理告警抑制的情况
|
||||
eventsMap := make(map[string][]*models.AlertCurEvent)
|
||||
for _, anomalyPoint := range anomalyPoints {
|
||||
event := p.BuildEvent(anomalyPoint, from, now, ruleHash)
|
||||
event := p.BuildEvent(anomalyPoint, from, now)
|
||||
// 如果 event 被 mute 了,本质也是 fire 的状态,这里无论如何都添加到 alertingKeys 中,防止 fire 的事件自动恢复了
|
||||
hash := event.Hash
|
||||
alertingKeys[hash] = struct{}{}
|
||||
@@ -182,7 +175,7 @@ func (p *Processor) Handle(anomalyPoints []models.AnomalyPoint, from string, inh
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Processor) BuildEvent(anomalyPoint models.AnomalyPoint, from string, now int64, ruleHash string) *models.AlertCurEvent {
|
||||
func (p *Processor) BuildEvent(anomalyPoint common.AnomalyPoint, from string, now int64) *models.AlertCurEvent {
|
||||
p.fillTags(anomalyPoint)
|
||||
p.mayHandleIdent()
|
||||
hash := Hash(p.rule.Id, p.datasourceId, anomalyPoint)
|
||||
@@ -208,7 +201,6 @@ func (p *Processor) BuildEvent(anomalyPoint models.AnomalyPoint, from string, no
|
||||
event.TargetNote = p.targetNote
|
||||
event.TriggerValue = anomalyPoint.ReadableValue()
|
||||
event.TriggerValues = anomalyPoint.Values
|
||||
event.TriggerValuesJson = models.EventTriggerValues{ValuesWithUnit: anomalyPoint.ValuesUnit}
|
||||
event.TagsJSON = p.tagsArr
|
||||
event.Tags = strings.Join(p.tagsArr, ",,")
|
||||
event.IsRecovered = false
|
||||
@@ -222,11 +214,9 @@ func (p *Processor) BuildEvent(anomalyPoint models.AnomalyPoint, from string, no
|
||||
event.ExtraConfig = p.rule.ExtraConfigJSON
|
||||
event.PromQl = anomalyPoint.Query
|
||||
event.RecoverConfig = anomalyPoint.RecoverConfig
|
||||
event.RuleHash = ruleHash
|
||||
|
||||
if p.target != "" {
|
||||
if pt, exist := p.TargetCache.Get(p.target); exist {
|
||||
pt.GroupNames = p.BusiGroupCache.GetNamesByBusiGroupIds(pt.GroupIds)
|
||||
event.Target = pt
|
||||
} else {
|
||||
logger.Infof("Target[ident: %s] doesn't exist in cache.", p.target)
|
||||
@@ -442,7 +432,6 @@ func (p *Processor) handleEvent(events []*models.AlertCurEvent) {
|
||||
preTriggerTime = event.TriggerTime
|
||||
}
|
||||
|
||||
event.PromEvalInterval = int(p.ScheduleEntry.Schedule.Next(time.Unix(p.EvalStart, 0)).Unix() - p.EvalStart)
|
||||
if event.LastEvalTime-preTriggerTime+int64(event.PromEvalInterval) >= int64(p.rule.PromForDuration) {
|
||||
fireEvents = append(fireEvents, event)
|
||||
if severity > event.Severity {
|
||||
@@ -519,7 +508,7 @@ func (p *Processor) pushEventToQueue(e *models.AlertCurEvent) {
|
||||
dispatch.LogEvent(e, "push_queue")
|
||||
if !queue.EventQueue.PushFront(e) {
|
||||
logger.Warningf("event_push_queue: queue is full, event:%+v", e)
|
||||
p.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", p.DatasourceId()), "push_event_queue", p.BusiGroupCache.GetNameByBusiGroupId(p.rule.GroupId), fmt.Sprintf("%v", p.rule.Id)).Inc()
|
||||
p.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", p.DatasourceId()), "push_event_queue").Inc()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,7 +519,7 @@ func (p *Processor) RecoverAlertCurEventFromDb() {
|
||||
curEvents, err := models.AlertCurEventGetByRuleIdAndDsId(p.ctx, p.rule.Id, p.datasourceId)
|
||||
if err != nil {
|
||||
logger.Errorf("recover event from db for rule:%s failed, err:%s", p.Key(), err)
|
||||
p.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", p.DatasourceId()), "get_recover_event", p.BusiGroupCache.GetNameByBusiGroupId(p.rule.GroupId), fmt.Sprintf("%v", p.rule.Id)).Inc()
|
||||
p.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", p.DatasourceId()), "get_recover_event").Inc()
|
||||
p.fires = NewAlertCurEventMap(nil)
|
||||
return
|
||||
}
|
||||
@@ -549,7 +538,6 @@ func (p *Processor) RecoverAlertCurEventFromDb() {
|
||||
event.DB2Mem()
|
||||
target, exists := p.TargetCache.Get(event.TargetIdent)
|
||||
if exists {
|
||||
target.GroupNames = p.BusiGroupCache.GetNamesByBusiGroupIds(target.GroupIds)
|
||||
event.Target = target
|
||||
}
|
||||
|
||||
@@ -564,7 +552,7 @@ func (p *Processor) RecoverAlertCurEventFromDb() {
|
||||
p.pendingsUseByRecover = NewAlertCurEventMap(pendingsUseByRecoverMap)
|
||||
}
|
||||
|
||||
func (p *Processor) fillTags(anomalyPoint models.AnomalyPoint) {
|
||||
func (p *Processor) fillTags(anomalyPoint common.AnomalyPoint) {
|
||||
// handle series tags
|
||||
tagsMap := make(map[string]string)
|
||||
for label, value := range anomalyPoint.Labels {
|
||||
@@ -654,10 +642,10 @@ func labelMapToArr(m map[string]string) []string {
|
||||
return labelStrings
|
||||
}
|
||||
|
||||
func Hash(ruleId, datasourceId int64, vector models.AnomalyPoint) string {
|
||||
func Hash(ruleId, datasourceId int64, vector common.AnomalyPoint) string {
|
||||
return str.MD5(fmt.Sprintf("%d_%s_%d_%d_%s", ruleId, vector.Labels.String(), datasourceId, vector.Severity, vector.Query))
|
||||
}
|
||||
|
||||
func TagHash(vector models.AnomalyPoint) string {
|
||||
func TagHash(vector common.AnomalyPoint) string {
|
||||
return str.MD5(vector.Labels.String())
|
||||
}
|
||||
|
||||
@@ -26,11 +26,9 @@ type Scheduler struct {
|
||||
writers *writer.WritersType
|
||||
|
||||
stats *astats.Stats
|
||||
|
||||
datasourceCache *memsto.DatasourceCacheType
|
||||
}
|
||||
|
||||
func NewScheduler(aconf aconf.Alert, rrc *memsto.RecordingRuleCacheType, promClients *prom.PromClientMap, writers *writer.WritersType, stats *astats.Stats, datasourceCache *memsto.DatasourceCacheType) *Scheduler {
|
||||
func NewScheduler(aconf aconf.Alert, rrc *memsto.RecordingRuleCacheType, promClients *prom.PromClientMap, writers *writer.WritersType, stats *astats.Stats) *Scheduler {
|
||||
scheduler := &Scheduler{
|
||||
aconf: aconf,
|
||||
recordRules: make(map[string]*RecordRuleContext),
|
||||
@@ -41,8 +39,6 @@ func NewScheduler(aconf aconf.Alert, rrc *memsto.RecordingRuleCacheType, promCli
|
||||
writers: writers,
|
||||
|
||||
stats: stats,
|
||||
|
||||
datasourceCache: datasourceCache,
|
||||
}
|
||||
|
||||
go scheduler.LoopSyncRules(context.Background())
|
||||
@@ -71,7 +67,7 @@ func (s *Scheduler) syncRecordRules() {
|
||||
continue
|
||||
}
|
||||
|
||||
datasourceIds := s.datasourceCache.GetIDsByDsCateAndQueries("prometheus", rule.DatasourceQueries)
|
||||
datasourceIds := s.promClients.Hit(rule.DatasourceIdsJson)
|
||||
for _, dsId := range datasourceIds {
|
||||
if !naming.DatasourceHashRing.IsHit(strconv.FormatInt(dsId, 10), fmt.Sprintf("%d", rule.Id), s.aconf.Heartbeat.Endpoint) {
|
||||
continue
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/common"
|
||||
"github.com/ccfos/nightingale/v6/alert/dispatch"
|
||||
"github.com/ccfos/nightingale/v6/alert/mute"
|
||||
"github.com/ccfos/nightingale/v6/alert/naming"
|
||||
@@ -91,7 +92,7 @@ func (rt *Router) eventPersist(c *gin.Context) {
|
||||
|
||||
type eventForm struct {
|
||||
Alert bool `json:"alert"`
|
||||
AnomalyPoints []models.AnomalyPoint `json:"vectors"`
|
||||
AnomalyPoints []common.AnomalyPoint `json:"vectors"`
|
||||
RuleId int64 `json:"rule_id"`
|
||||
DatasourceId int64 `json:"datasource_id"`
|
||||
Inhibit bool `json:"inhibit"`
|
||||
|
||||
@@ -129,39 +129,43 @@ func (c *DefaultCallBacker) CallBack(ctx CallBackContext) {
|
||||
return
|
||||
}
|
||||
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, event, "callback", ctx.Stats, ctx.Events)
|
||||
ctx.Stats.AlertNotifyTotal.WithLabelValues("rule_callback").Inc()
|
||||
resp, code, err := poster.PostJSON(ctx.CallBackURL, 5*time.Second, event, 3)
|
||||
if err != nil {
|
||||
logger.Errorf("event_callback_fail(rule_id=%d url=%s), event:%+v, resp: %s, err: %v, code: %d",
|
||||
event.RuleId, ctx.CallBackURL, event, string(resp), err, code)
|
||||
ctx.Stats.AlertNotifyErrorTotal.WithLabelValues("rule_callback").Inc()
|
||||
} else {
|
||||
logger.Infof("event_callback_succ(rule_id=%d url=%s), event:%+v, resp: %s, code: %d",
|
||||
event.RuleId, ctx.CallBackURL, event, string(resp), code)
|
||||
}
|
||||
}
|
||||
|
||||
func doSendAndRecord(ctx *ctx.Context, url, token string, body interface{}, channel string,
|
||||
stats *astats.Stats, events []*models.AlertCurEvent) {
|
||||
stats *astats.Stats, event *models.AlertCurEvent) {
|
||||
res, err := doSend(url, body, channel, stats)
|
||||
NotifyRecord(ctx, events, channel, token, res, err)
|
||||
NotifyRecord(ctx, event, channel, token, res, err)
|
||||
}
|
||||
|
||||
func NotifyRecord(ctx *ctx.Context, evts []*models.AlertCurEvent, channel, target, res string, err error) {
|
||||
// 一个通知可能对应多个 event,都需要记录
|
||||
notis := make([]*models.NotificaitonRecord, 0, len(evts))
|
||||
for _, evt := range evts {
|
||||
noti := models.NewNotificationRecord(evt, channel, target)
|
||||
if err != nil {
|
||||
noti.SetStatus(models.NotiStatusFailure)
|
||||
noti.SetDetails(err.Error())
|
||||
} else if res != "" {
|
||||
noti.SetDetails(string(res))
|
||||
}
|
||||
notis = append(notis, noti)
|
||||
func NotifyRecord(ctx *ctx.Context, evt *models.AlertCurEvent, channel, target, res string, err error) {
|
||||
noti := models.NewNotificationRecord(evt, channel, target)
|
||||
if err != nil {
|
||||
noti.SetStatus(models.NotiStatusFailure)
|
||||
noti.SetDetails(err.Error())
|
||||
} else if res != "" {
|
||||
noti.SetDetails(string(res))
|
||||
}
|
||||
|
||||
if !ctx.IsCenter {
|
||||
_, err := poster.PostByUrlsWithResp[[]int64](ctx, "/v1/n9e/notify-record", notis)
|
||||
_, err := poster.PostByUrlsWithResp[int64](ctx, "/v1/n9e/notify-record", noti)
|
||||
if err != nil {
|
||||
logger.Errorf("add notis:%v failed, err: %v", notis, err)
|
||||
logger.Errorf("add noti:%v failed, err: %v", noti, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := models.DB(ctx).CreateInBatches(notis, 100).Error; err != nil {
|
||||
logger.Errorf("add notis:%v failed, err: %v", notis, err)
|
||||
if err := noti.Add(ctx); err != nil {
|
||||
logger.Errorf("add noti:%v failed, err: %v", noti, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,8 +195,8 @@ func PushCallbackEvent(ctx *ctx.Context, webhook *models.Webhook, event *models.
|
||||
|
||||
if queue == nil {
|
||||
queue = &WebhookQueue{
|
||||
eventQueue: NewSafeEventQueue(QueueMaxSize),
|
||||
closeCh: make(chan struct{}),
|
||||
list: NewSafeListLimited(QueueMaxSize),
|
||||
closeCh: make(chan struct{}),
|
||||
}
|
||||
|
||||
CallbackEventQueueLock.Lock()
|
||||
@@ -202,8 +206,8 @@ func PushCallbackEvent(ctx *ctx.Context, webhook *models.Webhook, event *models.
|
||||
StartConsumer(ctx, queue, webhook.Batch, webhook, stats)
|
||||
}
|
||||
|
||||
succ := queue.eventQueue.Push(event)
|
||||
succ := queue.list.PushFront(event)
|
||||
if !succ {
|
||||
logger.Warningf("Write channel(%s) full, current channel size: %d event:%v", webhook.Url, queue.eventQueue.Len(), event)
|
||||
logger.Warningf("Write channel(%s) full, current channel size: %d event:%v", webhook.Url, queue.list.Len(), event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (ds *DingtalkSender) Send(ctx MessageContext) {
|
||||
}
|
||||
}
|
||||
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Dingtalk, ctx.Stats, ctx.Events)
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Dingtalk, ctx.Stats, ctx.Events[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,10 @@ func (ds *DingtalkSender) CallBack(ctx CallBackContext) {
|
||||
body.Markdown.Text = message
|
||||
}
|
||||
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback", ctx.Stats, ctx.Events)
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body,
|
||||
"callback", ctx.Stats, ctx.Events[0])
|
||||
|
||||
ctx.Stats.AlertNotifyTotal.WithLabelValues("rule_callback").Inc()
|
||||
}
|
||||
|
||||
// extract urls and ats from Users
|
||||
|
||||
@@ -25,8 +25,8 @@ type EmailSender struct {
|
||||
}
|
||||
|
||||
type EmailContext struct {
|
||||
events []*models.AlertCurEvent
|
||||
mail *gomail.Message
|
||||
event *models.AlertCurEvent
|
||||
mail *gomail.Message
|
||||
}
|
||||
|
||||
func (es *EmailSender) Send(ctx MessageContext) {
|
||||
@@ -42,7 +42,7 @@ func (es *EmailSender) Send(ctx MessageContext) {
|
||||
subject = ctx.Events[0].RuleName
|
||||
}
|
||||
content := BuildTplMessage(models.Email, es.contentTpl, ctx.Events)
|
||||
es.WriteEmail(subject, content, tos, ctx.Events)
|
||||
es.WriteEmail(subject, content, tos, ctx.Events[0])
|
||||
|
||||
ctx.Stats.AlertNotifyTotal.WithLabelValues(models.Email).Add(float64(len(tos)))
|
||||
}
|
||||
@@ -79,7 +79,8 @@ func SendEmail(subject, content string, tos []string, stmp aconf.SMTPConfig) err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *EmailSender) WriteEmail(subject, content string, tos []string, events []*models.AlertCurEvent) {
|
||||
func (es *EmailSender) WriteEmail(subject, content string, tos []string,
|
||||
event *models.AlertCurEvent) {
|
||||
m := gomail.NewMessage()
|
||||
|
||||
m.SetHeader("From", es.smtp.From)
|
||||
@@ -87,7 +88,7 @@ func (es *EmailSender) WriteEmail(subject, content string, tos []string, events
|
||||
m.SetHeader("Subject", subject)
|
||||
m.SetBody("text/html", content)
|
||||
|
||||
mailch <- &EmailContext{events, m}
|
||||
mailch <- &EmailContext{event, m}
|
||||
}
|
||||
|
||||
func dialSmtp(d *gomail.Dialer) gomail.SendCloser {
|
||||
@@ -201,11 +202,7 @@ func startEmailSender(ctx *ctx.Context, smtp aconf.SMTPConfig) {
|
||||
}
|
||||
|
||||
for _, to := range m.mail.GetHeader("To") {
|
||||
msg := ""
|
||||
if err == nil {
|
||||
msg = "ok"
|
||||
}
|
||||
NotifyRecord(ctx, m.events, models.Email, to, msg, err)
|
||||
NotifyRecord(ctx, m.event, models.Email, to, "", err)
|
||||
}
|
||||
|
||||
size++
|
||||
|
||||
@@ -54,7 +54,9 @@ func (fs *FeishuSender) CallBack(ctx CallBackContext) {
|
||||
},
|
||||
}
|
||||
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback", ctx.Stats, ctx.Events)
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback",
|
||||
ctx.Stats, ctx.Events[0])
|
||||
ctx.Stats.AlertNotifyTotal.WithLabelValues("rule_callback").Inc()
|
||||
}
|
||||
|
||||
func (fs *FeishuSender) Send(ctx MessageContext) {
|
||||
@@ -76,7 +78,7 @@ func (fs *FeishuSender) Send(ctx MessageContext) {
|
||||
IsAtAll: false,
|
||||
}
|
||||
}
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Feishu, ctx.Stats, ctx.Events)
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Feishu, ctx.Stats, ctx.Events[0])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +135,8 @@ func (fs *FeishuCardSender) CallBack(ctx CallBackContext) {
|
||||
}
|
||||
parsedURL.RawQuery = ""
|
||||
|
||||
doSendAndRecord(ctx.Ctx, parsedURL.String(), parsedURL.String(), body, "callback", ctx.Stats, ctx.Events)
|
||||
doSendAndRecord(ctx.Ctx, parsedURL.String(), parsedURL.String(), body, "callback",
|
||||
ctx.Stats, ctx.Events[0])
|
||||
}
|
||||
|
||||
func (fs *FeishuCardSender) Send(ctx MessageContext) {
|
||||
@@ -159,7 +160,8 @@ func (fs *FeishuCardSender) Send(ctx MessageContext) {
|
||||
body.Card.Elements[0].Text.Content = message
|
||||
body.Card.Elements[2].Elements[0].Content = SendTitle
|
||||
for i, url := range urls {
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.FeishuCard, ctx.Stats, ctx.Events)
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.FeishuCard,
|
||||
ctx.Stats, ctx.Events[0])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,6 @@ func CallIbex(ctx *ctx.Context, id int64, host string,
|
||||
// 附加告警级别 告警触发值标签
|
||||
tagsMap["alert_severity"] = strconv.Itoa(event.Severity)
|
||||
tagsMap["alert_trigger_value"] = event.TriggerValue
|
||||
tagsMap["is_recovered"] = strconv.FormatBool(event.IsRecovered)
|
||||
|
||||
tags, err := json.Marshal(tagsMap)
|
||||
if err != nil {
|
||||
|
||||
@@ -27,29 +27,30 @@ func (lk *LarkSender) CallBack(ctx CallBackContext) {
|
||||
},
|
||||
}
|
||||
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback", ctx.Stats, ctx.Events)
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback",
|
||||
ctx.Stats, ctx.Events[0])
|
||||
ctx.Stats.AlertNotifyTotal.WithLabelValues("rule_callback").Inc()
|
||||
}
|
||||
|
||||
func (lk *LarkSender) Send(ctx MessageContext) {
|
||||
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
|
||||
return
|
||||
}
|
||||
urls, tokens := lk.extract(ctx.Users)
|
||||
urls := lk.extract(ctx.Users)
|
||||
message := BuildTplMessage(models.Lark, lk.tpl, ctx.Events)
|
||||
for i, url := range urls {
|
||||
for _, url := range urls {
|
||||
body := feishu{
|
||||
Msgtype: "text",
|
||||
Content: feishuContent{
|
||||
Text: message,
|
||||
},
|
||||
}
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Lark, ctx.Stats, ctx.Events)
|
||||
doSend(url, body, models.Lark, ctx.Stats)
|
||||
}
|
||||
}
|
||||
|
||||
func (lk *LarkSender) extract(users []*models.User) ([]string, []string) {
|
||||
func (lk *LarkSender) extract(users []*models.User) []string {
|
||||
urls := make([]string, 0, len(users))
|
||||
tokens := make([]string, 0, len(users))
|
||||
|
||||
for _, user := range users {
|
||||
if token, has := user.ExtractToken(models.Lark); has {
|
||||
@@ -58,8 +59,7 @@ func (lk *LarkSender) extract(users []*models.User) ([]string, []string) {
|
||||
url = "https://open.larksuite.com/open-apis/bot/v2/hook/" + token
|
||||
}
|
||||
urls = append(urls, url)
|
||||
tokens = append(tokens, token)
|
||||
}
|
||||
}
|
||||
return urls, tokens
|
||||
return urls
|
||||
}
|
||||
|
||||
@@ -56,14 +56,15 @@ func (fs *LarkCardSender) CallBack(ctx CallBackContext) {
|
||||
}
|
||||
parsedURL.RawQuery = ""
|
||||
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback", ctx.Stats, ctx.Events)
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback",
|
||||
ctx.Stats, ctx.Events[0])
|
||||
}
|
||||
|
||||
func (fs *LarkCardSender) Send(ctx MessageContext) {
|
||||
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
|
||||
return
|
||||
}
|
||||
urls, tokens := fs.extract(ctx.Users)
|
||||
urls, _ := fs.extract(ctx.Users)
|
||||
message := BuildTplMessage(models.LarkCard, fs.tpl, ctx.Events)
|
||||
color := "red"
|
||||
lowerUnicode := strings.ToLower(message)
|
||||
@@ -79,14 +80,14 @@ func (fs *LarkCardSender) Send(ctx MessageContext) {
|
||||
body.Card.Header.Template = color
|
||||
body.Card.Elements[0].Text.Content = message
|
||||
body.Card.Elements[2].Elements[0].Content = SendTitle
|
||||
for i, url := range urls {
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.LarkCard, ctx.Stats, ctx.Events)
|
||||
for _, url := range urls {
|
||||
doSend(url, body, models.LarkCard, ctx.Stats)
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *LarkCardSender) extract(users []*models.User) ([]string, []string) {
|
||||
urls := make([]string, 0, len(users))
|
||||
tokens := make([]string, 0)
|
||||
ats := make([]string, 0)
|
||||
for i := range users {
|
||||
if token, has := users[i].ExtractToken(models.Lark); has {
|
||||
url := token
|
||||
@@ -94,8 +95,7 @@ func (fs *LarkCardSender) extract(users []*models.User) ([]string, []string) {
|
||||
url = "https://open.larksuite.com/open-apis/bot/v2/hook/" + strings.TrimSpace(token)
|
||||
}
|
||||
urls = append(urls, url)
|
||||
tokens = append(tokens, token)
|
||||
}
|
||||
}
|
||||
return urls, tokens
|
||||
return urls, ats
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func (ms *MmSender) Send(ctx MessageContext) {
|
||||
Text: message,
|
||||
Tokens: urls,
|
||||
Stats: ctx.Stats,
|
||||
}, ctx.Events, models.Mm)
|
||||
}, ctx.Events[0])
|
||||
}
|
||||
|
||||
func (ms *MmSender) CallBack(ctx CallBackContext) {
|
||||
@@ -56,7 +56,9 @@ func (ms *MmSender) CallBack(ctx CallBackContext) {
|
||||
Text: message,
|
||||
Tokens: []string{ctx.CallBackURL},
|
||||
Stats: ctx.Stats,
|
||||
}, ctx.Events, "callback")
|
||||
}, ctx.Events[0])
|
||||
|
||||
ctx.Stats.AlertNotifyTotal.WithLabelValues("rule_callback").Inc()
|
||||
}
|
||||
|
||||
func (ms *MmSender) extract(users []*models.User) []string {
|
||||
@@ -69,12 +71,11 @@ func (ms *MmSender) extract(users []*models.User) []string {
|
||||
return tokens
|
||||
}
|
||||
|
||||
func SendMM(ctx *ctx.Context, message MatterMostMessage, events []*models.AlertCurEvent, channel string) {
|
||||
func SendMM(ctx *ctx.Context, message MatterMostMessage, event *models.AlertCurEvent) {
|
||||
for i := 0; i < len(message.Tokens); i++ {
|
||||
u, err := url.Parse(message.Tokens[i])
|
||||
if err != nil {
|
||||
logger.Errorf("mm_sender: failed to parse error=%v", err)
|
||||
NotifyRecord(ctx, events, channel, message.Tokens[i], "", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -103,7 +104,7 @@ func SendMM(ctx *ctx.Context, message MatterMostMessage, events []*models.AlertC
|
||||
Username: username,
|
||||
Text: txt + message.Text,
|
||||
}
|
||||
doSendAndRecord(ctx, ur, message.Tokens[i], body, channel, message.Stats, events)
|
||||
doSendAndRecord(ctx, ur, message.Tokens[i], body, models.Mm, message.Stats, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func alertingCallScript(ctx *ctx.Context, stdinBytes []byte, notifyScript models
|
||||
}
|
||||
|
||||
err, isTimeout := sys.WrapTimeout(cmd, time.Duration(config.Timeout)*time.Second)
|
||||
NotifyRecord(ctx, []*models.AlertCurEvent{event}, channel, cmd.String(), "", buildErr(err, isTimeout))
|
||||
NotifyRecord(ctx, event, channel, cmd.String(), "", buildErr(err, isTimeout))
|
||||
|
||||
if isTimeout {
|
||||
if err == nil {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package sender
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"html/template"
|
||||
"strings"
|
||||
|
||||
@@ -41,7 +40,9 @@ func (ts *TelegramSender) CallBack(ctx CallBackContext) {
|
||||
Text: message,
|
||||
Tokens: []string{ctx.CallBackURL},
|
||||
Stats: ctx.Stats,
|
||||
}, ctx.Events, "callback")
|
||||
}, ctx.Events[0])
|
||||
|
||||
ctx.Stats.AlertNotifyTotal.WithLabelValues("rule_callback").Inc()
|
||||
}
|
||||
|
||||
func (ts *TelegramSender) Send(ctx MessageContext) {
|
||||
@@ -55,7 +56,7 @@ func (ts *TelegramSender) Send(ctx MessageContext) {
|
||||
Text: message,
|
||||
Tokens: tokens,
|
||||
Stats: ctx.Stats,
|
||||
}, ctx.Events, models.Telegram)
|
||||
}, ctx.Events[0])
|
||||
}
|
||||
|
||||
func (ts *TelegramSender) extract(users []*models.User) []string {
|
||||
@@ -68,11 +69,10 @@ func (ts *TelegramSender) extract(users []*models.User) []string {
|
||||
return tokens
|
||||
}
|
||||
|
||||
func SendTelegram(ctx *ctx.Context, message TelegramMessage, events []*models.AlertCurEvent, channel string) {
|
||||
func SendTelegram(ctx *ctx.Context, message TelegramMessage, event *models.AlertCurEvent) {
|
||||
for i := 0; i < len(message.Tokens); i++ {
|
||||
if !strings.Contains(message.Tokens[i], "/") && !strings.HasPrefix(message.Tokens[i], "https://") {
|
||||
logger.Errorf("telegram_sender: result=fail invalid token=%s", message.Tokens[i])
|
||||
NotifyRecord(ctx, events, channel, message.Tokens[i], "", errors.New("invalid token"))
|
||||
continue
|
||||
}
|
||||
var url string
|
||||
@@ -93,6 +93,6 @@ func SendTelegram(ctx *ctx.Context, message TelegramMessage, events []*models.Al
|
||||
Text: message.Text,
|
||||
}
|
||||
|
||||
doSendAndRecord(ctx, url, message.Tokens[i], body, channel, message.Stats, events)
|
||||
doSendAndRecord(ctx, url, message.Tokens[i], body, models.Telegram, message.Stats, event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func SingleSendWebhooks(ctx *ctx.Context, webhooks []*models.Webhook, event *mod
|
||||
retryCount := 0
|
||||
for retryCount < 3 {
|
||||
needRetry, res, err := sendWebhook(conf, event, stats)
|
||||
NotifyRecord(ctx, []*models.AlertCurEvent{event}, "webhook", conf.Url, res, err)
|
||||
NotifyRecord(ctx, event, "webhook", conf.Url, res, err)
|
||||
if !needRetry {
|
||||
break
|
||||
}
|
||||
@@ -121,8 +121,8 @@ var EventQueueLock sync.RWMutex
|
||||
const QueueMaxSize = 100000
|
||||
|
||||
type WebhookQueue struct {
|
||||
eventQueue *SafeEventQueue
|
||||
closeCh chan struct{}
|
||||
list *SafeListLimited
|
||||
closeCh chan struct{}
|
||||
}
|
||||
|
||||
func PushEvent(ctx *ctx.Context, webhook *models.Webhook, event *models.AlertCurEvent, stats *astats.Stats) {
|
||||
@@ -132,8 +132,8 @@ func PushEvent(ctx *ctx.Context, webhook *models.Webhook, event *models.AlertCur
|
||||
|
||||
if queue == nil {
|
||||
queue = &WebhookQueue{
|
||||
eventQueue: NewSafeEventQueue(QueueMaxSize),
|
||||
closeCh: make(chan struct{}),
|
||||
list: NewSafeListLimited(QueueMaxSize),
|
||||
closeCh: make(chan struct{}),
|
||||
}
|
||||
|
||||
EventQueueLock.Lock()
|
||||
@@ -143,10 +143,10 @@ func PushEvent(ctx *ctx.Context, webhook *models.Webhook, event *models.AlertCur
|
||||
StartConsumer(ctx, queue, webhook.Batch, webhook, stats)
|
||||
}
|
||||
|
||||
succ := queue.eventQueue.Push(event)
|
||||
succ := queue.list.PushFront(event)
|
||||
if !succ {
|
||||
stats.AlertNotifyErrorTotal.WithLabelValues("push_event_queue").Inc()
|
||||
logger.Warningf("Write channel(%s) full, current channel size: %d event:%v", webhook.Url, queue.eventQueue.Len(), event)
|
||||
logger.Warningf("Write channel(%s) full, current channel size: %d event:%v", webhook.Url, queue.list.Len(), event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ func StartConsumer(ctx *ctx.Context, queue *WebhookQueue, popSize int, webhook *
|
||||
logger.Infof("event queue:%v closed", queue)
|
||||
return
|
||||
default:
|
||||
events := queue.eventQueue.PopN(popSize)
|
||||
events := queue.list.PopBack(popSize)
|
||||
if len(events) == 0 {
|
||||
time.Sleep(time.Millisecond * 400)
|
||||
continue
|
||||
@@ -166,7 +166,7 @@ func StartConsumer(ctx *ctx.Context, queue *WebhookQueue, popSize int, webhook *
|
||||
retryCount := 0
|
||||
for retryCount < webhook.RetryCount {
|
||||
needRetry, res, err := sendWebhook(webhook, events, stats)
|
||||
go NotifyRecord(ctx, events, "webhook", webhook.Url, res, err)
|
||||
go RecordEvents(ctx, webhook, events, stats, res, err)
|
||||
if !needRetry {
|
||||
break
|
||||
}
|
||||
@@ -176,3 +176,10 @@ func StartConsumer(ctx *ctx.Context, queue *WebhookQueue, popSize int, webhook *
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RecordEvents(ctx *ctx.Context, webhook *models.Webhook, events []*models.AlertCurEvent, stats *astats.Stats, res string, err error) {
|
||||
for _, event := range events {
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
NotifyRecord(ctx, event, "webhook", webhook.Url, res, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
package sender
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
)
|
||||
|
||||
type SafeEventQueue struct {
|
||||
lock sync.RWMutex
|
||||
maxSize int
|
||||
queueHigh *list.List
|
||||
queueMiddle *list.List
|
||||
queueLow *list.List
|
||||
}
|
||||
|
||||
const (
|
||||
High = 1
|
||||
Middle = 2
|
||||
Low = 3
|
||||
)
|
||||
|
||||
func NewSafeEventQueue(maxSize int) *SafeEventQueue {
|
||||
return &SafeEventQueue{
|
||||
maxSize: maxSize,
|
||||
lock: sync.RWMutex{},
|
||||
queueHigh: list.New(),
|
||||
queueMiddle: list.New(),
|
||||
queueLow: list.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (spq *SafeEventQueue) Len() int {
|
||||
spq.lock.RLock()
|
||||
defer spq.lock.RUnlock()
|
||||
return spq.queueHigh.Len() + spq.queueMiddle.Len() + spq.queueLow.Len()
|
||||
}
|
||||
|
||||
// len 无锁读取长度,不要在本文件外调用
|
||||
func (spq *SafeEventQueue) len() int {
|
||||
return spq.queueHigh.Len() + spq.queueMiddle.Len() + spq.queueLow.Len()
|
||||
}
|
||||
|
||||
func (spq *SafeEventQueue) Push(event *models.AlertCurEvent) bool {
|
||||
spq.lock.Lock()
|
||||
defer spq.lock.Unlock()
|
||||
|
||||
for spq.len() > spq.maxSize {
|
||||
return false
|
||||
}
|
||||
|
||||
switch event.Severity {
|
||||
case High:
|
||||
spq.queueHigh.PushBack(event)
|
||||
case Middle:
|
||||
spq.queueMiddle.PushBack(event)
|
||||
case Low:
|
||||
spq.queueLow.PushBack(event)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// pop 无锁弹出事件,不要在本文件外调用
|
||||
func (spq *SafeEventQueue) pop() *models.AlertCurEvent {
|
||||
if spq.len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var elem interface{}
|
||||
|
||||
if spq.queueHigh.Len() > 0 {
|
||||
elem = spq.queueHigh.Remove(spq.queueHigh.Front())
|
||||
} else if spq.queueMiddle.Len() > 0 {
|
||||
elem = spq.queueMiddle.Remove(spq.queueMiddle.Front())
|
||||
} else {
|
||||
elem = spq.queueLow.Remove(spq.queueLow.Front())
|
||||
}
|
||||
event, ok := elem.(*models.AlertCurEvent)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
}
|
||||
|
||||
func (spq *SafeEventQueue) Pop() *models.AlertCurEvent {
|
||||
spq.lock.Lock()
|
||||
defer spq.lock.Unlock()
|
||||
return spq.pop()
|
||||
}
|
||||
|
||||
func (spq *SafeEventQueue) PopN(n int) []*models.AlertCurEvent {
|
||||
spq.lock.Lock()
|
||||
defer spq.lock.Unlock()
|
||||
|
||||
events := make([]*models.AlertCurEvent, 0, n)
|
||||
count := 0
|
||||
for count < n && spq.len() > 0 {
|
||||
event := spq.pop()
|
||||
if event != nil {
|
||||
events = append(events, event)
|
||||
}
|
||||
count++
|
||||
}
|
||||
return events
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
package sender
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSafePriorityQueue_ConcurrentPushPop(t *testing.T) {
|
||||
spq := NewSafeEventQueue(100000)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
numGoroutines := 100
|
||||
numEvents := 1000
|
||||
|
||||
// 并发 Push
|
||||
wg.Add(numGoroutines)
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
go func(goroutineID int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < numEvents; j++ {
|
||||
event := &models.AlertCurEvent{
|
||||
Severity: goroutineID%3 + 1,
|
||||
TriggerTime: time.Now().UnixNano(),
|
||||
}
|
||||
spq.Push(event)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// 检查队列长度是否正确
|
||||
expectedLen := numGoroutines * numEvents
|
||||
assert.Equal(t, expectedLen, spq.Len(), "Queue length mismatch after concurrent pushes")
|
||||
|
||||
// 并发 Pop
|
||||
wg.Add(numGoroutines)
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
event := spq.Pop()
|
||||
if event == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// 最终队列应该为空
|
||||
assert.Equal(t, 0, spq.Len(), "Queue should be empty after concurrent pops")
|
||||
}
|
||||
|
||||
func TestSafePriorityQueue_ConcurrentPopMax(t *testing.T) {
|
||||
spq := NewSafeEventQueue(100000)
|
||||
|
||||
// 添加初始数据
|
||||
for i := 0; i < 1000; i++ {
|
||||
spq.Push(&models.AlertCurEvent{
|
||||
Severity: i%3 + 1,
|
||||
TriggerTime: time.Now().UnixNano(),
|
||||
})
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
numGoroutines := 10
|
||||
popMax := 100
|
||||
|
||||
// 并发 PopN
|
||||
wg.Add(numGoroutines)
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
events := spq.PopN(popMax)
|
||||
assert.LessOrEqual(t, len(events), popMax, "PopN exceeded maximum")
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// 检查队列长度是否正确
|
||||
expectedRemaining := 1000 - (numGoroutines * popMax)
|
||||
if expectedRemaining < 0 {
|
||||
expectedRemaining = 0
|
||||
}
|
||||
assert.Equal(t, expectedRemaining, spq.Len(), "Queue length mismatch after concurrent PopN")
|
||||
}
|
||||
|
||||
func TestSafePriorityQueue_ConcurrentPushPopWithDifferentSeverities(t *testing.T) {
|
||||
spq := NewSafeEventQueue(100000)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
numGoroutines := 50
|
||||
numEvents := 500
|
||||
|
||||
// 并发 Push 不同优先级的事件
|
||||
wg.Add(numGoroutines)
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
go func(goroutineID int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < numEvents; j++ {
|
||||
event := &models.AlertCurEvent{
|
||||
Severity: goroutineID%3 + 1, // 模拟不同的 Severity
|
||||
TriggerTime: time.Now().UnixNano(),
|
||||
}
|
||||
spq.Push(event)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// 检查队列长度是否正确
|
||||
expectedLen := numGoroutines * numEvents
|
||||
assert.Equal(t, expectedLen, spq.Len(), "Queue length mismatch after concurrent pushes")
|
||||
|
||||
// 检查事件的顺序是否按照优先级排列
|
||||
var lastEvent *models.AlertCurEvent
|
||||
for spq.Len() > 0 {
|
||||
event := spq.Pop()
|
||||
if lastEvent != nil {
|
||||
assert.LessOrEqual(t, lastEvent.Severity, event.Severity, "Events are not in correct priority order")
|
||||
}
|
||||
lastEvent = event
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafePriorityQueue_ExceedMaxSize(t *testing.T) {
|
||||
spq := NewSafeEventQueue(5)
|
||||
|
||||
// 插入超过最大容量的事件
|
||||
for i := 0; i < 10; i++ {
|
||||
spq.Push(&models.AlertCurEvent{
|
||||
Severity: i % 3,
|
||||
TriggerTime: int64(i),
|
||||
})
|
||||
}
|
||||
|
||||
// 验证队列的长度是否不超过 maxSize
|
||||
assert.LessOrEqual(t, spq.Len(), spq.maxSize)
|
||||
|
||||
// 验证队列中剩余事件的内容
|
||||
expectedEvents := 5
|
||||
if spq.Len() < 5 {
|
||||
expectedEvents = spq.Len()
|
||||
}
|
||||
|
||||
// 检查最后存入的事件是否是按优先级排序
|
||||
for i := 0; i < expectedEvents; i++ {
|
||||
event := spq.Pop()
|
||||
if event != nil {
|
||||
assert.LessOrEqual(t, event.Severity, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,9 @@ func (ws *WecomSender) CallBack(ctx CallBackContext) {
|
||||
},
|
||||
}
|
||||
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback", ctx.Stats, ctx.Events)
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback",
|
||||
ctx.Stats, ctx.Events[0])
|
||||
ctx.Stats.AlertNotifyTotal.WithLabelValues("rule_callback").Inc()
|
||||
}
|
||||
|
||||
func (ws *WecomSender) Send(ctx MessageContext) {
|
||||
@@ -53,7 +55,7 @@ func (ws *WecomSender) Send(ctx MessageContext) {
|
||||
Content: message,
|
||||
},
|
||||
}
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Wecom, ctx.Stats, ctx.Events)
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Wecom, ctx.Stats, ctx.Events[0])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert"
|
||||
"github.com/ccfos/nightingale/v6/alert/astats"
|
||||
"github.com/ccfos/nightingale/v6/alert/dispatch"
|
||||
"github.com/ccfos/nightingale/v6/alert/process"
|
||||
alertrt "github.com/ccfos/nightingale/v6/alert/router"
|
||||
"github.com/ccfos/nightingale/v6/center/cconf"
|
||||
@@ -68,7 +67,7 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
}
|
||||
ctx := ctx.NewContext(context.Background(), db, true)
|
||||
migrate.Migrate(db)
|
||||
isRootInit := models.InitRoot(ctx)
|
||||
models.InitRoot(ctx)
|
||||
|
||||
config.HTTP.JWTAuth.SigningKey = models.InitJWTSigningKey(ctx)
|
||||
|
||||
@@ -104,9 +103,6 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
|
||||
sso := sso.Init(config.Center, ctx, configCache)
|
||||
promClients := prom.NewPromClient(ctx)
|
||||
|
||||
dispatch.InitRegisterQueryFunc(promClients)
|
||||
|
||||
tdengineClients := tdengine.NewTdengineClient(ctx, config.Alert.Heartbeat)
|
||||
|
||||
externalProcessors := process.NewExternalProcessors()
|
||||
@@ -125,7 +121,7 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
pushgwRouter := pushgwrt.New(config.HTTP, config.Pushgw, config.Alert, targetCache, busiGroupCache, idents, metas, writers, ctx)
|
||||
|
||||
go func() {
|
||||
if config.Center.MigrateBusiGroupLabel || models.CanMigrateBg(ctx) {
|
||||
if models.CanMigrateBg(ctx) {
|
||||
models.MigrateBg(ctx, pushgwRouter.Pushgw.BusiGroupLabelKey)
|
||||
}
|
||||
}()
|
||||
@@ -144,11 +140,6 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
|
||||
httpClean := httpx.Init(config.HTTP, r)
|
||||
|
||||
fmt.Printf("please view n9e at http://%v:%v\n", config.Alert.Heartbeat.IP, config.HTTP.Port)
|
||||
if isRootInit {
|
||||
fmt.Println("username/password: root/root.2020")
|
||||
}
|
||||
|
||||
return func() {
|
||||
logxClean()
|
||||
httpClean()
|
||||
|
||||
@@ -113,12 +113,6 @@ func Init(ctx *ctx.Context, builtinIntegrationsDir string) {
|
||||
logger.Warning("delete builtin metrics fail ", err)
|
||||
}
|
||||
|
||||
// 删除 uuid%1000 不为 0 uuid > 1000000000000000000 且 type 为 dashboard 的记录
|
||||
err = models.DB(ctx).Exec("delete from builtin_payloads where uuid%1000 != 0 and uuid > 1000000000000000000 and type = 'dashboard' and updated_by = 'system'").Error
|
||||
if err != nil {
|
||||
logger.Warning("delete builtin payloads fail ", err)
|
||||
}
|
||||
|
||||
// alerts
|
||||
files, err = file.FilesUnder(componentDir + "/alerts")
|
||||
if err == nil && len(files) > 0 {
|
||||
@@ -224,8 +218,7 @@ func Init(ctx *ctx.Context, builtinIntegrationsDir string) {
|
||||
}
|
||||
|
||||
if dashboard.UUID == 0 {
|
||||
time.Sleep(time.Microsecond)
|
||||
dashboard.UUID = time.Now().UnixMicro()
|
||||
dashboard.UUID = time.Now().UnixNano()
|
||||
// 补全文件中的 uuid
|
||||
bs, err = json.MarshalIndent(dashboard, "", " ")
|
||||
if err != nil {
|
||||
|
||||
@@ -184,7 +184,6 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.POST("/query-range-batch", rt.promBatchQueryRange)
|
||||
pages.POST("/query-instant-batch", rt.promBatchQueryInstant)
|
||||
pages.GET("/datasource/brief", rt.datasourceBriefs)
|
||||
pages.POST("/datasource/query", rt.datasourceQuery)
|
||||
|
||||
pages.POST("/ds-query", rt.QueryData)
|
||||
pages.POST("/logs-query", rt.QueryLog)
|
||||
@@ -198,7 +197,6 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.POST("/query-range-batch", rt.auth(), rt.promBatchQueryRange)
|
||||
pages.POST("/query-instant-batch", rt.auth(), rt.promBatchQueryInstant)
|
||||
pages.GET("/datasource/brief", rt.auth(), rt.user(), rt.datasourceBriefs)
|
||||
pages.POST("/datasource/query", rt.auth(), rt.user(), rt.datasourceQuery)
|
||||
|
||||
pages.POST("/ds-query", rt.auth(), rt.QueryData)
|
||||
pages.POST("/logs-query", rt.auth(), rt.QueryLog)
|
||||
@@ -280,7 +278,6 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.DELETE("/busi-group/:id/members", rt.auth(), rt.user(), rt.perm("/busi-groups/put"), rt.bgrw(), rt.busiGroupMemberDel)
|
||||
pages.DELETE("/busi-group/:id", rt.auth(), rt.user(), rt.perm("/busi-groups/del"), rt.bgrw(), rt.busiGroupDel)
|
||||
pages.GET("/busi-group/:id/perm/:perm", rt.auth(), rt.user(), rt.checkBusiGroupPerm)
|
||||
pages.GET("/busi-groups/tags", rt.auth(), rt.user(), rt.busiGroupsGetTags)
|
||||
|
||||
pages.GET("/targets", rt.auth(), rt.user(), rt.targetGets)
|
||||
pages.GET("/target/extra-meta", rt.auth(), rt.user(), rt.targetExtendInfoByIdent)
|
||||
|
||||
@@ -77,11 +77,6 @@ func (rt *Router) alertRuleGetsByGids(c *gin.Context) {
|
||||
for i := 0; i < len(ars); i++ {
|
||||
ars[i].FillNotifyGroups(rt.Ctx, cache)
|
||||
ars[i].FillSeverities()
|
||||
|
||||
if len(ars[i].DatasourceQueries) != 0 {
|
||||
ars[i].DatasourceIdsJson = rt.DatasourceCache.GetIDsByDsCateAndQueries(ars[i].Cate, ars[i].DatasourceQueries)
|
||||
}
|
||||
|
||||
rids = append(rids, ars[i].Id)
|
||||
names = append(names, ars[i].UpdateBy)
|
||||
}
|
||||
@@ -128,10 +123,6 @@ func (rt *Router) alertRulesGetByService(c *gin.Context) {
|
||||
cache := make(map[int64]*models.UserGroup)
|
||||
for i := 0; i < len(ars); i++ {
|
||||
ars[i].FillNotifyGroups(rt.Ctx, cache)
|
||||
|
||||
if len(ars[i].DatasourceQueries) != 0 {
|
||||
ars[i].DatasourceIdsJson = rt.DatasourceCache.GetIDsByDsCateAndQueries(ars[i].Cate, ars[i].DatasourceQueries)
|
||||
}
|
||||
}
|
||||
}
|
||||
ginx.NewRender(c).Data(ars, err)
|
||||
@@ -166,14 +157,6 @@ func (rt *Router) alertRuleAddByImport(c *gin.Context) {
|
||||
ginx.Bomb(http.StatusBadRequest, "input json is empty")
|
||||
}
|
||||
|
||||
for i := range lst {
|
||||
if len(lst[i].DatasourceQueries) == 0 {
|
||||
lst[i].DatasourceQueries = []models.DatasourceQuery{
|
||||
models.DataSourceQueryAll,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bgid := ginx.UrlParamInt64(c, "id")
|
||||
reterr := rt.alertRuleAdd(lst, username, bgid, c.GetHeader("X-Language"))
|
||||
|
||||
@@ -181,9 +164,9 @@ func (rt *Router) alertRuleAddByImport(c *gin.Context) {
|
||||
}
|
||||
|
||||
type promRuleForm struct {
|
||||
Payload string `json:"payload" binding:"required"`
|
||||
DatasourceQueries []models.DatasourceQuery `json:"datasource_queries" binding:"required"`
|
||||
Disabled int `json:"disabled" binding:"gte=0,lte=1"`
|
||||
Payload string `json:"payload" binding:"required"`
|
||||
DatasourceIds []int64 `json:"datasource_ids" binding:"required"`
|
||||
Disabled int `json:"disabled" binding:"gte=0,lte=1"`
|
||||
}
|
||||
|
||||
func (rt *Router) alertRuleAddByImportPromRule(c *gin.Context) {
|
||||
@@ -202,7 +185,7 @@ func (rt *Router) alertRuleAddByImportPromRule(c *gin.Context) {
|
||||
ginx.Bomb(http.StatusBadRequest, "input yaml is empty")
|
||||
}
|
||||
|
||||
lst := models.DealPromGroup(pr.Groups, f.DatasourceQueries, f.Disabled)
|
||||
lst := models.DealPromGroup(pr.Groups, f.DatasourceIds, f.Disabled)
|
||||
username := c.MustGet("username").(string)
|
||||
bgid := ginx.UrlParamInt64(c, "id")
|
||||
ginx.NewRender(c).Data(rt.alertRuleAdd(lst, username, bgid, c.GetHeader("X-Language")), nil)
|
||||
@@ -415,16 +398,6 @@ func (rt *Router) alertRulePutFields(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if f.Action == "datasource_change" {
|
||||
// 修改数据源
|
||||
if datasourceQueries, has := f.Fields["datasource_queries"]; has {
|
||||
bytes, err := json.Marshal(datasourceQueries)
|
||||
ginx.Dangerous(err)
|
||||
ginx.Dangerous(ar.UpdateFieldsMap(rt.Ctx, map[string]interface{}{"datasource_queries": bytes}))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range f.Fields {
|
||||
ginx.Dangerous(ar.UpdateColumn(rt.Ctx, k, v))
|
||||
}
|
||||
@@ -444,10 +417,6 @@ func (rt *Router) alertRuleGet(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(ar.DatasourceQueries) != 0 {
|
||||
ar.DatasourceIdsJson = rt.DatasourceCache.GetIDsByDsCateAndQueries(ar.Cate, ar.DatasourceQueries)
|
||||
}
|
||||
|
||||
err = ar.FillNotifyGroups(rt.Ctx, make(map[int64]*models.UserGroup))
|
||||
ginx.Dangerous(err)
|
||||
|
||||
@@ -654,7 +623,7 @@ func (rt *Router) cloneToMachine(c *gin.Context) {
|
||||
newRule.CreateAt = now
|
||||
newRule.RuleConfig = alertRules[i].RuleConfig
|
||||
|
||||
exist, err := models.AlertRuleExists(rt.Ctx, 0, newRule.GroupId, newRule.Name)
|
||||
exist, err := models.AlertRuleExists(rt.Ctx, 0, newRule.GroupId, newRule.DatasourceIdsJson, newRule.Name)
|
||||
if err != nil {
|
||||
errMsg[f.IdentList[j]] = err.Error()
|
||||
continue
|
||||
|
||||
@@ -43,7 +43,7 @@ func (rt *Router) builtinPayloadsAdd(c *gin.Context) {
|
||||
|
||||
for _, rule := range alertRules {
|
||||
if rule.UUID == 0 {
|
||||
rule.UUID = time.Now().UnixMicro()
|
||||
rule.UUID = time.Now().UnixNano()
|
||||
}
|
||||
|
||||
contentBytes, err := json.Marshal(rule)
|
||||
@@ -78,13 +78,7 @@ func (rt *Router) builtinPayloadsAdd(c *gin.Context) {
|
||||
}
|
||||
|
||||
if alertRule.UUID == 0 {
|
||||
alertRule.UUID = time.Now().UnixMicro()
|
||||
}
|
||||
|
||||
contentBytes, err := json.Marshal(alertRule)
|
||||
if err != nil {
|
||||
reterr[alertRule.Name] = err.Error()
|
||||
continue
|
||||
alertRule.UUID = time.Now().UnixNano()
|
||||
}
|
||||
|
||||
bp := models.BuiltinPayload{
|
||||
@@ -94,7 +88,7 @@ func (rt *Router) builtinPayloadsAdd(c *gin.Context) {
|
||||
Name: alertRule.Name,
|
||||
Tags: alertRule.AppendTags,
|
||||
UUID: alertRule.UUID,
|
||||
Content: string(contentBytes),
|
||||
Content: lst[i].Content,
|
||||
CreatedBy: username,
|
||||
UpdatedBy: username,
|
||||
}
|
||||
@@ -112,7 +106,7 @@ func (rt *Router) builtinPayloadsAdd(c *gin.Context) {
|
||||
|
||||
for _, dashboard := range dashboards {
|
||||
if dashboard.UUID == 0 {
|
||||
dashboard.UUID = time.Now().UnixMicro()
|
||||
dashboard.UUID = time.Now().UnixNano()
|
||||
}
|
||||
|
||||
contentBytes, err := json.Marshal(dashboard)
|
||||
@@ -147,13 +141,7 @@ func (rt *Router) builtinPayloadsAdd(c *gin.Context) {
|
||||
}
|
||||
|
||||
if dashboard.UUID == 0 {
|
||||
dashboard.UUID = time.Now().UnixMicro()
|
||||
}
|
||||
|
||||
contentBytes, err := json.Marshal(dashboard)
|
||||
if err != nil {
|
||||
reterr[dashboard.Name] = err.Error()
|
||||
continue
|
||||
dashboard.UUID = time.Now().UnixNano()
|
||||
}
|
||||
|
||||
bp := models.BuiltinPayload{
|
||||
@@ -163,7 +151,7 @@ func (rt *Router) builtinPayloadsAdd(c *gin.Context) {
|
||||
Name: dashboard.Name,
|
||||
Tags: dashboard.Tags,
|
||||
UUID: dashboard.UUID,
|
||||
Content: string(contentBytes),
|
||||
Content: lst[i].Content,
|
||||
CreatedBy: username,
|
||||
UpdatedBy: username,
|
||||
}
|
||||
|
||||
@@ -140,12 +140,3 @@ func (rt *Router) busiGroupGet(c *gin.Context) {
|
||||
ginx.Dangerous(bg.FillUserGroups(rt.Ctx))
|
||||
ginx.NewRender(c).Data(bg, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) busiGroupsGetTags(c *gin.Context) {
|
||||
bgids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
|
||||
targetIdents, err := models.TargetIndentsGetByBgids(rt.Ctx, bgids)
|
||||
ginx.Dangerous(err)
|
||||
tags, err := models.TargetGetTags(rt.Ctx, targetIdents, true, "busigroup")
|
||||
ginx.Dangerous(err)
|
||||
ginx.NewRender(c).Data(tags, nil)
|
||||
}
|
||||
|
||||
@@ -122,14 +122,12 @@ func (rt *Router) datasourceUpsert(c *gin.Context) {
|
||||
}
|
||||
|
||||
func DatasourceCheck(ds models.Datasource) error {
|
||||
if ds.PluginType != models.ELASTICSEARCH {
|
||||
if ds.HTTPJson.Url == "" {
|
||||
return fmt.Errorf("url is empty")
|
||||
}
|
||||
if ds.HTTPJson.Url == "" {
|
||||
return fmt.Errorf("url is empty")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(ds.HTTPJson.Url, "http") {
|
||||
return fmt.Errorf("url must start with http or https")
|
||||
}
|
||||
if !strings.HasPrefix(ds.HTTPJson.Url, "http") {
|
||||
return fmt.Errorf("url must start with http or https")
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
@@ -140,11 +138,11 @@ func DatasourceCheck(ds models.Datasource) error {
|
||||
},
|
||||
}
|
||||
|
||||
var fullURL string
|
||||
req, err := ds.HTTPJson.NewReq(&fullURL)
|
||||
fullURL := ds.HTTPJson.Url
|
||||
req, err := http.NewRequest("GET", fullURL, nil)
|
||||
if err != nil {
|
||||
logger.Errorf("Error creating request: %v", err)
|
||||
return fmt.Errorf("request urls:%v failed", ds.HTTPJson.GetUrls())
|
||||
return fmt.Errorf("request url:%s failed", fullURL)
|
||||
}
|
||||
|
||||
if ds.PluginType == models.PROMETHEUS {
|
||||
@@ -251,37 +249,3 @@ func (rt *Router) getDatasourceIds(c *gin.Context) {
|
||||
|
||||
ginx.NewRender(c).Data(datasourceIds, err)
|
||||
}
|
||||
|
||||
type datasourceQueryForm struct {
|
||||
Cate string `json:"datasource_cate"`
|
||||
DatasourceQueries []models.DatasourceQuery `json:"datasource_queries"`
|
||||
}
|
||||
|
||||
type datasourceQueryResp struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (rt *Router) datasourceQuery(c *gin.Context) {
|
||||
var dsf datasourceQueryForm
|
||||
ginx.BindJSON(c, &dsf)
|
||||
datasources, err := models.GetDatasourcesGetsByTypes(rt.Ctx, []string{dsf.Cate})
|
||||
ginx.Dangerous(err)
|
||||
|
||||
nameToID := make(map[string]int64)
|
||||
IDToName := make(map[int64]string)
|
||||
for _, ds := range datasources {
|
||||
nameToID[ds.Name] = ds.Id
|
||||
IDToName[ds.Id] = ds.Name
|
||||
}
|
||||
|
||||
ids := models.GetDatasourceIDsByDatasourceQueries(dsf.DatasourceQueries, IDToName, nameToID)
|
||||
var req []datasourceQueryResp
|
||||
for _, id := range ids {
|
||||
req = append(req, datasourceQueryResp{
|
||||
ID: id,
|
||||
Name: IDToName[id],
|
||||
})
|
||||
}
|
||||
ginx.NewRender(c).Data(req, err)
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ func HandleHeartbeat(c *gin.Context, ctx *ctx.Context, engineName string, metaSe
|
||||
groupIds = append(groupIds, groupId)
|
||||
}
|
||||
|
||||
err := models.TargetOverrideBgids(ctx, []string{target.Ident}, groupIds, nil)
|
||||
err := models.TargetOverrideBgids(ctx, []string{target.Ident}, groupIds)
|
||||
if err != nil {
|
||||
logger.Warningf("update target:%s group ids failed, err: %v", target.Ident, err)
|
||||
}
|
||||
@@ -113,7 +113,7 @@ func HandleHeartbeat(c *gin.Context, ctx *ctx.Context, engineName string, metaSe
|
||||
}
|
||||
|
||||
if !target.MatchGroupId(groupId) {
|
||||
err := models.TargetBindBgids(ctx, []string{target.Ident}, []int64{groupId}, nil)
|
||||
err := models.TargetBindBgids(ctx, []string{target.Ident}, []int64{groupId})
|
||||
if err != nil {
|
||||
logger.Warningf("update target:%s group ids failed, err: %v", target.Ident, err)
|
||||
}
|
||||
|
||||
@@ -35,18 +35,11 @@ type Record struct {
|
||||
|
||||
// notificationRecordAdd
|
||||
func (rt *Router) notificationRecordAdd(c *gin.Context) {
|
||||
var req []*models.NotificaitonRecord
|
||||
var req models.NotificaitonRecord
|
||||
ginx.BindJSON(c, &req)
|
||||
err := models.DB(rt.Ctx).CreateInBatches(req, 100).Error
|
||||
var ids []int64
|
||||
if err == nil {
|
||||
ids = make([]int64, len(req))
|
||||
for i, noti := range req {
|
||||
ids[i] = noti.Id
|
||||
}
|
||||
}
|
||||
err := req.Add(rt.Ctx)
|
||||
|
||||
ginx.NewRender(c).Data(ids, err)
|
||||
ginx.NewRender(c).Data(req.Id, err)
|
||||
}
|
||||
|
||||
func (rt *Router) notificationRecordList(c *gin.Context) {
|
||||
|
||||
@@ -161,11 +161,7 @@ func (rt *Router) notifyTplPreview(c *gin.Context) {
|
||||
func (rt *Router) notifyTplAdd(c *gin.Context) {
|
||||
var f models.NotifyTpl
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
user := c.MustGet("user").(*models.User)
|
||||
f.CreateBy = user.Username
|
||||
|
||||
f.Channel = strings.TrimSpace(f.Channel)
|
||||
f.Channel = strings.TrimSpace(f.Channel)
|
||||
ginx.Dangerous(templateValidate(f))
|
||||
|
||||
count, err := models.Count(models.DB(rt.Ctx).Model(&models.NotifyTpl{}).Where("channel = ? or name = ?", f.Channel, f.Name))
|
||||
@@ -173,8 +169,6 @@ func (rt *Router) notifyTplAdd(c *gin.Context) {
|
||||
if count != 0 {
|
||||
ginx.Bomb(200, "Refuse to create duplicate channel(unique)")
|
||||
}
|
||||
|
||||
f.CreateAt = time.Now().Unix()
|
||||
ginx.NewRender(c).Message(f.Create(rt.Ctx))
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -111,9 +112,9 @@ func (rt *Router) dsProxy(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
target, err := ds.HTTPJson.ParseUrl()
|
||||
target, err := url.Parse(ds.HTTPJson.Url)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "invalid urls: %s", ds.HTTPJson.GetUrls())
|
||||
c.String(http.StatusInternalServerError, "invalid url: %s", ds.HTTPJson.Url)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ package router
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
@@ -72,14 +74,6 @@ func (rt *Router) recordingRuleAddByFE(c *gin.Context) {
|
||||
ginx.Bomb(http.StatusBadRequest, "input json is empty")
|
||||
}
|
||||
|
||||
for i := range lst {
|
||||
if len(lst[i].DatasourceQueries) == 0 {
|
||||
lst[i].DatasourceQueries = []models.DatasourceQuery{
|
||||
models.DataSourceQueryAll,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bgid := ginx.UrlParamInt64(c, "id")
|
||||
reterr := make(map[string]string)
|
||||
for i := 0; i < count; i++ {
|
||||
@@ -143,10 +137,23 @@ func (rt *Router) recordingRulePutFields(c *gin.Context) {
|
||||
f.Fields["update_by"] = c.MustGet("username").(string)
|
||||
f.Fields["update_at"] = time.Now().Unix()
|
||||
|
||||
if datasourceQueries, ok := f.Fields["datasource_queries"]; ok {
|
||||
bytes, err := json.Marshal(datasourceQueries)
|
||||
ginx.Dangerous(err)
|
||||
f.Fields["datasource_queries"] = string(bytes)
|
||||
if _, ok := f.Fields["datasource_ids"]; ok {
|
||||
// datasource_ids = "1 2 3"
|
||||
idsStr := strings.Fields(f.Fields["datasource_ids"].(string))
|
||||
ids := make([]int64, 0)
|
||||
for _, idStr := range idsStr {
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil {
|
||||
ginx.Bomb(http.StatusBadRequest, "datasource_ids error")
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
bs, err := json.Marshal(ids)
|
||||
if err != nil {
|
||||
ginx.Bomb(http.StatusBadRequest, "datasource_ids error")
|
||||
}
|
||||
f.Fields["datasource_ids"] = string(bs)
|
||||
}
|
||||
|
||||
for i := 0; i < len(f.Ids); i++ {
|
||||
|
||||
@@ -169,7 +169,7 @@ func (rt *Router) targetGetTags(c *gin.Context) {
|
||||
idents := ginx.QueryStr(c, "idents", "")
|
||||
idents = strings.ReplaceAll(idents, ",", " ")
|
||||
ignoreHostTag := ginx.QueryBool(c, "ignore_host_tag", false)
|
||||
lst, err := models.TargetGetTags(rt.Ctx, strings.Fields(idents), ignoreHostTag, "")
|
||||
lst, err := models.TargetGetTags(rt.Ctx, strings.Fields(idents), ignoreHostTag)
|
||||
ginx.NewRender(c).Data(lst, err)
|
||||
}
|
||||
|
||||
@@ -397,7 +397,6 @@ type targetBgidsForm struct {
|
||||
Idents []string `json:"idents" binding:"required_without=HostIps"`
|
||||
HostIps []string `json:"host_ips" binding:"required_without=Idents"`
|
||||
Bgids []int64 `json:"bgids"`
|
||||
Tags []string `json:"tags"`
|
||||
Action string `json:"action"` // add del reset
|
||||
}
|
||||
|
||||
@@ -453,11 +452,11 @@ func (rt *Router) targetBindBgids(c *gin.Context) {
|
||||
|
||||
switch f.Action {
|
||||
case "add":
|
||||
ginx.NewRender(c).Data(failedResults, models.TargetBindBgids(rt.Ctx, f.Idents, f.Bgids, f.Tags))
|
||||
ginx.NewRender(c).Data(failedResults, models.TargetBindBgids(rt.Ctx, f.Idents, f.Bgids))
|
||||
case "del":
|
||||
ginx.NewRender(c).Data(failedResults, models.TargetUnbindBgids(rt.Ctx, f.Idents, f.Bgids))
|
||||
case "reset":
|
||||
ginx.NewRender(c).Data(failedResults, models.TargetOverrideBgids(rt.Ctx, f.Idents, f.Bgids, f.Tags))
|
||||
ginx.NewRender(c).Data(failedResults, models.TargetOverrideBgids(rt.Ctx, f.Idents, f.Bgids))
|
||||
default:
|
||||
ginx.Bomb(http.StatusBadRequest, "invalid action")
|
||||
}
|
||||
@@ -479,7 +478,7 @@ func (rt *Router) targetUpdateBgidByService(c *gin.Context) {
|
||||
ginx.Bomb(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(failedResults, models.TargetOverrideBgids(rt.Ctx, f.Idents, []int64{f.Bgid}, nil))
|
||||
ginx.NewRender(c).Data(failedResults, models.TargetOverrideBgids(rt.Ctx, f.Idents, []int64{f.Bgid}))
|
||||
}
|
||||
|
||||
type identsForm struct {
|
||||
|
||||
@@ -82,7 +82,7 @@ func (rt *Router) QueryData(c *gin.Context) {
|
||||
var err error
|
||||
tdClient := rt.TdendgineClients.GetCli(f.DatasourceId)
|
||||
for _, q := range f.Querys {
|
||||
datas, err := tdClient.Query(q)
|
||||
datas, err := tdClient.Query(q, 0)
|
||||
ginx.Dangerous(err)
|
||||
resp = append(resp, datas...)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ type ClusterOptions struct {
|
||||
MaxIdleConnsPerHost int
|
||||
}
|
||||
|
||||
func Parse(fpath string, configPtr *Config) error {
|
||||
func Parse(fpath string, configPtr interface{}) error {
|
||||
var (
|
||||
tBuf []byte
|
||||
)
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert"
|
||||
"github.com/ccfos/nightingale/v6/alert/astats"
|
||||
"github.com/ccfos/nightingale/v6/alert/dispatch"
|
||||
"github.com/ccfos/nightingale/v6/alert/process"
|
||||
alertrt "github.com/ccfos/nightingale/v6/alert/router"
|
||||
"github.com/ccfos/nightingale/v6/center/metas"
|
||||
@@ -74,9 +73,6 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
taskTplsCache := memsto.NewTaskTplCache(ctx)
|
||||
|
||||
promClients := prom.NewPromClient(ctx)
|
||||
|
||||
dispatch.InitRegisterQueryFunc(promClients)
|
||||
|
||||
tdengineClients := tdengine.NewTdengineClient(ctx, config.Alert.Heartbeat)
|
||||
externalProcessors := process.NewExternalProcessors()
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
version: "3.7"
|
||||
|
||||
networks:
|
||||
nightingale:
|
||||
driver: bridge
|
||||
|
||||
@@ -19,8 +19,8 @@ precision = "ms"
|
||||
# global collect interval
|
||||
interval = 15
|
||||
|
||||
# [global.labels]
|
||||
# source="categraf"
|
||||
[global.labels]
|
||||
source="categraf"
|
||||
# region = "shanghai"
|
||||
# env = "localhost"
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
[[instances]]
|
||||
address = "mysql:3306"
|
||||
username = "root"
|
||||
password = "1234"
|
||||
|
||||
# # set tls=custom to enable tls
|
||||
# parameters = "tls=false"
|
||||
|
||||
# extra_status_metrics = true
|
||||
# extra_innodb_metrics = false
|
||||
# gather_processlist_processes_by_state = false
|
||||
# gather_processlist_processes_by_user = false
|
||||
# gather_schema_size = true
|
||||
# gather_table_size = false
|
||||
# gather_system_table_size = false
|
||||
# gather_slave_status = true
|
||||
|
||||
# # timeout
|
||||
# timeout_seconds = 3
|
||||
|
||||
# # interval = global.interval * interval_times
|
||||
# interval_times = 1
|
||||
|
||||
# important! use global unique string to specify instance
|
||||
labels = { instance="docker-compose-mysql" }
|
||||
|
||||
## Optional TLS Config
|
||||
# use_tls = false
|
||||
# tls_min_version = "1.2"
|
||||
# tls_ca = "/etc/categraf/ca.pem"
|
||||
# tls_cert = "/etc/categraf/cert.pem"
|
||||
# tls_key = "/etc/categraf/key.pem"
|
||||
## Use TLS but skip chain & host verification
|
||||
# insecure_skip_verify = true
|
||||
|
||||
#[[instances.queries]]
|
||||
# mesurement = "lock_wait"
|
||||
# metric_fields = [ "total" ]
|
||||
# timeout = "3s"
|
||||
# request = '''
|
||||
#SELECT count(*) as total FROM information_schema.innodb_trx WHERE trx_state='LOCK WAIT'
|
||||
#'''
|
||||
@@ -1,37 +0,0 @@
|
||||
[[instances]]
|
||||
address = "redis:6379"
|
||||
username = ""
|
||||
password = ""
|
||||
# pool_size = 2
|
||||
|
||||
## 是否开启slowlog 收集
|
||||
# gather_slowlog = true
|
||||
## 最多收集少条slowlog
|
||||
# slowlog_max_len = 100
|
||||
## 收集距离现在多少秒以内的slowlog
|
||||
## 注意插件的采集周期,该参数不要小于采集周期,否则会有slowlog查不到
|
||||
# slowlog_time_window=30
|
||||
|
||||
# 指标
|
||||
# redis_slow_log{ident=dev-01 client_addr=127.0.0.1:56364 client_name= cmd="info ALL" log_id=983} 74 (单位微秒)
|
||||
|
||||
# # Optional. Specify redis commands to retrieve values
|
||||
# commands = [
|
||||
# {command = ["get", "sample-key1"], metric = "custom_metric_name1"},
|
||||
# {command = ["get", "sample-key2"], metric = "custom_metric_name2"}
|
||||
# ]
|
||||
|
||||
# # interval = global.interval * interval_times
|
||||
# interval_times = 1
|
||||
|
||||
# important! use global unique string to specify instance
|
||||
labels = { instance="docker-compose-redis" }
|
||||
|
||||
## Optional TLS Config
|
||||
# use_tls = false
|
||||
# tls_min_version = "1.2"
|
||||
# tls_ca = "/etc/categraf/ca.pem"
|
||||
# tls_cert = "/etc/categraf/cert.pem"
|
||||
# tls_key = "/etc/categraf/key.pem"
|
||||
## Use TLS but skip chain & host verification
|
||||
# insecure_skip_verify = true
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
network_mode: host
|
||||
|
||||
prometheus:
|
||||
image: prom/prometheus:v2.55.1
|
||||
image: prom/prometheus
|
||||
container_name: prometheus
|
||||
hostname: prometheus
|
||||
restart: always
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,3 @@
|
||||
CREATE USER IF NOT EXISTS 'root'@'127.0.0.1' IDENTIFIED BY '1234';
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'root'@'127.0.0.1' WITH GRANT OPTION;
|
||||
|
||||
CREATE USER IF NOT EXISTS 'root'@'localhost' IDENTIFIED BY '1234';
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION;
|
||||
|
||||
CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED BY '1234';
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
|
||||
|
||||
FLUSH PRIVILEGES;
|
||||
GRANT ALL ON *.* TO 'root'@'127.0.0.1' IDENTIFIED BY '1234';
|
||||
GRANT ALL ON *.* TO 'root'@'localhost' IDENTIFIED BY '1234';
|
||||
GRANT ALL ON *.* TO 'root'@'%' IDENTIFIED BY '1234';
|
||||
@@ -116,8 +116,4 @@ CREATE TABLE `target_busi_group` (
|
||||
`update_at` bigint NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `idx_target_group` (`target_ident`,`group_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
/* v7.7.2 2024-12-02 */
|
||||
ALTER TABLE alert_subscribe MODIFY COLUMN rule_ids varchar(1024);
|
||||
ALTER TABLE alert_subscribe MODIFY COLUMN busi_groups varchar(4096);
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
9
go.mod
9
go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/ccfos/nightingale/v6
|
||||
|
||||
go 1.22
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
@@ -32,7 +32,6 @@ require (
|
||||
github.com/rakyll/statik v0.1.7
|
||||
github.com/redis/go-redis/v9 v9.0.2
|
||||
github.com/spaolacci/murmur3 v1.1.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/tidwall/gjson v1.14.0
|
||||
github.com/toolkits/pkg v1.3.8
|
||||
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
|
||||
@@ -45,8 +44,6 @@ require (
|
||||
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde
|
||||
)
|
||||
|
||||
require github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
@@ -90,7 +87,7 @@ require (
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/procfs v0.11.0 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/tidwall/match v1.1.1
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
@@ -100,7 +97,7 @@ require (
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/image v0.18.0 // indirect
|
||||
golang.org/x/net v0.23.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
|
||||
29
go.sum
29
go.sum
@@ -1,28 +1,20 @@
|
||||
github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 h1:8q4SaHjFsClSvuVne0ID/5Ka8u3fcIHyqkLjcFpNRHQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||
github.com/aws/aws-sdk-go v1.44.302 h1:ST3ko6GrJKn3Xi+nAvxjG3uk/V1pW8KC52WLeIxqqNk=
|
||||
github.com/aws/aws-sdk-go v1.44.302/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bsm/ginkgo/v2 v2.5.0 h1:aOAnND1T40wEdAtkGSkvSICWeQ8L3UASX7YVCqQx+eQ=
|
||||
github.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
||||
github.com/bsm/gomega v1.20.0 h1:JhAwLmtRzXFTx2AkALSLa8ijZafntmhSoU63Ok18Uq8=
|
||||
github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
@@ -102,7 +94,6 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -114,7 +105,6 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
@@ -178,17 +168,14 @@ github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
@@ -200,14 +187,12 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
@@ -238,25 +223,20 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
|
||||
github.com/mojocn/base64Captcha v1.3.6 h1:gZEKu1nsKpttuIAQgWHO+4Mhhls8cAKyiV2Ew03H+Tw=
|
||||
github.com/mojocn/base64Captcha v1.3.6/go.mod h1:i5CtHvm+oMbj1UzEPXaA8IH/xHFZ3DGY3Wh3dBpZ28E=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc=
|
||||
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
|
||||
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
||||
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
|
||||
@@ -264,7 +244,6 @@ github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJ
|
||||
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||
github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4=
|
||||
github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI=
|
||||
github.com/prometheus/procfs v0.11.0 h1:5EAgkfkMl659uZPbe9AS2N68a7Cc1TJbPEuGzFuRbyk=
|
||||
github.com/prometheus/procfs v0.11.0/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||
github.com/prometheus/prometheus v0.47.1 h1:bd2LiZyxzHn9Oo2Ei4eK2D86vz/L/OiqR1qYo0XmMBo=
|
||||
@@ -280,7 +259,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
@@ -310,7 +288,6 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w=
|
||||
github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
@@ -339,7 +316,6 @@ go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhW
|
||||
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
||||
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
@@ -402,7 +378,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -425,8 +400,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## AppDynamics
|
||||
## Appdynamics
|
||||
|
||||
AppDynamics 采集插件, 采集 AppDynamics 数据
|
||||
Appdynamics 采集插件, 采集 Appdynamics 数据
|
||||
|
||||
## Configuration
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
[[instances]]
|
||||
urls = [
|
||||
# "http://localhost:8053/xml/v3",
|
||||
]
|
||||
gather_memory_contexts = true
|
||||
gather_views = true
|
||||
timeout = "5s"
|
||||
# labels={app="bind"}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.9 KiB |
@@ -1,13 +0,0 @@
|
||||
forked from [telegraf/snmp](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/bind)
|
||||
|
||||
配置示例
|
||||
```
|
||||
[[instances]]
|
||||
urls = [
|
||||
#"http://localhost:8053/xml/v3",
|
||||
]
|
||||
|
||||
timeout = "5s"
|
||||
gather_memory_contexts = true
|
||||
gather_views = true
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
## canal
|
||||
|
||||
canal 默认提供了 prometheus 格式指标的接口 [Prometheus-QuickStart](https://github.com/alibaba/canal/wiki/Prometheus-QuickStart) ,所以可以直接通过[ prometheus 插件](https://flashcat.cloud/docs/content/flashcat-monitor/categraf/plugin/prometheus)采集。
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,22 +0,0 @@
|
||||
# doris_fe
|
||||
[[instances]]
|
||||
# 配置 fe metrics 服务地址
|
||||
urls = [
|
||||
"http://127.0.0.1:8030/metrics"
|
||||
]
|
||||
|
||||
url_label_key = "instance"
|
||||
url_label_value = "{{.Host}}"
|
||||
# 指定 fe 服务 group 和 job 标签,这里是仪表盘变量调用,可根据实际需求修改。
|
||||
labels = { group = "fe",job = "doris_cluster01"}
|
||||
|
||||
# doris_be
|
||||
[[instances]]
|
||||
# 配置 be metrics 服务地址
|
||||
urls = [
|
||||
"http://127.0.0.1:8040/metrics"
|
||||
]
|
||||
url_label_key = "instance"
|
||||
url_label_value = "{{.Host}}"
|
||||
# 指定 be 服务 group 和 job 标签,这里是仪表盘变量调用,可根据实际需求修改。
|
||||
labels = { group = "be",job = "doris_cluster01"}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.6.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#00A5CA;}
|
||||
.st1{fill:#3ACA9B;}
|
||||
.st2{fill:#405AAD;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M17.4,4.6l-3.3-3.3c-0.4-0.4-0.9-0.8-1.5-1C12.1,0.1,11.5,0,11,0C9.9,0,8.8,0.4,8,1.2C7.6,1.6,7.3,2,7.1,2.6
|
||||
c-0.3,0.5-0.4,1-0.4,1.6S6.8,5.3,7,5.9c0.2,0.5,0.5,1,0.9,1.4l5.9,5.9c0.1,0.1,0.3,0.2,0.5,0.2s0.3-0.1,0.5-0.2l2.6-2.6
|
||||
C17.6,10.5,20.2,7.4,17.4,4.6z"/>
|
||||
<path class="st1" d="M22.8,9.8c-0.6-0.6-1.3-1.2-1.9-1.9l0,0c0,0.1,0,0.1,0,0.2c-0.2,1.4-0.9,2.7-2,3.7
|
||||
c-3.4,3.4-6.9,6.9-10.3,10.3l-0.5,0.5c-0.7,0.6-1.2,1.5-1.3,2.4c-0.1,0.7,0,1.3,0.2,2c0.2,0.6,0.5,1.2,1,1.7
|
||||
c0.4,0.4,0.9,0.8,1.4,1c0.5,0.2,1.1,0.3,1.7,0.3c1.3,0,2-0.2,3-1.1c3.9-3.8,7.8-7.7,10.8-10.6c1.4-1.4,1.7-3.7,0.7-5.2
|
||||
C24.8,11.8,23.8,10.8,22.8,9.8z"/>
|
||||
<path class="st2" d="M3.8,7.8v14.5c0,0.2,0,0.3,0.1,0.4C4,22.8,4.1,22.9,4.3,23c0.1,0,0.3,0,0.5,0c0.2,0,0.3-0.1,0.4-0.2l7.3-7.3
|
||||
c0.1-0.1,0.2-0.3,0.2-0.5s-0.1-0.4-0.2-0.5L5.2,7.2C5.1,7.1,5,7.1,4.9,7C4.8,7,4.7,7,4.6,7C4.4,7,4.2,7.1,4,7.2
|
||||
C3.9,7.4,3.8,7.6,3.8,7.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1,39 +0,0 @@
|
||||
# Doris
|
||||
|
||||
Doris 的进程都会暴露 `/metrics` 接口,通过这个接口暴露 Prometheus 协议的监控数据。
|
||||
|
||||
## 采集配置
|
||||
|
||||
categraf 的 `conf/input.prometheus/prometheus.toml`。因为 Doris 是暴露的 Prometheus 协议的监控数据,所以使用 categraf 的 prometheus 插件即可采集。
|
||||
|
||||
```toml
|
||||
# doris_fe
|
||||
[[instances]]
|
||||
urls = [
|
||||
"http://127.0.0.1:8030/metrics"
|
||||
]
|
||||
|
||||
url_label_key = "instance"
|
||||
url_label_value = "{{.Host}}"
|
||||
|
||||
labels = { group = "fe",job = "doris_cluster01"}
|
||||
|
||||
# doris_be
|
||||
[[instances]]
|
||||
urls = [
|
||||
"http://127.0.0.1:8040/metrics"
|
||||
]
|
||||
url_label_key = "instance"
|
||||
url_label_value = "{{.Host}}"
|
||||
labels = { group = "be",job = "doris_cluster01"}
|
||||
```
|
||||
|
||||
## 告警规则
|
||||
|
||||
夜莺内置了 Doris 的告警规则,克隆到自己的业务组下即可使用。
|
||||
|
||||
## 仪表盘
|
||||
|
||||
夜莺内置了 Doris 的仪表盘,克隆到自己的业务组下即可使用。
|
||||
|
||||
|
||||
@@ -459,5 +459,5 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"uuid": 1727587308068775000
|
||||
"uuid": 1727587308068775200
|
||||
}
|
||||
|
||||
@@ -1702,5 +1702,5 @@
|
||||
],
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"uuid": 1727335102129685000
|
||||
"uuid": 1727335102129685800
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
# # collect interval
|
||||
# interval = 15
|
||||
|
||||
[[instances]]
|
||||
# # append some labels for series
|
||||
# labels = { region="cloud", product="n9e" }
|
||||
|
||||
# # interval = global.interval * interval_times
|
||||
# interval_times = 1
|
||||
|
||||
## Server to monitor
|
||||
## The scheme determines the mode to use for connection with
|
||||
## ldap://... -- unencrypted (non-TLS) connection
|
||||
## ldaps://... -- TLS connection
|
||||
## starttls://... -- StartTLS connection
|
||||
## If no port is given, the default ports, 389 for ldap and starttls and
|
||||
## 636 for ldaps, are used.
|
||||
#server = "ldap://localhost"
|
||||
|
||||
## Server dialect, can be "openldap" or "389ds"
|
||||
# dialect = "openldap"
|
||||
|
||||
# DN and password to bind with
|
||||
## If bind_dn is empty an anonymous bind is performed.
|
||||
bind_dn = ""
|
||||
bind_password = ""
|
||||
|
||||
## Reverse the field names constructed from the monitoring DN
|
||||
# reverse_field_names = false
|
||||
|
||||
## Optional TLS Config
|
||||
# use_tls = false
|
||||
# tls_ca = "/etc/categraf/ca.pem"
|
||||
# tls_cert = "/etc/categraf/cert.pem"
|
||||
# tls_key = "/etc/categraf/key.pem"
|
||||
## Use TLS but skip chain & host verification
|
||||
# insecure_skip_verify = false
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.1 KiB |
@@ -1,113 +0,0 @@
|
||||
# LDAP Input Plugin
|
||||
|
||||
This plugin gathers metrics from LDAP servers' monitoring (`cn=Monitor`)
|
||||
backend. Currently this plugin supports [OpenLDAP](https://www.openldap.org/)
|
||||
and [389ds](https://www.port389.org/) servers.
|
||||
|
||||
To use this plugin you must enable the monitoring backend/plugin of your LDAP
|
||||
server. See
|
||||
[OpenLDAP](https://www.openldap.org/devel/admin/monitoringslapd.html) or 389ds
|
||||
documentation for details.
|
||||
|
||||
## Metrics
|
||||
|
||||
Depending on the server dialect, different metrics are produced. The metrics
|
||||
are usually named according to the selected dialect.
|
||||
|
||||
### Tags
|
||||
|
||||
- server -- Server name or IP
|
||||
- port -- Port used for connecting
|
||||
|
||||
## Example Output
|
||||
|
||||
Using the `openldap` dialect
|
||||
|
||||
```text
|
||||
openldap_modify_operations_completed agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_referrals_statistics agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_unbind_operations_initiated agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_delete_operations_completed agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_extended_operations_completed agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_pdu_statistics agent_hostname=zy-fat port=389 server=localhost 42
|
||||
openldap_starting_threads agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_active_threads agent_hostname=zy-fat port=389 server=localhost 1
|
||||
openldap_uptime_time agent_hostname=zy-fat port=389 server=localhost 102
|
||||
openldap_bytes_statistics agent_hostname=zy-fat port=389 server=localhost 3176
|
||||
openldap_compare_operations_completed agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_bind_operations_completed agent_hostname=zy-fat port=389 server=localhost 1
|
||||
openldap_total_connections agent_hostname=zy-fat port=389 server=localhost 1002
|
||||
openldap_search_operations_completed agent_hostname=zy-fat port=389 server=localhost 1
|
||||
openldap_abandon_operations_initiated agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_add_operations_initiated agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_open_threads agent_hostname=zy-fat port=389 server=localhost 1
|
||||
openldap_add_operations_completed agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_operations_initiated agent_hostname=zy-fat port=389 server=localhost 3
|
||||
openldap_write_waiters agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_entries_statistics agent_hostname=zy-fat port=389 server=localhost 41
|
||||
openldap_modrdn_operations_completed agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_pending_threads agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_max_pending_threads agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_bind_operations_initiated agent_hostname=zy-fat port=389 server=localhost 1
|
||||
openldap_max_file_descriptors_connections agent_hostname=zy-fat port=389 server=localhost 1024
|
||||
openldap_compare_operations_initiated agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_search_operations_initiated agent_hostname=zy-fat port=389 server=localhost 2
|
||||
openldap_modrdn_operations_initiated agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_read_waiters agent_hostname=zy-fat port=389 server=localhost 1
|
||||
openldap_backload_threads agent_hostname=zy-fat port=389 server=localhost 1
|
||||
openldap_current_connections agent_hostname=zy-fat port=389 server=localhost 1
|
||||
openldap_unbind_operations_completed agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_delete_operations_initiated agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_extended_operations_initiated agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_modify_operations_initiated agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_max_threads agent_hostname=zy-fat port=389 server=localhost 16
|
||||
openldap_abandon_operations_completed agent_hostname=zy-fat port=389 server=localhost 0
|
||||
openldap_operations_completed agent_hostname=zy-fat port=389 server=localhost 2
|
||||
openldap_database_2_databases agent_hostname=zy-fat port=389 server=localhost 0
|
||||
```
|
||||
|
||||
Using the `389ds` dialect
|
||||
|
||||
```text
|
||||
389ds_current_connections_at_max_threads agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_connections_max_threads agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_add_operations agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_dtablesize agent_hostname=zy-fat port=389 server=localhost 63936
|
||||
389ds_strongauth_binds agent_hostname=zy-fat port=389 server=localhost 13
|
||||
389ds_modrdn_operations agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_maxthreads_per_conn_hits agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_current_connections agent_hostname=zy-fat port=389 server=localhost 2
|
||||
389ds_security_errors agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_entries_sent agent_hostname=zy-fat port=389 server=localhost 13
|
||||
389ds_cache_entries agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_backends agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_threads agent_hostname=zy-fat port=389 server=localhost 17
|
||||
389ds_connections agent_hostname=zy-fat port=389 server=localhost 2
|
||||
389ds_read_operations agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_entries_returned agent_hostname=zy-fat port=389 server=localhost 13
|
||||
389ds_unauth_binds agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_search_operations agent_hostname=zy-fat port=389 server=localhost 14
|
||||
389ds_simpleauth_binds agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_operations_completed agent_hostname=zy-fat port=389 server=localhost 51
|
||||
389ds_connections_in_max_threads agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_modify_operations agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_wholesubtree_search_operations agent_hostname=zy-fat port=389 server=localhost 1
|
||||
389ds_read_waiters agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_compare_operations agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_errors agent_hostname=zy-fat port=389 server=localhost 13
|
||||
389ds_in_operations agent_hostname=zy-fat port=389 server=localhost 52
|
||||
389ds_total_connections agent_hostname=zy-fat port=389 server=localhost 15
|
||||
389ds_cache_hits agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_list_operations agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_referrals_returned agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_copy_entries agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_operations_initiated agent_hostname=zy-fat port=389 server=localhost 52
|
||||
389ds_chainings agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_bind_security_errors agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_onelevel_search_operations agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_bytes_sent agent_hostname=zy-fat port=389 server=localhost 1702
|
||||
389ds_bytes_received agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_referrals agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_delete_operations agent_hostname=zy-fat port=389 server=localhost 0
|
||||
389ds_anonymous_binds agent_hostname=zy-fat port=389 server=localhost 0
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "MySQL 仪表盘(使用 instance 筛选,需要采集时自行打上 instance 标签)",
|
||||
"name": "MySQL 仪表盘(远端)",
|
||||
"tags": "",
|
||||
"ident": "",
|
||||
"configs": {
|
||||
@@ -1802,5 +1802,5 @@
|
||||
],
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"uuid": 1717556328087995000
|
||||
"uuid": 1717556328087994322
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "MySQL 仪表盘,适用于 Categraf 采集本机 MySQL 的场景",
|
||||
"name": "MySQL 仪表盘",
|
||||
"tags": "",
|
||||
"ident": "",
|
||||
"configs": {
|
||||
@@ -1798,5 +1798,5 @@
|
||||
],
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"uuid": 1717556328087994000
|
||||
"uuid": 1717556328087994321
|
||||
}
|
||||
@@ -139,11 +139,3 @@ timeout = "3s"
|
||||
request = '''
|
||||
select METRIC_NAME,VALUE from v$sysmetric where group_id=2
|
||||
'''
|
||||
|
||||
[[metrics]]
|
||||
mesurement = "applylag"
|
||||
metric_fields = [ "value" ]
|
||||
timeout = "3s"
|
||||
request = '''
|
||||
SELECT TO_NUMBER(EXTRACT(SECOND FROM TO_DSINTERVAL (value))) as value FROM v$dataguard_stats WHERE name = 'apply lag'
|
||||
'''
|
||||
@@ -1,925 +0,0 @@
|
||||
{
|
||||
"name": "Redis by address",
|
||||
"tags": "Redis Categraf",
|
||||
"configs": {
|
||||
"panels": [
|
||||
{
|
||||
"collapsed": true,
|
||||
"id": "2ecb82c6-4d1a-41b5-8cdc-0284db16bd54",
|
||||
"layout": {
|
||||
"h": 1,
|
||||
"i": "2ecb82c6-4d1a-41b5-8cdc-0284db16bd54",
|
||||
"isResizable": false,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "Basic Info",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"custom": {
|
||||
"alignItems": "center",
|
||||
"bgColor": "rgba(0, 0, 0, 0)",
|
||||
"content": "<img src=\"https://download.flashcat.cloud/ulric/redis.png\" width=128 />",
|
||||
"justifyContent": "center",
|
||||
"textColor": "#000000",
|
||||
"textDarkColor": "#FFFFFF",
|
||||
"textSize": 12
|
||||
},
|
||||
"id": "b5acc352-a2bd-4afc-b6cd-d6db0905f807",
|
||||
"layout": {
|
||||
"h": 3,
|
||||
"i": "b5acc352-a2bd-4afc-b6cd-d6db0905f807",
|
||||
"isResizable": true,
|
||||
"w": 4,
|
||||
"x": 0,
|
||||
"y": 1
|
||||
},
|
||||
"maxPerRow": 4,
|
||||
"name": "",
|
||||
"type": "text",
|
||||
"version": "3.0.0"
|
||||
},
|
||||
{
|
||||
"custom": {
|
||||
"calc": "lastNotNull",
|
||||
"colSpan": 0,
|
||||
"colorMode": "background",
|
||||
"graphMode": "none",
|
||||
"orientation": "vertical",
|
||||
"textMode": "valueAndName",
|
||||
"textSize": {},
|
||||
"valueField": "Value"
|
||||
},
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"id": "5eb6fbcf-4260-40d0-ad6a-540e54a1f922",
|
||||
"layout": {
|
||||
"h": 3,
|
||||
"i": "2a02e1d4-2ed3-4bd2-9fa0-69bb10f13888",
|
||||
"isResizable": true,
|
||||
"w": 5,
|
||||
"x": 4,
|
||||
"y": 1
|
||||
},
|
||||
"maxPerRow": 4,
|
||||
"name": "Redis Uptime",
|
||||
"options": {
|
||||
"standardOptions": {
|
||||
"decimals": 2,
|
||||
"util": "seconds"
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "rgba(63, 196, 83, 1)",
|
||||
"type": "base",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"valueMappings": [
|
||||
{
|
||||
"match": {
|
||||
"to": 600
|
||||
},
|
||||
"result": {
|
||||
"color": "rgba(255, 101, 107, 1)"
|
||||
},
|
||||
"type": "range"
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"from": 600
|
||||
},
|
||||
"result": {
|
||||
"color": "rgba(63, 196, 83, 1)"
|
||||
},
|
||||
"type": "range"
|
||||
}
|
||||
]
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "redis_uptime_in_seconds{address=~\"$address\"}",
|
||||
"legend": "{{address}}",
|
||||
"maxDataPoints": 240
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"type": "stat",
|
||||
"version": "3.0.0"
|
||||
},
|
||||
{
|
||||
"custom": {
|
||||
"calc": "lastNotNull",
|
||||
"colSpan": 0,
|
||||
"colorMode": "background",
|
||||
"graphMode": "none",
|
||||
"orientation": "vertical",
|
||||
"textMode": "valueAndName",
|
||||
"textSize": {},
|
||||
"valueField": "Value"
|
||||
},
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"id": "8ccada5e-02f3-4efc-9b36-2a367612e4cb",
|
||||
"layout": {
|
||||
"h": 3,
|
||||
"i": "8ccada5e-02f3-4efc-9b36-2a367612e4cb",
|
||||
"isResizable": true,
|
||||
"w": 5,
|
||||
"x": 9,
|
||||
"y": 1
|
||||
},
|
||||
"maxPerRow": 4,
|
||||
"name": "Connected Clients",
|
||||
"options": {
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#6C53B1",
|
||||
"type": "base",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"valueMappings": [
|
||||
{
|
||||
"match": {
|
||||
"to": 500
|
||||
},
|
||||
"result": {
|
||||
"color": "rgba(63, 196, 83, 1)"
|
||||
},
|
||||
"type": "range"
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"from": 500
|
||||
},
|
||||
"result": {
|
||||
"color": "rgba(255, 101, 107, 1)"
|
||||
},
|
||||
"type": "range"
|
||||
}
|
||||
]
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "redis_connected_clients{address=~\"$address\"}",
|
||||
"legend": "{{address}}",
|
||||
"maxDataPoints": 240
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"type": "stat",
|
||||
"version": "3.0.0"
|
||||
},
|
||||
{
|
||||
"custom": {
|
||||
"calc": "lastNotNull",
|
||||
"colSpan": 0,
|
||||
"colorMode": "background",
|
||||
"graphMode": "none",
|
||||
"orientation": "vertical",
|
||||
"textMode": "valueAndName",
|
||||
"textSize": {},
|
||||
"valueField": "Value"
|
||||
},
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"id": "716dc7e7-c9ec-4195-93f6-db1c572ae8b0",
|
||||
"layout": {
|
||||
"h": 3,
|
||||
"i": "716dc7e7-c9ec-4195-93f6-db1c572ae8b0",
|
||||
"isResizable": true,
|
||||
"w": 5,
|
||||
"x": 14,
|
||||
"y": 1
|
||||
},
|
||||
"maxPerRow": 4,
|
||||
"name": "Memory Used",
|
||||
"options": {
|
||||
"standardOptions": {
|
||||
"decimals": 1,
|
||||
"util": "bytesIEC"
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#6C53B1",
|
||||
"type": "base",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"valueMappings": [
|
||||
{
|
||||
"match": {
|
||||
"to": 128000000
|
||||
},
|
||||
"result": {
|
||||
"color": "#079e05"
|
||||
},
|
||||
"type": "range"
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"from": 128000000
|
||||
},
|
||||
"result": {
|
||||
"color": "#f10909"
|
||||
},
|
||||
"type": "range"
|
||||
}
|
||||
]
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "redis_used_memory{address=~\"$address\"}",
|
||||
"legend": "{{address}}",
|
||||
"maxDataPoints": 240
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"type": "stat",
|
||||
"version": "3.0.0"
|
||||
},
|
||||
{
|
||||
"custom": {
|
||||
"calc": "lastNotNull",
|
||||
"colSpan": 0,
|
||||
"colorMode": "background",
|
||||
"graphMode": "none",
|
||||
"orientation": "vertical",
|
||||
"textMode": "valueAndName",
|
||||
"textSize": {},
|
||||
"valueField": "Value"
|
||||
},
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"id": "c6948161-db07-42df-beb1-765ee9c071a9",
|
||||
"layout": {
|
||||
"h": 3,
|
||||
"i": "c6948161-db07-42df-beb1-765ee9c071a9",
|
||||
"isResizable": true,
|
||||
"w": 5,
|
||||
"x": 19,
|
||||
"y": 1
|
||||
},
|
||||
"maxPerRow": 4,
|
||||
"name": "Max Memory Limit",
|
||||
"options": {
|
||||
"standardOptions": {
|
||||
"decimals": 1,
|
||||
"util": "bytesIEC"
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "rgba(63, 196, 83, 1)",
|
||||
"type": "base",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "redis_maxmemory{address=~\"$address\"}",
|
||||
"legend": "{{address}}",
|
||||
"maxDataPoints": 240
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"type": "stat",
|
||||
"version": "3.0.0"
|
||||
},
|
||||
{
|
||||
"collapsed": true,
|
||||
"id": "bd54cf4f-1abb-4945-8aab-f89aec16daef",
|
||||
"layout": {
|
||||
"h": 1,
|
||||
"i": "bd54cf4f-1abb-4945-8aab-f89aec16daef",
|
||||
"isResizable": false,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 4
|
||||
},
|
||||
"name": "Commands",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"fillOpacity": 0.3,
|
||||
"gradientMode": "opacity",
|
||||
"lineInterpolation": "smooth",
|
||||
"lineWidth": 2,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"spanNulls": false,
|
||||
"stack": "off"
|
||||
},
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"id": "3d5f8c4e-0ddf-4d68-9f6d-2cc57d864a8e",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"i": "3d5f8c4e-0ddf-4d68-9f6d-2cc57d864a8e",
|
||||
"isResizable": true,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 5
|
||||
},
|
||||
"maxPerRow": 4,
|
||||
"name": "Commands Executed / sec",
|
||||
"options": {
|
||||
"legend": {
|
||||
"behaviour": "showItem",
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {
|
||||
"decimals": 2
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#6C53B1",
|
||||
"type": "base",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byFrameRefID"
|
||||
},
|
||||
"properties": {
|
||||
"rightYAxisDisplay": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(redis_total_commands_processed{address=~\"$address\"}[5m])",
|
||||
"legend": "{{address}}",
|
||||
"maxDataPoints": 240
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"type": "timeseries",
|
||||
"version": "3.0.0"
|
||||
},
|
||||
{
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"fillOpacity": 0.3,
|
||||
"gradientMode": "opacity",
|
||||
"lineInterpolation": "smooth",
|
||||
"lineWidth": 2,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"spanNulls": false,
|
||||
"stack": "noraml"
|
||||
},
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"id": "344a874d-c34d-4d2d-9bb4-46e0912cd9f5",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"i": "344a874d-c34d-4d2d-9bb4-46e0912cd9f5",
|
||||
"isResizable": true,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 5
|
||||
},
|
||||
"maxPerRow": 4,
|
||||
"name": "Hits / Misses per Sec",
|
||||
"options": {
|
||||
"legend": {
|
||||
"behaviour": "showItem",
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {
|
||||
"decimals": 2
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#6C53B1",
|
||||
"type": "base",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byFrameRefID"
|
||||
},
|
||||
"properties": {
|
||||
"rightYAxisDisplay": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"expr": "irate(redis_keyspace_hits{address=~\"$address\"}[5m])",
|
||||
"legend": "{{address}} hits",
|
||||
"maxDataPoints": 240
|
||||
},
|
||||
{
|
||||
"expr": "irate(redis_keyspace_misses{address=~\"$address\"}[5m])",
|
||||
"legend": "{{address}} misses",
|
||||
"maxDataPoints": 240
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"type": "timeseries",
|
||||
"version": "3.0.0"
|
||||
},
|
||||
{
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"fillOpacity": 0.3,
|
||||
"gradientMode": "opacity",
|
||||
"lineInterpolation": "smooth",
|
||||
"lineWidth": 2,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"spanNulls": false,
|
||||
"stack": "off"
|
||||
},
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"id": "3c83cd35-585c-4070-a210-1f17345f13f4",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"i": "3c83cd35-585c-4070-a210-1f17345f13f4",
|
||||
"isResizable": true,
|
||||
"w": 8,
|
||||
"x": 16,
|
||||
"y": 5
|
||||
},
|
||||
"maxPerRow": 4,
|
||||
"name": "Top Commands",
|
||||
"options": {
|
||||
"legend": {
|
||||
"behaviour": "showItem",
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {
|
||||
"decimals": 2
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#6C53B1",
|
||||
"type": "base",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "desc"
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byFrameRefID"
|
||||
},
|
||||
"properties": {
|
||||
"rightYAxisDisplay": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"expr": "topk(5, irate(redis_cmdstat_calls{address=~\"$address\"}[1m]))",
|
||||
"legend": "{{address}} {{command}}",
|
||||
"maxDataPoints": 240
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"type": "timeseries",
|
||||
"version": "3.0.0"
|
||||
},
|
||||
{
|
||||
"collapsed": true,
|
||||
"id": "1ea61073-a46d-4d7c-b072-fcdcbc5ac084",
|
||||
"layout": {
|
||||
"h": 1,
|
||||
"i": "1ea61073-a46d-4d7c-b072-fcdcbc5ac084",
|
||||
"isResizable": false,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 10
|
||||
},
|
||||
"name": "Keys",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"fillOpacity": 0.3,
|
||||
"gradientMode": "opacity",
|
||||
"lineInterpolation": "smooth",
|
||||
"lineWidth": 2,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"spanNulls": false,
|
||||
"stack": "off"
|
||||
},
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"id": "b2b4451c-4f8a-438a-8c48-69c95c68361e",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"i": "b2b4451c-4f8a-438a-8c48-69c95c68361e",
|
||||
"isResizable": true,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 11
|
||||
},
|
||||
"maxPerRow": 4,
|
||||
"name": "Total Items per DB",
|
||||
"options": {
|
||||
"legend": {
|
||||
"behaviour": "showItem",
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {
|
||||
"decimals": 2
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#6C53B1",
|
||||
"type": "base",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "desc"
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byFrameRefID"
|
||||
},
|
||||
"properties": {
|
||||
"rightYAxisDisplay": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(redis_keyspace_keys{address=~\"$address\"}) by (address, db)",
|
||||
"legend": "{{address}} {{db}}",
|
||||
"maxDataPoints": 240
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"type": "timeseries",
|
||||
"version": "3.0.0"
|
||||
},
|
||||
{
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"fillOpacity": 0.3,
|
||||
"gradientMode": "opacity",
|
||||
"lineInterpolation": "smooth",
|
||||
"lineWidth": 2,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"spanNulls": false,
|
||||
"stack": "off"
|
||||
},
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"id": "894b9beb-e764-441c-ae04-13e5dbbb901d",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"i": "894b9beb-e764-441c-ae04-13e5dbbb901d",
|
||||
"isResizable": true,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 11
|
||||
},
|
||||
"maxPerRow": 4,
|
||||
"name": "Expired / Evicted",
|
||||
"options": {
|
||||
"legend": {
|
||||
"behaviour": "showItem",
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {
|
||||
"decimals": 2
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#6C53B1",
|
||||
"type": "base",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "desc"
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byFrameRefID"
|
||||
},
|
||||
"properties": {
|
||||
"rightYAxisDisplay": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(redis_expired_keys{address=~\"$address\"}[5m])) by (address)",
|
||||
"legend": "{{address}} expired",
|
||||
"maxDataPoints": 240
|
||||
},
|
||||
{
|
||||
"expr": "sum(rate(redis_evicted_keys{address=~\"$address\"}[5m])) by (address)",
|
||||
"legend": "{{address}} evicted",
|
||||
"maxDataPoints": 240
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"type": "timeseries",
|
||||
"version": "3.0.0"
|
||||
},
|
||||
{
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"fillOpacity": 0.3,
|
||||
"gradientMode": "opacity",
|
||||
"lineInterpolation": "smooth",
|
||||
"lineWidth": 2,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"spanNulls": false,
|
||||
"stack": "noraml"
|
||||
},
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"id": "f721a641-28c7-4e82-a37c-ec17704a0c57",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"i": "f721a641-28c7-4e82-a37c-ec17704a0c57",
|
||||
"isResizable": true,
|
||||
"w": 8,
|
||||
"x": 16,
|
||||
"y": 11
|
||||
},
|
||||
"maxPerRow": 4,
|
||||
"name": "Expiring vs Not-Expiring Keys",
|
||||
"options": {
|
||||
"legend": {
|
||||
"behaviour": "showItem",
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {
|
||||
"decimals": 2
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#6C53B1",
|
||||
"type": "base",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byFrameRefID"
|
||||
},
|
||||
"properties": {
|
||||
"rightYAxisDisplay": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(redis_keyspace_keys{address=~\"$address\"}) - sum(redis_keyspace_expires{address=~\"$address\"}) ",
|
||||
"legend": "{{address}} not expiring",
|
||||
"maxDataPoints": 240
|
||||
},
|
||||
{
|
||||
"expr": "sum(redis_keyspace_expires{address=~\"$address\"}) ",
|
||||
"legend": "{{address}} expiring",
|
||||
"maxDataPoints": 240
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"type": "timeseries",
|
||||
"version": "3.0.0"
|
||||
},
|
||||
{
|
||||
"collapsed": true,
|
||||
"id": "60ff41ed-9d41-40ee-a13b-c968f3ca49d0",
|
||||
"layout": {
|
||||
"h": 1,
|
||||
"i": "60ff41ed-9d41-40ee-a13b-c968f3ca49d0",
|
||||
"isResizable": false,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 16
|
||||
},
|
||||
"name": "Network",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"fillOpacity": 0.3,
|
||||
"gradientMode": "opacity",
|
||||
"lineInterpolation": "smooth",
|
||||
"lineWidth": 2,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"spanNulls": false,
|
||||
"stack": "off"
|
||||
},
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"id": "1841950c-e867-4a62-b846-78754dc0e34d",
|
||||
"layout": {
|
||||
"h": 7,
|
||||
"i": "1841950c-e867-4a62-b846-78754dc0e34d",
|
||||
"isResizable": true,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 17
|
||||
},
|
||||
"maxPerRow": 4,
|
||||
"name": "Network I/O",
|
||||
"options": {
|
||||
"legend": {
|
||||
"behaviour": "showItem",
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {
|
||||
"decimals": 2,
|
||||
"util": "bytesIEC"
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#6C53B1",
|
||||
"type": "base",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "desc"
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byFrameRefID"
|
||||
},
|
||||
"properties": {
|
||||
"rightYAxisDisplay": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(redis_total_net_input_bytes{address=~\"$address\"}[5m]))",
|
||||
"legend": "input",
|
||||
"maxDataPoints": 240
|
||||
},
|
||||
{
|
||||
"expr": "sum(rate(redis_total_net_output_bytes{address=~\"$address\"}[5m]))",
|
||||
"legend": "output",
|
||||
"maxDataPoints": 240
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"type": "timeseries",
|
||||
"version": "3.0.0"
|
||||
}
|
||||
],
|
||||
"var": [
|
||||
{
|
||||
"definition": "prometheus",
|
||||
"name": "prom",
|
||||
"type": "datasource"
|
||||
},
|
||||
{
|
||||
"allOption": true,
|
||||
"datasource": {
|
||||
"cate": "prometheus",
|
||||
"value": "${prom}"
|
||||
},
|
||||
"definition": "label_values(redis_uptime_in_seconds,address)",
|
||||
"hide": false,
|
||||
"multi": true,
|
||||
"name": "address",
|
||||
"type": "query"
|
||||
}
|
||||
],
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"uuid": 1732008163114399
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Redis by instance",
|
||||
"tags": "Redis Categraf",
|
||||
"name": "Redis Overview - categraf",
|
||||
"tags": "Redis Prometheus",
|
||||
"ident": "",
|
||||
"configs": {
|
||||
"panels": [
|
||||
|
||||
@@ -60,18 +60,6 @@ func (c *BusiGroupCacheType) GetByBusiGroupId(id int64) *models.BusiGroup {
|
||||
return c.ugs[id]
|
||||
}
|
||||
|
||||
func (c *BusiGroupCacheType) GetNamesByBusiGroupIds(ids []int64) []string {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
names := make([]string, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
if ug, exists := c.ugs[id]; exists {
|
||||
names = append(names, ug.Name)
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (c *BusiGroupCacheType) SyncBusiGroups() {
|
||||
err := c.syncBusiGroups()
|
||||
if err != nil {
|
||||
@@ -124,14 +112,3 @@ func (c *BusiGroupCacheType) syncBusiGroups() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *BusiGroupCacheType) GetNameByBusiGroupId(id int64) string {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
busiGroup := c.ugs[id]
|
||||
if busiGroup == nil {
|
||||
return ""
|
||||
}
|
||||
return busiGroup.Name
|
||||
}
|
||||
|
||||
@@ -23,9 +23,7 @@ type DatasourceCacheType struct {
|
||||
DatasourceFilter func([]*models.Datasource, *models.User) []*models.Datasource
|
||||
|
||||
sync.RWMutex
|
||||
ds map[int64]*models.Datasource // key: id value: datasource
|
||||
CateToIDs map[string]map[int64]*models.Datasource // key1: cate key2: id value: datasource
|
||||
CateToNames map[string]map[string]int64 // key1: cate key2: name value: id
|
||||
ds map[int64]*models.Datasource // key: id
|
||||
}
|
||||
|
||||
func NewDatasourceCache(ctx *ctx.Context, stats *Stats) *DatasourceCacheType {
|
||||
@@ -35,8 +33,6 @@ func NewDatasourceCache(ctx *ctx.Context, stats *Stats) *DatasourceCacheType {
|
||||
ctx: ctx,
|
||||
stats: stats,
|
||||
ds: make(map[int64]*models.Datasource),
|
||||
CateToIDs: make(map[string]map[int64]*models.Datasource),
|
||||
CateToNames: make(map[string]map[string]int64),
|
||||
DatasourceCheckHook: func(ctx *gin.Context) bool { return false },
|
||||
DatasourceFilter: func(ds []*models.Datasource, user *models.User) []*models.Datasource { return ds },
|
||||
}
|
||||
@@ -44,12 +40,6 @@ func NewDatasourceCache(ctx *ctx.Context, stats *Stats) *DatasourceCacheType {
|
||||
return ds
|
||||
}
|
||||
|
||||
func (d *DatasourceCacheType) GetIDsByDsCateAndQueries(cate string, datasourceQueries []models.DatasourceQuery) []int64 {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return models.GetDatasourceIDsByDatasourceQueries(datasourceQueries, d.CateToIDs[cate], d.CateToNames[cate])
|
||||
}
|
||||
|
||||
func (d *DatasourceCacheType) StatChanged(total, lastUpdated int64) bool {
|
||||
if d.statTotal == total && d.statLastUpdated == lastUpdated {
|
||||
return false
|
||||
@@ -59,22 +49,8 @@ func (d *DatasourceCacheType) StatChanged(total, lastUpdated int64) bool {
|
||||
}
|
||||
|
||||
func (d *DatasourceCacheType) Set(ds map[int64]*models.Datasource, total, lastUpdated int64) {
|
||||
cateToDs := make(map[string]map[int64]*models.Datasource)
|
||||
cateToNames := make(map[string]map[string]int64)
|
||||
for _, datasource := range ds {
|
||||
if _, exists := cateToDs[datasource.PluginType]; !exists {
|
||||
cateToDs[datasource.PluginType] = make(map[int64]*models.Datasource)
|
||||
}
|
||||
cateToDs[datasource.PluginType][datasource.Id] = datasource
|
||||
if _, exists := cateToNames[datasource.PluginType]; !exists {
|
||||
cateToNames[datasource.PluginType] = make(map[string]int64)
|
||||
}
|
||||
cateToNames[datasource.PluginType][datasource.Name] = datasource.Id
|
||||
}
|
||||
d.Lock()
|
||||
d.CateToIDs = cateToDs
|
||||
d.ds = ds
|
||||
d.CateToNames = cateToNames
|
||||
d.Unlock()
|
||||
|
||||
// only one goroutine used, so no need lock
|
||||
@@ -123,20 +99,20 @@ func (d *DatasourceCacheType) syncDatasources() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
ds, err := models.DatasourceGetMap(d.ctx)
|
||||
m, err := models.DatasourceGetMap(d.ctx)
|
||||
if err != nil {
|
||||
dumper.PutSyncRecord("datasources", start.Unix(), -1, -1, "failed to query records: "+err.Error())
|
||||
return errors.WithMessage(err, "failed to call DatasourceGetMap")
|
||||
}
|
||||
|
||||
d.Set(ds, stat.Total, stat.LastUpdated)
|
||||
d.Set(m, stat.Total, stat.LastUpdated)
|
||||
|
||||
ms := time.Since(start).Milliseconds()
|
||||
d.stats.GaugeCronDuration.WithLabelValues("sync_datasources").Set(float64(ms))
|
||||
d.stats.GaugeSyncNumber.WithLabelValues("sync_datasources").Set(float64(len(ds)))
|
||||
d.stats.GaugeSyncNumber.WithLabelValues("sync_datasources").Set(float64(len(m)))
|
||||
|
||||
logger.Infof("timer: sync datasources done, cost: %dms, number: %d", ms, len(ds))
|
||||
dumper.PutSyncRecord("datasources", start.Unix(), ms, len(ds), "success")
|
||||
logger.Infof("timer: sync datasources done, cost: %dms, number: %d", ms, len(m))
|
||||
dumper.PutSyncRecord("datasources", start.Unix(), ms, len(m), "success")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -12,71 +12,63 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/poster"
|
||||
"github.com/ccfos/nightingale/v6/pkg/tplx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/unit"
|
||||
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
type AlertCurEvent struct {
|
||||
Id int64 `json:"id" gorm:"primaryKey"`
|
||||
Cate string `json:"cate"`
|
||||
Cluster string `json:"cluster"`
|
||||
DatasourceId int64 `json:"datasource_id"`
|
||||
GroupId int64 `json:"group_id"` // busi group id
|
||||
GroupName string `json:"group_name"` // busi group name
|
||||
Hash string `json:"hash"` // rule_id + vector_key
|
||||
RuleId int64 `json:"rule_id"`
|
||||
RuleName string `json:"rule_name"`
|
||||
RuleNote string `json:"rule_note"`
|
||||
RuleProd string `json:"rule_prod"`
|
||||
RuleAlgo string `json:"rule_algo"`
|
||||
Severity int `json:"severity"`
|
||||
PromForDuration int `json:"prom_for_duration"`
|
||||
PromQl string `json:"prom_ql"`
|
||||
RuleConfig string `json:"-" gorm:"rule_config"` // rule config
|
||||
RuleConfigJson interface{} `json:"rule_config" gorm:"-"` // rule config for fe
|
||||
PromEvalInterval int `json:"prom_eval_interval"`
|
||||
Callbacks string `json:"-"` // for db
|
||||
CallbacksJSON []string `json:"callbacks" gorm:"-"` // for fe
|
||||
RunbookUrl string `json:"runbook_url"`
|
||||
NotifyRecovered int `json:"notify_recovered"`
|
||||
NotifyChannels string `json:"-"` // for db
|
||||
NotifyChannelsJSON []string `json:"notify_channels" gorm:"-"` // for fe
|
||||
NotifyGroups string `json:"-"` // for db
|
||||
NotifyGroupsJSON []string `json:"notify_groups" gorm:"-"` // for fe
|
||||
NotifyGroupsObj []*UserGroup `json:"notify_groups_obj" gorm:"-"` // for fe
|
||||
TargetIdent string `json:"target_ident"`
|
||||
TargetNote string `json:"target_note"`
|
||||
TriggerTime int64 `json:"trigger_time"`
|
||||
TriggerValue string `json:"trigger_value"`
|
||||
TriggerValues string `json:"trigger_values" gorm:"-"`
|
||||
TriggerValuesJson EventTriggerValues `json:"trigger_values_json" gorm:"-"`
|
||||
Tags string `json:"-"` // for db
|
||||
TagsJSON []string `json:"tags" gorm:"-"` // for fe
|
||||
TagsMap map[string]string `json:"tags_map" gorm:"-"` // for internal usage
|
||||
OriginalTags string `json:"-"` // for db
|
||||
OriginalTagsJSON []string `json:"original_tags" gorm:"-"` // for fe
|
||||
Annotations string `json:"-"` //
|
||||
AnnotationsJSON map[string]string `json:"annotations" gorm:"-"` // for fe
|
||||
IsRecovered bool `json:"is_recovered" gorm:"-"` // for notify.py
|
||||
NotifyUsersObj []*User `json:"notify_users_obj" gorm:"-"` // for notify.py
|
||||
LastEvalTime int64 `json:"last_eval_time" gorm:"-"` // for notify.py 上次计算的时间
|
||||
LastSentTime int64 `json:"last_sent_time" gorm:"-"` // 上次发送时间
|
||||
NotifyCurNumber int `json:"notify_cur_number"` // notify: current number
|
||||
FirstTriggerTime int64 `json:"first_trigger_time"` // 连续告警的首次告警时间
|
||||
ExtraConfig interface{} `json:"extra_config" gorm:"-"`
|
||||
Status int `json:"status" gorm:"-"`
|
||||
Claimant string `json:"claimant" gorm:"-"`
|
||||
SubRuleId int64 `json:"sub_rule_id" gorm:"-"`
|
||||
ExtraInfo []string `json:"extra_info" gorm:"-"`
|
||||
Target *Target `json:"target" gorm:"-"`
|
||||
RecoverConfig RecoverConfig `json:"recover_config" gorm:"-"`
|
||||
RuleHash string `json:"rule_hash" gorm:"-"`
|
||||
ExtraInfoMap []map[string]string `json:"extra_info_map" gorm:"-"`
|
||||
}
|
||||
|
||||
type EventTriggerValues struct {
|
||||
ValuesWithUnit map[string]unit.FormattedValue `json:"values_with_unit"`
|
||||
Id int64 `json:"id" gorm:"primaryKey"`
|
||||
Cate string `json:"cate"`
|
||||
Cluster string `json:"cluster"`
|
||||
DatasourceId int64 `json:"datasource_id"`
|
||||
GroupId int64 `json:"group_id"` // busi group id
|
||||
GroupName string `json:"group_name"` // busi group name
|
||||
Hash string `json:"hash"` // rule_id + vector_key
|
||||
RuleId int64 `json:"rule_id"`
|
||||
RuleName string `json:"rule_name"`
|
||||
RuleNote string `json:"rule_note"`
|
||||
RuleProd string `json:"rule_prod"`
|
||||
RuleAlgo string `json:"rule_algo"`
|
||||
Severity int `json:"severity"`
|
||||
PromForDuration int `json:"prom_for_duration"`
|
||||
PromQl string `json:"prom_ql"`
|
||||
RuleConfig string `json:"-" gorm:"rule_config"` // rule config
|
||||
RuleConfigJson interface{} `json:"rule_config" gorm:"-"` // rule config for fe
|
||||
PromEvalInterval int `json:"prom_eval_interval"`
|
||||
Callbacks string `json:"-"` // for db
|
||||
CallbacksJSON []string `json:"callbacks" gorm:"-"` // for fe
|
||||
RunbookUrl string `json:"runbook_url"`
|
||||
NotifyRecovered int `json:"notify_recovered"`
|
||||
NotifyChannels string `json:"-"` // for db
|
||||
NotifyChannelsJSON []string `json:"notify_channels" gorm:"-"` // for fe
|
||||
NotifyGroups string `json:"-"` // for db
|
||||
NotifyGroupsJSON []string `json:"notify_groups" gorm:"-"` // for fe
|
||||
NotifyGroupsObj []*UserGroup `json:"notify_groups_obj" gorm:"-"` // for fe
|
||||
TargetIdent string `json:"target_ident"`
|
||||
TargetNote string `json:"target_note"`
|
||||
TriggerTime int64 `json:"trigger_time"`
|
||||
TriggerValue string `json:"trigger_value"`
|
||||
TriggerValues string `json:"trigger_values" gorm:"-"`
|
||||
Tags string `json:"-"` // for db
|
||||
TagsJSON []string `json:"tags" gorm:"-"` // for fe
|
||||
TagsMap map[string]string `json:"tags_map" gorm:"-"` // for internal usage
|
||||
OriginalTags string `json:"-"` // for db
|
||||
OriginalTagsJSON []string `json:"original_tags" gorm:"-"` // for fe
|
||||
Annotations string `json:"-"` //
|
||||
AnnotationsJSON map[string]string `json:"annotations" gorm:"-"` // for fe
|
||||
IsRecovered bool `json:"is_recovered" gorm:"-"` // for notify.py
|
||||
NotifyUsersObj []*User `json:"notify_users_obj" gorm:"-"` // for notify.py
|
||||
LastEvalTime int64 `json:"last_eval_time" gorm:"-"` // for notify.py 上次计算的时间
|
||||
LastSentTime int64 `json:"last_sent_time" gorm:"-"` // 上次发送时间
|
||||
NotifyCurNumber int `json:"notify_cur_number"` // notify: current number
|
||||
FirstTriggerTime int64 `json:"first_trigger_time"` // 连续告警的首次告警时间
|
||||
ExtraConfig interface{} `json:"extra_config" gorm:"-"`
|
||||
Status int `json:"status" gorm:"-"`
|
||||
Claimant string `json:"claimant" gorm:"-"`
|
||||
SubRuleId int64 `json:"sub_rule_id" gorm:"-"`
|
||||
ExtraInfo []string `json:"extra_info" gorm:"-"`
|
||||
Target *Target `json:"target" gorm:"-"`
|
||||
RecoverConfig RecoverConfig `json:"recover_config" gorm:"-"`
|
||||
}
|
||||
|
||||
func (e *AlertCurEvent) TableName() string {
|
||||
@@ -115,18 +107,8 @@ func (e *AlertCurEvent) ParseRule(field string) error {
|
||||
"{{$value := .TriggerValue}}",
|
||||
}
|
||||
|
||||
templateFuncMapCopy := tplx.NewTemplateFuncMap()
|
||||
templateFuncMapCopy["query"] = func(promql string, param ...int64) []AnomalyPoint {
|
||||
datasourceId := e.DatasourceId
|
||||
if len(param) > 0 {
|
||||
datasourceId = param[0]
|
||||
}
|
||||
value := tplx.Query(datasourceId, promql)
|
||||
return ConvertAnomalyPoints(value)
|
||||
}
|
||||
|
||||
text := strings.Join(append(defs, f), "")
|
||||
t, err := template.New(fmt.Sprint(e.RuleId)).Funcs(templateFuncMapCopy).Parse(text)
|
||||
t, err := template.New(fmt.Sprint(e.RuleId)).Funcs(template.FuncMap(tplx.TemplateFuncMap)).Parse(text)
|
||||
if err != nil {
|
||||
e.AnnotationsJSON[k] = fmt.Sprintf("failed to parse annotations: %v", err)
|
||||
continue
|
||||
@@ -375,15 +357,6 @@ func (e *AlertCurEvent) DB2Mem() {
|
||||
}
|
||||
}
|
||||
|
||||
func (e *AlertCurEvent) OverrideGlobalWebhook() bool {
|
||||
var rc RuleConfig
|
||||
if err := json.Unmarshal([]byte(e.RuleConfig), &rc); err != nil {
|
||||
logger.Warningf("failed to unmarshal rule config: %v", err)
|
||||
return false
|
||||
}
|
||||
return rc.OverrideGlobalWebhook
|
||||
}
|
||||
|
||||
func FillRuleConfigTplName(ctx *ctx.Context, ruleConfig string) (interface{}, bool) {
|
||||
var config RuleConfig
|
||||
err := json.Unmarshal([]byte(ruleConfig), &config)
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tidwall/match"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
"github.com/toolkits/pkg/str"
|
||||
)
|
||||
@@ -24,9 +23,8 @@ const (
|
||||
HOST = "host"
|
||||
LOKI = "loki"
|
||||
|
||||
PROMETHEUS = "prometheus"
|
||||
TDENGINE = "tdengine"
|
||||
ELASTICSEARCH = "elasticsearch"
|
||||
PROMETHEUS = "prometheus"
|
||||
TDENGINE = "tdengine"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -46,56 +44,55 @@ const (
|
||||
|
||||
type AlertRule struct {
|
||||
Id int64 `json:"id" gorm:"primaryKey"`
|
||||
GroupId int64 `json:"group_id"` // busi group id
|
||||
Cate string `json:"cate"` // alert rule cate (prometheus|elasticsearch)
|
||||
DatasourceIds string `json:"-" gorm:"datasource_ids"`
|
||||
DatasourceIdsJson []int64 `json:"datasource_ids,omitempty" gorm:"-"` // alert rule list page use this field
|
||||
DatasourceQueries []DatasourceQuery `json:"datasource_queries" gorm:"datasource_queries;type:text;serializer:json"` // datasource queries
|
||||
Cluster string `json:"cluster"` // take effect by clusters, seperated by space
|
||||
Name string `json:"name"` // rule name
|
||||
Note string `json:"note"` // will sent in notify
|
||||
Prod string `json:"prod"` // product empty means n9e
|
||||
Algorithm string `json:"algorithm"` // algorithm (''|holtwinters), empty means threshold
|
||||
AlgoParams string `json:"-" gorm:"algo_params"` // params algorithm need
|
||||
AlgoParamsJson interface{} `json:"algo_params" gorm:"-"` // for fe
|
||||
Delay int `json:"delay"` // Time (in seconds) to delay evaluation
|
||||
Severity int `json:"severity"` // 1: Emergency 2: Warning 3: Notice
|
||||
Severities []int `json:"severities" gorm:"-"` // 1: Emergency 2: Warning 3: Notice
|
||||
Disabled int `json:"disabled"` // 0: enabled, 1: disabled
|
||||
PromForDuration int `json:"prom_for_duration"` // prometheus for, unit:s
|
||||
PromQl string `json:"prom_ql"` // just one ql
|
||||
RuleConfig string `json:"-" gorm:"rule_config"` // rule config
|
||||
RuleConfigJson interface{} `json:"rule_config" gorm:"-"` // rule config for fe
|
||||
EventRelabelConfig []*pconf.RelabelConfig `json:"event_relabel_config" gorm:"-"` // event relabel config
|
||||
PromEvalInterval int `json:"prom_eval_interval"` // unit:s
|
||||
EnableStime string `json:"-"` // split by space: "00:00 10:00 12:00"
|
||||
EnableStimeJSON string `json:"enable_stime" gorm:"-"` // for fe
|
||||
EnableStimesJSON []string `json:"enable_stimes" gorm:"-"` // for fe
|
||||
EnableEtime string `json:"-"` // split by space: "00:00 10:00 12:00"
|
||||
EnableEtimeJSON string `json:"enable_etime" gorm:"-"` // for fe
|
||||
EnableEtimesJSON []string `json:"enable_etimes" gorm:"-"` // for fe
|
||||
EnableDaysOfWeek string `json:"-"` // eg: "0 1 2 3 4 5 6 ; 0 1 2"
|
||||
EnableDaysOfWeekJSON []string `json:"enable_days_of_week" gorm:"-"` // for fe
|
||||
EnableDaysOfWeeksJSON [][]string `json:"enable_days_of_weeks" gorm:"-"` // for fe
|
||||
EnableInBG int `json:"enable_in_bg"` // 0: global 1: enable one busi-group
|
||||
NotifyRecovered int `json:"notify_recovered"` // whether notify when recovery
|
||||
NotifyChannels string `json:"-"` // split by space: sms voice email dingtalk wecom
|
||||
NotifyChannelsJSON []string `json:"notify_channels" gorm:"-"` // for fe
|
||||
NotifyGroups string `json:"-"` // split by space: 233 43
|
||||
NotifyGroupsObj []UserGroup `json:"notify_groups_obj" gorm:"-"` // for fe
|
||||
NotifyGroupsJSON []string `json:"notify_groups" gorm:"-"` // for fe
|
||||
NotifyRepeatStep int `json:"notify_repeat_step"` // notify repeat interval, unit: min
|
||||
NotifyMaxNumber int `json:"notify_max_number"` // notify: max number
|
||||
RecoverDuration int64 `json:"recover_duration"` // unit: s
|
||||
Callbacks string `json:"-"` // split by space: http://a.com/api/x http://a.com/api/y'
|
||||
CallbacksJSON []string `json:"callbacks" gorm:"-"` // for fe
|
||||
RunbookUrl string `json:"runbook_url"` // sop url
|
||||
AppendTags string `json:"-"` // split by space: service=n9e mod=api
|
||||
AppendTagsJSON []string `json:"append_tags" gorm:"-"` // for fe
|
||||
Annotations string `json:"-"` //
|
||||
AnnotationsJSON map[string]string `json:"annotations" gorm:"-"` // for fe
|
||||
ExtraConfig string `json:"-" gorm:"extra_config"` // extra config
|
||||
ExtraConfigJSON interface{} `json:"extra_config" gorm:"-"` // for fe
|
||||
GroupId int64 `json:"group_id"` // busi group id
|
||||
Cate string `json:"cate"` // alert rule cate (prometheus|elasticsearch)
|
||||
DatasourceIds string `json:"-" gorm:"datasource_ids"` // datasource ids
|
||||
DatasourceIdsJson []int64 `json:"datasource_ids" gorm:"-"` // for fe
|
||||
Cluster string `json:"cluster"` // take effect by clusters, seperated by space
|
||||
Name string `json:"name"` // rule name
|
||||
Note string `json:"note"` // will sent in notify
|
||||
Prod string `json:"prod"` // product empty means n9e
|
||||
Algorithm string `json:"algorithm"` // algorithm (''|holtwinters), empty means threshold
|
||||
AlgoParams string `json:"-" gorm:"algo_params"` // params algorithm need
|
||||
AlgoParamsJson interface{} `json:"algo_params" gorm:"-"` // for fe
|
||||
Delay int `json:"delay"` // Time (in seconds) to delay evaluation
|
||||
Severity int `json:"severity"` // 1: Emergency 2: Warning 3: Notice
|
||||
Severities []int `json:"severities" gorm:"-"` // 1: Emergency 2: Warning 3: Notice
|
||||
Disabled int `json:"disabled"` // 0: enabled, 1: disabled
|
||||
PromForDuration int `json:"prom_for_duration"` // prometheus for, unit:s
|
||||
PromQl string `json:"prom_ql"` // just one ql
|
||||
RuleConfig string `json:"-" gorm:"rule_config"` // rule config
|
||||
RuleConfigJson interface{} `json:"rule_config" gorm:"-"` // rule config for fe
|
||||
EventRelabelConfig []*pconf.RelabelConfig `json:"event_relabel_config" gorm:"-"` // event relabel config
|
||||
PromEvalInterval int `json:"prom_eval_interval"` // unit:s
|
||||
EnableStime string `json:"-"` // split by space: "00:00 10:00 12:00"
|
||||
EnableStimeJSON string `json:"enable_stime" gorm:"-"` // for fe
|
||||
EnableStimesJSON []string `json:"enable_stimes" gorm:"-"` // for fe
|
||||
EnableEtime string `json:"-"` // split by space: "00:00 10:00 12:00"
|
||||
EnableEtimeJSON string `json:"enable_etime" gorm:"-"` // for fe
|
||||
EnableEtimesJSON []string `json:"enable_etimes" gorm:"-"` // for fe
|
||||
EnableDaysOfWeek string `json:"-"` // eg: "0 1 2 3 4 5 6 ; 0 1 2"
|
||||
EnableDaysOfWeekJSON []string `json:"enable_days_of_week" gorm:"-"` // for fe
|
||||
EnableDaysOfWeeksJSON [][]string `json:"enable_days_of_weeks" gorm:"-"` // for fe
|
||||
EnableInBG int `json:"enable_in_bg"` // 0: global 1: enable one busi-group
|
||||
NotifyRecovered int `json:"notify_recovered"` // whether notify when recovery
|
||||
NotifyChannels string `json:"-"` // split by space: sms voice email dingtalk wecom
|
||||
NotifyChannelsJSON []string `json:"notify_channels" gorm:"-"` // for fe
|
||||
NotifyGroups string `json:"-"` // split by space: 233 43
|
||||
NotifyGroupsObj []UserGroup `json:"notify_groups_obj" gorm:"-"` // for fe
|
||||
NotifyGroupsJSON []string `json:"notify_groups" gorm:"-"` // for fe
|
||||
NotifyRepeatStep int `json:"notify_repeat_step"` // notify repeat interval, unit: min
|
||||
NotifyMaxNumber int `json:"notify_max_number"` // notify: max number
|
||||
RecoverDuration int64 `json:"recover_duration"` // unit: s
|
||||
Callbacks string `json:"-"` // split by space: http://a.com/api/x http://a.com/api/y'
|
||||
CallbacksJSON []string `json:"callbacks" gorm:"-"` // for fe
|
||||
RunbookUrl string `json:"runbook_url"` // sop url
|
||||
AppendTags string `json:"-"` // split by space: service=n9e mod=api
|
||||
AppendTagsJSON []string `json:"append_tags" gorm:"-"` // for fe
|
||||
Annotations string `json:"-"` //
|
||||
AnnotationsJSON map[string]string `json:"annotations" gorm:"-"` // for fe
|
||||
ExtraConfig string `json:"-" gorm:"extra_config"` // extra config
|
||||
ExtraConfigJSON interface{} `json:"extra_config" gorm:"-"` // for fe
|
||||
CreateAt int64 `json:"create_at"`
|
||||
CreateBy string `json:"create_by"`
|
||||
UpdateAt int64 `json:"update_at"`
|
||||
@@ -103,29 +100,6 @@ type AlertRule struct {
|
||||
UUID int64 `json:"uuid" gorm:"-"` // tpl identifier
|
||||
CurEventCount int64 `json:"cur_event_count" gorm:"-"`
|
||||
UpdateByNickname string `json:"update_by_nickname" gorm:"-"` // for fe
|
||||
CronPattern string `json:"cron_pattern"`
|
||||
}
|
||||
|
||||
type ChildVarConfig struct {
|
||||
ParamVal []map[string]ParamQuery `json:"param_val"`
|
||||
ChildVarConfigs *ChildVarConfig `json:"child_var_configs"`
|
||||
}
|
||||
|
||||
type ParamQuery struct {
|
||||
ParamType string `json:"param_type"` // host、device、enum、threshold 三种类型
|
||||
Query interface{} `json:"query"`
|
||||
}
|
||||
|
||||
type VarConfig struct {
|
||||
ParamVal []ParamQueryForFirst `json:"param_val"`
|
||||
ChildVarConfigs *ChildVarConfig `json:"child_var_configs"`
|
||||
}
|
||||
|
||||
// ParamQueryForFirst 同 ParamQuery,仅在第一层出现
|
||||
type ParamQueryForFirst struct {
|
||||
Name string `json:"name"`
|
||||
ParamType string `json:"param_type"`
|
||||
Query interface{} `json:"query"`
|
||||
}
|
||||
|
||||
type Tpl struct {
|
||||
@@ -135,16 +109,15 @@ type Tpl struct {
|
||||
}
|
||||
|
||||
type RuleConfig struct {
|
||||
Version string `json:"version,omitempty"`
|
||||
EventRelabelConfig []*pconf.RelabelConfig `json:"event_relabel_config,omitempty"`
|
||||
TaskTpls []*Tpl `json:"task_tpls,omitempty"`
|
||||
Queries interface{} `json:"queries,omitempty"`
|
||||
Triggers []Trigger `json:"triggers,omitempty"`
|
||||
Inhibit bool `json:"inhibit,omitempty"`
|
||||
PromQl string `json:"prom_ql,omitempty"`
|
||||
Severity int `json:"severity,omitempty"`
|
||||
AlgoParams interface{} `json:"algo_params,omitempty"`
|
||||
OverrideGlobalWebhook bool `json:"override_global_webhook,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
EventRelabelConfig []*pconf.RelabelConfig `json:"event_relabel_config,omitempty"`
|
||||
TaskTpls []*Tpl `json:"task_tpls,omitempty"`
|
||||
Queries interface{} `json:"queries,omitempty"`
|
||||
Triggers []Trigger `json:"triggers,omitempty"`
|
||||
Inhibit bool `json:"inhibit,omitempty"`
|
||||
PromQl string `json:"prom_ql,omitempty"`
|
||||
Severity int `json:"severity,omitempty"`
|
||||
AlgoParams interface{} `json:"algo_params,omitempty"`
|
||||
}
|
||||
|
||||
type PromRuleConfig struct {
|
||||
@@ -177,10 +150,7 @@ type HostRuleConfig struct {
|
||||
type PromQuery struct {
|
||||
PromQl string `json:"prom_ql"`
|
||||
Severity int `json:"severity"`
|
||||
VarEnabled bool `json:"var_enabled"`
|
||||
VarConfig VarConfig `json:"var_config"`
|
||||
RecoverConfig RecoverConfig `json:"recover_config"`
|
||||
Unit string `json:"unit"`
|
||||
}
|
||||
|
||||
type HostTrigger struct {
|
||||
@@ -203,11 +173,11 @@ type Trigger struct {
|
||||
Exp string `json:"exp"`
|
||||
Severity int `json:"severity"`
|
||||
|
||||
Type string `json:"type,omitempty"`
|
||||
Duration int `json:"duration,omitempty"`
|
||||
Percent int `json:"percent,omitempty"`
|
||||
Joins []Join `json:"joins"`
|
||||
JoinRef string `json:"join_ref"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Duration int `json:"duration,omitempty"`
|
||||
Percent int `json:"percent,omitempty"`
|
||||
Joins []Join `json:"joins"`
|
||||
JoinRef string `json:"join_ref"`
|
||||
RecoverConfig RecoverConfig `json:"recover_config"`
|
||||
}
|
||||
|
||||
@@ -217,132 +187,6 @@ type Join struct {
|
||||
On []string `json:"on"`
|
||||
}
|
||||
|
||||
var DataSourceQueryAll = DatasourceQuery{
|
||||
MatchType: 2,
|
||||
Op: "in",
|
||||
Values: []interface{}{DatasourceIdAll},
|
||||
}
|
||||
|
||||
type DatasourceQuery struct {
|
||||
MatchType int `json:"match_type"`
|
||||
Op string `json:"op"`
|
||||
Values []interface{} `json:"values"`
|
||||
}
|
||||
|
||||
// GetDatasourceIDsByDatasourceQueries 从 datasourceQueries 中获取 datasourceIDs
|
||||
// 查询分为精确\模糊匹配,逻辑有 in 与 not in
|
||||
// idMap 为当前 datasourceQueries 对应的数据源全集
|
||||
// nameMap 为所有 datasource 的 name 到 id 的映射,用于名称的模糊匹配
|
||||
func GetDatasourceIDsByDatasourceQueries[T any](datasourceQueries []DatasourceQuery, idMap map[int64]T, nameMap map[string]int64) []int64 {
|
||||
if len(datasourceQueries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 所有 query 取交集,初始集合为全集
|
||||
curIDs := make(map[int64]struct{})
|
||||
for id, _ := range idMap {
|
||||
curIDs[id] = struct{}{}
|
||||
}
|
||||
|
||||
for i := range datasourceQueries {
|
||||
// 每次 query 都在 curIDs 的基础上得到 dsIDs
|
||||
dsIDs := make(map[int64]struct{})
|
||||
q := datasourceQueries[i]
|
||||
if q.MatchType == 0 {
|
||||
// 精确匹配转为 id 匹配
|
||||
idValues := make([]int64, 0, len(q.Values))
|
||||
for v := range q.Values {
|
||||
var val int64
|
||||
switch v := q.Values[v].(type) {
|
||||
case int64:
|
||||
val = v
|
||||
case int:
|
||||
val = int64(v)
|
||||
case float64:
|
||||
val = int64(v)
|
||||
case float32:
|
||||
val = int64(v)
|
||||
case int8:
|
||||
val = int64(v)
|
||||
case int16:
|
||||
val = int64(v)
|
||||
case int32:
|
||||
val = int64(v)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
idValues = append(idValues, int64(val))
|
||||
}
|
||||
|
||||
if q.Op == "in" {
|
||||
if len(idValues) == 1 && idValues[0] == DatasourceIdAll {
|
||||
for id := range curIDs {
|
||||
dsIDs[id] = struct{}{}
|
||||
}
|
||||
} else {
|
||||
for idx := range idValues {
|
||||
if _, exist := curIDs[idValues[idx]]; exist {
|
||||
dsIDs[idValues[idx]] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if q.Op == "not in" {
|
||||
for idx := range idValues {
|
||||
delete(curIDs, idValues[idx])
|
||||
}
|
||||
dsIDs = curIDs
|
||||
}
|
||||
} else if q.MatchType == 1 {
|
||||
// 模糊匹配使用 datasource name
|
||||
if q.Op == "in" {
|
||||
for dsName, dsID := range nameMap {
|
||||
if _, exist := curIDs[dsID]; exist {
|
||||
for idx := range q.Values {
|
||||
if _, ok := q.Values[idx].(string); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if match.Match(dsName, q.Values[idx].(string)) {
|
||||
dsIDs[nameMap[dsName]] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if q.Op == "not in" {
|
||||
for dsName, _ := range nameMap {
|
||||
for idx := range q.Values {
|
||||
if _, ok := q.Values[idx].(string); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if match.Match(dsName, q.Values[idx].(string)) {
|
||||
delete(curIDs, nameMap[dsName])
|
||||
}
|
||||
}
|
||||
}
|
||||
dsIDs = curIDs
|
||||
}
|
||||
} else if q.MatchType == 2 {
|
||||
// 全部数据源
|
||||
for id := range curIDs {
|
||||
dsIDs[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
curIDs = dsIDs
|
||||
if len(curIDs) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
dsIds := make([]int64, 0, len(curIDs))
|
||||
for c := range curIDs {
|
||||
dsIds = append(dsIds, c)
|
||||
}
|
||||
|
||||
return dsIds
|
||||
}
|
||||
|
||||
func GetHostsQuery(queries []HostQuery) []map[string]interface{} {
|
||||
var query []map[string]interface{}
|
||||
for _, q := range queries {
|
||||
@@ -441,9 +285,9 @@ func (ar *AlertRule) Verify() error {
|
||||
return fmt.Errorf("GroupId(%d) invalid", ar.GroupId)
|
||||
}
|
||||
|
||||
//if IsAllDatasource(ar.DatasourceIdsJson) {
|
||||
// ar.DatasourceIdsJson = []int64{0}
|
||||
//}
|
||||
if IsAllDatasource(ar.DatasourceIdsJson) {
|
||||
ar.DatasourceIdsJson = []int64{0}
|
||||
}
|
||||
|
||||
if str.Dangerous(ar.Name) {
|
||||
return errors.New("Name has invalid characters")
|
||||
@@ -497,7 +341,7 @@ func (ar *AlertRule) Add(ctx *ctx.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
exists, err := AlertRuleExists(ctx, 0, ar.GroupId, ar.Name)
|
||||
exists, err := AlertRuleExists(ctx, 0, ar.GroupId, ar.DatasourceIdsJson, ar.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -515,7 +359,7 @@ func (ar *AlertRule) Add(ctx *ctx.Context) error {
|
||||
|
||||
func (ar *AlertRule) Update(ctx *ctx.Context, arf AlertRule) error {
|
||||
if ar.Name != arf.Name {
|
||||
exists, err := AlertRuleExists(ctx, ar.Id, ar.GroupId, arf.Name)
|
||||
exists, err := AlertRuleExists(ctx, ar.Id, ar.GroupId, ar.DatasourceIdsJson, arf.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -664,30 +508,11 @@ func (ar *AlertRule) UpdateFieldsMap(ctx *ctx.Context, fields map[string]interfa
|
||||
return DB(ctx).Model(ar).Updates(fields).Error
|
||||
}
|
||||
|
||||
func (ar *AlertRule) FillDatasourceQueries() error {
|
||||
// 兼容旧逻辑,将 datasourceIds 转换为 datasourceQueries
|
||||
if len(ar.DatasourceQueries) == 0 && len(ar.DatasourceIds) != 0 {
|
||||
datasourceQueries := DatasourceQuery{
|
||||
MatchType: 0,
|
||||
Op: "in",
|
||||
Values: make([]interface{}, 0),
|
||||
}
|
||||
|
||||
var values []int
|
||||
if ar.DatasourceIds != "" {
|
||||
json.Unmarshal([]byte(ar.DatasourceIds), &values)
|
||||
|
||||
}
|
||||
|
||||
for i := range values {
|
||||
if values[i] == 0 {
|
||||
// 0 表示所有数据源
|
||||
datasourceQueries.MatchType = 2
|
||||
break
|
||||
}
|
||||
datasourceQueries.Values = append(datasourceQueries.Values, values[i])
|
||||
}
|
||||
ar.DatasourceQueries = []DatasourceQuery{datasourceQueries}
|
||||
// for v5 rule
|
||||
func (ar *AlertRule) FillDatasourceIds() error {
|
||||
if ar.DatasourceIds != "" {
|
||||
json.Unmarshal([]byte(ar.DatasourceIds), &ar.DatasourceIdsJson)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -806,6 +631,14 @@ func (ar *AlertRule) FE2DB() error {
|
||||
}
|
||||
ar.AlgoParams = string(algoParamsByte)
|
||||
|
||||
if len(ar.DatasourceIdsJson) > 0 {
|
||||
idsByte, err := json.Marshal(ar.DatasourceIdsJson)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal datasource_ids err:%v", err)
|
||||
}
|
||||
ar.DatasourceIds = string(idsByte)
|
||||
}
|
||||
|
||||
if ar.RuleConfigJson == nil {
|
||||
query := PromQuery{
|
||||
PromQl: ar.PromQl,
|
||||
@@ -877,17 +710,8 @@ func (ar *AlertRule) DB2FE() error {
|
||||
json.Unmarshal([]byte(ar.RuleConfig), &ruleConfig)
|
||||
ar.EventRelabelConfig = ruleConfig.EventRelabelConfig
|
||||
|
||||
// 兼容旧逻辑填充 cron_pattern
|
||||
if ar.CronPattern == "" && ar.PromEvalInterval != 0 {
|
||||
ar.CronPattern = fmt.Sprintf("@every %ds", ar.PromEvalInterval)
|
||||
}
|
||||
|
||||
err := ar.FillDatasourceQueries()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
err := ar.FillDatasourceIds()
|
||||
return err
|
||||
}
|
||||
|
||||
func AlertRuleDels(ctx *ctx.Context, ids []int64, bgid ...int64) error {
|
||||
@@ -901,7 +725,7 @@ func AlertRuleDels(ctx *ctx.Context, ids []int64, bgid ...int64) error {
|
||||
return ret.Error
|
||||
}
|
||||
|
||||
// 说明确实删掉了,把相关的活跃告警也删了,这些告警永远都不会恢复了,而且策略都没了,说明没<EFBFBD><EFBFBD><EFBFBD>关心了
|
||||
// 说明确实删掉了,把相关的活跃告警也删了,这些告警永远都不会恢复了,而且策略都没了,说明没人关心了
|
||||
if ret.RowsAffected > 0 {
|
||||
DB(ctx).Where("rule_id = ?", ids[i]).Delete(new(AlertCurEvent))
|
||||
}
|
||||
@@ -910,7 +734,7 @@ func AlertRuleDels(ctx *ctx.Context, ids []int64, bgid ...int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func AlertRuleExists(ctx *ctx.Context, id, groupId int64, name string) (bool, error) {
|
||||
func AlertRuleExists(ctx *ctx.Context, id, groupId int64, datasourceIds []int64, name string) (bool, error) {
|
||||
session := DB(ctx).Where("id <> ? and group_id = ? and name = ?", id, groupId, name)
|
||||
|
||||
var lst []AlertRule
|
||||
@@ -922,6 +746,15 @@ func AlertRuleExists(ctx *ctx.Context, id, groupId int64, name string) (bool, er
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// match cluster
|
||||
for _, r := range lst {
|
||||
r.FillDatasourceIds()
|
||||
for _, id := range r.DatasourceIdsJson {
|
||||
if MatchDatasource(datasourceIds, id) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -1141,6 +974,7 @@ func (ar *AlertRule) UpdateEvent(event *AlertCurEvent) {
|
||||
event.PromForDuration = ar.PromForDuration
|
||||
event.RuleConfig = ar.RuleConfig
|
||||
event.RuleConfigJson = ar.RuleConfigJson
|
||||
event.PromEvalInterval = ar.PromEvalInterval
|
||||
event.Callbacks = ar.Callbacks
|
||||
event.CallbacksJSON = ar.CallbacksJSON
|
||||
event.RunbookUrl = ar.RunbookUrl
|
||||
@@ -1292,7 +1126,3 @@ func InsertAlertRule(ctx *ctx.Context, ars []*AlertRule) error {
|
||||
}
|
||||
return DB(ctx).Create(ars).Error
|
||||
}
|
||||
|
||||
func (ar *AlertRule) Hash() string {
|
||||
return str.MD5(fmt.Sprintf("%d_%s_%s", ar.Id, ar.DatasourceIds, ar.RuleConfig))
|
||||
}
|
||||
|
||||
@@ -114,11 +114,6 @@ func (s *AlertSubscribe) Verify() error {
|
||||
return errors.New("severities is required")
|
||||
}
|
||||
|
||||
if s.UserGroupIds != "" && s.NewChannels == "" {
|
||||
// 如果指定了用户组,那么新告警的通知渠道必须指定,否则容易出现告警规则中没有指定通知渠道,导致订阅通知时,没有通知渠道
|
||||
return errors.New("new_channels is required")
|
||||
}
|
||||
|
||||
ugids := strings.Fields(s.UserGroupIds)
|
||||
for i := 0; i < len(ugids); i++ {
|
||||
if _, err := strconv.ParseInt(ugids[i], 10, 64); err != nil {
|
||||
|
||||
@@ -10,20 +10,9 @@ import (
|
||||
|
||||
// BuiltinComponent represents a builtin component along with its metadata.
|
||||
type BuiltinComponent struct {
|
||||
ID uint64 `json:"id" gorm:"primaryKey;type:bigint;autoIncrement;comment:'unique identifier'"`
|
||||
Ident string `json:"ident" gorm:"type:varchar(191);not null;uniqueIndex:idx_ident,sort:asc"`
|
||||
Logo string `json:"logo" gorm:"type:mediumtext;comment:'logo of component'"`
|
||||
Readme string `json:"readme" gorm:"type:text;not null;comment:'readme of component'"`
|
||||
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:'create time'"`
|
||||
CreatedBy string `json:"created_by" gorm:"type:varchar(191);not null;default:'';comment:'creator'"`
|
||||
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:'update time'"`
|
||||
UpdatedBy string `json:"updated_by" gorm:"type:varchar(191);not null;default:'';comment:'updater'"`
|
||||
}
|
||||
|
||||
type PostgresBuiltinComponent struct {
|
||||
ID uint64 `json:"id" gorm:"primaryKey;type:bigint;autoIncrement;comment:'unique identifier'"`
|
||||
Ident string `json:"ident" gorm:"type:varchar(191);not null;uniqueIndex:idx_ident,sort:asc;comment:'identifier of component'"`
|
||||
Logo string `json:"logo" gorm:"type:text;comment:'logo of component'"`
|
||||
Logo string `json:"logo" gorm:"type:varchar(191);not null;comment:'logo of component'"`
|
||||
Readme string `json:"readme" gorm:"type:text;not null;comment:'readme of component'"`
|
||||
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:'create time'"`
|
||||
CreatedBy string `json:"created_by" gorm:"type:varchar(191);not null;default:'';comment:'creator'"`
|
||||
|
||||
@@ -14,12 +14,12 @@ import (
|
||||
type BuiltinMetric struct {
|
||||
ID int64 `json:"id" gorm:"primaryKey;type:bigint;autoIncrement;comment:'unique identifier'"`
|
||||
UUID int64 `json:"uuid" gorm:"type:bigint;not null;default:0;comment:'uuid'"`
|
||||
Collector string `json:"collector" gorm:"uniqueIndex:idx_collector_typ_name;type:varchar(191);not null;index:idx_collector,sort:asc;comment:'type of collector'"`
|
||||
Typ string `json:"typ" gorm:"uniqueIndex:idx_collector_typ_name;type:varchar(191);not null;index:idx_typ,sort:asc;comment:'type of metric'"`
|
||||
Name string `json:"name" gorm:"uniqueIndex:idx_collector_typ_name;type:varchar(191);not null;index:idx_builtinmetric_name,sort:asc;comment:'name of metric'"`
|
||||
Collector string `json:"collector" gorm:"type:varchar(191);not null;index:idx_collector,sort:asc;comment:'type of collector'"` // Type of collector (e.g., 'categraf', 'telegraf')
|
||||
Typ string `json:"typ" gorm:"type:varchar(191);not null;index:idx_typ,sort:asc;comment:'type of metric'"` // Type of metric (e.g., 'host', 'mysql', 'redis')
|
||||
Name string `json:"name" gorm:"type:varchar(191);not null;index:idx_builtinmetric_name,sort:asc;comment:'name of metric'"`
|
||||
Unit string `json:"unit" gorm:"type:varchar(191);not null;comment:'unit of metric'"`
|
||||
Note string `json:"note" gorm:"type:varchar(4096);not null;comment:'description of metric'"`
|
||||
Lang string `json:"lang" gorm:"uniqueIndex:idx_collector_typ_name;type:varchar(191);not null;default:'zh';index:idx_lang,sort:asc;comment:'language'"`
|
||||
Lang string `json:"lang" gorm:"type:varchar(191);not null;default:'zh';index:idx_lang,sort:asc;comment:'language'"`
|
||||
Expression string `json:"expression" gorm:"type:varchar(4096);not null;comment:'expression of metric'"`
|
||||
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:'create time'"`
|
||||
CreatedBy string `json:"created_by" gorm:"type:varchar(191);not null;default:'';comment:'creator'"`
|
||||
|
||||
@@ -2,9 +2,7 @@ package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -52,7 +50,6 @@ type HTTP struct {
|
||||
TLS TLS `json:"tls"`
|
||||
MaxIdleConnsPerHost int `json:"max_idle_conns_per_host"`
|
||||
Url string `json:"url"`
|
||||
Urls []string `json:"urls"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
}
|
||||
|
||||
@@ -71,49 +68,6 @@ func (h HTTP) IsLoki() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (h HTTP) GetUrls() []string {
|
||||
var urls []string
|
||||
if len(h.Urls) == 0 {
|
||||
urls = []string{h.Url}
|
||||
} else {
|
||||
// 复制切片以避免修改原始数据
|
||||
urls = make([]string, len(h.Urls))
|
||||
copy(urls, h.Urls)
|
||||
}
|
||||
|
||||
// 使用 Fisher-Yates 洗牌算法随机打乱顺序
|
||||
for i := len(urls) - 1; i > 0; i-- {
|
||||
j := rand.Intn(i + 1)
|
||||
urls[i], urls[j] = urls[j], urls[i]
|
||||
}
|
||||
|
||||
return urls
|
||||
}
|
||||
|
||||
func (h HTTP) NewReq(reqUrl *string) (req *http.Request, err error) {
|
||||
urls := h.GetUrls()
|
||||
for i := 0; i < len(urls); i++ {
|
||||
if req, err = http.NewRequest("GET", urls[i], nil); err == nil {
|
||||
*reqUrl = urls[i]
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h HTTP) ParseUrl() (target *url.URL, err error) {
|
||||
urls := h.GetUrls()
|
||||
if len(urls) == 0 {
|
||||
return nil, errors.New("no urls")
|
||||
}
|
||||
|
||||
target, err = url.Parse(urls[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type TLS struct {
|
||||
SkipTlsVerify bool `json:"skip_tls_verify"`
|
||||
}
|
||||
@@ -346,10 +300,6 @@ func (ds *Datasource) DB2FE() error {
|
||||
ds.HTTPJson.MaxIdleConnsPerHost = 100
|
||||
}
|
||||
|
||||
if ds.PluginType == ELASTICSEARCH && len(ds.HTTPJson.Urls) == 0 {
|
||||
ds.HTTPJson.Urls = []string{ds.HTTPJson.Url}
|
||||
}
|
||||
|
||||
if ds.Auth != "" {
|
||||
err := json.Unmarshal([]byte(ds.Auth), &ds.AuthJson)
|
||||
if err != nil {
|
||||
@@ -386,12 +336,12 @@ func DatasourceGetMap(ctx *ctx.Context) (map[int64]*Datasource, error) {
|
||||
}
|
||||
}
|
||||
|
||||
ds := make(map[int64]*Datasource)
|
||||
ret := make(map[int64]*Datasource)
|
||||
for i := 0; i < len(lst); i++ {
|
||||
ds[lst[i].Id] = lst[i]
|
||||
ret[lst[i].Id] = lst[i]
|
||||
}
|
||||
|
||||
return ds, nil
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func DatasourceStatistics(ctx *ctx.Context) (*Statistics, error) {
|
||||
|
||||
@@ -28,7 +28,7 @@ func MigrateIbexTables(db *gorm.DB) {
|
||||
db = db.Set("gorm:table_options", tableOptions)
|
||||
}
|
||||
|
||||
dts := []interface{}{&imodels.TaskMeta{}, &imodels.TaskScheduler{}, &TaskHostDoing{}, &imodels.TaskAction{}}
|
||||
dts := []interface{}{&imodels.TaskMeta{}, &imodels.TaskScheduler{}, &imodels.TaskSchedulerHealth{}, &TaskHostDoing{}, &imodels.TaskAction{}}
|
||||
for _, dt := range dts {
|
||||
err := db.AutoMigrate(dt)
|
||||
if err != nil {
|
||||
@@ -38,22 +38,13 @@ func MigrateIbexTables(db *gorm.DB) {
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
tableName := fmt.Sprintf("task_host_%d", i)
|
||||
exists := db.Migrator().HasTable(tableName)
|
||||
if exists {
|
||||
continue
|
||||
} else {
|
||||
err := db.Table(tableName).AutoMigrate(&imodels.TaskHost{})
|
||||
if err != nil {
|
||||
logger.Errorf("failed to migrate table:%s %v", tableName, err)
|
||||
}
|
||||
err := db.Table(tableName).AutoMigrate(&imodels.TaskHost{})
|
||||
if err != nil {
|
||||
logger.Errorf("failed to migrate table:%s %v", tableName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isPostgres(db *gorm.DB) bool {
|
||||
dialect := db.Dialector.Name()
|
||||
return dialect == "postgres"
|
||||
}
|
||||
func MigrateTables(db *gorm.DB) error {
|
||||
var tableOptions string
|
||||
switch db.Dialector.(type) {
|
||||
@@ -63,22 +54,13 @@ func MigrateTables(db *gorm.DB) error {
|
||||
if tableOptions != "" {
|
||||
db = db.Set("gorm:table_options", tableOptions)
|
||||
}
|
||||
|
||||
dts := []interface{}{&RecordingRule{}, &AlertRule{}, &AlertSubscribe{}, &AlertMute{},
|
||||
&TaskRecord{}, &ChartShare{}, &Target{}, &Configs{}, &Datasource{}, &NotifyTpl{},
|
||||
&Board{}, &BoardBusigroup{}, &Users{}, &SsoConfig{}, &models.BuiltinMetric{},
|
||||
&models.MetricFilter{}, &models.NotificaitonRecord{},
|
||||
&models.MetricFilter{}, &models.BuiltinComponent{}, &models.NotificaitonRecord{},
|
||||
&models.TargetBusiGroup{}}
|
||||
|
||||
if isPostgres(db) {
|
||||
dts = append(dts, &models.PostgresBuiltinComponent{})
|
||||
} else {
|
||||
dts = append(dts, &models.BuiltinComponent{})
|
||||
}
|
||||
|
||||
if !db.Migrator().HasColumn(&imodels.TaskSchedulerHealth{}, "scheduler") {
|
||||
dts = append(dts, &imodels.TaskSchedulerHealth{})
|
||||
}
|
||||
|
||||
if !columnHasIndex(db, &AlertHisEvent{}, "original_tags") ||
|
||||
!columnHasIndex(db, &AlertCurEvent{}, "original_tags") {
|
||||
asyncDts := []interface{}{&AlertHisEvent{}, &AlertCurEvent{}}
|
||||
@@ -204,17 +186,15 @@ func InsertPermPoints(db *gorm.DB) {
|
||||
}
|
||||
|
||||
type AlertRule struct {
|
||||
ExtraConfig string `gorm:"type:text;column:extra_config"`
|
||||
CronPattern string `gorm:"type:varchar(64);column:cron_pattern"`
|
||||
DatasourceQueries []models.DatasourceQuery `gorm:"datasource_queries;type:text;serializer:json"` // datasource queries
|
||||
ExtraConfig string `gorm:"type:text;column:extra_config"` // extra config
|
||||
}
|
||||
|
||||
type AlertSubscribe struct {
|
||||
ExtraConfig string `gorm:"type:text;column:extra_config"` // extra config
|
||||
Severities string `gorm:"column:severities;type:varchar(32);not null;default:''"`
|
||||
BusiGroups ormx.JSONArr `gorm:"column:busi_groups;type:varchar(4096)"`
|
||||
BusiGroups ormx.JSONArr `gorm:"column:busi_groups;type:varchar(4096);not null;default:'[]'"`
|
||||
Note string `gorm:"column:note;type:varchar(1024);default:'';comment:note"`
|
||||
RuleIds []int64 `gorm:"column:rule_ids;type:varchar(1024)"`
|
||||
RuleIds []int64 `gorm:"column:rule_ids;type:varchar(1024);default:'';comment:rule_ids"`
|
||||
}
|
||||
|
||||
type AlertMute struct {
|
||||
@@ -223,10 +203,9 @@ type AlertMute struct {
|
||||
}
|
||||
|
||||
type RecordingRule struct {
|
||||
QueryConfigs string `gorm:"type:text;not null;column:query_configs"` // query_configs
|
||||
DatasourceIds string `gorm:"column:datasource_ids;type:varchar(255);default:'';comment:datasource ids"`
|
||||
CronPattern string `gorm:"column:cron_pattern;type:varchar(255);default:'';comment:cron pattern"`
|
||||
DatasourceQueries []models.DatasourceQuery `json:"datasource_queries" gorm:"datasource_queries;type:text;serializer:json"` // datasource queries
|
||||
QueryConfigs string `gorm:"type:text;not null;column:query_configs"` // query_configs
|
||||
DatasourceIds string `gorm:"column:datasource_ids;type:varchar(255);default:'';comment:datasource ids"`
|
||||
CronPattern string `gorm:"column:cron_pattern;type:varchar(255);default:'';comment:cron pattern"`
|
||||
}
|
||||
|
||||
type AlertingEngines struct {
|
||||
|
||||
@@ -283,7 +283,7 @@ var TplMap = map[string]string{
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
{{$domain := "http://请联系管理员修改通知模板将域名替换为实际的域名" }}
|
||||
[事件详情]({{$domain}}/alert-his-events/{{.Id}})|[屏蔽1小时]({{$domain}}/alert-mutes/add?busiGroup={{.GroupId}}&cate={{.Cate}}&datasource_ids={{.DatasourceId}}&prod={{.RuleProd}}{{range $key, $value := .TagsMap}}&tags={{$key}}%3D{{$value}}{{end}})|[查看曲线]({{$domain}}/metric/explorer?data_source_id={{.DatasourceId}}&data_source_name=prometheus&mode=graph&prom_ql={{.PromQl|escape}})`,
|
||||
[事件详情]({{$domain}}/alert-his-events/{{.Id}})|[屏蔽1小时]({{$domain}}/alert-mutes/add?busiGroup={{.GroupId}}&cate={{.Cate}}&datasource_ids={{.DatasourceId}}&prod={{.RuleProd}}{{range $key, $value := .TagsMap}}&tags={{$key}}%3D{{$value}}{{end}})|[查看曲线]({{$domain}}/metric/explorer?data_source_id={{.DatasourceId}}&data_source_name=prometheus&mode=graph&prom_ql={{.PromQl}})`,
|
||||
Email: `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -529,7 +529,7 @@ var TplMap = map[string]string{
|
||||
{{if .RuleNote }}**告警描述:** **{{.RuleNote}}**{{end}}
|
||||
{{- end -}}
|
||||
{{$domain := "http://请联系管理员修改通知模板将域名替换为实际的域名" }}
|
||||
[事件详情]({{$domain}}/alert-his-events/{{.Id}})|[屏蔽1小时]({{$domain}}/alert-mutes/add?busiGroup={{.GroupId}}&cate={{.Cate}}&datasource_ids={{.DatasourceId}}&prod={{.RuleProd}}{{range $key, $value := .TagsMap}}&tags={{$key}}%3D{{$value}}{{end}})|[查看曲线]({{$domain}}/metric/explorer?data_source_id={{.DatasourceId}}&data_source_name=prometheus&mode=graph&prom_ql={{.PromQl|escape}})`,
|
||||
[事件详情]({{$domain}}/alert-his-events/{{.Id}})|[屏蔽1小时]({{$domain}}/alert-mutes/add?busiGroup={{.GroupId}}&cate={{.Cate}}&datasource_ids={{.DatasourceId}}&prod={{.RuleProd}}{{range $key, $value := .TagsMap}}&tags={{$key}}%3D{{$value}}{{end}})|[查看曲线]({{$domain}}/metric/explorer?data_source_id={{.DatasourceId}}&data_source_name=prometheus&mode=graph&prom_ql={{.PromQl}})`,
|
||||
EmailSubject: `{{if .IsRecovered}}Recovered{{else}}Triggered{{end}}: {{.RuleName}} {{.TagsJSON}}`,
|
||||
Mm: `级别状态: S{{.Severity}} {{if .IsRecovered}}Recovered{{else}}Triggered{{end}}
|
||||
规则名称: {{.RuleName}}{{if .RuleNote}}
|
||||
@@ -557,7 +557,7 @@ var TplMap = map[string]string{
|
||||
{{$time_duration := sub now.Unix .FirstTriggerTime }}{{if .IsRecovered}}{{$time_duration = sub .LastEvalTime .FirstTriggerTime }}{{end}}**距离首次告警**: {{humanizeDurationInterface $time_duration}}
|
||||
**发送时间**: {{timestamp}}
|
||||
{{$domain := "http://请联系管理员修改通知模板将域名替换为实际的域名" }}
|
||||
[事件详情]({{$domain}}/alert-his-events/{{.Id}})|[屏蔽1小时]({{$domain}}/alert-mutes/add?busiGroup={{.GroupId}}&cate={{.Cate}}&datasource_ids={{.DatasourceId}}&prod={{.RuleProd}}{{range $key, $value := .TagsMap}}&tags={{$key}}%3D{{$value}}{{end}})|[查看曲线]({{$domain}}/metric/explorer?data_source_id={{.DatasourceId}}&data_source_name=prometheus&mode=graph&prom_ql={{.PromQl|escape}})`,
|
||||
[事件详情]({{$domain}}/alert-his-events/{{.Id}})|[屏蔽1小时]({{$domain}}/alert-mutes/add?busiGroup={{.GroupId}}&cate={{.Cate}}&datasource_ids={{.DatasourceId}}&prod={{.RuleProd}}{{range $key, $value := .TagsMap}}&tags={{$key}}%3D{{$value}}{{end}})|[查看曲线]({{$domain}}/metric/explorer?data_source_id={{.DatasourceId}}&data_source_name=prometheus&mode=graph&prom_ql={{.PromQl}})`,
|
||||
Lark: `级别状态: S{{.Severity}} {{if .IsRecovered}}Recovered{{else}}Triggered{{end}}
|
||||
规则名称: {{.RuleName}}{{if .RuleNote}}
|
||||
规则备注: {{.RuleNote}}{{end}}
|
||||
@@ -588,5 +588,5 @@ var TplMap = map[string]string{
|
||||
{{if .RuleNote }}**告警描述:** **{{.RuleNote}}**{{end}}
|
||||
{{- end -}}
|
||||
{{$domain := "http://请联系管理员修改通知模板将域名替换为实际的域名" }}
|
||||
[事件详情]({{$domain}}/alert-his-events/{{.Id}})|[屏蔽1小时]({{$domain}}/alert-mutes/add?busiGroup={{.GroupId}}&cate={{.Cate}}&datasource_ids={{.DatasourceId}}&prod={{.RuleProd}}{{range $key, $value := .TagsMap}}&tags={{$key}}%3D{{$value}}{{end}})|[查看曲线]({{$domain}}/metric/explorer?data_source_id={{.DatasourceId}}&data_source_name=prometheus&mode=graph&prom_ql={{.PromQl|escape}})`,
|
||||
[事件详情]({{$domain}}/alert-his-events/{{.Id}})|[屏蔽1小时]({{$domain}}/alert-mutes/add?busiGroup={{.GroupId}}&cate={{.Cate}}&datasource_ids={{.DatasourceId}}&prod={{.RuleProd}}{{range $key, $value := .TagsMap}}&tags={{$key}}%3D{{$value}}{{end}})|[查看曲线]({{$domain}}/metric/explorer?data_source_id={{.DatasourceId}}&data_source_name=prometheus&mode=graph&prom_ql={{.PromQl}})`,
|
||||
}
|
||||
|
||||
@@ -31,12 +31,11 @@ func convertInterval(interval string) int {
|
||||
return int(duration.Seconds())
|
||||
}
|
||||
|
||||
func ConvertAlert(rule PromRule, interval string, datasouceQueries []DatasourceQuery, disabled int) AlertRule {
|
||||
func ConvertAlert(rule PromRule, interval string, datasouceIds []int64, disabled int) AlertRule {
|
||||
annotations := rule.Annotations
|
||||
appendTags := []string{}
|
||||
severity := 2
|
||||
|
||||
ruleName := rule.Alert
|
||||
if len(rule.Labels) > 0 {
|
||||
for k, v := range rule.Labels {
|
||||
if k != "severity" {
|
||||
@@ -50,36 +49,33 @@ func ConvertAlert(rule PromRule, interval string, datasouceQueries []DatasourceQ
|
||||
case "info":
|
||||
severity = 3
|
||||
}
|
||||
ruleName += "-" + v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ar := AlertRule{
|
||||
Name: rule.Alert,
|
||||
Severity: severity,
|
||||
Disabled: disabled,
|
||||
PromForDuration: convertInterval(rule.For),
|
||||
PromQl: rule.Expr,
|
||||
PromEvalInterval: convertInterval(interval),
|
||||
EnableStimeJSON: "00:00",
|
||||
EnableEtimeJSON: "23:59",
|
||||
return AlertRule{
|
||||
Name: rule.Alert,
|
||||
Severity: severity,
|
||||
DatasourceIdsJson: datasouceIds,
|
||||
Disabled: disabled,
|
||||
PromForDuration: convertInterval(rule.For),
|
||||
PromQl: rule.Expr,
|
||||
PromEvalInterval: convertInterval(interval),
|
||||
EnableStimeJSON: "00:00",
|
||||
EnableEtimeJSON: "23:59",
|
||||
EnableDaysOfWeekJSON: []string{
|
||||
"1", "2", "3", "4", "5", "6", "0",
|
||||
},
|
||||
EnableInBG: AlertRuleEnableInGlobalBG,
|
||||
NotifyRecovered: AlertRuleNotifyRecovered,
|
||||
NotifyRepeatStep: AlertRuleNotifyRepeatStep60Min,
|
||||
RecoverDuration: AlertRuleRecoverDuration0Sec,
|
||||
AnnotationsJSON: annotations,
|
||||
AppendTagsJSON: appendTags,
|
||||
DatasourceQueries: datasouceQueries,
|
||||
EnableInBG: AlertRuleEnableInGlobalBG,
|
||||
NotifyRecovered: AlertRuleNotifyRecovered,
|
||||
NotifyRepeatStep: AlertRuleNotifyRepeatStep60Min,
|
||||
RecoverDuration: AlertRuleRecoverDuration0Sec,
|
||||
AnnotationsJSON: annotations,
|
||||
AppendTagsJSON: appendTags,
|
||||
}
|
||||
|
||||
return ar
|
||||
}
|
||||
|
||||
func DealPromGroup(promRule []PromRuleGroup, dataSourceQueries []DatasourceQuery, disabled int) []AlertRule {
|
||||
func DealPromGroup(promRule []PromRuleGroup, dataSourceIds []int64, disabled int) []AlertRule {
|
||||
var alertRules []AlertRule
|
||||
|
||||
for _, group := range promRule {
|
||||
@@ -90,7 +86,7 @@ func DealPromGroup(promRule []PromRuleGroup, dataSourceQueries []DatasourceQuery
|
||||
for _, rule := range group.Rules {
|
||||
if rule.Alert != "" {
|
||||
alertRules = append(alertRules,
|
||||
ConvertAlert(rule, interval, dataSourceQueries, disabled))
|
||||
ConvertAlert(rule, interval, dataSourceIds, disabled))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestConvertAlert(t *testing.T) {
|
||||
t.Errorf("Failed to Unmarshal, err: %s", err)
|
||||
}
|
||||
t.Logf("jobMissing: %+v", jobMissing[0])
|
||||
convJobMissing := models.ConvertAlert(jobMissing[0], "30s", []models.DatasourceQuery{}, 0)
|
||||
convJobMissing := models.ConvertAlert(jobMissing[0], "30s", []int64{1}, 0)
|
||||
if convJobMissing.PromEvalInterval != 30 {
|
||||
t.Errorf("PromEvalInterval is expected to be 30, but got %d",
|
||||
convJobMissing.PromEvalInterval)
|
||||
@@ -45,7 +45,7 @@ func TestConvertAlert(t *testing.T) {
|
||||
description: "Prometheus rule evaluation took more time than the scheduled interval. It indicates a slower storage backend access or too complex query.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
|
||||
`), &ruleEvaluationSlow)
|
||||
t.Logf("ruleEvaluationSlow: %+v", ruleEvaluationSlow[0])
|
||||
convRuleEvaluationSlow := models.ConvertAlert(ruleEvaluationSlow[0], "1m", []models.DatasourceQuery{}, 0)
|
||||
convRuleEvaluationSlow := models.ConvertAlert(ruleEvaluationSlow[0], "1m", []int64{1}, 0)
|
||||
if convRuleEvaluationSlow.PromEvalInterval != 60 {
|
||||
t.Errorf("PromEvalInterval is expected to be 60, but got %d",
|
||||
convJobMissing.PromEvalInterval)
|
||||
@@ -69,7 +69,7 @@ func TestConvertAlert(t *testing.T) {
|
||||
description: "A Prometheus target has disappeared. An exporter might be crashed.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
|
||||
`), &targetMissing)
|
||||
t.Logf("targetMissing: %+v", targetMissing[0])
|
||||
convTargetMissing := models.ConvertAlert(targetMissing[0], "1h", []models.DatasourceQuery{}, 0)
|
||||
convTargetMissing := models.ConvertAlert(targetMissing[0], "1h", []int64{1}, 0)
|
||||
if convTargetMissing.PromEvalInterval != 3600 {
|
||||
t.Errorf("PromEvalInterval is expected to be 3600, but got %d",
|
||||
convTargetMissing.PromEvalInterval)
|
||||
|
||||
@@ -16,25 +16,25 @@ import (
|
||||
|
||||
// A RecordingRule records its vector expression into new timeseries.
|
||||
type RecordingRule struct {
|
||||
Id int64 `json:"id" gorm:"primaryKey"`
|
||||
GroupId int64 `json:"group_id"` // busi group id
|
||||
DatasourceIds string `json:"-" gorm:"datasource_ids,omitempty"` // datasource ids
|
||||
DatasourceQueries []DatasourceQuery `json:"datasource_queries,omitempty" gorm:"datasource_queries;type:text;serializer:json"` // datasource queries
|
||||
Cluster string `json:"cluster"` // take effect by cluster, seperated by space
|
||||
Name string `json:"name"` // new metric name
|
||||
Disabled int `json:"disabled"` // 0: enabled, 1: disabled
|
||||
PromQl string `json:"prom_ql"` // just one ql for promql
|
||||
QueryConfigs string `json:"-" gorm:"query_configs"` // query_configs
|
||||
QueryConfigsJson []QueryConfig `json:"query_configs" gorm:"-"` // query_configs for fe
|
||||
PromEvalInterval int `json:"prom_eval_interval"` // unit:s
|
||||
CronPattern string `json:"cron_pattern"`
|
||||
AppendTags string `json:"-"` // split by space: service=n9e mod=api
|
||||
AppendTagsJSON []string `json:"append_tags" gorm:"-"` // for fe
|
||||
Note string `json:"note"` // note
|
||||
CreateAt int64 `json:"create_at"`
|
||||
CreateBy string `json:"create_by"`
|
||||
UpdateAt int64 `json:"update_at"`
|
||||
UpdateBy string `json:"update_by"`
|
||||
Id int64 `json:"id" gorm:"primaryKey"`
|
||||
GroupId int64 `json:"group_id"` // busi group id
|
||||
DatasourceIds string `json:"-" gorm:"datasource_ids"` // datasource ids
|
||||
DatasourceIdsJson []int64 `json:"datasource_ids" gorm:"-"` // for fe
|
||||
Cluster string `json:"cluster"` // take effect by cluster, seperated by space
|
||||
Name string `json:"name"` // new metric name
|
||||
Disabled int `json:"disabled"` // 0: enabled, 1: disabled
|
||||
PromQl string `json:"prom_ql"` // just one ql for promql
|
||||
QueryConfigs string `json:"-" gorm:"query_configs"` // query_configs
|
||||
QueryConfigsJson []QueryConfig `json:"query_configs" gorm:"-"` // query_configs for fe
|
||||
PromEvalInterval int `json:"prom_eval_interval"` // unit:s
|
||||
CronPattern string `json:"cron_pattern"`
|
||||
AppendTags string `json:"-"` // split by space: service=n9e mod=api
|
||||
AppendTagsJSON []string `json:"append_tags" gorm:"-"` // for fe
|
||||
Note string `json:"note"` // note
|
||||
CreateAt int64 `json:"create_at"`
|
||||
CreateBy string `json:"create_by"`
|
||||
UpdateAt int64 `json:"update_at"`
|
||||
UpdateBy string `json:"update_by"`
|
||||
}
|
||||
|
||||
type QueryConfig struct {
|
||||
@@ -46,10 +46,9 @@ type QueryConfig struct {
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
DatasourceIds []int64 `json:"datasource_ids"`
|
||||
DatasourceQueries []DatasourceQuery `json:"datasource_queries"`
|
||||
Cate string `json:"cate"`
|
||||
Config interface{} `json:"config"`
|
||||
DatasourceIds []int64 `json:"datasource_ids"`
|
||||
Cate string `json:"cate"`
|
||||
Config interface{} `json:"config"`
|
||||
}
|
||||
|
||||
func (re *RecordingRule) TableName() string {
|
||||
@@ -58,6 +57,8 @@ func (re *RecordingRule) TableName() string {
|
||||
|
||||
func (re *RecordingRule) FE2DB() {
|
||||
re.AppendTags = strings.Join(re.AppendTagsJSON, " ")
|
||||
idsByte, _ := json.Marshal(re.DatasourceIdsJson)
|
||||
re.DatasourceIds = string(idsByte)
|
||||
|
||||
queryConfigsByte, _ := json.Marshal(re.QueryConfigsJson)
|
||||
re.QueryConfigs = string(queryConfigsByte)
|
||||
@@ -65,28 +66,9 @@ func (re *RecordingRule) FE2DB() {
|
||||
|
||||
func (re *RecordingRule) DB2FE() error {
|
||||
re.AppendTagsJSON = strings.Fields(re.AppendTags)
|
||||
|
||||
re.FillDatasourceQueries()
|
||||
json.Unmarshal([]byte(re.DatasourceIds), &re.DatasourceIdsJson)
|
||||
|
||||
json.Unmarshal([]byte(re.QueryConfigs), &re.QueryConfigsJson)
|
||||
// 存量数据规则不包含 DatasourceQueries 字段,将 DatasourceIds 转换为 DatasourceQueries 字段
|
||||
for i := range re.QueryConfigsJson {
|
||||
for j := range re.QueryConfigsJson[i].Queries {
|
||||
if len(re.QueryConfigsJson[i].Queries[j].DatasourceQueries) == 0 {
|
||||
values := make([]interface{}, 0, len(re.QueryConfigsJson[i].Queries[j].DatasourceIds))
|
||||
for _, dsID := range re.QueryConfigsJson[i].Queries[j].DatasourceIds {
|
||||
values = append(values, dsID)
|
||||
}
|
||||
re.QueryConfigsJson[i].Queries[j].DatasourceQueries = []DatasourceQuery{
|
||||
{
|
||||
MatchType: 0,
|
||||
Op: "in",
|
||||
Values: values,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if re.CronPattern == "" && re.PromEvalInterval != 0 {
|
||||
re.CronPattern = fmt.Sprintf("@every %ds", re.PromEvalInterval)
|
||||
@@ -95,42 +77,14 @@ func (re *RecordingRule) DB2FE() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (re *RecordingRule) FillDatasourceQueries() error {
|
||||
// 兼容旧逻辑,将 datasourceIds 转换为 datasourceQueries
|
||||
if len(re.DatasourceQueries) == 0 && len(re.DatasourceIds) != 0 {
|
||||
datasourceQueries := DatasourceQuery{
|
||||
MatchType: 0,
|
||||
Op: "in",
|
||||
Values: make([]interface{}, 0),
|
||||
}
|
||||
|
||||
var values []int64
|
||||
if re.DatasourceIds != "" {
|
||||
json.Unmarshal([]byte(re.DatasourceIds), &values)
|
||||
}
|
||||
|
||||
for i := range values {
|
||||
if values[i] == 0 {
|
||||
// 0 表示所有数据源
|
||||
datasourceQueries.MatchType = 2
|
||||
break
|
||||
}
|
||||
datasourceQueries.Values = append(datasourceQueries.Values, values[i])
|
||||
}
|
||||
|
||||
re.DatasourceQueries = []DatasourceQuery{datasourceQueries}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (re *RecordingRule) Verify() error {
|
||||
if re.GroupId < 0 {
|
||||
return fmt.Errorf("GroupId(%d) invalid", re.GroupId)
|
||||
}
|
||||
|
||||
//if IsAllDatasource(re.DatasourceIdsJson) {
|
||||
// re.DatasourceIdsJson = []int64{0}
|
||||
//}
|
||||
if IsAllDatasource(re.DatasourceIdsJson) {
|
||||
re.DatasourceIdsJson = []int64{0}
|
||||
}
|
||||
|
||||
if re.PromQl != "" && !model.MetricNameRE.MatchString(re.Name) {
|
||||
return errors.New("Name has invalid chreacters")
|
||||
|
||||
@@ -34,16 +34,15 @@ type Target struct {
|
||||
OS string `json:"os" gorm:"column:os"`
|
||||
HostTags []string `json:"host_tags" gorm:"serializer:json"`
|
||||
|
||||
UnixTime int64 `json:"unixtime" gorm:"-"`
|
||||
Offset int64 `json:"offset" gorm:"-"`
|
||||
TargetUp float64 `json:"target_up" gorm:"-"`
|
||||
MemUtil float64 `json:"mem_util" gorm:"-"`
|
||||
CpuNum int `json:"cpu_num" gorm:"-"`
|
||||
CpuUtil float64 `json:"cpu_util" gorm:"-"`
|
||||
Arch string `json:"arch" gorm:"-"`
|
||||
RemoteAddr string `json:"remote_addr" gorm:"-"`
|
||||
GroupIds []int64 `json:"group_ids" gorm:"-"`
|
||||
GroupNames []string `json:"group_names" gorm:"-"`
|
||||
UnixTime int64 `json:"unixtime" gorm:"-"`
|
||||
Offset int64 `json:"offset" gorm:"-"`
|
||||
TargetUp float64 `json:"target_up" gorm:"-"`
|
||||
MemUtil float64 `json:"mem_util" gorm:"-"`
|
||||
CpuNum int `json:"cpu_num" gorm:"-"`
|
||||
CpuUtil float64 `json:"cpu_util" gorm:"-"`
|
||||
Arch string `json:"arch" gorm:"-"`
|
||||
RemoteAddr string `json:"remote_addr" gorm:"-"`
|
||||
GroupIds []int64 `json:"group_ids" gorm:"-"`
|
||||
}
|
||||
|
||||
func (t *Target) TableName() string {
|
||||
@@ -409,8 +408,7 @@ func TargetsGetIdentsByIdentsAndHostIps(ctx *ctx.Context, idents, hostIps []stri
|
||||
return inexistence, identSet.ToSlice(), nil
|
||||
}
|
||||
|
||||
func TargetGetTags(ctx *ctx.Context, idents []string, ignoreHostTag bool, bgLabelKey string) (
|
||||
[]string, error) {
|
||||
func TargetGetTags(ctx *ctx.Context, idents []string, ignoreHostTag bool) ([]string, error) {
|
||||
session := DB(ctx).Model(new(Target))
|
||||
|
||||
var arr []*Target
|
||||
@@ -448,22 +446,7 @@ func TargetGetTags(ctx *ctx.Context, idents []string, ignoreHostTag bool, bgLabe
|
||||
ret = append(ret, key)
|
||||
}
|
||||
|
||||
if bgLabelKey != "" {
|
||||
sort.Slice(ret, func(i, j int) bool {
|
||||
if strings.HasPrefix(ret[i], bgLabelKey) && strings.HasPrefix(ret[j], bgLabelKey) {
|
||||
return ret[i] < ret[j]
|
||||
}
|
||||
if strings.HasPrefix(ret[i], bgLabelKey) {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(ret[j], bgLabelKey) {
|
||||
return false
|
||||
}
|
||||
return ret[i] < ret[j]
|
||||
})
|
||||
} else {
|
||||
sort.Strings(ret)
|
||||
}
|
||||
sort.Strings(ret)
|
||||
|
||||
return ret, err
|
||||
}
|
||||
@@ -580,7 +563,6 @@ func (m *Target) UpdateFieldsMap(ctx *ctx.Context, fields map[string]interface{}
|
||||
return DB(ctx).Model(m).Updates(fields).Error
|
||||
}
|
||||
|
||||
// 1. 是否可以进行 busi_group 迁移
|
||||
func CanMigrateBg(ctx *ctx.Context) bool {
|
||||
// 1.1 检查 target 表是否为空
|
||||
var cnt int64
|
||||
@@ -601,13 +583,25 @@ func CanMigrateBg(ctx *ctx.Context) bool {
|
||||
}
|
||||
|
||||
if maxGroupId == 0 {
|
||||
log.Println("migration bgid has been completed.")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func MigrateBg(ctx *ctx.Context, bgLabelKey string) {
|
||||
// 1. 判断是否已经完成迁移
|
||||
var maxGroupId int64
|
||||
if err := DB(ctx).Model(&Target{}).Select("MAX(group_id)").Scan(&maxGroupId).Error; err != nil {
|
||||
log.Println("failed to get max group_id from target table, err:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if maxGroupId == 0 {
|
||||
log.Println("migration bgid has been completed.")
|
||||
return
|
||||
}
|
||||
|
||||
err := DoMigrateBg(ctx, bgLabelKey)
|
||||
if err != nil {
|
||||
log.Println("failed to migrate bgid, err:", err)
|
||||
@@ -642,7 +636,7 @@ func DoMigrateBg(ctx *ctx.Context, bgLabelKey string) error {
|
||||
}
|
||||
err := DB(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// 4.1 将 group_id 迁移至关联表
|
||||
if err := TargetBindBgids(ctx, []string{t.Ident}, []int64{t.GroupId}, nil); err != nil {
|
||||
if err := TargetBindBgids(ctx, []string{t.Ident}, []int64{t.GroupId}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := TargetUpdateBgid(ctx, []string{t.Ident}, 0, false); err != nil {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
@@ -21,10 +20,6 @@ func (t *TargetBusiGroup) TableName() string {
|
||||
return "target_busi_group"
|
||||
}
|
||||
|
||||
func (t *TargetBusiGroup) TableOptions() string {
|
||||
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"
|
||||
}
|
||||
|
||||
func TargetBusiGroupsGetAll(ctx *ctx.Context) (map[string][]int64, error) {
|
||||
var lst []*TargetBusiGroup
|
||||
err := DB(ctx).Find(&lst).Error
|
||||
@@ -65,7 +60,7 @@ func TargetGroupIdsGetByIdents(ctx *ctx.Context, idents []string) ([]int64, erro
|
||||
return groupIds, nil
|
||||
}
|
||||
|
||||
func TargetBindBgids(ctx *ctx.Context, idents []string, bgids []int64, tags []string) error {
|
||||
func TargetBindBgids(ctx *ctx.Context, idents []string, bgids []int64) error {
|
||||
lst := make([]TargetBusiGroup, 0, len(bgids)*len(idents))
|
||||
updateAt := time.Now().Unix()
|
||||
for _, bgid := range bgids {
|
||||
@@ -78,6 +73,7 @@ func TargetBindBgids(ctx *ctx.Context, idents []string, bgids []int64, tags []st
|
||||
lst = append(lst, cur)
|
||||
}
|
||||
}
|
||||
|
||||
var cl clause.Expression = clause.Insert{Modifier: "ignore"}
|
||||
switch DB(ctx).Dialector.Name() {
|
||||
case "sqlite":
|
||||
@@ -85,23 +81,7 @@ func TargetBindBgids(ctx *ctx.Context, idents []string, bgids []int64, tags []st
|
||||
case "postgres":
|
||||
cl = clause.OnConflict{DoNothing: true}
|
||||
}
|
||||
|
||||
return DB(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := DB(ctx).Clauses(cl).CreateInBatches(&lst, 10).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if targets, err := TargetsGetByIdents(ctx, idents); err != nil {
|
||||
return err
|
||||
} else if len(tags) > 0 {
|
||||
for _, t := range targets {
|
||||
if err := t.AddTags(ctx, tags); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return DB(ctx).Clauses(cl).CreateInBatches(&lst, 10).Error
|
||||
}
|
||||
|
||||
func TargetUnbindBgids(ctx *ctx.Context, idents []string, bgids []int64) error {
|
||||
@@ -113,7 +93,7 @@ func TargetDeleteBgids(ctx *ctx.Context, idents []string) error {
|
||||
return DB(ctx).Where("target_ident in ?", idents).Delete(&TargetBusiGroup{}).Error
|
||||
}
|
||||
|
||||
func TargetOverrideBgids(ctx *ctx.Context, idents []string, bgids []int64, tags []string) error {
|
||||
func TargetOverrideBgids(ctx *ctx.Context, idents []string, bgids []int64) error {
|
||||
return DB(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// 先删除旧的关联
|
||||
if err := tx.Where("target_ident IN ?", idents).Delete(&TargetBusiGroup{}).Error; err != nil {
|
||||
@@ -146,15 +126,7 @@ func TargetOverrideBgids(ctx *ctx.Context, idents []string, bgids []int64, tags
|
||||
case "postgres":
|
||||
cl = clause.OnConflict{DoNothing: true}
|
||||
}
|
||||
if err := tx.Clauses(cl).CreateInBatches(&lst, 10).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(tags) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return tx.Model(Target{}).Where("ident IN ?", idents).Updates(map[string]interface{}{
|
||||
"tags": strings.Join(tags, " ") + " ", "update_at": updateAt}).Error
|
||||
return tx.Clauses(cl).CreateInBatches(&lst, 10).Error
|
||||
})
|
||||
}
|
||||
|
||||
@@ -184,13 +156,3 @@ func SeparateTargetIdents(ctx *ctx.Context, idents []string) (existing, nonExist
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func TargetIndentsGetByBgids(ctx *ctx.Context, bgids []int64) ([]string, error) {
|
||||
var idents []string
|
||||
err := DB(ctx).Model(&TargetBusiGroup{}).
|
||||
Where("group_id IN ?", bgids).
|
||||
Distinct("target_ident").
|
||||
Pluck("target_ident", &idents).
|
||||
Error
|
||||
return idents, err
|
||||
}
|
||||
|
||||
@@ -348,7 +348,7 @@ func UsersGetByGroupIds(ctx *ctx.Context, groupIds []int64) ([]User, error) {
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func InitRoot(ctx *ctx.Context) bool {
|
||||
func InitRoot(ctx *ctx.Context) {
|
||||
user, err := UserGetByUsername(ctx, "root")
|
||||
if err != nil {
|
||||
fmt.Println("failed to query user root:", err)
|
||||
@@ -356,12 +356,12 @@ func InitRoot(ctx *ctx.Context) bool {
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
if len(user.Password) > 31 {
|
||||
// already done before
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
newPass, err := CryptoPass(ctx, user.Password)
|
||||
@@ -377,7 +377,6 @@ func InitRoot(ctx *ctx.Context) bool {
|
||||
}
|
||||
|
||||
fmt.Println("root password init done")
|
||||
return true
|
||||
}
|
||||
|
||||
func reachLoginFailCount(ctx *ctx.Context, redisObj storage.Redis, username string, count int64) (bool, error) {
|
||||
@@ -804,10 +803,6 @@ func (u *User) BusiGroups(ctx *ctx.Context, limit int, query string, all ...bool
|
||||
return lst, err
|
||||
}
|
||||
|
||||
if t == nil {
|
||||
return lst, nil
|
||||
}
|
||||
|
||||
t.GroupIds, err = TargetGroupIdsGetByIdent(ctx, t.Ident)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,60 +0,0 @@
|
||||
package ormx
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestDataBaseInit(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config DBConfig
|
||||
}{
|
||||
{
|
||||
name: "MySQL",
|
||||
config: DBConfig{
|
||||
DBType: "mysql",
|
||||
DSN: "root:1234@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local&allowNativePasswords=true",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Postgres",
|
||||
config: DBConfig{
|
||||
DBType: "postgres",
|
||||
DSN: "host=127.0.0.1 port=5432 user=postgres dbname=test password=1234 sslmode=disable",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SQLite",
|
||||
config: DBConfig{
|
||||
DBType: "sqlite",
|
||||
DSN: "./test.db",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := createDatabase(tt.config, &gorm.Config{})
|
||||
assert.NoError(t, err)
|
||||
var dialector gorm.Dialector
|
||||
switch tt.config.DBType {
|
||||
case "mysql":
|
||||
dialector = mysql.Open(tt.config.DSN)
|
||||
case "postgres":
|
||||
dialector = postgres.Open(tt.config.DSN)
|
||||
case "sqlite":
|
||||
dialector = sqlite.Open(tt.config.DSN)
|
||||
}
|
||||
db, err := gorm.Open(dialector, &gorm.Config{})
|
||||
assert.NoError(t, err)
|
||||
err = DataBaseInit(tt.config, db)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
252
pkg/ormx/ormx.go
252
pkg/ormx/ormx.go
@@ -2,7 +2,6 @@ package ormx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -71,234 +70,6 @@ func (l *TKitLogger) Printf(s string, i ...interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func createDatabase(c DBConfig, gconfig *gorm.Config) error {
|
||||
switch strings.ToLower(c.DBType) {
|
||||
case "mysql":
|
||||
return createMysqlDatabase(c.DSN, gconfig)
|
||||
case "postgres":
|
||||
return createPostgresDatabase(c.DSN, gconfig)
|
||||
case "sqlite":
|
||||
return createSqliteDatabase(c.DSN, gconfig)
|
||||
default:
|
||||
return fmt.Errorf("dialector(%s) not supported", c.DBType)
|
||||
}
|
||||
}
|
||||
|
||||
func createSqliteDatabase(dsn string, gconfig *gorm.Config) error {
|
||||
tempDialector := sqlite.Open(dsn)
|
||||
|
||||
_, err := gorm.Open(tempDialector, gconfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open temporary connection: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("sqlite file created")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createPostgresDatabase(dsn string, gconfig *gorm.Config) error {
|
||||
dsnParts := strings.Split(dsn, " ")
|
||||
dbName := ""
|
||||
connectionWithoutDB := ""
|
||||
for _, part := range dsnParts {
|
||||
if strings.HasPrefix(part, "dbname=") {
|
||||
dbName = part[strings.Index(part, "=")+1:]
|
||||
} else {
|
||||
connectionWithoutDB += part
|
||||
connectionWithoutDB += " "
|
||||
}
|
||||
}
|
||||
|
||||
createDBQuery := fmt.Sprintf("CREATE DATABASE %s ENCODING='UTF8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8';", dbName)
|
||||
|
||||
tempDialector := postgres.Open(connectionWithoutDB)
|
||||
|
||||
tempDB, err := gorm.Open(tempDialector, gconfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open temporary connection: %v", err)
|
||||
}
|
||||
|
||||
result := tempDB.Exec(createDBQuery)
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("failed to execute create database query: %v", result.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createMysqlDatabase(dsn string, gconfig *gorm.Config) error {
|
||||
dsnParts := strings.SplitN(dsn, "/", 2)
|
||||
if len(dsnParts) != 2 {
|
||||
return fmt.Errorf("failed to parse DSN: %s", dsn)
|
||||
}
|
||||
|
||||
connectionInfo := dsnParts[0]
|
||||
dbInfo := dsnParts[1]
|
||||
dbName := dbInfo
|
||||
|
||||
queryIndex := strings.Index(dbInfo, "?")
|
||||
if queryIndex != -1 {
|
||||
dbName = dbInfo[:queryIndex]
|
||||
} else {
|
||||
return fmt.Errorf("failed to parse database name from DSN: %s", dsn)
|
||||
}
|
||||
|
||||
connectionWithoutDB := connectionInfo + "/?" + dbInfo[queryIndex+1:]
|
||||
createDBQuery := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4", dbName)
|
||||
|
||||
tempDialector := mysql.Open(connectionWithoutDB)
|
||||
|
||||
tempDB, err := gorm.Open(tempDialector, gconfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open temporary connection: %v", err)
|
||||
}
|
||||
|
||||
result := tempDB.Exec(createDBQuery)
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("failed to execute create database query: %v", result.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDatabaseExist(c DBConfig) (bool, error) {
|
||||
switch strings.ToLower(c.DBType) {
|
||||
case "mysql":
|
||||
return checkMysqlDatabaseExist(c)
|
||||
case "postgres":
|
||||
return checkPostgresDatabaseExist(c)
|
||||
case "sqlite":
|
||||
return checkSqliteDatabaseExist(c)
|
||||
default:
|
||||
return false, fmt.Errorf("dialector(%s) not supported", c.DBType)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func checkSqliteDatabaseExist(c DBConfig) (bool, error) {
|
||||
if _, err := os.Stat(c.DSN); os.IsNotExist(err) {
|
||||
fmt.Printf("sqlite file not exists: %s\n", c.DSN)
|
||||
return false, nil
|
||||
} else {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func checkPostgresDatabaseExist(c DBConfig) (bool, error) {
|
||||
dsnParts := strings.Split(c.DSN, " ")
|
||||
dbName := ""
|
||||
connectionWithoutDB := ""
|
||||
for _, part := range dsnParts {
|
||||
if strings.HasPrefix(part, "dbname=") {
|
||||
dbName = part[strings.Index(part, "=")+1:]
|
||||
} else {
|
||||
connectionWithoutDB += part
|
||||
connectionWithoutDB += " "
|
||||
}
|
||||
}
|
||||
|
||||
dialector := postgres.Open(connectionWithoutDB)
|
||||
|
||||
gconfig := &gorm.Config{
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
TablePrefix: c.TablePrefix,
|
||||
SingularTable: true,
|
||||
},
|
||||
Logger: gormLogger,
|
||||
}
|
||||
|
||||
db, err := gorm.Open(dialector, gconfig)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to open database: %v", err)
|
||||
}
|
||||
|
||||
var databases []string
|
||||
query := genQuery(c)
|
||||
if err := db.Raw(query).Scan(&databases).Error; err != nil {
|
||||
return false, fmt.Errorf("failed to query: %v", err)
|
||||
}
|
||||
|
||||
for _, database := range databases {
|
||||
if database == dbName {
|
||||
fmt.Println("Database exist")
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func checkMysqlDatabaseExist(c DBConfig) (bool, error) {
|
||||
dsnParts := strings.SplitN(c.DSN, "/", 2)
|
||||
if len(dsnParts) != 2 {
|
||||
return false, fmt.Errorf("failed to parse DSN: %s", c.DSN)
|
||||
}
|
||||
|
||||
connectionInfo := dsnParts[0]
|
||||
dbInfo := dsnParts[1]
|
||||
dbName := dbInfo
|
||||
|
||||
queryIndex := strings.Index(dbInfo, "?")
|
||||
if queryIndex != -1 {
|
||||
dbName = dbInfo[:queryIndex]
|
||||
} else {
|
||||
return false, fmt.Errorf("failed to parse database name from DSN: %s", c.DSN)
|
||||
}
|
||||
|
||||
connectionWithoutDB := connectionInfo + "/?" + dbInfo[queryIndex+1:]
|
||||
|
||||
var dialector gorm.Dialector
|
||||
switch strings.ToLower(c.DBType) {
|
||||
case "mysql":
|
||||
dialector = mysql.Open(connectionWithoutDB)
|
||||
case "postgres":
|
||||
dialector = postgres.Open(connectionWithoutDB)
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported database type: %s", c.DBType)
|
||||
}
|
||||
|
||||
gconfig := &gorm.Config{
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
TablePrefix: c.TablePrefix,
|
||||
SingularTable: true,
|
||||
},
|
||||
Logger: gormLogger,
|
||||
}
|
||||
|
||||
db, err := gorm.Open(dialector, gconfig)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to open database: %v", err)
|
||||
}
|
||||
|
||||
var databases []string
|
||||
query := genQuery(c)
|
||||
if err := db.Raw(query).Scan(&databases).Error; err != nil {
|
||||
return false, fmt.Errorf("failed to query: %v", err)
|
||||
}
|
||||
|
||||
for _, database := range databases {
|
||||
if database == dbName {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func genQuery(c DBConfig) string {
|
||||
switch strings.ToLower(c.DBType) {
|
||||
case "mysql":
|
||||
return "SHOW DATABASES"
|
||||
case "postgres":
|
||||
return "SELECT datname FROM pg_database"
|
||||
case "sqlite":
|
||||
return ""
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// New Create gorm.DB instance
|
||||
func New(c DBConfig) (*gorm.DB, error) {
|
||||
var dialector gorm.Dialector
|
||||
@@ -324,30 +95,9 @@ func New(c DBConfig) (*gorm.DB, error) {
|
||||
Logger: gormLogger,
|
||||
}
|
||||
|
||||
dbExist, checkErr := checkDatabaseExist(c)
|
||||
if checkErr != nil {
|
||||
return nil, checkErr
|
||||
}
|
||||
if !dbExist {
|
||||
fmt.Println("Database not exist, trying to create it")
|
||||
createErr := createDatabase(c, gconfig)
|
||||
if createErr != nil {
|
||||
return nil, fmt.Errorf("failed to create database: %v", createErr)
|
||||
}
|
||||
|
||||
db, err := gorm.Open(dialector, gconfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to reopen database after creation: %v", err)
|
||||
}
|
||||
err = DataBaseInit(c, db)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to init database: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
db, err := gorm.Open(dialector, gconfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open database: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.Debug {
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/conf"
|
||||
@@ -63,10 +61,6 @@ func GetByUrl[T any](url string, cfg conf.CenterApi) (T, error) {
|
||||
Timeout: time.Duration(cfg.Timeout) * time.Millisecond,
|
||||
}
|
||||
|
||||
if useProxy(url) {
|
||||
client.Transport = ProxyTransporter
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return dat, fmt.Errorf("failed to fetch from url: %w", err)
|
||||
@@ -93,7 +87,8 @@ func GetByUrl[T any](url string, cfg conf.CenterApi) (T, error) {
|
||||
return dat, fmt.Errorf("error from server: %s", dataResp.Err)
|
||||
}
|
||||
|
||||
logger.Debugf("get data from %s, data: %+v", url, dataResp.Dat)
|
||||
prettyData, _ := json.Marshal(dataResp.Dat)
|
||||
logger.Debugf("get data from %s, data: %s", url, string(prettyData))
|
||||
return dataResp.Dat, nil
|
||||
}
|
||||
|
||||
@@ -147,10 +142,6 @@ func PostByUrl[T any](url string, cfg conf.CenterApi, v interface{}) (t T, err e
|
||||
Timeout: time.Duration(cfg.Timeout) * time.Millisecond,
|
||||
}
|
||||
|
||||
if useProxy(url) {
|
||||
client.Transport = ProxyTransporter
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bf)
|
||||
if err != nil {
|
||||
return t, fmt.Errorf("failed to create request %q: %w", url, err)
|
||||
@@ -186,32 +177,9 @@ func PostByUrl[T any](url string, cfg conf.CenterApi, v interface{}) (t T, err e
|
||||
return t, fmt.Errorf("error from server: %s", dataResp.Err)
|
||||
}
|
||||
|
||||
logger.Debugf("get data from %s, data: %+v", url, dataResp.Dat)
|
||||
prettyData, _ := json.Marshal(dataResp.Dat)
|
||||
logger.Debugf("get data from %s, data: %s", url, string(prettyData))
|
||||
return dataResp.Dat, nil
|
||||
|
||||
}
|
||||
|
||||
var ProxyTransporter = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
|
||||
func useProxy(url string) bool {
|
||||
// N9E_PROXY_URL=oapi.dingtalk.com,feishu.com
|
||||
patterns := os.Getenv("N9E_PROXY_URL")
|
||||
if patterns != "" {
|
||||
// 说明要让某些 URL 走代理
|
||||
for _, u := range strings.Split(patterns, ",") {
|
||||
u = strings.TrimSpace(u)
|
||||
if u == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(url, u) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func PostJSON(url string, timeout time.Duration, v interface{}, retries ...int) (response []byte, code int, err error) {
|
||||
@@ -228,10 +196,6 @@ func PostJSON(url string, timeout time.Duration, v interface{}, retries ...int)
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
if useProxy(url) {
|
||||
client.Transport = ProxyTransporter
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bf)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -697,27 +697,18 @@ func (h *httpAPI) LabelValues(ctx context.Context, label string, matchs []string
|
||||
}
|
||||
|
||||
func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, error) {
|
||||
var err error
|
||||
var warnings Warnings
|
||||
var value model.Value
|
||||
var statusCode int
|
||||
for i := 0; i < 3; i++ {
|
||||
value, warnings, statusCode, err = h.query(ctx, query, ts)
|
||||
value, warnings, err := h.query(ctx, query, ts)
|
||||
if err == nil {
|
||||
return value, warnings, nil
|
||||
}
|
||||
|
||||
// statusCode 4xx do not retry
|
||||
if statusCode >= 400 && statusCode < 500 {
|
||||
return nil, warnings, err
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return nil, warnings, err
|
||||
return nil, nil, errors.New("query failed")
|
||||
}
|
||||
|
||||
func (h *httpAPI) query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, int, error) {
|
||||
func (h *httpAPI) query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, error) {
|
||||
u := h.client.URL(epQuery, nil)
|
||||
q := u.Query()
|
||||
|
||||
@@ -728,11 +719,15 @@ func (h *httpAPI) query(ctx context.Context, query string, ts time.Time) (model.
|
||||
|
||||
resp, body, warnings, err := h.client.DoGetFallback(ctx, u, q)
|
||||
if err != nil {
|
||||
return nil, warnings, 0, err
|
||||
return nil, warnings, err
|
||||
}
|
||||
|
||||
if resp.StatusCode > 200 {
|
||||
fmt.Println("status code:", resp.StatusCode)
|
||||
}
|
||||
|
||||
var qres queryResult
|
||||
return model.Value(qres.v), warnings, resp.StatusCode, json.Unmarshal(body, &qres)
|
||||
return model.Value(qres.v), warnings, json.Unmarshal(body, &qres)
|
||||
}
|
||||
|
||||
func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, Warnings, error) {
|
||||
|
||||
@@ -27,15 +27,6 @@ type sample struct {
|
||||
Value float64
|
||||
}
|
||||
|
||||
type QueryFunc func(int64, string) model.Value
|
||||
|
||||
var queryFunc QueryFunc
|
||||
|
||||
// RegisterQueryFunc 为了避免循环引用,通过外部注入的方式注册 queryFunc
|
||||
func RegisterQueryFunc(f QueryFunc) {
|
||||
queryFunc = f
|
||||
}
|
||||
|
||||
type queryResult []*sample
|
||||
|
||||
type queryResultByLabelSorter struct {
|
||||
@@ -573,13 +564,3 @@ func convertToFloat(i interface{}) (float64, error) {
|
||||
return 0, fmt.Errorf("can't convert %T to float", v)
|
||||
}
|
||||
}
|
||||
|
||||
func Query(datasourceID int64, promql string) model.Value {
|
||||
|
||||
value := queryFunc(datasourceID, promql)
|
||||
if value != nil {
|
||||
return value
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -54,15 +54,6 @@ var TemplateFuncMap = template.FuncMap{
|
||||
"printf": Printf,
|
||||
}
|
||||
|
||||
// NewTemplateFuncMap copy on write for TemplateFuncMap
|
||||
func NewTemplateFuncMap() template.FuncMap {
|
||||
m := template.FuncMap{}
|
||||
for k, v := range TemplateFuncMap {
|
||||
m[k] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// ReplaceTemplateUseHtml replaces variables in a template string with values.
|
||||
//
|
||||
// It accepts the following parameters:
|
||||
|
||||
@@ -1,320 +0,0 @@
|
||||
package unit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FormattedValue 格式化后的值的结构
|
||||
type FormattedValue struct {
|
||||
Value float64 `json:"value"`
|
||||
Unit string `json:"unit"`
|
||||
Text string `json:"text"`
|
||||
Stat float64 `json:"stat"`
|
||||
}
|
||||
|
||||
// FormatOptions 格式化选项
|
||||
type FormatOptions struct {
|
||||
Type string // "si" 或 "iec"
|
||||
Base string // "bits" 或 "bytes"
|
||||
Decimals int // 小数位数
|
||||
Postfix string // 后缀
|
||||
}
|
||||
|
||||
// 时间相关常量
|
||||
const (
|
||||
NanosecondVal = 0.000000001
|
||||
MicrosecondVal = 0.000001
|
||||
MillisecondVal = 0.001
|
||||
SecondVal = 1
|
||||
MinuteVal = 60
|
||||
HourVal = 3600
|
||||
DayVal = 86400
|
||||
WeekVal = 86400 * 7
|
||||
YearVal = 86400 * 365
|
||||
)
|
||||
|
||||
var (
|
||||
valueMap = []struct {
|
||||
Exp int
|
||||
Si string
|
||||
Iec string
|
||||
IecExp int
|
||||
}{
|
||||
{0, "", "", 1},
|
||||
{3, "k", "Ki", 10},
|
||||
{6, "M", "Mi", 20},
|
||||
{9, "G", "Gi", 30},
|
||||
{12, "T", "Ti", 40},
|
||||
{15, "P", "Pi", 50},
|
||||
{18, "E", "Ei", 60},
|
||||
{21, "Z", "Zi", 70},
|
||||
{24, "Y", "Yi", 80},
|
||||
}
|
||||
|
||||
baseUtilMap = map[string]string{
|
||||
"bits": "b",
|
||||
"bytes": "B",
|
||||
}
|
||||
)
|
||||
|
||||
// ValueFormatter 格式化入口函数
|
||||
func ValueFormatter(unit string, decimals int, value float64) FormattedValue {
|
||||
if math.IsNaN(value) {
|
||||
return FormattedValue{
|
||||
Value: 0,
|
||||
Unit: "",
|
||||
Text: "NaN",
|
||||
Stat: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// 处理时间单位
|
||||
switch unit {
|
||||
case "none":
|
||||
return formatNone(value, decimals)
|
||||
case "ns", "nanoseconds":
|
||||
return formatDuration(value, "ns", decimals)
|
||||
case "µs", "microseconds":
|
||||
return formatDuration(value, "µs", decimals)
|
||||
case "ms", "milliseconds":
|
||||
return formatDuration(value, "ms", decimals)
|
||||
case "s", "seconds":
|
||||
return formatDuration(value, "s", decimals)
|
||||
case "min", "h", "d", "w":
|
||||
return formatDuration(value, unit, decimals)
|
||||
case "percent":
|
||||
return formatPercent(value, decimals, false)
|
||||
case "percentUnit":
|
||||
return formatPercent(value, decimals, true)
|
||||
case "bytesIEC", "bytes(IEC)", "bitsIEC", "bits(IEC)":
|
||||
base := unit
|
||||
base = strings.TrimSuffix(base, "(IEC)")
|
||||
base = strings.TrimSuffix(base, "IEC")
|
||||
base = strings.TrimSuffix(base, "s")
|
||||
opts := FormatOptions{
|
||||
Type: "iec",
|
||||
Base: base,
|
||||
Decimals: decimals,
|
||||
}
|
||||
return formatBytes(value, opts)
|
||||
case "bytesSI", "bytes(SI)", "bitsSI", "bits(SI)", "default", "sishort":
|
||||
base := unit
|
||||
base = strings.TrimSuffix(base, "(SI)")
|
||||
base = strings.TrimSuffix(base, "SI")
|
||||
base = strings.TrimSuffix(base, "s")
|
||||
opts := FormatOptions{
|
||||
Type: "si",
|
||||
Base: base,
|
||||
Decimals: decimals,
|
||||
}
|
||||
return formatBytes(value, opts)
|
||||
case "bytesSecIEC":
|
||||
opts := FormatOptions{
|
||||
Type: "iec",
|
||||
Base: "bytes",
|
||||
Decimals: decimals,
|
||||
Postfix: "/s",
|
||||
}
|
||||
return formatBytes(value, opts)
|
||||
case "bitsSecIEC":
|
||||
opts := FormatOptions{
|
||||
Type: "iec",
|
||||
Base: "bits",
|
||||
Decimals: decimals,
|
||||
Postfix: "/s",
|
||||
}
|
||||
return formatBytes(value, opts)
|
||||
case "bytesSecSI":
|
||||
opts := FormatOptions{
|
||||
Type: "si",
|
||||
Base: "bytes",
|
||||
Decimals: decimals,
|
||||
Postfix: "/s",
|
||||
}
|
||||
return formatBytes(value, opts)
|
||||
case "bitsSecSI":
|
||||
opts := FormatOptions{
|
||||
Type: "si",
|
||||
Base: "bits",
|
||||
Decimals: decimals,
|
||||
Postfix: "/s",
|
||||
}
|
||||
return formatBytes(value, opts)
|
||||
case "datetimeSeconds", "datetimeMilliseconds":
|
||||
return formatDateTime(unit, value)
|
||||
default:
|
||||
return formatNone(value, decimals)
|
||||
}
|
||||
}
|
||||
|
||||
// formatDuration 处理时间单位的转换
|
||||
func formatDuration(originValue float64, unit string, decimals int) FormattedValue {
|
||||
var converted float64
|
||||
var targetUnit string
|
||||
value := originValue
|
||||
// 标准化到秒
|
||||
switch unit {
|
||||
case "ns":
|
||||
value *= NanosecondVal
|
||||
case "µs":
|
||||
value *= MicrosecondVal
|
||||
case "ms":
|
||||
value *= MillisecondVal
|
||||
case "min":
|
||||
value *= MinuteVal
|
||||
case "h":
|
||||
value *= HourVal
|
||||
case "d":
|
||||
value *= DayVal
|
||||
case "w":
|
||||
value *= WeekVal
|
||||
}
|
||||
|
||||
// 选择合适的单位
|
||||
switch {
|
||||
case value >= YearVal:
|
||||
converted = value / YearVal
|
||||
targetUnit = "y"
|
||||
case value >= WeekVal:
|
||||
converted = value / WeekVal
|
||||
targetUnit = "w"
|
||||
case value >= DayVal:
|
||||
converted = value / DayVal
|
||||
targetUnit = "d"
|
||||
case value >= HourVal:
|
||||
converted = value / HourVal
|
||||
targetUnit = "h"
|
||||
case value >= MinuteVal:
|
||||
converted = value / MinuteVal
|
||||
targetUnit = "min"
|
||||
case value >= SecondVal:
|
||||
converted = value
|
||||
targetUnit = "s"
|
||||
case value >= MillisecondVal:
|
||||
converted = value / MillisecondVal
|
||||
targetUnit = "ms"
|
||||
case value >= MicrosecondVal:
|
||||
converted = value / MicrosecondVal
|
||||
targetUnit = "µs"
|
||||
default:
|
||||
converted = value / NanosecondVal
|
||||
targetUnit = "ns"
|
||||
}
|
||||
|
||||
return FormattedValue{
|
||||
Value: roundFloat(converted, decimals),
|
||||
Unit: targetUnit,
|
||||
Text: fmt.Sprintf("%.*f %s", decimals, converted, targetUnit),
|
||||
Stat: originValue,
|
||||
}
|
||||
}
|
||||
|
||||
// formatBytes 处理字节相关的转换
|
||||
func formatBytes(value float64, opts FormatOptions) FormattedValue {
|
||||
if value == 0 {
|
||||
baseUtil := baseUtilMap[opts.Base]
|
||||
return FormattedValue{
|
||||
Value: 0,
|
||||
Unit: baseUtil + opts.Postfix,
|
||||
Text: fmt.Sprintf("0%s%s", baseUtil, opts.Postfix),
|
||||
Stat: 0,
|
||||
}
|
||||
}
|
||||
|
||||
baseUtil := baseUtilMap[opts.Base]
|
||||
threshold := 1000.0
|
||||
if opts.Type == "iec" {
|
||||
threshold = 1024.0
|
||||
}
|
||||
|
||||
if math.Abs(value) < threshold {
|
||||
return FormattedValue{
|
||||
Value: roundFloat(value, opts.Decimals),
|
||||
Unit: baseUtil + opts.Postfix,
|
||||
Text: fmt.Sprintf("%.*f%s%s", opts.Decimals, value, baseUtil, opts.Postfix),
|
||||
Stat: value,
|
||||
}
|
||||
}
|
||||
|
||||
// 计算指数
|
||||
exp := int(math.Floor(math.Log10(math.Abs(value))/3.0)) * 3
|
||||
if exp > 24 {
|
||||
exp = 24
|
||||
}
|
||||
|
||||
var unit string
|
||||
var divider float64
|
||||
|
||||
// 查找对应的单位
|
||||
for _, v := range valueMap {
|
||||
if v.Exp == exp {
|
||||
if opts.Type == "iec" {
|
||||
unit = v.Iec
|
||||
divider = math.Pow(2, float64(v.IecExp))
|
||||
} else {
|
||||
unit = v.Si
|
||||
divider = math.Pow(10, float64(v.Exp))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
newValue := value / divider
|
||||
return FormattedValue{
|
||||
Value: roundFloat(newValue, opts.Decimals),
|
||||
Unit: unit + baseUtil + opts.Postfix,
|
||||
Text: fmt.Sprintf("%.*f%s%s%s", opts.Decimals, newValue, unit, baseUtil, opts.Postfix),
|
||||
Stat: value,
|
||||
}
|
||||
}
|
||||
|
||||
// formatPercent 处理百分比格式化
|
||||
func formatPercent(value float64, decimals int, isUnit bool) FormattedValue {
|
||||
if isUnit {
|
||||
value = value * 100
|
||||
}
|
||||
return FormattedValue{
|
||||
Value: roundFloat(value, decimals),
|
||||
Unit: "%",
|
||||
Text: fmt.Sprintf("%.*f%%", decimals, value),
|
||||
Stat: value,
|
||||
}
|
||||
}
|
||||
|
||||
// formatNone 处理无单位格式化
|
||||
func formatNone(value float64, decimals int) FormattedValue {
|
||||
return FormattedValue{
|
||||
Value: value,
|
||||
Unit: "",
|
||||
Text: fmt.Sprintf("%.*f", decimals, value),
|
||||
Stat: value,
|
||||
}
|
||||
}
|
||||
|
||||
// formatDateTime 处理时间戳格式化
|
||||
func formatDateTime(uint string, value float64) FormattedValue {
|
||||
var t time.Time
|
||||
switch uint {
|
||||
case "datetimeSeconds":
|
||||
t = time.Unix(int64(value), 0)
|
||||
case "datetimeMilliseconds":
|
||||
t = time.Unix(0, int64(value)*int64(time.Millisecond))
|
||||
}
|
||||
|
||||
text := t.Format("2006-01-02 15:04:05")
|
||||
return FormattedValue{
|
||||
Value: value,
|
||||
Unit: "",
|
||||
Text: text,
|
||||
Stat: value,
|
||||
}
|
||||
}
|
||||
|
||||
// roundFloat 四舍五入到指定小数位
|
||||
func roundFloat(val float64, precision int) float64 {
|
||||
ratio := math.Pow(10, float64(precision))
|
||||
return math.Round(val*ratio) / ratio
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user