add render label template api

This commit is contained in:
liugq 2023-06-16 17:30:11 +08:00
parent bd69c89780
commit 32ed24cee0
6 changed files with 158 additions and 57 deletions

View File

@ -5,10 +5,15 @@
package common
import (
"bytes"
"fmt"
log "github.com/cihub/seelog"
"infini.sh/framework/core/elastic"
"infini.sh/framework/core/orm"
"infini.sh/framework/core/util"
"sync"
"text/template"
"time"
)
//GetClusterNames query cluster names by cluster ids
@ -86,3 +91,81 @@ func GetMapStringValue(m util.MapStr, key string) string {
}
return util.ToString(v)
}
func MapLabel(labelName, indexName, indexKeyField, indexValueField string, client elastic.API) string {
labelMaps, err := getOrInitLabelCache(indexName, indexKeyField, indexValueField, client)
if err != nil {
log.Error(err)
return ""
}
return labelMaps[labelName]
}
var labelCache = sync.Map{}
type LabelCacheItem struct {
KeyValues map[string]string
Timestamp time.Time
}
func getOrInitLabelCache(indexName, indexKeyField, indexValueField string, client elastic.API) (map[string]string, error){
cacheKey := fmt.Sprintf("%s_%s_%s", indexName, indexKeyField, indexValueField )
var (
labelMaps = map[string]string{}
err error
)
if v, ok := labelCache.Load(cacheKey); ok {
if cacheItem, ok := v.(*LabelCacheItem); ok {
if cacheItem.Timestamp.Add(time.Minute).After(time.Now()){
return cacheItem.KeyValues, nil
}
//cache expired
}
}
labelMaps, err = getLabelMaps(indexName, indexKeyField, indexValueField, client)
if err != nil {
return labelMaps, err
}
labelCache.Store(cacheKey, &LabelCacheItem{
KeyValues: labelMaps,
Timestamp: time.Now(),
})
return labelMaps, nil
}
func getLabelMaps( indexName, indexKeyField, indexValueField string, client elastic.API) (map[string]string, error){
if client == nil {
return nil, fmt.Errorf("cluster client must not be empty")
}
query := util.MapStr{
"size": 1000,
"collapse": util.MapStr{
"field": indexKeyField,
},
"_source": []string{indexKeyField, indexValueField},
}
queryDsl := util.MustToJSONBytes(query)
searchRes, err := client.SearchWithRawQueryDSL(indexName, queryDsl)
if err != nil {
return nil, err
}
labelMaps := map[string]string{}
for _, hit := range searchRes.Hits.Hits {
sourceM := util.MapStr(hit.Source)
v := GetMapStringValue(sourceM, indexValueField)
var key string
if indexKeyField == "_id" {
key = hit.ID
}else{
key = GetMapStringValue(sourceM, indexKeyField)
}
if key != "" {
labelMaps[key] = v
}
}
return labelMaps, nil
}
func ExecuteTemplate( tpl *template.Template, ctx map[string]interface{}) ([]byte, error){
msgBuffer := &bytes.Buffer{}
err := tpl.Execute(msgBuffer, ctx)
return msgBuffer.Bytes(), err
}

View File

