diff --git a/alert/process/eval.go b/alert/process/eval.go new file mode 100644 index 0000000..3d8f1b6 --- /dev/null +++ b/alert/process/eval.go @@ -0,0 +1,53 @@ +package process + +import ( + "watchAlert/internal/global" + "watchAlert/internal/models" + "watchAlert/pkg/ctx" +) + +// EvalCondition 评估告警条件 +func EvalCondition(ctx *ctx.Context, f func() models.AlertCurEvent, value float64, ec models.EvalCondition) { + + switch ec.Type { + case "count", "metric": + switch ec.Operator { + case ">": + if value > ec.Value { + processEvent(ctx, f()) + } + case ">=": + if value >= ec.Value { + processEvent(ctx, f()) + } + case "<": + if value < ec.Value { + processEvent(ctx, f()) + } + case "<=": + if value <= ec.Value { + processEvent(ctx, f()) + } + case "==": + if value == ec.Value { + processEvent(ctx, f()) + } + case "!=": + if value != ec.Value { + processEvent(ctx, f()) + } + default: + global.Logger.Sugar().Error("无效的评估条件", ec.Type, ec.Operator, ec.Value) + } + default: + global.Logger.Sugar().Error("无效的评估类型", ec.Type) + } + +} + +func processEvent(ctx *ctx.Context, event models.AlertCurEvent) { + ok := ctx.DB.Rule().GetRuleIsExist(event.RuleId) + if ok { + SaveEventCache(ctx, event) + } +} diff --git a/alert/process/process.go b/alert/process/process.go index 2af5c3b..90fd8b6 100644 --- a/alert/process/process.go +++ b/alert/process/process.go @@ -116,50 +116,11 @@ func ParserDuration(curTime time.Time, logScope int, timeType string) time.Time } -// EvalCondition 评估告警条件 -func EvalCondition(f func(), value int, ec models.EvalCondition) { - - switch ec.Type { - case "count", "value": - switch ec.Operator { - case ">": - if value > ec.Value { - f() - } - case ">=": - if value >= ec.Value { - f() - } - case "<": - if value < ec.Value { - f() - } - case "<=": - if value <= ec.Value { - f() - } - case "==": - if value == ec.Value { - f() - } - case "!=": - if value != ec.Value { - f() - } - default: - global.Logger.Sugar().Error("无效的评估条件", ec.Type, ec.Operator, ec.Value) - } - default: - global.Logger.Sugar().Error("无效的评估类型", ec.Type) - } - -} - /* - GcPendingCache - 清理 Pending 数据的缓存. - 场景: 第一次查询到有异常的指标会写入 Pending 缓存, 当该指标持续 Pending 到达持续时间后才会写入 Firing 缓存, - 那么未到达持续时间并且该指标恢复正常, 那么就需要清理该指标的 Pending 数据. +GcPendingCache +清理 Pending 数据的缓存. +场景: 第一次查询到有异常的指标会写入 Pending 缓存, 当该指标持续 Pending 到达持续时间后才会写入 Firing 缓存, +那么未到达持续时间并且该指标恢复正常, 那么就需要清理该指标的 Pending 数据. */ func GcPendingCache(ctx *ctx.Context, rule models.AlertRule, curKeys []string) { pendingKeys, err := ctx.Redis.Rule().GetAlertPendingCacheKeys(models.AlertRuleQuery{ diff --git a/alert/process/prom.go b/alert/process/prom.go deleted file mode 100644 index 0c0766b..0000000 --- a/alert/process/prom.go +++ /dev/null @@ -1,58 +0,0 @@ -package process - -import ( - "watchAlert/internal/models" - "watchAlert/pkg/client" - "watchAlert/pkg/ctx" - "watchAlert/pkg/utils/cmd" -) - -func CalIndicatorValue(ctx *ctx.Context, m string, Threshold float64, rule models.AlertRule, v client.Vector, datasourceId string, curFiringKeys, curPendingKeys *[]string, severity string) { - switch m { - case ">": - if v.Value > Threshold { - f(ctx, datasourceId, curFiringKeys, curPendingKeys, v, rule, severity) - } - case ">=": - if v.Value >= Threshold { - f(ctx, datasourceId, curFiringKeys, curPendingKeys, v, rule, severity) - } - case "<": - if v.Value < Threshold { - f(ctx, datasourceId, curFiringKeys, curPendingKeys, v, rule, severity) - } - case "<=": - if v.Value <= Threshold { - f(ctx, datasourceId, curFiringKeys, curPendingKeys, v, rule, severity) - } - case "=": - if v.Value == Threshold { - f(ctx, datasourceId, curFiringKeys, curPendingKeys, v, rule, severity) - } - case "!=": - if v.Value != Threshold { - f(ctx, datasourceId, curFiringKeys, curPendingKeys, v, rule, severity) - } - } -} - -func f(ctx *ctx.Context, datasourceId string, curFiringKeys, curPendingKeys *[]string, v client.Vector, rule models.AlertRule, severity string) { - event := ParserDefaultEvent(rule) - event.DatasourceId = datasourceId - event.Fingerprint = v.GetFingerprint() - event.Metric = v.GetMetric() - event.Metric["severity"] = severity - event.Severity = severity - event.Annotations = cmd.ParserVariables(rule.PrometheusConfig.Annotations, event.Metric) - - firingKey := event.GetFiringAlertCacheKey() - pendingKey := event.GetPendingAlertCacheKey() - - *curFiringKeys = append(*curFiringKeys, firingKey) - *curPendingKeys = append(*curPendingKeys, pendingKey) - - ok := ctx.DB.Rule().GetRuleIsExist(event.RuleId) - if ok { - SaveEventCache(ctx, event) - } -} diff --git a/alert/query/query.go b/alert/query/query.go index 8a277bf..141bac4 100644 --- a/alert/query/query.go +++ b/alert/query/query.go @@ -13,6 +13,7 @@ import ( "watchAlert/pkg/community/aws/cloudwatch" "watchAlert/pkg/community/aws/cloudwatch/types" "watchAlert/pkg/ctx" + "watchAlert/pkg/utils/cmd" ) type RuleQuery struct { @@ -27,6 +28,8 @@ func (rq *RuleQuery) Query(ctx *ctx.Context, rule models.AlertRule) { switch rule.DatasourceType { case "Prometheus": rq.prometheus(dsId, rule) + case "VictoriaMetrics": + rq.victoriametrics(dsId, rule) case "AliCloudSLS": rq.aliCloudSLS(dsId, rule) case "Loki": @@ -88,14 +91,14 @@ func (rq *RuleQuery) alertRecover(rule models.AlertRule, curKeys []string) { // Prometheus 数据源 func (rq *RuleQuery) prometheus(datasourceId string, rule models.AlertRule) { var ( - curFiringKeys = &[]string{} - curPendingKeys = &[]string{} + curFiringKeys []string + curPendingKeys []string ) defer func() { - go process.GcPendingCache(rq.ctx, rule, *curPendingKeys) - rq.alertRecover(rule, *curFiringKeys) - go process.GcRecoverWaitCache(rule, *curFiringKeys) + go process.GcPendingCache(rq.ctx, rule, curPendingKeys) + rq.alertRecover(rule, curFiringKeys) + go process.GcRecoverWaitCache(rule, curFiringKeys) }() r := models.DatasourceQuery{ @@ -123,7 +126,101 @@ func (rq *RuleQuery) prometheus(datasourceId string, rule models.AlertRule) { re := regexp.MustCompile(`([^\d]+)(\d+)`) matches := re.FindStringSubmatch(ruleExpr.Expr) t, _ := strconv.ParseFloat(matches[2], 64) - process.CalIndicatorValue(rq.ctx, matches[1], t, rule, v, datasourceId, curFiringKeys, curPendingKeys, ruleExpr.Severity) + + f := func() models.AlertCurEvent { + event := process.ParserDefaultEvent(rule) + event.DatasourceId = datasourceId + event.Fingerprint = v.GetFingerprint() + event.Metric = v.GetMetric() + event.Metric["severity"] = ruleExpr.Severity + event.Severity = ruleExpr.Severity + event.Annotations = cmd.ParserVariables(rule.PrometheusConfig.Annotations, event.Metric) + + firingKey := event.GetFiringAlertCacheKey() + pendingKey := event.GetPendingAlertCacheKey() + + curFiringKeys = append(curFiringKeys, firingKey) + curPendingKeys = append(curPendingKeys, pendingKey) + + return event + } + + option := models.EvalCondition{ + Type: "metric", + Operator: matches[1], + Value: t, + } + + process.EvalCondition(rq.ctx, f, v.Value, option) + } + } + +} + +// VictorMetrics 数据源 +func (rq *RuleQuery) victoriametrics(datasourceId string, rule models.AlertRule) { + var ( + curFiringKeys []string + curPendingKeys []string + ) + + defer func() { + go process.GcPendingCache(rq.ctx, rule, curPendingKeys) + rq.alertRecover(rule, curFiringKeys) + go process.GcRecoverWaitCache(rule, curFiringKeys) + }() + + r := models.DatasourceQuery{ + TenantId: rule.TenantId, + Id: datasourceId, + Type: "VictoriaMetrics", + } + datasourceInfo, err := rq.ctx.DB.Datasource().Get(r) + if err != nil { + return + } + + cmCli := client.NewVictoriaMetricsClient(datasourceInfo) + resQuery, err := cmCli.Query(rule.PrometheusConfig.PromQL) + if err != nil { + return + } + + if resQuery == nil { + return + } + + for _, v := range resQuery { + for _, ruleExpr := range rule.PrometheusConfig.Rules { + re := regexp.MustCompile(`([^\d]+)(\d+)`) + matches := re.FindStringSubmatch(ruleExpr.Expr) + t, _ := strconv.ParseFloat(matches[2], 64) + + f := func() models.AlertCurEvent { + event := process.ParserDefaultEvent(rule) + event.DatasourceId = datasourceId + event.Fingerprint = v.GetFingerprint() + event.Metric = v.GetMetric() + event.Metric["severity"] = ruleExpr.Severity + event.Severity = ruleExpr.Severity + event.Annotations = cmd.ParserVariables(rule.PrometheusConfig.Annotations, event.Metric) + + firingKey := event.GetFiringAlertCacheKey() + pendingKey := event.GetPendingAlertCacheKey() + + curFiringKeys = append(curFiringKeys, firingKey) + curPendingKeys = append(curPendingKeys, pendingKey) + + return event + } + + option := models.EvalCondition{ + Type: "metric", + Operator: matches[1], + Value: t, + } + + process.EvalCondition(rq.ctx, f, v.Value, option) } } @@ -170,7 +267,7 @@ func (rq *RuleQuery) aliCloudSLS(datasourceId string, rule models.AlertRule) { for _, body := range bodyList.MetricList { - event := func() { + event := func() models.AlertCurEvent { event := process.ParserDefaultEvent(rule) event.DatasourceId = datasourceId event.Fingerprint = body.GetFingerprint() @@ -180,10 +277,7 @@ func (rq *RuleQuery) aliCloudSLS(datasourceId string, rule models.AlertRule) { key := event.GetFiringAlertCacheKey() curKeys = append(curKeys, key) - ok := rq.ctx.DB.Rule().GetRuleIsExist(event.RuleId) - if ok { - process.SaveEventCache(rq.ctx, event) - } + return event } options := models.EvalCondition{ @@ -197,7 +291,7 @@ func (rq *RuleQuery) aliCloudSLS(datasourceId string, rule models.AlertRule) { } // 评估告警条件 - process.EvalCondition(event, count, options) + process.EvalCondition(rq.ctx, event, float64(count), options) } } @@ -239,7 +333,7 @@ func (rq *RuleQuery) loki(datasourceId string, rule models.AlertRule) { continue } - event := func() { + event := func() models.AlertCurEvent { event := process.ParserDefaultEvent(rule) event.DatasourceId = datasourceId event.Fingerprint = v.GetFingerprint() @@ -249,10 +343,7 @@ func (rq *RuleQuery) loki(datasourceId string, rule models.AlertRule) { key := event.GetPendingAlertCacheKey() curKeys = append(curKeys, key) - ok := rq.ctx.DB.Rule().GetRuleIsExist(event.RuleId) - if ok { - process.SaveEventCache(rq.ctx, event) - } + return event } options := models.EvalCondition{ @@ -262,7 +353,7 @@ func (rq *RuleQuery) loki(datasourceId string, rule models.AlertRule) { } // 评估告警条件 - process.EvalCondition(event, count, options) + process.EvalCondition(rq.ctx, event, float64(count), options) } @@ -358,22 +449,22 @@ func (rq *RuleQuery) cloudWatch(datasourceId string, rule models.AlertRule) { return } - event := func() { + event := func() models.AlertCurEvent { event := process.ParserDefaultEvent(rule) event.DatasourceId = datasourceId event.Fingerprint = query.GetFingerprint() event.Metric = query.GetMetrics() event.Annotations = fmt.Sprintf("%s %s %s %s %d", query.Namespace, query.MetricName, query.Statistic, rule.CloudWatchConfig.Expr, rule.CloudWatchConfig.Threshold) - process.SaveEventCache(rq.ctx, event) + return event } options := models.EvalCondition{ - Type: "value", + Type: "metric", Operator: rule.CloudWatchConfig.Expr, - Value: rule.CloudWatchConfig.Threshold, + Value: float64(rule.CloudWatchConfig.Threshold), } - process.EvalCondition(event, int(values[0]), options) + process.EvalCondition(rq.ctx, event, values[0], options) } } diff --git a/api/datasource.go b/api/datasource.go index 4050039..ad3ee17 100644 --- a/api/datasource.go +++ b/api/datasource.go @@ -1,17 +1,21 @@ package api import ( + "encoding/json" + "fmt" "github.com/gin-gonic/gin" + "io" middleware "watchAlert/internal/middleware" "watchAlert/internal/models" "watchAlert/internal/services" + "watchAlert/pkg/utils/http" ) type DatasourceController struct{} /* - 数据源 API - /api/w8t/datasource +数据源 API +/api/w8t/datasource */ func (dc DatasourceController) API(gin *gin.RouterGroup) { datasourceA := gin.Group("datasource") @@ -37,6 +41,7 @@ func (dc DatasourceController) API(gin *gin.RouterGroup) { datasourceB.GET("dataSourceList", dc.List) datasourceB.GET("dataSourceGet", dc.Get) datasourceB.GET("dataSourceSearch", dc.Search) + datasourceB.GET("promQuery", dc.PromQuery) } } @@ -112,3 +117,32 @@ func (dc DatasourceController) Delete(ctx *gin.Context) { return services.DatasourceService.Delete(r) }) } + +func (dc DatasourceController) PromQuery(ctx *gin.Context) { + r := new(models.PromQueryReq) + BindQuery(ctx, r) + + Service(ctx, func() (interface{}, interface{}) { + var res models.PromQueryRes + path := "/api/v1/query" + if r.DatasourceType == "VictoriaMetrics" { + path = "/prometheus" + path + } + get, err := http.Get(fmt.Sprintf("%s%s?query=%s", r.Addr, path, r.Query)) + if err != nil { + return nil, err + } + + all, err := io.ReadAll(get.Body) + if err != nil { + return nil, err + } + + err = json.Unmarshal(all, &res) + if err != nil { + return nil, err + } + + return res, nil + }) +} diff --git a/go.sum b/go.sum index 4d2f55d..e73095a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/VictoriaMetrics/metrics v1.34.1 h1:7EUEObv45ekfyY6PWat0K/ytluZ4q6aujzXN3g41g/A= +github.com/VictoriaMetrics/metrics v1.34.1/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8= github.com/alibabacloud-go/alibabacloud-gateway-sls v0.0.6 h1:LmBsV3DRJJyGP7GhP+OZONFuyvYPI9t3yvEj8dXVkOM= github.com/alibabacloud-go/alibabacloud-gateway-sls v0.0.6/go.mod h1:w1LdOGxFI7W3KSG8j2zruZUCknYZw8zW4QRpi+V4lOQ= github.com/alibabacloud-go/alibabacloud-gateway-sls-util v0.0.1 h1:l2sAkhQvmgEqXSZsC0ILaYvPpktFNhj5i6St/UVSPrE= @@ -267,6 +269,10 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/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.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/internal/models/datasource.go b/internal/models/datasource.go index b35ea75..cf2c44c 100644 --- a/internal/models/datasource.go +++ b/internal/models/datasource.go @@ -44,7 +44,7 @@ func (ds AlertDataSource) CheckHealth() (bool, error) { fullPath string ) switch ds.Type { - case "Prometheus": + case "Prometheus", "VictoriaMetrics": fullPath = "/-/healthy" case "Jaeger": return true, nil @@ -67,3 +67,23 @@ func (ds AlertDataSource) CheckHealth() (bool, error) { return true, nil } + +type PromQueryReq struct { + DatasourceType string `json:"datasourceType"` + Addr string `form:"addr"` + Query string `form:"query"` +} + +type PromQueryRes struct { + Data data `json:"data"` +} + +type data struct { + Result []result `json:"result"` + ResultType string `json:"resultType"` +} + +type result struct { + Metric map[string]interface{} `json:"metric"` + Value []interface{} `json:"value"` +} diff --git a/internal/models/rule.go b/internal/models/rule.go index 1835d24..492cf01 100644 --- a/internal/models/rule.go +++ b/internal/models/rule.go @@ -97,9 +97,9 @@ type CloudWatchConfig struct { // EvalCondition 日志评估条件 type EvalCondition struct { - Type string `json:"type"` - Operator string `json:"operator"` - Value int `json:"value"` + Type string `json:"type"` + Operator string `json:"operator"` + Value float64 `json:"value"` } type Fingerprint uint64 diff --git a/internal/models/user_permissions.go b/internal/models/user_permissions.go index ca6f5ff..21208eb 100644 --- a/internal/models/user_permissions.go +++ b/internal/models/user_permissions.go @@ -287,5 +287,9 @@ func PermissionsInfo() map[string]UserPermissions { Key: "获取系统配置", API: "/api/w8t/setting/getSystemSetting", }, + "promQuery": { + Key: "Prometheus指标查询", + API: "/api/w8t/datasource/promQuery", + }, } } diff --git a/internal/services/rule.go b/internal/services/rule.go index 94f0c12..c94c95e 100644 --- a/internal/services/rule.go +++ b/internal/services/rule.go @@ -53,6 +53,17 @@ func (rs ruleService) Update(req interface{}) (interface{}, interface{}) { Where("tenant_id = ? AND rule_id = ?", rule.TenantId, rule.RuleId). First(&alertInfo) + delEvent := func() { + // 删除缓存 + iter := rs.ctx.Redis.Redis().Scan(0, rule.TenantId+":"+models.FiringAlertCachePrefix+rule.RuleId+"*", 0).Iterator() + keys := make([]string, 0) + for iter.Next() { + key := iter.Val() + keys = append(keys, key) + } + rs.ctx.Redis.Redis().Del(keys...) + } + /* 重启协程 判断当前状态是否是false 并且 历史状态是否为true @@ -68,19 +79,12 @@ func (rs ruleService) Update(req interface{}) (interface{}, interface{}) { } } - // 删除缓存 - iter := rs.ctx.Redis.Redis().Scan(0, rule.TenantId+":"+models.FiringAlertCachePrefix+rule.RuleId+"*", 0).Iterator() - keys := make([]string, 0) - for iter.Next() { - key := iter.Val() - keys = append(keys, key) - } - rs.ctx.Redis.Redis().Del(keys...) - // 启动协程 if *rule.Enabled { rs.rule <- rule global.Logger.Sugar().Infof("重启 RuleId 为 %s 的 Worker 进程", rule.RuleId) + } else { + delEvent() } // 更新数据 diff --git a/pkg/client/victoriametrics.go b/pkg/client/victoriametrics.go new file mode 100644 index 0000000..3b5c084 --- /dev/null +++ b/pkg/client/victoriametrics.go @@ -0,0 +1,117 @@ +package client + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "time" + "watchAlert/internal/global" + "watchAlert/internal/models" + "watchAlert/pkg/utils/hash" + utilsHttp "watchAlert/pkg/utils/http" +) + +type VM struct { + address string +} + +type QueryResponse struct { + VMData VMData `json:"data"` +} + +type VMData struct { + VMResult []VMResult `json:"result"` + ResultType string `json:"resultType"` +} + +type VMResult struct { + Metric map[string]interface{} `json:"metric"` + Value []interface{} `json:"value"` +} + +type VMVector struct { + Metric map[string]interface{} + Value float64 + Timestamp float64 +} + +func NewVictoriaMetricsClient(ds models.AlertDataSource) VM { + _, err := ds.CheckHealth() + if err != nil { + global.Logger.Sugar().Errorf(fmt.Sprintf("数据源不健康, Type: %s, Name: %s, Address: %s, Msg: %s", ds.Type, ds.Name, ds.HTTP.URL, err.Error())) + return VM{} + } + + return VM{address: ds.HTTP.URL} +} + +func (a VM) Query(promQL string) ([]VMVector, error) { + apiEndpoint := fmt.Sprintf("%s%s?query=%s&time=%d", a.address, "/prometheus/api/v1/query", promQL, time.Now().Unix()) + + resp, err := utilsHttp.Get(apiEndpoint) + if err != nil { + global.Logger.Sugar().Error(err.Error()) + return nil, err + } + defer func(Body io.ReadCloser) { + err = Body.Close() + if err != nil { + global.Logger.Sugar().Error(err.Error()) + } + }(resp.Body) + + body, err := io.ReadAll(resp.Body) + if err != nil { + global.Logger.Sugar().Error(err.Error()) + return nil, err + } + + var vmRespBody QueryResponse + err = json.Unmarshal(body, &vmRespBody) + if err != nil { + global.Logger.Sugar().Error(err.Error()) + return nil, err + } + + return vmVectors(vmRespBody.VMData.VMResult), nil +} + +func vmVectors(res []VMResult) []VMVector { + var vectors []VMVector + for _, item := range res { + valueFloat, err := strconv.ParseFloat(item.Value[1].(string), 64) + if err != nil { + global.Logger.Sugar().Error(err.Error()) + return nil + } + vectors = append(vectors, VMVector{ + Metric: item.Metric, + Value: valueFloat, + Timestamp: item.Value[0].(float64), + }) + } + + return vectors +} + +func (a VMVector) GetFingerprint() string { + if len(a.Metric) == 0 { + return strconv.FormatUint(hash.HashNew(), 10) + } + + var result uint64 + for labelName, labelValue := range a.Metric { + sum := hash.HashNew() + sum = hash.HashAdd(sum, labelName) + sum = hash.HashAdd(sum, labelValue.(string)) + result ^= sum + } + + return strconv.FormatUint(result, 10) +} + +func (a VMVector) GetMetric() map[string]interface{} { + a.Metric["value"] = a.Value + return a.Metric +}