mirror of
https://github.com/ccfos/nightingale.git
synced 2026-03-10 09:59:00 +00:00
Compare commits
64 Commits
fix-v7
...
dash-annot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
640e914420 | ||
|
|
6d6dee160b | ||
|
|
473a00bbaa | ||
|
|
b0131a3799 | ||
|
|
cbb03a7c63 | ||
|
|
080d412124 | ||
|
|
752e02f32d | ||
|
|
e05d59d72a | ||
|
|
854e30551a | ||
|
|
0b6dc5beba | ||
|
|
8685a95fa5 | ||
|
|
7ca7fd8d66 | ||
|
|
1b5dc81b6c | ||
|
|
04495f0892 | ||
|
|
8158ce1b90 | ||
|
|
a43952e168 | ||
|
|
5702fc81d0 | ||
|
|
7cc65a2ca7 | ||
|
|
7bb6c6541a | ||
|
|
8b4cfe65e3 | ||
|
|
7227de8c22 | ||
|
|
069e267af8 | ||
|
|
7c5c9a95c3 | ||
|
|
e3da7f344b | ||
|
|
dd741a177f | ||
|
|
4fdd25f020 | ||
|
|
62350bfbc6 | ||
|
|
5ee1baaf07 | ||
|
|
fa12889f06 | ||
|
|
39306a5bf0 | ||
|
|
0aea38e564 | ||
|
|
45e9253b2a | ||
|
|
9385ca9931 | ||
|
|
fdd3d14871 | ||
|
|
e890034c19 | ||
|
|
3aaab9e6ad | ||
|
|
7f7d707cfc | ||
|
|
98402e9f8a | ||
|
|
017094fd78 | ||
|
|
8b6b896362 | ||
|
|
acaa00cfb6 | ||
|
|
87f3d8595d | ||
|
|
42791a374d | ||
|
|
3855c25805 | ||
|
|
10ec0ccbd1 | ||
|
|
94cf304222 | ||
|
|
994de4635a | ||
|
|
9a0013a406 | ||
|
|
6dcd5dd01e | ||
|
|
70126e3aec | ||
|
|
767482d358 | ||
|
|
9a46106cc0 | ||
|
|
da9ea67cee | ||
|
|
c13ecd780b | ||
|
|
cab37c796a | ||
|
|
078578772b | ||
|
|
31883ec844 | ||
|
|
6100cd084a | ||
|
|
b82e260d65 | ||
|
|
3983386af3 | ||
|
|
83f2054062 | ||
|
|
83e0b3cb98 | ||
|
|
f6bfa17e2e | ||
|
|
3d8019b738 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.db
|
||||
*.sw[po]
|
||||
*.tar.gz
|
||||
*.[568vq]
|
||||
|
||||
@@ -65,6 +65,7 @@ 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()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dispatch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -13,8 +14,10 @@ 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"
|
||||
)
|
||||
@@ -27,6 +30,18 @@ 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{
|
||||
@@ -169,7 +184,7 @@ func (e *Consumer) queryRecoveryVal(event *models.AlertCurEvent) {
|
||||
logger.Errorf("rule_eval:%s promql:%s, warnings:%v", getKey(event), promql, warnings)
|
||||
}
|
||||
|
||||
anomalyPoints := common.ConvertAnomalyPoints(value)
|
||||
anomalyPoints := models.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")
|
||||
|
||||
@@ -193,9 +193,9 @@ func (e *Dispatch) blockEventNotify(rule *models.AlertRule, event *models.AlertC
|
||||
}
|
||||
|
||||
// 恢复通知,检测规则配置是否改变
|
||||
if event.IsRecovered && event.RuleHash != rule.Hash() {
|
||||
return true
|
||||
}
|
||||
// if event.IsRecovered && event.RuleHash != rule.Hash() {
|
||||
// return true
|
||||
// }
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -293,9 +293,9 @@ func (e *Dispatch) Send(rule *models.AlertRule, event *models.AlertCurEvent, not
|
||||
// handle global webhooks
|
||||
if !event.OverrideGlobalWebhook() {
|
||||
if e.alerting.WebhookBatchSend {
|
||||
sender.BatchSendWebhooks(e.ctx, notifyTarget.ToWebhookList(), event, e.Astats)
|
||||
sender.BatchSendWebhooks(e.ctx, notifyTarget.ToWebhookMap(), event, e.Astats)
|
||||
} else {
|
||||
sender.SingleSendWebhooks(e.ctx, notifyTarget.ToWebhookList(), event, e.Astats)
|
||||
sender.SingleSendWebhooks(e.ctx, notifyTarget.ToWebhookMap(), event, e.Astats)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,52 +76,8 @@ func (s *NotifyTarget) ToCallbackList() []string {
|
||||
return callbacks
|
||||
}
|
||||
|
||||
func (s *NotifyTarget) ToWebhookList() []*models.Webhook {
|
||||
webhooks := make([]*models.Webhook, 0, len(s.webhooks))
|
||||
for _, wh := range s.webhooks {
|
||||
if wh.Batch == 0 {
|
||||
wh.Batch = 1000
|
||||
}
|
||||
|
||||
if wh.Timeout == 0 {
|
||||
wh.Timeout = 10
|
||||
}
|
||||
|
||||
if wh.RetryCount == 0 {
|
||||
wh.RetryCount = 10
|
||||
}
|
||||
|
||||
if wh.RetryInterval == 0 {
|
||||
wh.RetryInterval = 10
|
||||
}
|
||||
|
||||
webhooks = append(webhooks, wh)
|
||||
}
|
||||
return webhooks
|
||||
}
|
||||
|
||||
func (s *NotifyTarget) ToWebhookMap() map[string]*models.Webhook {
|
||||
webhookMap := make(map[string]*models.Webhook, len(s.webhooks))
|
||||
for _, wh := range s.webhooks {
|
||||
if wh.Batch == 0 {
|
||||
wh.Batch = 1000
|
||||
}
|
||||
|
||||
if wh.Timeout == 0 {
|
||||
wh.Timeout = 10
|
||||
}
|
||||
|
||||
if wh.RetryCount == 0 {
|
||||
wh.RetryCount = 10
|
||||
}
|
||||
|
||||
if wh.RetryInterval == 0 {
|
||||
wh.RetryInterval = 10
|
||||
}
|
||||
|
||||
webhookMap[wh.Url] = wh
|
||||
}
|
||||
return webhookMap
|
||||
return s.webhooks
|
||||
}
|
||||
|
||||
func (s *NotifyTarget) ToUidList() []int64 {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -269,3 +269,190 @@ 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractVarMapping(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
promql string
|
||||
want map[string]string
|
||||
}{
|
||||
{
|
||||
name: "单个花括号单个变量",
|
||||
promql: `mem_used_percent{host="$my_host"} > $val`,
|
||||
want: map[string]string{"my_host": "host"},
|
||||
},
|
||||
{
|
||||
name: "单个花括号多个变量",
|
||||
promql: `mem_used_percent{host="$my_host",region="$region",env="prod"} > $val`,
|
||||
want: map[string]string{"my_host": "host", "region": "region"},
|
||||
},
|
||||
{
|
||||
name: "多个花括号多个变量",
|
||||
promql: `sum(rate(mem_used_percent{host="$my_host"})) by (instance) + avg(node_load1{region="$region"}) > $val`,
|
||||
want: map[string]string{"my_host": "host", "region": "region"},
|
||||
},
|
||||
{
|
||||
name: "相同变量出现多次",
|
||||
promql: `sum(rate(mem_used_percent{host="$my_host"})) + avg(node_load1{host="$my_host"}) > $val`,
|
||||
want: map[string]string{"my_host": "host"},
|
||||
},
|
||||
{
|
||||
name: "没有变量",
|
||||
promql: `mem_used_percent{host="localhost",region="cn"} > 80`,
|
||||
want: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "没有花括号",
|
||||
promql: `80 > $val`,
|
||||
want: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "格式不规范的标签",
|
||||
promql: `mem_used_percent{host=$my_host,region = $region} > $val`,
|
||||
want: map[string]string{"my_host": "host", "region": "region"},
|
||||
},
|
||||
{
|
||||
name: "空花括号",
|
||||
promql: `mem_used_percent{} > $val`,
|
||||
want: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "不完整的花括号",
|
||||
promql: `mem_used_percent{host="$my_host"`,
|
||||
want: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "复杂表达式",
|
||||
promql: `sum(rate(http_requests_total{handler="$handler",code="$code"}[5m])) by (handler) / sum(rate(http_requests_total{handler="$handler"}[5m])) by (handler) * 100 > $threshold`,
|
||||
want: map[string]string{"handler": "handler", "code": "code"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ExtractVarMapping(tt.promql)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ExtractVarMapping() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package process
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"sort"
|
||||
@@ -21,6 +22,7 @@ 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"
|
||||
)
|
||||
@@ -78,6 +80,9 @@ type Processor struct {
|
||||
HandleFireEventHook HandleEventFunc
|
||||
HandleRecoverEventHook HandleEventFunc
|
||||
EventMuteHook EventMuteHookFunc
|
||||
|
||||
ScheduleEntry cron.Entry
|
||||
PromEvalInterval int
|
||||
}
|
||||
|
||||
func (p *Processor) Key() string {
|
||||
@@ -89,9 +94,9 @@ func (p *Processor) DatasourceId() int64 {
|
||||
}
|
||||
|
||||
func (p *Processor) Hash() string {
|
||||
return str.MD5(fmt.Sprintf("%d_%d_%s_%d",
|
||||
return str.MD5(fmt.Sprintf("%d_%s_%s_%d",
|
||||
p.rule.Id,
|
||||
p.rule.PromEvalInterval,
|
||||
p.rule.CronPattern,
|
||||
p.rule.RuleConfig,
|
||||
p.datasourceId,
|
||||
))
|
||||
@@ -126,7 +131,7 @@ func NewProcessor(engineName string, rule *models.AlertRule, datasourceId int64,
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Processor) Handle(anomalyPoints []common.AnomalyPoint, from string, inhibit bool) {
|
||||
func (p *Processor) Handle(anomalyPoints []models.AnomalyPoint, from string, inhibit bool) {
|
||||
// 有可能rule的一些配置已经发生变化,比如告警接收人、callbacks等
|
||||
// 这些信息的修改是不会引起worker restart的,但是确实会影响告警处理逻辑
|
||||
// 所以,这里直接从memsto.AlertRuleCache中获取并覆盖
|
||||
@@ -178,7 +183,7 @@ func (p *Processor) Handle(anomalyPoints []common.AnomalyPoint, from string, inh
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Processor) BuildEvent(anomalyPoint common.AnomalyPoint, from string, now int64, ruleHash string) *models.AlertCurEvent {
|
||||
func (p *Processor) BuildEvent(anomalyPoint models.AnomalyPoint, from string, now int64, ruleHash string) *models.AlertCurEvent {
|
||||
p.fillTags(anomalyPoint)
|
||||
p.mayHandleIdent()
|
||||
hash := Hash(p.rule.Id, p.datasourceId, anomalyPoint)
|
||||
@@ -211,7 +216,6 @@ func (p *Processor) BuildEvent(anomalyPoint common.AnomalyPoint, from string, no
|
||||
event.Callbacks = p.rule.Callbacks
|
||||
event.CallbacksJSON = p.rule.CallbacksJSON
|
||||
event.Annotations = p.rule.Annotations
|
||||
event.AnnotationsJSON = make(map[string]string)
|
||||
event.RuleConfig = p.rule.RuleConfig
|
||||
event.RuleConfigJson = p.rule.RuleConfigJson
|
||||
event.Severity = anomalyPoint.Severity
|
||||
@@ -220,6 +224,11 @@ func (p *Processor) BuildEvent(anomalyPoint common.AnomalyPoint, from string, no
|
||||
event.RecoverConfig = anomalyPoint.RecoverConfig
|
||||
event.RuleHash = ruleHash
|
||||
|
||||
if err := json.Unmarshal([]byte(p.rule.Annotations), &event.AnnotationsJSON); err != nil {
|
||||
event.AnnotationsJSON = make(map[string]string) // 解析失败时使用空 map
|
||||
logger.Warningf("unmarshal annotations json failed: %v, rule: %d", err, p.rule.Id)
|
||||
}
|
||||
|
||||
if p.target != "" {
|
||||
if pt, exist := p.TargetCache.Get(p.target); exist {
|
||||
pt.GroupNames = p.BusiGroupCache.GetNamesByBusiGroupIds(pt.GroupIds)
|
||||
@@ -420,6 +429,7 @@ func (p *Processor) handleEvent(events []*models.AlertCurEvent) {
|
||||
p.pendingsUseByRecover.Set(event.Hash, event)
|
||||
}
|
||||
|
||||
event.PromEvalInterval = p.PromEvalInterval
|
||||
if p.rule.PromForDuration == 0 {
|
||||
fireEvents = append(fireEvents, event)
|
||||
if severity > event.Severity {
|
||||
@@ -559,7 +569,7 @@ func (p *Processor) RecoverAlertCurEventFromDb() {
|
||||
p.pendingsUseByRecover = NewAlertCurEventMap(pendingsUseByRecoverMap)
|
||||
}
|
||||
|
||||
func (p *Processor) fillTags(anomalyPoint common.AnomalyPoint) {
|
||||
func (p *Processor) fillTags(anomalyPoint models.AnomalyPoint) {
|
||||
// handle series tags
|
||||
tagsMap := make(map[string]string)
|
||||
for label, value := range anomalyPoint.Labels {
|
||||
@@ -649,10 +659,10 @@ func labelMapToArr(m map[string]string) []string {
|
||||
return labelStrings
|
||||
}
|
||||
|
||||
func Hash(ruleId, datasourceId int64, vector common.AnomalyPoint) string {
|
||||
func Hash(ruleId, datasourceId int64, vector models.AnomalyPoint) string {
|
||||
return str.MD5(fmt.Sprintf("%d_%s_%d_%d_%s", ruleId, vector.Labels.String(), datasourceId, vector.Severity, vector.Query))
|
||||
}
|
||||
|
||||
func TagHash(vector common.AnomalyPoint) string {
|
||||
func TagHash(vector models.AnomalyPoint) string {
|
||||
return str.MD5(vector.Labels.String())
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ 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"
|
||||
@@ -92,7 +91,7 @@ func (rt *Router) eventPersist(c *gin.Context) {
|
||||
|
||||
type eventForm struct {
|
||||
Alert bool `json:"alert"`
|
||||
AnomalyPoints []common.AnomalyPoint `json:"vectors"`
|
||||
AnomalyPoints []models.AnomalyPoint `json:"vectors"`
|
||||
RuleId int64 `json:"rule_id"`
|
||||
DatasourceId int64 `json:"datasource_id"`
|
||||
Inhibit bool `json:"inhibit"`
|
||||
|
||||
@@ -129,34 +129,39 @@ func (c *DefaultCallBacker) CallBack(ctx CallBackContext) {
|
||||
return
|
||||
}
|
||||
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, event, "callback", ctx.Stats, event)
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, event, "callback", ctx.Stats, ctx.Events)
|
||||
}
|
||||
|
||||
func doSendAndRecord(ctx *ctx.Context, url, token string, body interface{}, channel string,
|
||||
stats *astats.Stats, event *models.AlertCurEvent) {
|
||||
stats *astats.Stats, events []*models.AlertCurEvent) {
|
||||
res, err := doSend(url, body, channel, stats)
|
||||
NotifyRecord(ctx, event, channel, token, res, err)
|
||||
NotifyRecord(ctx, events, channel, token, res, err)
|
||||
}
|
||||
|
||||
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))
|
||||
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)
|
||||
}
|
||||
|
||||
if !ctx.IsCenter {
|
||||
_, err := poster.PostByUrlsWithResp[int64](ctx, "/v1/n9e/notify-record", noti)
|
||||
_, err := poster.PostByUrlsWithResp[[]int64](ctx, "/v1/n9e/notify-record", notis)
|
||||
if err != nil {
|
||||
logger.Errorf("add noti:%v failed, err: %v", noti, err)
|
||||
logger.Errorf("add notis:%v failed, err: %v", notis, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := noti.Add(ctx); err != nil {
|
||||
logger.Errorf("add noti:%v failed, err: %v", noti, err)
|
||||
if err := models.DB(ctx).CreateInBatches(notis, 100).Error; err != nil {
|
||||
logger.Errorf("add notis:%v failed, err: %v", notis, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ func (ds *DingtalkSender) Send(ctx MessageContext) {
|
||||
}
|
||||
}
|
||||
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Dingtalk, ctx.Stats, ctx.Events[0])
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Dingtalk, ctx.Stats, ctx.Events)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,8 +97,7 @@ func (ds *DingtalkSender) CallBack(ctx CallBackContext) {
|
||||
body.Markdown.Text = message
|
||||
}
|
||||
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body,
|
||||
"callback", ctx.Stats, ctx.Events[0])
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback", ctx.Stats, ctx.Events)
|
||||
}
|
||||
|
||||
// extract urls and ats from Users
|
||||
|
||||
@@ -25,8 +25,8 @@ type EmailSender struct {
|
||||
}
|
||||
|
||||
type EmailContext struct {
|
||||
event *models.AlertCurEvent
|
||||
mail *gomail.Message
|
||||
events []*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[0])
|
||||
es.WriteEmail(subject, content, tos, ctx.Events)
|
||||
|
||||
ctx.Stats.AlertNotifyTotal.WithLabelValues(models.Email).Add(float64(len(tos)))
|
||||
}
|
||||
@@ -79,8 +79,7 @@ func SendEmail(subject, content string, tos []string, stmp aconf.SMTPConfig) err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *EmailSender) WriteEmail(subject, content string, tos []string,
|
||||
event *models.AlertCurEvent) {
|
||||
func (es *EmailSender) WriteEmail(subject, content string, tos []string, events []*models.AlertCurEvent) {
|
||||
m := gomail.NewMessage()
|
||||
|
||||
m.SetHeader("From", es.smtp.From)
|
||||
@@ -88,7 +87,7 @@ func (es *EmailSender) WriteEmail(subject, content string, tos []string,
|
||||
m.SetHeader("Subject", subject)
|
||||
m.SetBody("text/html", content)
|
||||
|
||||
mailch <- &EmailContext{event, m}
|
||||
mailch <- &EmailContext{events, m}
|
||||
}
|
||||
|
||||
func dialSmtp(d *gomail.Dialer) gomail.SendCloser {
|
||||
@@ -206,7 +205,7 @@ func startEmailSender(ctx *ctx.Context, smtp aconf.SMTPConfig) {
|
||||
if err == nil {
|
||||
msg = "ok"
|
||||
}
|
||||
NotifyRecord(ctx, m.event, models.Email, to, msg, err)
|
||||
NotifyRecord(ctx, m.events, models.Email, to, msg, err)
|
||||
}
|
||||
|
||||
size++
|
||||
|
||||
@@ -54,8 +54,7 @@ func (fs *FeishuSender) CallBack(ctx CallBackContext) {
|
||||
},
|
||||
}
|
||||
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback",
|
||||
ctx.Stats, ctx.Events[0])
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback", ctx.Stats, ctx.Events)
|
||||
}
|
||||
|
||||
func (fs *FeishuSender) Send(ctx MessageContext) {
|
||||
@@ -77,7 +76,7 @@ func (fs *FeishuSender) Send(ctx MessageContext) {
|
||||
IsAtAll: false,
|
||||
}
|
||||
}
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Feishu, ctx.Stats, ctx.Events[0])
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Feishu, ctx.Stats, ctx.Events)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,8 +135,7 @@ func (fs *FeishuCardSender) CallBack(ctx CallBackContext) {
|
||||
}
|
||||
parsedURL.RawQuery = ""
|
||||
|
||||
doSendAndRecord(ctx.Ctx, parsedURL.String(), parsedURL.String(), body, "callback",
|
||||
ctx.Stats, ctx.Events[0])
|
||||
doSendAndRecord(ctx.Ctx, parsedURL.String(), parsedURL.String(), body, "callback", ctx.Stats, ctx.Events)
|
||||
}
|
||||
|
||||
func (fs *FeishuCardSender) Send(ctx MessageContext) {
|
||||
@@ -160,8 +159,7 @@ 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[0])
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.FeishuCard, ctx.Stats, ctx.Events)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,7 @@ func (lk *LarkSender) CallBack(ctx CallBackContext) {
|
||||
},
|
||||
}
|
||||
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback",
|
||||
ctx.Stats, ctx.Events[0])
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback", ctx.Stats, ctx.Events)
|
||||
}
|
||||
|
||||
func (lk *LarkSender) Send(ctx MessageContext) {
|
||||
@@ -44,7 +43,7 @@ func (lk *LarkSender) Send(ctx MessageContext) {
|
||||
Text: message,
|
||||
},
|
||||
}
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Lark, ctx.Stats, ctx.Events[0])
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Lark, ctx.Stats, ctx.Events)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,8 +56,7 @@ func (fs *LarkCardSender) CallBack(ctx CallBackContext) {
|
||||
}
|
||||
parsedURL.RawQuery = ""
|
||||
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback",
|
||||
ctx.Stats, ctx.Events[0])
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback", ctx.Stats, ctx.Events)
|
||||
}
|
||||
|
||||
func (fs *LarkCardSender) Send(ctx MessageContext) {
|
||||
@@ -81,7 +80,7 @@ func (fs *LarkCardSender) 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.LarkCard, ctx.Stats, ctx.Events[0])
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.LarkCard, ctx.Stats, ctx.Events)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ func (ms *MmSender) Send(ctx MessageContext) {
|
||||
Text: message,
|
||||
Tokens: urls,
|
||||
Stats: ctx.Stats,
|
||||
}, ctx.Events[0], models.Mm)
|
||||
}, ctx.Events, models.Mm)
|
||||
}
|
||||
|
||||
func (ms *MmSender) CallBack(ctx CallBackContext) {
|
||||
@@ -56,7 +56,7 @@ func (ms *MmSender) CallBack(ctx CallBackContext) {
|
||||
Text: message,
|
||||
Tokens: []string{ctx.CallBackURL},
|
||||
Stats: ctx.Stats,
|
||||
}, ctx.Events[0], "callback")
|
||||
}, ctx.Events, "callback")
|
||||
}
|
||||
|
||||
func (ms *MmSender) extract(users []*models.User) []string {
|
||||
@@ -69,12 +69,12 @@ func (ms *MmSender) extract(users []*models.User) []string {
|
||||
return tokens
|
||||
}
|
||||
|
||||
func SendMM(ctx *ctx.Context, message MatterMostMessage, event *models.AlertCurEvent, channel string) {
|
||||
func SendMM(ctx *ctx.Context, message MatterMostMessage, events []*models.AlertCurEvent, channel string) {
|
||||
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, event, channel, message.Tokens[i], "", err)
|
||||
NotifyRecord(ctx, events, channel, message.Tokens[i], "", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ func SendMM(ctx *ctx.Context, message MatterMostMessage, event *models.AlertCurE
|
||||
Username: username,
|
||||
Text: txt + message.Text,
|
||||
}
|
||||
doSendAndRecord(ctx, ur, message.Tokens[i], body, channel, message.Stats, event)
|
||||
doSendAndRecord(ctx, ur, message.Tokens[i], body, channel, message.Stats, events)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, event, channel, cmd.String(), "", buildErr(err, isTimeout))
|
||||
NotifyRecord(ctx, []*models.AlertCurEvent{event}, channel, cmd.String(), "", buildErr(err, isTimeout))
|
||||
|
||||
if isTimeout {
|
||||
if err == nil {
|
||||
|
||||
@@ -41,7 +41,7 @@ func (ts *TelegramSender) CallBack(ctx CallBackContext) {
|
||||
Text: message,
|
||||
Tokens: []string{ctx.CallBackURL},
|
||||
Stats: ctx.Stats,
|
||||
}, ctx.Events[0], "callback")
|
||||
}, ctx.Events, "callback")
|
||||
}
|
||||
|
||||
func (ts *TelegramSender) Send(ctx MessageContext) {
|
||||
@@ -55,7 +55,7 @@ func (ts *TelegramSender) Send(ctx MessageContext) {
|
||||
Text: message,
|
||||
Tokens: tokens,
|
||||
Stats: ctx.Stats,
|
||||
}, ctx.Events[0], models.Telegram)
|
||||
}, ctx.Events, models.Telegram)
|
||||
}
|
||||
|
||||
func (ts *TelegramSender) extract(users []*models.User) []string {
|
||||
@@ -68,11 +68,11 @@ func (ts *TelegramSender) extract(users []*models.User) []string {
|
||||
return tokens
|
||||
}
|
||||
|
||||
func SendTelegram(ctx *ctx.Context, message TelegramMessage, event *models.AlertCurEvent, channel string) {
|
||||
func SendTelegram(ctx *ctx.Context, message TelegramMessage, events []*models.AlertCurEvent, channel string) {
|
||||
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, event, channel, message.Tokens[i], "", errors.New("invalid token"))
|
||||
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, event *models.Alert
|
||||
Text: message.Text,
|
||||
}
|
||||
|
||||
doSendAndRecord(ctx, url, message.Tokens[i], body, channel, message.Stats, event)
|
||||
doSendAndRecord(ctx, url, message.Tokens[i], body, channel, message.Stats, events)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,17 +59,21 @@ func sendWebhook(webhook *models.Webhook, event interface{}, stats *astats.Stats
|
||||
if webhook != nil {
|
||||
insecureSkipVerify = webhook.SkipVerify
|
||||
}
|
||||
client := http.Client{
|
||||
Timeout: time.Duration(conf.Timeout) * time.Second,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureSkipVerify},
|
||||
},
|
||||
|
||||
if conf.Client == nil {
|
||||
logger.Warningf("event_%s, event:%s, url: [%s], error: [%s]", channel, string(bs), conf.Url, "client is nil")
|
||||
conf.Client = &http.Client{
|
||||
Timeout: time.Duration(conf.Timeout) * time.Second,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureSkipVerify},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
stats.AlertNotifyTotal.WithLabelValues(channel).Inc()
|
||||
var resp *http.Response
|
||||
var body []byte
|
||||
resp, err = client.Do(req)
|
||||
resp, err = conf.Client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
stats.AlertNotifyErrorTotal.WithLabelValues(channel).Inc()
|
||||
@@ -91,12 +95,12 @@ func sendWebhook(webhook *models.Webhook, event interface{}, stats *astats.Stats
|
||||
return false, string(body), nil
|
||||
}
|
||||
|
||||
func SingleSendWebhooks(ctx *ctx.Context, webhooks []*models.Webhook, event *models.AlertCurEvent, stats *astats.Stats) {
|
||||
func SingleSendWebhooks(ctx *ctx.Context, webhooks map[string]*models.Webhook, event *models.AlertCurEvent, stats *astats.Stats) {
|
||||
for _, conf := range webhooks {
|
||||
retryCount := 0
|
||||
for retryCount < 3 {
|
||||
needRetry, res, err := sendWebhook(conf, event, stats)
|
||||
NotifyRecord(ctx, event, "webhook", conf.Url, res, err)
|
||||
NotifyRecord(ctx, []*models.AlertCurEvent{event}, "webhook", conf.Url, res, err)
|
||||
if !needRetry {
|
||||
break
|
||||
}
|
||||
@@ -106,7 +110,7 @@ func SingleSendWebhooks(ctx *ctx.Context, webhooks []*models.Webhook, event *mod
|
||||
}
|
||||
}
|
||||
|
||||
func BatchSendWebhooks(ctx *ctx.Context, webhooks []*models.Webhook, event *models.AlertCurEvent, stats *astats.Stats) {
|
||||
func BatchSendWebhooks(ctx *ctx.Context, webhooks map[string]*models.Webhook, event *models.AlertCurEvent, stats *astats.Stats) {
|
||||
for _, conf := range webhooks {
|
||||
logger.Infof("push event:%+v to queue:%v", event, conf)
|
||||
PushEvent(ctx, conf, event, stats)
|
||||
@@ -166,7 +170,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 RecordEvents(ctx, webhook, events, stats, res, err)
|
||||
go NotifyRecord(ctx, events, "webhook", webhook.Url, res, err)
|
||||
if !needRetry {
|
||||
break
|
||||
}
|
||||
@@ -176,10 +180,3 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,7 @@ func (ws *WecomSender) CallBack(ctx CallBackContext) {
|
||||
},
|
||||
}
|
||||
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback",
|
||||
ctx.Stats, ctx.Events[0])
|
||||
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback", ctx.Stats, ctx.Events)
|
||||
}
|
||||
|
||||
func (ws *WecomSender) Send(ctx MessageContext) {
|
||||
@@ -54,7 +53,7 @@ func (ws *WecomSender) Send(ctx MessageContext) {
|
||||
Content: message,
|
||||
},
|
||||
}
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Wecom, ctx.Stats, ctx.Events[0])
|
||||
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Wecom, ctx.Stats, ctx.Events)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ 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"
|
||||
@@ -103,6 +104,9 @@ 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()
|
||||
|
||||
@@ -280,6 +280,7 @@ 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)
|
||||
@@ -321,6 +322,11 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.GET("/share-charts", rt.chartShareGets)
|
||||
pages.POST("/share-charts", rt.auth(), rt.chartShareAdd)
|
||||
|
||||
pages.POST("/dashboard-annotations", rt.auth(), rt.user(), rt.perm("/dashboards/put"), rt.dashAnnotationAdd)
|
||||
pages.GET("/dashboard-annotations", rt.dashAnnotationGets)
|
||||
pages.PUT("/dashboard-annotation/:id", rt.auth(), rt.user(), rt.perm("/dashboards/put"), rt.dashAnnotationPut)
|
||||
pages.DELETE("/dashboard-annotation/:id", rt.auth(), rt.user(), rt.perm("/dashboards/del"), rt.dashAnnotationDel)
|
||||
|
||||
// pages.GET("/alert-rules/builtin/alerts-cates", rt.auth(), rt.user(), rt.builtinAlertCateGets)
|
||||
// pages.GET("/alert-rules/builtin/list", rt.auth(), rt.user(), rt.builtinAlertRules)
|
||||
pages.GET("/alert-rules/callbacks", rt.auth(), rt.user(), rt.alertRuleCallbacks)
|
||||
@@ -478,6 +484,7 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.PUT("/builtin-payloads", rt.auth(), rt.user(), rt.perm("/built-in-components/put"), rt.builtinPayloadsPut)
|
||||
pages.DELETE("/builtin-payloads", rt.auth(), rt.user(), rt.perm("/built-in-components/del"), rt.builtinPayloadsDel)
|
||||
pages.GET("/builtin-payload", rt.auth(), rt.user(), rt.builtinPayloadsGetByUUIDOrID)
|
||||
|
||||
}
|
||||
|
||||
r.GET("/api/n9e/versions", func(c *gin.Context) {
|
||||
|
||||
@@ -36,8 +36,9 @@ func (rt *Router) builtinComponentsAdd(c *gin.Context) {
|
||||
|
||||
func (rt *Router) builtinComponentsGets(c *gin.Context) {
|
||||
query := ginx.QueryStr(c, "query", "")
|
||||
disabled := ginx.QueryInt(c, "disabled", -1)
|
||||
|
||||
bc, err := models.BuiltinComponentGets(rt.Ctx, query)
|
||||
bc, err := models.BuiltinComponentGets(rt.Ctx, query, disabled)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
ginx.NewRender(c).Data(bc, nil)
|
||||
|
||||
@@ -140,3 +140,12 @@ 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)
|
||||
}
|
||||
|
||||
99
center/router/router_dash_annotation.go
Normal file
99
center/router/router_dash_annotation.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
)
|
||||
|
||||
func checkAnnotationPermission(c *gin.Context, ctx *ctx.Context, dashboardId int64) {
|
||||
dashboard, err := models.BoardGetByID(ctx, dashboardId)
|
||||
if err != nil {
|
||||
ginx.Bomb(http.StatusInternalServerError, "failed to get dashboard: %v", err)
|
||||
}
|
||||
|
||||
if dashboard == nil {
|
||||
ginx.Bomb(http.StatusNotFound, "dashboard not found")
|
||||
}
|
||||
|
||||
bg := BusiGroup(ctx, dashboard.GroupId)
|
||||
me := c.MustGet("user").(*models.User)
|
||||
can, err := me.CanDoBusiGroup(ctx, bg, "rw")
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if !can {
|
||||
ginx.Bomb(http.StatusForbidden, "forbidden")
|
||||
}
|
||||
}
|
||||
|
||||
func (rt *Router) dashAnnotationAdd(c *gin.Context) {
|
||||
var f models.DashAnnotation
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
username := c.MustGet("username").(string)
|
||||
now := time.Now().Unix()
|
||||
|
||||
checkAnnotationPermission(c, rt.Ctx, f.DashboardId)
|
||||
|
||||
f.CreateBy = username
|
||||
f.CreateAt = now
|
||||
f.UpdateBy = username
|
||||
f.UpdateAt = now
|
||||
|
||||
ginx.NewRender(c).Data(f.Id, f.Add(rt.Ctx))
|
||||
}
|
||||
|
||||
func (rt *Router) dashAnnotationGets(c *gin.Context) {
|
||||
dashboardId := ginx.QueryInt64(c, "dashboard_id")
|
||||
from := ginx.QueryInt64(c, "from")
|
||||
to := ginx.QueryInt64(c, "to")
|
||||
limit := ginx.QueryInt(c, "limit", 100)
|
||||
|
||||
lst, err := models.DashAnnotationGets(rt.Ctx, dashboardId, from, to, limit)
|
||||
ginx.NewRender(c).Data(lst, err)
|
||||
}
|
||||
|
||||
func (rt *Router) dashAnnotationPut(c *gin.Context) {
|
||||
var f models.DashAnnotation
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
id := ginx.UrlParamInt64(c, "id")
|
||||
annotation, err := getAnnotationById(rt.Ctx, id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
checkAnnotationPermission(c, rt.Ctx, annotation.DashboardId)
|
||||
|
||||
f.Id = id
|
||||
f.UpdateAt = time.Now().Unix()
|
||||
f.UpdateBy = c.MustGet("username").(string)
|
||||
|
||||
ginx.NewRender(c).Message(f.Update(rt.Ctx))
|
||||
}
|
||||
|
||||
func (rt *Router) dashAnnotationDel(c *gin.Context) {
|
||||
id := ginx.UrlParamInt64(c, "id")
|
||||
|
||||
annotation, err := getAnnotationById(rt.Ctx, id)
|
||||
ginx.Dangerous(err)
|
||||
checkAnnotationPermission(c, rt.Ctx, annotation.DashboardId)
|
||||
|
||||
ginx.NewRender(c).Message(models.DashAnnotationDel(rt.Ctx, id))
|
||||
}
|
||||
|
||||
// 可以提取获取注释的通用方法
|
||||
func getAnnotationById(ctx *ctx.Context, id int64) (*models.DashAnnotation, error) {
|
||||
annotation, err := models.DashAnnotationGet(ctx, "id=?", id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if annotation == nil {
|
||||
return nil, fmt.Errorf("annotation not found")
|
||||
}
|
||||
return annotation, nil
|
||||
}
|
||||
@@ -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)
|
||||
err := models.TargetOverrideBgids(ctx, []string{target.Ident}, groupIds, nil)
|
||||
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})
|
||||
err := models.TargetBindBgids(ctx, []string{target.Ident}, []int64{groupId}, nil)
|
||||
if err != nil {
|
||||
logger.Warningf("update target:%s group ids failed, err: %v", target.Ident, err)
|
||||
}
|
||||
|
||||
@@ -130,9 +130,9 @@ func (rt *Router) User() gin.HandlerFunc {
|
||||
|
||||
func (rt *Router) user() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
userid := c.MustGet("userid").(int64)
|
||||
username := c.MustGet("username").(string)
|
||||
|
||||
user, err := models.UserGetById(rt.Ctx, userid)
|
||||
user, err := models.UserGetByUsername(rt.Ctx, username)
|
||||
if err != nil {
|
||||
ginx.Bomb(http.StatusUnauthorized, "unauthorized")
|
||||
}
|
||||
|
||||
@@ -35,11 +35,18 @@ type Record struct {
|
||||
|
||||
// notificationRecordAdd
|
||||
func (rt *Router) notificationRecordAdd(c *gin.Context) {
|
||||
var req models.NotificaitonRecord
|
||||
var req []*models.NotificaitonRecord
|
||||
ginx.BindJSON(c, &req)
|
||||
err := req.Add(rt.Ctx)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(req.Id, err)
|
||||
ginx.NewRender(c).Data(ids, err)
|
||||
}
|
||||
|
||||
func (rt *Router) notificationRecordList(c *gin.Context) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/storage"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -169,7 +170,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)
|
||||
}
|
||||
|
||||
@@ -272,11 +273,9 @@ func (rt *Router) validateTags(tags []string) error {
|
||||
}
|
||||
|
||||
func (rt *Router) addTagsToTarget(target *models.Target, tags []string) error {
|
||||
hostTagsMap := target.GetHostTagsMap()
|
||||
for _, tag := range tags {
|
||||
tagKey := strings.Split(tag, "=")[0]
|
||||
if _, ok := hostTagsMap[tagKey]; ok ||
|
||||
strings.Contains(target.Tags, tagKey+"=") {
|
||||
if _, exist := target.TagsMap[tagKey]; exist {
|
||||
return fmt.Errorf("duplicate tagkey(%s)", tagKey)
|
||||
}
|
||||
}
|
||||
@@ -397,9 +396,26 @@ 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
|
||||
}
|
||||
|
||||
func haveNeverGroupedIdent(ctx *ctx.Context, idents []string) (bool, error) {
|
||||
for _, ident := range idents {
|
||||
bgids, err := models.TargetGroupIdsGetByIdent(ctx, ident)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(bgids) <= 0 {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
|
||||
func (rt *Router) targetBindBgids(c *gin.Context) {
|
||||
var f targetBgidsForm
|
||||
var err error
|
||||
@@ -442,21 +458,25 @@ func (rt *Router) targetBindBgids(c *gin.Context) {
|
||||
ginx.Bomb(http.StatusForbidden, "No permission. You are not admin of BG(%s)", bg.Name)
|
||||
}
|
||||
}
|
||||
isNeverGrouped, checkErr := haveNeverGroupedIdent(rt.Ctx, f.Idents)
|
||||
ginx.Dangerous(checkErr)
|
||||
|
||||
can, err := user.CheckPerm(rt.Ctx, "/targets/bind")
|
||||
ginx.Dangerous(err)
|
||||
if !can {
|
||||
ginx.Bomb(http.StatusForbidden, "No permission. Only admin can assign BG")
|
||||
if isNeverGrouped {
|
||||
can, err := user.CheckPerm(rt.Ctx, "/targets/bind")
|
||||
ginx.Dangerous(err)
|
||||
if !can {
|
||||
ginx.Bomb(http.StatusForbidden, "No permission. Only admin can assign BG")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch f.Action {
|
||||
case "add":
|
||||
ginx.NewRender(c).Data(failedResults, models.TargetBindBgids(rt.Ctx, f.Idents, f.Bgids))
|
||||
ginx.NewRender(c).Data(failedResults, models.TargetBindBgids(rt.Ctx, f.Idents, f.Bgids, f.Tags))
|
||||
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))
|
||||
ginx.NewRender(c).Data(failedResults, models.TargetOverrideBgids(rt.Ctx, f.Idents, f.Bgids, f.Tags))
|
||||
default:
|
||||
ginx.Bomb(http.StatusBadRequest, "invalid action")
|
||||
}
|
||||
@@ -478,7 +498,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}))
|
||||
ginx.NewRender(c).Data(failedResults, models.TargetOverrideBgids(rt.Ctx, f.Idents, []int64{f.Bgid}, nil))
|
||||
}
|
||||
|
||||
type identsForm struct {
|
||||
|
||||
@@ -33,7 +33,7 @@ type ClusterOptions struct {
|
||||
MaxIdleConnsPerHost int
|
||||
}
|
||||
|
||||
func Parse(fpath string, configPtr interface{}) error {
|
||||
func Parse(fpath string, configPtr *Config) error {
|
||||
var (
|
||||
tBuf []byte
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ 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"
|
||||
@@ -73,6 +74,9 @@ 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()
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ Enable = true
|
||||
# user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[HTTP.APIForService]
|
||||
Enable = true
|
||||
Enable = false
|
||||
[HTTP.APIForService.BasicAuth]
|
||||
user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ Enable = true
|
||||
# user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[HTTP.APIForService]
|
||||
Enable = true
|
||||
Enable = false
|
||||
[HTTP.APIForService.BasicAuth]
|
||||
user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
network_mode: host
|
||||
|
||||
prometheus:
|
||||
image: prom/prometheus
|
||||
image: prom/prometheus:v2.55.1
|
||||
container_name: prometheus
|
||||
hostname: prometheus
|
||||
restart: always
|
||||
|
||||
@@ -50,7 +50,7 @@ Enable = true
|
||||
# user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[HTTP.APIForService]
|
||||
Enable = true
|
||||
Enable = false
|
||||
[HTTP.APIForService.BasicAuth]
|
||||
user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
|
||||
@@ -790,6 +790,7 @@ CREATE TABLE es_index_pattern (
|
||||
time_field varchar(128) not null default '@timestamp',
|
||||
allow_hide_system_indices smallint not null default 0,
|
||||
fields_format varchar(4096) not null default '',
|
||||
cross_cluster_enabled int not null default 0,
|
||||
create_at bigint default '0',
|
||||
create_by varchar(64) default '',
|
||||
update_at bigint default '0',
|
||||
@@ -859,6 +860,7 @@ CREATE TABLE builtin_components (
|
||||
ident VARCHAR(191) NOT NULL,
|
||||
logo VARCHAR(191) NOT NULL,
|
||||
readme TEXT NOT NULL,
|
||||
disabled INT NOT NULL DEFAULT 0,
|
||||
created_at BIGINT NOT NULL DEFAULT 0,
|
||||
created_by VARCHAR(191) NOT NULL DEFAULT '',
|
||||
updated_at BIGINT NOT NULL DEFAULT 0,
|
||||
@@ -885,4 +887,20 @@ CREATE TABLE builtin_payloads (
|
||||
CREATE INDEX idx_component ON builtin_payloads (component);
|
||||
CREATE INDEX idx_builtin_payloads_name ON builtin_payloads (name);
|
||||
CREATE INDEX idx_cate ON builtin_payloads (cate);
|
||||
CREATE INDEX idx_type ON builtin_payloads (type);
|
||||
CREATE INDEX idx_type ON builtin_payloads (type);
|
||||
|
||||
|
||||
CREATE TABLE dash_annotation (
|
||||
id bigserial PRIMARY KEY,
|
||||
dashboard_id bigint not null,
|
||||
panel_id varchar(191) not null,
|
||||
tags text,
|
||||
description text,
|
||||
config text,
|
||||
time_start bigint not null default 0,
|
||||
time_end bigint not null default 0,
|
||||
create_at bigint not null default 0,
|
||||
create_by varchar(64) not null default '',
|
||||
update_at bigint not null default 0,
|
||||
update_by varchar(64) not null default ''
|
||||
);
|
||||
@@ -50,7 +50,7 @@ Enable = true
|
||||
# user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[HTTP.APIForService]
|
||||
Enable = true
|
||||
Enable = false
|
||||
[HTTP.APIForService.BasicAuth]
|
||||
user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,10 @@
|
||||
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';
|
||||
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;
|
||||
|
||||
@@ -116,4 +116,35 @@ 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);
|
||||
|
||||
/* v8.0.0-beta.1 2024-12-13 */
|
||||
ALTER TABLE `alert_rule` ADD COLUMN `cron_pattern` VARCHAR(64);
|
||||
ALTER TABLE `builtin_components` MODIFY COLUMN `logo` mediumtext COMMENT '''logo of component''';
|
||||
|
||||
/* v8.0.0-beta.2 2024-12-26 */
|
||||
ALTER TABLE `es_index_pattern` ADD COLUMN `cross_cluster_enabled` int not null default 0;
|
||||
|
||||
/* v8.0.0-beta.3 2024-01-03 */
|
||||
ALTER TABLE `builtin_components` ADD COLUMN `disabled` INT NOT NULL DEFAULT 0 COMMENT 'is disabled or not';
|
||||
|
||||
CREATE TABLE `dash_annotation` (
|
||||
`id` bigint unsigned not null auto_increment,
|
||||
`dashboard_id` bigint not null comment 'dashboard id',
|
||||
`panel_id` varchar(191) not null comment 'panel id',
|
||||
`tags` text comment 'tags array json string',
|
||||
`description` text comment 'annotation description',
|
||||
`config` text comment 'annotation config',
|
||||
`time_start` bigint not null default 0 comment 'start timestamp',
|
||||
`time_end` bigint not null default 0 comment 'end timestamp',
|
||||
`create_at` bigint not null default 0 comment 'create time',
|
||||
`create_by` varchar(64) not null default '' comment 'creator',
|
||||
`update_at` bigint not null default 0 comment 'update time',
|
||||
`update_by` varchar(64) not null default '' comment 'updater',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_dashboard_id` (`dashboard_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
@@ -17,6 +17,8 @@ CREATE TABLE `users` (
|
||||
`update_by` varchar(64) not null default ''
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_users_username ON `users` (username);
|
||||
|
||||
insert into `users`(id, username, nickname, password, roles, create_at, create_by, update_at, update_by) values(1, 'root', '超管', 'root.2020', 'Admin', strftime('%s', 'now'), 'system', strftime('%s', 'now'), 'system');
|
||||
|
||||
CREATE TABLE `user_group` (
|
||||
@@ -182,8 +184,9 @@ CREATE TABLE `board` (
|
||||
`create_by` varchar(64) not null default '',
|
||||
`update_at` bigint not null default 0,
|
||||
`update_by` varchar(64) not null default '',
|
||||
unique (`group_id`, `name`)
|
||||
`public_cate` bigint not null default 0
|
||||
);
|
||||
CREATE UNIQUE INDEX idx_board_group_id_name ON `board` (group_id, name);
|
||||
CREATE INDEX `idx_board_ident` ON `board` (`ident` asc);
|
||||
|
||||
-- for dashboard new version
|
||||
@@ -192,6 +195,15 @@ CREATE TABLE `board_payload` (
|
||||
`payload` mediumtext not null
|
||||
);
|
||||
|
||||
CREATE TABLE `chart` (
|
||||
`id` integer primary key autoincrement,
|
||||
`group_id` integer not null,
|
||||
`configs` text,
|
||||
`weight` integer not null default 0
|
||||
);
|
||||
|
||||
CREATE INDEX idx_chart_group_id ON `chart` (group_id);
|
||||
|
||||
CREATE TABLE `chart_share` (
|
||||
`id` integer primary key autoincrement,
|
||||
`cluster` varchar(128) not null,
|
||||
@@ -238,7 +250,9 @@ CREATE TABLE `alert_rule` (
|
||||
`create_at` bigint not null default 0,
|
||||
`create_by` varchar(64) not null default '',
|
||||
`update_at` bigint not null default 0,
|
||||
`update_by` varchar(64) not null default ''
|
||||
`update_by` varchar(64) not null default '',
|
||||
`cron_pattern` varchar(64),
|
||||
`datasource_queries` text
|
||||
);
|
||||
CREATE INDEX `idx_alert_rule_group_id` ON `alert_rule` (`group_id` asc);
|
||||
CREATE INDEX `idx_alert_rule_update_at` ON `alert_rule` (`update_at` asc);
|
||||
@@ -308,11 +322,18 @@ CREATE TABLE `target` (
|
||||
`tags` varchar(512) not null default '',
|
||||
`host_ip` varchar(15) default '',
|
||||
`agent_version` varchar(255) default '',
|
||||
`host_tags` text,
|
||||
`engine_name` varchar(255) default '',
|
||||
`os` varchar(31) default '',
|
||||
`update_at` bigint not null default 0
|
||||
);
|
||||
CREATE INDEX `idx_target_group_id` ON `target` (`group_id` asc);
|
||||
|
||||
CREATE INDEX `idx_target_group_id` ON `target` (`group_id` asc);
|
||||
CREATE UNIQUE INDEX idx_target_ident ON `target` (ident);
|
||||
CREATE INDEX idx_host_ip ON `target` (host_ip);
|
||||
CREATE INDEX idx_agent_version ON `target` (agent_version);
|
||||
CREATE INDEX idx_engine_name ON `target` (engine_name);
|
||||
CREATE INDEX idx_os ON `target` (os);
|
||||
|
||||
CREATE TABLE `metric_view` (
|
||||
`id` integer primary key autoincrement,
|
||||
@@ -337,12 +358,14 @@ CREATE TABLE `recording_rule` (
|
||||
`disabled` tinyint(1) not null default 0,
|
||||
`prom_ql` varchar(8192) not null,
|
||||
`prom_eval_interval` int not null,
|
||||
`cron_pattern` varchar(255) default '',
|
||||
`append_tags` varchar(255) default '',
|
||||
`query_configs` text not null,
|
||||
`create_at` bigint default '0',
|
||||
`create_by` varchar(64) default '',
|
||||
`update_at` bigint default '0',
|
||||
`update_by` varchar(64) default ''
|
||||
`update_by` varchar(64) default '',
|
||||
`datasource_queries` text
|
||||
);
|
||||
CREATE INDEX `idx_recording_rule_group_id` ON `recording_rule` (`group_id` asc);
|
||||
CREATE INDEX `idx_recording_rule_update_at` ON `recording_rule` (`update_at` asc);
|
||||
@@ -430,6 +453,7 @@ CREATE TABLE `alert_his_event` (
|
||||
`trigger_value` varchar(2048) not null,
|
||||
`recover_time` bigint not null default 0,
|
||||
`last_eval_time` bigint not null default 0,
|
||||
`original_tags` varchar(8192),
|
||||
`tags` varchar(1024) not null default '',
|
||||
`annotations` text not null,
|
||||
`rule_config` text not null
|
||||
@@ -459,6 +483,8 @@ CREATE INDEX `idx_builtin_components_ident` ON `builtin_components` (`ident` asc
|
||||
|
||||
CREATE TABLE `builtin_payloads` (
|
||||
`id` integer primary key autoincrement,
|
||||
`component_id` integer not null default 0,
|
||||
`uuid` integer not null,
|
||||
`type` varchar(191) not null,
|
||||
`component` varchar(191) not null,
|
||||
`cate` varchar(191) not null,
|
||||
@@ -474,6 +500,20 @@ CREATE INDEX `idx_builtin_payloads_component` ON `builtin_payloads` (`component`
|
||||
CREATE INDEX `idx_builtin_payloads_name` ON `builtin_payloads` (`name` asc);
|
||||
CREATE INDEX `idx_builtin_payloads_cate` ON `builtin_payloads` (`cate` asc);
|
||||
CREATE INDEX `idx_builtin_payloads_type` ON `builtin_payloads` (`type` asc);
|
||||
CREATE INDEX idx_uuid ON `builtin_payloads` (uuid);
|
||||
|
||||
|
||||
CREATE TABLE `notification_record` (
|
||||
`id` integer primary key autoincrement,
|
||||
`event_id` integer not null,
|
||||
`sub_id` integer,
|
||||
`channel` varchar(255) not null,
|
||||
`status` integer,
|
||||
`target` varchar(1024) not null,
|
||||
`details` varchar(2048) default '',
|
||||
`created_at` integer not null
|
||||
);
|
||||
CREATE INDEX idx_evt ON notification_record (event_id);
|
||||
|
||||
CREATE TABLE `task_tpl` (
|
||||
`id` integer primary key autoincrement,
|
||||
@@ -553,6 +593,8 @@ CREATE TABLE `datasource`
|
||||
`updated_by` varchar(64) not null default ''
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_datasource_name ON datasource (name);
|
||||
|
||||
CREATE TABLE `builtin_cate` (
|
||||
`id` integer primary key autoincrement,
|
||||
`name` varchar(191) not null,
|
||||
@@ -570,6 +612,8 @@ CREATE TABLE `notify_tpl` (
|
||||
`update_by` varchar(64) not null default ''
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_notify_tpl_channel ON notify_tpl (channel);
|
||||
|
||||
CREATE TABLE `sso_config` (
|
||||
`id` integer primary key autoincrement,
|
||||
`name` varchar(191) not null unique,
|
||||
@@ -577,6 +621,8 @@ CREATE TABLE `sso_config` (
|
||||
`update_at` bigint not null default 0
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_sso_config_name ON sso_config (name);
|
||||
|
||||
CREATE TABLE `es_index_pattern` (
|
||||
`id` integer primary key autoincrement,
|
||||
`datasource_id` bigint not null default 0,
|
||||
@@ -584,6 +630,7 @@ CREATE TABLE `es_index_pattern` (
|
||||
`time_field` varchar(128) not null default '@timestamp',
|
||||
`allow_hide_system_indices` tinyint(1) not null default 0,
|
||||
`fields_format` varchar(4096) not null default '',
|
||||
`cross_cluster_enabled` int not null default 0,
|
||||
`create_at` bigint default '0',
|
||||
`create_by` varchar(64) default '',
|
||||
`update_at` bigint default '0',
|
||||
@@ -591,6 +638,8 @@ CREATE TABLE `es_index_pattern` (
|
||||
unique (`datasource_id`, `name`)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_es_index_pattern_datasource_id_name ON es_index_pattern (datasource_id, name);
|
||||
|
||||
CREATE TABLE `builtin_metrics` (
|
||||
`id` integer primary key autoincrement,
|
||||
`collector` varchar(191) NOT NULL,
|
||||
@@ -603,13 +652,15 @@ CREATE TABLE `builtin_metrics` (
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_by` varchar(191) NOT NULL DEFAULT '',
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_by` varchar(191) NOT NULL DEFAULT ''
|
||||
`updated_by` varchar(191) NOT NULL DEFAULT '',
|
||||
`uuid integer` not null default 0
|
||||
);
|
||||
-- CREATE UNIQUE INDEX `idx_builtin_metrics_collector_typ_name` ON `builtin_metrics` (`lang`,`collector`, `typ`, `name` asc);
|
||||
-- CREATE INDEX `idx_builtin_metrics_collector` ON `builtin_metrics` (`collector` asc);
|
||||
-- CREATE INDEX `idx_builtin_metrics_typ` ON `builtin_metrics` (`typ` asc);
|
||||
-- CREATE INDEX `idx_builtin_metrics_name` ON `builtin_metrics` (`name` asc);
|
||||
-- CREATE INDEX `idx_builtin_metrics_lang` ON `builtin_metrics` (`lang` asc);
|
||||
|
||||
CREATE UNIQUE INDEX idx_collector_typ_name ON builtin_metrics (lang, collector, typ, name);
|
||||
CREATE INDEX idx_collector ON builtin_metrics (collector);
|
||||
CREATE INDEX idx_typ ON builtin_metrics (typ);
|
||||
CREATE INDEX idx_builtinmetric_name ON builtin_metrics (name);
|
||||
CREATE INDEX idx_lang ON builtin_metrics (lang);
|
||||
|
||||
|
||||
CREATE TABLE `metric_filter` (
|
||||
@@ -624,6 +675,30 @@ CREATE TABLE `metric_filter` (
|
||||
);
|
||||
CREATE INDEX `idx_metric_filter_name` ON `metric_filter` (`name` asc);
|
||||
|
||||
CREATE TABLE `target_busi_group` (
|
||||
`id` integer primary key autoincrement,
|
||||
`target_ident` varchar(191) not null,
|
||||
`group_id` integer not null,
|
||||
`update_at` integer not null
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_target_busi_group ON target_busi_group (target_ident, group_id);
|
||||
|
||||
|
||||
CREATE TABLE `dash_annotation` (
|
||||
`id` integer primary key autoincrement,
|
||||
`dashboard_id` bigint not null,
|
||||
`panel_id` varchar(191) not null,
|
||||
`tags` text,
|
||||
`description` text,
|
||||
`config` text,
|
||||
`time_start` bigint not null default 0,
|
||||
`time_end` bigint not null default 0,
|
||||
`create_at` bigint not null default 0,
|
||||
`create_by` varchar(64) not null default '',
|
||||
`update_at` bigint not null default 0,
|
||||
`update_by` varchar(64) not null default ''
|
||||
);
|
||||
|
||||
CREATE TABLE `task_meta`
|
||||
(
|
||||
|
||||
@@ -50,7 +50,7 @@ Enable = true
|
||||
# user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[HTTP.APIForService]
|
||||
Enable = true
|
||||
Enable = false
|
||||
[HTTP.APIForService.BasicAuth]
|
||||
user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
@@ -73,14 +73,14 @@ DefaultRoles = ["Standard"]
|
||||
OpenRSA = false
|
||||
|
||||
[DB]
|
||||
# mysql postgres sqlite
|
||||
DBType = "sqlite"
|
||||
# postgres: host=%s port=%s user=%s dbname=%s password=%s sslmode=%s
|
||||
# postgres: DSN="host=127.0.0.1 port=5432 user=root dbname=n9e_v6 password=1234 sslmode=disable"
|
||||
# sqlite: DSN="/path/to/filename.db"
|
||||
DSN = "root:1234@tcp(127.0.0.1:3306)/n9e_v6?charset=utf8mb4&parseTime=True&loc=Local&allowNativePasswords=true"
|
||||
# mysql: DSN="root:1234@tcp(localhost:3306)/n9e_v6?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
DSN = "n9e.db"
|
||||
# enable debug mode or not
|
||||
Debug = false
|
||||
# mysql postgres sqlite
|
||||
DBType = "mysql"
|
||||
# unit: s
|
||||
MaxLifetime = 7200
|
||||
# max open connections
|
||||
@@ -98,8 +98,8 @@ Address = "127.0.0.1:6379"
|
||||
# DB = 0
|
||||
# UseTLS = false
|
||||
# TLSMinVersion = "1.2"
|
||||
# standalone cluster sentinel
|
||||
RedisType = "standalone"
|
||||
# standalone cluster sentinel miniredis
|
||||
RedisType = "miniredis"
|
||||
# Mastername for sentinel type
|
||||
# MasterName = "mymaster"
|
||||
# SentinelUsername = ""
|
||||
@@ -138,6 +138,9 @@ ForceUseServerTS = true
|
||||
# [Pushgw.WriterOpt]
|
||||
# QueueMaxSize = 1000000
|
||||
# QueuePopSize = 1000
|
||||
# AllQueueMaxSize = 1000000
|
||||
# fresh time, unit ms
|
||||
# AllQueueMaxSizeInterval = 200
|
||||
|
||||
[[Pushgw.Writers]]
|
||||
# Url = "http://127.0.0.1:8480/insert/0/prometheus/api/v1/write"
|
||||
|
||||
@@ -54,7 +54,7 @@ Enable = true
|
||||
# user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[HTTP.APIForService]
|
||||
Enable = true
|
||||
Enable = false
|
||||
[HTTP.APIForService.BasicAuth]
|
||||
user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
|
||||
34
go.mod
34
go.mod
@@ -4,6 +4,7 @@ go 1.22
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/VictoriaMetrics/metricsql v0.81.1
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
@@ -11,6 +12,7 @@ require (
|
||||
github.com/flashcatcloud/ibex v1.3.5
|
||||
github.com/gin-contrib/pprof v1.4.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.4
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
@@ -40,15 +42,33 @@ require (
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gorm.io/driver/mysql v1.4.4
|
||||
gorm.io/driver/postgres v1.4.5
|
||||
gorm.io/driver/postgres v1.5.11
|
||||
gorm.io/driver/sqlite v1.5.5
|
||||
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde
|
||||
gorm.io/gorm v1.25.12
|
||||
)
|
||||
|
||||
require github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
require (
|
||||
github.com/VictoriaMetrics/metrics v1.34.0 // indirect
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.1 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/valyala/fastrand v1.1.0 // indirect
|
||||
github.com/valyala/histogram v1.2.0 // indirect
|
||||
github.com/yuin/gopher-lua v1.1.1 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/sqlite v1.23.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect
|
||||
github.com/alicebob/miniredis/v2 v2.33.0
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
@@ -74,7 +94,7 @@ require (
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.3.1 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgtype v1.12.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.17.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
@@ -97,11 +117,11 @@ require (
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/automaxprocs v1.5.2 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/image v0.18.0 // indirect
|
||||
golang.org/x/net v0.23.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
|
||||
66
go.sum
66
go.sum
@@ -13,8 +13,16 @@ 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/VictoriaMetrics/metrics v1.34.0 h1:0i8k/gdOJdSoZB4Z9pikVnVQXfhcIvnG7M7h2WaQW2w=
|
||||
github.com/VictoriaMetrics/metrics v1.34.0/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8=
|
||||
github.com/VictoriaMetrics/metricsql v0.81.1 h1:1gpqI3Mwru1tCM8nZiKxBG0P+DNkjlRwLhRPII3cuho=
|
||||
github.com/VictoriaMetrics/metricsql v0.81.1/go.mod h1:1g4hdCwlbJZ851PU9VN65xy9Rdlzupo6fx3SNZ8Z64U=
|
||||
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/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUiji6lZrA=
|
||||
github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0=
|
||||
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=
|
||||
@@ -49,6 +57,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/expr-lang/expr v1.16.1 h1:Na8CUcMdyGbnNpShY7kzcHCU7WqxuL+hnxgHZ4vaz/A=
|
||||
github.com/expr-lang/expr v1.16.1/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ=
|
||||
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
|
||||
@@ -67,6 +77,10 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
@@ -116,6 +130,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
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/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA=
|
||||
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -154,6 +170,8 @@ github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y
|
||||
github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||
@@ -166,10 +184,15 @@ github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQ
|
||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||
github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
|
||||
github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
|
||||
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
|
||||
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
|
||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
@@ -273,6 +296,9 @@ github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ=
|
||||
github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc=
|
||||
github.com/redis/go-redis/v9 v9.0.2 h1:BA426Zqe/7r56kCcvxYLWe1mkaz71LKF77GwgFzSxfE=
|
||||
github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62/go.mod h1:65XQgovT59RWatovFwnwocoUxiI/eENTnOY5GK3STuY=
|
||||
@@ -325,9 +351,15 @@ github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
|
||||
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
|
||||
github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
@@ -364,8 +396,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
|
||||
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
|
||||
@@ -401,8 +433,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.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 +457,10 @@ 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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.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=
|
||||
@@ -444,8 +478,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
@@ -496,11 +530,23 @@ gorm.io/driver/mysql v1.4.4 h1:MX0K9Qvy0Na4o7qSC/YI7XxqUw5KDw01umqgID+svdQ=
|
||||
gorm.io/driver/mysql v1.4.4/go.mod h1:BCg8cKI+R0j/rZRQxeKis/forqRwRSYOR8OM3Wo6hOM=
|
||||
gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc=
|
||||
gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg=
|
||||
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
|
||||
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
|
||||
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
|
||||
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde h1:9DShaph9qhkIYw7QF91I/ynrr4cOO2PZra2PFD7Mfeg=
|
||||
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 377 KiB After Width: | Height: | Size: 377 KiB |
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "MongoDB by instance",
|
||||
"name": "MongoDB Overview by exporter",
|
||||
"tags": "Prometheus MongoDB",
|
||||
"ident": "",
|
||||
"configs": {
|
||||
@@ -9,11 +9,11 @@
|
||||
"id": "939298f2-b21f-4e2f-9142-c10946cc4032",
|
||||
"layout": {
|
||||
"h": 1,
|
||||
"i": "939298f2-b21f-4e2f-9142-c10946cc4032",
|
||||
"isResizable": false,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"i": "939298f2-b21f-4e2f-9142-c10946cc4032",
|
||||
"isResizable": false
|
||||
"y": 0
|
||||
},
|
||||
"name": "Basic Info",
|
||||
"type": "row"
|
||||
@@ -32,12 +32,12 @@
|
||||
"description": "instance count",
|
||||
"id": "91970d24-3f04-4424-a1ed-73e7d28f5706",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"h": 7,
|
||||
"i": "91970d24-3f04-4424-a1ed-73e7d28f5706",
|
||||
"isResizable": true,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"i": "91970d24-3f04-4424-a1ed-73e7d28f5706",
|
||||
"isResizable": true
|
||||
"y": 1
|
||||
},
|
||||
"name": "Up",
|
||||
"options": {
|
||||
@@ -77,54 +77,39 @@
|
||||
"version": "2.0.0"
|
||||
},
|
||||
{
|
||||
"type": "stat",
|
||||
"id": "c7b52e8e-b417-4c61-a15e-e2f186fccd67",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 1,
|
||||
"i": "c7b52e8e-b417-4c61-a15e-e2f186fccd67",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "3.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "mongodb_ss_uptime{instance=\"$instance\"}",
|
||||
"refId": "A",
|
||||
"maxDataPoints": 240
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"name": "Uptime",
|
||||
"description": "Uptime",
|
||||
"maxPerRow": 4,
|
||||
"custom": {
|
||||
"textMode": "value",
|
||||
"graphMode": "none",
|
||||
"colorMode": "value",
|
||||
"calc": "lastNotNull",
|
||||
"valueField": "Value",
|
||||
"colSpan": 1,
|
||||
"colorMode": "value",
|
||||
"textMode": "value",
|
||||
"textSize": {
|
||||
"title": null
|
||||
},
|
||||
"orientation": "auto"
|
||||
"valueField": "Value"
|
||||
},
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"description": "Uptime",
|
||||
"id": "c7b52e8e-b417-4c61-a15e-e2f186fccd67",
|
||||
"layout": {
|
||||
"h": 7,
|
||||
"i": "c7b52e8e-b417-4c61-a15e-e2f186fccd67",
|
||||
"isResizable": true,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 1
|
||||
},
|
||||
"name": "Uptime",
|
||||
"options": {
|
||||
"standardOptions": {
|
||||
"util": "humantimeSeconds"
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
"type": "base",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -147,51 +132,34 @@
|
||||
},
|
||||
"type": "range"
|
||||
}
|
||||
],
|
||||
"standardOptions": {
|
||||
"util": "seconds",
|
||||
"decimals": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
"targets": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byFrameRefID"
|
||||
},
|
||||
"properties": {
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#6C53B1",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
},
|
||||
"standardOptions": {
|
||||
"decimals": 0
|
||||
}
|
||||
}
|
||||
"expr": "mongodb_ss_uptime{instance=\"$instance\"}",
|
||||
"refId": "A"
|
||||
}
|
||||
]
|
||||
],
|
||||
"type": "stat",
|
||||
"version": "2.0.0"
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "8446dded-9e11-4ee9-bdad-769b193ddf3e",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"h": 7,
|
||||
"i": "8446dded-9e11-4ee9-bdad-769b193ddf3e",
|
||||
"isResizable": true,
|
||||
"w": 6,
|
||||
"x": 12,
|
||||
"y": 1,
|
||||
"i": "8446dded-9e11-4ee9-bdad-769b193ddf3e",
|
||||
"isResizable": true
|
||||
"y": 1
|
||||
},
|
||||
"version": "3.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "mongodb_ss_mem_resident * 1024 * 1024",
|
||||
"expr": "mongodb_ss_mem_resident{instance='$instance'} * 1024 * 1024",
|
||||
"legend": "{{type}}",
|
||||
"refId": "A",
|
||||
"maxDataPoints": 240
|
||||
@@ -219,8 +187,7 @@
|
||||
"selectMode": "single"
|
||||
},
|
||||
"standardOptions": {
|
||||
"util": "bytesIEC",
|
||||
"decimals": 2
|
||||
"util": "bytesIEC"
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
@@ -275,12 +242,12 @@
|
||||
"description": "Page faults indicate that requests are processed from disk either because an index is missing or there is not enough memory for the data set. Consider increasing memory or sharding out.",
|
||||
"id": "3eda28e7-2480-4ddc-b346-89ced1c33034",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"h": 7,
|
||||
"i": "3eda28e7-2480-4ddc-b346-89ced1c33034",
|
||||
"isResizable": true,
|
||||
"w": 6,
|
||||
"x": 18,
|
||||
"y": 1,
|
||||
"i": "3eda28e7-2480-4ddc-b346-89ced1c33034",
|
||||
"isResizable": true
|
||||
"y": 1
|
||||
},
|
||||
"name": "Page Faults",
|
||||
"options": {
|
||||
@@ -333,12 +300,12 @@
|
||||
"description": "Network traffic (bytes)",
|
||||
"id": "528d0485-f947-470d-95f3-59eae157ebb6",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"h": 7,
|
||||
"i": "528d0485-f947-470d-95f3-59eae157ebb6",
|
||||
"isResizable": true,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 5,
|
||||
"i": "528d0485-f947-470d-95f3-59eae157ebb6",
|
||||
"isResizable": true
|
||||
"y": 8
|
||||
},
|
||||
"name": "Network I/O",
|
||||
"options": {
|
||||
@@ -395,12 +362,12 @@
|
||||
"description": "Number of connections Keep in mind the hard limit on the maximum number of connections set by your distribution.",
|
||||
"id": "067e97c3-4e57-447f-a9dc-a49627b6ce18",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"h": 7,
|
||||
"i": "067e97c3-4e57-447f-a9dc-a49627b6ce18",
|
||||
"isResizable": true,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 5,
|
||||
"i": "067e97c3-4e57-447f-a9dc-a49627b6ce18",
|
||||
"isResizable": true
|
||||
"y": 8
|
||||
},
|
||||
"name": "Connections",
|
||||
"options": {
|
||||
@@ -450,12 +417,12 @@
|
||||
"description": "Number of assertion errors, Asserts are not important by themselves, but you can correlate spikes with other graphs.",
|
||||
"id": "9e9b7356-cf0e-4e5f-95f5-00258c576bf4",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"h": 7,
|
||||
"i": "9e9b7356-cf0e-4e5f-95f5-00258c576bf4",
|
||||
"isResizable": true,
|
||||
"w": 6,
|
||||
"x": 12,
|
||||
"y": 5,
|
||||
"i": "9e9b7356-cf0e-4e5f-95f5-00258c576bf4",
|
||||
"isResizable": true
|
||||
"y": 8
|
||||
},
|
||||
"name": "Assert Events",
|
||||
"options": {
|
||||
@@ -505,12 +472,12 @@
|
||||
"description": "Number of operations waiting to acquire locks, Any number of queued operations for long periods of time is an indication of possible issues. Find the cause and fix it before requests get stuck in the queue.",
|
||||
"id": "2698f0f8-a76a-499b-99cf-30504f0f4db6",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"h": 7,
|
||||
"i": "2698f0f8-a76a-499b-99cf-30504f0f4db6",
|
||||
"isResizable": true,
|
||||
"w": 6,
|
||||
"x": 18,
|
||||
"y": 5,
|
||||
"i": "2698f0f8-a76a-499b-99cf-30504f0f4db6",
|
||||
"isResizable": true
|
||||
"y": 8
|
||||
},
|
||||
"name": "Lock Queue",
|
||||
"options": {
|
||||
@@ -547,11 +514,11 @@
|
||||
"id": "2bdb8cc9-92f4-449e-8f70-a4c470a21604",
|
||||
"layout": {
|
||||
"h": 1,
|
||||
"i": "2bdb8cc9-92f4-449e-8f70-a4c470a21604",
|
||||
"isResizable": false,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 9,
|
||||
"i": "2bdb8cc9-92f4-449e-8f70-a4c470a21604",
|
||||
"isResizable": false
|
||||
"y": 15
|
||||
},
|
||||
"name": "Operation Info",
|
||||
"type": "row"
|
||||
@@ -574,12 +541,12 @@
|
||||
"description": "Number of requests received Shows how many times a command is executed per second on average during the selected interval.",
|
||||
"id": "c2819508-95e7-4c63-aeae-ce19f92469cd",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"h": 7,
|
||||
"i": "c2819508-95e7-4c63-aeae-ce19f92469cd",
|
||||
"isResizable": true,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 10,
|
||||
"i": "c2819508-95e7-4c63-aeae-ce19f92469cd",
|
||||
"isResizable": true
|
||||
"y": 16
|
||||
},
|
||||
"name": "Command Operations",
|
||||
"options": {
|
||||
@@ -625,12 +592,12 @@
|
||||
"type": "timeseries",
|
||||
"id": "7030d97a-d69f-4916-a415-ec57503ab1ed",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"h": 7,
|
||||
"i": "7030d97a-d69f-4916-a415-ec57503ab1ed",
|
||||
"isResizable": true,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 10,
|
||||
"i": "7030d97a-d69f-4916-a415-ec57503ab1ed",
|
||||
"isResizable": true
|
||||
"y": 16
|
||||
},
|
||||
"version": "3.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
@@ -704,19 +671,19 @@
|
||||
"type": "timeseries",
|
||||
"id": "1c3b73d5-c25c-449f-995d-26acc9c621e1",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"h": 7,
|
||||
"i": "1c3b73d5-c25c-449f-995d-26acc9c621e1",
|
||||
"isResizable": true,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 15,
|
||||
"i": "1c3b73d5-c25c-449f-995d-26acc9c621e1",
|
||||
"isResizable": true
|
||||
"y": 23
|
||||
},
|
||||
"version": "3.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(mongodb_ss_opLatencies_latency{}[5m]) / rate(mongodb_ss_opLatencies_latency{}[5m]) / 1000",
|
||||
"expr": "rate(mongodb_ss_opLatencies_latency{instance='$instance'}[5m]) / rate(mongodb_ss_opLatencies_latency{instance='$instance'}[5m]) / 1000",
|
||||
"legend": "{{op_type}}",
|
||||
"refId": "A",
|
||||
"maxDataPoints": 240
|
||||
@@ -799,12 +766,12 @@
|
||||
"description": "",
|
||||
"id": "e642183c-8ba2-4f60-abc6-c65de49e7577",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"h": 7,
|
||||
"i": "e642183c-8ba2-4f60-abc6-c65de49e7577",
|
||||
"isResizable": true,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 15,
|
||||
"i": "e642183c-8ba2-4f60-abc6-c65de49e7577",
|
||||
"isResizable": true
|
||||
"y": 23
|
||||
},
|
||||
"name": "Query Efficiency",
|
||||
"options": {
|
||||
@@ -861,12 +828,12 @@
|
||||
"description": "number of cursors Helps identify why connections are increasing. Shows active cursors compared to cursors being automatically killed after 10 minutes due to an application not closing the connection.",
|
||||
"id": "8b5a4f44-3291-4822-ab73-f56be6c62674",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"h": 7,
|
||||
"i": "8b5a4f44-3291-4822-ab73-f56be6c62674",
|
||||
"isResizable": true,
|
||||
"w": 8,
|
||||
"x": 16,
|
||||
"y": 15,
|
||||
"i": "8b5a4f44-3291-4822-ab73-f56be6c62674",
|
||||
"isResizable": true
|
||||
"y": 23
|
||||
},
|
||||
"name": "Cursors",
|
||||
"options": {
|
||||
@@ -903,11 +870,11 @@
|
||||
"id": "06946b19-94b4-4f72-bd87-70f87989257d",
|
||||
"layout": {
|
||||
"h": 1,
|
||||
"i": "06946b19-94b4-4f72-bd87-70f87989257d",
|
||||
"isResizable": false,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 20,
|
||||
"i": "06946b19-94b4-4f72-bd87-70f87989257d",
|
||||
"isResizable": false
|
||||
"y": 30
|
||||
},
|
||||
"name": "Cache Info",
|
||||
"panels": [],
|
||||
@@ -917,19 +884,19 @@
|
||||
"type": "timeseries",
|
||||
"id": "bb0ae571-43a1-430b-8f63-256f6f1ebee6",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"h": 7,
|
||||
"i": "bb0ae571-43a1-430b-8f63-256f6f1ebee6",
|
||||
"isResizable": true,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 21,
|
||||
"i": "bb0ae571-43a1-430b-8f63-256f6f1ebee6",
|
||||
"isResizable": true
|
||||
"y": 31
|
||||
},
|
||||
"version": "3.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "mongodb_ss_wt_cache_bytes_currently_in_the_cache{}",
|
||||
"expr": "mongodb_ss_wt_cache_bytes_currently_in_the_cache{instance='$instance'}",
|
||||
"legend": "total",
|
||||
"refId": "A",
|
||||
"maxDataPoints": 240
|
||||
@@ -975,8 +942,7 @@
|
||||
"selectMode": "single"
|
||||
},
|
||||
"standardOptions": {
|
||||
"util": "bytesIEC",
|
||||
"decimals": 2
|
||||
"util": "bytesIEC"
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
@@ -1017,19 +983,19 @@
|
||||
"type": "timeseries",
|
||||
"id": "f1ffd169-2a1a-42bc-9647-0e6621be0fef",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"h": 7,
|
||||
"i": "f1ffd169-2a1a-42bc-9647-0e6621be0fef",
|
||||
"isResizable": true,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 21,
|
||||
"i": "f1ffd169-2a1a-42bc-9647-0e6621be0fef",
|
||||
"isResizable": true
|
||||
"y": 31
|
||||
},
|
||||
"version": "3.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(mongodb_ss_wt_cache_bytes_read_into_cache{}[5m])",
|
||||
"expr": "rate(mongodb_ss_wt_cache_bytes_read_into_cache{instance='$instance'}[5m])",
|
||||
"legend": "read",
|
||||
"refId": "A",
|
||||
"maxDataPoints": 240
|
||||
@@ -1104,19 +1070,19 @@
|
||||
"type": "timeseries",
|
||||
"id": "43ee140d-ae6d-474a-9892-fa4743d7f97e",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"h": 7,
|
||||
"i": "43ee140d-ae6d-474a-9892-fa4743d7f97e",
|
||||
"isResizable": true,
|
||||
"w": 6,
|
||||
"x": 12,
|
||||
"y": 21,
|
||||
"i": "43ee140d-ae6d-474a-9892-fa4743d7f97e",
|
||||
"isResizable": true
|
||||
"y": 31
|
||||
},
|
||||
"version": "3.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "100 * sum(mongodb_ss_wt_cache_tracked_dirty_pages_in_the_cache{}) / sum(mongodb_ss_wt_cache_pages_currently_held_in_the_cache{})",
|
||||
"expr": "100 * sum(mongodb_ss_wt_cache_tracked_dirty_pages_in_the_cache{instance='$instance'}) / sum(mongodb_ss_wt_cache_pages_currently_held_in_the_cache{instance='$instance'})",
|
||||
"legend": "dirty rate",
|
||||
"refId": "A",
|
||||
"maxDataPoints": 240
|
||||
@@ -1185,19 +1151,19 @@
|
||||
"type": "timeseries",
|
||||
"id": "1a22c31a-859a-400c-af2a-ae83c308d0f2",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"h": 7,
|
||||
"i": "1a22c31a-859a-400c-af2a-ae83c308d0f2",
|
||||
"isResizable": true,
|
||||
"w": 6,
|
||||
"x": 18,
|
||||
"y": 21,
|
||||
"i": "1a22c31a-859a-400c-af2a-ae83c308d0f2",
|
||||
"isResizable": true
|
||||
"y": 31
|
||||
},
|
||||
"version": "3.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(mongodb_mongod_wiredtiger_cache_evicted_total{}[5m])",
|
||||
"expr": "rate(mongodb_mongod_wiredtiger_cache_evicted_total{instance='$instance'}[5m])",
|
||||
"legend": "evicted pages",
|
||||
"refId": "A",
|
||||
"maxDataPoints": 240
|
||||
@@ -1265,95 +1231,125 @@
|
||||
"id": "b0016f4a-c565-4276-a08d-bacdf94b6b5a",
|
||||
"layout": {
|
||||
"h": 1,
|
||||
"i": "b0016f4a-c565-4276-a08d-bacdf94b6b5a",
|
||||
"isResizable": false,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 26,
|
||||
"i": "b0016f4a-c565-4276-a08d-bacdf94b6b5a",
|
||||
"isResizable": false
|
||||
"y": 45
|
||||
},
|
||||
"name": "ReplSet Info",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "f73fd0cd-ecbe-41f0-a2dc-4e02f7eaef1c",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 27,
|
||||
"i": "f73fd0cd-ecbe-41f0-a2dc-4e02f7eaef1c",
|
||||
"isResizable": true
|
||||
"custom": {
|
||||
"calc": "lastNotNull",
|
||||
"colSpan": 1,
|
||||
"colorMode": "value",
|
||||
"textMode": "value",
|
||||
"textSize": {},
|
||||
"valueField": "Value"
|
||||
},
|
||||
"version": "3.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "mongodb_mongod_replset_member_replication_lag{instance=\"$instance\"}",
|
||||
"legend": "",
|
||||
"refId": "A",
|
||||
"maxDataPoints": 240
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"name": "Replset Lag Seconds",
|
||||
"description": "replica set member master-slave synchronization delay",
|
||||
"maxPerRow": 4,
|
||||
"description": "",
|
||||
"id": "6187ceee-7c25-43f2-be1b-c44ad612ab52",
|
||||
"layout": {
|
||||
"h": 7,
|
||||
"i": "6187ceee-7c25-43f2-be1b-c44ad612ab52",
|
||||
"isResizable": true,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 46
|
||||
},
|
||||
"name": "Replset Election",
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden",
|
||||
"heightInPercentage": 30,
|
||||
"placement": "bottom",
|
||||
"behaviour": "showItem",
|
||||
"selectMode": "single"
|
||||
},
|
||||
"standardOptions": {
|
||||
"decimals": 1,
|
||||
"util": "seconds"
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#6C53B1",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
"color": "#634CD9",
|
||||
"type": "base",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"valueMappings": [
|
||||
{
|
||||
"match": {
|
||||
"to": 1800
|
||||
},
|
||||
"result": {
|
||||
"color": "#f24526"
|
||||
},
|
||||
"type": "range"
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"from": 1800
|
||||
},
|
||||
"result": {
|
||||
"color": "#53b503"
|
||||
},
|
||||
"type": "range"
|
||||
}
|
||||
]
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "time() - mongodb_mongod_replset_member_election_date",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"type": "stat",
|
||||
"version": "2.0.0"
|
||||
},
|
||||
{
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 2,
|
||||
"fillOpacity": 0.3,
|
||||
"gradientMode": "opacity",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "none",
|
||||
"pointSize": 5
|
||||
"lineInterpolation": "smooth",
|
||||
"lineWidth": 2,
|
||||
"stack": "off"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byFrameRefID"
|
||||
},
|
||||
"properties": {
|
||||
"rightYAxisDisplay": "off"
|
||||
}
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceValue": "${prom}",
|
||||
"description": "replica set member master-slave synchronization delay",
|
||||
"id": "f73fd0cd-ecbe-41f0-a2dc-4e02f7eaef1c",
|
||||
"layout": {
|
||||
"h": 7,
|
||||
"i": "f73fd0cd-ecbe-41f0-a2dc-4e02f7eaef1c",
|
||||
"isResizable": true,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 46
|
||||
},
|
||||
"name": "Replset Lag Seconds",
|
||||
"options": {
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {
|
||||
"util": "seconds"
|
||||
},
|
||||
"thresholds": {},
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
}
|
||||
]
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "mongodb_mongod_replset_member_replication_lag{instance=\"$instance\"}",
|
||||
"legend": "lag",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"type": "timeseries",
|
||||
"version": "2.0.0"
|
||||
}
|
||||
],
|
||||
"var": [
|
||||
@@ -1375,4 +1371,4 @@
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"uuid": 1717556328065329000
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
package memsto
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/pkg/tplx"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/ccfos/nightingale/v6/alert/aconf"
|
||||
"github.com/ccfos/nightingale/v6/dumper"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/poster"
|
||||
"github.com/ccfos/nightingale/v6/pkg/tplx"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
type NotifyConfigCacheType struct {
|
||||
ctx *ctx.Context
|
||||
ConfigCache *ConfigCache
|
||||
webhooks []*models.Webhook
|
||||
webhooks map[string]*models.Webhook
|
||||
smtp aconf.SMTPConfig
|
||||
script models.NotifyScript
|
||||
|
||||
@@ -47,6 +50,7 @@ func NewNotifyConfigCache(ctx *ctx.Context, configCache *ConfigCache) *NotifyCon
|
||||
w := &NotifyConfigCacheType{
|
||||
ctx: ctx,
|
||||
ConfigCache: configCache,
|
||||
webhooks: make(map[string]*models.Webhook),
|
||||
}
|
||||
w.SyncNotifyConfigs()
|
||||
return w
|
||||
@@ -85,11 +89,60 @@ func (w *NotifyConfigCacheType) syncNotifyConfigs() error {
|
||||
}
|
||||
|
||||
if strings.TrimSpace(cval) != "" {
|
||||
err = json.Unmarshal([]byte(cval), &w.webhooks)
|
||||
var webhooks []*models.Webhook
|
||||
err = json.Unmarshal([]byte(cval), &webhooks)
|
||||
if err != nil {
|
||||
dumper.PutSyncRecord("webhooks", start.Unix(), -1, -1, "failed to unmarshal configs.webhook: "+err.Error())
|
||||
logger.Errorf("failed to unmarshal webhooks:%s error:%v", cval, err)
|
||||
}
|
||||
|
||||
newWebhooks := make(map[string]*models.Webhook, len(webhooks))
|
||||
for i := 0; i < len(webhooks); i++ {
|
||||
if webhooks[i].Batch == 0 {
|
||||
webhooks[i].Batch = 1000
|
||||
}
|
||||
|
||||
if webhooks[i].Timeout == 0 {
|
||||
webhooks[i].Timeout = 10
|
||||
}
|
||||
|
||||
if webhooks[i].RetryCount == 0 {
|
||||
webhooks[i].RetryCount = 10
|
||||
}
|
||||
|
||||
if webhooks[i].RetryInterval == 0 {
|
||||
webhooks[i].RetryInterval = 10
|
||||
}
|
||||
|
||||
if webhooks[i].Client == nil {
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: webhooks[i].SkipVerify},
|
||||
}
|
||||
if poster.UseProxy(webhooks[i].Url) {
|
||||
transport.Proxy = http.ProxyFromEnvironment
|
||||
}
|
||||
webhooks[i].Client = &http.Client{
|
||||
Timeout: time.Second * time.Duration(webhooks[i].Timeout),
|
||||
Transport: transport,
|
||||
}
|
||||
}
|
||||
|
||||
newWebhooks[webhooks[i].Url] = webhooks[i]
|
||||
}
|
||||
|
||||
for url, wh := range newWebhooks {
|
||||
if oldWh, has := w.webhooks[url]; has && oldWh.Hash() != wh.Hash() {
|
||||
w.webhooks[url] = wh
|
||||
} else {
|
||||
w.webhooks[url] = wh
|
||||
}
|
||||
}
|
||||
|
||||
for url := range w.webhooks {
|
||||
if _, has := newWebhooks[url]; !has {
|
||||
delete(w.webhooks, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dumper.PutSyncRecord("webhooks", start.Unix(), time.Since(start).Milliseconds(), len(w.webhooks), "success, webhooks:\n"+cval)
|
||||
@@ -133,7 +186,7 @@ func (w *NotifyConfigCacheType) syncNotifyConfigs() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *NotifyConfigCacheType) GetWebhooks() []*models.Webhook {
|
||||
func (w *NotifyConfigCacheType) GetWebhooks() map[string]*models.Webhook {
|
||||
w.RWMutex.RLock()
|
||||
defer w.RWMutex.RUnlock()
|
||||
return w.webhooks
|
||||
|
||||
@@ -115,8 +115,18 @@ 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(template.FuncMap(tplx.TemplateFuncMap)).Parse(text)
|
||||
t, err := template.New(fmt.Sprint(e.RuleId)).Funcs(templateFuncMapCopy).Parse(text)
|
||||
if err != nil {
|
||||
e.AnnotationsJSON[k] = fmt.Sprintf("failed to parse annotations: %v", err)
|
||||
continue
|
||||
|
||||
@@ -103,6 +103,29 @@ 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 {
|
||||
@@ -154,6 +177,8 @@ 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"`
|
||||
}
|
||||
@@ -347,12 +372,14 @@ func GetHostsQuery(queries []HostQuery) []map[string]interface{} {
|
||||
blank += " "
|
||||
}
|
||||
} else {
|
||||
blank := " "
|
||||
var args []interface{}
|
||||
var query []string
|
||||
for _, tag := range lst {
|
||||
m["tags not like ?"+blank] = "%" + tag + "%"
|
||||
m["host_tags not like ?"+blank] = "%" + tag + "%"
|
||||
blank += " "
|
||||
query = append(query, "tags not like ?",
|
||||
"(host_tags not like ? or host_tags is null)")
|
||||
args = append(args, "%"+tag+"%", "%"+tag+"%")
|
||||
}
|
||||
m[strings.Join(query, " and ")] = args
|
||||
}
|
||||
case "hosts":
|
||||
lst := []string{}
|
||||
@@ -373,11 +400,13 @@ func GetHostsQuery(queries []HostQuery) []map[string]interface{} {
|
||||
blank += " "
|
||||
}
|
||||
} else if q.Op == "!~" {
|
||||
blank := " "
|
||||
var args []interface{}
|
||||
var query []string
|
||||
for _, host := range lst {
|
||||
m["ident not like ?"+blank] = strings.ReplaceAll(host, "*", "%")
|
||||
blank += " "
|
||||
query = append(query, "ident not like ?")
|
||||
args = append(args, strings.ReplaceAll(host, "*", "%"))
|
||||
}
|
||||
m[strings.Join(query, " and ")] = args
|
||||
}
|
||||
}
|
||||
query = append(query, m)
|
||||
@@ -852,6 +881,11 @@ 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
|
||||
@@ -1111,7 +1145,6 @@ 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
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package common
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/unit"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
@@ -20,7 +19,7 @@ type AnomalyPoint struct {
|
||||
Query string `json:"query"`
|
||||
Values string `json:"values"`
|
||||
ValuesUnit map[string]unit.FormattedValue `json:"values_unit"`
|
||||
RecoverConfig models.RecoverConfig `json:"recover_config"`
|
||||
RecoverConfig RecoverConfig `json:"recover_config"`
|
||||
}
|
||||
|
||||
func NewAnomalyPoint(key string, labels map[string]string, ts int64, value float64, severity int) AnomalyPoint {
|
||||
@@ -11,15 +11,32 @@ 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;comment:'identifier of component'"`
|
||||
Logo string `json:"logo" gorm:"type:varchar(191);not null;comment:'logo of component'"`
|
||||
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'"`
|
||||
Disabled int `json:"disabled" gorm:"type:int;not null;default:0;comment:'is disabled or not'"`
|
||||
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'"`
|
||||
Readme string `json:"readme" gorm:"type:text;not null;comment:'readme of component'"`
|
||||
Disabled int `json:"disabled" gorm:"type:int;not null;default:0;comment:'is disabled or not'"`
|
||||
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'"`
|
||||
}
|
||||
|
||||
func (bc *PostgresBuiltinComponent) TableName() string {
|
||||
return "builtin_components"
|
||||
}
|
||||
|
||||
func (bc *BuiltinComponent) TableName() string {
|
||||
return "builtin_components"
|
||||
}
|
||||
@@ -86,12 +103,15 @@ func BuiltinComponentDels(ctx *ctx.Context, ids []int64) error {
|
||||
return DB(ctx).Where("id in ?", ids).Delete(new(BuiltinComponent)).Error
|
||||
}
|
||||
|
||||
func BuiltinComponentGets(ctx *ctx.Context, query string) ([]*BuiltinComponent, error) {
|
||||
func BuiltinComponentGets(ctx *ctx.Context, query string, disabled int) ([]*BuiltinComponent, error) {
|
||||
session := DB(ctx)
|
||||
if query != "" {
|
||||
queryPattern := "%" + query + "%"
|
||||
session = session.Where("ident LIKE ?", queryPattern)
|
||||
}
|
||||
if disabled == 0 || disabled == 1 {
|
||||
session = session.Where("disabled = ?", disabled)
|
||||
}
|
||||
|
||||
var lst []*BuiltinComponent
|
||||
|
||||
|
||||
@@ -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:"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'"`
|
||||
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'"`
|
||||
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:"type:varchar(191);not null;default:'zh';index:idx_lang,sort:asc;comment:'language'"`
|
||||
Lang string `json:"lang" gorm:"uniqueIndex:idx_collector_typ_name;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'"`
|
||||
|
||||
@@ -168,7 +168,7 @@ func BuiltinPayloadComponents(ctx *ctx.Context, typ, cate string) (string, error
|
||||
func InitBuiltinPayloads(ctx *ctx.Context) error {
|
||||
var lst []*BuiltinPayload
|
||||
|
||||
components, err := BuiltinComponentGets(ctx, "")
|
||||
components, err := BuiltinComponentGets(ctx, "", -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -115,68 +115,75 @@ func BusiGroupExists(ctx *ctx.Context, where string, args ...interface{}) (bool,
|
||||
return num > 0, err
|
||||
}
|
||||
|
||||
// RegisterGroupDelCheckEntries 提供给外部注册删除 group 时需要检查的表
|
||||
func RegisterGroupDelCheckEntries(e []CheckEntry) {
|
||||
entries = append(entries, e...)
|
||||
}
|
||||
|
||||
type CheckEntry struct {
|
||||
Entry interface{}
|
||||
ErrorMessage string
|
||||
FieldName string
|
||||
}
|
||||
|
||||
var entries = []CheckEntry{
|
||||
{
|
||||
Entry: &AlertRule{},
|
||||
ErrorMessage: "Some alert rules still in the BusiGroup",
|
||||
FieldName: "group_id",
|
||||
},
|
||||
{
|
||||
Entry: &AlertMute{},
|
||||
ErrorMessage: "Some alert mutes still in the BusiGroup",
|
||||
FieldName: "group_id",
|
||||
},
|
||||
{
|
||||
Entry: &AlertSubscribe{},
|
||||
ErrorMessage: "Some alert subscribes still in the BusiGroup",
|
||||
FieldName: "group_id",
|
||||
},
|
||||
{
|
||||
Entry: &Board{},
|
||||
ErrorMessage: "Some Board still in the BusiGroup",
|
||||
FieldName: "group_id",
|
||||
},
|
||||
{
|
||||
Entry: &Target{},
|
||||
ErrorMessage: "Some targets still in the BusiGroup",
|
||||
FieldName: "group_id",
|
||||
},
|
||||
{
|
||||
Entry: &RecordingRule{},
|
||||
ErrorMessage: "Some recording rules still in the BusiGroup",
|
||||
FieldName: "group_id",
|
||||
},
|
||||
{
|
||||
Entry: &TaskTpl{},
|
||||
ErrorMessage: "Some recovery scripts still in the BusiGroup",
|
||||
FieldName: "group_id",
|
||||
},
|
||||
{
|
||||
Entry: &TaskRecord{},
|
||||
ErrorMessage: "Some Task Record records still in the BusiGroup",
|
||||
FieldName: "group_id",
|
||||
},
|
||||
{
|
||||
Entry: &TargetBusiGroup{},
|
||||
ErrorMessage: "Some target busigroups still in the BusiGroup",
|
||||
FieldName: "group_id",
|
||||
},
|
||||
}
|
||||
|
||||
func (bg *BusiGroup) Del(ctx *ctx.Context) error {
|
||||
has, err := Exists(DB(ctx).Model(&AlertMute{}).Where("group_id=?", bg.Id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, e := range entries {
|
||||
has, err := Exists(DB(ctx).Model(e.Entry).Where(fmt.Sprintf("%s=?", e.FieldName), bg.Id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if has {
|
||||
return errors.New("Some alert mutes still in the BusiGroup")
|
||||
}
|
||||
|
||||
has, err = Exists(DB(ctx).Model(&AlertSubscribe{}).Where("group_id=?", bg.Id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if has {
|
||||
return errors.New("Some alert subscribes still in the BusiGroup")
|
||||
}
|
||||
|
||||
has, err = Exists(DB(ctx).Model(&TargetBusiGroup{}).Where("group_id=?", bg.Id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if has {
|
||||
return errors.New("Some targets still in the BusiGroup")
|
||||
}
|
||||
|
||||
has, err = Exists(DB(ctx).Model(&Board{}).Where("group_id=?", bg.Id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if has {
|
||||
return errors.New("Some dashboards still in the BusiGroup")
|
||||
}
|
||||
|
||||
has, err = Exists(DB(ctx).Model(&TaskTpl{}).Where("group_id=?", bg.Id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if has {
|
||||
return errors.New("Some recovery scripts still in the BusiGroup")
|
||||
}
|
||||
|
||||
// hasCR, err := Exists(DB(ctx).Table("collect_rule").Where("group_id=?", bg.Id))
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// if hasCR {
|
||||
// return errors.New("Some collect rules still in the BusiGroup")
|
||||
// }
|
||||
|
||||
has, err = Exists(DB(ctx).Model(&AlertRule{}).Where("group_id=?", bg.Id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if has {
|
||||
return errors.New("Some alert rules still in the BusiGroup")
|
||||
if has {
|
||||
return errors.New(e.ErrorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
return DB(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
|
||||
@@ -130,7 +130,7 @@ func ConfigsGetAll(ctx *ctx.Context) ([]*Configs, error) { // select built-in ty
|
||||
}
|
||||
|
||||
var lst []*Configs
|
||||
err := DB(ctx).Model(&Configs{}).Select("ckey, cval").
|
||||
err := DB(ctx).Model(&Configs{}).Select("id, ckey, cval").
|
||||
Where("ckey!='' and external=? ", 0).Find(&lst).Error
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to query configs")
|
||||
|
||||
89
models/dash_annotation.go
Normal file
89
models/dash_annotation.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
)
|
||||
|
||||
type DashAnnotation struct {
|
||||
Id int64 `json:"id" gorm:"primaryKey"`
|
||||
DashboardId int64 `json:"dashboard_id"`
|
||||
PanelId string `json:"panel_id"`
|
||||
Tags string `json:"-"`
|
||||
TagsJSON []string `json:"tags" gorm:"-"`
|
||||
Description string `json:"description"`
|
||||
Config string `json:"config"`
|
||||
TimeStart int64 `json:"time_start"`
|
||||
TimeEnd int64 `json:"time_end"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
CreateBy string `json:"create_by"`
|
||||
UpdateAt int64 `json:"update_at"`
|
||||
UpdateBy string `json:"update_by"`
|
||||
}
|
||||
|
||||
func (da *DashAnnotation) TableName() string {
|
||||
return "dash_annotation"
|
||||
}
|
||||
|
||||
func (da *DashAnnotation) DB2FE() error {
|
||||
return json.Unmarshal([]byte(da.Tags), &da.TagsJSON)
|
||||
}
|
||||
|
||||
func (da *DashAnnotation) FE2DB() error {
|
||||
b, err := json.Marshal(da.TagsJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
da.Tags = string(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (da *DashAnnotation) Add(ctx *ctx.Context) error {
|
||||
if err := da.FE2DB(); err != nil {
|
||||
return err
|
||||
}
|
||||
return Insert(ctx, da)
|
||||
}
|
||||
|
||||
func (da *DashAnnotation) Update(ctx *ctx.Context) error {
|
||||
if err := da.FE2DB(); err != nil {
|
||||
return err
|
||||
}
|
||||
return DB(ctx).Model(da).Select("dashboard_id", "panel_id", "tags", "description", "config", "time_start", "time_end", "update_at", "update_by").Updates(da).Error
|
||||
}
|
||||
|
||||
func DashAnnotationDel(ctx *ctx.Context, id int64) error {
|
||||
return DB(ctx).Where("id = ?", id).Delete(&DashAnnotation{}).Error
|
||||
}
|
||||
|
||||
func DashAnnotationGet(ctx *ctx.Context, where string, args ...interface{}) (*DashAnnotation, error) {
|
||||
var lst []*DashAnnotation
|
||||
err := DB(ctx).Where(where, args...).Find(&lst).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(lst) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err = lst[0].DB2FE()
|
||||
return lst[0], err
|
||||
}
|
||||
|
||||
func DashAnnotationGets(ctx *ctx.Context, dashboardId int64, from, to int64, limit int) ([]DashAnnotation, error) {
|
||||
session := DB(ctx).Where("dashboard_id = ? AND time_start <= ? AND time_end >= ?", dashboardId, to, from)
|
||||
|
||||
var lst []DashAnnotation
|
||||
err := session.Order("id").Limit(limit).Find(&lst).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(lst); i++ {
|
||||
lst[i].DB2FE()
|
||||
}
|
||||
|
||||
return lst, nil
|
||||
}
|
||||
@@ -20,6 +20,7 @@ type EsIndexPattern struct {
|
||||
CreateBy string `json:"create_by"`
|
||||
UpdateAt int64 `json:"update_at"`
|
||||
UpdateBy string `json:"update_by"`
|
||||
CrossClusterEnabled int `json:"cross_cluster_enabled"`
|
||||
}
|
||||
|
||||
func (t *EsIndexPattern) TableName() string {
|
||||
|
||||
@@ -28,7 +28,7 @@ func MigrateIbexTables(db *gorm.DB) {
|
||||
db = db.Set("gorm:table_options", tableOptions)
|
||||
}
|
||||
|
||||
dts := []interface{}{&imodels.TaskMeta{}, &imodels.TaskScheduler{}, &imodels.TaskSchedulerHealth{}, &TaskHostDoing{}, &imodels.TaskAction{}}
|
||||
dts := []interface{}{&imodels.TaskMeta{}, &imodels.TaskScheduler{}, &TaskHostDoing{}, &imodels.TaskAction{}}
|
||||
for _, dt := range dts {
|
||||
err := db.AutoMigrate(dt)
|
||||
if err != nil {
|
||||
@@ -38,13 +38,22 @@ func MigrateIbexTables(db *gorm.DB) {
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
tableName := fmt.Sprintf("task_host_%d", i)
|
||||
err := db.Table(tableName).AutoMigrate(&imodels.TaskHost{})
|
||||
if err != nil {
|
||||
logger.Errorf("failed to migrate table:%s %v", tableName, err)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -54,12 +63,21 @@ 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.BuiltinComponent{}, &models.NotificaitonRecord{},
|
||||
&models.TargetBusiGroup{}}
|
||||
&models.MetricFilter{}, &models.NotificaitonRecord{},
|
||||
&models.TargetBusiGroup{}, &EsIndexPatternMigrate{}, &DashAnnotation{}}
|
||||
|
||||
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") {
|
||||
@@ -74,7 +92,7 @@ func MigrateTables(db *gorm.DB) error {
|
||||
|
||||
for _, dt := range asyncDts {
|
||||
if err := db.AutoMigrate(dt); err != nil {
|
||||
logger.Errorf("failed to migrate table: %v", err)
|
||||
logger.Errorf("failed to migrate table %+v err:%v", dt, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -170,14 +188,20 @@ func InsertPermPoints(db *gorm.DB) {
|
||||
})
|
||||
|
||||
for _, op := range ops {
|
||||
exists, err := models.Exists(db.Model(&models.RoleOperation{}).Where("operation = ? and role_name = ?", op.Operation, op.RoleName))
|
||||
var count int64
|
||||
|
||||
err := db.Raw("SELECT COUNT(*) FROM role_operation WHERE operation = ? AND role_name = ?",
|
||||
op.Operation, op.RoleName).Scan(&count).Error
|
||||
|
||||
if err != nil {
|
||||
logger.Errorf("check role operation exists failed, %v", err)
|
||||
continue
|
||||
}
|
||||
if exists {
|
||||
|
||||
if count > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
err = db.Create(&op).Error
|
||||
if err != nil {
|
||||
logger.Errorf("insert role operation failed, %v", err)
|
||||
@@ -186,16 +210,17 @@ func InsertPermPoints(db *gorm.DB) {
|
||||
}
|
||||
|
||||
type AlertRule struct {
|
||||
ExtraConfig string `gorm:"type:text;column:extra_config"` // extra config
|
||||
DatasourceQueries []models.DatasourceQuery `json:"datasource_queries" gorm:"datasource_queries;type:text;serializer:json"` // datasource queries
|
||||
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
|
||||
}
|
||||
|
||||
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);not null;default:'[]'"`
|
||||
BusiGroups ormx.JSONArr `gorm:"column:busi_groups;type:varchar(4096)"`
|
||||
Note string `gorm:"column:note;type:varchar(1024);default:'';comment:note"`
|
||||
RuleIds []int64 `gorm:"column:rule_ids;type:varchar(1024);default:'';comment:rule_ids"`
|
||||
RuleIds []int64 `gorm:"column:rule_ids;type:varchar(1024)"`
|
||||
}
|
||||
|
||||
type AlertMute struct {
|
||||
@@ -294,3 +319,30 @@ type TaskHostDoing struct {
|
||||
func (TaskHostDoing) TableName() string {
|
||||
return "task_host_doing"
|
||||
}
|
||||
|
||||
type EsIndexPatternMigrate struct {
|
||||
CrossClusterEnabled int `gorm:"column:cross_cluster_enabled;type:int;default:0"`
|
||||
}
|
||||
|
||||
func (EsIndexPatternMigrate) TableName() string {
|
||||
return "es_index_pattern"
|
||||
}
|
||||
|
||||
type DashAnnotation struct {
|
||||
Id int64 `gorm:"column:id;primaryKey;autoIncrement"`
|
||||
DashboardId int64 `gorm:"column:dashboard_id;not null"`
|
||||
PanelId string `gorm:"column:panel_id;type:varchar(191);not null"`
|
||||
Tags string `gorm:"column:tags;type:text"`
|
||||
Description string `gorm:"column:description;type:text"`
|
||||
Config string `gorm:"column:config;type:text"`
|
||||
TimeStart int64 `gorm:"column:time_start;not null;default:0"`
|
||||
TimeEnd int64 `gorm:"column:time_end;not null;default:0"`
|
||||
CreateAt int64 `gorm:"column:create_at;not null;default:0"`
|
||||
CreateBy string `gorm:"column:create_by;type:varchar(64);not null;default:''"`
|
||||
UpdateAt int64 `gorm:"column:update_at;not null;default:0"`
|
||||
UpdateBy string `gorm:"column:update_by;type:varchar(64);not null;default:''"`
|
||||
}
|
||||
|
||||
func (DashAnnotation) TableName() string {
|
||||
return "dash_annotation"
|
||||
}
|
||||
|
||||
69
models/migrate/migrate_test.go
Normal file
69
models/migrate/migrate_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
func TestInsertPermPoints(t *testing.T) {
|
||||
db, err := gorm.Open(mysql.Open("root:1234@tcp(127.0.0.1:3306)/n9e_v6?charset=utf8mb4&parseTime=True&loc=Local&allowNativePasswords=true"), &gorm.Config{NamingStrategy: schema.NamingStrategy{
|
||||
SingularTable: true,
|
||||
}})
|
||||
if err != nil {
|
||||
fmt.Printf("failed to connect database: %v", err)
|
||||
}
|
||||
|
||||
var ops []models.RoleOperation
|
||||
ops = append(ops, models.RoleOperation{
|
||||
RoleName: "Standard",
|
||||
Operation: "/alert-mutes/put",
|
||||
})
|
||||
|
||||
ops = append(ops, models.RoleOperation{
|
||||
RoleName: "Standard",
|
||||
Operation: "/log/index-patterns",
|
||||
})
|
||||
|
||||
ops = append(ops, models.RoleOperation{
|
||||
RoleName: "Standard",
|
||||
Operation: "/help/variable-configs",
|
||||
})
|
||||
|
||||
ops = append(ops, models.RoleOperation{
|
||||
RoleName: "Admin",
|
||||
Operation: "/permissions",
|
||||
})
|
||||
|
||||
ops = append(ops, models.RoleOperation{
|
||||
RoleName: "Standard",
|
||||
Operation: "/ibex-settings",
|
||||
})
|
||||
|
||||
db = db.Debug()
|
||||
for _, op := range ops {
|
||||
var count int64
|
||||
|
||||
err := db.Raw("SELECT COUNT(*) FROM role_operation WHERE operation = ? AND role_name = ?",
|
||||
op.Operation, op.RoleName).Scan(&count).Error
|
||||
fmt.Printf("count: %d\n", count)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("check role operation exists failed, %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
err = db.Create(&op).Error
|
||||
if err != nil {
|
||||
fmt.Printf("insert role operation failed, %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,12 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/toolkits/pkg/str"
|
||||
)
|
||||
|
||||
const WEBHOOKKEY = "webhook"
|
||||
const NOTIFYSCRIPT = "notify_script"
|
||||
const NOTIFYCHANNEL = "notify_channel"
|
||||
@@ -24,6 +31,11 @@ type Webhook struct {
|
||||
RetryCount int `json:"retry_count"`
|
||||
RetryInterval int `json:"retry_interval"`
|
||||
Batch int `json:"batch"`
|
||||
Client *http.Client `json:"-"`
|
||||
}
|
||||
|
||||
func (w *Webhook) Hash() string {
|
||||
return str.MD5(fmt.Sprintf("%d_%t_%s_%s_%s_%d_%v_%t_%s_%d_%d_%d", w.Type, w.Enable, w.Url, w.BasicAuthUser, w.BasicAuthPass, w.Timeout, w.HeaderMap, w.SkipVerify, w.Note, w.RetryCount, w.RetryInterval, w.Batch))
|
||||
}
|
||||
|
||||
type NotifyScript struct {
|
||||
|
||||
@@ -185,8 +185,16 @@ func BuildTargetWhereWithQuery(query string) BuildTargetWhereOption {
|
||||
if query != "" {
|
||||
arr := strings.Fields(query)
|
||||
for i := 0; i < len(arr); i++ {
|
||||
q := "%" + arr[i] + "%"
|
||||
session = session.Where("ident like ? or host_ip like ? or note like ? or tags like ? or host_tags like ? or os like ?", q, q, q, q, q, q)
|
||||
if strings.HasPrefix(arr[i], "-") {
|
||||
q := "%" + arr[i][1:] + "%"
|
||||
session = session.Where("ident not like ? and host_ip not like ? and "+
|
||||
"note not like ? and tags not like ? and (host_tags not like ? or "+
|
||||
"host_tags is null) and os not like ?", q, q, q, q, q, q)
|
||||
} else {
|
||||
q := "%" + arr[i] + "%"
|
||||
session = session.Where("ident like ? or host_ip like ? or note like ? or "+
|
||||
"tags like ? or host_tags like ? or os like ?", q, q, q, q, q, q)
|
||||
}
|
||||
}
|
||||
}
|
||||
return session
|
||||
@@ -197,6 +205,8 @@ func BuildTargetWhereWithDowntime(downtime int64) BuildTargetWhereOption {
|
||||
return func(session *gorm.DB) *gorm.DB {
|
||||
if downtime > 0 {
|
||||
session = session.Where("target.update_at < ?", time.Now().Unix()-downtime)
|
||||
} else if downtime < 0 {
|
||||
session = session.Where("target.update_at > ?", time.Now().Unix()+downtime)
|
||||
}
|
||||
return session
|
||||
}
|
||||
@@ -270,7 +280,11 @@ func TargetFilterQueryBuild(ctx *ctx.Context, query []map[string]interface{}, li
|
||||
for _, q := range query {
|
||||
tx := DB(ctx).Model(&Target{})
|
||||
for k, v := range q {
|
||||
tx = tx.Or(k, v)
|
||||
if strings.Count(k, "?") > 1 {
|
||||
tx = tx.Or(k, v.([]interface{})...)
|
||||
} else {
|
||||
tx = tx.Or(k, v)
|
||||
}
|
||||
}
|
||||
sub = sub.Where(tx)
|
||||
}
|
||||
@@ -409,7 +423,8 @@ func TargetsGetIdentsByIdentsAndHostIps(ctx *ctx.Context, idents, hostIps []stri
|
||||
return inexistence, identSet.ToSlice(), nil
|
||||
}
|
||||
|
||||
func TargetGetTags(ctx *ctx.Context, idents []string, ignoreHostTag bool) ([]string, error) {
|
||||
func TargetGetTags(ctx *ctx.Context, idents []string, ignoreHostTag bool, bgLabelKey string) (
|
||||
[]string, error) {
|
||||
session := DB(ctx).Model(new(Target))
|
||||
|
||||
var arr []*Target
|
||||
@@ -447,7 +462,22 @@ func TargetGetTags(ctx *ctx.Context, idents []string, ignoreHostTag bool) ([]str
|
||||
ret = append(ret, key)
|
||||
}
|
||||
|
||||
sort.Strings(ret)
|
||||
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)
|
||||
}
|
||||
|
||||
return ret, err
|
||||
}
|
||||
@@ -626,7 +656,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}); err != nil {
|
||||
if err := TargetBindBgids(ctx, []string{t.Ident}, []int64{t.GroupId}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := TargetUpdateBgid(ctx, []string{t.Ident}, 0, false); err != nil {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
@@ -20,6 +21,10 @@ 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
|
||||
@@ -60,7 +65,7 @@ func TargetGroupIdsGetByIdents(ctx *ctx.Context, idents []string) ([]int64, erro
|
||||
return groupIds, nil
|
||||
}
|
||||
|
||||
func TargetBindBgids(ctx *ctx.Context, idents []string, bgids []int64) error {
|
||||
func TargetBindBgids(ctx *ctx.Context, idents []string, bgids []int64, tags []string) error {
|
||||
lst := make([]TargetBusiGroup, 0, len(bgids)*len(idents))
|
||||
updateAt := time.Now().Unix()
|
||||
for _, bgid := range bgids {
|
||||
@@ -73,7 +78,6 @@ func TargetBindBgids(ctx *ctx.Context, idents []string, bgids []int64) error {
|
||||
lst = append(lst, cur)
|
||||
}
|
||||
}
|
||||
|
||||
var cl clause.Expression = clause.Insert{Modifier: "ignore"}
|
||||
switch DB(ctx).Dialector.Name() {
|
||||
case "sqlite":
|
||||
@@ -81,7 +85,23 @@ func TargetBindBgids(ctx *ctx.Context, idents []string, bgids []int64) error {
|
||||
case "postgres":
|
||||
cl = clause.OnConflict{DoNothing: true}
|
||||
}
|
||||
return DB(ctx).Clauses(cl).CreateInBatches(&lst, 10).Error
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
func TargetUnbindBgids(ctx *ctx.Context, idents []string, bgids []int64) error {
|
||||
@@ -93,7 +113,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) error {
|
||||
func TargetOverrideBgids(ctx *ctx.Context, idents []string, bgids []int64, tags []string) error {
|
||||
return DB(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// 先删除旧的关联
|
||||
if err := tx.Where("target_ident IN ?", idents).Delete(&TargetBusiGroup{}).Error; err != nil {
|
||||
@@ -126,7 +146,15 @@ func TargetOverrideBgids(ctx *ctx.Context, idents []string, bgids []int64) error
|
||||
case "postgres":
|
||||
cl = clause.OnConflict{DoNothing: true}
|
||||
}
|
||||
return tx.Clauses(cl).CreateInBatches(&lst, 10).Error
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -156,3 +184,13 @@ 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
|
||||
}
|
||||
|
||||
@@ -804,6 +804,10 @@ 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
|
||||
|
||||
@@ -29,16 +29,22 @@ func LoadConfigByDir(configDir string, configPtr interface{}) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list files under: %s : %v", configDir, err)
|
||||
}
|
||||
|
||||
found := false
|
||||
|
||||
s := NewFileScanner()
|
||||
for _, fpath := range files {
|
||||
switch {
|
||||
case strings.HasSuffix(fpath, ".toml"):
|
||||
found = true
|
||||
s.Read(path.Join(configDir, fpath))
|
||||
tBuf = append(tBuf, s.Data()...)
|
||||
tBuf = append(tBuf, []byte("\n")...)
|
||||
case strings.HasSuffix(fpath, ".json"):
|
||||
found = true
|
||||
loaders = append(loaders, &multiconfig.JSONLoader{Path: path.Join(configDir, fpath)})
|
||||
case strings.HasSuffix(fpath, ".yaml") || strings.HasSuffix(fpath, ".yml"):
|
||||
found = true
|
||||
loaders = append(loaders, &multiconfig.YAMLLoader{Path: path.Join(configDir, fpath)})
|
||||
}
|
||||
if s.Err() != nil {
|
||||
@@ -46,6 +52,10 @@ func LoadConfigByDir(configDir string, configPtr interface{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("fail to found config file, config dir path: %v", configDir)
|
||||
}
|
||||
|
||||
if len(tBuf) != 0 {
|
||||
loaders = append(loaders, &multiconfig.TOMLLoader{Reader: bytes.NewReader(tBuf)})
|
||||
}
|
||||
|
||||
2001
pkg/ormx/database_init.go
Normal file
2001
pkg/ormx/database_init.go
Normal file
File diff suppressed because it is too large
Load Diff
98
pkg/ormx/database_init_test.go
Normal file
98
pkg/ormx/database_init_test.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package ormx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestCheckPostgresDatabaseExist(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=root dbname=n9e_v6 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) {
|
||||
exist, err := checkPostgresDatabaseExist(tt.config)
|
||||
fmt.Printf("exitst: %v", exist)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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,14 +2,15 @@ package ormx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
tklog "github.com/toolkits/pkg/logger"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
@@ -70,6 +71,232 @@ 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.utf8' LC_CTYPE='en_US.utf8';", 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 := ""
|
||||
dbpair := ""
|
||||
for _, part := range dsnParts {
|
||||
if strings.HasPrefix(part, "dbname=") {
|
||||
dbName = part[strings.Index(part, "=")+1:]
|
||||
dbpair = part
|
||||
}
|
||||
}
|
||||
connectionStr := strings.Replace(c.DSN, dbpair, "dbname=postgres", 1)
|
||||
dialector := postgres.Open(connectionStr)
|
||||
|
||||
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
|
||||
@@ -95,9 +322,30 @@ 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, err
|
||||
return nil, fmt.Errorf("failed to open database: %v", err)
|
||||
}
|
||||
|
||||
if c.Debug {
|
||||
|
||||
@@ -63,7 +63,7 @@ func GetByUrl[T any](url string, cfg conf.CenterApi) (T, error) {
|
||||
Timeout: time.Duration(cfg.Timeout) * time.Millisecond,
|
||||
}
|
||||
|
||||
if useProxy(url) {
|
||||
if UseProxy(url) {
|
||||
client.Transport = ProxyTransporter
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ 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) {
|
||||
if UseProxy(url) {
|
||||
client.Transport = ProxyTransporter
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ var ProxyTransporter = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
|
||||
func useProxy(url string) bool {
|
||||
func UseProxy(url string) bool {
|
||||
// N9E_PROXY_URL=oapi.dingtalk.com,feishu.com
|
||||
patterns := os.Getenv("N9E_PROXY_URL")
|
||||
if patterns != "" {
|
||||
@@ -228,7 +228,7 @@ func PostJSON(url string, timeout time.Duration, v interface{}, retries ...int)
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
if useProxy(url) {
|
||||
if UseProxy(url) {
|
||||
client.Transport = ProxyTransporter
|
||||
}
|
||||
|
||||
|
||||
@@ -700,18 +700,24 @@ func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.
|
||||
var err error
|
||||
var warnings Warnings
|
||||
var value model.Value
|
||||
var statusCode int
|
||||
for i := 0; i < 3; i++ {
|
||||
value, warnings, err = h.query(ctx, query, ts)
|
||||
value, warnings, statusCode, 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
|
||||
}
|
||||
|
||||
func (h *httpAPI) query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, error) {
|
||||
func (h *httpAPI) query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, int, error) {
|
||||
u := h.client.URL(epQuery, nil)
|
||||
q := u.Query()
|
||||
|
||||
@@ -722,15 +728,11 @@ 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, err
|
||||
}
|
||||
|
||||
if resp.StatusCode > 200 {
|
||||
fmt.Println("status code:", resp.StatusCode)
|
||||
return nil, warnings, 0, err
|
||||
}
|
||||
|
||||
var qres queryResult
|
||||
return model.Value(qres.v), warnings, json.Unmarshal(body, &qres)
|
||||
return model.Value(qres.v), warnings, resp.StatusCode, json.Unmarshal(body, &qres)
|
||||
}
|
||||
|
||||
func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, Warnings, error) {
|
||||
|
||||
181
pkg/promql/parser.go
Normal file
181
pkg/promql/parser.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/metricsql"
|
||||
"github.com/prometheus/prometheus/promql/parser"
|
||||
)
|
||||
|
||||
func SplitBinaryOp(code string) ([]string, error) {
|
||||
var lst []string
|
||||
expr, err := metricsql.Parse(code)
|
||||
|
||||
if err != nil {
|
||||
return lst, err
|
||||
}
|
||||
|
||||
m := make(map[string]struct{})
|
||||
ParseExpr(expr, false, m)
|
||||
for k := range m {
|
||||
lst = append(lst, k)
|
||||
}
|
||||
|
||||
return lst, nil
|
||||
}
|
||||
|
||||
func GetMetric(ql string) (map[string]string, error) {
|
||||
metrics := make(map[string]string)
|
||||
expr, err := parser.ParseExpr(ql)
|
||||
if err != nil {
|
||||
return metrics, err
|
||||
}
|
||||
|
||||
selectors := parser.ExtractSelectors(expr)
|
||||
for i := 0; i < len(selectors); i++ {
|
||||
var metric string
|
||||
var labels []string
|
||||
for j := 0; j < len(selectors[i]); j++ {
|
||||
if selectors[i][j].Name == "__name__" {
|
||||
metric = selectors[i][j].Value
|
||||
} else {
|
||||
labels = append(labels, selectors[i][j].Name+selectors[i][j].Type.String()+"\""+selectors[i][j].Value+"\"")
|
||||
}
|
||||
}
|
||||
|
||||
if len(labels) != 0 {
|
||||
metrics[metric] = metric + "{" + strings.Join(labels, ",") + "}"
|
||||
} else {
|
||||
metrics[metric] = metric
|
||||
}
|
||||
}
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
// GetLabels 解析PromQL查询并返回其中的所有标签和它们的值。
|
||||
func GetLabels(ql string) (map[string]string, error) {
|
||||
labels := make(map[string]string)
|
||||
|
||||
// 解析PromQL表达式
|
||||
expr, err := parser.ParseExpr(ql)
|
||||
if err != nil {
|
||||
return labels, err
|
||||
}
|
||||
|
||||
// 提取所有的选择器
|
||||
selectors := parser.ExtractSelectors(expr)
|
||||
for _, selector := range selectors {
|
||||
for _, labelMatcher := range selector {
|
||||
if labelMatcher.Name != "__name__" {
|
||||
labels[labelMatcher.Name] = labelMatcher.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
func GetLabelsAndMetricName(ql string) (map[string]string, string, error) {
|
||||
labels := make(map[string]string)
|
||||
metricName := ""
|
||||
|
||||
// 解析PromQL表达式
|
||||
expr, err := parser.ParseExpr(ql)
|
||||
if err != nil {
|
||||
return labels, metricName, err
|
||||
}
|
||||
|
||||
// 提取所有的选择器
|
||||
selectors := parser.ExtractSelectors(expr)
|
||||
for _, selector := range selectors {
|
||||
for _, labelMatcher := range selector {
|
||||
if labelMatcher.Name != "__name__" {
|
||||
labels[labelMatcher.Name] = labelMatcher.Value
|
||||
} else {
|
||||
metricName = labelMatcher.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return labels, metricName, nil
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
Name string
|
||||
Value string
|
||||
Op string
|
||||
}
|
||||
|
||||
func GetLabelsAndMetricNameWithReplace(ql string, rep string) (map[string]Label, string, error) {
|
||||
labels := make(map[string]Label)
|
||||
metricName := ""
|
||||
|
||||
ql = strings.ReplaceAll(ql, rep, "____")
|
||||
ql = removeBrackets(ql)
|
||||
// 解析PromQL表达式
|
||||
expr, err := parser.ParseExpr(ql)
|
||||
if err != nil {
|
||||
return labels, metricName, err
|
||||
}
|
||||
|
||||
// 提取所有的选择器
|
||||
selectors := parser.ExtractSelectors(expr)
|
||||
for _, selector := range selectors {
|
||||
for _, labelMatcher := range selector {
|
||||
labelMatcher.Value = strings.ReplaceAll(labelMatcher.Value, "____", rep)
|
||||
if labelMatcher.Name != "__name__" {
|
||||
label := Label{
|
||||
Name: labelMatcher.Name,
|
||||
Value: labelMatcher.Value,
|
||||
Op: labelMatcher.Type.String(),
|
||||
}
|
||||
labels[labelMatcher.Name] = label
|
||||
} else {
|
||||
if strings.Contains(labelMatcher.Value, "$") {
|
||||
continue
|
||||
}
|
||||
metricName = labelMatcher.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return labels, metricName, nil
|
||||
}
|
||||
|
||||
func GetFirstMetric(ql string) (string, error) {
|
||||
var metric string
|
||||
expr, err := parser.ParseExpr(ql)
|
||||
if err != nil {
|
||||
return metric, err
|
||||
}
|
||||
|
||||
selectors := parser.ExtractSelectors(expr)
|
||||
for i := 0; i < len(selectors); i++ {
|
||||
for j := 0; j < len(selectors[i]); j++ {
|
||||
if selectors[i][j].Name == "__name__" {
|
||||
metric = selectors[i][j].Value
|
||||
return metric, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return metric, nil
|
||||
}
|
||||
|
||||
func removeBrackets(promql string) string {
|
||||
if strings.Contains(promql, "_over_time") || strings.Contains(promql, "rate") || strings.Contains(promql, "increase") ||
|
||||
strings.Contains(promql, "predict_linear") || strings.Contains(promql, "resets") ||
|
||||
strings.Contains(promql, "changes") || strings.Contains(promql, "holt_winters") ||
|
||||
strings.Contains(promql, "delta") || strings.Contains(promql, "deriv") {
|
||||
return promql
|
||||
}
|
||||
|
||||
if !strings.Contains(promql, "[") {
|
||||
return promql
|
||||
}
|
||||
|
||||
// 使用正则表达式匹配 [xx] 形式的内容,xx 可以是任何字符序列
|
||||
re := regexp.MustCompile(`\[[^\]]*\]`)
|
||||
// 删除匹配到的内容
|
||||
return re.ReplaceAllString(promql, "")
|
||||
}
|
||||
218
pkg/promql/perser_test.go
Normal file
218
pkg/promql/perser_test.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetMetric(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ql string
|
||||
want map[string]string
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "Valid query with labels",
|
||||
ql: "metric_name{label1=\"value1\",label2=\"value2\"}",
|
||||
want: map[string]string{"metric_name": "metric_name{label1=\"value1\",label2=\"value2\"}"},
|
||||
wantErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := GetMetric(tt.ql)
|
||||
if err != tt.wantErr && err != nil {
|
||||
t.Errorf("GetMetric() error = %v, wantErr %v ql:%s", err, tt.wantErr, tt.ql)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("GetMetric() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLabels(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ql string
|
||||
want map[string]string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Valid query with multiple labels",
|
||||
ql: "metric_name{label1=\"value1\", label2=\"value2\"} > 3",
|
||||
want: map[string]string{"label1": "value1", "label2": "value2"},
|
||||
},
|
||||
{
|
||||
name: "Valid query with multiple labels",
|
||||
ql: "metric_name{label1=\"$value1\", label2=\"$value2\"} > 3",
|
||||
want: map[string]string{"label1": "$value1", "label2": "$value2"},
|
||||
},
|
||||
{
|
||||
name: "Query without labels",
|
||||
ql: "metric_name",
|
||||
want: map[string]string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := GetLabels(tt.ql)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetLabels() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("GetLabels() = %v, want %v ql:%s", got, tt.want, tt.ql)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLabelsAndMetricNameWithReplace(t *testing.T) {
|
||||
// 定义测试案例
|
||||
tests := []struct {
|
||||
name string
|
||||
ql string
|
||||
rep string
|
||||
expectedLabels map[string]Label
|
||||
expectedMetricName string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "正常情况",
|
||||
ql: `(snmp_arista_system_cpuuse{ent_descr="$ent_descr"} / 100 > $cpu_high_threshold[1m])`,
|
||||
rep: "$",
|
||||
expectedLabels: map[string]Label{
|
||||
"ent_descr": {Name: "ent_descr", Value: "$ent_descr", Op: "="},
|
||||
},
|
||||
expectedMetricName: "snmp_arista_system_cpuuse",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "正常情况",
|
||||
ql: `rate(snmp_interface_incoming{agent_host='$agent_host',ifname='$ifname'}[2m]) * 8 / 10^9 > snmp_interface_speed{agent_host='$agent_host',ifname='$ifname'}/ 10^3 * $traffic_in and snmp_interface_speed{agent_host='$agent_host',ifname='$ifname'} > 0`,
|
||||
rep: "$",
|
||||
expectedLabels: map[string]Label{
|
||||
"agent_host": {Name: "agent_host", Value: "$agent_host", Op: "="},
|
||||
"ifname": {Name: "ifname", Value: "$ifname", Op: "="},
|
||||
},
|
||||
expectedMetricName: "snmp_interface_speed",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "正常情况",
|
||||
ql: `rate(snmp_interface_incoming{agent_host='$agent_host',ifname='$ifname'}[2m]) * 8 / 10^9 > snmp_interface_speed{agent_host='$agent_host',ifname='$ifname'}/ 10^3 * $traffic_in`,
|
||||
rep: "$",
|
||||
expectedLabels: map[string]Label{
|
||||
"agent_host": {Name: "agent_host", Value: "$agent_host", Op: "="},
|
||||
"ifname": {Name: "ifname", Value: "$ifname", Op: "="},
|
||||
},
|
||||
expectedMetricName: "snmp_interface_speed",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "正常情况",
|
||||
ql: `rate(snmp_interface_incoming{agent_host='$agent_host',ifname='$ifname'}[2m]) * 8 / 10^9 > 10`,
|
||||
rep: "$",
|
||||
expectedLabels: map[string]Label{
|
||||
"agent_host": {Name: "agent_host", Value: "$agent_host", Op: "="},
|
||||
"ifname": {Name: "ifname", Value: "$ifname", Op: "="},
|
||||
},
|
||||
expectedMetricName: "snmp_interface_incoming",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "带有替换字符",
|
||||
ql: `rate(snmp_interface_outgoing{Role=~'ZRT.*',agent_host='$agent_host',ifname='$ifname'}[2m]) * 8 / 10^9 > snmp_interface_speed{Role=~'ZRT.*',agent_host='$agent_host',ifname='$ifname'}/ 10^3 * $outgoing_warning and snmp_interface_speed{Role=~'ZRT.*',agent_host='$agent_host',ifname='$ifname'} > 0`,
|
||||
rep: "$",
|
||||
expectedLabels: map[string]Label{
|
||||
"agent_host": {Name: "agent_host", Value: "$agent_host", Op: "="},
|
||||
"ifname": {Name: "ifname", Value: "$ifname", Op: "="},
|
||||
"Role": {Name: "Role", Value: "ZRT.*", Op: "=~"},
|
||||
},
|
||||
expectedMetricName: "snmp_interface_speed",
|
||||
expectError: false,
|
||||
},
|
||||
// 更多测试案例...
|
||||
{
|
||||
name: "告警规则支持变量",
|
||||
ql: `mem{test1="$test1", test2="$test2", test3="test3"} > $val`,
|
||||
rep: "$",
|
||||
expectedLabels: map[string]Label{},
|
||||
expectedMetricName: "snmp_interface_speed",
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
// 运行测试案例
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
labels, metricName, err := GetLabelsAndMetricNameWithReplace(tc.ql, tc.rep)
|
||||
|
||||
if (err != nil) != tc.expectError {
|
||||
t.Errorf("ql:%s 测试 '%v' 发生错误: %v, 期望的错误状态: %v", tc.ql, tc.name, err, tc.expectError)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(labels, tc.expectedLabels) {
|
||||
t.Errorf("ql:%s 测试 '%v' 返回的标签不匹配: got %v, want %v", tc.ql, tc.name, labels, tc.expectedLabels)
|
||||
}
|
||||
|
||||
if metricName != tc.expectedMetricName {
|
||||
t.Errorf("ql:%s 测试 '%v' 返回的度量名称不匹配: got %s, want %s", tc.ql, tc.name, metricName, tc.expectedMetricName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitBinaryOp(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
code string
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid binary operation with spaces",
|
||||
code: "cpu_usage + memory_usage",
|
||||
want: []string{"cpu_usage + memory_usage"},
|
||||
},
|
||||
{
|
||||
name: "12",
|
||||
code: "cpu_usage > 0 and memory_usage>0",
|
||||
want: []string{"cpu_usage", "memory_usage"},
|
||||
},
|
||||
{
|
||||
name: "12",
|
||||
code: "cpu_usage +1> 0",
|
||||
want: []string{"cpu_usage + 1"},
|
||||
},
|
||||
{
|
||||
name: "valid complex binary operation",
|
||||
code: "(cpu_usage + memory_usage) / 2",
|
||||
want: []string{"(cpu_usage + memory_usage) / 2"},
|
||||
},
|
||||
{
|
||||
name: "invalid binary operation",
|
||||
code: "cpu_usage + ",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := SplitBinaryOp(tt.code)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("SplitBinaryOp() code:%s error = %v, wantErr %v", tt.code, err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("SplitBinaryOp() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
104
pkg/promql/promql.go
Normal file
104
pkg/promql/promql.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"github.com/VictoriaMetrics/metricsql"
|
||||
)
|
||||
|
||||
// copy from https://github.com/laixintao/promqlpy/blob/main/go/promql/promql.go
|
||||
// ModifierExpr represents MetricsQL modifier such as `<op> (...)`
|
||||
type ModifierExpr struct {
|
||||
// Op is modifier operation.
|
||||
Op string `json:"op"`
|
||||
|
||||
// Args contains modifier args from parens.
|
||||
Args []string `json:"args"`
|
||||
}
|
||||
|
||||
type Expression struct {
|
||||
// if true, all fields are set
|
||||
// if false, then it's a normal expression, only `code` is set
|
||||
IsBinaryOp bool `json:"is_binary_op"`
|
||||
|
||||
Left *Expression `json:"left"`
|
||||
Right *Expression `json:"right"`
|
||||
Op string `json:"op"`
|
||||
// GroupModifier contains modifier such as "on" or "ignoring".
|
||||
GroupModifier ModifierExpr `json:"group_modifier"`
|
||||
// JoinModifier contains modifier such as "group_left" or "group_right".
|
||||
JoinModifier ModifierExpr `json:"join_modifier"`
|
||||
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
var compareOps = map[string]bool{
|
||||
"==": true,
|
||||
"!=": true,
|
||||
">": true,
|
||||
"<": true,
|
||||
">=": true,
|
||||
"<=": true,
|
||||
}
|
||||
|
||||
var logicalOps = map[string]bool{
|
||||
"and": true,
|
||||
"or": true,
|
||||
"unless": true,
|
||||
}
|
||||
|
||||
// if `mustBeExpression` is true, means that the last level is compareOps
|
||||
// or ready.
|
||||
// example:
|
||||
// (a > 10) > b
|
||||
// result: a > 10 is expression, compare to b
|
||||
func ParseExpr(expr metricsql.Expr, mustBeExpression bool, m map[string]struct{}) *Expression {
|
||||
|
||||
// I am sure it is a normal expression!
|
||||
if mustBeExpression {
|
||||
return &Expression{
|
||||
Code: string(expr.AppendString(nil)),
|
||||
IsBinaryOp: false,
|
||||
}
|
||||
}
|
||||
|
||||
if bop, ok := expr.(*metricsql.BinaryOpExpr); ok {
|
||||
|
||||
if logicalOps[bop.Op] {
|
||||
|
||||
return &Expression{
|
||||
Left: ParseExpr(bop.Left, false, m),
|
||||
Right: ParseExpr(bop.Right, false, m),
|
||||
GroupModifier: ModifierExpr(bop.GroupModifier),
|
||||
JoinModifier: ModifierExpr(bop.JoinModifier),
|
||||
Op: bop.Op,
|
||||
Code: string(bop.AppendString(nil)),
|
||||
IsBinaryOp: true,
|
||||
}
|
||||
}
|
||||
|
||||
if compareOps[bop.Op] {
|
||||
m[string(bop.Left.AppendString(nil))] = struct{}{}
|
||||
|
||||
return &Expression{
|
||||
Left: ParseExpr(bop.Left, true, m),
|
||||
Right: ParseExpr(bop.Right, true, m),
|
||||
GroupModifier: ModifierExpr(bop.GroupModifier),
|
||||
JoinModifier: ModifierExpr(bop.JoinModifier),
|
||||
Op: bop.Op,
|
||||
Code: string(bop.AppendString(nil)),
|
||||
IsBinaryOp: true,
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if len(m) == 0 {
|
||||
m[string(expr.AppendString(nil))] = struct{}{}
|
||||
}
|
||||
|
||||
// treat +,-,* etc still as normal expression
|
||||
// default: just return the literal code as it is
|
||||
return &Expression{
|
||||
Code: string(expr.AppendString(nil)),
|
||||
IsBinaryOp: false,
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,15 @@ 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 {
|
||||
@@ -564,3 +573,13 @@ 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,6 +54,15 @@ 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:
|
||||
|
||||
@@ -24,8 +24,10 @@ type Pushgw struct {
|
||||
}
|
||||
|
||||
type WriterGlobalOpt struct {
|
||||
QueueMaxSize int
|
||||
QueuePopSize int
|
||||
QueueMaxSize int
|
||||
QueuePopSize int
|
||||
AllQueueMaxSize int
|
||||
AllQueueMaxSizeInterval int
|
||||
}
|
||||
|
||||
type WriterOptions struct {
|
||||
@@ -77,6 +79,14 @@ func (p *Pushgw) PreCheck() {
|
||||
p.WriterOpt.QueuePopSize = 1000
|
||||
}
|
||||
|
||||
if p.WriterOpt.AllQueueMaxSize <= 0 {
|
||||
p.WriterOpt.AllQueueMaxSize = 10000000
|
||||
}
|
||||
|
||||
if p.WriterOpt.AllQueueMaxSizeInterval <= 0 {
|
||||
p.WriterOpt.AllQueueMaxSizeInterval = 200
|
||||
}
|
||||
|
||||
if p.WriteConcurrency <= 0 {
|
||||
p.WriteConcurrency = 5000
|
||||
}
|
||||
|
||||
@@ -79,6 +79,10 @@ func (rt *Router) AppendLabels(pt *prompb.TimeSeries, target *models.Target, bgC
|
||||
// }
|
||||
|
||||
func (rt *Router) debugSample(remoteAddr string, v *prompb.TimeSeries) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
filter := rt.Pushgw.DebugSample
|
||||
if len(filter) == 0 {
|
||||
return
|
||||
@@ -164,6 +168,7 @@ func (rt *Router) ForwardByIdent(clientIP string, ident string, v *prompb.TimeSe
|
||||
|
||||
func (rt *Router) ForwardByMetric(clientIP string, metric string, v *prompb.TimeSeries) {
|
||||
v = rt.BeforePush(clientIP, v)
|
||||
rt.debugSample(clientIP, v)
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ func (rt *Router) remoteWrite(c *gin.Context) {
|
||||
|
||||
var (
|
||||
ignoreIdent = ginx.QueryBool(c, "ignore_ident", false)
|
||||
ignoreHost = ginx.QueryBool(c, "ignore_host", false)
|
||||
ignoreHost = ginx.QueryBool(c, "ignore_host", true) // 默认值改成 true,要不然答疑成本太高。发版的时候通知 telegraf 用户,让他们设置 ignore_host=false
|
||||
ids = make(map[string]struct{})
|
||||
)
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/pkg/fasttime"
|
||||
@@ -138,9 +139,10 @@ func (w WriterType) Post(req []byte, headers ...map[string]string) error {
|
||||
}
|
||||
|
||||
type WritersType struct {
|
||||
pushgw pconf.Pushgw
|
||||
backends map[string]WriterType
|
||||
queues map[string]*IdentQueue
|
||||
pushgw pconf.Pushgw
|
||||
backends map[string]WriterType
|
||||
queues map[string]*IdentQueue
|
||||
allQueueLen atomic.Value
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
@@ -160,14 +162,30 @@ func (ws *WritersType) ReportQueueStats(ident string, identQueue *IdentQueue) (i
|
||||
}
|
||||
}
|
||||
|
||||
func (ws *WritersType) SetAllQueueLen() {
|
||||
for {
|
||||
curMetricLen := 0
|
||||
ws.RLock()
|
||||
for _, q := range ws.queues {
|
||||
curMetricLen += q.list.Len()
|
||||
}
|
||||
ws.RUnlock()
|
||||
ws.allQueueLen.Store(curMetricLen)
|
||||
time.Sleep(time.Duration(ws.pushgw.WriterOpt.AllQueueMaxSizeInterval) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func NewWriters(pushgwConfig pconf.Pushgw) *WritersType {
|
||||
writers := &WritersType{
|
||||
backends: make(map[string]WriterType),
|
||||
queues: make(map[string]*IdentQueue),
|
||||
pushgw: pushgwConfig,
|
||||
backends: make(map[string]WriterType),
|
||||
queues: make(map[string]*IdentQueue),
|
||||
pushgw: pushgwConfig,
|
||||
allQueueLen: atomic.Value{},
|
||||
}
|
||||
|
||||
writers.Init()
|
||||
|
||||
go writers.SetAllQueueLen()
|
||||
go writers.CleanExpQueue()
|
||||
return writers
|
||||
}
|
||||
@@ -217,6 +235,13 @@ func (ws *WritersType) PushSample(ident string, v interface{}) {
|
||||
}
|
||||
|
||||
identQueue.ts = time.Now().Unix()
|
||||
curLen := ws.allQueueLen.Load().(int)
|
||||
if curLen > ws.pushgw.WriterOpt.AllQueueMaxSize {
|
||||
logger.Warningf("Write %+v full, metric count over limit: %d", v, curLen)
|
||||
CounterPushQueueErrorTotal.WithLabelValues(ident).Inc()
|
||||
return
|
||||
}
|
||||
|
||||
succ := identQueue.list.PushFront(v)
|
||||
if !succ {
|
||||
logger.Warningf("Write channel(%s) full, current channel size: %d", ident, identQueue.list.Len())
|
||||
@@ -245,6 +270,7 @@ func (ws *WritersType) StartConsumer(identQueue *IdentQueue) {
|
||||
|
||||
func (ws *WritersType) Init() error {
|
||||
opts := ws.pushgw.Writers
|
||||
ws.allQueueLen.Store(0)
|
||||
|
||||
for i := 0; i < len(opts); i++ {
|
||||
tlsConf, err := opts[i].ClientConfig.TLSConfig()
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/ccfos/nightingale/v6/pkg/tlsx"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
@@ -28,6 +29,7 @@ type Redis redis.Cmdable
|
||||
|
||||
func NewRedis(cfg RedisConfig) (Redis, error) {
|
||||
var redisClient Redis
|
||||
|
||||
switch cfg.RedisType {
|
||||
case "standalone", "":
|
||||
redisOptions := &redis.Options{
|
||||
@@ -88,6 +90,16 @@ func NewRedis(cfg RedisConfig) (Redis, error) {
|
||||
|
||||
redisClient = redis.NewFailoverClient(redisOptions)
|
||||
|
||||
case "miniredis":
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
fmt.Println("failed to init miniredis:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
redisClient = redis.NewClient(&redis.Options{
|
||||
Addr: s.Addr(),
|
||||
})
|
||||
|
||||
default:
|
||||
fmt.Println("failed to init redis , redis type is illegal:", cfg.RedisType)
|
||||
os.Exit(1)
|
||||
|
||||
44
storage/redis_test.go
Normal file
44
storage/redis_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMiniRedisMGet(t *testing.T) {
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to start miniredis: %v", err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: s.Addr(),
|
||||
})
|
||||
|
||||
err = rdb.Ping(context.Background()).Err()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to ping miniredis: %v", err)
|
||||
}
|
||||
|
||||
mp := make(map[string]interface{})
|
||||
mp["key1"] = "value1"
|
||||
mp["key2"] = "value2"
|
||||
mp["key3"] = "value3"
|
||||
|
||||
err = MSet(context.Background(), rdb, mp)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to set miniredis value: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
keys := []string{"key1", "key2", "key3", "key4"}
|
||||
vals := MGet(ctx, rdb, keys)
|
||||
|
||||
expected := [][]byte{[]byte("value1"), []byte("value2"), []byte("value3")}
|
||||
assert.Equal(t, expected, vals)
|
||||
}
|
||||
Reference in New Issue
Block a user