You've already forked wp-prometheus
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da6d5081f7 | |||
| 3eb66b0ebe | |||
| bc108f6bd5 | |||
| 6256ba777c | |||
| f1748727ce | |||
| 71f87b320a | |||
| 4a676aa195 | |||
| 898d711b2f | |||
| 9b100c5f45 | |||
| 8c24dac85c |
@@ -161,6 +161,20 @@ jobs:
|
||||
# Read release notes
|
||||
BODY=$(cat release_notes.txt)
|
||||
|
||||
# Check if release already exists for this tag and delete it
|
||||
EXISTING_RELEASE=$(curl -s \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG_NAME}")
|
||||
|
||||
EXISTING_ID=$(echo "$EXISTING_RELEASE" | jq -r '.id // empty')
|
||||
|
||||
if [ -n "$EXISTING_ID" ] && [ "$EXISTING_ID" != "null" ]; then
|
||||
echo "Deleting existing release ID: $EXISTING_ID"
|
||||
curl -s -X DELETE \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/${EXISTING_ID}"
|
||||
fi
|
||||
|
||||
# Create release via Gitea API
|
||||
RELEASE_RESPONSE=$(curl -s -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
|
||||
79
CHANGELOG.md
79
CHANGELOG.md
@@ -5,6 +5,85 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.2.2] - 2026-02-02
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `wc_orders_count()` call missing required status parameter in WooCommerce orders metrics
|
||||
|
||||
## [0.2.1] - 2026-02-02
|
||||
|
||||
### Added
|
||||
|
||||
- Localhost license bypass for development environments (localhost, 127.0.0.1, ::1, \*.localhost, \*.local)
|
||||
- Automatic rewrite rules flush when license status changes
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed 404 error on `/metrics` endpoint when license becomes valid after plugin activation
|
||||
|
||||
## [0.2.0] - 2026-02-02
|
||||
|
||||
### Added
|
||||
|
||||
- WooCommerce integration metrics (when WooCommerce is active):
|
||||
- `wordpress_woocommerce_products_total` - Products by status and type
|
||||
- `wordpress_woocommerce_orders_total` - Orders by status
|
||||
- `wordpress_woocommerce_revenue_total` - Revenue (all time, today, month)
|
||||
- `wordpress_woocommerce_customers_total` - Customers (registered, guest)
|
||||
- Cron job metrics:
|
||||
- `wordpress_cron_events_total` - Scheduled cron events by hook
|
||||
- `wordpress_cron_overdue_total` - Number of overdue cron events
|
||||
- `wordpress_cron_next_run_timestamp` - Unix timestamp of next scheduled cron
|
||||
- Transient cache metrics:
|
||||
- `wordpress_transients_total` - Transients by type (total, with_expiration, persistent, expired)
|
||||
- WooCommerce metrics section in settings (only visible when WooCommerce is active)
|
||||
- Support for WooCommerce HPOS (High-Performance Order Storage)
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated Help tab with new metrics reference
|
||||
|
||||
## [0.1.1] - 2026-02-02
|
||||
|
||||
### Changed
|
||||
|
||||
- Reorganized settings page with tabbed interface (License, Metrics, Help tabs)
|
||||
- Moved Prometheus configuration help to dedicated Help tab
|
||||
- Separated static and runtime metrics in settings with descriptions
|
||||
- Added admin CSS for improved tab styling
|
||||
|
||||
### Added
|
||||
|
||||
- New Help tab with endpoint information, curl examples, and metrics reference table
|
||||
- Custom code examples section in Help tab
|
||||
|
||||
## [0.1.0] - 2026-02-02
|
||||
|
||||
### Added
|
||||
|
||||
- HTTP request metrics:
|
||||
- `wordpress_http_requests_total` - Counter of HTTP requests by method, status code, and endpoint
|
||||
- `wordpress_http_request_duration_seconds` - Histogram of request durations
|
||||
- Database query metrics:
|
||||
- `wordpress_db_queries_total` - Counter of database queries by endpoint
|
||||
- `wordpress_db_query_duration_seconds` - Histogram of query durations (requires SAVEQUERIES)
|
||||
- RuntimeCollector class for collecting metrics during WordPress request lifecycle
|
||||
- New settings options for enabling/disabling runtime metrics
|
||||
- Translation files (.pot, .po, .mo) for German (Switzerland)
|
||||
|
||||
### Changed
|
||||
|
||||
- Metrics are now categorized into static metrics (users, posts, etc.) and runtime metrics (HTTP, database)
|
||||
- Runtime metrics only collected when explicitly enabled and license is valid
|
||||
|
||||
## [0.0.2] - 2026-02-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed composer.json dependency version constraint for wc-licensed-product-client (^0.2.2 instead of dev-main)
|
||||
- Changed minimum-stability back to stable
|
||||
|
||||
## [0.0.1] - 2026-02-01
|
||||
|
||||
### Added
|
||||
|
||||
82
CLAUDE.md
82
CLAUDE.md
@@ -14,6 +14,10 @@ This plugin provides a Prometheus `/metrics` endpoint and an extensible way to a
|
||||
|
||||
- Prometheus compatible authenticated `/metrics` endpoint
|
||||
- Optional default metrics (users, posts, comments, plugins)
|
||||
- Runtime metrics (HTTP requests, request duration, database queries)
|
||||
- Cron job metrics (scheduled events, overdue, next run)
|
||||
- Transient cache metrics (total, expiring, expired)
|
||||
- WooCommerce integration (products, orders, revenue, customers)
|
||||
- Dedicated plugin settings under 'Settings/Metrics' menu
|
||||
- Extensible by other plugins using `wp_prometheus_collect_metrics` action hook
|
||||
- License management integration
|
||||
@@ -26,11 +30,11 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
|
||||
|
||||
**Note for AI Assistants:** Clean this section after the specific features are done or new releases are made. Effective changes are tracked in `CHANGELOG.md`. Do not add completed versions here - document them in the Session History section at the end of this file.
|
||||
|
||||
### Version 0.1.0 (Planned)
|
||||
### Version 0.3.0 (Planned)
|
||||
|
||||
- Add request/response timing metrics
|
||||
- Add HTTP status code counters
|
||||
- Add database query metrics
|
||||
- Custom metric builder in admin
|
||||
- Metric export/import
|
||||
- Grafana dashboard templates
|
||||
|
||||
## Technical Stack
|
||||
|
||||
@@ -224,7 +228,8 @@ wp-prometheus/
|
||||
│ ├── License/
|
||||
│ │ └── Manager.php # License management
|
||||
│ ├── Metrics/
|
||||
│ │ └── Collector.php # Prometheus metrics collector
|
||||
│ │ ├── Collector.php # Prometheus metrics collector
|
||||
│ │ └── RuntimeCollector.php # Runtime metrics collector
|
||||
│ ├── Installer.php # Activation/Deactivation
|
||||
│ ├── Plugin.php # Main plugin class
|
||||
│ └── index.php
|
||||
@@ -280,6 +285,73 @@ add_action( 'wp_prometheus_collect_metrics', function( $collector ) {
|
||||
|
||||
## Session History
|
||||
|
||||
### 2026-02-02 - Extended Metrics (v0.2.0)
|
||||
|
||||
- Added WooCommerce integration metrics (only when WooCommerce is active):
|
||||
- `wordpress_woocommerce_products_total` - Products by status and type
|
||||
- `wordpress_woocommerce_orders_total` - Orders by status
|
||||
- `wordpress_woocommerce_revenue_total` - Revenue (all time, today, month)
|
||||
- `wordpress_woocommerce_customers_total` - Customers (registered, guest)
|
||||
- Added cron job metrics:
|
||||
- `wordpress_cron_events_total` - Scheduled cron events by hook (top 20)
|
||||
- `wordpress_cron_overdue_total` - Number of overdue cron events
|
||||
- `wordpress_cron_next_run_timestamp` - Unix timestamp of next scheduled cron
|
||||
- Added transient cache metrics:
|
||||
- `wordpress_transients_total` - Transients by type (total, with_expiration, persistent, expired)
|
||||
- Updated Settings page with new metric categories
|
||||
- Updated Help tab with new metrics reference
|
||||
- **Key Learning**: WooCommerce HPOS (High-Performance Order Storage) requires different queries
|
||||
- Check `OrderUtil::custom_orders_table_usage_is_enabled()` to determine storage type
|
||||
- HPOS uses `wc_orders` table instead of `posts` and `postmeta`
|
||||
- **Key Learning**: Cron event labeling requires cardinality control
|
||||
- Limit to top 20 hooks to prevent label explosion
|
||||
- Use `arsort()` to get most frequent hooks first
|
||||
|
||||
### 2026-02-02 - Runtime Metrics (v0.1.0)
|
||||
|
||||
- Implemented runtime metrics collection for HTTP requests and database queries
|
||||
- Created `RuntimeCollector` class that hooks into WordPress request lifecycle
|
||||
- Added new metrics:
|
||||
- `wordpress_http_requests_total` - Counter by method, status, endpoint
|
||||
- `wordpress_http_request_duration_seconds` - Histogram of request durations
|
||||
- `wordpress_db_queries_total` - Counter by endpoint
|
||||
- `wordpress_db_query_duration_seconds` - Histogram (requires SAVEQUERIES)
|
||||
- Updated `Collector` class to expose stored runtime metrics
|
||||
- Added new settings options in admin for enabling/disabling runtime metrics
|
||||
- Created translation files (.pot, .po, .mo) for internationalization
|
||||
- **Key Learning**: With InMemory Prometheus storage, counters/histograms reset per request
|
||||
- Solution: Store aggregated data in WordPress options, read during metrics collection
|
||||
- Histograms exposed as gauge metrics following Prometheus naming conventions (`_bucket`, `_sum`, `_count`)
|
||||
- **Key Learning**: Endpoint normalization is important for cardinality control
|
||||
- Group requests into categories (admin, ajax, cron, rest-api, frontend, etc.)
|
||||
- Avoid high-cardinality labels like full URL paths
|
||||
|
||||
### 2026-02-01 - CI/CD Fixes (v0.0.2)
|
||||
|
||||
- Fixed composer.json dependency configuration for CI compatibility
|
||||
- **Key Learning**: Git submodules with path repositories need explicit version aliases for CI:
|
||||
|
||||
```json
|
||||
"repositories": [
|
||||
{
|
||||
"type": "path",
|
||||
"url": "lib/wc-licensed-product-client",
|
||||
"options": {
|
||||
"symlink": false,
|
||||
"versions": {
|
||||
"magdev/wc-licensed-product-client": "0.2.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
- Using `dev-main` constraints with `minimum-stability: dev` causes issues in CI
|
||||
- Path repository with `symlink: false` and explicit `versions` mapping works reliably
|
||||
- **Key Learning**: Gitea API returns "Release has no Tag" error when re-releasing existing tags
|
||||
- Fixed release.yml to check for and delete existing releases before creating new ones
|
||||
- Changed minimum-stability back to stable
|
||||
|
||||
### 2026-02-01 - Initial Setup (v0.0.1)
|
||||
|
||||
- Created initial plugin structure based on wp-fedistream blueprint
|
||||
|
||||
23
PLAN.md
23
PLAN.md
@@ -91,6 +91,8 @@ wp-prometheus/
|
||||
|
||||
The plugin provides the following default metrics (can be toggled in settings):
|
||||
|
||||
### Static Metrics
|
||||
|
||||
| Metric | Type | Labels | Description |
|
||||
|--------|------|--------|-------------|
|
||||
| wordpress_info | Gauge | version, php_version, multisite | WordPress installation info |
|
||||
@@ -99,6 +101,15 @@ The plugin provides the following default metrics (can be toggled in settings):
|
||||
| wordpress_comments_total | Gauge | status | Total comments by status |
|
||||
| wordpress_plugins_total | Gauge | status | Total plugins (active/inactive) |
|
||||
|
||||
### Runtime Metrics
|
||||
|
||||
| Metric | Type | Labels | Description |
|
||||
| ---------------------------------------- | --------- | ------------------------ | ------------------------------------- |
|
||||
| wordpress_http_requests_total | Counter | method, status, endpoint | Total HTTP requests |
|
||||
| wordpress_http_request_duration_seconds | Histogram | method, endpoint | Request duration distribution |
|
||||
| wordpress_db_queries_total | Counter | endpoint | Total database queries |
|
||||
| wordpress_db_query_duration_seconds | Histogram | endpoint | Query duration (requires SAVEQUERIES) |
|
||||
|
||||
## Extensibility
|
||||
|
||||
### Adding Custom Metrics
|
||||
@@ -150,18 +161,6 @@ https://example.com/metrics/?token=your-auth-token
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Version 0.1.0
|
||||
|
||||
- Request/Response timing metrics
|
||||
- HTTP status code counters
|
||||
- Database query metrics
|
||||
|
||||
### Version 0.2.0
|
||||
|
||||
- WooCommerce integration metrics
|
||||
- Cron job metrics
|
||||
- Transient cache metrics
|
||||
|
||||
### Version 0.3.0
|
||||
|
||||
- Custom metric builder in admin
|
||||
|
||||
40
README.md
40
README.md
@@ -71,6 +71,8 @@ scrape_configs:
|
||||
|
||||
## Default Metrics
|
||||
|
||||
### Static Metrics
|
||||
|
||||
| Metric | Type | Labels | Description |
|
||||
|--------|------|--------|-------------|
|
||||
| wordpress_info | Gauge | version, php_version, multisite | WordPress installation info |
|
||||
@@ -79,6 +81,44 @@ scrape_configs:
|
||||
| wordpress_comments_total | Gauge | status | Total comments by status |
|
||||
| wordpress_plugins_total | Gauge | status | Total plugins (active/inactive) |
|
||||
|
||||
### Runtime Metrics (v0.1.0+)
|
||||
|
||||
| Metric | Type | Labels | Description |
|
||||
|--------|------|--------|-------------|
|
||||
| wordpress_http_requests_total | Counter | method, status, endpoint | Total HTTP requests |
|
||||
| wordpress_http_request_duration_seconds | Histogram | method, endpoint | Request duration distribution |
|
||||
| wordpress_db_queries_total | Counter | endpoint | Total database queries |
|
||||
| wordpress_db_query_duration_seconds | Histogram | endpoint | Query duration (requires SAVEQUERIES) |
|
||||
|
||||
**Note:** Runtime metrics aggregate data across requests. Enable only the metrics you need to minimize performance impact.
|
||||
|
||||
### Cron Metrics (v0.2.0+)
|
||||
|
||||
| Metric | Type | Labels | Description |
|
||||
|--------|------|--------|-------------|
|
||||
| wordpress_cron_events_total | Gauge | hook | Scheduled cron events by hook |
|
||||
| wordpress_cron_overdue_total | Gauge | - | Number of overdue cron events |
|
||||
| wordpress_cron_next_run_timestamp | Gauge | - | Unix timestamp of next scheduled cron |
|
||||
|
||||
### Transient Metrics (v0.2.0+)
|
||||
|
||||
| Metric | Type | Labels | Description |
|
||||
|--------|------|--------|-------------|
|
||||
| wordpress_transients_total | Gauge | type | Transients by type (total, with_expiration, persistent, expired) |
|
||||
|
||||
### WooCommerce Metrics (v0.2.0+)
|
||||
|
||||
These metrics are only available when WooCommerce is active.
|
||||
|
||||
| Metric | Type | Labels | Description |
|
||||
|--------|------|--------|-------------|
|
||||
| wordpress_woocommerce_products_total | Gauge | status, type | Products by status and type |
|
||||
| wordpress_woocommerce_orders_total | Gauge | status | Orders by status |
|
||||
| wordpress_woocommerce_revenue_total | Gauge | period, currency | Revenue (all_time, today, month) |
|
||||
| wordpress_woocommerce_customers_total | Gauge | type | Customers (registered, guest) |
|
||||
|
||||
**Note:** WooCommerce metrics support both legacy post-based orders and HPOS (High-Performance Order Storage).
|
||||
|
||||
## Extending with Custom Metrics
|
||||
|
||||
Add your own metrics using the `wp_prometheus_collect_metrics` action:
|
||||
|
||||
47
assets/css/admin.css
Normal file
47
assets/css/admin.css
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* WP Prometheus Admin Styles
|
||||
*
|
||||
* @package WP_Prometheus
|
||||
*/
|
||||
|
||||
/* Tab content styling */
|
||||
.wp-prometheus-tab-content {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* License status box */
|
||||
.wp-prometheus-license-status {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
/* Help tab code blocks */
|
||||
.wp-prometheus-tab-content pre {
|
||||
background: #f1f1f1;
|
||||
padding: 15px;
|
||||
overflow-x: auto;
|
||||
margin: 15px 0;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
/* Help tab tables */
|
||||
.wp-prometheus-tab-content .widefat {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.wp-prometheus-tab-content .widefat code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Metrics fieldset */
|
||||
.wp-prometheus-tab-content fieldset p strong {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Form table adjustments for tabs */
|
||||
.wp-prometheus-tab-content .form-table {
|
||||
margin-top: 10px;
|
||||
}
|
||||
@@ -17,12 +17,18 @@
|
||||
"repositories": [
|
||||
{
|
||||
"type": "path",
|
||||
"url": "lib/wc-licensed-product-client"
|
||||
"url": "lib/wc-licensed-product-client",
|
||||
"options": {
|
||||
"symlink": false,
|
||||
"versions": {
|
||||
"magdev/wc-licensed-product-client": "0.2.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.3",
|
||||
"magdev/wc-licensed-product-client": "dev-main",
|
||||
"magdev/wc-licensed-product-client": "^0.2.2",
|
||||
"promphp/prometheus_client_php": "^2.10"
|
||||
},
|
||||
"require-dev": {
|
||||
@@ -56,6 +62,6 @@
|
||||
"phpcbf": "phpcbf",
|
||||
"test": "phpunit"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true
|
||||
}
|
||||
|
||||
3111
composer.lock
generated
Normal file
3111
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
languages/wp-prometheus-de_CH.mo
Normal file
BIN
languages/wp-prometheus-de_CH.mo
Normal file
Binary file not shown.
361
languages/wp-prometheus-de_CH.po
Normal file
361
languages/wp-prometheus-de_CH.po
Normal file
@@ -0,0 +1,361 @@
|
||||
# German (Switzerland, formal) translation for WP Prometheus.
|
||||
# Copyright (C) 2026 Marco Graetsch
|
||||
# This file is distributed under the GPL v2 or later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: WP Prometheus 0.2.0\n"
|
||||
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/magdev/wp-prometheus/issues\n"
|
||||
"POT-Creation-Date: 2026-02-02T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2026-02-02T00:00:00+00:00\n"
|
||||
"Last-Translator: Marco Graetsch <magdev3.0@gmail.com>\n"
|
||||
"Language-Team: German (Switzerland) <de_CH@li.org>\n"
|
||||
"Language: de_CH\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Metrics Settings"
|
||||
msgstr "Metriken-Einstellungen"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Metrics"
|
||||
msgstr "Metriken"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License"
|
||||
msgstr "Lizenz"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Help"
|
||||
msgstr "Hilfe"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License settings saved."
|
||||
msgstr "Lizenz-Einstellungen gespeichert."
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License is active and valid."
|
||||
msgstr "Lizenz ist aktiv und gueltig."
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License is invalid."
|
||||
msgstr "Lizenz ist ungueltig."
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License has expired."
|
||||
msgstr "Lizenz ist abgelaufen."
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License has been revoked."
|
||||
msgstr "Lizenz wurde widerrufen."
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License is inactive."
|
||||
msgstr "Lizenz ist inaktiv."
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License has not been validated yet."
|
||||
msgstr "Lizenz wurde noch nicht validiert."
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License server is not configured."
|
||||
msgstr "Lizenz-Server ist nicht konfiguriert."
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Unknown status."
|
||||
msgstr "Unbekannter Status."
|
||||
|
||||
#. translators: %s: Expiration date
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Expires: %s"
|
||||
msgstr "Laeuft ab: %s"
|
||||
|
||||
#. translators: %s: Time ago
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Last checked: %s ago"
|
||||
msgstr "Zuletzt geprueft: vor %s"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License Server URL"
|
||||
msgstr "Lizenz-Server URL"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License Key"
|
||||
msgstr "Lizenzschluessel"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Server Secret"
|
||||
msgstr "Server-Geheimnis"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Leave empty to keep existing."
|
||||
msgstr "Leer lassen, um bestehenden Wert zu behalten."
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Save License Settings"
|
||||
msgstr "Lizenz-Einstellungen speichern"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Validate License"
|
||||
msgstr "Lizenz validieren"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Activate License"
|
||||
msgstr "Lizenz aktivieren"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Authentication"
|
||||
msgstr "Authentifizierung"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Enabled Metrics"
|
||||
msgstr "Aktivierte Metriken"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Configure authentication for the /metrics endpoint."
|
||||
msgstr "Authentifizierung fuer den /metrics-Endpunkt konfigurieren."
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Select which metrics to expose on the /metrics endpoint."
|
||||
msgstr "Waehlen Sie, welche Metriken auf dem /metrics-Endpunkt bereitgestellt werden sollen."
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Auth Token"
|
||||
msgstr "Auth-Token"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Select Metrics"
|
||||
msgstr "Metriken auswaehlen"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Regenerate"
|
||||
msgstr "Neu generieren"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Use this token to authenticate Prometheus scrape requests."
|
||||
msgstr "Verwenden Sie diesen Token zur Authentifizierung von Prometheus-Abfragen."
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Static Metrics"
|
||||
msgstr "Statische Metriken"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WordPress Info (version, PHP version, multisite)"
|
||||
msgstr "WordPress-Info (Version, PHP-Version, Multisite)"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total Users by Role"
|
||||
msgstr "Benutzer nach Rolle"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total Posts by Type and Status"
|
||||
msgstr "Beitraege nach Typ und Status"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total Comments by Status"
|
||||
msgstr "Kommentare nach Status"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total Plugins (active/inactive)"
|
||||
msgstr "Plugins (aktiv/inaktiv)"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Cron Events (scheduled tasks, overdue, next run)"
|
||||
msgstr "Cron-Ereignisse (geplante Aufgaben, ueberfaellig, naechste Ausfuehrung)"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Transients (total, expiring, expired)"
|
||||
msgstr "Transienten (gesamt, ablaufend, abgelaufen)"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Runtime Metrics"
|
||||
msgstr "Laufzeit-Metriken"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Runtime metrics track data across requests. Enable only what you need to minimize performance impact."
|
||||
msgstr "Laufzeit-Metriken erfassen Daten ueber Anfragen hinweg. Aktivieren Sie nur, was Sie benoetigen, um Auswirkungen auf die Leistung zu minimieren."
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "HTTP Requests Total (by method, status, endpoint)"
|
||||
msgstr "HTTP-Anfragen gesamt (nach Methode, Status, Endpunkt)"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "HTTP Request Duration (histogram)"
|
||||
msgstr "HTTP-Anfragedauer (Histogramm)"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Database Queries Total (by endpoint)"
|
||||
msgstr "Datenbank-Abfragen gesamt (nach Endpunkt)"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce Metrics"
|
||||
msgstr "WooCommerce-Metriken"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Metrics specific to WooCommerce stores. Only available when WooCommerce is active."
|
||||
msgstr "Metriken speziell fuer WooCommerce-Shops. Nur verfuegbar, wenn WooCommerce aktiv ist."
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce Products (by status and type)"
|
||||
msgstr "WooCommerce-Produkte (nach Status und Typ)"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce Orders (by status)"
|
||||
msgstr "WooCommerce-Bestellungen (nach Status)"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce Revenue (all time, today, month)"
|
||||
msgstr "WooCommerce-Umsatz (gesamt, heute, Monat)"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce Customers (registered, guest)"
|
||||
msgstr "WooCommerce-Kunden (registriert, Gast)"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Prometheus Configuration"
|
||||
msgstr "Prometheus-Konfiguration"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Add the following to your prometheus.yml:"
|
||||
msgstr "Fuegen Sie Folgendes zu Ihrer prometheus.yml hinzu:"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Endpoint Information"
|
||||
msgstr "Endpunkt-Informationen"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Metrics URL"
|
||||
msgstr "Metriken-URL"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Testing the Endpoint"
|
||||
msgstr "Endpunkt testen"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "You can test the endpoint using curl:"
|
||||
msgstr "Sie koennen den Endpunkt mit curl testen:"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Available Metrics"
|
||||
msgstr "Verfuegbare Metriken"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Metric"
|
||||
msgstr "Metrik"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Type"
|
||||
msgstr "Typ"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Description"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Gauge"
|
||||
msgstr "Gauge"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Counter"
|
||||
msgstr "Counter"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Histogram"
|
||||
msgstr "Histogramm"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WordPress installation info"
|
||||
msgstr "WordPress-Installationsinformationen"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total users by role"
|
||||
msgstr "Benutzer gesamt nach Rolle"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total posts by type and status"
|
||||
msgstr "Beitraege gesamt nach Typ und Status"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total comments by status"
|
||||
msgstr "Kommentare gesamt nach Status"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total plugins by status"
|
||||
msgstr "Plugins gesamt nach Status"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "HTTP requests by method, status, endpoint"
|
||||
msgstr "HTTP-Anfragen nach Methode, Status, Endpunkt"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "HTTP request duration distribution"
|
||||
msgstr "HTTP-Anfragedauer-Verteilung"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Database queries by endpoint"
|
||||
msgstr "Datenbank-Abfragen nach Endpunkt"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Scheduled cron events by hook"
|
||||
msgstr "Geplante Cron-Ereignisse nach Hook"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Number of overdue cron events"
|
||||
msgstr "Anzahl ueberfaelliger Cron-Ereignisse"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Unix timestamp of next scheduled cron"
|
||||
msgstr "Unix-Zeitstempel des naechsten geplanten Crons"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total transients by type"
|
||||
msgstr "Transienten gesamt nach Typ"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce products by status and type"
|
||||
msgstr "WooCommerce-Produkte nach Status und Typ"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce orders by status"
|
||||
msgstr "WooCommerce-Bestellungen nach Status"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce revenue by period"
|
||||
msgstr "WooCommerce-Umsatz nach Zeitraum"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce customers by type"
|
||||
msgstr "WooCommerce-Kunden nach Typ"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Custom Metrics"
|
||||
msgstr "Benutzerdefinierte Metriken"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "You can add custom metrics using the wp_prometheus_collect_metrics action:"
|
||||
msgstr "Sie koennen benutzerdefinierte Metriken mit der wp_prometheus_collect_metrics-Aktion hinzufuegen:"
|
||||
|
||||
#: src/Plugin.php
|
||||
msgid "Settings"
|
||||
msgstr "Einstellungen"
|
||||
|
||||
#. translators: 1: Required PHP version, 2: Current PHP version
|
||||
#: wp-prometheus.php
|
||||
msgid "WP Prometheus requires PHP version %1$s or higher. You are running PHP %2$s."
|
||||
msgstr "WP Prometheus erfordert PHP-Version %1$s oder hoeher. Sie verwenden PHP %2$s."
|
||||
|
||||
#. translators: 1: Required WordPress version, 2: Current WordPress version
|
||||
#: wp-prometheus.php
|
||||
msgid "WP Prometheus requires WordPress version %1$s or higher. You are running WordPress %2$s."
|
||||
msgstr "WP Prometheus erfordert WordPress-Version %1$s oder hoeher. Sie verwenden WordPress %2$s."
|
||||
|
||||
#: wp-prometheus.php
|
||||
msgid "WP Prometheus requires Composer dependencies to be installed. Please run \"composer install\" in the plugin directory."
|
||||
msgstr "WP Prometheus erfordert installierte Composer-Abhaengigkeiten. Bitte fuehren Sie \"composer install\" im Plugin-Verzeichnis aus."
|
||||
|
||||
#. translators: %s: Required PHP version
|
||||
#: wp-prometheus.php
|
||||
msgid "WP Prometheus requires PHP version %s or higher."
|
||||
msgstr "WP Prometheus erfordert PHP-Version %s oder hoeher."
|
||||
358
languages/wp-prometheus.pot
Normal file
358
languages/wp-prometheus.pot
Normal file
@@ -0,0 +1,358 @@
|
||||
# Copyright (C) 2026 Marco Graetsch
|
||||
# This file is distributed under the GPL v2 or later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: WP Prometheus 0.2.0\n"
|
||||
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/magdev/wp-prometheus/issues\n"
|
||||
"POT-Creation-Date: 2026-02-02T00:00:00+00:00\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2026-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Metrics Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Metrics"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Help"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License settings saved."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License is active and valid."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License is invalid."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License has expired."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License has been revoked."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License is inactive."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License has not been validated yet."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License server is not configured."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Unknown status."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Expiration date
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Expires: %s"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Time ago
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Last checked: %s ago"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License Server URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "License Key"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Server Secret"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Leave empty to keep existing."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Save License Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Validate License"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Activate License"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Enabled Metrics"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Configure authentication for the /metrics endpoint."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Select which metrics to expose on the /metrics endpoint."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Auth Token"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Select Metrics"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Regenerate"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Use this token to authenticate Prometheus scrape requests."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Static Metrics"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WordPress Info (version, PHP version, multisite)"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total Users by Role"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total Posts by Type and Status"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total Comments by Status"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total Plugins (active/inactive)"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Cron Events (scheduled tasks, overdue, next run)"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Transients (total, expiring, expired)"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Runtime Metrics"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Runtime metrics track data across requests. Enable only what you need to minimize performance impact."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "HTTP Requests Total (by method, status, endpoint)"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "HTTP Request Duration (histogram)"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Database Queries Total (by endpoint)"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce Metrics"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Metrics specific to WooCommerce stores. Only available when WooCommerce is active."
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce Products (by status and type)"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce Orders (by status)"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce Revenue (all time, today, month)"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce Customers (registered, guest)"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Prometheus Configuration"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Add the following to your prometheus.yml:"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Endpoint Information"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Metrics URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Testing the Endpoint"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "You can test the endpoint using curl:"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Available Metrics"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Metric"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Type"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Gauge"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Counter"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Histogram"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WordPress installation info"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total users by role"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total posts by type and status"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total comments by status"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total plugins by status"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "HTTP requests by method, status, endpoint"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "HTTP request duration distribution"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Database queries by endpoint"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Scheduled cron events by hook"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Number of overdue cron events"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Unix timestamp of next scheduled cron"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Total transients by type"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce products by status and type"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce orders by status"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce revenue by period"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "WooCommerce customers by type"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "Custom Metrics"
|
||||
msgstr ""
|
||||
|
||||
#: src/Admin/Settings.php
|
||||
msgid "You can add custom metrics using the wp_prometheus_collect_metrics action:"
|
||||
msgstr ""
|
||||
|
||||
#: src/Plugin.php
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#. translators: 1: Required PHP version, 2: Current PHP version
|
||||
#: wp-prometheus.php
|
||||
msgid "WP Prometheus requires PHP version %1$s or higher. You are running PHP %2$s."
|
||||
msgstr ""
|
||||
|
||||
#. translators: 1: Required WordPress version, 2: Current WordPress version
|
||||
#: wp-prometheus.php
|
||||
msgid "WP Prometheus requires WordPress version %1$s or higher. You are running WordPress %2$s."
|
||||
msgstr ""
|
||||
|
||||
#: wp-prometheus.php
|
||||
msgid "WP Prometheus requires Composer dependencies to be installed. Please run \"composer install\" in the plugin directory."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Required PHP version
|
||||
#: wp-prometheus.php
|
||||
msgid "WP Prometheus requires PHP version %s or higher."
|
||||
msgstr ""
|
||||
@@ -21,15 +21,38 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
*/
|
||||
class Settings {
|
||||
|
||||
/**
|
||||
* Available tabs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $tabs = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->tabs = array(
|
||||
'license' => __( 'License', 'wp-prometheus' ),
|
||||
'metrics' => __( 'Metrics', 'wp-prometheus' ),
|
||||
'help' => __( 'Help', 'wp-prometheus' ),
|
||||
);
|
||||
|
||||
add_action( 'admin_menu', array( $this, 'add_settings_page' ) );
|
||||
add_action( 'admin_init', array( $this, 'register_settings' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current tab.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_current_tab(): string {
|
||||
$tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'license';
|
||||
return array_key_exists( $tab, $this->tabs ) ? $tab : 'license';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add settings page to admin menu.
|
||||
*
|
||||
@@ -51,56 +74,48 @@ class Settings {
|
||||
* @return void
|
||||
*/
|
||||
public function register_settings(): void {
|
||||
// License settings section.
|
||||
add_settings_section(
|
||||
'wp_prometheus_license_section',
|
||||
__( 'License Settings', 'wp-prometheus' ),
|
||||
array( $this, 'render_license_section' ),
|
||||
'wp-prometheus'
|
||||
);
|
||||
// Register settings for metrics tab.
|
||||
register_setting( 'wp_prometheus_metrics_settings', 'wp_prometheus_auth_token', array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
) );
|
||||
|
||||
register_setting( 'wp_prometheus_metrics_settings', 'wp_prometheus_enabled_metrics', array(
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => array( $this, 'sanitize_metrics' ),
|
||||
) );
|
||||
|
||||
// Auth token section.
|
||||
add_settings_section(
|
||||
'wp_prometheus_auth_section',
|
||||
__( 'Authentication', 'wp-prometheus' ),
|
||||
array( $this, 'render_auth_section' ),
|
||||
'wp-prometheus'
|
||||
'wp-prometheus-metrics'
|
||||
);
|
||||
|
||||
// Metrics section.
|
||||
add_settings_section(
|
||||
'wp_prometheus_metrics_section',
|
||||
__( 'Default Metrics', 'wp-prometheus' ),
|
||||
__( 'Enabled Metrics', 'wp-prometheus' ),
|
||||
array( $this, 'render_metrics_section' ),
|
||||
'wp-prometheus'
|
||||
'wp-prometheus-metrics'
|
||||
);
|
||||
|
||||
// Register settings.
|
||||
register_setting( 'wp_prometheus_settings', 'wp_prometheus_auth_token', array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
) );
|
||||
|
||||
register_setting( 'wp_prometheus_settings', 'wp_prometheus_enabled_metrics', array(
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => array( $this, 'sanitize_metrics' ),
|
||||
) );
|
||||
|
||||
// Auth token field.
|
||||
add_settings_field(
|
||||
'wp_prometheus_auth_token',
|
||||
__( 'Auth Token', 'wp-prometheus' ),
|
||||
array( $this, 'render_auth_token_field' ),
|
||||
'wp-prometheus',
|
||||
'wp-prometheus-metrics',
|
||||
'wp_prometheus_auth_section'
|
||||
);
|
||||
|
||||
// Enabled metrics field.
|
||||
add_settings_field(
|
||||
'wp_prometheus_enabled_metrics',
|
||||
__( 'Enabled Metrics', 'wp-prometheus' ),
|
||||
__( 'Select Metrics', 'wp-prometheus' ),
|
||||
array( $this, 'render_enabled_metrics_field' ),
|
||||
'wp-prometheus',
|
||||
'wp-prometheus-metrics',
|
||||
'wp_prometheus_metrics_section'
|
||||
);
|
||||
}
|
||||
@@ -116,6 +131,13 @@ class Settings {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_style(
|
||||
'wp-prometheus-admin',
|
||||
WP_PROMETHEUS_URL . 'assets/css/admin.css',
|
||||
array(),
|
||||
WP_PROMETHEUS_VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'wp-prometheus-admin',
|
||||
WP_PROMETHEUS_URL . 'assets/js/admin.js',
|
||||
@@ -140,8 +162,10 @@ class Settings {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_tab = $this->get_current_tab();
|
||||
|
||||
// Handle license settings save.
|
||||
if ( isset( $_POST['wp_prometheus_license_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['wp_prometheus_license_nonce'] ), 'wp_prometheus_save_license' ) ) {
|
||||
if ( 'license' === $current_tab && isset( $_POST['wp_prometheus_license_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['wp_prometheus_license_nonce'] ), 'wp_prometheus_save_license' ) ) {
|
||||
LicenseManager::save_settings( array(
|
||||
'license_key' => isset( $_POST['license_key'] ) ? sanitize_text_field( wp_unslash( $_POST['license_key'] ) ) : '',
|
||||
'server_url' => isset( $_POST['license_server_url'] ) ? esc_url_raw( wp_unslash( $_POST['license_server_url'] ) ) : '',
|
||||
@@ -154,27 +178,64 @@ class Settings {
|
||||
<div class="wrap">
|
||||
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
|
||||
|
||||
<?php $this->render_license_form(); ?>
|
||||
<?php $this->render_tabs( $current_tab ); ?>
|
||||
|
||||
<form method="post" action="options.php">
|
||||
<div class="wp-prometheus-tab-content">
|
||||
<?php
|
||||
settings_fields( 'wp_prometheus_settings' );
|
||||
do_settings_sections( 'wp-prometheus' );
|
||||
submit_button();
|
||||
switch ( $current_tab ) {
|
||||
case 'license':
|
||||
$this->render_license_tab();
|
||||
break;
|
||||
case 'metrics':
|
||||
$this->render_metrics_tab();
|
||||
break;
|
||||
case 'help':
|
||||
$this->render_help_tab();
|
||||
break;
|
||||
}
|
||||
?>
|
||||
</form>
|
||||
|
||||
<?php $this->render_endpoint_info(); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render license settings form.
|
||||
* Render tabs navigation.
|
||||
*
|
||||
* @param string $current_tab Current active tab.
|
||||
* @return void
|
||||
*/
|
||||
private function render_tabs( string $current_tab ): void {
|
||||
?>
|
||||
<nav class="nav-tab-wrapper wp-clearfix">
|
||||
<?php
|
||||
foreach ( $this->tabs as $tab_id => $tab_name ) {
|
||||
$tab_url = add_query_arg(
|
||||
array(
|
||||
'page' => 'wp-prometheus',
|
||||
'tab' => $tab_id,
|
||||
),
|
||||
admin_url( 'options-general.php' )
|
||||
);
|
||||
$active_class = ( $current_tab === $tab_id ) ? ' nav-tab-active' : '';
|
||||
printf(
|
||||
'<a href="%s" class="nav-tab%s">%s</a>',
|
||||
esc_url( $tab_url ),
|
||||
esc_attr( $active_class ),
|
||||
esc_html( $tab_name )
|
||||
);
|
||||
}
|
||||
?>
|
||||
</nav>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render license tab content.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_license_form(): void {
|
||||
private function render_license_tab(): void {
|
||||
$license_key = LicenseManager::get_license_key();
|
||||
$server_url = LicenseManager::get_server_url();
|
||||
$license_status = LicenseManager::get_cached_status();
|
||||
@@ -204,7 +265,7 @@ class Settings {
|
||||
$status_class = $status_classes[ $license_status ] ?? 'notice-info';
|
||||
$status_message = $status_messages[ $license_status ] ?? __( 'Unknown status.', 'wp-prometheus' );
|
||||
?>
|
||||
<div class="wp-prometheus-license-status notice <?php echo esc_attr( $status_class ); ?>" style="padding: 12px;">
|
||||
<div class="wp-prometheus-license-status notice <?php echo esc_attr( $status_class ); ?>" style="padding: 12px; margin: 15px 0;">
|
||||
<strong><?php echo esc_html( $status_message ); ?></strong>
|
||||
<?php if ( 'valid' === $license_status && ! empty( $license_data['expires_at'] ) ) : ?>
|
||||
<br><span class="description">
|
||||
@@ -233,7 +294,7 @@ class Settings {
|
||||
<form method="post" action="" id="wp-prometheus-license-form">
|
||||
<?php wp_nonce_field( 'wp_prometheus_save_license', 'wp_prometheus_license_nonce' ); ?>
|
||||
|
||||
<table class="form-table">
|
||||
<table class="form-table" role="presentation">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="license_server_url"><?php esc_html_e( 'License Server URL', 'wp-prometheus' ); ?></label>
|
||||
@@ -279,17 +340,167 @@ class Settings {
|
||||
</form>
|
||||
|
||||
<div id="wp-prometheus-license-message" style="display: none; margin-top: 10px;"></div>
|
||||
<hr>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render license section description.
|
||||
* Render metrics tab content.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_license_section(): void {
|
||||
// License section rendered separately in render_license_form().
|
||||
private function render_metrics_tab(): void {
|
||||
?>
|
||||
<form method="post" action="options.php">
|
||||
<?php
|
||||
settings_fields( 'wp_prometheus_metrics_settings' );
|
||||
do_settings_sections( 'wp-prometheus-metrics' );
|
||||
submit_button();
|
||||
?>
|
||||
</form>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render help tab content.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_help_tab(): void {
|
||||
$token = get_option( 'wp_prometheus_auth_token', '' );
|
||||
$endpoint_url = home_url( '/metrics/' );
|
||||
?>
|
||||
<h2><?php esc_html_e( 'Prometheus Configuration', 'wp-prometheus' ); ?></h2>
|
||||
<p><?php esc_html_e( 'Add the following to your prometheus.yml:', 'wp-prometheus' ); ?></p>
|
||||
<pre style="background: #f1f1f1; padding: 15px; overflow-x: auto; margin: 15px 0;">scrape_configs:
|
||||
- job_name: 'wordpress'
|
||||
static_configs:
|
||||
- targets: ['<?php echo esc_html( wp_parse_url( home_url(), PHP_URL_HOST ) ); ?>']
|
||||
metrics_path: '/metrics/'
|
||||
scheme: '<?php echo esc_html( wp_parse_url( home_url(), PHP_URL_SCHEME ) ); ?>'
|
||||
authorization:
|
||||
type: Bearer
|
||||
credentials: '<?php echo esc_html( $token ); ?>'</pre>
|
||||
|
||||
<h3><?php esc_html_e( 'Endpoint Information', 'wp-prometheus' ); ?></h3>
|
||||
<table class="form-table" role="presentation">
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Metrics URL', 'wp-prometheus' ); ?></th>
|
||||
<td><code><?php echo esc_url( $endpoint_url ); ?></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Auth Token', 'wp-prometheus' ); ?></th>
|
||||
<td><code><?php echo esc_html( $token ); ?></code></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3><?php esc_html_e( 'Testing the Endpoint', 'wp-prometheus' ); ?></h3>
|
||||
<p><?php esc_html_e( 'You can test the endpoint using curl:', 'wp-prometheus' ); ?></p>
|
||||
<pre style="background: #f1f1f1; padding: 15px; overflow-x: auto; margin: 15px 0;">curl -H "Authorization: Bearer <?php echo esc_html( $token ); ?>" <?php echo esc_url( $endpoint_url ); ?></pre>
|
||||
|
||||
<h3><?php esc_html_e( 'Available Metrics', 'wp-prometheus' ); ?></h3>
|
||||
<table class="widefat striped" style="margin: 15px 0;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php esc_html_e( 'Metric', 'wp-prometheus' ); ?></th>
|
||||
<th><?php esc_html_e( 'Type', 'wp-prometheus' ); ?></th>
|
||||
<th><?php esc_html_e( 'Description', 'wp-prometheus' ); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>wordpress_info</code></td>
|
||||
<td><?php esc_html_e( 'Gauge', 'wp-prometheus' ); ?></td>
|
||||
<td><?php esc_html_e( 'WordPress installation info', 'wp-prometheus' ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>wordpress_users_total</code></td>
|
||||
<td><?php esc_html_e( 'Gauge', 'wp-prometheus' ); ?></td>
|
||||
<td><?php esc_html_e( 'Total users by role', 'wp-prometheus' ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>wordpress_posts_total</code></td>
|
||||
<td><?php esc_html_e( 'Gauge', 'wp-prometheus' ); ?></td>
|
||||
<td><?php esc_html_e( 'Total posts by type and status', 'wp-prometheus' ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>wordpress_comments_total</code></td>
|
||||
<td><?php esc_html_e( 'Gauge', 'wp-prometheus' ); ?></td>
|
||||
<td><?php esc_html_e( 'Total comments by status', 'wp-prometheus' ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>wordpress_plugins_total</code></td>
|
||||
<td><?php esc_html_e( 'Gauge', 'wp-prometheus' ); ?></td>
|
||||
<td><?php esc_html_e( 'Total plugins by status', 'wp-prometheus' ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>wordpress_http_requests_total</code></td>
|
||||
<td><?php esc_html_e( 'Counter', 'wp-prometheus' ); ?></td>
|
||||
<td><?php esc_html_e( 'HTTP requests by method, status, endpoint', 'wp-prometheus' ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>wordpress_http_request_duration_seconds</code></td>
|
||||
<td><?php esc_html_e( 'Histogram', 'wp-prometheus' ); ?></td>
|
||||
<td><?php esc_html_e( 'HTTP request duration distribution', 'wp-prometheus' ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>wordpress_db_queries_total</code></td>
|
||||
<td><?php esc_html_e( 'Counter', 'wp-prometheus' ); ?></td>
|
||||
<td><?php esc_html_e( 'Database queries by endpoint', 'wp-prometheus' ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>wordpress_cron_events_total</code></td>
|
||||
<td><?php esc_html_e( 'Gauge', 'wp-prometheus' ); ?></td>
|
||||
<td><?php esc_html_e( 'Scheduled cron events by hook', 'wp-prometheus' ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>wordpress_cron_overdue_total</code></td>
|
||||
<td><?php esc_html_e( 'Gauge', 'wp-prometheus' ); ?></td>
|
||||
<td><?php esc_html_e( 'Number of overdue cron events', 'wp-prometheus' ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>wordpress_cron_next_run_timestamp</code></td>
|
||||
<td><?php esc_html_e( 'Gauge', 'wp-prometheus' ); ?></td>
|
||||
<td><?php esc_html_e( 'Unix timestamp of next scheduled cron', 'wp-prometheus' ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>wordpress_transients_total</code></td>
|
||||
<td><?php esc_html_e( 'Gauge', 'wp-prometheus' ); ?></td>
|
||||
<td><?php esc_html_e( 'Total transients by type', 'wp-prometheus' ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>wordpress_woocommerce_products_total</code></td>
|
||||
<td><?php esc_html_e( 'Gauge', 'wp-prometheus' ); ?></td>
|
||||
<td><?php esc_html_e( 'WooCommerce products by status and type', 'wp-prometheus' ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>wordpress_woocommerce_orders_total</code></td>
|
||||
<td><?php esc_html_e( 'Gauge', 'wp-prometheus' ); ?></td>
|
||||
<td><?php esc_html_e( 'WooCommerce orders by status', 'wp-prometheus' ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>wordpress_woocommerce_revenue_total</code></td>
|
||||
<td><?php esc_html_e( 'Gauge', 'wp-prometheus' ); ?></td>
|
||||
<td><?php esc_html_e( 'WooCommerce revenue by period', 'wp-prometheus' ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>wordpress_woocommerce_customers_total</code></td>
|
||||
<td><?php esc_html_e( 'Gauge', 'wp-prometheus' ); ?></td>
|
||||
<td><?php esc_html_e( 'WooCommerce customers by type', 'wp-prometheus' ); ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3><?php esc_html_e( 'Custom Metrics', 'wp-prometheus' ); ?></h3>
|
||||
<p><?php esc_html_e( 'You can add custom metrics using the wp_prometheus_collect_metrics action:', 'wp-prometheus' ); ?></p>
|
||||
<pre style="background: #f1f1f1; padding: 15px; overflow-x: auto; margin: 15px 0;">add_action( 'wp_prometheus_collect_metrics', function( $collector ) {
|
||||
$gauge = $collector->register_gauge(
|
||||
'my_custom_metric',
|
||||
'Description of my metric',
|
||||
array( 'label1', 'label2' )
|
||||
);
|
||||
$gauge->set( 42, array( 'value1', 'value2' ) );
|
||||
} );</pre>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,7 +518,7 @@ class Settings {
|
||||
* @return void
|
||||
*/
|
||||
public function render_metrics_section(): void {
|
||||
echo '<p>' . esc_html__( 'Select which default metrics to expose.', 'wp-prometheus' ) . '</p>';
|
||||
echo '<p>' . esc_html__( 'Select which metrics to expose on the /metrics endpoint.', 'wp-prometheus' ) . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -336,15 +547,35 @@ class Settings {
|
||||
*/
|
||||
public function render_enabled_metrics_field(): void {
|
||||
$enabled = get_option( 'wp_prometheus_enabled_metrics', array() );
|
||||
$metrics = array(
|
||||
'wordpress_info' => __( 'WordPress Info (version, PHP version, multisite)', 'wp-prometheus' ),
|
||||
'wordpress_users_total' => __( 'Total Users by Role', 'wp-prometheus' ),
|
||||
'wordpress_posts_total' => __( 'Total Posts by Type and Status', 'wp-prometheus' ),
|
||||
|
||||
$static_metrics = array(
|
||||
'wordpress_info' => __( 'WordPress Info (version, PHP version, multisite)', 'wp-prometheus' ),
|
||||
'wordpress_users_total' => __( 'Total Users by Role', 'wp-prometheus' ),
|
||||
'wordpress_posts_total' => __( 'Total Posts by Type and Status', 'wp-prometheus' ),
|
||||
'wordpress_comments_total' => __( 'Total Comments by Status', 'wp-prometheus' ),
|
||||
'wordpress_plugins_total' => __( 'Total Plugins (active/inactive)', 'wp-prometheus' ),
|
||||
'wordpress_plugins_total' => __( 'Total Plugins (active/inactive)', 'wp-prometheus' ),
|
||||
'wordpress_cron_events_total' => __( 'Cron Events (scheduled tasks, overdue, next run)', 'wp-prometheus' ),
|
||||
'wordpress_transients_total' => __( 'Transients (total, expiring, expired)', 'wp-prometheus' ),
|
||||
);
|
||||
|
||||
foreach ( $metrics as $key => $label ) {
|
||||
$runtime_metrics = array(
|
||||
'wordpress_http_requests_total' => __( 'HTTP Requests Total (by method, status, endpoint)', 'wp-prometheus' ),
|
||||
'wordpress_http_request_duration_seconds' => __( 'HTTP Request Duration (histogram)', 'wp-prometheus' ),
|
||||
'wordpress_db_queries_total' => __( 'Database Queries Total (by endpoint)', 'wp-prometheus' ),
|
||||
);
|
||||
|
||||
$woocommerce_metrics = array(
|
||||
'wordpress_woocommerce_products_total' => __( 'WooCommerce Products (by status and type)', 'wp-prometheus' ),
|
||||
'wordpress_woocommerce_orders_total' => __( 'WooCommerce Orders (by status)', 'wp-prometheus' ),
|
||||
'wordpress_woocommerce_revenue_total' => __( 'WooCommerce Revenue (all time, today, month)', 'wp-prometheus' ),
|
||||
'wordpress_woocommerce_customers_total' => __( 'WooCommerce Customers (registered, guest)', 'wp-prometheus' ),
|
||||
);
|
||||
|
||||
echo '<fieldset>';
|
||||
echo '<legend class="screen-reader-text"><span>' . esc_html__( 'Static Metrics', 'wp-prometheus' ) . '</span></legend>';
|
||||
echo '<p><strong>' . esc_html__( 'Static Metrics', 'wp-prometheus' ) . '</strong></p>';
|
||||
|
||||
foreach ( $static_metrics as $key => $label ) {
|
||||
?>
|
||||
<label style="display: block; margin-bottom: 5px;">
|
||||
<input type="checkbox" name="wp_prometheus_enabled_metrics[]"
|
||||
@@ -354,40 +585,39 @@ class Settings {
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render endpoint info.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_endpoint_info(): void {
|
||||
$token = get_option( 'wp_prometheus_auth_token', '' );
|
||||
$endpoint_url = home_url( '/metrics/' );
|
||||
?>
|
||||
<hr>
|
||||
<h2><?php esc_html_e( 'Prometheus Configuration', 'wp-prometheus' ); ?></h2>
|
||||
<p><?php esc_html_e( 'Add the following to your prometheus.yml:', 'wp-prometheus' ); ?></p>
|
||||
<pre style="background: #f1f1f1; padding: 15px; overflow-x: auto;">
|
||||
scrape_configs:
|
||||
- job_name: 'wordpress'
|
||||
static_configs:
|
||||
- targets: ['<?php echo esc_html( wp_parse_url( home_url(), PHP_URL_HOST ) ); ?>']
|
||||
metrics_path: '/metrics/'
|
||||
scheme: '<?php echo esc_html( wp_parse_url( home_url(), PHP_URL_SCHEME ) ); ?>'
|
||||
authorization:
|
||||
type: Bearer
|
||||
credentials: '<?php echo esc_html( $token ); ?>'</pre>
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: Endpoint URL */
|
||||
esc_html__( 'Metrics endpoint: %s', 'wp-prometheus' ),
|
||||
'<code>' . esc_url( $endpoint_url ) . '</code>'
|
||||
);
|
||||
echo '<br><p><strong>' . esc_html__( 'Runtime Metrics', 'wp-prometheus' ) . '</strong></p>';
|
||||
echo '<p class="description" style="margin-bottom: 10px;">' . esc_html__( 'Runtime metrics track data across requests. Enable only what you need to minimize performance impact.', 'wp-prometheus' ) . '</p>';
|
||||
|
||||
foreach ( $runtime_metrics as $key => $label ) {
|
||||
?>
|
||||
</p>
|
||||
<?php
|
||||
<label style="display: block; margin-bottom: 5px;">
|
||||
<input type="checkbox" name="wp_prometheus_enabled_metrics[]"
|
||||
value="<?php echo esc_attr( $key ); ?>"
|
||||
<?php checked( in_array( $key, $enabled, true ) ); ?>>
|
||||
<?php echo esc_html( $label ); ?>
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
|
||||
// WooCommerce metrics (only show if WooCommerce is active).
|
||||
if ( class_exists( 'WooCommerce' ) ) {
|
||||
echo '<br><p><strong>' . esc_html__( 'WooCommerce Metrics', 'wp-prometheus' ) . '</strong></p>';
|
||||
echo '<p class="description" style="margin-bottom: 10px;">' . esc_html__( 'Metrics specific to WooCommerce stores. Only available when WooCommerce is active.', 'wp-prometheus' ) . '</p>';
|
||||
|
||||
foreach ( $woocommerce_metrics as $key => $label ) {
|
||||
?>
|
||||
<label style="display: block; margin-bottom: 5px;">
|
||||
<input type="checkbox" name="wp_prometheus_enabled_metrics[]"
|
||||
value="<?php echo esc_attr( $key ); ?>"
|
||||
<?php checked( in_array( $key, $enabled, true ) ); ?>>
|
||||
<?php echo esc_html( $label ); ?>
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
echo '</fieldset>';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -63,6 +63,7 @@ final class Installer {
|
||||
'wp_prometheus_auth_token',
|
||||
'wp_prometheus_enable_default_metrics',
|
||||
'wp_prometheus_enabled_metrics',
|
||||
'wp_prometheus_runtime_metrics',
|
||||
);
|
||||
|
||||
foreach ( $options as $option ) {
|
||||
|
||||
@@ -302,10 +302,47 @@ final class Manager {
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_license_valid(): bool {
|
||||
// Bypass license check on localhost for development.
|
||||
if ( self::is_localhost() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$status = get_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
|
||||
return 'valid' === $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current site is running on localhost.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_localhost(): bool {
|
||||
$host = wp_parse_url( home_url(), PHP_URL_HOST );
|
||||
|
||||
$localhost_patterns = array(
|
||||
'localhost',
|
||||
'127.0.0.1',
|
||||
'::1',
|
||||
);
|
||||
|
||||
// Check exact matches.
|
||||
if ( in_array( $host, $localhost_patterns, true ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check .localhost TLD (e.g., mysite.localhost).
|
||||
if ( str_ends_with( $host, '.localhost' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check .local TLD (common for local development).
|
||||
if ( str_ends_with( $host, '.local' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the license key.
|
||||
*
|
||||
@@ -396,9 +433,16 @@ final class Manager {
|
||||
* @return void
|
||||
*/
|
||||
private function update_cached_status( string $status, array $data = array() ): void {
|
||||
$previous_status = get_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
|
||||
|
||||
update_option( self::OPTION_LICENSE_STATUS, $status );
|
||||
update_option( self::OPTION_LICENSE_DATA, $data );
|
||||
update_option( self::OPTION_LAST_CHECK, time() );
|
||||
|
||||
// Flush rewrite rules when license becomes valid to register the /metrics endpoint.
|
||||
if ( 'valid' === $status && 'valid' !== $previous_status ) {
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -465,6 +509,9 @@ final class Manager {
|
||||
update_option( self::OPTION_LICENSE_DATA, array() );
|
||||
delete_transient( self::TRANSIENT_LICENSE_CHECK );
|
||||
|
||||
// Flush rewrite rules to remove the /metrics endpoint.
|
||||
flush_rewrite_rules();
|
||||
|
||||
wp_send_json_success( array(
|
||||
'success' => true,
|
||||
'message' => __( 'License deactivated.', 'wp-prometheus' ),
|
||||
|
||||
@@ -95,6 +95,24 @@ class Collector {
|
||||
$this->collect_plugins_total();
|
||||
}
|
||||
|
||||
// Collect cron metrics.
|
||||
if ( in_array( 'wordpress_cron_events_total', $enabled_metrics, true ) ) {
|
||||
$this->collect_cron_metrics();
|
||||
}
|
||||
|
||||
// Collect transient metrics.
|
||||
if ( in_array( 'wordpress_transients_total', $enabled_metrics, true ) ) {
|
||||
$this->collect_transient_metrics();
|
||||
}
|
||||
|
||||
// Collect WooCommerce metrics (if WooCommerce is active).
|
||||
if ( $this->is_woocommerce_active() ) {
|
||||
$this->collect_woocommerce_metrics( $enabled_metrics );
|
||||
}
|
||||
|
||||
// Collect runtime metrics (HTTP requests, DB queries).
|
||||
$this->collect_runtime_metrics( $enabled_metrics );
|
||||
|
||||
/**
|
||||
* Fires after default metrics are collected.
|
||||
*
|
||||
@@ -233,6 +251,522 @@ class Collector {
|
||||
$gauge->set( count( $all_plugins ) - count( $active_plugins ), array( 'inactive' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect cron metrics.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function collect_cron_metrics(): void {
|
||||
$cron_array = _get_cron_array();
|
||||
|
||||
if ( ! is_array( $cron_array ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Events total gauge.
|
||||
$events_gauge = $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
'cron_events_total',
|
||||
'Total number of scheduled cron events',
|
||||
array( 'hook' )
|
||||
);
|
||||
|
||||
// Count events by hook.
|
||||
$hook_counts = array();
|
||||
$total_events = 0;
|
||||
$overdue_count = 0;
|
||||
$current_time = time();
|
||||
$next_run = PHP_INT_MAX;
|
||||
|
||||
foreach ( $cron_array as $timestamp => $cron ) {
|
||||
if ( $timestamp < $next_run ) {
|
||||
$next_run = $timestamp;
|
||||
}
|
||||
|
||||
foreach ( $cron as $hook => $events ) {
|
||||
$event_count = count( $events );
|
||||
$total_events += $event_count;
|
||||
|
||||
if ( ! isset( $hook_counts[ $hook ] ) ) {
|
||||
$hook_counts[ $hook ] = 0;
|
||||
}
|
||||
$hook_counts[ $hook ] += $event_count;
|
||||
|
||||
// Check if overdue.
|
||||
if ( $timestamp < $current_time ) {
|
||||
$overdue_count += $event_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set events by hook (limit to top 20 to avoid cardinality explosion).
|
||||
arsort( $hook_counts );
|
||||
$hook_counts = array_slice( $hook_counts, 0, 20, true );
|
||||
foreach ( $hook_counts as $hook => $count ) {
|
||||
$events_gauge->set( $count, array( $hook ) );
|
||||
}
|
||||
|
||||
// Overdue events gauge.
|
||||
$overdue_gauge = $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
'cron_overdue_total',
|
||||
'Number of overdue cron events',
|
||||
array()
|
||||
);
|
||||
$overdue_gauge->set( $overdue_count, array() );
|
||||
|
||||
// Next run timestamp.
|
||||
$next_run_gauge = $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
'cron_next_run_timestamp',
|
||||
'Unix timestamp of next scheduled cron event',
|
||||
array()
|
||||
);
|
||||
if ( $next_run !== PHP_INT_MAX ) {
|
||||
$next_run_gauge->set( $next_run, array() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect transient metrics.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function collect_transient_metrics(): void {
|
||||
global $wpdb;
|
||||
|
||||
// Count all transients.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$transient_count = $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE '_transient_%' AND option_name NOT LIKE '_transient_timeout_%'"
|
||||
);
|
||||
|
||||
// Count transients with expiration.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$expiring_count = $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_%'"
|
||||
);
|
||||
|
||||
// Count expired transients.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$expired_count = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_%%' AND option_value < %d",
|
||||
time()
|
||||
)
|
||||
);
|
||||
|
||||
// Transients total gauge.
|
||||
$transients_gauge = $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
'transients_total',
|
||||
'Total number of transients in database',
|
||||
array( 'type' )
|
||||
);
|
||||
|
||||
$transients_gauge->set( (int) $transient_count, array( 'total' ) );
|
||||
$transients_gauge->set( (int) $expiring_count, array( 'with_expiration' ) );
|
||||
$transients_gauge->set( (int) $transient_count - (int) $expiring_count, array( 'persistent' ) );
|
||||
$transients_gauge->set( (int) $expired_count, array( 'expired' ) );
|
||||
|
||||
// Site transients (for multisite).
|
||||
if ( is_multisite() ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$site_transient_count = $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM {$wpdb->sitemeta} WHERE meta_key LIKE '_site_transient_%' AND meta_key NOT LIKE '_site_transient_timeout_%'"
|
||||
);
|
||||
|
||||
$transients_gauge->set( (int) $site_transient_count, array( 'site_transients' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if WooCommerce is active.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_woocommerce_active(): bool {
|
||||
return class_exists( 'WooCommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect WooCommerce metrics.
|
||||
*
|
||||
* @param array $enabled_metrics List of enabled metrics.
|
||||
* @return void
|
||||
*/
|
||||
private function collect_woocommerce_metrics( array $enabled_metrics ): void {
|
||||
// Products total.
|
||||
if ( in_array( 'wordpress_woocommerce_products_total', $enabled_metrics, true ) ) {
|
||||
$this->collect_woocommerce_products();
|
||||
}
|
||||
|
||||
// Orders total.
|
||||
if ( in_array( 'wordpress_woocommerce_orders_total', $enabled_metrics, true ) ) {
|
||||
$this->collect_woocommerce_orders();
|
||||
}
|
||||
|
||||
// Revenue.
|
||||
if ( in_array( 'wordpress_woocommerce_revenue_total', $enabled_metrics, true ) ) {
|
||||
$this->collect_woocommerce_revenue();
|
||||
}
|
||||
|
||||
// Customers.
|
||||
if ( in_array( 'wordpress_woocommerce_customers_total', $enabled_metrics, true ) ) {
|
||||
$this->collect_woocommerce_customers();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect WooCommerce products metrics.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function collect_woocommerce_products(): void {
|
||||
$gauge = $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
'woocommerce_products_total',
|
||||
'Total number of WooCommerce products by status and type',
|
||||
array( 'status', 'type' )
|
||||
);
|
||||
|
||||
// Get product counts by status.
|
||||
$product_counts = wp_count_posts( 'product' );
|
||||
$product_types = wc_get_product_types();
|
||||
|
||||
foreach ( get_object_vars( $product_counts ) as $status => $count ) {
|
||||
if ( (int) $count > 0 ) {
|
||||
$gauge->set( (int) $count, array( $status, 'all' ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Count by product type (for published products only).
|
||||
foreach ( array_keys( $product_types ) as $type ) {
|
||||
$args = array(
|
||||
'status' => 'publish',
|
||||
'type' => $type,
|
||||
'limit' => -1,
|
||||
'return' => 'ids',
|
||||
);
|
||||
$products = wc_get_products( $args );
|
||||
$gauge->set( count( $products ), array( 'publish', $type ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect WooCommerce orders metrics.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function collect_woocommerce_orders(): void {
|
||||
$gauge = $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
'woocommerce_orders_total',
|
||||
'Total number of WooCommerce orders by status',
|
||||
array( 'status' )
|
||||
);
|
||||
|
||||
// Get all registered order statuses and count each.
|
||||
$statuses = wc_get_order_statuses();
|
||||
|
||||
foreach ( array_keys( $statuses ) as $status ) {
|
||||
// Remove 'wc-' prefix for the label.
|
||||
$status_label = str_replace( 'wc-', '', $status );
|
||||
$count = wc_orders_count( $status );
|
||||
$gauge->set( (int) $count, array( $status_label ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect WooCommerce revenue metrics.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function collect_woocommerce_revenue(): void {
|
||||
global $wpdb;
|
||||
|
||||
$gauge = $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
'woocommerce_revenue_total',
|
||||
'Total WooCommerce revenue',
|
||||
array( 'period', 'currency' )
|
||||
);
|
||||
|
||||
$currency = get_woocommerce_currency();
|
||||
|
||||
// Check if HPOS (High-Performance Order Storage) is enabled.
|
||||
$hpos_enabled = class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' )
|
||||
&& \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled();
|
||||
|
||||
if ( $hpos_enabled ) {
|
||||
$orders_table = $wpdb->prefix . 'wc_orders';
|
||||
|
||||
// Total revenue (all time) - completed and processing orders.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$total_revenue = $wpdb->get_var(
|
||||
"SELECT SUM(total_amount) FROM {$orders_table} WHERE status IN ('wc-completed', 'wc-processing')"
|
||||
);
|
||||
|
||||
// Today's revenue.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$today_revenue = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT SUM(total_amount) FROM {$orders_table} WHERE status IN ('wc-completed', 'wc-processing') AND DATE(date_created_gmt) = %s",
|
||||
gmdate( 'Y-m-d' )
|
||||
)
|
||||
);
|
||||
|
||||
// This month's revenue.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$month_revenue = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT SUM(total_amount) FROM {$orders_table} WHERE status IN ('wc-completed', 'wc-processing') AND YEAR(date_created_gmt) = %d AND MONTH(date_created_gmt) = %d",
|
||||
gmdate( 'Y' ),
|
||||
gmdate( 'm' )
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Legacy post-based orders.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$total_revenue = $wpdb->get_var(
|
||||
"SELECT SUM(meta_value) FROM {$wpdb->postmeta} pm
|
||||
JOIN {$wpdb->posts} p ON p.ID = pm.post_id
|
||||
WHERE pm.meta_key = '_order_total'
|
||||
AND p.post_type = 'shop_order'
|
||||
AND p.post_status IN ('wc-completed', 'wc-processing')"
|
||||
);
|
||||
|
||||
// Today's revenue.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$today_revenue = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT SUM(meta_value) FROM {$wpdb->postmeta} pm
|
||||
JOIN {$wpdb->posts} p ON p.ID = pm.post_id
|
||||
WHERE pm.meta_key = '_order_total'
|
||||
AND p.post_type = 'shop_order'
|
||||
AND p.post_status IN ('wc-completed', 'wc-processing')
|
||||
AND DATE(p.post_date_gmt) = %s",
|
||||
gmdate( 'Y-m-d' )
|
||||
)
|
||||
);
|
||||
|
||||
// This month's revenue.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$month_revenue = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT SUM(meta_value) FROM {$wpdb->postmeta} pm
|
||||
JOIN {$wpdb->posts} p ON p.ID = pm.post_id
|
||||
WHERE pm.meta_key = '_order_total'
|
||||
AND p.post_type = 'shop_order'
|
||||
AND p.post_status IN ('wc-completed', 'wc-processing')
|
||||
AND YEAR(p.post_date_gmt) = %d
|
||||
AND MONTH(p.post_date_gmt) = %d",
|
||||
gmdate( 'Y' ),
|
||||
gmdate( 'm' )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$gauge->set( (float) ( $total_revenue ?? 0 ), array( 'all_time', $currency ) );
|
||||
$gauge->set( (float) ( $today_revenue ?? 0 ), array( 'today', $currency ) );
|
||||
$gauge->set( (float) ( $month_revenue ?? 0 ), array( 'month', $currency ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect WooCommerce customers metrics.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function collect_woocommerce_customers(): void {
|
||||
$gauge = $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
'woocommerce_customers_total',
|
||||
'Total number of WooCommerce customers',
|
||||
array( 'type' )
|
||||
);
|
||||
|
||||
// Count users with customer role.
|
||||
$customer_count = count_users();
|
||||
$customers = $customer_count['avail_roles']['customer'] ?? 0;
|
||||
|
||||
$gauge->set( $customers, array( 'registered' ) );
|
||||
|
||||
// Count guest orders (orders without user_id).
|
||||
global $wpdb;
|
||||
|
||||
// Check if HPOS is enabled.
|
||||
$hpos_enabled = class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' )
|
||||
&& \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled();
|
||||
|
||||
if ( $hpos_enabled ) {
|
||||
$orders_table = $wpdb->prefix . 'wc_orders';
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$guest_orders = $wpdb->get_var(
|
||||
"SELECT COUNT(DISTINCT billing_email) FROM {$orders_table} WHERE customer_id = 0 AND billing_email != ''"
|
||||
);
|
||||
} else {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$guest_orders = $wpdb->get_var(
|
||||
"SELECT COUNT(DISTINCT pm.meta_value) FROM {$wpdb->postmeta} pm
|
||||
JOIN {$wpdb->posts} p ON p.ID = pm.post_id
|
||||
LEFT JOIN {$wpdb->postmeta} pm2 ON pm2.post_id = p.ID AND pm2.meta_key = '_customer_user'
|
||||
WHERE pm.meta_key = '_billing_email'
|
||||
AND p.post_type = 'shop_order'
|
||||
AND (pm2.meta_value = '0' OR pm2.meta_value IS NULL)"
|
||||
);
|
||||
}
|
||||
|
||||
$gauge->set( (int) $guest_orders, array( 'guest' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect runtime metrics from stored data.
|
||||
*
|
||||
* @param array $enabled_metrics List of enabled metrics.
|
||||
* @return void
|
||||
*/
|
||||
private function collect_runtime_metrics( array $enabled_metrics ): void {
|
||||
$runtime_collector = RuntimeCollector::get_instance();
|
||||
$stored_metrics = $runtime_collector->get_stored_metrics();
|
||||
|
||||
// HTTP requests total counter.
|
||||
if ( in_array( 'wordpress_http_requests_total', $enabled_metrics, true ) && ! empty( $stored_metrics['counters'] ) ) {
|
||||
foreach ( $stored_metrics['counters'] as $counter_data ) {
|
||||
if ( 'http_requests_total' !== $counter_data['name'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$counter = $this->registry->getOrRegisterCounter(
|
||||
$this->namespace,
|
||||
'http_requests_total',
|
||||
'Total number of HTTP requests',
|
||||
array( 'method', 'status', 'endpoint' )
|
||||
);
|
||||
|
||||
$counter->incBy(
|
||||
(int) $counter_data['value'],
|
||||
array(
|
||||
$counter_data['labels']['method'] ?? 'GET',
|
||||
$counter_data['labels']['status'] ?? '200',
|
||||
$counter_data['labels']['endpoint'] ?? 'unknown',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request duration histogram.
|
||||
if ( in_array( 'wordpress_http_request_duration_seconds', $enabled_metrics, true ) && ! empty( $stored_metrics['histograms'] ) ) {
|
||||
foreach ( $stored_metrics['histograms'] as $histogram_data ) {
|
||||
if ( 'http_request_duration_seconds' !== $histogram_data['name'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// For histograms, we expose as a gauge with pre-aggregated bucket counts.
|
||||
// This is a workaround since we can't directly populate histogram buckets.
|
||||
$this->expose_histogram_as_gauges(
|
||||
'http_request_duration_seconds',
|
||||
'HTTP request duration in seconds',
|
||||
$histogram_data,
|
||||
array( 'method', 'endpoint' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Database queries total counter.
|
||||
if ( in_array( 'wordpress_db_queries_total', $enabled_metrics, true ) && ! empty( $stored_metrics['counters'] ) ) {
|
||||
foreach ( $stored_metrics['counters'] as $counter_data ) {
|
||||
if ( 'db_queries_total' !== $counter_data['name'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$counter = $this->registry->getOrRegisterCounter(
|
||||
$this->namespace,
|
||||
'db_queries_total',
|
||||
'Total number of database queries',
|
||||
array( 'endpoint' )
|
||||
);
|
||||
|
||||
$counter->incBy(
|
||||
(int) $counter_data['value'],
|
||||
array(
|
||||
$counter_data['labels']['endpoint'] ?? 'unknown',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Database query duration histogram (if SAVEQUERIES is enabled).
|
||||
if ( in_array( 'wordpress_db_queries_total', $enabled_metrics, true ) && ! empty( $stored_metrics['histograms'] ) ) {
|
||||
foreach ( $stored_metrics['histograms'] as $histogram_data ) {
|
||||
if ( 'db_query_duration_seconds' !== $histogram_data['name'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->expose_histogram_as_gauges(
|
||||
'db_query_duration_seconds',
|
||||
'Database query duration in seconds',
|
||||
$histogram_data,
|
||||
array( 'endpoint' )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose pre-aggregated histogram data as gauge metrics.
|
||||
*
|
||||
* Since we store histogram data externally, we expose it using gauges
|
||||
* that follow Prometheus histogram naming conventions.
|
||||
*
|
||||
* @param string $name Metric name.
|
||||
* @param string $help Metric description.
|
||||
* @param array $histogram_data Stored histogram data.
|
||||
* @param array $label_names Label names.
|
||||
* @return void
|
||||
*/
|
||||
private function expose_histogram_as_gauges( string $name, string $help, array $histogram_data, array $label_names ): void {
|
||||
$label_values = array();
|
||||
foreach ( $label_names as $label_name ) {
|
||||
$label_values[] = $histogram_data['labels'][ $label_name ] ?? 'unknown';
|
||||
}
|
||||
|
||||
// Expose bucket counts.
|
||||
$bucket_gauge = $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
$name . '_bucket',
|
||||
$help . ' (bucket)',
|
||||
array_merge( $label_names, array( 'le' ) )
|
||||
);
|
||||
|
||||
$cumulative_count = 0;
|
||||
foreach ( $histogram_data['buckets'] as $le => $count ) {
|
||||
$cumulative_count += $count;
|
||||
$bucket_gauge->set(
|
||||
$cumulative_count,
|
||||
array_merge( $label_values, array( $le ) )
|
||||
);
|
||||
}
|
||||
|
||||
// Expose sum.
|
||||
$sum_gauge = $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
$name . '_sum',
|
||||
$help . ' (sum)',
|
||||
$label_names
|
||||
);
|
||||
$sum_gauge->set( $histogram_data['sum'], $label_values );
|
||||
|
||||
// Expose count.
|
||||
$count_gauge = $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
$name . '_count',
|
||||
$help . ' (count)',
|
||||
$label_names
|
||||
);
|
||||
$count_gauge->set( $histogram_data['count'], $label_values );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom gauge metric.
|
||||
*
|
||||
|
||||
371
src/Metrics/RuntimeCollector.php
Normal file
371
src/Metrics/RuntimeCollector.php
Normal file
@@ -0,0 +1,371 @@
|
||||
<?php
|
||||
/**
|
||||
* Runtime metrics collector class.
|
||||
*
|
||||
* @package WP_Prometheus
|
||||
*/
|
||||
|
||||
namespace Magdev\WpPrometheus\Metrics;
|
||||
|
||||
// Prevent direct file access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* RuntimeCollector class.
|
||||
*
|
||||
* Collects runtime metrics during WordPress request lifecycle.
|
||||
* Stores aggregated data for later retrieval by the Prometheus endpoint.
|
||||
*/
|
||||
class RuntimeCollector {
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*
|
||||
* @var RuntimeCollector|null
|
||||
*/
|
||||
private static ?RuntimeCollector $instance = null;
|
||||
|
||||
/**
|
||||
* Request start time.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
private float $request_start_time;
|
||||
|
||||
/**
|
||||
* Option name for stored metrics.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private const OPTION_NAME = 'wp_prometheus_runtime_metrics';
|
||||
|
||||
/**
|
||||
* Histogram buckets for request duration (in seconds).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private const DURATION_BUCKETS = array( 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10 );
|
||||
|
||||
/**
|
||||
* Get singleton instance.
|
||||
*
|
||||
* @return RuntimeCollector
|
||||
*/
|
||||
public static function get_instance(): RuntimeCollector {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
*/
|
||||
private function __construct() {
|
||||
$this->request_start_time = microtime( true );
|
||||
$this->init_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize WordPress hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function init_hooks(): void {
|
||||
// Record metrics at the end of the request.
|
||||
add_action( 'shutdown', array( $this, 'record_request_metrics' ), 9999 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Record request metrics at shutdown.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function record_request_metrics(): void {
|
||||
// Skip metrics endpoint requests to avoid self-referential metrics.
|
||||
if ( $this->is_metrics_request() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip AJAX requests for license validation etc. from this plugin.
|
||||
if ( $this->is_plugin_ajax_request() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$enabled_metrics = get_option( 'wp_prometheus_enabled_metrics', array() );
|
||||
$metrics = $this->get_stored_metrics();
|
||||
$duration = microtime( true ) - $this->request_start_time;
|
||||
$status_code = http_response_code() ?: 200;
|
||||
$method = isset( $_SERVER['REQUEST_METHOD'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : 'GET';
|
||||
$endpoint = $this->get_normalized_endpoint();
|
||||
|
||||
// Record HTTP request count and duration.
|
||||
if ( in_array( 'wordpress_http_requests_total', $enabled_metrics, true ) ) {
|
||||
$this->increment_counter(
|
||||
$metrics,
|
||||
'http_requests_total',
|
||||
array(
|
||||
'method' => $method,
|
||||
'status' => (string) $status_code,
|
||||
'endpoint' => $endpoint,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Record request duration histogram.
|
||||
if ( in_array( 'wordpress_http_request_duration_seconds', $enabled_metrics, true ) ) {
|
||||
$this->observe_histogram(
|
||||
$metrics,
|
||||
'http_request_duration_seconds',
|
||||
$duration,
|
||||
array(
|
||||
'method' => $method,
|
||||
'endpoint' => $endpoint,
|
||||
),
|
||||
self::DURATION_BUCKETS
|
||||
);
|
||||
}
|
||||
|
||||
// Record database query metrics.
|
||||
if ( in_array( 'wordpress_db_queries_total', $enabled_metrics, true ) ) {
|
||||
global $wpdb;
|
||||
$query_count = $wpdb->num_queries;
|
||||
|
||||
$this->increment_counter(
|
||||
$metrics,
|
||||
'db_queries_total',
|
||||
array( 'endpoint' => $endpoint ),
|
||||
$query_count
|
||||
);
|
||||
|
||||
// Track query time if SAVEQUERIES is enabled.
|
||||
if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES && ! empty( $wpdb->queries ) ) {
|
||||
$total_query_time = 0;
|
||||
foreach ( $wpdb->queries as $query ) {
|
||||
$total_query_time += $query[1]; // Query time is the second element.
|
||||
}
|
||||
|
||||
$this->observe_histogram(
|
||||
$metrics,
|
||||
'db_query_duration_seconds',
|
||||
$total_query_time,
|
||||
array( 'endpoint' => $endpoint ),
|
||||
self::DURATION_BUCKETS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->save_stored_metrics( $metrics );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current request is for the metrics endpoint.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_metrics_request(): bool {
|
||||
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
|
||||
return strpos( $request_uri, '/metrics' ) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current request is a plugin AJAX request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_plugin_ajax_request(): bool {
|
||||
if ( ! wp_doing_ajax() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$action = isset( $_REQUEST['action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : '';
|
||||
return strpos( $action, 'wp_prometheus_' ) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get normalized endpoint for labeling.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_normalized_endpoint(): string {
|
||||
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '/';
|
||||
|
||||
// Remove query string.
|
||||
$path = strtok( $request_uri, '?' );
|
||||
|
||||
// Normalize common patterns.
|
||||
if ( is_admin() ) {
|
||||
return 'admin';
|
||||
}
|
||||
|
||||
if ( wp_doing_ajax() ) {
|
||||
return 'ajax';
|
||||
}
|
||||
|
||||
if ( wp_doing_cron() ) {
|
||||
return 'cron';
|
||||
}
|
||||
|
||||
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
|
||||
return 'rest-api';
|
||||
}
|
||||
|
||||
if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) {
|
||||
return 'xmlrpc';
|
||||
}
|
||||
|
||||
// Check for common WordPress patterns.
|
||||
if ( preg_match( '#^/wp-json/#', $path ) ) {
|
||||
return 'rest-api';
|
||||
}
|
||||
|
||||
if ( preg_match( '#^/wp-login\.php#', $path ) ) {
|
||||
return 'login';
|
||||
}
|
||||
|
||||
if ( preg_match( '#^/wp-cron\.php#', $path ) ) {
|
||||
return 'cron';
|
||||
}
|
||||
|
||||
if ( preg_match( '#^/feed/#', $path ) || preg_match( '#/feed/?$#', $path ) ) {
|
||||
return 'feed';
|
||||
}
|
||||
|
||||
// Generic frontend.
|
||||
return 'frontend';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stored metrics from database.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_stored_metrics(): array {
|
||||
$metrics = get_option( self::OPTION_NAME, array() );
|
||||
|
||||
if ( ! is_array( $metrics ) ) {
|
||||
return array(
|
||||
'counters' => array(),
|
||||
'histograms' => array(),
|
||||
'last_reset' => time(),
|
||||
);
|
||||
}
|
||||
|
||||
return $metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save stored metrics to database.
|
||||
*
|
||||
* @param array $metrics Metrics data.
|
||||
* @return void
|
||||
*/
|
||||
private function save_stored_metrics( array $metrics ): void {
|
||||
update_option( self::OPTION_NAME, $metrics, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment a counter metric.
|
||||
*
|
||||
* @param array $metrics Reference to metrics array.
|
||||
* @param string $name Counter name.
|
||||
* @param array $labels Label values.
|
||||
* @param int $increment Amount to increment (default 1).
|
||||
* @return void
|
||||
*/
|
||||
private function increment_counter( array &$metrics, string $name, array $labels, int $increment = 1 ): void {
|
||||
if ( ! isset( $metrics['counters'] ) ) {
|
||||
$metrics['counters'] = array();
|
||||
}
|
||||
|
||||
$key = $this->make_label_key( $name, $labels );
|
||||
|
||||
if ( ! isset( $metrics['counters'][ $key ] ) ) {
|
||||
$metrics['counters'][ $key ] = array(
|
||||
'name' => $name,
|
||||
'labels' => $labels,
|
||||
'value' => 0,
|
||||
);
|
||||
}
|
||||
|
||||
$metrics['counters'][ $key ]['value'] += $increment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Observe a value in a histogram metric.
|
||||
*
|
||||
* @param array $metrics Reference to metrics array.
|
||||
* @param string $name Histogram name.
|
||||
* @param float $value Observed value.
|
||||
* @param array $labels Label values.
|
||||
* @param array $buckets Bucket boundaries.
|
||||
* @return void
|
||||
*/
|
||||
private function observe_histogram( array &$metrics, string $name, float $value, array $labels, array $buckets ): void {
|
||||
if ( ! isset( $metrics['histograms'] ) ) {
|
||||
$metrics['histograms'] = array();
|
||||
}
|
||||
|
||||
$key = $this->make_label_key( $name, $labels );
|
||||
|
||||
if ( ! isset( $metrics['histograms'][ $key ] ) ) {
|
||||
$bucket_counts = array();
|
||||
foreach ( $buckets as $bucket ) {
|
||||
$bucket_counts[ (string) $bucket ] = 0;
|
||||
}
|
||||
$bucket_counts['+Inf'] = 0;
|
||||
|
||||
$metrics['histograms'][ $key ] = array(
|
||||
'name' => $name,
|
||||
'labels' => $labels,
|
||||
'buckets' => $bucket_counts,
|
||||
'sum' => 0.0,
|
||||
'count' => 0,
|
||||
);
|
||||
}
|
||||
|
||||
// Increment bucket counts.
|
||||
foreach ( $buckets as $bucket ) {
|
||||
if ( $value <= $bucket ) {
|
||||
$metrics['histograms'][ $key ]['buckets'][ (string) $bucket ]++;
|
||||
}
|
||||
}
|
||||
$metrics['histograms'][ $key ]['buckets']['+Inf']++;
|
||||
|
||||
// Update sum and count.
|
||||
$metrics['histograms'][ $key ]['sum'] += $value;
|
||||
$metrics['histograms'][ $key ]['count']++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a unique key from name and labels.
|
||||
*
|
||||
* @param string $name Metric name.
|
||||
* @param array $labels Label values.
|
||||
* @return string
|
||||
*/
|
||||
private function make_label_key( string $name, array $labels ): string {
|
||||
ksort( $labels );
|
||||
return $name . ':' . wp_json_encode( $labels );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset stored metrics.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reset_metrics(): void {
|
||||
delete_option( self::OPTION_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get histogram buckets.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_duration_buckets(): array {
|
||||
return self::DURATION_BUCKETS;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use Magdev\WpPrometheus\Admin\Settings;
|
||||
use Magdev\WpPrometheus\Endpoint\MetricsEndpoint;
|
||||
use Magdev\WpPrometheus\License\Manager as LicenseManager;
|
||||
use Magdev\WpPrometheus\Metrics\Collector;
|
||||
use Magdev\WpPrometheus\Metrics\RuntimeCollector;
|
||||
|
||||
// Prevent direct file access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
@@ -90,6 +91,20 @@ final class Plugin {
|
||||
new Settings();
|
||||
}
|
||||
|
||||
// Initialize runtime collector for request metrics (always runs to collect data).
|
||||
// Only collect if at least one runtime metric is enabled.
|
||||
$enabled_metrics = get_option( 'wp_prometheus_enabled_metrics', array() );
|
||||
$runtime_metrics = array(
|
||||
'wordpress_http_requests_total',
|
||||
'wordpress_http_request_duration_seconds',
|
||||
'wordpress_db_queries_total',
|
||||
);
|
||||
$has_runtime_metrics = ! empty( array_intersect( $runtime_metrics, $enabled_metrics ) );
|
||||
|
||||
if ( $has_runtime_metrics && LicenseManager::is_license_valid() ) {
|
||||
RuntimeCollector::get_instance();
|
||||
}
|
||||
|
||||
// Initialize metrics endpoint (only if licensed).
|
||||
if ( LicenseManager::is_license_valid() ) {
|
||||
$this->collector = new Collector();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Plugin Name: WP Prometheus
|
||||
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wp-prometheus
|
||||
* Description: Prometheus metrics endpoint for WordPress with extensible hooks for custom metrics.
|
||||
* Version: 0.0.1
|
||||
* Version: 0.2.2
|
||||
* Requires at least: 6.4
|
||||
* Requires PHP: 8.3
|
||||
* Author: Marco Graetsch
|
||||
@@ -26,7 +26,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
define( 'WP_PROMETHEUS_VERSION', '0.0.1' );
|
||||
define( 'WP_PROMETHEUS_VERSION', '0.2.2' );
|
||||
|
||||
/**
|
||||
* Plugin file path.
|
||||
|
||||
Reference in New Issue
Block a user