Skip to content

Commit

Permalink
refactor(sumologicextension): use bytes slices and strings.Builder to…
Browse files Browse the repository at this point in the history
… decrease allocations
  • Loading branch information
pmalek committed Apr 6, 2022
1 parent 54b5775 commit 46df176
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 17 deletions.
86 changes: 70 additions & 16 deletions pkg/exporter/sumologicexporter/prometheus_formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,38 +57,90 @@ func newPrometheusFormatter() (prometheusFormatter, error) {

// PrometheusLabels returns all attributes as sanitized prometheus labels string
func (f *prometheusFormatter) tags2String(attr pdata.AttributeMap, labels pdata.AttributeMap) prometheusTags {
attrsPlusLabelsLen := attr.Len() + labels.Len()
if attrsPlusLabelsLen == 0 {
return ""
}

mergedAttributes := pdata.NewAttributeMap()
mergedAttributes.EnsureCapacity(attrsPlusLabelsLen)

attr.CopyTo(mergedAttributes)
labels.Range(func(k string, v pdata.AttributeValue) bool {
mergedAttributes.UpsertString(k, v.StringVal())
return true
})
length := mergedAttributes.Len()

if length == 0 {
return ""
}

returnValue := make([]string, 0, length)
mergedAttributes.Range(func(k string, v pdata.AttributeValue) bool {
key := f.sanitizeKeyBytes([]byte(k))
value := f.sanitizeValue(v.AsString())

returnValue = append(
returnValue,
fmt.Sprintf(
`%s="%s"`,
f.sanitizeKey(k),
f.sanitizeValue(v.AsString()),
),
formatKeyValuePair(key, value),
)
return true
})

return prometheusTags(fmt.Sprintf("{%s}", strings.Join(returnValue, ",")))
return prometheusTags(stringsJoinAndSurround(returnValue, ',', '{', '}'))
}

func formatKeyValuePair(key []byte, value string) string {
// Use strings.Builder and not fmt.Sprintf as it uses significantly less
// allocations.
sb := strings.Builder{}
// We preallocate space for key, value, equal sign and quotes.
sb.Grow(len(key) + len(value) + 1 + 2)
sb.Write(key)
sb.WriteRune('=')
sb.WriteRune('"')
sb.WriteString(value)
sb.WriteRune('"')
return sb.String()
}

// sanitizeKey returns sanitized key string by replacing
// stringsJoinAndSurround joins the strings in s slice using the separator adds front
// the the front of the resulting string and back at the end.
//
// This has a benefit over using the strings.Join() of using just one strings.Buidler
// instance and hence using less allocations to produce the final string.
func stringsJoinAndSurround(s []string, separator rune, front rune, back rune) string {
switch len(s) {
case 0:
return ""
case 1:
var b strings.Builder
b.Grow(len(s[0]) + 2)
b.WriteRune(front)
b.WriteString(s[0])
b.WriteRune(back)
return b.String()
}
n := (len(s) - 1)
for i := 0; i < len(s); i++ {
n += len(s[i])
}

var b strings.Builder
// We preallocate space for all the keys and values as well as the surrounding
// characters.
b.Grow(n + 2)
b.WriteRune(front)
b.WriteString(s[0])
for _, s := range s[1:] {
b.WriteRune(separator)
b.WriteString(s)
}
b.WriteRune(back)
return b.String()
}

// sanitizeKeyBytes returns sanitized key byte slice by replacing
// all non-allowed chars with `_`
func (f *prometheusFormatter) sanitizeKey(s string) string {
return f.sanitNameRegex.ReplaceAllString(s, "_")
func (f *prometheusFormatter) sanitizeKeyBytes(s []byte) []byte {
return f.sanitNameRegex.ReplaceAll(s, []byte{'_'})
}

// sanitizeKey returns sanitized value string performing the following substitutions:
Expand All @@ -103,7 +155,7 @@ func (f *prometheusFormatter) sanitizeValue(s string) string {
func (f *prometheusFormatter) doubleLine(name string, attributes prometheusTags, value float64, timestamp pdata.Timestamp) string {
return fmt.Sprintf(
"%s%s %g %d",
f.sanitizeKey(name),
f.sanitizeKeyBytes([]byte(name)),
attributes,
value,
timestamp/pdata.Timestamp(time.Millisecond),
Expand All @@ -114,7 +166,7 @@ func (f *prometheusFormatter) doubleLine(name string, attributes prometheusTags,
func (f *prometheusFormatter) intLine(name string, attributes prometheusTags, value int64, timestamp pdata.Timestamp) string {
return fmt.Sprintf(
"%s%s %d %d",
f.sanitizeKey(name),
f.sanitizeKeyBytes([]byte(name)),
attributes,
value,
timestamp/pdata.Timestamp(time.Millisecond),
Expand All @@ -125,7 +177,7 @@ func (f *prometheusFormatter) intLine(name string, attributes prometheusTags, va
func (f *prometheusFormatter) uintLine(name string, attributes prometheusTags, value uint64, timestamp pdata.Timestamp) string {
return fmt.Sprintf(
"%s%s %d %d",
f.sanitizeKey(name),
f.sanitizeKeyBytes([]byte(name)),
attributes,
value,
timestamp/pdata.Timestamp(time.Millisecond),
Expand Down Expand Up @@ -186,6 +238,8 @@ func (f *prometheusFormatter) countMetric(name string) string {
// mergeAttributes gets two pdata.AttributeMaps and returns new which contains values from both of them
func (f *prometheusFormatter) mergeAttributes(attributes pdata.AttributeMap, additionalAttributes pdata.AttributeMap) pdata.AttributeMap {
mergedAttributes := pdata.NewAttributeMap()
mergedAttributes.EnsureCapacity(attributes.Len() + additionalAttributes.Len())

attributes.CopyTo(mergedAttributes)
additionalAttributes.Range(func(k string, v pdata.AttributeValue) bool {
mergedAttributes.Upsert(k, v)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestSanitizeKey(t *testing.T) {

key := "&^*123-abc-ABC!./?_:\n\r"
expected := "___123-abc-ABC_./__:__"
assert.Equal(t, expected, f.sanitizeKey(key))
assert.EqualValues(t, expected, f.sanitizeKeyBytes([]byte(key)))
}

func TestSanitizeValue(t *testing.T) {
Expand Down

0 comments on commit 46df176

Please sign in to comment.