Skip to content

Commit

Permalink
perf: improve Prometheus render escaping by 23% (#2922)
Browse files Browse the repository at this point in the history
  • Loading branch information
cgrinds committed May 24, 2024
1 parent c59616b commit 2bbb078
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 9 deletions.
23 changes: 14 additions & 9 deletions cmd/exporters/prometheus/prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type Prometheus struct {
checkAddrs bool
addMetaTags bool
globalPrefix string
replacer *strings.Replacer
}

func New(abc *exporter.AbstractExporter) exporter.Exporter {
Expand All @@ -71,6 +72,8 @@ func (p *Prometheus) Init() error {
return err
}

p.replacer = newReplacer()

if instance, err := p.Metadata.NewInstance("info"); err == nil {
instance.SetLabel("task", "info")
} else {
Expand Down Expand Up @@ -188,6 +191,10 @@ func (p *Prometheus) Init() error {
return nil
}

func newReplacer() *strings.Replacer {
return strings.NewReplacer(`\`, `\\`, `"`, `\"`, "\n", "\\n")
}

// Export - Unlike other Harvest exporters, we don't export data
// but put it in cache. The HTTP daemon serves that cache on request.
//
Expand Down Expand Up @@ -270,7 +277,6 @@ func (p *Prometheus) render(data *matrix.Matrix) ([][]byte, exporter.Stats) {
keysToInclude []string
prefix string
err error
replacer *strings.Replacer
histograms map[string]*histogram
normalizedLabels map[string][]string // cache of histogram normalized labels
instancesExported uint64
Expand All @@ -279,7 +285,6 @@ func (p *Prometheus) render(data *matrix.Matrix) ([][]byte, exporter.Stats) {
rendered = make([][]byte, 0)
globalLabels := make([]string, 0, len(data.GetGlobalLabels()))
normalizedLabels = make(map[string][]string)
replacer = strings.NewReplacer(`\`, `\\`, `"`, `\"`, "\n", "\\n")

if p.addMetaTags {
tagged = set.New()
Expand Down Expand Up @@ -313,7 +318,7 @@ func (p *Prometheus) render(data *matrix.Matrix) ([][]byte, exporter.Stats) {
prefix = p.globalPrefix + data.Object

for key, value := range data.GetGlobalLabels() {
globalLabels = append(globalLabels, escape(replacer, key, value))
globalLabels = append(globalLabels, escape(p.replacer, key, value))
}

for _, instance := range data.GetInstances() {
Expand All @@ -337,21 +342,21 @@ func (p *Prometheus) render(data *matrix.Matrix) ([][]byte, exporter.Stats) {
// instance label (even though it's already a global label for 7modes)
_, ok := data.GetGlobalLabels()[label]
if !ok {
instanceKeys = append(instanceKeys, escape(replacer, label, value)) //nolint:makezero
instanceKeys = append(instanceKeys, escape(p.replacer, label, value)) //nolint:makezero
}
}
} else {
for _, key := range keysToInclude {
value := instance.GetLabel(key)
instanceKeys = append(instanceKeys, escape(replacer, key, value)) //nolint:makezero
instanceKeys = append(instanceKeys, escape(p.replacer, key, value)) //nolint:makezero
if !instanceKeysOk && value != "" {
instanceKeysOk = true
}
}

for _, label := range labelsToInclude {
value := instance.GetLabel(label)
kv := escape(replacer, label, value)
kv := escape(p.replacer, label, value)
_, ok := instanceLabelsSet[kv]
if ok {
continue
Expand Down Expand Up @@ -431,7 +436,7 @@ func (p *Prometheus) render(data *matrix.Matrix) ([][]byte, exporter.Stats) {
}
metricLabels := make([]string, 0, len(metric.GetLabels()))
for k, v := range metric.GetLabels() {
metricLabels = append(metricLabels, escape(replacer, k, v))
metricLabels = append(metricLabels, escape(p.replacer, k, v))
}
x := fmt.Sprintf(
"%s_%s{%s,%s} %s",
Expand Down Expand Up @@ -526,7 +531,7 @@ func (p *Prometheus) render(data *matrix.Matrix) ([][]byte, exporter.Stats) {
prefix,
metric.GetName(),
strings.Join(instanceKeys, ","),
escape(replacer, "metric", bucketName),
escape(p.replacer, "metric", bucketName),
value,
)
}
Expand Down Expand Up @@ -603,7 +608,7 @@ func escape(replacer *strings.Replacer, key string, value string) string {
// label_value can be any sequence of UTF-8 characters, but the backslash (\), double-quote ("),
// and line feed (\n) characters have to be escaped as \\, \", and \n, respectively.

return fmt.Sprintf("%s=%q", key, replacer.Replace(value))
return key + "=" + strconv.Quote(replacer.Replace(value))
}

type histogram struct {
Expand Down
33 changes: 33 additions & 0 deletions cmd/exporters/prometheus/prometheus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,36 @@ func TestFilterMetaTags(t *testing.T) {

t.Log("OK - output is exactly what is expected")
}

func TestEscape(t *testing.T) {
replacer := newReplacer()

type test struct {
key string
value string
want string
}

tests := []test{
{key: `abc`, value: `abc`, want: `abc="abc"`},
{key: `abc`, value: `a"b"c`, want: `abc="a\\\"b\\\"c"`},
{key: `abc`, value: `a\c`, want: `abc="a\\\\c"`},
{key: `abc`, value: `a\nc`, want: `abc="a\\\\nc"`},
}

for _, tc := range tests {
t.Run(tc.want, func(t *testing.T) {
got := escape(replacer, tc.key, tc.value)
if got != tc.want {
t.Errorf("escape failed got=[%s] want=[%s] for key=[%s] value=[%s]", got, tc.want, tc.key, tc.value)
}
})
}
}

func BenchmarkEscape(b *testing.B) {
replacer := newReplacer()
for range b.N {
escape(replacer, "abc", `a\c"foo"\ndef`)
}
}

0 comments on commit 2bbb078

Please sign in to comment.