Skip to content

Commit

Permalink
Tidy unmarshalling responses
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandreLamarre committed Aug 11, 2022
1 parent 816fc9e commit d1d36e8
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 137 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ require (
github.com/samber/lo v1.26.0
github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5
github.com/tidwall/gjson v1.14.1
github.com/tidwall/gjson v1.14.2
github.com/tidwall/sjson v1.2.4
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31
github.com/vbauerster/mpb/v7 v7.4.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2715,8 +2715,8 @@ github.com/tidwall/gjson v1.3.2/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJH
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/gjson v1.7.1/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
Expand Down
72 changes: 3 additions & 69 deletions pkg/metrics/unmarshal/unmarshal.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
package unmarshal

import (
"bytes"
"encoding/json"
"fmt"
"github.com/prometheus/common/model"
"go.uber.org/zap"
"io"
"net/http"
"reflect"

"github.com/prometheus/common/model"
)

// Struct for unmarshalling from github.com/prometheus/common/model
Expand Down Expand Up @@ -123,23 +119,8 @@ func unmarshallPrometheusWebResponseData(data []byte) (*response, error) {
}

func UnmarshallPrometheusWebResponse(resp *http.Response, lg *zap.SugaredLogger) (*response, error) {
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
lg.With(
"error", err,
).Error("failed to close response body")
}
}(resp.Body)
responseBuf := new(bytes.Buffer)
if _, err := io.Copy(responseBuf, resp.Body); err != nil {
lg.With(
"error", err,
).Error("failed to read response body")
return nil, err
}

val, err := unmarshallPrometheusWebResponseData(responseBuf.Bytes())
var val *response
err := json.NewDecoder(resp.Body).Decode(&val)
if err != nil {
return nil, err
}
Expand All @@ -148,50 +129,3 @@ func UnmarshallPrometheusWebResponse(resp *http.Response, lg *zap.SugaredLogger)
}
return val, nil
}

func InterfaceToInterfaceSlice(input interface{}) ([]interface{}, error) {
i := reflect.ValueOf(input)
if i.Kind() != reflect.Slice {
return nil, fmt.Errorf("input is not a slice")
}
if i.IsNil() {
return nil, fmt.Errorf("input is nil")
}
output := make([]interface{}, i.Len())
for j := 0; j < i.Len(); j++ {
output[j] = i.Index(j).Interface()
}
return output, nil
}

func InterfaceToMap(input interface{}) (map[string]interface{}, error) {
m := reflect.ValueOf(input)
if m.Kind() != reflect.Map {
return nil, fmt.Errorf("input is not a map")
}
if m.IsNil() {
return nil, fmt.Errorf("provided interface is nil")
}

output := make(map[string]interface{})
for _, k := range m.MapKeys() {
output[k.String()] = m.MapIndex(k).Interface()
}

return output, nil
}

