pkg/metrics_store: Cache byte slices instead of strings
When converting a string to a byte slice, in Golang would do a full copy which introduces memory allocations. On an HTTP scrape request WriteAll in the metrics store component is called, which writes all cached metrics into the given io.Writer. io.Writer takes a byte slice, whereas metrics are cached as strings. The connect the two, the metrics store component converts the metric strings to byte slices. This results in `runtime.stringtoslicebyte` [1] being a prominent CPU user. Instead of caching metrics as strings, cache them as byte slices, removing the additional translation from the hot-path. [1] https://golang.org/src/runtime/string.go?s=4063:4115#L145
This commit is contained in:
parent
bfdfa8b87e
commit
bbb1dca85d
|
|
@ -31,14 +31,14 @@ type generateMetricsTestCase struct {
|
|||
Obj interface{}
|
||||
MetricNames []string
|
||||
Want string
|
||||
Func func(interface{}) []metricsstore.FamilyStringer
|
||||
Func func(interface{}) []metricsstore.FamilyByteSlicer
|
||||
}
|
||||
|
||||
func (testCase *generateMetricsTestCase) run() error {
|
||||
metricFamilies := testCase.Func(testCase.Obj)
|
||||
metricFamilyStrings := []string{}
|
||||
for _, f := range metricFamilies {
|
||||
metricFamilyStrings = append(metricFamilyStrings, f.String())
|
||||
metricFamilyStrings = append(metricFamilyStrings, string(f.ByteSlice()))
|
||||
}
|
||||
|
||||
metric := strings.Split(strings.Join(metricFamilyStrings, ""), "\n")
|
||||
|
|
|
|||
|
|
@ -26,19 +26,13 @@ type Family struct {
|
|||
Metrics []*Metric
|
||||
}
|
||||
|
||||
// String returns the given Family in its string representation.
|
||||
func (f Family) String() string {
|
||||
// ByteSlice returns the given Family in its string representation.
|
||||
func (f Family) ByteSlice() []byte {
|
||||
b := strings.Builder{}
|
||||
for _, m := range f.Metrics {
|
||||
b.WriteString(f.Name)
|
||||
m.Write(&b)
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// FamilyStringer represents a metric family that can be converted to its string
|
||||
// representation.
|
||||
type FamilyStringer interface {
|
||||
String() string
|
||||
return []byte(b.String())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,9 +70,9 @@ func ExtractMetricFamilyHeaders(families []FamilyGenerator) []string {
|
|||
|
||||
// ComposeMetricGenFuncs takes a slice of metric families and returns a function
|
||||
// that composes their metric generation functions into a single one.
|
||||
func ComposeMetricGenFuncs(familyGens []FamilyGenerator) func(obj interface{}) []metricsstore.FamilyStringer {
|
||||
return func(obj interface{}) []metricsstore.FamilyStringer {
|
||||
families := make([]metricsstore.FamilyStringer, len(familyGens))
|
||||
func ComposeMetricGenFuncs(familyGens []FamilyGenerator) func(obj interface{}) []metricsstore.FamilyByteSlicer {
|
||||
return func(obj interface{}) []metricsstore.FamilyByteSlicer {
|
||||
families := make([]metricsstore.FamilyByteSlicer, len(familyGens))
|
||||
|
||||
for i, gen := range familyGens {
|
||||
families[i] = gen.Generate(obj)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package metric
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -56,7 +57,10 @@ type Metric struct {
|
|||
|
||||
func (m *Metric) Write(s *strings.Builder) {
|
||||
if len(m.LabelKeys) != len(m.LabelValues) {
|
||||
panic("expected labelKeys to be of same length as labelValues")
|
||||
panic(fmt.Sprintf(
|
||||
"expected labelKeys %q to be of same length as labelValues %q",
|
||||
m.LabelKeys, m.LabelValues,
|
||||
))
|
||||
}
|
||||
|
||||
labelsToString(s, m.LabelKeys, m.LabelValues)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func TestFamilyString(t *testing.T) {
|
|||
}
|
||||
|
||||
expected := "kube_pod_info{namespace=\"default\"} 1"
|
||||
got := strings.TrimSpace(f.String())
|
||||
got := strings.TrimSpace(string(f.ByteSlice()))
|
||||
|
||||
if got != expected {
|
||||
t.Fatalf("expected %v but got %v", expected, got)
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ import (
|
|||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// FamilyStringer represents a metric family that can be converted to its string
|
||||
// FamilyByteSlicer represents a metric family that can be converted to its string
|
||||
// representation.
|
||||
type FamilyStringer interface {
|
||||
String() string
|
||||
type FamilyByteSlicer interface {
|
||||
ByteSlice() []byte
|
||||
}
|
||||
|
||||
// MetricsStore implements the k8s.io/client-go/tools/cache.Store
|
||||
|
|
@ -40,7 +40,7 @@ type MetricsStore struct {
|
|||
// metric families, containing a slice of metrics. We need to keep metrics
|
||||
// grouped by metric families in order to zip families with their help text in
|
||||
// MetricsStore.WriteAll().
|
||||
metrics map[types.UID][]string
|
||||
metrics map[types.UID][][]byte
|
||||
// headers contains the header (TYPE and HELP) of each metric family. It is
|
||||
// later on zipped with with their corresponding metric families in
|
||||
// MetricStore.WriteAll().
|
||||
|
|
@ -48,15 +48,15 @@ type MetricsStore struct {
|
|||
|
||||
// generateMetricsFunc generates metrics based on a given Kubernetes object
|
||||
// and returns them grouped by metric family.
|
||||
generateMetricsFunc func(interface{}) []FamilyStringer
|
||||
generateMetricsFunc func(interface{}) []FamilyByteSlicer
|
||||
}
|
||||
|
||||
// NewMetricsStore returns a new MetricsStore
|
||||
func NewMetricsStore(headers []string, generateFunc func(interface{}) []FamilyStringer) *MetricsStore {
|
||||
func NewMetricsStore(headers []string, generateFunc func(interface{}) []FamilyByteSlicer) *MetricsStore {
|
||||
return &MetricsStore{
|
||||
generateMetricsFunc: generateFunc,
|
||||
headers: headers,
|
||||
metrics: map[types.UID][]string{},
|
||||
metrics: map[types.UID][][]byte{},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,10 +74,10 @@ func (s *MetricsStore) Add(obj interface{}) error {
|
|||
defer s.mutex.Unlock()
|
||||
|
||||
families := s.generateMetricsFunc(obj)
|
||||
familyStrings := make([]string, len(families))
|
||||
familyStrings := make([][]byte, len(families))
|
||||
|
||||
for i, f := range families {
|
||||
familyStrings[i] = f.String()
|
||||
familyStrings[i] = f.ByteSlice()
|
||||
}
|
||||
|
||||
s.metrics[o.GetUID()] = familyStrings
|
||||
|
|
@ -131,7 +131,7 @@ func (s *MetricsStore) GetByKey(key string) (item interface{}, exists bool, err
|
|||
// given list.
|
||||
func (s *MetricsStore) Replace(list []interface{}, _ string) error {
|
||||
s.mutex.Lock()
|
||||
s.metrics = map[types.UID][]string{}
|
||||
s.metrics = map[types.UID][][]byte{}
|
||||
s.mutex.Unlock()
|
||||
|
||||
for _, o := range list {
|
||||
|
|
|
|||
|
|
@ -30,28 +30,28 @@ import (
|
|||
// Mock metricFamily instead of importing /pkg/metric to prevent cyclic
|
||||
// dependency.
|
||||
type metricFamily struct {
|
||||
value string
|
||||
value []byte
|
||||
}
|
||||
|
||||
// Implement FamilyStringer interface.
|
||||
func (f *metricFamily) String() string {
|
||||
// Implement FamilyByteSlicer interface.
|
||||
func (f *metricFamily) ByteSlice() []byte {
|
||||
return f.value
|
||||
}
|
||||
|
||||
func TestObjectsSameNameDifferentNamespaces(t *testing.T) {
|
||||
serviceIDS := []string{"a", "b"}
|
||||
|
||||
genFunc := func(obj interface{}) []FamilyStringer {
|
||||
genFunc := func(obj interface{}) []FamilyByteSlicer {
|
||||
o, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
metricFamily := metricFamily{
|
||||
fmt.Sprintf("kube_service_info{uid=\"%v\"} 1", string(o.GetUID())),
|
||||
[]byte(fmt.Sprintf("kube_service_info{uid=\"%v\"} 1", string(o.GetUID()))),
|
||||
}
|
||||
|
||||
return []FamilyStringer{&metricFamily}
|
||||
return []FamilyByteSlicer{&metricFamily}
|
||||
}
|
||||
|
||||
ms := NewMetricsStore([]string{"Information about service."}, genFunc)
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ func serviceCollector(kubeClient clientset.Interface) *collector.Collector {
|
|||
return collector.NewCollector(store)
|
||||
}
|
||||
|
||||
func generateServiceMetrics(obj interface{}) []metricsstore.FamilyStringer {
|
||||
func generateServiceMetrics(obj interface{}) []metricsstore.FamilyByteSlicer {
|
||||
sPointer := obj.(*v1.Service)
|
||||
s := *sPointer
|
||||
|
||||
|
|
@ -97,5 +97,5 @@ func generateServiceMetrics(obj interface{}) []metricsstore.FamilyStringer {
|
|||
Metrics: []*metric.Metric{&m},
|
||||
}
|
||||
|
||||
return []metricsstore.FamilyStringer{&family}
|
||||
return []metricsstore.FamilyByteSlicer{&family}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue