Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,71 @@ Note: The example dashboard assumes you're fetching all pagespeed categories.

Prometheus exporter for google pagespeed metrics

## CrUX Metrics (Real User Monitoring)

The exporter provides Chrome User Experience Report (CrUX) metrics, which represent real-world user experience data collected from Chrome browsers. CrUX data is only available for URLs and origins with sufficient user traffic.

### Metric Prefixes

- `pagespeed_loading_experience_*` - URL-specific RUM data (when available for the specific page)
- `pagespeed_origin_loading_experience_*` - Origin-wide RUM data (aggregated across the entire domain)

### Available CrUX Metrics

The exporter provides three types of metrics for each Core Web Vital:

1. **P75 Percentile** - The 75th percentile value (75% of users experience better performance)
2. **Category Ratios** - Proportion of users experiencing fast/average/slow performance
3. **Thresholds** - Google's Core Web Vitals performance boundaries

#### Core Web Vitals Included

- **Largest Contentful Paint (LCP)** - Measures loading performance
- **Interaction to Next Paint (INP)** - Measures interactivity
- **Cumulative Layout Shift (CLS)** - Measures visual stability
- **First Contentful Paint (FCP)** - Measures perceived load speed
- **Experimental Time to First Byte (TTFB)** - Measures server responsiveness

### Example Metrics Output

For Largest Contentful Paint (LCP):

```prometheus
# P75 percentile (existing metric)
pagespeed_loading_experience_metrics_largest_contentful_paint_duration_seconds 1.278

# Category distribution ratios (proportion of users in each category)
pagespeed_loading_experience_metrics_largest_contentful_paint_category_ratio{category="fast"} 0.9389
pagespeed_loading_experience_metrics_largest_contentful_paint_category_ratio{category="average"} 0.0371
pagespeed_loading_experience_metrics_largest_contentful_paint_category_ratio{category="slow"} 0.0240

# Core Web Vitals thresholds
pagespeed_loading_experience_metrics_largest_contentful_paint_threshold_duration_seconds{threshold="good"} 2.5
pagespeed_loading_experience_metrics_largest_contentful_paint_threshold_duration_seconds{threshold="poor"} 4.0
```

The same pattern applies to INP, FCP, and TTFB. For CLS (which is unitless), the metrics omit the `_duration_seconds` suffix:

```prometheus
pagespeed_loading_experience_metrics_cumulative_layout_shift_score 0.0
pagespeed_loading_experience_metrics_cumulative_layout_shift_score_category_ratio{category="fast"} 0.9994
pagespeed_loading_experience_metrics_cumulative_layout_shift_score_threshold{threshold="good"} 0.1
```

### Performance Categories

- **Fast** - Meets Google's "good" threshold (provides a good user experience)
- **Average** - Between "good" and "poor" thresholds (needs improvement)
- **Slow** - Exceeds "poor" threshold (provides a poor user experience)

### Data Availability

CrUX data is based on real user measurements from Chrome browsers over the last 28 days. Metrics will only be available for:
- URLs with sufficient Chrome user traffic
- Origins (domains) with sufficient Chrome user traffic

If data is unavailable, the corresponding metrics will not be exported.


## Building And Running

Expand Down
65 changes: 65 additions & 0 deletions collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,75 @@ func collectLoadingExperience(prefix string, lexp *pagespeedonline.PagespeedApiL

for k, v := range lexp.Metrics {
name := strings.TrimSuffix(strings.ToLower(k), "_ms")

// Export P75 percentile (existing metric - unchanged)
ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(fqname(prefix, "metrics", name, "duration_seconds"), "Percentile metrics for "+strings.Replace(name, "_", " ", -1), nil, constLables),
prometheus.GaugeValue,
float64(v.Percentile)/1000)

// Export category distribution ratios (NEW)
if len(v.Distributions) >= 3 {
categories := []string{"fast", "average", "slow"}
for i, dist := range v.Distributions {
if i >= 3 {
break
}
ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(fqname(prefix, "metrics", name, "category_ratio"), "Proportion of users experiencing "+categories[i]+" performance", []string{"category"}, constLables),
prometheus.GaugeValue,
dist.Proportion,
categories[i])
}
}

// Export Core Web Vitals thresholds (NEW)
if len(v.Distributions) >= 2 {
// Determine if this is CLS (uses hundredths) or time-based (uses ms)
isCLS := strings.Contains(strings.ToLower(k), "cumulative_layout_shift")

// Extract good threshold (upper bound of FAST bucket)
if v.Distributions[0].Max > 0 {
goodThreshold := float64(v.Distributions[0].Max)
if isCLS {
goodThreshold = goodThreshold / 100.0 // Convert hundredths to decimal
} else {
goodThreshold = goodThreshold / 1000.0 // Convert ms to seconds
}

metricSuffix := "duration_seconds"
if isCLS {
metricSuffix = "" // CLS is unitless
}

ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(fqname(prefix, "metrics", name, "threshold", metricSuffix), "Core Web Vitals threshold for "+strings.Replace(name, "_", " ", -1), []string{"threshold"}, constLables),
prometheus.GaugeValue,
goodThreshold,
"good")
}

// Extract poor threshold (upper bound of AVERAGE bucket)
if v.Distributions[1].Max > 0 {
poorThreshold := float64(v.Distributions[1].Max)
if isCLS {
poorThreshold = poorThreshold / 100.0
} else {
poorThreshold = poorThreshold / 1000.0
}

metricSuffix := "duration_seconds"
if isCLS {
metricSuffix = ""
}

ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(fqname(prefix, "metrics", name, "threshold", metricSuffix), "Core Web Vitals threshold for "+strings.Replace(name, "_", " ", -1), []string{"threshold"}, constLables),
prometheus.GaugeValue,
poorThreshold,
"poor")
}
}
}

}
Expand Down
Loading