Skip to content

Commit 80149ab

Browse files
committed
clearer & simpler API. move out single_label_provider as a separate case
Signed-off-by: Eugene <[email protected]>
1 parent aa7e203 commit 80149ab

File tree

2 files changed

+193
-123
lines changed

2 files changed

+193
-123
lines changed

prometheus/promsafe/safe.go

+160-96
Original file line numberDiff line numberDiff line change
@@ -53,140 +53,78 @@ func SetPromsafeTag(tag string) {
5353
promsafeTag = tag
5454
}
5555

56-
// labelProviderMarker is a marker interface for enforcing type-safety.
57-
// With its help we can force our label-related functions to only accept SingleLabelProvider or StructLabelProvider.
58-
type labelProviderMarker interface {
59-
marker()
60-
}
61-
62-
// SingleLabelProvider is a type used for declaring a single label.
63-
// When used as labelProviderMarker it provides just a label name.
64-
// It's meant to be used with single-label metrics only!
65-
// Use StructLabelProvider for multi-label metrics.
66-
type SingleLabelProvider string
67-
68-
var _ labelProviderMarker = SingleLabelProvider("")
69-
70-
func (s SingleLabelProvider) marker() {
71-
panic("marker interface method should never be called")
56+
// labelsProviderMarker is a marker interface for enforcing type-safety of StructLabelProvider.
57+
type labelsProviderMarker interface {
58+
labelsProviderMarker()
7259
}
7360

7461
// StructLabelProvider should be embedded in any struct that serves as a label provider.
7562
type StructLabelProvider struct{}
7663

77-
var _ labelProviderMarker = (*StructLabelProvider)(nil)
78-
79-
func (s StructLabelProvider) marker() {
80-
panic("marker interface method should never be called")
81-
}
64+
var _ labelsProviderMarker = (*StructLabelProvider)(nil)
8265

83-
// handler is a helper struct that helps us to handle type-safe labels
84-
// It holds a label name in case if it's the only label (when SingleLabelProvider is used).
85-
type handler[T labelProviderMarker] struct {
86-
theOnlyLabelName string
66+
func (s StructLabelProvider) labelsProviderMarker() {
67+
panic("labelsProviderMarker interface method should never be called")
8768
}
8869

89-
func newHandler[T labelProviderMarker](labelProvider T) handler[T] {
90-
var h handler[T]
91-
if s, ok := any(labelProvider).(SingleLabelProvider); ok {
92-
h.theOnlyLabelName = string(s)
93-
}
94-
return h
95-
}
70+
// newEmptyLabels creates a new empty labels instance of type T
71+
// It's a bit tricky as we want to support both structs and pointers to structs
72+
// e.g. &MyLabels{StructLabelProvider} or MyLabels{StructLabelProvider}
73+
func newEmptyLabels[T labelsProviderMarker]() T {
74+
var emptyLabels T
9675

97-
// extractLabelsWithValues extracts labels names+values from a given labelProviderMarker (SingleLabelProvider or StructLabelProvider)
98-
func (h handler[T]) extractLabels(labelProvider T) []string {
99-
if any(labelProvider) == nil {
100-
return nil
101-
}
102-
if s, ok := any(labelProvider).(SingleLabelProvider); ok {
103-
return []string{string(s)}
76+
// Let's Support both Structs or Pointer to Structs given as T
77+
val := reflect.ValueOf(&emptyLabels).Elem()
78+
if val.Kind() == reflect.Ptr {
79+
val.Set(reflect.New(val.Type().Elem()))
10480
}
10581

106-
// Here, then, it can be only a struct, that is a parent of StructLabelProvider
107-
labels := extractLabelFromStruct(labelProvider)
108-
labelNames := make([]string, 0, len(labels))
109-
for k := range labels {
110-
labelNames = append(labelNames, k)
111-
}
112-
return labelNames
113-
}
114-
115-
// extractLabelsWithValues extracts labels names+values from a given labelProviderMarker (SingleLabelProvider or StructLabelProvider)
116-
func (h handler[T]) extractLabelsWithValues(labelProvider T) prometheus.Labels {
117-
if any(labelProvider) == nil {
118-
return nil
119-
}
120-
121-
// TODO: let's handle defaults as well, why not?
122-
123-
if s, ok := any(labelProvider).(SingleLabelProvider); ok {
124-
return prometheus.Labels{h.theOnlyLabelName: string(s)}
125-
}
126-
127-
// Here, then, it can be only a struct, that is a parent of StructLabelProvider
128-
return extractLabelFromStruct(labelProvider)
129-
}
130-
131-
// extractLabelValues extracts label string values from a given labelProviderMarker (SingleLabelProvider or StructLabelProvider)
132-
func (h handler[T]) extractLabelValues(labelProvider T) []string {
133-
m := h.extractLabelsWithValues(labelProvider)
134-
135-
labelValues := make([]string, 0, len(m))
136-
for _, v := range m {
137-
labelValues = append(labelValues, v)
138-
}
139-
return labelValues
82+
return emptyLabels
14083
}
14184

14285
// NewCounterVecT creates a new CounterVecT with type-safe labels.
143-
func NewCounterVecT[T labelProviderMarker](opts prometheus.CounterOpts, labels T) *CounterVecT[T] {
144-
h := newHandler(labels)
86+
func NewCounterVecT[T labelsProviderMarker](opts prometheus.CounterOpts) *CounterVecT[T] {
87+
emptyLabels := newEmptyLabels[T]()
14588

14689
var inner *prometheus.CounterVec
147-
14890
if factory != nil {
149-
inner = factory.NewCounterVec(opts, h.extractLabels(labels))
91+
inner = factory.NewCounterVec(opts, extractLabelNames(emptyLabels))
15092
} else {
151-
inner = prometheus.NewCounterVec(opts, h.extractLabels(labels))
93+
inner = prometheus.NewCounterVec(opts, extractLabelNames(emptyLabels))
15294
}
15395

154-
return &CounterVecT[T]{
155-
handler: h,
156-
inner: inner,
157-
}
96+
return &CounterVecT[T]{inner: inner}
15897
}
15998

160-
// CounterVecT is a wrapper around prometheus.CounterVecT that allows type-safe labels.
161-
type CounterVecT[T labelProviderMarker] struct {
162-
handler[T]
99+
// CounterVecT is a wrapper around prometheus.CounterVec that allows type-safe labels.
100+
type CounterVecT[T labelsProviderMarker] struct {
163101
inner *prometheus.CounterVec
164102
}
165103

166104
// GetMetricWithLabelValues behaves like prometheus.CounterVec.GetMetricWithLabelValues but with type-safe labels.
167105
func (c *CounterVecT[T]) GetMetricWithLabelValues(labels T) (prometheus.Counter, error) {
168-
return c.inner.GetMetricWithLabelValues(c.handler.extractLabelValues(labels)...)
106+
return c.inner.GetMetricWithLabelValues(extractLabelValues(labels)...)
169107
}
170108

171109
// GetMetricWith behaves like prometheus.CounterVec.GetMetricWith but with type-safe labels.
172110
func (c *CounterVecT[T]) GetMetricWith(labels T) (prometheus.Counter, error) {
173-
return c.inner.GetMetricWith(c.handler.extractLabelsWithValues(labels))
111+
return c.inner.GetMetricWith(extractLabelsWithValues(labels))
174112
}
175113

176114
// WithLabelValues behaves like prometheus.CounterVec.WithLabelValues but with type-safe labels.
177115
func (c *CounterVecT[T]) WithLabelValues(labels T) prometheus.Counter {
178-
return c.inner.WithLabelValues(c.handler.extractLabelValues(labels)...)
116+
return c.inner.WithLabelValues(extractLabelValues(labels)...)
179117
}
180118

181119
// With behaves like prometheus.CounterVec.With but with type-safe labels.
182120
func (c *CounterVecT[T]) With(labels T) prometheus.Counter {
183-
return c.inner.With(c.handler.extractLabelsWithValues(labels))
121+
return c.inner.With(extractLabelsWithValues(labels))
184122
}
185123

186124
// CurryWith behaves like prometheus.CounterVec.CurryWith but with type-safe labels.
187125
// It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
188126
func (c *CounterVecT[T]) CurryWith(labels T) (*CounterVecT[T], error) {
189-
curriedInner, err := c.inner.CurryWith(c.handler.extractLabelsWithValues(labels))
127+
curriedInner, err := c.inner.CurryWith(extractLabelsWithValues(labels))
190128
if err != nil {
191129
return nil, err
192130
}
@@ -197,7 +135,7 @@ func (c *CounterVecT[T]) CurryWith(labels T) (*CounterVecT[T], error) {
197135
// MustCurryWith behaves like prometheus.CounterVec.MustCurryWith but with type-safe labels.
198136
// It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
199137
func (c *CounterVecT[T]) MustCurryWith(labels T) *CounterVecT[T] {
200-
c.inner = c.inner.MustCurryWith(c.handler.extractLabelsWithValues(labels))
138+
c.inner = c.inner.MustCurryWith(extractLabelsWithValues(labels))
201139
return c
202140
}
203141

@@ -221,24 +159,110 @@ func NewCounterFuncT(opts prometheus.CounterOpts, function func() float64) prome
221159
return prometheus.NewCounterFunc(opts, function)
222160
}
223161

162+
//
163+
// Shorthand for Metrics with a single label
164+
//
165+
166+
// singleLabelProviderMarker is a marker interface for enforcing type-safety of SingleLabelProvider.
167+
type singleLabelProviderMarker interface {
168+
singleLabelProviderMarker()
169+
}
170+
171+
// SingleLabelProvider is a type used for declaring a single label only.
172+
// When declaring a metric it's values used as a label name
173+
// When calling With() it's values used as a label value
174+
type SingleLabelProvider string
175+
176+
var _ singleLabelProviderMarker = SingleLabelProvider("")
177+
178+
func (s SingleLabelProvider) singleLabelProviderMarker() {
179+
panic("singleLabelProviderMarker interface method should never be called")
180+
}
181+
182+
// NewCounterVecT1 creates a new CounterVecT with the only single label
183+
func NewCounterVecT1(opts prometheus.CounterOpts, singleLabelProvider singleLabelProviderMarker) *CounterVecT1 {
184+
// labelName is the string itself
185+
// and singleLabelProviderMarker here can ONLY be SingleLabelProvider
186+
labelName := string(singleLabelProvider.(SingleLabelProvider))
187+
188+
var inner *prometheus.CounterVec
189+
if factory != nil {
190+
inner = factory.NewCounterVec(opts, []string{labelName})
191+
} else {
192+
inner = prometheus.NewCounterVec(opts, []string{labelName})
193+
}
194+
195+
return &CounterVecT1{inner: inner, labelName: labelName}
196+
}
197+
198+
// CounterVecT1 is a wrapper around prometheus.CounterVec that allows a single type-safe label.
199+
type CounterVecT1 struct {
200+
labelName string
201+
inner *prometheus.CounterVec
202+
}
203+
204+
// GetMetricWithLabelValues behaves like prometheus.CounterVec.GetMetricWithLabelValues but with type-safe labels.
205+
func (c *CounterVecT1) GetMetricWithLabelValues(labelValue string) (prometheus.Counter, error) {
206+
return c.inner.GetMetricWithLabelValues(labelValue)
207+
}
208+
209+
// GetMetricWith behaves like prometheus.CounterVec.GetMetricWith but with type-safe labels.
210+
func (c *CounterVecT1) GetMetricWith(labelValue string) (prometheus.Counter, error) {
211+
return c.inner.GetMetricWith(prometheus.Labels{c.labelName: labelValue})
212+
}
213+
214+
// WithLabelValues behaves like prometheus.CounterVec.WithLabelValues but with type-safe labels.
215+
func (c *CounterVecT1) WithLabelValues(labelValue string) prometheus.Counter {
216+
return c.inner.WithLabelValues(labelValue)
217+
}
218+
219+
// With behaves like prometheus.CounterVec.With but with type-safe labels.
220+
func (c *CounterVecT1) With(labelValue string) prometheus.Counter {
221+
return c.inner.With(prometheus.Labels{c.labelName: labelValue})
222+
}
223+
224+
// CurryWith behaves like prometheus.CounterVec.CurryWith but with type-safe labels.
225+
// It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
226+
func (c *CounterVecT1) CurryWith(labelValue string) (*CounterVecT1, error) {
227+
curriedInner, err := c.inner.CurryWith(prometheus.Labels{c.labelName: labelValue})
228+
if err != nil {
229+
return nil, err
230+
}
231+
c.inner = curriedInner
232+
return c, nil
233+
}
234+
235+
// MustCurryWith behaves like prometheus.CounterVec.MustCurryWith but with type-safe labels.
236+
// It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
237+
func (c *CounterVecT1) MustCurryWith(labelValue string) *CounterVecT1 {
238+
c.inner = c.inner.MustCurryWith(prometheus.Labels{c.labelName: labelValue})
239+
return c
240+
}
241+
242+
// Unsafe returns the underlying prometheus.CounterVec
243+
// it's used to call any other method of prometheus.CounterVec that doesn't require type-safe labels
244+
func (c *CounterVecT1) Unsafe() *prometheus.CounterVec {
245+
return c.inner
246+
}
247+
224248
//
225249
// Promauto compatibility
226250
//
227251

228252
// Factory is a promauto-like factory that allows type-safe labels.
229253
// We have to duplicate promauto.Factory logic here, because promauto.Factory's registry is private.
230-
type Factory[T labelProviderMarker] struct {
254+
type Factory[T labelsProviderMarker] struct {
231255
r prometheus.Registerer
232256
}
233257

234258
// WithAuto is a helper function that allows to use promauto.With with promsafe.With
235-
func WithAuto(r prometheus.Registerer) Factory[labelProviderMarker] {
236-
return Factory[labelProviderMarker]{r: r}
259+
func WithAuto[T labelsProviderMarker](r prometheus.Registerer) Factory[T] {
260+
return Factory[T]{r: r}
237261
}
238262

239263
// NewCounterVecT works like promauto.NewCounterVec but with type-safe labels
240-
func (f Factory[T]) NewCounterVecT(opts prometheus.CounterOpts, labels T) *CounterVecT[T] {
241-
c := NewCounterVecT(opts, labels)
264+
func (f Factory[T]) NewCounterVecT(opts prometheus.CounterOpts) *CounterVecT[T] {
265+
c := NewCounterVecT[T](opts)
242266
if f.r != nil {
243267
f.r.MustRegister(c.inner)
244268
}
@@ -257,10 +281,50 @@ func (f Factory[T]) NewCounterFuncT(opts prometheus.CounterOpts, function func()
257281
return promauto.With(f.r).NewCounterFunc(opts, function)
258282
}
259283

284+
// TODO: we can't use Factory with NewCounterT1. If we need, then we need a new type-less Factory
285+
260286
//
261287
// Helpers
262288
//
263289

290+
// extractLabelsWithValues extracts labels names+values from a given labelsProviderMarker (parent instance of a StructLabelProvider)
291+
func extractLabelsWithValues(labelProvider labelsProviderMarker) prometheus.Labels {
292+
if any(labelProvider) == nil {
293+
return nil
294+
}
295+
296+
// TODO: let's handle defaults as well, why not?
297+
298+
// Here, then, it can be only a struct, that is a parent of StructLabelProvider
299+
return extractLabelFromStruct(labelProvider)
300+
}
301+
302+
// extractLabelValues extracts label string values from a given labelsProviderMarker (parent instance of aStructLabelProvider)
303+
func extractLabelValues(labelProvider labelsProviderMarker) []string {
304+
m := extractLabelsWithValues(labelProvider)
305+
306+
labelValues := make([]string, 0, len(m))
307+
for _, v := range m {
308+
labelValues = append(labelValues, v)
309+
}
310+
return labelValues
311+
}
312+
313+
// extractLabelNames extracts labels names from a given labelsProviderMarker (parent instance of aStructLabelProvider)
314+
func extractLabelNames(labelProvider labelsProviderMarker) []string {
315+
if any(labelProvider) == nil {
316+
return nil
317+
}
318+
319+
// Here, then, it can be only a struct, that is a parent of StructLabelProvider
320+
labels := extractLabelFromStruct(labelProvider)
321+
labelNames := make([]string, 0, len(labels))
322+
for k := range labels {
323+
labelNames = append(labelNames, k)
324+
}
325+
return labelNames
326+
}
327+
264328
// extractLabelFromStruct extracts labels names+values from a given StructLabelProvider
265329
func extractLabelFromStruct(structWithLabels any) prometheus.Labels {
266330
labels := prometheus.Labels{}

0 commit comments

Comments
 (0)