Repository navigation
fix(metrics): chart histogram metrics from their bucket distribution #101244
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
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 |
|---|---|---|
|
|
@@ -62,8 +62,27 @@ | |
| // A derived type ((typeof METRIC_AGGREGATIONS)[number]) would keep these in sync, but | ||
| // kea-typegen inlines derived unions into every consumer's generated block — keep the | ||
| // named alias so those blocks stay stable. | ||
| export type MetricAggregation = 'sum' | 'avg' | 'count' | 'min' | 'max' | 'p95' | 'rate' | 'increase' | ||
| export const METRIC_AGGREGATIONS: MetricAggregation[] = ['sum', 'avg', 'count', 'min', 'max', 'p95', 'rate', 'increase'] | ||
| export type MetricAggregation = | ||
| | 'sum' | ||
| | 'avg' | ||
| | 'count' | ||
| | 'min' | ||
| | 'max' | ||
| | 'p95' | ||
| | 'rate' | ||
| | 'increase' | ||
| | 'histogram_quantile' | ||
| export const METRIC_AGGREGATIONS: MetricAggregation[] = [ | ||
| 'sum', | ||
| 'avg', | ||
| 'count', | ||
| 'min', | ||
| 'max', | ||
| 'p95', | ||
| 'rate', | ||
| 'increase', | ||
| 'histogram_quantile', | ||
| ] | ||
|
|
||
| /** Narrows an untrusted value (a URL param, a saved link) to an aggregation the backend accepts. */ | ||
| export const isMetricAggregation = (value: unknown): value is MetricAggregation => | ||
|
|
@@ -157,13 +176,16 @@ | |
| // Cumulative counters (OTel type 'sum') summed raw give meaningless ever-growing | ||
| // totals — 'increase' is the honest default and is temporality-aware server-side | ||
| // (delta samples are summed as-is), so it's correct for delta producers too. | ||
| // Histograms keep their distribution in histogram_bounds/histogram_counts; the | ||
| // scalar value column holds the cumulative observation sum, so only | ||
| // 'histogram_quantile' charts the actual distribution (e.g. request latency). | ||
| export const RECOMMENDED_AGGREGATION_BY_TYPE: Record<string, MetricAggregation> = { | ||
| gauge: 'avg', | ||
| sum: 'increase', | ||
| counter: 'increase', | ||
| histogram: 'p95', | ||
| histogram: 'histogram_quantile', | ||
| summary: 'p95', | ||
| exponential_histogram: 'p95', | ||
| exponential_histogram: 'histogram_quantile', | ||
|
Contributor
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.
Defaulting exponential histograms to Prompt To Fix With AIThis is a comment left during a code review.
Path: products/metrics/frontend/components/metricsViewerLogic.tsx
Line: 188
Comment:
**Negative buckets produce wrong quantiles**
Defaulting exponential histograms to `histogram_quantile` produces incorrect results for valid distributions containing negative buckets. Ingest emits zero first and then negative buckets without sorting, while the quantile implementation assumes ascending bounds and interpolates between adjacent entries. Normalize or sort exponential bounds with their counts before making this aggregation the default.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly. |
||
| } | ||
| export const DEFAULT_DATE_FROM = '-1h' | ||
| // Kept off the persisted node: a saved query with no `display` renders as a line chart anyway. | ||
|
|
@@ -196,7 +218,7 @@ | |
| // Convert one filter-bar chip into the backend's `{key, op, value}` matcher. Filters run with | ||
| // scope 'auto' (resource attributes first, datapoint attributes as fallback), so scope is omitted. | ||
| // Returns null for chips still being edited (no key/value) or unsupported operators. | ||
| const propertyFilterToMetricFilter = (filter: UniversalFilterValue): _MetricFilterApi | null => { | ||
| const key = 'key' in filter && filter.key ? String(filter.key) : '' | ||
| const operator = 'operator' in filter && filter.operator ? filter.operator : PropertyOperator.Exact | ||
| // A non-PropertyOperator value (e.g. an ActionFilter's fields) simply isn't in the map -> null. | ||
|
|
@@ -261,11 +283,19 @@ | |
| return metricType && known.includes(metricType) ? (metricType as OtelMetricTypeEnumApi) : null | ||
| } | ||
|
|
||
| /** The REST viewer's 'p95' shorthand maps to the schema node's quantile aggregation. */ | ||
| /** The REST viewer's 'p95' shorthand maps to the schema node's quantile aggregation; | ||
| * 'histogram_quantile' keeps its name and carries the viewer's fixed 0.95 quantile. */ | ||
| export const nodeAggregationFields = ( | ||
| aggregation: MetricAggregation | ||
| ): Pick<MetricsQueryClause, 'aggregation' | 'quantile'> => | ||
| aggregation === 'p95' ? { aggregation: 'quantile', quantile: 0.95 } : { aggregation } | ||
| ): Pick<MetricsQueryClause, 'aggregation' | 'quantile'> => { | ||
| if (aggregation === 'p95') { | ||
| return { aggregation: 'quantile', quantile: 0.95 } | ||
| } | ||
| if (aggregation === 'histogram_quantile') { | ||
| return { aggregation: 'histogram_quantile', quantile: 0.95 } | ||
| } | ||
| return { aggregation } | ||
| } | ||
|
|
||
| /** Inverse of `nodeAggregationFields`, for reading a saved node back in viewer vocabulary. */ | ||
| export const viewerAggregationFromNode = (aggregation: MetricsQueryClause['aggregation'] | undefined): string | null => | ||
|
|
@@ -277,6 +307,8 @@ | |
| name: clause.name, | ||
| metricName: clause.metricName.trim(), | ||
| aggregation: clause.aggregation, | ||
| // The backend requires a quantile for histogram_quantile; the viewer fixes it at 0.95. | ||
| ...(clause.aggregation === 'histogram_quantile' ? { quantile: 0.95 } : {}), | ||
| // Pins the OTel type so a name that exists as several types (e.g. a counter | ||
| // and a gauge) charts only the picked one instead of blending them. | ||
| ...(clause.selectedMetricType ? { metricType: clause.selectedMetricType } : {}), | ||
|
|
||
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.
Histogram p95is offered for every metric type, but the backend rejects it for summaries and cannot derive it from scalar gauge or sum rows. Selecting it for those metrics returns a 400 or an empty chart. Filter this option to histogram and exponential-histogram clauses, as the repository requires the UI to show only supported options.Rule Used: When implementing new features, ensure that the UI reflects only the supported options to avoid confusion for users, especially during beta testing. (source)
Learned From
PostHog/posthog#32677
PostHog/posthog#32595
Prompt To Fix With AI