Add ByteSize method for Labels (#16717)

Add `ByteSize()` method to different labels implementations.
One of the use case so that we can track the memory used by Labels.

Signed-off-by: Jon Kartago Lamida <me@lamida.net>
This commit is contained in:
Jon Kartago Lamida 2025-07-04 21:09:01 +07:00 committed by GitHub
parent 5a5424cbc1
commit 819500bdbc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 72 additions and 0 deletions

View File

@ -248,6 +248,17 @@ func (ls Labels) WithoutEmpty() Labels {
return ls
}
// ByteSize returns the approximate size of the labels in bytes including
// the two string headers size for name and value.
// Slice header size is ignored because it should be amortized to zero.
func (ls Labels) ByteSize() uint64 {
var size uint64 = 0
for _, l := range ls {
size += uint64(len(l.Name)+len(l.Value)) + 2*uint64(unsafe.Sizeof(""))
}
return size
}
// Equal returns whether the two label sets are equal.
func Equal(ls, o Labels) bool {
return slices.Equal(ls, o)

View File

@ -417,6 +417,13 @@ func (ls Labels) WithoutEmpty() Labels {
return ls
}
// ByteSize returns the approximate size of the labels in bytes.
// String header size is ignored because it should be amortized to zero.
// SymbolTable size is also not taken into account.
func (ls Labels) ByteSize() uint64 {
return uint64(len(ls.data))
}
// Equal returns whether the two label sets are equal.
func Equal(a, b Labels) bool {
if a.syms == b.syms {

View File

@ -30,6 +30,15 @@ var expectedSizeOfLabels = []uint64{ // Values must line up with testCaseLabels.
325,
}
var expectedByteSize = []uint64{ // Values must line up with testCaseLabels.
8,
0,
8,
8,
8,
32,
}
func TestVarint(t *testing.T) {
cases := []struct {
v int

View File

@ -23,3 +23,5 @@ var expectedSizeOfLabels = []uint64{ // Values must line up with testCaseLabels.
327,
549,
}
var expectedByteSize = expectedSizeOfLabels // They are identical

View File

@ -283,6 +283,13 @@ func (ls Labels) WithoutEmpty() Labels {
return ls
}
// ByteSize returns the approximate size of the labels in bytes.
// String header size is ignored because it should be amortized to zero
// because it may be shared across multiple copies of the Labels.
func (ls Labels) ByteSize() uint64 {
return uint64(len(ls.data))
}
// Equal returns whether the two label sets are equal.
func Equal(ls, o Labels) bool {
return ls.data == o.data

View File

@ -23,3 +23,12 @@ var expectedSizeOfLabels = []uint64{ // Values must line up with testCaseLabels.
270,
309,
}
var expectedByteSize = []uint64{ // Values must line up with testCaseLabels.
12,
0,
37,
266,
270,
309,
}

View File

@ -74,6 +74,33 @@ func TestSizeOfLabels(t *testing.T) {
}
}
func TestByteSize(t *testing.T) {
require.Len(t, expectedByteSize, len(testCaseLabels))
for i, c := range expectedByteSize { // Declared in build-tag-specific files, e.g. labels_slicelabels_test.go.
require.Equal(t, c, testCaseLabels[i].ByteSize())
}
}
var GlobalTotal uint64 // Encourage the compiler not to elide the benchmark computation.
func BenchmarkSize(b *testing.B) {
lb := New(benchmarkLabels...)
b.Run("SizeOfLabels", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var total uint64
lb.Range(func(l Label) {
total += SizeOfLabels(l.Name, l.Value, 1)
})
GlobalTotal = total
}
})
b.Run("ByteSize", func(b *testing.B) {
for i := 0; i < b.N; i++ {
GlobalTotal = lb.ByteSize()
}
})
}
func TestLabels_MatchLabels(t *testing.T) {
labels := FromStrings(
"__name__", "ALERTS",