diff --git a/cache/user_group.go b/cache/user_group.go new file mode 100644 index 00000000..56e751f0 --- /dev/null +++ b/cache/user_group.go @@ -0,0 +1,41 @@ +package cache + +import ( + "sync" + + "github.com/didi/nightingale/v5/models" +) + +type UserGroupMap struct { + sync.RWMutex + Data map[int64]*models.UserGroup +} + +var UserGroupCache = &UserGroupMap{Data: make(map[int64]*models.UserGroup)} + +func (s *UserGroupMap) GetBy(id int64) *models.UserGroup { + s.RLock() + defer s.RUnlock() + + return s.Data[id] +} + +func (s *UserGroupMap) GetByIds(ids []int64) []*models.UserGroup { + s.RLock() + defer s.RUnlock() + var userGroups []*models.UserGroup + for _, id := range ids { + if s.Data[id] == nil { + continue + } + userGroups = append(userGroups, s.Data[id]) + } + + return userGroups +} + +func (s *UserGroupMap) SetAll(userGroups map[int64]*models.UserGroup) { + s.Lock() + defer s.Unlock() + s.Data = userGroups +} diff --git a/http/router_alert_rule.go b/http/router_alert_rule.go index b50100ec..c34e052f 100644 --- a/http/router_alert_rule.go +++ b/http/router_alert_rule.go @@ -3,16 +3,21 @@ package http import ( "encoding/json" "net/http" + "strconv" + "strings" "time" "github.com/gin-gonic/gin" + "github.com/didi/nightingale/v5/cache" "github.com/didi/nightingale/v5/config" "github.com/didi/nightingale/v5/models" ) func alertRuleGet(c *gin.Context) { - renderData(c, AlertRule(urlParamInt64(c, "id")), nil) + alertRule := AlertRule(urlParamInt64(c, "id")) + alertRuleFillUserAndGroups(alertRule) + renderData(c, alertRule, nil) } type alertRuleForm struct { @@ -163,3 +168,22 @@ func alertRuleDel(c *gin.Context) { func notifyChannelsGet(c *gin.Context) { renderData(c, config.Config.NotifyChannels, nil) } + +func alertRuleFillUserAndGroups(alertRule *models.AlertRule) { + uidStrs := strings.Fields(alertRule.NotifyUsers) + var uids []int64 + for _, uidStr := range uidStrs { + uid, _ := strconv.ParseInt(uidStr, 10, 64) + uids = append(uids, uid) + } + alertRule.NotifyUsersDetail = cache.UserCache.GetByIds(uids) + + gidStrs := strings.Fields(alertRule.NotifyGroups) + var gids []int64 + for _, gidStr := range gidStrs { + gid, _ := strconv.ParseInt(gidStr, 10, 64) + gids = append(gids, gid) + } + + alertRule.NotifyGroupsDetail = cache.UserGroupCache.GetByIds(gids) +} diff --git a/http/router_alert_rule_group.go b/http/router_alert_rule_group.go index edc9b14c..cb6ed970 100644 --- a/http/router_alert_rule_group.go +++ b/http/router_alert_rule_group.go @@ -68,6 +68,10 @@ func alertRuleGroupGet(c *gin.Context) { func alertRuleOfGroupGet(c *gin.Context) { ars, err := models.AlertRulesOfGroup(urlParamInt64(c, "id")) + for i := range ars { + alertRuleFillUserAndGroups(&ars[i]) + } + renderData(c, ars, err) } diff --git a/main.go b/main.go index 12afc4da..7e1613ed 100644 --- a/main.go +++ b/main.go @@ -71,6 +71,7 @@ func main() { timer.SyncResourceTags() timer.SyncUsers() + timer.SyncUserGroups() timer.SyncUserGroupMember() timer.SyncClasspathReses() timer.SyncCollectRules() diff --git a/models/alert_rule.go b/models/alert_rule.go index 0e25d867..0b95d5eb 100644 --- a/models/alert_rule.go +++ b/models/alert_rule.go @@ -19,32 +19,34 @@ const ALERT_RULE_ACTIVE = 0 const ALERT_RULE_DISABLED = 1 type AlertRule struct { - Id int64 `json:"id"` - GroupId int64 `json:"group_id"` - Name string `json:"name"` - Type int `json:"type"` // 0: nightingale, 1: prometheus - Expression json.RawMessage `json:"expression"` - Status int `json:"status"` // 0: active, 1: disabled - AppendTags string `json:"append_tags"` - EnableStime string `json:"enable_stime"` - EnableEtime string `json:"enable_etime"` - EnableDaysOfWeek string `json:"enable_days_of_week"` - RecoveryNotify int `json:"recovery_notify"` - Priority int `json:"priority"` - NotifyChannels string `json:"notify_channels"` - NotifyGroups string `json:"notify_groups"` - NotifyUsers string `json:"notify_users"` - Callbacks string `json:"callbacks"` - RunbookUrl string `json:"runbook_url"` - Note string `json:"note"` - CreateAt int64 `json:"create_at"` - CreateBy string `json:"create_by"` - UpdateAt int64 `json:"update_at"` - UpdateBy string `json:"update_by"` - AlertDuration int `json:"alert_duration"` // 告警统计周期,PULL模型会当做P8S的for时间 - PushExpr PushExpression `xorm:"-" json:"-"` - PullExpr PullExpression `xorm:"-" json:"-"` - FirstMetric string `xorm:"-" json:"-"` // Exps里可能有多个metric,只取第一个,给后续制作map使用 + Id int64 `json:"id"` + GroupId int64 `json:"group_id"` + Name string `json:"name"` + Type int `json:"type"` // 0: nightingale, 1: prometheus + Expression json.RawMessage `json:"expression"` + Status int `json:"status"` // 0: active, 1: disabled + AppendTags string `json:"append_tags"` + EnableStime string `json:"enable_stime"` + EnableEtime string `json:"enable_etime"` + EnableDaysOfWeek string `json:"enable_days_of_week"` + RecoveryNotify int `json:"recovery_notify"` + Priority int `json:"priority"` + NotifyChannels string `json:"notify_channels"` + NotifyGroups string `json:"notify_groups"` + NotifyUsers string `json:"notify_users"` + Callbacks string `json:"callbacks"` + RunbookUrl string `json:"runbook_url"` + Note string `json:"note"` + CreateAt int64 `json:"create_at"` + CreateBy string `json:"create_by"` + UpdateAt int64 `json:"update_at"` + UpdateBy string `json:"update_by"` + AlertDuration int `json:"alert_duration"` // 告警统计周期,PULL模型会当做P8S的for时间 + PushExpr PushExpression `xorm:"-" json:"-"` + PullExpr PullExpression `xorm:"-" json:"-"` + FirstMetric string `xorm:"-" json:"-"` // Exps里可能有多个metric,只取第一个,给后续制作map使用 + NotifyUsersDetail []*User `xorm:"-" json:"notify_users_detail"` + NotifyGroupsDetail []*UserGroup `xorm:"-" json:"notify_groups_detail"` } type PushExpression struct { diff --git a/models/user_group.go b/models/user_group.go index edf18004..fcc29a6f 100644 --- a/models/user_group.go +++ b/models/user_group.go @@ -204,3 +204,19 @@ func (ug *UserGroup) Del() error { return session.Commit() } + +func UserGroupGetAll() ([]UserGroup, error) { + var userGroups []UserGroup + + err := DB.Find(&userGroups) + if err != nil { + logger.Errorf("mysql.error: select user_group fail: %v", err) + return userGroups, internalServerError + } + + if len(userGroups) == 0 { + return []UserGroup{}, nil + } + + return userGroups, nil +} diff --git a/timer/user_group.go b/timer/user_group.go new file mode 100644 index 00000000..89614f94 --- /dev/null +++ b/timer/user_group.go @@ -0,0 +1,57 @@ +package timer + +import ( + "fmt" + "math/rand" + "time" + + "github.com/didi/nightingale/v5/cache" + "github.com/didi/nightingale/v5/models" + + "github.com/toolkits/pkg/logger" +) + +// user_group_id->user_group 将数据库中的用户信息缓存在内存里, +// 在生成告警事件的时候,根据用户ID快速找到用户的详情 +func SyncUserGroups() { + err := syncUserGroups() + if err != nil { + fmt.Println("timer: sync users fail:", err) + exit(1) + } + + go loopSyncUserGroups() +} + +func loopSyncUserGroups() { + randtime := rand.Intn(9000) + fmt.Printf("timer: sync users: random sleep %dms\n", randtime) + time.Sleep(time.Duration(randtime) * time.Millisecond) + + for { + time.Sleep(time.Second * time.Duration(9)) + err := syncUserGroups() + if err != nil { + logger.Warning("timer: sync users fail:", err) + } + } +} + +func syncUserGroups() error { + start := time.Now() + + userGroups, err := models.UserGroupGetAll() + if err != nil { + return err + } + + userGroupsMap := make(map[int64]*models.UserGroup) + for i := range userGroups { + userGroupsMap[userGroups[i].Id] = &userGroups[i] + } + + cache.UserGroupCache.SetAll(userGroupsMap) + logger.Debugf("timer: sync userGroups done, cost: %dms", time.Since(start).Milliseconds()) + + return nil +}