@ -22,6 +22,7 @@ type Metric struct {
FormatType string `json:"format_type,omitempty"`
TimeFilter interface{} `json:"time_filter,omitempty"`
TimeBeforeGroup bool `json:"time_before_group,omitempty"`
BucketLabel *BucketLabel `json:"bucket_label,omitempty"`
}
type MetricGroupItem struct {
@ -71,10 +72,16 @@ type MetricDataItem struct {
Timestamp interface{} `json:"timestamp,omitempty"`
Value interface{} `json:"value"`
Groups []string `json:"groups,omitempty"`
GroupLabel string `json:"group_label,omitempty"`
}
type MetricData struct {
Groups []string `json:"groups,omitempty"`
Data map[string][]MetricDataItem
GroupLabel string `json:"group_label,omitempty"`
}
type BucketLabel struct {
Enabled bool `json:"enabled"`
Template string `json:"template,omitempty"`
}

View File

@ -13,8 +13,8 @@ type Visualization struct {
Title string `json:"title,omitempty" elastic_mapping:"title: { type: keyword }"`
IndexPattern string `json:"index_pattern,omitempty" elastic_mapping:"index_pattern: { type: keyword }"`
ClusterId string `json:"cluster_id,omitempty" elastic_mapping:"cluster_id: { type: keyword }"`
Series []SeriesItem `json:"series" elastic_mapping:"series: { type: object }"`
Position *Position `json:"position,omitempty" elastic_mapping:"position: { type: object }"`
Series []SeriesItem `json:"series" elastic_mapping:"series: { type: object,enabled:false }"`
Position *Position `json:"position,omitempty" elastic_mapping:"position: { type: object,enabled:false }"`
Description string `json:"description,omitempty" elastic_mapping:"description: { type: keyword }"`
}

View File

@ -27,5 +27,5 @@ func InitAPI() {
api.HandleAPIMethod(api.PUT, "/insight/dashboard/:dashboard_id", insight.updateDashboard)
api.HandleAPIMethod(api.DELETE, "/insight/dashboard/:dashboard_id", insight.deleteDashboard)
api.HandleAPIMethod(api.GET, "/insight/dashboard/_search", insight.searchDashboard)
api.HandleAPIMethod(api.POST, "/elasticsearch/:id/map_label/_render", insight.renderMapLabelTemplate)
}

View File

@ -0,0 +1,63 @@
/* Copyright © INFINI Ltd. All rights reserved.
* Web: https://infinilabs.com
* Email: hello#infini.ltd */
package insight
import (
log "github.com/cihub/seelog"
common2 "infini.sh/console/common"
httprouter "infini.sh/framework/core/api/router"
"infini.sh/framework/core/elastic"
"infini.sh/framework/core/util"
"net/http"
"text/template"
)
func (h *InsightAPI) renderMapLabelTemplate(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
clusterID := ps.MustGetParameter("id")
body := &RenderTemplateRequest{}
err := h.DecodeJSON(req, &body)
if err != nil {
log.Error(err)
h.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
if len(body.Contexts) == 0 && body.Template == "" {
log.Error("got bad request body: %v", body)
h.WriteError(w, "bad request", http.StatusInternalServerError)
}
client := elastic.GetClient(clusterID)
tpl, err := template.New("template_render").Funcs(map[string]any{
"map_label": func(indexName, indexKeyField, indexValueField, labelName string) string {
return common2.MapLabel(labelName, indexName, indexKeyField, indexValueField, client)
},
}).Parse(body.Template)
if err != nil {
log.Error(err)
h.WriteError(w, err.Error(), http.StatusInternalServerError)
return
}
resultLabels := map[string]string{}
for _, ctx := range body.Contexts {
label, err := common2.ExecuteTemplate(tpl, ctx.Value)
if err != nil {
log.Error(err)
continue
}
resultLabels[ctx.Key] = string(label)
}
h.WriteJSON(w, util.MapStr{
"labels": resultLabels,
}, http.StatusOK)
}
type RenderTemplateRequest struct {
Contexts []RenderTemplateContext `json:"contexts"`
Template string `json:"template"`
}
type RenderTemplateContext struct {
Key string `json:"key"`
Value map[string]interface{} `json:"value"`
}

View File

@ -7,7 +7,6 @@ package insight
import (
"github.com/Knetic/govaluate"
log "github.com/cihub/seelog"
common2 "infini.sh/console/common"
"infini.sh/console/model/insight"
httprouter "infini.sh/framework/core/api/router"
"infini.sh/framework/core/elastic"
@ -278,60 +277,9 @@ func getMetricData(metric *insight.Metric) (interface{}, error) {
targetMetricData = append(targetMetricData, targetData)
}
}
result := []insight.MetricDataItem{}
//transform cluster_id, node_id to name
var (
clusterIDsM = map[string]struct{}{}
nodeIDsM = map[string]struct{}{}
)
for _, md := range targetMetricData {
for i, gv := range md.Groups {
switch metric.Groups[i].Field {
case "metadata.labels.cluster_id", "metadata.cluster_id":
clusterIDsM[gv] = struct{}{}
case "metadata.node_id", "metadata.labels.node_id":
nodeIDsM[gv] = struct{}{}
default:
}
}
}
var (
clusterIDs []string
nodeIDs []string
clusterIDToNames = map[string]string{}
nodeIDToNames = map[string]string{}
)
if len(clusterIDsM) > 0 {
for k, _ := range clusterIDsM {
clusterIDs = append(clusterIDs, k)
}
clusterIDToNames, err = common2.GetClusterNames(clusterIDs)
if err != nil {
return nil, err
}
}
if len(nodeIDsM) > 0 {
for k, _ := range nodeIDsM {
nodeIDs = append(nodeIDs, k)
}
nodeIDToNames, err = common2.GetNodeNames(nodeIDs)
if err != nil {
return nil, err
}
}
for _, md := range targetMetricData {
for i, gv := range md.Groups {
switch metric.Groups[i].Field {
case "metadata.labels.cluster_id", "metadata.cluster_id":
if name, ok := clusterIDToNames[gv]; ok && name != "" {
md.Groups[i] = name
}
case "metadata.node_id", "metadata.labels.node_id":
if name, ok := nodeIDToNames[gv]; ok && name != "" {
md.Groups[i] = name
}
}
}
for _, v := range md.Data {
for _, mitem := range v {
mitem.Groups = md.Groups