Files
Jonathan Miller 4cc3f5bee4
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 5s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 6s
Generic: Repo Health / Release configuration (push) Successful in 5s
Generic: Repo Health / Scripts governance (push) Successful in 5s
Generic: Repo Health / Release configuration (pull_request) Successful in 6s
Generic: Repo Health / Scripts governance (pull_request) Successful in 6s
Generic: Repo Health / Repository health (push) Successful in 14s
Generic: Repo Health / Repository health (pull_request) Successful in 12s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 44s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 49s
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Has been skipped
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been skipped
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Platform: moko-platform CI / CI Summary (pull_request) Has been cancelled
style: fix all PHPCS PSR-12 violations across 74 files (7539 → 0 errors)
- Convert tabs to spaces (3,413 violations)
- Fix line endings, trailing whitespace, brace placement
- Break lines exceeding 150-char absolute limit
- Replace heredoc tab closers with spaces
- Fix empty elseif, forbidden function calls
- Update phpcs.xml: exclude rules inappropriate for CLI scripts
  (SideEffects, MissingNamespace, MultipleClasses, HeaderOrder,
  empty catch blocks)

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-24 17:07:51 -05:00

345 lines
10 KiB
PHP

<?php
declare(strict_types=1);
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Enterprise.Metrics
* INGROUP: MokoStandards.Enterprise
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /lib/Enterprise/MetricsCollector.php
* BRIEF: Metrics collection framework
*/
/**
* Metrics Collector for MokoStandards
*
* Provides observability and monitoring capabilities:
* - Execution time tracking with timers
* - Success/failure rate monitoring
* - Counter and gauge metrics
* - Histogram support for distributions
* - Prometheus format export
* - Time series data collection
*
* Example usage:
* ```php
* $metrics = new MetricsCollector('my_service');
* $metrics->increment('requests_total');
* $metrics->setGauge('cpu_usage', 45.5);
*
* // Timing operations
* $timer = $metrics->startTimer('operation');
* // ... do work ...
* $timer->stop();
*
* // Export for monitoring
* echo $metrics->exportPrometheus();
* ```
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* @package MokoStandards\Enterprise
* @version 04.00.04
* @author MokoStandards Team
* @license GPL-3.0-or-later
*/
namespace MokoEnterprise;
use DateTime;
use DateTimeZone;
/**
* Timer class for timing operations
*/
class MetricsTimer
{
private MetricsCollector $collector;
private string $metricName;
private array $labels;
private float $startTime;
public function __construct(MetricsCollector $collector, string $metricName, array $labels = [])
{
$this->collector = $collector;
$this->metricName = $metricName;
$this->labels = $labels;
$this->startTime = microtime(true);
}
public function stop(bool $success = true): float
{
$duration = microtime(true) - $this->startTime;
$this->collector->observe($this->metricName . '_duration_seconds', $duration, $this->labels);
if ($success) {
$this->collector->increment($this->metricName . '_success_total', 1, $this->labels);
} else {
$this->collector->increment($this->metricName . '_failure_total', 1, $this->labels);
}
return $duration;
}
}
/**
* Metrics collector for monitoring and observability
*/
class MetricsCollector
{
private const VERSION = '04.06.00';
private string $serviceName;
private array $counters = [];
private array $gauges = [];
private array $histograms = [];
private float $startTime;
public function __construct(string $serviceName = 'mokostandards')
{
$this->serviceName = $serviceName;
$this->startTime = microtime(true);
}
/**
* Increment a counter metric
*
* @param string $metricName Name of the metric
* @param int $value Value to increment by
* @param array<string, string> $labels Optional labels for the metric
*/
public function increment(string $metricName, int $value = 1, array $labels = []): void
{
$key = $this->makeKey($metricName, $labels);
if (!isset($this->counters[$key])) {
$this->counters[$key] = 0;
}
$this->counters[$key] += $value;
}
/**
* Set a gauge metric
*
* @param string $metricName Name of the metric
* @param float $value Value to set
* @param array<string, string> $labels Optional labels for the metric
*/
public function setGauge(string $metricName, float $value, array $labels = []): void
{
$key = $this->makeKey($metricName, $labels);
$this->gauges[$key] = $value;
}
/**
* Observe a value for histogram
*
* @param string $metricName Name of the metric
* @param float $value Value to observe
* @param array<string, string> $labels Optional labels for the metric
*/
public function observe(string $metricName, float $value, array $labels = []): void
{
$key = $this->makeKey($metricName, $labels);
if (!isset($this->histograms[$key])) {
$this->histograms[$key] = [];
}
$this->histograms[$key][] = $value;
}
/**
* Start a timer for timing operations
*
* @param string $metricName Name of the metric
* @param array<string, string> $labels Optional labels for the metric
* @return MetricsTimer Timer instance
*/
public function startTimer(string $metricName, array $labels = []): MetricsTimer
{
return new MetricsTimer($this, $metricName, $labels);
}
/**
* Create a metric key with labels
*
* @param string $metricName Name of the metric
* @param array<string, string> $labels Optional labels
* @return string Metric key string
*/
private function makeKey(string $metricName, array $labels = []): string
{
if (empty($labels)) {
return $metricName;
}
ksort($labels);
$labelPairs = [];
foreach ($labels as $key => $value) {
$labelPairs[] = sprintf('%s="%s"', $key, $value);
}
return sprintf('%s{%s}', $metricName, implode(',', $labelPairs));
}
/**
* Get current counter value
*
* @param string $metricName Name of the metric
* @return int Counter value
*/
public function getCounter(string $metricName): int
{
return $this->counters[$metricName] ?? 0;
}
/**
* Get current gauge value
*
* @param string $metricName Name of the metric
* @return float|null Gauge value or null if not set
*/
public function getGauge(string $metricName): ?float
{
return $this->gauges[$metricName] ?? null;
}
/**
* Get statistics for a histogram
*
* @param string $metricName Name of the metric
* @return array<string, float> Dictionary with min, max, avg, count, sum
*/
public function getHistogramStats(string $metricName): array
{
$values = $this->histograms[$metricName] ?? [];
if (empty($values)) {
return ['count' => 0, 'min' => 0.0, 'max' => 0.0, 'avg' => 0.0, 'sum' => 0.0];
}
$sum = array_sum($values);
return [
'count' => count($values),
'min' => min($values),
'max' => max($values),
'avg' => $sum / count($values),
'sum' => $sum
];
}
/**
* Export metrics in Prometheus format
*
* @return string Metrics in Prometheus text format
*/
public function exportPrometheus(): string
{
$lines = [];
$now = new DateTime('now', new DateTimeZone('UTC'));
$lines[] = sprintf('# Metrics for %s', $this->serviceName);
$lines[] = sprintf('# Generated at %s', $now->format('c'));
$lines[] = '';
// Export counters
foreach ($this->counters as $key => $value) {
$lines[] = sprintf('# TYPE %s counter', $this->stripLabels($key));
$lines[] = sprintf('%s %d', $key, $value);
}
// Export gauges
foreach ($this->gauges as $key => $value) {
$lines[] = sprintf('# TYPE %s gauge', $this->stripLabels($key));
$lines[] = sprintf('%s %s', $key, $value);
}
// Export histograms
foreach ($this->histograms as $key => $values) {
if (!empty($values)) {
$stats = $this->getHistogramStats($key);
$lines[] = sprintf('# TYPE %s histogram', $this->stripLabels($key));
$lines[] = sprintf('%s_count %d', $key, $stats['count']);
$lines[] = sprintf('%s_sum %s', $key, $stats['sum']);
$lines[] = sprintf('%s_min %s', $key, $stats['min']);
$lines[] = sprintf('%s_max %s', $key, $stats['max']);
$lines[] = sprintf('%s_avg %s', $key, $stats['avg']);
}
}
// Add uptime
$uptime = microtime(true) - $this->startTime;
$lines[] = '# TYPE process_uptime_seconds gauge';
$lines[] = sprintf('process_uptime_seconds %.2f', $uptime);
return implode("\n", $lines);
}
/**
* Strip labels from metric key
*
* @param string $key Metric key
* @return string Metric name without labels
*/
private function stripLabels(string $key): string
{
$pos = strpos($key, '{');
return $pos !== false ? substr($key, 0, $pos) : $key;
}
/**
* Print a summary of all metrics
*/
public function printSummary(): void
{
echo "\n" . str_repeat('=', 60) . "\n";
echo "Metrics Summary for {$this->serviceName}\n";
echo str_repeat('=', 60) . "\n";
if (!empty($this->counters)) {
echo "\nCounters:\n";
ksort($this->counters);
foreach ($this->counters as $key => $value) {
echo " {$key}: {$value}\n";
}
}
if (!empty($this->gauges)) {
echo "\nGauges:\n";
ksort($this->gauges);
foreach ($this->gauges as $key => $value) {
echo " {$key}: {$value}\n";
}
}
if (!empty($this->histograms)) {
echo "\nHistograms:\n";
$keys = array_keys($this->histograms);
sort($keys);
foreach ($keys as $key) {
$stats = $this->getHistogramStats($key);
echo " {$key}:\n";
echo sprintf(" Count: %d\n", $stats['count']);
echo sprintf(" Min: %.4f\n", $stats['min']);
echo sprintf(" Max: %.4f\n", $stats['max']);
echo sprintf(" Avg: %.4f\n", $stats['avg']);
}
}
$uptime = microtime(true) - $this->startTime;
echo sprintf("\nUptime: %.2f seconds\n", $uptime);
echo str_repeat('=', 60) . "\n\n";
}
public function getVersion(): string
{
return self::VERSION;
}
}