From bf1d8b1be48ed671094198fc79abaa0e1c79c8f1 Mon Sep 17 00:00:00 2001 From: ning1875 <907974064@qq.com> Date: Mon, 19 Jul 2021 16:14:52 +0800 Subject: [PATCH 1/7] feat: add instance_query api (#731) --- backend/datasource.go | 1 + backend/prome/query.go | 30 ++++++++++++++++++++++++++++++ http/router.go | 10 ++++++++++ http/router_ts_data.go | 14 ++++++++++++++ vos/query.go | 9 +++++++++ 5 files changed, 64 insertions(+) diff --git a/backend/datasource.go b/backend/datasource.go index 72acf8c3..8863e596 100644 --- a/backend/datasource.go +++ b/backend/datasource.go @@ -20,6 +20,7 @@ type DataSource interface { PushEndpoint QueryData(inputs vos.DataQueryParam) []*vos.DataQueryResp // 查询一段时间 + QueryDataInstance(ql string) []*vos.DataQueryInstanceResp // 查询一个时间点数据 等同于prometheus instance_query QueryTagKeys(recv vos.CommonTagQueryParam) *vos.TagKeyQueryResp // 获取标签的names QueryTagValues(recv vos.CommonTagQueryParam) *vos.TagValueQueryResp // 根据一个label_name获取 values QueryTagPairs(recv vos.CommonTagQueryParam) *vos.TagPairQueryResp // 根据匹配拿到所有 series 上面三个使用统一的结构体 diff --git a/backend/prome/query.go b/backend/prome/query.go index 3270653f..15837141 100644 --- a/backend/prome/query.go +++ b/backend/prome/query.go @@ -616,6 +616,36 @@ func (pd *PromeDataSource) QueryTagPairs(recv vos.CommonTagQueryParam) *vos.TagP return respD } +func (pd *PromeDataSource) QueryDataInstance(ql string) []*vos.DataQueryInstanceResp { + respD := make([]*vos.DataQueryInstanceResp, 0) + pv := pd.QueryVector(ql) + if pv == nil { + + return respD + } + + for _, s := range pv { + metricOne := make(map[string]interface{}) + valueOne := make([]float64, 0) + + for _, l := range s.Metric { + if l.Name == LABEL_NAME { + continue + } + metricOne[l.Name] = l.Value + } + // 毫秒时间时间戳转 秒时间戳 + valueOne = append(valueOne, float64(s.Point.T)/1e3) + valueOne = append(valueOne, s.Point.V) + respD = append(respD, &vos.DataQueryInstanceResp{ + Metric: metricOne, + Value: valueOne, + }) + + } + return respD +} + func (pd *PromeDataSource) QueryVector(ql string) promql.Vector { t := time.Now() q, err := pd.QueryEngine.NewInstantQuery(pd.Queryable, ql, t) diff --git a/http/router.go b/http/router.go index 900b53d9..9a8f60e7 100644 --- a/http/router.go +++ b/http/router.go @@ -169,6 +169,14 @@ func configRoutes(r *gin.Engine) { pages.GET("/tpl/content", tplGet) pages.GET("/status", Status) + + pages.POST("/query", GetData) + pages.POST("/query_instance", GetDataInstance) + pages.POST("/tag-keys", GetTagKeys) + pages.POST("/tag-values", GetTagValues) + pages.POST("/tag-metrics", GetMetrics) + pages.POST("/tag-pairs", GetTagPairs) + } // for thirdparty, do not expose location in nginx.conf @@ -288,6 +296,8 @@ func configRoutes(r *gin.Engine) { v1.GET("/status", Status) v1.POST("/query", GetData) + v1.POST("/query_instance", GetDataInstance) + v1.GET("/check-promql", checkPromeQl) v1.POST("/tag-keys", GetTagKeys) v1.POST("/tag-values", GetTagValues) diff --git a/http/router_ts_data.go b/http/router_ts_data.go index cd104259..de830163 100644 --- a/http/router_ts_data.go +++ b/http/router_ts_data.go @@ -205,3 +205,17 @@ func GetData(c *gin.Context) { resp := dataSource.QueryData(input) renderData(c, resp, nil) } + +func GetDataInstance(c *gin.Context) { + dataSource, err := backend.GetDataSourceFor("") + if err != nil { + logger.Warningf("could not find datasource") + renderMessage(c, err) + return + } + + var input vos.DataQueryInstanceParam + dangerous(c.ShouldBindJSON(&input)) + resp := dataSource.QueryDataInstance(input.PromeQl) + renderData(c, resp, nil) +} diff --git a/vos/query.go b/vos/query.go index 3a251841..63add723 100644 --- a/vos/query.go +++ b/vos/query.go @@ -33,6 +33,10 @@ type DataQueryParam struct { End int64 `json:"end"` } +type DataQueryInstanceParam struct { + PromeQl string `json:"prome_ql"` +} + type DataQueryParamOne struct { PromeQl string `json:"prome_ql"` Idents []string `json:"idents"` @@ -59,6 +63,11 @@ type DataQueryResp struct { PNum int `json:"pNum"` } +type DataQueryInstanceResp struct { + Metric map[string]interface{} `json:"metric"` + Value []float64 `json:"value"` +} + type DataQL struct { Start int64 `json:"start"` End int64 `json:"end"` From 407f9ca6adfaea2150b5bc6e21cf9162efc0b24a Mon Sep 17 00:00:00 2001 From: 710leo <710leo@gmail.com> Date: Mon, 19 Jul 2021 16:27:37 +0800 Subject: [PATCH 2/7] refactor: change query default resolution --- backend/prome/query.go | 2 +- sql/n9e.sql | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/prome/query.go b/backend/prome/query.go index 15837141..e5c424b6 100644 --- a/backend/prome/query.go +++ b/backend/prome/query.go @@ -24,7 +24,7 @@ const ( LABEL_IDENT = "ident" LABEL_NAME = "__name__" DEFAULT_QL = `{__name__=~".*a.*|.*e.*"}` - DEFAULT_STEP = 14 + DEFAULT_STEP = 15 ) type commonQueryObj struct { diff --git a/sql/n9e.sql b/sql/n9e.sql index e63e6dd0..82394aea 100644 --- a/sql/n9e.sql +++ b/sql/n9e.sql @@ -333,12 +333,12 @@ insert into metric_description(metric, description) values('system_cpu_num_cores insert into metric_description(metric, description) values('system_cpu_steal', '等待处理其他虚拟核的时间占比(单位:%)'); insert into metric_description(metric, description) values('system_cpu_system', '内核态CPU时间占比(单位:%)'); insert into metric_description(metric, description) values('system_cpu_user', '用户态CPU时间占比(单位:%)'); -insert into metric_description(metric, description) values('system_disk_bytes_free', '磁盘某分区余量大小(单位:kb)'); +insert into metric_description(metric, description) values('system_disk_bytes_free', '磁盘某分区余量大小(单位:byte)'); insert into metric_description(metric, description) values('system_disk_used_percent', '磁盘某分区用量占比(单位:%)'); insert into metric_description(metric, description) values('system_disk_read_time', '设备读操作耗时(单位:ms)'); insert into metric_description(metric, description) values('system_disk_read_time_percent', '读取磁盘时间百分比(单位:%)'); -insert into metric_description(metric, description) values('system_disk_bytes_total', '磁盘某分区总量(单位:kb)'); -insert into metric_description(metric, description) values('system_disk_bytes_used', '磁盘某分区用量大小(单位:kb)'); +insert into metric_description(metric, description) values('system_disk_bytes_total', '磁盘某分区总量(单位:byte)'); +insert into metric_description(metric, description) values('system_disk_bytes_used', '磁盘某分区用量大小(单位:byte)'); insert into metric_description(metric, description) values('system_disk_write_time', '设备写操作耗时(单位:ms)'); insert into metric_description(metric, description) values('system_disk_write_time_percent', ''); insert into metric_description(metric, description) values('system_files_allocated', '系统已分配文件句柄数'); From 034b7a642e38e4ea7a9f1c47d456edef0b0dee5f Mon Sep 17 00:00:00 2001 From: ning1875 <907974064@qq.com> Date: Mon, 19 Jul 2021 19:17:22 +0800 Subject: [PATCH 3/7] refactor: rename instance_query to instant_query --- backend/datasource.go | 2 +- backend/prome/query.go | 2 +- http/router.go | 14 ++++---------- http/router_ts_data.go | 6 +++--- vos/query.go | 2 +- 5 files changed, 10 insertions(+), 16 deletions(-) diff --git a/backend/datasource.go b/backend/datasource.go index 8863e596..106c2fa0 100644 --- a/backend/datasource.go +++ b/backend/datasource.go @@ -20,7 +20,7 @@ type DataSource interface { PushEndpoint QueryData(inputs vos.DataQueryParam) []*vos.DataQueryResp // 查询一段时间 - QueryDataInstance(ql string) []*vos.DataQueryInstanceResp // 查询一个时间点数据 等同于prometheus instance_query + QueryDataInstant(ql string) []*vos.DataQueryInstanceResp // 查询一个时间点数据 等同于prometheus instant_query QueryTagKeys(recv vos.CommonTagQueryParam) *vos.TagKeyQueryResp // 获取标签的names QueryTagValues(recv vos.CommonTagQueryParam) *vos.TagValueQueryResp // 根据一个label_name获取 values QueryTagPairs(recv vos.CommonTagQueryParam) *vos.TagPairQueryResp // 根据匹配拿到所有 series 上面三个使用统一的结构体 diff --git a/backend/prome/query.go b/backend/prome/query.go index e5c424b6..e658a020 100644 --- a/backend/prome/query.go +++ b/backend/prome/query.go @@ -616,7 +616,7 @@ func (pd *PromeDataSource) QueryTagPairs(recv vos.CommonTagQueryParam) *vos.TagP return respD } -func (pd *PromeDataSource) QueryDataInstance(ql string) []*vos.DataQueryInstanceResp { +func (pd *PromeDataSource) QueryDataInstant(ql string) []*vos.DataQueryInstanceResp { respD := make([]*vos.DataQueryInstanceResp, 0) pv := pd.QueryVector(ql) if pv == nil { diff --git a/http/router.go b/http/router.go index 9a8f60e7..cd982b1f 100644 --- a/http/router.go +++ b/http/router.go @@ -42,10 +42,12 @@ func configRoutes(r *gin.Engine) { // 开源版本,为了支持图表分享功能,允许匿名查询数据 guest.POST("/query", GetData) + guest.POST("/instant-query", GetDataInstant) guest.POST("/tag-pairs", GetTagPairs) guest.POST("/tag-keys", GetTagKeys) guest.POST("/tag-values", GetTagValues) guest.POST("/tag-metrics", GetMetrics) + guest.GET("/check-promql", checkPromeQl) } // for brower, expose location in nginx.conf @@ -170,13 +172,6 @@ func configRoutes(r *gin.Engine) { pages.GET("/status", Status) - pages.POST("/query", GetData) - pages.POST("/query_instance", GetDataInstance) - pages.POST("/tag-keys", GetTagKeys) - pages.POST("/tag-values", GetTagValues) - pages.POST("/tag-metrics", GetMetrics) - pages.POST("/tag-pairs", GetTagPairs) - } // for thirdparty, do not expose location in nginx.conf @@ -296,13 +291,12 @@ func configRoutes(r *gin.Engine) { v1.GET("/status", Status) v1.POST("/query", GetData) - v1.POST("/query_instance", GetDataInstance) - - v1.GET("/check-promql", checkPromeQl) + v1.POST("/instant-query", GetDataInstant) v1.POST("/tag-keys", GetTagKeys) v1.POST("/tag-values", GetTagValues) v1.POST("/tag-metrics", GetMetrics) v1.POST("/tag-pairs", GetTagPairs) + v1.GET("/check-promql", checkPromeQl) } push := r.Group("/v1/n9e/series").Use(gzip.Gzip(gzip.DefaultCompression)) diff --git a/http/router_ts_data.go b/http/router_ts_data.go index de830163..c2c403ab 100644 --- a/http/router_ts_data.go +++ b/http/router_ts_data.go @@ -206,7 +206,7 @@ func GetData(c *gin.Context) { renderData(c, resp, nil) } -func GetDataInstance(c *gin.Context) { +func GetDataInstant(c *gin.Context) { dataSource, err := backend.GetDataSourceFor("") if err != nil { logger.Warningf("could not find datasource") @@ -214,8 +214,8 @@ func GetDataInstance(c *gin.Context) { return } - var input vos.DataQueryInstanceParam + var input vos.DataQueryInstantParam dangerous(c.ShouldBindJSON(&input)) - resp := dataSource.QueryDataInstance(input.PromeQl) + resp := dataSource.QueryDataInstant(input.PromeQl) renderData(c, resp, nil) } diff --git a/vos/query.go b/vos/query.go index 63add723..a211b722 100644 --- a/vos/query.go +++ b/vos/query.go @@ -33,7 +33,7 @@ type DataQueryParam struct { End int64 `json:"end"` } -type DataQueryInstanceParam struct { +type DataQueryInstantParam struct { PromeQl string `json:"prome_ql"` } From 255b2b232094b0bca42af23f90dcd985d2af7360 Mon Sep 17 00:00:00 2001 From: 710leo <710leo@gmail.com> Date: Mon, 19 Jul 2021 19:31:58 +0800 Subject: [PATCH 4/7] fix: router path duplicate --- http/router.go | 1 - 1 file changed, 1 deletion(-) diff --git a/http/router.go b/http/router.go index cd982b1f..284af216 100644 --- a/http/router.go +++ b/http/router.go @@ -140,7 +140,6 @@ func configRoutes(r *gin.Engine) { pages.DELETE("/alert-rule-group/:id", login(), alertRuleGroupDel) pages.POST("/alert-rule-group/:id/favorites", login(), alertRuleGroupFavoriteAdd) pages.DELETE("/alert-rule-group/:id/favorites", login(), alertRuleGroupFavoriteDel) - pages.GET("/check-promql", checkPromeQl) pages.POST("/alert-rules", login(), alertRuleAdd) pages.PUT("/alert-rules/status", login(), alertRuleStatusPut) From de840af33174a11328d608da7c3f0ec59b48cbe8 Mon Sep 17 00:00:00 2001 From: UlricQin Date: Tue, 20 Jul 2021 14:40:50 +0800 Subject: [PATCH 5/7] Multi roles support (#737) * support multi user roles * resources list support search tags and note --- http/http_middle.go | 13 ++++- http/router_alert_rule_group.go | 7 ++- http/router_user.go | 52 +++++++++++------- models/resource.go | 4 +- models/role_operation.go | 12 +++-- models/user.go | 94 +++++++++++++++++++++------------ 6 files changed, 120 insertions(+), 62 deletions(-) diff --git a/http/http_middle.go b/http/http_middle.go index ebcf3380..d7f51f04 100644 --- a/http/http_middle.go +++ b/http/http_middle.go @@ -2,6 +2,7 @@ package http import ( "net/http" + "strings" "github.com/didi/nightingale/v5/pkg/ierr" "github.com/gin-gonic/gin" @@ -23,7 +24,17 @@ func admin() gin.HandlerFunc { c.Set("username", username) user := loginUser(c) - if user.Role != "Admin" { + + roles := strings.Fields(user.RolesForDB) + found := false + for i := 0; i < len(roles); i++ { + if roles[i] == "Admin" { + found = true + break + } + } + + if !found { ierr.Bomb(http.StatusForbidden, "forbidden") } diff --git a/http/router_alert_rule_group.go b/http/router_alert_rule_group.go index cb6ed970..3f82dadd 100644 --- a/http/router_alert_rule_group.go +++ b/http/router_alert_rule_group.go @@ -129,8 +129,11 @@ func alertRuleGroupFavoriteDel(c *gin.Context) { } func alertRuleWritePermCheck(alertRuleGroup *models.AlertRuleGroup, user *models.User) { - if user.Role == "Admin" { - return + roles := strings.Fields(user.RolesForDB) + for i := 0; i < len(roles); i++ { + if roles[i] == "Admin" { + return + } } gids := IdsInt64(alertRuleGroup.UserGroupIds) diff --git a/http/router_user.go b/http/router_user.go index d462c6d4..2d1798bf 100644 --- a/http/router_user.go +++ b/http/router_user.go @@ -2,6 +2,7 @@ package http import ( "encoding/json" + "strings" "time" "github.com/gin-gonic/gin" @@ -20,7 +21,14 @@ func userGets(c *gin.Context) { list, err := models.UserGets(query, limit, offset(c, limit)) dangerous(err) - admin := loginUser(c).Role == "Admin" + admin := false + roles := strings.Fields(loginUser(c).RolesForDB) + for i := 0; i < len(roles); i++ { + if roles[i] == "Admin" { + admin = true + break + } + } renderData(c, gin.H{ "list": list, @@ -36,7 +44,7 @@ type userAddForm struct { Phone string `json:"phone"` Email string `json:"email"` Portrait string `json:"portrait"` - Role string `json:"role"` + Roles []string `json:"roles"` Contacts json.RawMessage `json:"contacts"` } @@ -50,23 +58,23 @@ func userAddPost(c *gin.Context) { now := time.Now().Unix() username := loginUsername(c) - u := models.User{ - Username: f.Username, - Password: password, - Nickname: f.Nickname, - Phone: f.Phone, - Email: f.Email, - Portrait: f.Portrait, - Role: f.Role, - Contacts: f.Contacts, - CreateAt: now, - UpdateAt: now, - CreateBy: username, - UpdateBy: username, + if len(f.Roles) == 0 { + bomb(200, "roles empty") } - if u.Role == "" { - u.Role = "Standard" + u := models.User{ + Username: f.Username, + Password: password, + Nickname: f.Nickname, + Phone: f.Phone, + Email: f.Email, + Portrait: f.Portrait, + RolesForDB: strings.Join(f.Roles, " "), + Contacts: f.Contacts, + CreateAt: now, + UpdateAt: now, + CreateBy: username, + UpdateBy: username, } renderMessage(c, u.Add()) @@ -81,7 +89,7 @@ type userProfileForm struct { Phone string `json:"phone"` Email string `json:"email"` Portrait string `json:"portrait"` - Role string `json:"role"` + Roles []string `json:"roles"` Status int `json:"status"` Contacts json.RawMessage `json:"contacts"` } @@ -90,12 +98,16 @@ func userProfilePut(c *gin.Context) { var f userProfileForm bind(c, &f) + if len(f.Roles) == 0 { + bomb(200, "roles empty") + } + target := User(urlParamInt64(c, "id")) target.Nickname = f.Nickname target.Phone = f.Phone target.Email = f.Email target.Portrait = f.Portrait - target.Role = f.Role + target.RolesForDB = strings.Join(f.Roles, " ") target.Status = f.Status target.Contacts = f.Contacts target.UpdateAt = time.Now().Unix() @@ -107,7 +119,7 @@ func userProfilePut(c *gin.Context) { "phone", "email", "portrait", - "role", + "roles", "status", "contacts", "update_at", diff --git a/models/resource.go b/models/resource.go index 6ba4d407..a654ce4e 100644 --- a/models/resource.go +++ b/models/resource.go @@ -44,7 +44,7 @@ func ResourceTotalByClasspathId(classpathIds []int64, query string) (int64, erro } q := "%" + query + "%" - num, err := DB.Where("ident in (select res_ident from classpath_resource where classpath_id in ("+str.IdsString(classpathIds)+")) and (ident like ? or alias like ?)", q, q).Count(new(Resource)) + num, err := DB.Where("ident in (select res_ident from classpath_resource where classpath_id in ("+str.IdsString(classpathIds)+")) and (ident like ? or alias like ? or tags like ? or note like ?)", q, q, q, q).Count(new(Resource)) if err != nil { logger.Errorf("mysql.error count resource in classpath(id=%v) query=%s fail: %v", classpathIds, query, err) return 0, internalServerError @@ -60,7 +60,7 @@ func ResourceGetsByClasspathId(classpathIds []int64, query string, limit, offset q := "%" + query + "%" var objs []Resource - err := DB.Where("ident in (select res_ident from classpath_resource where classpath_id in ("+str.IdsString(classpathIds)+")) and (ident like ? or alias like ?)", q, q).OrderBy("ident").Limit(limit, offset).Find(&objs) + err := DB.Where("ident in (select res_ident from classpath_resource where classpath_id in ("+str.IdsString(classpathIds)+")) and (ident like ? or alias like ? or tags like ? or note like ?)", q, q, q, q).OrderBy("ident").Limit(limit, offset).Find(&objs) if err != nil { logger.Errorf("mysql.error query resource in classpath(id=%d) query=%s fail: %v", classpathIds, query, err) return nil, internalServerError diff --git a/models/role_operation.go b/models/role_operation.go index 2b53459c..f0d34de4 100644 --- a/models/role_operation.go +++ b/models/role_operation.go @@ -1,6 +1,9 @@ package models -import "github.com/toolkits/pkg/logger" +import ( + "github.com/toolkits/pkg/logger" + "xorm.io/builder" +) type RoleOperation struct { RoleName string @@ -11,8 +14,11 @@ func (RoleOperation) TableName() string { return "role_operation" } -func RoleHasOperation(roleName, operation string) (bool, error) { - num, err := DB.Where("role_name=? and operation=?", roleName, operation).Count(new(RoleOperation)) +func RoleHasOperation(roles []string, operation string) (bool, error) { + cond := builder.NewCond() + cond = cond.And(builder.In("role_name", roles)) + cond = cond.And(builder.Eq{"operation": operation}) + num, err := DB.Where(cond).Count(new(RoleOperation)) if err != nil { logger.Errorf("mysql.error query role_operation fail: %v", err) return false, internalServerError diff --git a/models/user.go b/models/user.go index 0ad530ca..6b7bd51a 100644 --- a/models/user.go +++ b/models/user.go @@ -15,20 +15,21 @@ import ( ) type User struct { - Id int64 `json:"id"` - Username string `json:"username"` - Nickname string `json:"nickname"` - Password string `json:"-"` - Phone string `json:"phone"` - Email string `json:"email"` - Portrait string `json:"portrait"` - Status int `json:"status"` - Role string `json:"role"` - Contacts json.RawMessage `json:"contacts"` //内容为 map[string]string 结构 - CreateAt int64 `json:"create_at"` - CreateBy string `json:"create_by"` - UpdateAt int64 `json:"update_at"` - UpdateBy string `json:"update_by"` + Id int64 `json:"id"` + Username string `json:"username"` + Nickname string `json:"nickname"` + Password string `json:"-"` + Phone string `json:"phone"` + Email string `json:"email"` + Portrait string `json:"portrait"` + Status int `json:"status"` + RolesForDB string `json:"-" xorm:"'roles'"` // 这个字段写入数据库 + RolesForFE []string `json:"roles" xorm:"-"` // 这个字段和前端交互 + Contacts json.RawMessage `json:"contacts"` // 内容为 map[string]string 结构 + CreateAt int64 `json:"create_at"` + CreateBy string `json:"create_by"` + UpdateAt int64 `json:"update_at"` + UpdateBy string `json:"update_by"` } func (u *User) TableName() string { @@ -110,16 +111,16 @@ func InitRoot() { now := time.Now().Unix() u = User{ - Username: "root", - Password: pass, - Nickname: "超管", - Portrait: "", - Role: "Admin", - Contacts: []byte("{}"), - CreateAt: now, - UpdateAt: now, - CreateBy: "system", - UpdateBy: "system", + Username: "root", + Password: pass, + Nickname: "超管", + Portrait: "", + RolesForDB: "Admin", + Contacts: []byte("{}"), + CreateAt: now, + UpdateAt: now, + CreateBy: "system", + UpdateBy: "system", } _, err = DB.Insert(u) @@ -151,6 +152,8 @@ func UserGet(where string, args ...interface{}) (*User, error) { return nil, nil } + obj.RolesForFE = strings.Fields(obj.RolesForDB) + return &obj, nil } @@ -188,6 +191,10 @@ func UserGets(query string, limit, offset int) ([]User, error) { return []User{}, nil } + for i := 0; i < len(users); i++ { + users[i].RolesForFE = strings.Fields(users[i].RolesForDB) + } + return users, nil } @@ -204,6 +211,10 @@ func UserGetAll() ([]User, error) { return []User{}, nil } + for i := 0; i < len(users); i++ { + users[i].RolesForFE = strings.Fields(users[i].RolesForDB) + } + return users, nil } @@ -223,23 +234,31 @@ func UserGetsByIds(ids []int64) ([]User, error) { return []User{}, nil } + for i := 0; i < len(users); i++ { + users[i].RolesForFE = strings.Fields(users[i].RolesForDB) + } + return users, nil } func UserGetsByIdsStr(ids []string) ([]User, error) { - var objs []User + var users []User - err := DB.Where("id in (" + strings.Join(ids, ",") + ")").Find(&objs) + err := DB.Where("id in (" + strings.Join(ids, ",") + ")").Find(&users) if err != nil { logger.Errorf("mysql.error: UserGetsByIds fail: %v", err) return nil, internalServerError } - if len(objs) == 0 { + if len(users) == 0 { return []User{}, nil } - return objs, nil + for i := 0; i < len(users); i++ { + users[i].RolesForFE = strings.Fields(users[i].RolesForDB) + } + + return users, nil } func PassLogin(username, pass string) (*User, error) { @@ -312,7 +331,8 @@ func LdapLogin(username, pass string) (*User, error) { user.Password = "******" user.Portrait = "/img/linux.jpeg" - user.Role = "Standard" + user.RolesForDB = "Standard" + user.RolesForFE = []string{"Standard"} user.Contacts = []byte("{}") user.CreateAt = now user.UpdateAt = now @@ -474,8 +494,11 @@ func (u *User) MyUserGroups() ([]UserGroup, error) { func (u *User) CanModifyUserGroup(ug *UserGroup) (bool, error) { // 我是管理员,自然可以 - if u.Role == "Admin" { - return true, nil + roles := strings.Fields(u.RolesForDB) + for i := 0; i < len(roles); i++ { + if roles[i] == "Admin" { + return true, nil + } } // 我是创建者,自然可以 @@ -493,11 +516,14 @@ func (u *User) CanModifyUserGroup(ug *UserGroup) (bool, error) { } func (u *User) CanDo(op string) (bool, error) { - if u.Role == "Admin" { - return true, nil + roles := strings.Fields(u.RolesForDB) + for i := 0; i < len(roles); i++ { + if roles[i] == "Admin" { + return true, nil + } } - return RoleHasOperation(u.Role, op) + return RoleHasOperation(roles, op) } // MustPerm return *User for link program From 0d8e5ec77ca8339c0e4ceb9463f04fe33141cd47 Mon Sep 17 00:00:00 2001 From: ning1875 <907974064@qq.com> Date: Tue, 20 Jul 2021 16:15:06 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=20prometheus=20range=5Fquery=20=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E6=88=B3=E5=92=8C=E5=88=86=E8=BE=A8=E7=8E=87=20(#738)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 1. notify.py 支持安装channel反射发送 2. 支持钉钉群发送 3. 生成告警模板信息 * 1. notify.py 支持安装channel反射发送 2. 支持钉钉群发送 3. 增加二开说明 * 1. notify.py 用户创建一个虚拟的用户保存上述im群 的机器人token信息 user的contacts map中 * 1. notify.py alerts目录改为原来的 * 1. notify.py dingtalk send continue匹配 * 1. push型告警支持多条件 任意一个触发就触发 * 1. prometheus查询接口 tag-keys tag-values支持 params为空的情况 * 1. prometheus查询接口 ident匹配全部改为精确匹配 2. tagKey 提示改为tag_key * 1. prometheus查询接口 支持instance_query 对外暴露 * 1. prometheus instance_query改名为instant-query 2. page group中去掉数据查询相关path * 1. prometheus range_query 时间戳改为秒级 2. 查询支持传入分辨率参数 --- backend/prome/query.go | 19 +++++++++++-------- vos/query.go | 1 + 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/backend/prome/query.go b/backend/prome/query.go index e658a020..2158e980 100644 --- a/backend/prome/query.go +++ b/backend/prome/query.go @@ -207,13 +207,15 @@ func (pd *PromeDataSource) QueryData(inputs vos.DataQueryParam) []*vos.DataQuery startT := tsToUtcTs(inputs.Start) endT := tsToUtcTs(inputs.End) - // TODO 前端传入分辨率还是后端计算,grafana和prometheus ui都是前端传入 - delta := (inputs.End - inputs.Start) / 3600 - if delta <= 0 { - delta = 1 + resolution := time.Second * time.Duration(inputs.Step) + if inputs.Step == 0 { + // step==0 说明要自己算 grafana和prometheus ui都是前端传入 + delta := (inputs.End - inputs.Start) / 3600 + if delta <= 0 { + delta = 1 + } + resolution = time.Second * time.Duration(delta*DEFAULT_STEP) } - resolution := time.Second * time.Duration(delta*DEFAULT_STEP) - q, err := pd.QueryEngine.NewRangeQuery(pd.Queryable, qlStrFinal, startT, endT, resolution) if err != nil { logger.Errorf("[prome_query_error][QueryData_error_may_be_parse_ql_error][args:%+v][err:%+v]", input, err) @@ -253,7 +255,8 @@ func (pd *PromeDataSource) QueryData(inputs vos.DataQueryParam) []*vos.DataQuery pNum := len(m.Points) for _, p := range m.Points { tmpP := &vos.Point{ - Timestamp: p.T, + // 毫秒时间时间戳转 秒时间戳 + Timestamp: p.T / 1e3, Value: vos.JsonFloat(p.V), } oneResp.Values = append(oneResp.Values, tmpP) @@ -266,7 +269,7 @@ func (pd *PromeDataSource) QueryData(inputs vos.DataQueryParam) []*vos.DataQuery } tagStr = strings.TrimRight(tagStr, ",") oneResp.Tags = tagStr - oneResp.Resolution = delta * DEFAULT_STEP + oneResp.Resolution = int64(resolution / time.Second) oneResp.PNum = pNum respD = append(respD, oneResp) diff --git a/vos/query.go b/vos/query.go index a211b722..16b97e87 100644 --- a/vos/query.go +++ b/vos/query.go @@ -31,6 +31,7 @@ type DataQueryParam struct { Limit int `json:"limit"` Start int64 `json:"start"` End int64 `json:"end"` + Step int64 `json:"step"` } type DataQueryInstantParam struct { From bedea9eb059821b17e03891df5a0189704d94f3e Mon Sep 17 00:00:00 2001 From: ning1875 <907974064@qq.com> Date: Wed, 21 Jul 2021 14:20:01 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E6=B7=BB=E5=8A=A0jmx=5Fexporter=E5=86=85?= =?UTF-8?q?=E7=BD=AE=E5=A4=A7=E7=9B=98=E5=9B=BE=20(#739)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 1. notify.py 支持安装channel反射发送 2. 支持钉钉群发送 3. 生成告警模板信息 * 1. notify.py 支持安装channel反射发送 2. 支持钉钉群发送 3. 增加二开说明 * 1. notify.py 用户创建一个虚拟的用户保存上述im群 的机器人token信息 user的contacts map中 * 1. notify.py alerts目录改为原来的 * 1. notify.py dingtalk send continue匹配 * 1. push型告警支持多条件 任意一个触发就触发 * 1. prometheus查询接口 tag-keys tag-values支持 params为空的情况 * 1. prometheus查询接口 ident匹配全部改为精确匹配 2. tagKey 提示改为tag_key * 1. prometheus查询接口 支持instance_query 对外暴露 * 1. prometheus instance_query改名为instant-query 2. page group中去掉数据查询相关path * 1. prometheus range_query 时间戳改为秒级 2. 查询支持传入分辨率参数 * 1. 新增jmx_exporter内置大盘 --- etc/dashboard/jmx_exporter | 306 +++++++++++++++++++++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 etc/dashboard/jmx_exporter diff --git a/etc/dashboard/jmx_exporter b/etc/dashboard/jmx_exporter new file mode 100644 index 00000000..4a04ec66 --- /dev/null +++ b/etc/dashboard/jmx_exporter @@ -0,0 +1,306 @@ +[ + { + "id": 0, + "name": "jmx_exporter", + "tags": "", + "configs": "{\"tags\":[{\"tagName\":\"java_app\",\"key\":\"java_app\",\"value\":\"*\",\"prefix\":false}]}", + "chart_groups": [ + { + "id": 0, + "dashboard_id": 0, + "name": "jvm统计", + "weight": 1, + "charts": [ + { + "id": 278, + "group_id": 75, + "configs": "{\"name\":\"jvm版本信息\",\"mode\":\"promethues\",\"prome_ql\":[\"avg(jvm_info{java_app=~\\\"$java_app\\\"}) without (runtime,vendor)\"],\"layout\":{\"h\":2,\"w\":12,\"x\":0,\"y\":0,\"i\":\"0\"}}", + "weight": 0 + }, + { + "id": 309, + "group_id": 75, + "configs": "{\"name\":\"java进程启动时间 单位:小时\",\"mode\":\"promethues\",\"prome_ql\":[\"(time() - process_start_time_seconds{java_app=~\\\"$java_app\\\"})/3600\"],\"layout\":{\"h\":2,\"w\":12,\"x\":12,\"y\":0,\"i\":\"1\"}}", + "weight": 0 + } + ] + }, + { + "id": 0, + "dashboard_id": 0, + "name": "jvm内存使用", + "weight": 2, + "charts": [ + { + "id": 279, + "group_id": 76, + "configs": "{\"name\":\"jvm内存使用 - nonheap 非堆区\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_memory_bytes_used{java_app=~\\\"$java_app\\\",area=\\\"nonheap\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":0,\"y\":0,\"i\":\"0\"}}", + "weight": 0 + }, + { + "id": 280, + "group_id": 76, + "configs": "{\"name\":\"jvm内存使用 - heap堆区\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_memory_bytes_used{java_app=~\\\"$java_app\\\",area=\\\"heap\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":6,\"y\":0,\"i\":\"1\"}}", + "weight": 0 + }, + { + "id": 281, + "group_id": 76, + "configs": "{\"name\":\"提交给 Java虚拟机使用的内存量 heap 堆区\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_memory_bytes_committed{java_app=~\\\"$java_app\\\",area=\\\"heap\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":12,\"y\":0,\"i\":\"2\"}}", + "weight": 0 + }, + { + "id": 282, + "group_id": 76, + "configs": "{\"name\":\"提交给 Java虚拟机使用的内存量 nonheap 非堆区\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_memory_bytes_committed{java_app=~\\\"$java_app\\\",area=\\\"nonheap\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":18,\"y\":0,\"i\":\"3\"}}", + "weight": 0 + }, + { + "id": 283, + "group_id": 76, + "configs": "{\"name\":\"jvm最大内存 \",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_memory_bytes_max{java_app=~\\\"$java_app\\\",area=\\\"heap\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":0,\"y\":2,\"i\":\"4\"}}", + "weight": 0 + }, + { + "id": 285, + "group_id": 76, + "configs": "{\"name\":\"jvm 初始化内存\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_memory_bytes_init{java_app=~\\\"$java_app\\\",area=\\\"heap\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":6,\"y\":2,\"i\":\"5\"}}", + "weight": 0 + }, + { + "id": 286, + "group_id": 76, + "configs": "{\"name\":\"jvm内存使用百分比% heap堆区 \",\"mode\":\"promethues\",\"prome_ql\":[\"100 * jvm_memory_bytes_used{java_app=~\\\"$java_app\\\",area=\\\"heap\\\"}/jvm_memory_bytes_max{java_app=~\\\"$java_app\\\",area=\\\"heap\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":12,\"y\":2,\"i\":\"6\"}}", + "weight": 0 + } + ] + }, + { + "id": 0, + "dashboard_id": 0, + "name": "jvm内存池", + "weight": 3, + "charts": [ + { + "id": 287, + "group_id": 77, + "configs": "{\"name\":\"jvm内存池分pool展示\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_memory_pool_bytes_max{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":24,\"x\":0,\"y\":0,\"i\":\"0\"}}", + "weight": 0 + }, + { + "id": 316, + "group_id": 77, + "configs": "{\"name\":\" JVM 缓冲池使用缓存大小\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_buffer_pool_used_bytes{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":0,\"y\":2,\"i\":\"1\"}}", + "weight": 0 + }, + { + "id": 317, + "group_id": 77, + "configs": "{\"name\":\"JVM 缓冲池的字节容量\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_buffer_pool_capacity_bytes{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":6,\"y\":2,\"i\":\"2\"}}", + "weight": 0 + }, + { + "id": 318, + "group_id": 77, + "configs": "{\"name\":\"JVM 缓冲池使用的字节大小\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_buffer_pool_used_bytes{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":12,\"y\":2,\"i\":\"3\"}}", + "weight": 0 + } + ] + }, + { + "id": 0, + "dashboard_id": 0, + "name": "jvm gc情况", + "weight": 4, + "charts": [ + { + "id": 288, + "group_id": 78, + "configs": "{\"name\":\"新生代gc耗时 1分钟\",\"mode\":\"promethues\",\"prome_ql\":[\"increase(jvm_gc_collection_seconds_sum{java_app=~\\\"$java_app\\\",gc=\\\"G1 Young Generation\\\" }[1m])\"],\"layout\":{\"h\":2,\"w\":8,\"x\":0,\"y\":0,\"i\":\"0\"}}", + "weight": 0 + }, + { + "id": 289, + "group_id": 78, + "configs": "{\"name\":\"老生代gc耗时 1分钟\",\"mode\":\"promethues\",\"prome_ql\":[\"increase(jvm_gc_collection_seconds_sum{java_app=~\\\"$java_app\\\",gc=\\\"G1 Old Generation\\\" }[1m])\"],\"layout\":{\"h\":2,\"w\":8,\"x\":8,\"y\":0,\"i\":\"1\"}}", + "weight": 0 + }, + { + "id": 290, + "group_id": 78, + "configs": "{\"name\":\"新生代gc次数 1分钟\",\"mode\":\"promethues\",\"prome_ql\":[\"increase(jvm_gc_collection_seconds_count{java_app=~\\\"$java_app\\\",gc=\\\"G1 Young Generation\\\" }[1m])\"],\"layout\":{\"h\":2,\"w\":8,\"x\":16,\"y\":0,\"i\":\"2\"}}", + "weight": 0 + }, + { + "id": 291, + "group_id": 78, + "configs": "{\"name\":\"老生代gc次数 1分钟\",\"mode\":\"promethues\",\"prome_ql\":[\"increase(jvm_gc_collection_seconds_count{java_app=~\\\"$java_app\\\",gc=\\\"G1 Old Generation\\\" }[1m])\"],\"layout\":{\"h\":2,\"w\":8,\"x\":0,\"y\":2,\"i\":\"3\"}}", + "weight": 0 + }, + { + "id": 292, + "group_id": 78, + "configs": "{\"name\":\"新生代平均gc耗时 秒\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_gc_collection_seconds_sum{java_app=~\\\"$java_app\\\",gc=\\\"G1 Young Generation\\\" }/jvm_gc_collection_seconds_count{java_app=~\\\"$java_app\\\",gc=\\\"G1 Young Generation\\\" }\"],\"layout\":{\"h\":2,\"w\":8,\"x\":8,\"y\":2,\"i\":\"4\"}}", + "weight": 0 + }, + { + "id": 293, + "group_id": 78, + "configs": "{\"name\":\"老生代平均gc耗时\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_gc_collection_seconds_sum{java_app=~\\\"$java_app\\\",gc=\\\"G1 Old Generation\\\"}/jvm_gc_collection_seconds_count{java_app=~\\\"$java_app\\\",gc=\\\"G1 Old Generation\\\" }\"],\"layout\":{\"h\":2,\"w\":8,\"x\":16,\"y\":2,\"i\":\"5\"}}", + "weight": 0 + } + ] + }, + { + "id": 0, + "dashboard_id": 0, + "name": "jvm线程情况", + "weight": 5, + "charts": [ + { + "id": 294, + "group_id": 79, + "configs": "{\"name\":\"当前线程数\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_threads_current{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":0,\"y\":0,\"i\":\"0\"}}", + "weight": 0 + }, + { + "id": 295, + "group_id": 79, + "configs": "{\"name\":\"守护线程数\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_threads_daemon{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":6,\"y\":0,\"i\":\"1\"}}", + "weight": 0 + }, + { + "id": 296, + "group_id": 79, + "configs": "{\"name\":\"死锁线程数\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_threads_deadlocked{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":12,\"y\":0,\"i\":\"2\"}}", + "weight": 0 + }, + { + "id": 297, + "group_id": 79, + "configs": "{\"name\":\"活动线程峰值\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_threads_peak{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":18,\"y\":0,\"i\":\"3\"}}", + "weight": 0 + }, + { + "id": 298, + "group_id": 79, + "configs": "{\"name\":\"自JVM启动后,启动的线程总量(包括daemon,non-daemon和终止了的)\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_threads_started_total{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":0,\"y\":2,\"i\":\"4\"}}", + "weight": 0 + }, + { + "id": 299, + "group_id": 79, + "configs": "{\"name\":\"当前TERMINATED线程个数\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_threads_state{java_app=~\\\"$java_app\\\",state=\\\"TERMINATED\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":6,\"y\":4,\"i\":\"5\"}}", + "weight": 0 + }, + { + "id": 300, + "group_id": 79, + "configs": "{\"name\":\"当前RUNNABLE线程个数\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_threads_state{java_app=~\\\"$java_app\\\",state=\\\"RUNNABLE\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":12,\"y\":2,\"i\":\"6\"}}", + "weight": 0 + }, + { + "id": 301, + "group_id": 79, + "configs": "{\"name\":\"当前NEW线程个数\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_threads_state{java_app=~\\\"$java_app\\\",state=\\\"NEW\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":18,\"y\":2,\"i\":\"7\"}}", + "weight": 0 + }, + { + "id": 302, + "group_id": 79, + "configs": "{\"name\":\"当前TIMED_WAITING线程个数\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_threads_state{java_app=~\\\"$java_app\\\",state=\\\"TIMED_WAITING\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":0,\"y\":4,\"i\":\"8\"}}", + "weight": 0 + }, + { + "id": 303, + "group_id": 79, + "configs": "{\"name\":\"当前BLOCKED线程个数\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_threads_state{java_app=~\\\"$java_app\\\",state=\\\"BLOCKED\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":18,\"y\":4,\"i\":\"9\"}}", + "weight": 0 + }, + { + "id": 304, + "group_id": 79, + "configs": "{\"name\":\"当前WAITING线程个数\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_threads_state{java_app=~\\\"$java_app\\\",state=\\\"WAITING\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":12,\"y\":4,\"i\":\"10\"}}", + "weight": 0 + }, + { + "id": 305, + "group_id": 79, + "configs": "{\"name\":\"当前线程状态汇总\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_threads_state{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":6,\"x\":6,\"y\":2,\"i\":\"11\"}}", + "weight": 0 + } + ] + }, + { + "id": 0, + "dashboard_id": 0, + "name": "加载类情况", + "weight": 6, + "charts": [ + { + "id": 306, + "group_id": 80, + "configs": "{\"name\":\"jvm 当前加载的类个数 \",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_classes_loaded{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":8,\"x\":0,\"y\":0,\"i\":\"0\"}}", + "weight": 0 + }, + { + "id": 307, + "group_id": 80, + "configs": "{\"name\":\"jvm启动以来加载的类总个数\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_classes_loaded_total{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":8,\"x\":8,\"y\":0,\"i\":\"1\"}}", + "weight": 0 + }, + { + "id": 308, + "group_id": 80, + "configs": "{\"name\":\"jvm启动以来卸载的类总个数\",\"mode\":\"promethues\",\"prome_ql\":[\"jvm_classes_unloaded_total{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":8,\"x\":16,\"y\":0,\"i\":\"2\"}}", + "weight": 0 + } + ] + }, + { + "id": 0, + "dashboard_id": 0, + "name": "机器指标(配置了java.lang才有)", + "weight": 7, + "charts": [ + { + "id": 311, + "group_id": 81, + "configs": "{\"name\":\"java进程打开fd数\",\"mode\":\"promethues\",\"prome_ql\":[\"os_open_file_descriptor_count{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":8,\"x\":0,\"y\":0,\"i\":\"0\"}}", + "weight": 0 + }, + { + "id": 312, + "group_id": 81, + "configs": "{\"name\":\"机器总内存\",\"mode\":\"promethues\",\"prome_ql\":[\"os_total_memory_size{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":8,\"x\":8,\"y\":0,\"i\":\"1\"}}", + "weight": 0 + }, + { + "id": 313, + "group_id": 81, + "configs": "{\"name\":\"机器可用内存数\",\"mode\":\"promethues\",\"prome_ql\":[\"os_free_memory_size{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":8,\"x\":16,\"y\":0,\"i\":\"2\"}}", + "weight": 0 + }, + { + "id": 314, + "group_id": 81, + "configs": "{\"name\":\"机器近期cpu使用率\",\"mode\":\"promethues\",\"link\":\"https://docs.oracle.com/javase/7/docs/jre/api/management/extension/com/sun/management/OperatingSystemMXBean.html#getSystemCpuLoad()\",\"prome_ql\":[\"100 * os_system_cpu_load{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":8,\"x\":0,\"y\":2,\"i\":\"3\"}}", + "weight": 0 + }, + { + "id": 315, + "group_id": 81, + "configs": "{\"name\":\"java进程cpu使用\",\"mode\":\"promethues\",\"link\":\"https://docs.oracle.com/javase/7/docs/jre/api/management/extension/com/sun/management/OperatingSystemMXBean.html#getProcessCpuLoad()\",\"prome_ql\":[\"os_process_cpu_load{java_app=~\\\"$java_app\\\"}\"],\"layout\":{\"h\":2,\"w\":8,\"x\":8,\"y\":2,\"i\":\"4\"}}", + "weight": 0 + }, + { + "id": 319, + "group_id": 81, + "configs": "{\"name\":\"jvm cpu百分比\",\"mode\":\"promethues\",\"prome_ql\":[\"100 *(os_process_cpu_load{java_app=~\\\"$java_app\\\"}/os_system_cpu_load{java_app=~\\\"$java_app\\\"})\"],\"layout\":{\"h\":2,\"w\":8,\"x\":16,\"y\":2,\"i\":\"5\"}}", + "weight": 0 + } + ] + } + ] + } +] \ No newline at end of file