-
Notifications
You must be signed in to change notification settings - Fork 1.3k
prometheus: expose descriptor metadata via Desc.Info #2094
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,6 +49,11 @@ type Desc struct { | |
| help string | ||
| // unit provides the unit of this metric. | ||
| unit string | ||
| // metricType is the type of the metric this Desc belongs to. It is | ||
| // MetricType_UNTYPED unless a typed constructor of this package set it, | ||
| // as a Desc created via NewDesc does not carry a type: the type of a | ||
| // const metric is provided per sample, e.g. via MustNewConstMetric. | ||
| metricType dto.MetricType | ||
| // constLabelPairs contains precalculated DTO label pairs based on | ||
| // the constant labels. | ||
| constLabelPairs []*dto.LabelPair | ||
|
|
@@ -78,6 +83,15 @@ func WithUnit(unit string) DescOpt { | |
| } | ||
| } | ||
|
|
||
| // withType sets the metric type for a Desc. It is unexported on purpose: only | ||
| // the typed constructors of this package know the type at Desc construction | ||
| // time. | ||
| func withType(t dto.MetricType) DescOpt { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One way to solve this is to add withType to NewConstMetric based on value type. It would break immutability though, but would work for majority of users 🤔 |
||
| return func(d *Desc) { | ||
| d.metricType = t | ||
| } | ||
| } | ||
|
|
||
| // NewDesc allocates and initializes a new Desc. Errors are recorded in the Desc | ||
| // and will be reported on registration time. variableLabels and constLabels can | ||
| // be nil if no such labels should be set. fqName must not be empty. | ||
|
|
@@ -106,6 +120,7 @@ func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, const | |
| fqName: fqName, | ||
| help: help, | ||
| variableLabels: variableLabels.compile(), | ||
| metricType: dto.MetricType_UNTYPED, | ||
| } | ||
|
|
||
| for _, opt := range opts { | ||
|
|
@@ -195,8 +210,59 @@ func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, const | |
| // a Collector to signal inability to describe itself. | ||
| func NewInvalidDesc(err error) *Desc { | ||
| return &Desc{ | ||
| err: err, | ||
| err: err, | ||
| metricType: dto.MetricType_UNTYPED, | ||
| } | ||
| } | ||
|
|
||
| // DescInfo is a read-only view of the meta-data a Desc declares. It is meant | ||
| // for introspection, e.g. to check that a Collector describes the metrics a | ||
| // schema says it should. Info allocates on every call and is not meant for | ||
| // hot paths. | ||
| type DescInfo struct { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs more thoughts. We never exposed those info publicly. Apparently the reason was the need for changes in this structure. I wonder if this is still true 🤔 Some fact digged by AI: Key Reasons Why
|
||
| // FQName is the fully-qualified name of the metric. | ||
| FQName string | ||
| // Help is the help string of the metric. | ||
| Help string | ||
| // Unit is the unit of the metric, empty if none was set. | ||
| Unit string | ||
| // Type is the type of the metric. It is MetricType_UNTYPED for a Desc | ||
| // created via NewDesc, which does not carry a type. | ||
| Type dto.MetricType | ||
| // VariableLabels are the names of the labels whose values vary per | ||
| // metric, in the order they were provided at construction. It is nil if | ||
| // the metric has no variable labels. | ||
| VariableLabels []string | ||
| // ConstLabels are the labels fixed at construction time. It is nil if | ||
| // the metric has no const labels. | ||
| ConstLabels Labels | ||
| // Err is the error that occurred during construction, if any. The | ||
| // remaining fields may be zero if Err is non-nil. | ||
| Err error | ||
| } | ||
|
|
||
| // Info returns a read-only view of the meta-data of the Desc. In contrast to | ||
| // String, the returned value is structured and therefore suitable for | ||
| // programmatic inspection. | ||
| func (d *Desc) Info() DescInfo { | ||
| info := DescInfo{ | ||
| FQName: d.fqName, | ||
| Help: d.help, | ||
| Unit: d.unit, | ||
| Type: d.metricType, | ||
| Err: d.err, | ||
| } | ||
| if d.variableLabels != nil && len(d.variableLabels.names) > 0 { | ||
| info.VariableLabels = make([]string, len(d.variableLabels.names)) | ||
| copy(info.VariableLabels, d.variableLabels.names) | ||
| } | ||
| if len(d.constLabelPairs) > 0 { | ||
| info.ConstLabels = make(Labels, len(d.constLabelPairs)) | ||
| for _, lp := range d.constLabelPairs { | ||
| info.ConstLabels[lp.GetName()] = lp.GetValue() | ||
| } | ||
| } | ||
| return info | ||
| } | ||
|
|
||
| // Err returns an error that occurred during construction, if any. | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -595,6 +595,26 @@ func (r *Registry) Describe(ch chan<- *Desc) { | |||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // DescribeAll returns the Desc of every checked Collector registered with r. | ||||||||||||
| // It is a convenience wrapper around Describe for callers that want a slice | ||||||||||||
| // rather than draining a channel, e.g. tests that assert on the metrics an | ||||||||||||
| // application declares. Unchecked Collectors are not included, as they do not | ||||||||||||
| // report any Desc. | ||||||||||||
| func (r *Registry) DescribeAll() []*Desc { | ||||||||||||
| ch := make(chan *Desc) | ||||||||||||
| go func() { | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider deferring
Suggested change
|
||||||||||||
| r.Describe(ch) | ||||||||||||
| close(ch) | ||||||||||||
| }() | ||||||||||||
|
|
||||||||||||
| var descs []*Desc | ||||||||||||
| for d := range ch { | ||||||||||||
| descs = append(descs, d) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| return descs | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // Helper wrapper around Collector.Collect. | ||||||||||||
| // It tries to collect from the channel, recovers on panic and | ||||||||||||
| // if it has recovered from a panic, then it sends an InvalidMetric into | ||||||||||||
|
|
||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -45,6 +45,7 @@ import ( | |||||||||||
| "fmt" | ||||||||||||
| "io" | ||||||||||||
| "net/http" | ||||||||||||
| "slices" | ||||||||||||
|
|
||||||||||||
| "github.com/kylelemons/godebug/diff" | ||||||||||||
| dto "github.com/prometheus/client_model/go" | ||||||||||||
|
|
@@ -120,6 +121,37 @@ func ToFloat64(c prometheus.Collector) float64 { | |||||||||||
| panic(fmt.Errorf("collected a non-gauge/counter/untyped metric: %s", pb)) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // CollectAndDescribe returns the DescInfo of every Desc the provided Collector | ||||||||||||
| // describes, restricted to the provided metricNames if any are given. | ||||||||||||
| // | ||||||||||||
| // In contrast to the GatherAnd… functions, it also reports metrics that have | ||||||||||||
| // not produced a sample yet and would therefore be absent from an exposition. | ||||||||||||
| // That makes it suitable for asserting that a Collector still declares the | ||||||||||||
| // metrics it is expected to, e.g. to catch a renamed metric or a changed label | ||||||||||||
| // set before it reaches users. | ||||||||||||
| // | ||||||||||||
| // There is no GatherAndDescribe counterpart because the prometheus.Gatherer | ||||||||||||
| // interface does not expose descriptors. Use prometheus.Registry.DescribeAll | ||||||||||||
| // to inspect a whole Registry. | ||||||||||||
| func CollectAndDescribe(c prometheus.Collector, metricNames ...string) []prometheus.DescInfo { | ||||||||||||
| ch := make(chan *prometheus.Desc) | ||||||||||||
| go func() { | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider deferring
Suggested change
|
||||||||||||
| c.Describe(ch) | ||||||||||||
| close(ch) | ||||||||||||
| }() | ||||||||||||
|
|
||||||||||||
| var infos []prometheus.DescInfo | ||||||||||||
| for desc := range ch { | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If a collector emits a nil |
||||||||||||
| info := desc.Info() | ||||||||||||
| if len(metricNames) > 0 && !slices.Contains(metricNames, info.FQName) { | ||||||||||||
| continue | ||||||||||||
| } | ||||||||||||
| infos = append(infos, info) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| return infos | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // CollectAndCount registers the provided Collector with a newly created | ||||||||||||
| // pedantic Registry. It then calls GatherAndCount with that Registry and with | ||||||||||||
| // the provided metricNames. In the unlikely case that the registration or the | ||||||||||||
|
|
||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interestingly this was always the plan in 2.0 but adding it without other changes is a bit of a hack.
It's also has a bit of performance penalty, but probably manageable if there is a good motivation