You've already forked wp-prometheus
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bc108f6bd5 | |||
| 6256ba777c | |||
| f1748727ce | |||
| 71f87b320a | |||
| 4a676aa195 | |||
| 898d711b2f | |||
| 9b100c5f45 | |||
| 8c24dac85c |
@@ -161,6 +161,20 @@ jobs:
|
|||||||
# Read release notes
|
# Read release notes
|
||||||
BODY=$(cat release_notes.txt)
|
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
|
# Create release via Gitea API
|
||||||
RELEASE_RESPONSE=$(curl -s -X POST \
|
RELEASE_RESPONSE=$(curl -s -X POST \
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
|||||||
40
CHANGELOG.md
40
CHANGELOG.md
@@ -5,6 +5,46 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [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
|
## [0.0.1] - 2026-02-01
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
57
CLAUDE.md
57
CLAUDE.md
@@ -14,6 +14,7 @@ This plugin provides a Prometheus `/metrics` endpoint and an extensible way to a
|
|||||||
|
|
||||||
- Prometheus compatible authenticated `/metrics` endpoint
|
- Prometheus compatible authenticated `/metrics` endpoint
|
||||||
- Optional default metrics (users, posts, comments, plugins)
|
- Optional default metrics (users, posts, comments, plugins)
|
||||||
|
- Runtime metrics (HTTP requests, request duration, database queries)
|
||||||
- Dedicated plugin settings under 'Settings/Metrics' menu
|
- Dedicated plugin settings under 'Settings/Metrics' menu
|
||||||
- Extensible by other plugins using `wp_prometheus_collect_metrics` action hook
|
- Extensible by other plugins using `wp_prometheus_collect_metrics` action hook
|
||||||
- License management integration
|
- License management integration
|
||||||
@@ -26,11 +27,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.
|
**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.2.0 (Planned)
|
||||||
|
|
||||||
- Add request/response timing metrics
|
- WooCommerce integration metrics
|
||||||
- Add HTTP status code counters
|
- Cron job metrics
|
||||||
- Add database query metrics
|
- Transient cache metrics
|
||||||
|
|
||||||
## Technical Stack
|
## Technical Stack
|
||||||
|
|
||||||
@@ -224,7 +225,8 @@ wp-prometheus/
|
|||||||
│ ├── License/
|
│ ├── License/
|
||||||
│ │ └── Manager.php # License management
|
│ │ └── Manager.php # License management
|
||||||
│ ├── Metrics/
|
│ ├── Metrics/
|
||||||
│ │ └── Collector.php # Prometheus metrics collector
|
│ │ ├── Collector.php # Prometheus metrics collector
|
||||||
|
│ │ └── RuntimeCollector.php # Runtime metrics collector
|
||||||
│ ├── Installer.php # Activation/Deactivation
|
│ ├── Installer.php # Activation/Deactivation
|
||||||
│ ├── Plugin.php # Main plugin class
|
│ ├── Plugin.php # Main plugin class
|
||||||
│ └── index.php
|
│ └── index.php
|
||||||
@@ -280,6 +282,51 @@ add_action( 'wp_prometheus_collect_metrics', function( $collector ) {
|
|||||||
|
|
||||||
## Session History
|
## Session History
|
||||||
|
|
||||||
|
### 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)
|
### 2026-02-01 - Initial Setup (v0.0.1)
|
||||||
|
|
||||||
- Created initial plugin structure based on wp-fedistream blueprint
|
- Created initial plugin structure based on wp-fedistream blueprint
|
||||||
|
|||||||
17
PLAN.md
17
PLAN.md
@@ -91,6 +91,8 @@ wp-prometheus/
|
|||||||
|
|
||||||
The plugin provides the following default metrics (can be toggled in settings):
|
The plugin provides the following default metrics (can be toggled in settings):
|
||||||
|
|
||||||
|
### Static Metrics
|
||||||
|
|
||||||
| Metric | Type | Labels | Description |
|
| Metric | Type | Labels | Description |
|
||||||
|--------|------|--------|-------------|
|
|--------|------|--------|-------------|
|
||||||
| wordpress_info | Gauge | version, php_version, multisite | WordPress installation info |
|
| 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_comments_total | Gauge | status | Total comments by status |
|
||||||
| wordpress_plugins_total | Gauge | status | Total plugins (active/inactive) |
|
| 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
|
## Extensibility
|
||||||
|
|
||||||
### Adding Custom Metrics
|
### Adding Custom Metrics
|
||||||
@@ -150,12 +161,6 @@ https://example.com/metrics/?token=your-auth-token
|
|||||||
|
|
||||||
## Future Enhancements
|
## Future Enhancements
|
||||||
|
|
||||||
### Version 0.1.0
|
|
||||||
|
|
||||||
- Request/Response timing metrics
|
|
||||||
- HTTP status code counters
|
|
||||||
- Database query metrics
|
|
||||||
|
|
||||||
### Version 0.2.0
|
### Version 0.2.0
|
||||||
|
|
||||||
- WooCommerce integration metrics
|
- WooCommerce integration metrics
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -71,6 +71,8 @@ scrape_configs:
|
|||||||
|
|
||||||
## Default Metrics
|
## Default Metrics
|
||||||
|
|
||||||
|
### Static Metrics
|
||||||
|
|
||||||
| Metric | Type | Labels | Description |
|
| Metric | Type | Labels | Description |
|
||||||
|--------|------|--------|-------------|
|
|--------|------|--------|-------------|
|
||||||
| wordpress_info | Gauge | version, php_version, multisite | WordPress installation info |
|
| wordpress_info | Gauge | version, php_version, multisite | WordPress installation info |
|
||||||
@@ -79,6 +81,17 @@ scrape_configs:
|
|||||||
| wordpress_comments_total | Gauge | status | Total comments by status |
|
| wordpress_comments_total | Gauge | status | Total comments by status |
|
||||||
| wordpress_plugins_total | Gauge | status | Total plugins (active/inactive) |
|
| 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.
|
||||||
|
|
||||||
## Extending with Custom Metrics
|
## Extending with Custom Metrics
|
||||||
|
|
||||||
Add your own metrics using the `wp_prometheus_collect_metrics` action:
|
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": [
|
"repositories": [
|
||||||
{
|
{
|
||||||
"type": "path",
|
"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": {
|
"require": {
|
||||||
"php": ">=8.3",
|
"php": ">=8.3",
|
||||||
"magdev/wc-licensed-product-client": "dev-main",
|
"magdev/wc-licensed-product-client": "^0.2.2",
|
||||||
"promphp/prometheus_client_php": "^2.10"
|
"promphp/prometheus_client_php": "^2.10"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
@@ -56,6 +62,6 @@
|
|||||||
"phpcbf": "phpcbf",
|
"phpcbf": "phpcbf",
|
||||||
"test": "phpunit"
|
"test": "phpunit"
|
||||||
},
|
},
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "stable",
|
||||||
"prefer-stable": true
|
"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.
198
languages/wp-prometheus-de_CH.po
Normal file
198
languages/wp-prometheus-de_CH.po
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# 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.1.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:40
|
||||||
|
msgid "Metrics Settings"
|
||||||
|
msgstr "Metriken-Einstellungen"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:41
|
||||||
|
msgid "Metrics"
|
||||||
|
msgstr "Metriken"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:58
|
||||||
|
msgid "License Settings"
|
||||||
|
msgstr "Lizenz-Einstellungen"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:65
|
||||||
|
msgid "Authentication"
|
||||||
|
msgstr "Authentifizierung"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:73
|
||||||
|
msgid "Default Metrics"
|
||||||
|
msgstr "Standard-Metriken"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:150
|
||||||
|
msgid "License settings saved."
|
||||||
|
msgstr "Lizenz-Einstellungen gespeichert."
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:195
|
||||||
|
msgid "License is active and valid."
|
||||||
|
msgstr "Lizenz ist aktiv und gueltig."
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:196
|
||||||
|
msgid "License is invalid."
|
||||||
|
msgstr "Lizenz ist ungueltig."
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:197
|
||||||
|
msgid "License has expired."
|
||||||
|
msgstr "Lizenz ist abgelaufen."
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:198
|
||||||
|
msgid "License has been revoked."
|
||||||
|
msgstr "Lizenz wurde widerrufen."
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:199
|
||||||
|
msgid "License is inactive."
|
||||||
|
msgstr "Lizenz ist inaktiv."
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:200
|
||||||
|
msgid "License has not been validated yet."
|
||||||
|
msgstr "Lizenz wurde noch nicht validiert."
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:201
|
||||||
|
msgid "License server is not configured."
|
||||||
|
msgstr "Lizenz-Server ist nicht konfiguriert."
|
||||||
|
|
||||||
|
#. translators: %s: Expiration date
|
||||||
|
#: src/Admin/Settings.php:214
|
||||||
|
msgid "Expires: %s"
|
||||||
|
msgstr "Laeuft ab: %s"
|
||||||
|
|
||||||
|
#. translators: %s: Time ago
|
||||||
|
#: src/Admin/Settings.php:225
|
||||||
|
msgid "Last checked: %s ago"
|
||||||
|
msgstr "Zuletzt geprueft: vor %s"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:239
|
||||||
|
msgid "License Server URL"
|
||||||
|
msgstr "Lizenz-Server URL"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:249
|
||||||
|
msgid "License Key"
|
||||||
|
msgstr "Lizenzschluessel"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:259
|
||||||
|
msgid "Server Secret"
|
||||||
|
msgstr "Server-Geheimnis"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:264
|
||||||
|
msgid "Leave empty to keep existing."
|
||||||
|
msgstr "Leer lassen, um bestehenden Wert zu behalten."
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:270
|
||||||
|
msgid "Save License Settings"
|
||||||
|
msgstr "Lizenz-Einstellungen speichern"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:272
|
||||||
|
msgid "Validate License"
|
||||||
|
msgstr "Lizenz validieren"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:275
|
||||||
|
msgid "Activate License"
|
||||||
|
msgstr "Lizenz aktivieren"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:301
|
||||||
|
msgid "Configure authentication for the /metrics endpoint."
|
||||||
|
msgstr "Authentifizierung fuer den /metrics-Endpunkt konfigurieren."
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:310
|
||||||
|
msgid "Select which default metrics to expose."
|
||||||
|
msgstr "Waehlen Sie, welche Standard-Metriken bereitgestellt werden sollen."
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:324
|
||||||
|
msgid "Regenerate"
|
||||||
|
msgstr "Neu generieren"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:327
|
||||||
|
msgid "Use this token to authenticate Prometheus scrape requests."
|
||||||
|
msgstr "Verwenden Sie diesen Token zur Authentifizierung von Prometheus-Abfragen."
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:340
|
||||||
|
msgid "WordPress Info (version, PHP version, multisite)"
|
||||||
|
msgstr "WordPress-Info (Version, PHP-Version, Multisite)"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:341
|
||||||
|
msgid "Total Users by Role"
|
||||||
|
msgstr "Benutzer nach Rolle"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:342
|
||||||
|
msgid "Total Posts by Type and Status"
|
||||||
|
msgstr "Beitraege nach Typ und Status"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:343
|
||||||
|
msgid "Total Comments by Status"
|
||||||
|
msgstr "Kommentare nach Status"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:344
|
||||||
|
msgid "Total Plugins (active/inactive)"
|
||||||
|
msgstr "Plugins (aktiv/inaktiv)"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:345
|
||||||
|
msgid "HTTP Requests Total (by method, status, endpoint)"
|
||||||
|
msgstr "HTTP-Anfragen gesamt (nach Methode, Status, Endpunkt)"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:346
|
||||||
|
msgid "HTTP Request Duration (histogram)"
|
||||||
|
msgstr "HTTP-Anfragedauer (Histogramm)"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:347
|
||||||
|
msgid "Database Queries Total (by endpoint)"
|
||||||
|
msgstr "Datenbank-Abfragen gesamt (nach Endpunkt)"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:369
|
||||||
|
msgid "Prometheus Configuration"
|
||||||
|
msgstr "Prometheus-Konfiguration"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:370
|
||||||
|
msgid "Add the following to your prometheus.yml:"
|
||||||
|
msgstr "Fuegen Sie Folgendes zu Ihrer prometheus.yml hinzu:"
|
||||||
|
|
||||||
|
#. translators: %s: Endpoint URL
|
||||||
|
#: src/Admin/Settings.php:385
|
||||||
|
msgid "Metrics endpoint: %s"
|
||||||
|
msgstr "Metriken-Endpunkt: %s"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:93
|
||||||
|
msgid "Auth Token"
|
||||||
|
msgstr "Auth-Token"
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:101
|
||||||
|
msgid "Enabled Metrics"
|
||||||
|
msgstr "Aktivierte Metriken"
|
||||||
|
|
||||||
|
#: src/Plugin.php:120
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "Einstellungen"
|
||||||
|
|
||||||
|
#. translators: 1: Required PHP version, 2: Current PHP version
|
||||||
|
#: wp-prometheus.php:112
|
||||||
|
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:127
|
||||||
|
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:140
|
||||||
|
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:156
|
||||||
|
msgid "WP Prometheus requires PHP version %s or higher."
|
||||||
|
msgstr "WP Prometheus erfordert PHP-Version %s oder hoeher."
|
||||||
195
languages/wp-prometheus.pot
Normal file
195
languages/wp-prometheus.pot
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
# Copyright (C) 2026 Marco Graetsch
|
||||||
|
# This file is distributed under the GPL v2 or later.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: WP Prometheus 0.1.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:40
|
||||||
|
msgid "Metrics Settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:41
|
||||||
|
msgid "Metrics"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:58
|
||||||
|
msgid "License Settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:65
|
||||||
|
msgid "Authentication"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:73
|
||||||
|
msgid "Default Metrics"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:150
|
||||||
|
msgid "License settings saved."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:195
|
||||||
|
msgid "License is active and valid."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:196
|
||||||
|
msgid "License is invalid."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:197
|
||||||
|
msgid "License has expired."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:198
|
||||||
|
msgid "License has been revoked."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:199
|
||||||
|
msgid "License is inactive."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:200
|
||||||
|
msgid "License has not been validated yet."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:201
|
||||||
|
msgid "License server is not configured."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. translators: %s: Expiration date
|
||||||
|
#: src/Admin/Settings.php:214
|
||||||
|
msgid "Expires: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. translators: %s: Time ago
|
||||||
|
#: src/Admin/Settings.php:225
|
||||||
|
msgid "Last checked: %s ago"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:239
|
||||||
|
msgid "License Server URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:249
|
||||||
|
msgid "License Key"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:259
|
||||||
|
msgid "Server Secret"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:264
|
||||||
|
msgid "Leave empty to keep existing."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:270
|
||||||
|
msgid "Save License Settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:272
|
||||||
|
msgid "Validate License"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:275
|
||||||
|
msgid "Activate License"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:301
|
||||||
|
msgid "Configure authentication for the /metrics endpoint."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:310
|
||||||
|
msgid "Select which default metrics to expose."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:324
|
||||||
|
msgid "Regenerate"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:327
|
||||||
|
msgid "Use this token to authenticate Prometheus scrape requests."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:340
|
||||||
|
msgid "WordPress Info (version, PHP version, multisite)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:341
|
||||||
|
msgid "Total Users by Role"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:342
|
||||||
|
msgid "Total Posts by Type and Status"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:343
|
||||||
|
msgid "Total Comments by Status"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:344
|
||||||
|
msgid "Total Plugins (active/inactive)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:345
|
||||||
|
msgid "HTTP Requests Total (by method, status, endpoint)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:346
|
||||||
|
msgid "HTTP Request Duration (histogram)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:347
|
||||||
|
msgid "Database Queries Total (by endpoint)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:369
|
||||||
|
msgid "Prometheus Configuration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:370
|
||||||
|
msgid "Add the following to your prometheus.yml:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. translators: %s: Endpoint URL
|
||||||
|
#: src/Admin/Settings.php:385
|
||||||
|
msgid "Metrics endpoint: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:93
|
||||||
|
msgid "Auth Token"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Admin/Settings.php:101
|
||||||
|
msgid "Enabled Metrics"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Plugin.php:120
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. translators: 1: Required PHP version, 2: Current PHP version
|
||||||
|
#: wp-prometheus.php:112
|
||||||
|
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:127
|
||||||
|
msgid "WP Prometheus requires WordPress version %1$s or higher. You are running WordPress %2$s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: wp-prometheus.php:140
|
||||||
|
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:156
|
||||||
|
msgid "WP Prometheus requires PHP version %s or higher."
|
||||||
|
msgstr ""
|
||||||
@@ -21,15 +21,38 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
*/
|
*/
|
||||||
class Settings {
|
class Settings {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available tabs.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private array $tabs = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct() {
|
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_menu', array( $this, 'add_settings_page' ) );
|
||||||
add_action( 'admin_init', array( $this, 'register_settings' ) );
|
add_action( 'admin_init', array( $this, 'register_settings' ) );
|
||||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
|
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.
|
* Add settings page to admin menu.
|
||||||
*
|
*
|
||||||
@@ -51,56 +74,48 @@ class Settings {
|
|||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function register_settings(): void {
|
public function register_settings(): void {
|
||||||
// License settings section.
|
// Register settings for metrics tab.
|
||||||
add_settings_section(
|
register_setting( 'wp_prometheus_metrics_settings', 'wp_prometheus_auth_token', array(
|
||||||
'wp_prometheus_license_section',
|
'type' => 'string',
|
||||||
__( 'License Settings', 'wp-prometheus' ),
|
'sanitize_callback' => 'sanitize_text_field',
|
||||||
array( $this, 'render_license_section' ),
|
) );
|
||||||
'wp-prometheus'
|
|
||||||
);
|
register_setting( 'wp_prometheus_metrics_settings', 'wp_prometheus_enabled_metrics', array(
|
||||||
|
'type' => 'array',
|
||||||
|
'sanitize_callback' => array( $this, 'sanitize_metrics' ),
|
||||||
|
) );
|
||||||
|
|
||||||
// Auth token section.
|
// Auth token section.
|
||||||
add_settings_section(
|
add_settings_section(
|
||||||
'wp_prometheus_auth_section',
|
'wp_prometheus_auth_section',
|
||||||
__( 'Authentication', 'wp-prometheus' ),
|
__( 'Authentication', 'wp-prometheus' ),
|
||||||
array( $this, 'render_auth_section' ),
|
array( $this, 'render_auth_section' ),
|
||||||
'wp-prometheus'
|
'wp-prometheus-metrics'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Metrics section.
|
// Metrics section.
|
||||||
add_settings_section(
|
add_settings_section(
|
||||||
'wp_prometheus_metrics_section',
|
'wp_prometheus_metrics_section',
|
||||||
__( 'Default Metrics', 'wp-prometheus' ),
|
__( 'Enabled Metrics', 'wp-prometheus' ),
|
||||||
array( $this, 'render_metrics_section' ),
|
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.
|
// Auth token field.
|
||||||
add_settings_field(
|
add_settings_field(
|
||||||
'wp_prometheus_auth_token',
|
'wp_prometheus_auth_token',
|
||||||
__( 'Auth Token', 'wp-prometheus' ),
|
__( 'Auth Token', 'wp-prometheus' ),
|
||||||
array( $this, 'render_auth_token_field' ),
|
array( $this, 'render_auth_token_field' ),
|
||||||
'wp-prometheus',
|
'wp-prometheus-metrics',
|
||||||
'wp_prometheus_auth_section'
|
'wp_prometheus_auth_section'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Enabled metrics field.
|
// Enabled metrics field.
|
||||||
add_settings_field(
|
add_settings_field(
|
||||||
'wp_prometheus_enabled_metrics',
|
'wp_prometheus_enabled_metrics',
|
||||||
__( 'Enabled Metrics', 'wp-prometheus' ),
|
__( 'Select Metrics', 'wp-prometheus' ),
|
||||||
array( $this, 'render_enabled_metrics_field' ),
|
array( $this, 'render_enabled_metrics_field' ),
|
||||||
'wp-prometheus',
|
'wp-prometheus-metrics',
|
||||||
'wp_prometheus_metrics_section'
|
'wp_prometheus_metrics_section'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -116,6 +131,13 @@ class Settings {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wp_enqueue_style(
|
||||||
|
'wp-prometheus-admin',
|
||||||
|
WP_PROMETHEUS_URL . 'assets/css/admin.css',
|
||||||
|
array(),
|
||||||
|
WP_PROMETHEUS_VERSION
|
||||||
|
);
|
||||||
|
|
||||||
wp_enqueue_script(
|
wp_enqueue_script(
|
||||||
'wp-prometheus-admin',
|
'wp-prometheus-admin',
|
||||||
WP_PROMETHEUS_URL . 'assets/js/admin.js',
|
WP_PROMETHEUS_URL . 'assets/js/admin.js',
|
||||||
@@ -140,8 +162,10 @@ class Settings {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$current_tab = $this->get_current_tab();
|
||||||
|
|
||||||
// Handle license settings save.
|
// 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(
|
LicenseManager::save_settings( array(
|
||||||
'license_key' => isset( $_POST['license_key'] ) ? sanitize_text_field( wp_unslash( $_POST['license_key'] ) ) : '',
|
'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'] ) ) : '',
|
'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">
|
<div class="wrap">
|
||||||
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
|
<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
|
<?php
|
||||||
settings_fields( 'wp_prometheus_settings' );
|
switch ( $current_tab ) {
|
||||||
do_settings_sections( 'wp-prometheus' );
|
case 'license':
|
||||||
submit_button();
|
$this->render_license_tab();
|
||||||
|
break;
|
||||||
|
case 'metrics':
|
||||||
|
$this->render_metrics_tab();
|
||||||
|
break;
|
||||||
|
case 'help':
|
||||||
|
$this->render_help_tab();
|
||||||
|
break;
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
</form>
|
</div>
|
||||||
|
|
||||||
<?php $this->render_endpoint_info(); ?>
|
|
||||||
</div>
|
</div>
|
||||||
<?php
|
<?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
|
* @return void
|
||||||
*/
|
*/
|
||||||
private function render_license_form(): void {
|
private function render_license_tab(): void {
|
||||||
$license_key = LicenseManager::get_license_key();
|
$license_key = LicenseManager::get_license_key();
|
||||||
$server_url = LicenseManager::get_server_url();
|
$server_url = LicenseManager::get_server_url();
|
||||||
$license_status = LicenseManager::get_cached_status();
|
$license_status = LicenseManager::get_cached_status();
|
||||||
@@ -204,7 +265,7 @@ class Settings {
|
|||||||
$status_class = $status_classes[ $license_status ] ?? 'notice-info';
|
$status_class = $status_classes[ $license_status ] ?? 'notice-info';
|
||||||
$status_message = $status_messages[ $license_status ] ?? __( 'Unknown status.', 'wp-prometheus' );
|
$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>
|
<strong><?php echo esc_html( $status_message ); ?></strong>
|
||||||
<?php if ( 'valid' === $license_status && ! empty( $license_data['expires_at'] ) ) : ?>
|
<?php if ( 'valid' === $license_status && ! empty( $license_data['expires_at'] ) ) : ?>
|
||||||
<br><span class="description">
|
<br><span class="description">
|
||||||
@@ -233,7 +294,7 @@ class Settings {
|
|||||||
<form method="post" action="" id="wp-prometheus-license-form">
|
<form method="post" action="" id="wp-prometheus-license-form">
|
||||||
<?php wp_nonce_field( 'wp_prometheus_save_license', 'wp_prometheus_license_nonce' ); ?>
|
<?php wp_nonce_field( 'wp_prometheus_save_license', 'wp_prometheus_license_nonce' ); ?>
|
||||||
|
|
||||||
<table class="form-table">
|
<table class="form-table" role="presentation">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">
|
<th scope="row">
|
||||||
<label for="license_server_url"><?php esc_html_e( 'License Server URL', 'wp-prometheus' ); ?></label>
|
<label for="license_server_url"><?php esc_html_e( 'License Server URL', 'wp-prometheus' ); ?></label>
|
||||||
@@ -279,17 +340,127 @@ class Settings {
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div id="wp-prometheus-license-message" style="display: none; margin-top: 10px;"></div>
|
<div id="wp-prometheus-license-message" style="display: none; margin-top: 10px;"></div>
|
||||||
<hr>
|
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render license section description.
|
* Render metrics tab content.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function render_license_section(): void {
|
private function render_metrics_tab(): void {
|
||||||
// License section rendered separately in render_license_form().
|
?>
|
||||||
|
<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>
|
||||||
|
</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 +478,7 @@ class Settings {
|
|||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function render_metrics_section(): 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,7 +507,8 @@ class Settings {
|
|||||||
*/
|
*/
|
||||||
public function render_enabled_metrics_field(): void {
|
public function render_enabled_metrics_field(): void {
|
||||||
$enabled = get_option( 'wp_prometheus_enabled_metrics', array() );
|
$enabled = get_option( 'wp_prometheus_enabled_metrics', array() );
|
||||||
$metrics = array(
|
|
||||||
|
$static_metrics = array(
|
||||||
'wordpress_info' => __( 'WordPress Info (version, PHP version, multisite)', 'wp-prometheus' ),
|
'wordpress_info' => __( 'WordPress Info (version, PHP version, multisite)', 'wp-prometheus' ),
|
||||||
'wordpress_users_total' => __( 'Total Users by Role', 'wp-prometheus' ),
|
'wordpress_users_total' => __( 'Total Users by Role', 'wp-prometheus' ),
|
||||||
'wordpress_posts_total' => __( 'Total Posts by Type and Status', 'wp-prometheus' ),
|
'wordpress_posts_total' => __( 'Total Posts by Type and Status', 'wp-prometheus' ),
|
||||||
@@ -344,7 +516,17 @@ class Settings {
|
|||||||
'wordpress_plugins_total' => __( 'Total Plugins (active/inactive)', 'wp-prometheus' ),
|
'wordpress_plugins_total' => __( 'Total Plugins (active/inactive)', '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' ),
|
||||||
|
);
|
||||||
|
|
||||||
|
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;">
|
<label style="display: block; margin-bottom: 5px;">
|
||||||
<input type="checkbox" name="wp_prometheus_enabled_metrics[]"
|
<input type="checkbox" name="wp_prometheus_enabled_metrics[]"
|
||||||
@@ -354,40 +536,22 @@ class Settings {
|
|||||||
</label>
|
</label>
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 ) {
|
||||||
|
?>
|
||||||
|
<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>';
|
||||||
* 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>'
|
|
||||||
);
|
|
||||||
?>
|
|
||||||
</p>
|
|
||||||
<?php
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ final class Installer {
|
|||||||
'wp_prometheus_auth_token',
|
'wp_prometheus_auth_token',
|
||||||
'wp_prometheus_enable_default_metrics',
|
'wp_prometheus_enable_default_metrics',
|
||||||
'wp_prometheus_enabled_metrics',
|
'wp_prometheus_enabled_metrics',
|
||||||
|
'wp_prometheus_runtime_metrics',
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ( $options as $option ) {
|
foreach ( $options as $option ) {
|
||||||
|
|||||||
@@ -95,6 +95,9 @@ class Collector {
|
|||||||
$this->collect_plugins_total();
|
$this->collect_plugins_total();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect runtime metrics (HTTP requests, DB queries).
|
||||||
|
$this->collect_runtime_metrics( $enabled_metrics );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires after default metrics are collected.
|
* Fires after default metrics are collected.
|
||||||
*
|
*
|
||||||
@@ -233,6 +236,153 @@ class Collector {
|
|||||||
$gauge->set( count( $all_plugins ) - count( $active_plugins ), array( 'inactive' ) );
|
$gauge->set( count( $all_plugins ) - count( $active_plugins ), array( 'inactive' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* 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\Endpoint\MetricsEndpoint;
|
||||||
use Magdev\WpPrometheus\License\Manager as LicenseManager;
|
use Magdev\WpPrometheus\License\Manager as LicenseManager;
|
||||||
use Magdev\WpPrometheus\Metrics\Collector;
|
use Magdev\WpPrometheus\Metrics\Collector;
|
||||||
|
use Magdev\WpPrometheus\Metrics\RuntimeCollector;
|
||||||
|
|
||||||
// Prevent direct file access.
|
// Prevent direct file access.
|
||||||
if ( ! defined( 'ABSPATH' ) ) {
|
if ( ! defined( 'ABSPATH' ) ) {
|
||||||
@@ -90,6 +91,20 @@ final class Plugin {
|
|||||||
new Settings();
|
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).
|
// Initialize metrics endpoint (only if licensed).
|
||||||
if ( LicenseManager::is_license_valid() ) {
|
if ( LicenseManager::is_license_valid() ) {
|
||||||
$this->collector = new Collector();
|
$this->collector = new Collector();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: WP Prometheus
|
* Plugin Name: WP Prometheus
|
||||||
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wp-prometheus
|
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wp-prometheus
|
||||||
* Description: Prometheus metrics endpoint for WordPress with extensible hooks for custom metrics.
|
* Description: Prometheus metrics endpoint for WordPress with extensible hooks for custom metrics.
|
||||||
* Version: 0.0.1
|
* Version: 0.1.1
|
||||||
* Requires at least: 6.4
|
* Requires at least: 6.4
|
||||||
* Requires PHP: 8.3
|
* Requires PHP: 8.3
|
||||||
* Author: Marco Graetsch
|
* Author: Marco Graetsch
|
||||||
@@ -26,7 +26,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
define( 'WP_PROMETHEUS_VERSION', '0.0.1' );
|
define( 'WP_PROMETHEUS_VERSION', '0.1.1' );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin file path.
|
* Plugin file path.
|
||||||
|
|||||||
Reference in New Issue
Block a user