func InterfaceToStringSlice(input interface{}) ([]string, error) {
s := reflect.ValueOf(input)
if s.Kind() != reflect.Slice {
return nil, fmt.Errorf("Provided interface cannot be converted to a slice")
}
if s.IsNil() {
return nil, fmt.Errorf("Provided interface is nil")
}
res := make([]string, s.Len())
for i := 0; i < s.Len(); i++ {
res[i] = fmt.Sprintf("%v", s.Index(i).Interface())
}
return res, nil
}
11 changes: 4 additions & 7 deletions plugins/cortex/pkg/cortex/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,13 +451,13 @@ func (p *Plugin) GetSeriesMetrics(ctx context.Context, request *cortexadmin.Seri
mapVal, err := parseCortexSeriesMetadata(resp, lg, uniqueMetricName)
if err == nil {
if metricHelp, ok := mapVal["help"]; ok {
m.Description = fmt.Sprintf("%v", metricHelp)
m.Description = metricHelp.String()
}
if metricType, ok := mapVal["type"]; ok {
m.Type = fmt.Sprintf("%v", metricType)
m.Type = metricType.String()
}
if metricUnit, ok := mapVal["unit"]; ok && metricUnit == "" {
m.Unit = fmt.Sprintf("%v", metricUnit)
if metricUnit, ok := mapVal["unit"]; ok && metricUnit.String() == "" {
m.Unit = metricUnit.String()
}
}
}
Expand Down Expand Up @@ -487,9 +487,6 @@ func (p *Plugin) GetMetricLabelSets(ctx context.Context, request *cortexadmin.La
}
labelSets := []*cortexadmin.LabelSet{} // label name -> list of label values
for _, labelName := range labelNames {
if labelName == "job" || labelName == "__name__" { //useless labels
continue
}
labelResp, err := getCortexLabelValues(p, ctx, request, labelName)
if err != nil {
return nil, err //FIXME: consider returning partial results
Expand Down
69 changes: 26 additions & 43 deletions plugins/cortex/pkg/cortex/prometheus_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package cortex
import (
"context"
"fmt"
"github.com/rancher/opni/pkg/metrics/unmarshal"
"github.com/rancher/opni/plugins/cortex/pkg/apis/cortexadmin"
"github.com/tidwall/gjson"
"go.uber.org/zap"
"io"
"net/http"
Expand Down Expand Up @@ -68,25 +68,17 @@ func enumerateCortexSeries(p *Plugin, lg *zap.SugaredLogger, ctx context.Context
}

func parseCortexEnumerateSeries(resp *http.Response, lg *zap.SugaredLogger) (set map[string]struct{}, err error) {
val, err := unmarshal.UnmarshallPrometheusWebResponse(resp, lg)
if err != nil {
return nil, err
b, err := io.ReadAll(resp.Body)
if !gjson.Valid(string(b)) {
return nil, fmt.Errorf("invalid json in response")
}
set = make(map[string]struct{})
// must convert to slice
interfaceSlice, err := unmarshal.InterfaceToInterfaceSlice(val.Data)
if err != nil {
return nil, err
result := gjson.Get(string(b), "data.#.__name__")
if !result.Exists() {
return nil, fmt.Errorf("Empty series response from cortex")
}
// must convert each to a map[string]interface{}
for _, i := range interfaceSlice {
mapStruct, err := unmarshal.InterfaceToMap(i)
if err != nil {
continue
}
if v, ok := mapStruct["__name__"]; ok {
set[fmt.Sprintf("%v", v)] = struct{}{}
}
for _, name := range result.Array() {
set[name.String()] = struct{}{}
}
return set, nil
}
Expand All @@ -102,30 +94,17 @@ func fetchCortexSeriesMetadata(p *Plugin, lg *zap.SugaredLogger, ctx context.Con
return resp, err
}

func parseCortexSeriesMetadata(resp *http.Response, lg *zap.SugaredLogger, metricName string) (map[string]interface{}, error) {
val, err := unmarshal.UnmarshallPrometheusWebResponse(resp, lg)
if err != nil {
return nil, err
}
if val.Data == nil { //FIXME currently cortex always returns nil here & its not clear why
return nil, fmt.Errorf("no metadata in response")
func parseCortexSeriesMetadata(resp *http.Response, lg *zap.SugaredLogger, metricName string) (map[string]gjson.Result, error) {
b, err := io.ReadAll(resp.Body)
if !gjson.Valid(string(b)) {
return nil, fmt.Errorf("invalid json in response")
}
// otherwise it is a map -> list -> map[string]interface{}
valMap, err := unmarshal.InterfaceToMap(val.Data)
if err != nil {
return nil, err
}
if _, ok := valMap[metricName]; !ok {
return nil, fmt.Errorf(fmt.Sprintf("no metadata for metric %s in response", metricName))
}
// technically each metric name can have multiple metadata if you set it up in a stupid way
bestResult, err := unmarshal.InterfaceToInterfaceSlice(valMap[metricName])
if err != nil {
return nil, err
result := gjson.Get(string(b), fmt.Sprintf("data.%s)", metricName))
if !result.Exists() {
return nil, fmt.Errorf("no metadata in cortex response")
}
// first result is not necessarily the best result
res, err := unmarshal.InterfaceToMap(bestResult[0])
return res, err
metadata := result.Array()[0].Map()
return metadata, err
}

func getCortexMetricLabels(p *Plugin, lg *zap.SugaredLogger, ctx context.Context, request *cortexadmin.LabelRequest) (*http.Response, error) {
Expand All @@ -140,13 +119,17 @@ func getCortexMetricLabels(p *Plugin, lg *zap.SugaredLogger, ctx context.Context
}

func parseCortexMetricLabels(p *Plugin, resp *http.Response) ([]string, error) {
allLabelsStruct, err := unmarshal.UnmarshallPrometheusWebResponse(resp, p.logger)
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
labelNames, err := unmarshal.InterfaceToStringSlice(allLabelsStruct.Data)
if err != nil {
return nil, err
labelNames := []string{}
result := gjson.Get(string(b), "data")
for _, name := range result.Array() {
if name.String() == "__name__" || name.String() == "job" {
continue
}
labelNames = append(labelNames, name.String())
}
return labelNames, nil
}
Expand Down
77 changes: 62 additions & 15 deletions plugins/slo/pkg/slo/simple.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package slo

import (
"bytes"
"fmt"
"github.com/google/uuid"
prommodel "github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/rulefmt"
"gopkg.in/yaml.v3"
"os"
"text/template"
"time"
)

Expand All @@ -15,14 +17,37 @@ const (
slo_service = "slo_opni_service"
slo_name = "slo_opni_name"
ratio_rate_query_name = "slo:sli_error:ratio_rate"
RecordingRuleSuffix = "-recording"
MetadataRuleSuffix = "-metadata"
AlertRuleSuffix = "-alerts"
)

var (
RecordingRuleSuffix = "-recording"
MetadataRuleSuffix = "-metadata"
AlertRuleSuffix = "-alerts"
goodQueryTpl = template.Must(template.New("").Parse(`
sum(rate({{.Metric}}\{job=\"{{.JobId }}\"{{.Labels}}\}[{{.Window}}]))
`))
totalQueryTpl = template.Must(template.New("").Parse(`
sum(rate({{.Metric}}\{job=\"{{.JobId}}\"{{.Labels}}\}[{{"{{.Window}}"}}]))
`))
rawSliQueryTpl = template.Must(template.New("").Parse(`
1 - (({{.GoodQuery}})/({{.TotalQuery}}))
`))
)

// QueryInfo used for filling query Templates
type QueryInfo struct {
Metric string
JodId string
Labels string
Window string
}

// SliQueryInfo used for filling sli query templates
type SliQueryInfo struct {
GoodQuery string
TotalQuery string
}

type ruleGroupYAMLv2 struct {
Name string `yaml:"name"`
Interval prommodel.Duration `yaml:"interval,omitempty"`
Expand Down Expand Up @@ -121,16 +146,35 @@ func (s *SLO) GetName() string {
return s.idLabels[slo_name] // let it panic if not found
}

func (s *SLO) RawSLIQuery(w string) string {
func (s *SLO) RawSLIQuery(w string) (string, error) {
goodConstructedEvents := s.goodEvents.Construct()
simpleQueryGood := fmt.Sprintf("%s{job=\"{%s}\"%s}", s.goodMetric, s.svc, goodConstructedEvents)
aggregateQueryGood := fmt.Sprintf("sum(rate(%s[%s]))", simpleQueryGood, w)

totalConstructedEvents := s.totalEvents.Construct()
simpleQueryTotal := fmt.Sprintf("%s{job=\"{%s}\"%s}", s.totalMetric, s.svc, totalConstructedEvents)
aggregateQueryTotal := fmt.Sprintf("sum(rate(%s[%s]))", simpleQueryTotal, w)

return fmt.Sprintf("1 - (%s/%s)", aggregateQueryGood, aggregateQueryTotal)
var bGood bytes.Buffer
q := QueryInfo{
Metric: string(s.goodMetric),
JodId: string(s.svc),
Labels: goodConstructedEvents,
Window: w,
}
err := goodQueryTpl.Execute(&bGood, q)
if err != nil {
return "", err
}
var bTotal bytes.Buffer
err = totalQueryTpl.Execute(&bTotal, QueryInfo{
Metric: string(s.totalMetric),
JodId: string(s.svc),
Labels: goodConstructedEvents,
Window: w,
})
if err != nil {
return "", err
}
var bQuery bytes.Buffer
err = rawSliQueryTpl.Execute(&bQuery, SliQueryInfo{
GoodQuery: bGood.String(),
TotalQuery: bTotal.String(),
})
return bQuery.String(), err
}

func (s *SLO) ConstructCortexRules() (queryStr string) {
Expand All @@ -151,9 +195,13 @@ func (s *SLO) ConstructCortexRules() (queryStr string) {
Interval: interval,
}
for _, w := range NewWindowRange(s.sloPeriod) {
rawSli, err := s.RawSLIQuery(w)
if err != nil {
panic(err)
}
rrecording.Rules = append(rrecording.Rules, rulefmt.Rule{
Record: ratio_rate_query_name + w,
Expr: s.RawSLIQuery(w),
Expr: rawSli,
Labels: MergeLabels(s.idLabels, map[string]string{
"slo_window": w,
}),
Expand All @@ -163,7 +211,7 @@ func (s *SLO) ConstructCortexRules() (queryStr string) {
rmetadata.Rules = []rulefmt.Rule{
{
Record: "slo:objective:ratio",
Expr: fmt.Sprintf("vector(0.%s)", s.objective/100),
Expr: fmt.Sprintf("vector(0.%f)", s.objective/100),
},
{
Record: "slo:error_budget:ratio",
Expand Down Expand Up @@ -245,7 +293,6 @@ func (s *SLO) ConstructCortexRules() (queryStr string) {
os.WriteFile(s.GetId()+"-metadata.yaml", smetadata, 0644)
os.WriteFile(s.GetId()+"-alerts.yaml", salerts, 0644)
}

return queryStr
}

Expand Down

0 comments on commit d1d36e8

Please sign in to comment.