Skip to content

Commit 240c724

Browse files
Order labels by cardinality (and show its count) (#11)
Signed-off-by: Pedro Tanaka <pedro.stanaka@gmail.com>
1 parent f2d7de5 commit 240c724

File tree

4 files changed

+132
-6
lines changed

4 files changed

+132
-6
lines changed

cmd/cardinality.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ type seriesTable struct {
4343
func newModel(sm map[string]scrape.SeriesSet, height int) *seriesTable {
4444
tbl := table.New(
4545
table.WithColumns([]table.Column{
46-
{Title: "Name", Width: 80},
46+
{Title: "Name", Width: 60},
4747
{Title: "Cardinality", Width: 16},
4848
{Title: "Type", Width: 10},
4949
{Title: "Labels", Width: 80},
50-
{Title: "Created TS", Width: 40},
50+
{Title: "Created TS", Width: 50},
5151
}),
5252
table.WithFocused(true),
5353
table.WithHeight(height),

go.mod

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ module github.com/pedro-stanaka/prom-scrape-analyzer
33
go 1.23.1
44

55
require (
6+
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9
67
github.com/charmbracelet/bubbles v0.20.0
8+
github.com/charmbracelet/bubbletea v1.1.0
9+
github.com/charmbracelet/lipgloss v0.13.0
710
github.com/go-kit/log v0.2.1
811
github.com/oklog/run v1.1.0
912
github.com/opentracing/opentracing-go v1.2.0
1013
github.com/pkg/errors v0.9.1
1114
github.com/prometheus/client_golang v1.19.1
1215
github.com/prometheus/prometheus v0.52.2-0.20240614130246-4c1e71fa0b3d
16+
github.com/stretchr/testify v1.9.0
1317
github.com/thanos-io/thanos v0.36.1
1418
gopkg.in/alecthomas/kingpin.v2 v2.2.6
1519
)
@@ -20,16 +24,14 @@ require (
2024
github.com/Azure/azure-sdk-for-go/sdk/internal v1.6.0 // indirect
2125
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
2226
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
23-
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect
2427
github.com/aws/aws-sdk-go v1.53.16 // indirect
2528
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
2629
github.com/beorn7/perks v1.0.1 // indirect
2730
github.com/cespare/xxhash/v2 v2.3.0 // indirect
28-
github.com/charmbracelet/bubbletea v1.1.0 // indirect
29-
github.com/charmbracelet/lipgloss v0.13.0 // indirect
3031
github.com/charmbracelet/x/ansi v0.2.3 // indirect
3132
github.com/charmbracelet/x/term v0.2.0 // indirect
3233
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
34+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
3335
github.com/dennwc/varint v1.0.0 // indirect
3436
github.com/edsrzf/mmap-go v1.1.0 // indirect
3537
github.com/efficientgo/tools/extkingpin v0.0.0-20220817170617-6c25e3b627dd // indirect
@@ -59,6 +61,7 @@ require (
5961
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
6062
github.com/oklog/ulid v1.3.1 // indirect
6163
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
64+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
6265
github.com/prometheus/client_model v0.6.1 // indirect
6366
github.com/prometheus/common v0.54.1-0.20240615204547-04635d2962f9 // indirect
6467
github.com/prometheus/common/sigv4 v0.1.0 // indirect
@@ -90,6 +93,7 @@ require (
9093
google.golang.org/grpc v1.64.0 // indirect
9194
google.golang.org/protobuf v1.34.2 // indirect
9295
gopkg.in/yaml.v2 v2.4.0 // indirect
96+
gopkg.in/yaml.v3 v3.0.1 // indirect
9397
k8s.io/apimachinery v0.29.3 // indirect
9498
k8s.io/client-go v0.29.3 // indirect
9599
k8s.io/klog/v2 v2.120.1 // indirect

pkg/scrape/series.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package scrape
22

33
import (
4+
"fmt"
45
"slices"
56
"strings"
67
"time"
@@ -68,6 +69,57 @@ func (s SeriesSet) LabelNames() string {
6869
return strings.Join(lbls, "|")
6970
}
7071

72+
func (s SeriesSet) LabelStats() LabelStatsSlice {
73+
if len(s) == 0 {
74+
return nil
75+
}
76+
labelValueSet := make(map[string]map[string]struct{})
77+
78+
for _, v := range s {
79+
for _, l := range v.Labels {
80+
if l.Name != "__name__" {
81+
// Initialize the inner map if it doesn't exist
82+
if _, exists := labelValueSet[l.Name]; !exists {
83+
labelValueSet[l.Name] = make(map[string]struct{})
84+
}
85+
// Add the value to the set
86+
labelValueSet[l.Name][l.Value] = struct{}{}
87+
}
88+
}
89+
}
90+
91+
var stats []LabelStats
92+
for label, valueSet := range labelValueSet {
93+
stats = append(stats, LabelStats{
94+
Name: label,
95+
DistinctValues: uint(len(valueSet)), // Count unique values
96+
})
97+
}
98+
return stats
99+
}
100+
101+
type LabelStats struct {
102+
Name string
103+
DistinctValues uint
104+
}
105+
106+
func (l LabelStats) String() string {
107+
return fmt.Sprintf("%s(%d)", l.Name, l.DistinctValues)
108+
}
109+
110+
type LabelStatsSlice []LabelStats
111+
112+
func (l LabelStatsSlice) String() string {
113+
var strBuf strings.Builder
114+
for i, ls := range l {
115+
if i > 0 {
116+
strBuf.WriteString("|")
117+
}
118+
strBuf.WriteString(ls.String())
119+
}
120+
return strBuf.String()
121+
}
122+
71123
type SeriesMap map[string]SeriesSet
72124

73125
type Result struct {
@@ -94,11 +146,13 @@ func (s SeriesMap) AsRows() []SeriesInfo {
94146
if createdTs > 0 {
95147
createdTsStr = time.UnixMilli(createdTs).String()
96148
}
149+
lblStats := s.LabelStats()
150+
slices.SortFunc(lblStats, func(i, j LabelStats) int { return (int(i.DistinctValues) - int(j.DistinctValues)) * -1 })
97151
rows = append(rows, SeriesInfo{
98152
Name: name,
99153
Cardinality: s.Cardinality(),
100154
Type: s.MetricTypeString(),
101-
Labels: s.LabelNames(),
155+
Labels: lblStats.String(),
102156
CreatedTS: createdTsStr,
103157
})
104158
}

pkg/scrape/series_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package scrape_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/prometheus/prometheus/model/labels"
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/pedro-stanaka/prom-scrape-analyzer/pkg/scrape"
10+
)
11+
12+
func TestSeriesSet_Cardinality(t *testing.T) {
13+
seriesSet := scrape.SeriesSet{
14+
1: {Name: "series1"},
15+
2: {Name: "series2"},
16+
}
17+
18+
expected := 2
19+
require.Equal(t, expected, seriesSet.Cardinality(), "Cardinality() should return the correct number of series")
20+
}
21+
22+
func TestSeriesSet_MetricTypeString(t *testing.T) {
23+
seriesSet := scrape.SeriesSet{
24+
1: {Name: "series1", Type: "gauge"},
25+
2: {Name: "series2", Type: "counter"},
26+
}
27+
28+
expected := "gauge|counter"
29+
require.Equal(t, expected, seriesSet.MetricTypeString(), "MetricTypeString() should return the correct metric types")
30+
}
31+
32+
func TestSeriesSet_CreatedTS(t *testing.T) {
33+
seriesSet := scrape.SeriesSet{
34+
1: {Name: "series1", CreatedTimestamp: 1620000000},
35+
2: {Name: "series2", CreatedTimestamp: 1620000001},
36+
}
37+
38+
expected := int64(1620000000)
39+
require.Equal(t, expected, seriesSet.CreatedTS(), "CreatedTS() should return the correct created timestamp")
40+
}
41+
42+
func TestSeriesSet_LabelNames(t *testing.T) {
43+
seriesSet := scrape.SeriesSet{
44+
1: {Name: "series1", Labels: labels.Labels{{Name: "label1"}, {Name: "label2"}}},
45+
2: {Name: "series2", Labels: labels.Labels{{Name: "label2"}, {Name: "label3"}}},
46+
}
47+
48+
expected := "label1|label2|label3"
49+
require.Equal(t, expected, seriesSet.LabelNames(), "LabelNames() should return the correct label names")
50+
}
51+
52+
func TestSeriesSet_LabelStats(t *testing.T) {
53+
seriesSet := scrape.SeriesSet{
54+
1: {Name: "series1", Labels: labels.Labels{{Name: "label1", Value: "foo"}, {Name: "label2", Value: "bar"}}},
55+
2: {Name: "series2", Labels: labels.Labels{{Name: "label2", Value: "baz"}, {Name: "label3", Value: "qux"}}},
56+
3: {Name: "series3", Labels: labels.Labels{{Name: "label2", Value: "baz"}, {Name: "label3", Value: "qua"}}},
57+
}
58+
59+
expected := scrape.LabelStatsSlice{
60+
{Name: "label1", DistinctValues: 1},
61+
{Name: "label2", DistinctValues: 2},
62+
{Name: "label3", DistinctValues: 2},
63+
}
64+
got := seriesSet.LabelStats()
65+
66+
require.Len(t, got, len(expected), "LabelStats() should return the correct number of label stats")
67+
require.EqualValues(t, expected, got, "LabelStats() should return the correct label stats")
68+
}

0 commit comments

Comments
 (0)