mirror of
https://devops.lemonos.cn/lawson/FendxPHP.git
synced 2026-06-15 23:12:49 +08:00
feat(database): 添加用户角色权限系统及相关监控功能
- 创建用户表(users)包含基本信息和认证字段 - 创建角色表(roles)用于权限控制 - 创建权限表(permissions)定义系统权限 - 创建用户角色关联表(user_roles)建立用户与角色关系 - 创建角色权限关联表(role_permissions)建立角色与权限关系 - 创建迁移记录表(migrations)追踪数据库变更 - 添加AdminController提供管理员面板功能 - 实现系统监控、配置管理、缓存清理等功能 - 添加AOP切面编程支持的各种通知类型 - 实现告警管理AlertManager支持多渠道告警 - 添加文档注解接口规范
This commit is contained in:
@@ -0,0 +1,577 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fendx\Service\CircuitBreaker;
|
||||
|
||||
use Fendx\Service\CircuitBreaker\State\CircuitState;
|
||||
use Fendx\Service\CircuitBreaker\State\ClosedState;
|
||||
use Fendx\Service\CircuitBreaker\State\OpenState;
|
||||
use Fendx\Service\CircuitBreaker\State\HalfOpenState;
|
||||
use Fendx\Service\CircuitBreaker\Storage\CircuitStorage;
|
||||
use Fendx\Service\CircuitBreaker\Monitor\CircuitMonitor;
|
||||
|
||||
class CircuitBreaker
|
||||
{
|
||||
protected string $name;
|
||||
protected array $config;
|
||||
protected CircuitState $state;
|
||||
protected CircuitStorage $storage;
|
||||
protected CircuitMonitor $monitor;
|
||||
protected array $statistics = [];
|
||||
protected float $lastStateChange = 0;
|
||||
protected int $failureCount = 0;
|
||||
protected int $successCount = 0;
|
||||
protected float $lastFailureTime = 0;
|
||||
|
||||
public function __construct(string $name, array $config = [])
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->config = array_merge($this->getDefaultConfig(), $config);
|
||||
$this->storage = new CircuitStorage($this->config);
|
||||
$this->monitor = new CircuitMonitor($this->config);
|
||||
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute function with circuit breaker protection.
|
||||
*/
|
||||
public function call(callable $function, ...$args)
|
||||
{
|
||||
if (!$this->canExecute()) {
|
||||
throw new CircuitBreakerOpenException("Circuit breaker '{$this->name}' is open");
|
||||
}
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
try {
|
||||
$result = $function(...$args);
|
||||
$duration = microtime(true) - $startTime;
|
||||
|
||||
$this->onSuccess($duration);
|
||||
|
||||
return $result;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
|
||||
$this->onFailure($e, $duration);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute function with fallback.
|
||||
*/
|
||||
public function callWithFallback(callable $function, callable $fallback, ...$args)
|
||||
{
|
||||
try {
|
||||
return $this->call($function, ...$args);
|
||||
} catch (CircuitBreakerOpenException $e) {
|
||||
return $fallback(...$args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if circuit breaker allows execution.
|
||||
*/
|
||||
public function canExecute(): bool
|
||||
{
|
||||
return $this->state->canExecute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current state.
|
||||
*/
|
||||
public function getState(): string
|
||||
{
|
||||
return $this->state->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get circuit breaker name.
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force open circuit breaker.
|
||||
*/
|
||||
public function forceOpen(): void
|
||||
{
|
||||
$this->transitionTo(new OpenState($this->config));
|
||||
$this->logInfo("Circuit breaker '{$this->name}' forced open");
|
||||
}
|
||||
|
||||
/**
|
||||
* Force close circuit breaker.
|
||||
*/
|
||||
public function forceClose(): void
|
||||
{
|
||||
$this->transitionTo(new ClosedState($this->config));
|
||||
$this->logInfo("Circuit breaker '{$this->name}' forced closed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset circuit breaker to initial state.
|
||||
*/
|
||||
public function reset(): void
|
||||
{
|
||||
$this->failureCount = 0;
|
||||
$this->successCount = 0;
|
||||
$this->lastFailureTime = 0;
|
||||
$this->statistics = [];
|
||||
|
||||
$this->transitionTo(new ClosedState($this->config));
|
||||
|
||||
$this->logInfo("Circuit breaker '{$this->name}' reset");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get circuit breaker statistics.
|
||||
*/
|
||||
public function getStatistics(): array
|
||||
{
|
||||
return array_merge($this->statistics, [
|
||||
'name' => $this->name,
|
||||
'state' => $this->getState(),
|
||||
'failure_count' => $this->failureCount,
|
||||
'success_count' => $this->successCount,
|
||||
'last_failure_time' => $this->lastFailureTime,
|
||||
'last_state_change' => $this->lastStateChange,
|
||||
'uptime_percentage' => $this->calculateUptimePercentage(),
|
||||
'average_response_time' => $this->calculateAverageResponseTime(),
|
||||
'error_rate' => $this->calculateErrorRate()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed status.
|
||||
*/
|
||||
public function getStatus(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'state' => $this->getState(),
|
||||
'config' => $this->config,
|
||||
'statistics' => $this->getStatistics(),
|
||||
'state_details' => $this->state->getDetails(),
|
||||
'monitoring' => $this->monitor->getStatus()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update configuration.
|
||||
*/
|
||||
public function updateConfig(array $config): void
|
||||
{
|
||||
$this->config = array_merge($this->config, $config);
|
||||
$this->state->updateConfig($this->config);
|
||||
|
||||
$this->logInfo("Configuration updated for circuit breaker '{$this->name}'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration.
|
||||
*/
|
||||
public function getConfig(): array
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable circuit breaker.
|
||||
*/
|
||||
public function setEnabled(bool $enabled): void
|
||||
{
|
||||
$this->config['enabled'] = $enabled;
|
||||
|
||||
if (!$enabled) {
|
||||
$this->forceClose();
|
||||
}
|
||||
|
||||
$this->logInfo("Circuit breaker '{$this->name}' " . ($enabled ? 'enabled' : 'disabled'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if circuit breaker is enabled.
|
||||
*/
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->config['enabled'] ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle successful execution.
|
||||
*/
|
||||
protected function onSuccess(float $duration): void
|
||||
{
|
||||
$this->successCount++;
|
||||
$this->recordExecution(true, $duration);
|
||||
|
||||
$this->state->onSuccess();
|
||||
|
||||
$this->monitor->recordSuccess($duration);
|
||||
|
||||
// Persist state
|
||||
$this->persistState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle failed execution.
|
||||
*/
|
||||
protected function onFailure(\Exception $exception, float $duration): void
|
||||
{
|
||||
$this->failureCount++;
|
||||
$this->lastFailureTime = microtime(true);
|
||||
|
||||
$this->recordExecution(false, $duration, $exception);
|
||||
|
||||
$this->state->onFailure();
|
||||
|
||||
$this->monitor->recordFailure($exception, $duration);
|
||||
|
||||
// Persist state
|
||||
$this->persistState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition to new state.
|
||||
*/
|
||||
protected function transitionTo(CircuitState $newState): void
|
||||
{
|
||||
$previousState = $this->getState();
|
||||
|
||||
$this->state = $newState;
|
||||
$this->lastStateChange = microtime(true);
|
||||
|
||||
// Record state transition
|
||||
$this->recordStateTransition($previousState, $newState->getName());
|
||||
|
||||
// Notify monitor
|
||||
$this->monitor->recordStateTransition($previousState, $newState->getName());
|
||||
|
||||
$this->logInfo("State transition: {$previousState} -> {$newState->getName()}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Record execution statistics.
|
||||
*/
|
||||
protected function recordExecution(bool $success, float $duration, \Exception $exception = null): void
|
||||
{
|
||||
$timestamp = microtime(true);
|
||||
|
||||
$this->statistics['executions'][] = [
|
||||
'timestamp' => $timestamp,
|
||||
'success' => $success,
|
||||
'duration' => $duration,
|
||||
'exception' => $exception ? [
|
||||
'class' => get_class($exception),
|
||||
'message' => $exception->getMessage()
|
||||
] : null
|
||||
];
|
||||
|
||||
// Limit execution history
|
||||
$maxHistory = $this->config['max_execution_history'] ?? 1000;
|
||||
if (count($this->statistics['executions']) > $maxHistory) {
|
||||
$this->statistics['executions'] = array_slice(
|
||||
$this->statistics['executions'],
|
||||
-$maxHistory
|
||||
);
|
||||
}
|
||||
|
||||
// Update aggregates
|
||||
$this->updateAggregates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Record state transition.
|
||||
*/
|
||||
protected function recordStateTransition(string $from, string $to): void
|
||||
{
|
||||
if (!isset($this->statistics['state_transitions'])) {
|
||||
$this->statistics['state_transitions'] = [];
|
||||
}
|
||||
|
||||
$this->statistics['state_transitions'][] = [
|
||||
'timestamp' => microtime(true),
|
||||
'from' => $from,
|
||||
'to' => $to
|
||||
];
|
||||
|
||||
// Limit transition history
|
||||
$maxHistory = $this->config['max_transition_history'] ?? 100;
|
||||
if (count($this->statistics['state_transitions']) > $maxHistory) {
|
||||
$this->statistics['state_transitions'] = array_slice(
|
||||
$this->statistics['state_transitions'],
|
||||
-$maxHistory
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update aggregate statistics.
|
||||
*/
|
||||
protected function updateAggregates(): void
|
||||
{
|
||||
$executions = $this->statistics['executions'] ?? [];
|
||||
|
||||
if (empty($executions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$recentExecutions = array_slice($executions, -$this->config['window_size']);
|
||||
|
||||
$totalDuration = 0;
|
||||
$successCount = 0;
|
||||
|
||||
foreach ($recentExecutions as $execution) {
|
||||
$totalDuration += $execution['duration'];
|
||||
if ($execution['success']) {
|
||||
$successCount++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->statistics['recent_average_duration'] = $totalDuration / count($recentExecutions);
|
||||
$this->statistics['recent_success_rate'] = ($successCount / count($recentExecutions)) * 100;
|
||||
$this->statistics['recent_error_rate'] = 100 - $this->statistics['recent_success_rate'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate uptime percentage.
|
||||
*/
|
||||
protected function calculateUptimePercentage(): float
|
||||
{
|
||||
$executions = $this->statistics['executions'] ?? [];
|
||||
|
||||
if (empty($executions)) {
|
||||
return 100.0;
|
||||
}
|
||||
|
||||
$successCount = 0;
|
||||
foreach ($executions as $execution) {
|
||||
if ($execution['success']) {
|
||||
$successCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return ($successCount / count($executions)) * 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate average response time.
|
||||
*/
|
||||
protected function calculateAverageResponseTime(): float
|
||||
{
|
||||
$executions = $this->statistics['executions'] ?? [];
|
||||
|
||||
if (empty($executions)) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$totalDuration = 0;
|
||||
foreach ($executions as $execution) {
|
||||
$totalDuration += $execution['duration'];
|
||||
}
|
||||
|
||||
return $totalDuration / count($executions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate error rate.
|
||||
*/
|
||||
protected function calculateErrorRate(): float
|
||||
{
|
||||
return 100.0 - $this->calculateUptimePercentage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist circuit breaker state.
|
||||
*/
|
||||
protected function persistState(): void
|
||||
{
|
||||
if (!$this->config['persist_state']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$state = [
|
||||
'name' => $this->name,
|
||||
'state' => $this->getState(),
|
||||
'failure_count' => $this->failureCount,
|
||||
'success_count' => $this->successCount,
|
||||
'last_failure_time' => $this->lastFailureTime,
|
||||
'last_state_change' => $this->lastStateChange,
|
||||
'statistics' => $this->statistics
|
||||
];
|
||||
|
||||
$this->storage->save($this->name, $state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load circuit breaker state.
|
||||
*/
|
||||
protected function loadState(): void
|
||||
{
|
||||
if (!$this->config['persist_state']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$state = $this->storage->load($this->name);
|
||||
|
||||
if (!$state) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->failureCount = $state['failure_count'] ?? 0;
|
||||
$this->successCount = $state['success_count'] ?? 0;
|
||||
$this->lastFailureTime = $state['last_failure_time'] ?? 0;
|
||||
$this->lastStateChange = $state['last_state_change'] ?? 0;
|
||||
$this->statistics = $state['statistics'] ?? [];
|
||||
|
||||
// Restore state
|
||||
switch ($state['state']) {
|
||||
case 'open':
|
||||
$this->state = new OpenState($this->config);
|
||||
break;
|
||||
case 'half_open':
|
||||
$this->state = new HalfOpenState($this->config);
|
||||
break;
|
||||
default:
|
||||
$this->state = new ClosedState($this->config);
|
||||
break;
|
||||
}
|
||||
|
||||
$this->logInfo("State loaded for circuit breaker '{$this->name}': {$state['state']}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize circuit breaker.
|
||||
*/
|
||||
protected function initialize(): void
|
||||
{
|
||||
// Load persisted state
|
||||
$this->loadState();
|
||||
|
||||
// Initialize state if not loaded
|
||||
if (!isset($this->state)) {
|
||||
$this->state = new ClosedState($this->config);
|
||||
$this->lastStateChange = microtime(true);
|
||||
}
|
||||
|
||||
// Start monitoring
|
||||
$this->monitor->start($this->name);
|
||||
|
||||
$this->logInfo("Circuit breaker '{$this->name}' initialized in {$this->getState()} state");
|
||||
}
|
||||
|
||||
/**
|
||||
* Log info message.
|
||||
*/
|
||||
protected function logInfo(string $message): void
|
||||
{
|
||||
if ($this->config['logging_enabled']) {
|
||||
error_log("[CircuitBreaker:{$this->name}] {$message}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log warning message.
|
||||
*/
|
||||
protected function logWarning(string $message): void
|
||||
{
|
||||
if ($this->config['logging_enabled']) {
|
||||
error_log("[CircuitBreaker:{$this->name}] WARNING: {$message}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default configuration.
|
||||
*/
|
||||
protected function getDefaultConfig(): array
|
||||
{
|
||||
return [
|
||||
'failure_threshold' => 5,
|
||||
'success_threshold' => 3,
|
||||
'timeout' => 60,
|
||||
'half_open_max_calls' => 3,
|
||||
'window_size' => 100,
|
||||
'enabled' => true,
|
||||
'persist_state' => true,
|
||||
'logging_enabled' => true,
|
||||
'max_execution_history' => 1000,
|
||||
'max_transition_history' => 100,
|
||||
'monitoring' => [
|
||||
'enabled' => true,
|
||||
'metrics_interval' => 60
|
||||
],
|
||||
'storage' => [
|
||||
'type' => 'file',
|
||||
'path' => __DIR__ . '/../../../storage/circuit_breaker'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create circuit breaker instance.
|
||||
*/
|
||||
public static function create(string $name, array $config = []): self
|
||||
{
|
||||
return new self($name, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create for high availability.
|
||||
*/
|
||||
public static function forHighAvailability(string $name): self
|
||||
{
|
||||
return new self($name, [
|
||||
'failure_threshold' => 3,
|
||||
'success_threshold' => 2,
|
||||
'timeout' => 30,
|
||||
'half_open_max_calls' => 5,
|
||||
'window_size' => 50
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create for development.
|
||||
*/
|
||||
public static function forDevelopment(string $name): self
|
||||
{
|
||||
return new self($name, [
|
||||
'failure_threshold' => 10,
|
||||
'success_threshold' => 5,
|
||||
'timeout' => 120,
|
||||
'persist_state' => false,
|
||||
'logging_enabled' => true
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create for testing.
|
||||
*/
|
||||
public static function forTesting(string $name): self
|
||||
{
|
||||
return new self($name, [
|
||||
'failure_threshold' => 2,
|
||||
'success_threshold' => 1,
|
||||
'timeout' => 5,
|
||||
'persist_state' => false,
|
||||
'logging_enabled' => false
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Circuit breaker open exception.
|
||||
*/
|
||||
class CircuitBreakerOpenException extends \Exception
|
||||
{
|
||||
public function __construct(string $message = "Circuit breaker is open", int $code = 0, \Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,662 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fendx\Service\CircuitBreaker\Detector;
|
||||
|
||||
use Fendx\Service\CircuitBreaker\Detector\Strategy\ErrorRateStrategy;
|
||||
use Fendx\Service\CircuitBreaker\Detector\Strategy\ResponseTimeStrategy;
|
||||
use Fendx\Service\CircuitBreaker\Detector\Strategy\TimeoutStrategy;
|
||||
use Fendx\Service\CircuitBreaker\Detector\Strategy\CustomStrategy;
|
||||
|
||||
class FailureDetector
|
||||
{
|
||||
protected array $strategies = [];
|
||||
protected array $config = [];
|
||||
protected array $metrics = [];
|
||||
protected array $detectionHistory = [];
|
||||
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = array_merge($this->getDefaultConfig(), $config);
|
||||
$this->initializeStrategies();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect failure based on metrics.
|
||||
*/
|
||||
public function detectFailure(array $metrics): array
|
||||
{
|
||||
$results = [];
|
||||
$overallFailure = false;
|
||||
$failureReasons = [];
|
||||
|
||||
foreach ($this->strategies as $name => $strategy) {
|
||||
$strategyResult = $strategy->detect($metrics);
|
||||
|
||||
$results[$name] = $strategyResult;
|
||||
|
||||
if ($strategyResult['failure']) {
|
||||
$overallFailure = true;
|
||||
$failureReasons[] = $strategyResult['reason'];
|
||||
}
|
||||
}
|
||||
|
||||
$detection = [
|
||||
'timestamp' => microtime(true),
|
||||
'failure' => $overallFailure,
|
||||
'reasons' => $failureReasons,
|
||||
'strategies' => $results,
|
||||
'metrics' => $metrics
|
||||
];
|
||||
|
||||
// Record detection
|
||||
$this->recordDetection($detection);
|
||||
|
||||
return $detection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom detection strategy.
|
||||
*/
|
||||
public function addStrategy(string $name, callable $detector): void
|
||||
{
|
||||
$this->strategies[$name] = new CustomStrategy($detector);
|
||||
|
||||
$this->logInfo("Added custom detection strategy: {$name}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove detection strategy.
|
||||
*/
|
||||
public function removeStrategy(string $name): bool
|
||||
{
|
||||
if (!isset($this->strategies[$name])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unset($this->strategies[$name]);
|
||||
|
||||
$this->logInfo("Removed detection strategy: {$name}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all strategies.
|
||||
*/
|
||||
public function getStrategies(): array
|
||||
{
|
||||
return array_keys($this->strategies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable strategy.
|
||||
*/
|
||||
public function setStrategyEnabled(string $name, bool $enabled): bool
|
||||
{
|
||||
if (!isset($this->strategies[$name])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->strategies[$name]->setEnabled($enabled);
|
||||
|
||||
$this->logInfo("Strategy '{$name}' " . ($enabled ? 'enabled' : 'disabled'));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update strategy configuration.
|
||||
*/
|
||||
public function updateStrategyConfig(string $name, array $config): bool
|
||||
{
|
||||
if (!isset($this->strategies[$name])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->strategies[$name]->updateConfig($config);
|
||||
|
||||
$this->logInfo("Configuration updated for strategy: {$name}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record metrics for analysis.
|
||||
*/
|
||||
public function recordMetrics(array $metrics): void
|
||||
{
|
||||
$timestamp = microtime(true);
|
||||
|
||||
$this->metrics[] = array_merge($metrics, [
|
||||
'timestamp' => $timestamp
|
||||
]);
|
||||
|
||||
// Limit metrics history
|
||||
$maxHistory = $this->config['max_metrics_history'] ?? 10000;
|
||||
if (count($this->metrics) > $maxHistory) {
|
||||
$this->metrics = array_slice($this->metrics, -$maxHistory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent metrics.
|
||||
*/
|
||||
public function getRecentMetrics(int $windowSize = null): array
|
||||
{
|
||||
$windowSize = $windowSize ?? $this->config['window_size'] ?? 100;
|
||||
|
||||
return array_slice($this->metrics, -$windowSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metrics in time range.
|
||||
*/
|
||||
public function getMetricsInRange(float $startTime, float $endTime): array
|
||||
{
|
||||
return array_filter($this->metrics, function($metric) use ($startTime, $endTime) {
|
||||
return $metric['timestamp'] >= $startTime && $metric['timestamp'] <= $endTime;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate aggregated metrics.
|
||||
*/
|
||||
public function calculateAggregates(array $metrics = null): array
|
||||
{
|
||||
$metrics = $metrics ?? $this->getRecentMetrics();
|
||||
|
||||
if (empty($metrics)) {
|
||||
return [
|
||||
'count' => 0,
|
||||
'success_rate' => 0,
|
||||
'error_rate' => 0,
|
||||
'average_response_time' => 0,
|
||||
'min_response_time' => 0,
|
||||
'max_response_time' => 0,
|
||||
'timeout_rate' => 0
|
||||
];
|
||||
}
|
||||
|
||||
$totalCount = count($metrics);
|
||||
$successCount = 0;
|
||||
$errorCount = 0;
|
||||
$timeoutCount = 0;
|
||||
$totalResponseTime = 0;
|
||||
$minResponseTime = PHP_FLOAT_MAX;
|
||||
$maxResponseTime = 0;
|
||||
|
||||
foreach ($metrics as $metric) {
|
||||
$success = $metric['success'] ?? false;
|
||||
$responseTime = $metric['response_time'] ?? 0;
|
||||
$timeout = $metric['timeout'] ?? false;
|
||||
|
||||
if ($success) {
|
||||
$successCount++;
|
||||
} else {
|
||||
$errorCount++;
|
||||
}
|
||||
|
||||
if ($timeout) {
|
||||
$timeoutCount++;
|
||||
}
|
||||
|
||||
$totalResponseTime += $responseTime;
|
||||
$minResponseTime = min($minResponseTime, $responseTime);
|
||||
$maxResponseTime = max($maxResponseTime, $responseTime);
|
||||
}
|
||||
|
||||
return [
|
||||
'count' => $totalCount,
|
||||
'success_rate' => ($successCount / $totalCount) * 100,
|
||||
'error_rate' => ($errorCount / $totalCount) * 100,
|
||||
'timeout_rate' => ($timeoutCount / $totalCount) * 100,
|
||||
'average_response_time' => $totalResponseTime / $totalCount,
|
||||
'min_response_time' => $minResponseTime === PHP_FLOAT_MAX ? 0 : $minResponseTime,
|
||||
'max_response_time' => $maxResponseTime,
|
||||
'success_count' => $successCount,
|
||||
'error_count' => $errorCount,
|
||||
'timeout_count' => $timeoutCount
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detection statistics.
|
||||
*/
|
||||
public function getStatistics(): array
|
||||
{
|
||||
$aggregates = $this->calculateAggregates();
|
||||
|
||||
$detectionStats = [
|
||||
'total_detections' => count($this->detectionHistory),
|
||||
'failure_detections' => 0,
|
||||
'strategy_performance' => []
|
||||
];
|
||||
|
||||
foreach ($this->detectionHistory as $detection) {
|
||||
if ($detection['failure']) {
|
||||
$detectionStats['failure_detections']++;
|
||||
}
|
||||
|
||||
foreach ($detection['strategies'] as $strategyName => $result) {
|
||||
if (!isset($detectionStats['strategy_performance'][$strategyName])) {
|
||||
$detectionStats['strategy_performance'][$strategyName] = [
|
||||
'detections' => 0,
|
||||
'failures' => 0
|
||||
];
|
||||
}
|
||||
|
||||
$detectionStats['strategy_performance'][$strategyName]['detections']++;
|
||||
|
||||
if ($result['failure']) {
|
||||
$detectionStats['strategy_performance'][$strategyName]['failures']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate strategy effectiveness
|
||||
foreach ($detectionStats['strategy_performance'] as $strategyName => &$stats) {
|
||||
$stats['failure_rate'] = $stats['detections'] > 0 ?
|
||||
($stats['failures'] / $stats['detections']) * 100 : 0;
|
||||
}
|
||||
|
||||
return array_merge($aggregates, $detectionStats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detection history.
|
||||
*/
|
||||
public function getDetectionHistory(int $limit = 100): array
|
||||
{
|
||||
return array_slice($this->detectionHistory, -$limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze failure patterns.
|
||||
*/
|
||||
public function analyzeFailurePatterns(): array
|
||||
{
|
||||
$patterns = [];
|
||||
|
||||
// Time-based patterns
|
||||
$hourlyFailures = [];
|
||||
$dailyFailures = [];
|
||||
|
||||
foreach ($this->detectionHistory as $detection) {
|
||||
if (!$detection['failure']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$timestamp = $detection['timestamp'];
|
||||
$hour = date('H', (int) $timestamp);
|
||||
$day = date('Y-m-d', (int) $timestamp);
|
||||
|
||||
$hourlyFailures[$hour] = ($hourlyFailures[$hour] ?? 0) + 1;
|
||||
$dailyFailures[$day] = ($dailyFailures[$day] ?? 0) + 1;
|
||||
}
|
||||
|
||||
// Strategy correlation
|
||||
$strategyCorrelations = [];
|
||||
|
||||
foreach ($this->detectionHistory as $detection) {
|
||||
foreach ($detection['strategies'] as $strategyName => $result) {
|
||||
if (!isset($strategyCorrelations[$strategyName])) {
|
||||
$strategyCorrelations[$strategyName] = [
|
||||
'total' => 0,
|
||||
'failures' => 0
|
||||
];
|
||||
}
|
||||
|
||||
$strategyCorrelations[$strategyName]['total']++;
|
||||
|
||||
if ($result['failure']) {
|
||||
$strategyCorrelations[$strategyName]['failures']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate correlation rates
|
||||
foreach ($strategyCorrelations as $strategyName => &$correlation) {
|
||||
$correlation['correlation_rate'] = $correlation['total'] > 0 ?
|
||||
($correlation['failures'] / $correlation['total']) * 100 : 0;
|
||||
}
|
||||
|
||||
return [
|
||||
'hourly_pattern' => $hourlyFailures,
|
||||
'daily_pattern' => $dailyFailures,
|
||||
'strategy_correlations' => $strategyCorrelations,
|
||||
'peak_failure_hour' => $this->findPeakHour($hourlyFailures),
|
||||
'most_correlated_strategy' => $this->findMostCorrelatedStrategy($strategyCorrelations)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Predict failure probability.
|
||||
*/
|
||||
public function predictFailureProbability(): float
|
||||
{
|
||||
$recentMetrics = $this->getRecentMetrics($this->config['prediction_window'] ?? 50);
|
||||
|
||||
if (count($recentMetrics) < $this->config['min_prediction_samples'] ?? 10) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$aggregates = $this->calculateAggregates($recentMetrics);
|
||||
|
||||
// Simple probability calculation based on recent trends
|
||||
$errorRate = $aggregates['error_rate'];
|
||||
$timeoutRate = $aggregates['timeout_rate'];
|
||||
|
||||
// Weight factors
|
||||
$errorWeight = $this->config['error_rate_weight'] ?? 0.6;
|
||||
$timeoutWeight = $this->config['timeout_rate_weight'] ?? 0.4;
|
||||
|
||||
$probability = ($errorRate * $errorWeight) + ($timeoutRate * $timeoutWeight);
|
||||
|
||||
// Apply trend analysis
|
||||
$trendFactor = $this->calculateTrendFactor($recentMetrics);
|
||||
$probability *= $trendFactor;
|
||||
|
||||
return min(100.0, max(0.0, $probability));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get health score.
|
||||
*/
|
||||
public function getHealthScore(): float
|
||||
{
|
||||
$failureProbability = $this->predictFailureProbability();
|
||||
|
||||
// Convert failure probability to health score (0-100)
|
||||
$healthScore = 100.0 - $failureProbability;
|
||||
|
||||
// Apply stability factor
|
||||
$stabilityFactor = $this->calculateStabilityFactor();
|
||||
$healthScore *= $stabilityFactor;
|
||||
|
||||
return min(100.0, max(0.0, $healthScore));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset detector state.
|
||||
*/
|
||||
public function reset(): void
|
||||
{
|
||||
$this->metrics = [];
|
||||
$this->detectionHistory = [];
|
||||
|
||||
$this->logInfo("Failure detector reset");
|
||||
}
|
||||
|
||||
/**
|
||||
* Export detector configuration and data.
|
||||
*/
|
||||
public function exportData(): array
|
||||
{
|
||||
return [
|
||||
'config' => $this->config,
|
||||
'strategies' => $this->getStrategyConfigs(),
|
||||
'metrics' => $this->metrics,
|
||||
'detection_history' => $this->detectionHistory,
|
||||
'statistics' => $this->getStatistics(),
|
||||
'exported_at' => date('Y-m-d H:i:s')
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Record detection result.
|
||||
*/
|
||||
protected function recordDetection(array $detection): void
|
||||
{
|
||||
$this->detectionHistory[] = $detection;
|
||||
|
||||
// Limit history size
|
||||
$maxHistory = $this->config['max_detection_history'] ?? 1000;
|
||||
if (count($this->detectionHistory) > $maxHistory) {
|
||||
$this->detectionHistory = array_slice($this->detectionHistory, -$maxHistory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize built-in strategies.
|
||||
*/
|
||||
protected function initializeStrategies(): void
|
||||
{
|
||||
if ($this->config['strategies']['error_rate']['enabled'] ?? true) {
|
||||
$this->strategies['error_rate'] = new ErrorRateStrategy(
|
||||
$this->config['strategies']['error_rate'] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->config['strategies']['response_time']['enabled'] ?? true) {
|
||||
$this->strategies['response_time'] = new ResponseTimeStrategy(
|
||||
$this->config['strategies']['response_time'] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->config['strategies']['timeout']['enabled'] ?? true) {
|
||||
$this->strategies['timeout'] = new TimeoutStrategy(
|
||||
$this->config['strategies']['timeout'] ?? []
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get strategy configurations.
|
||||
*/
|
||||
protected function getStrategyConfigs(): array
|
||||
{
|
||||
$configs = [];
|
||||
|
||||
foreach ($this->strategies as $name => $strategy) {
|
||||
$configs[$name] = $strategy->getConfig();
|
||||
}
|
||||
|
||||
return $configs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find peak failure hour.
|
||||
*/
|
||||
protected function findPeakHour(array $hourlyFailures): ?int
|
||||
{
|
||||
if (empty($hourlyFailures)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array_keys($hourlyFailures, max($hourlyFailures))[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find most correlated strategy.
|
||||
*/
|
||||
protected function findMostCorrelatedStrategy(array $correlations): ?string
|
||||
{
|
||||
if (empty($correlations)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$maxCorrelation = 0;
|
||||
$mostCorrelated = null;
|
||||
|
||||
foreach ($correlations as $strategy => $data) {
|
||||
if ($data['correlation_rate'] > $maxCorrelation) {
|
||||
$maxCorrelation = $data['correlation_rate'];
|
||||
$mostCorrelated = $strategy;
|
||||
}
|
||||
}
|
||||
|
||||
return $mostCorrelated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate trend factor.
|
||||
*/
|
||||
protected function calculateTrendFactor(array $recentMetrics): float
|
||||
{
|
||||
if (count($recentMetrics) < 10) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
// Split into two halves
|
||||
$mid = count($recentMetrics) / 2;
|
||||
$firstHalf = array_slice($recentMetrics, 0, $mid);
|
||||
$secondHalf = array_slice($recentMetrics, $mid);
|
||||
|
||||
$firstHalfAggregates = $this->calculateAggregates($firstHalf);
|
||||
$secondHalfAggregates = $this->calculateAggregates($secondHalf);
|
||||
|
||||
// Compare error rates
|
||||
$errorRateChange = $secondHalfAggregates['error_rate'] - $firstHalfAggregates['error_rate'];
|
||||
|
||||
// Convert to factor (1.0 = no change, >1.0 = worsening, <1.0 = improving)
|
||||
$factor = 1.0 + ($errorRateChange / 100.0);
|
||||
|
||||
return max(0.5, min(2.0, $factor));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate stability factor.
|
||||
*/
|
||||
protected function calculateStabilityFactor(): float
|
||||
{
|
||||
if (count($this->detectionHistory) < 10) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
$recentDetections = array_slice($this->detectionHistory, -20);
|
||||
$fluctuations = 0;
|
||||
|
||||
for ($i = 1; $i < count($recentDetections); $i++) {
|
||||
$current = $recentDetections[$i]['failure'];
|
||||
$previous = $recentDetections[$i - 1]['failure'];
|
||||
|
||||
if ($current !== $previous) {
|
||||
$fluctuations++;
|
||||
}
|
||||
}
|
||||
|
||||
$fluctuationRate = $fluctuations / (count($recentDetections) - 1);
|
||||
|
||||
// Convert fluctuation rate to stability factor
|
||||
return max(0.7, 1.0 - ($fluctuationRate * 0.5));
|
||||
}
|
||||
|
||||
/**
|
||||
* Log info message.
|
||||
*/
|
||||
protected function logInfo(string $message): void
|
||||
{
|
||||
if ($this->config['logging_enabled']) {
|
||||
error_log("[FailureDetector] {$message}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default configuration.
|
||||
*/
|
||||
protected function getDefaultConfig(): array
|
||||
{
|
||||
return [
|
||||
'window_size' => 100,
|
||||
'prediction_window' => 50,
|
||||
'min_prediction_samples' => 10,
|
||||
'max_metrics_history' => 10000,
|
||||
'max_detection_history' => 1000,
|
||||
'error_rate_weight' => 0.6,
|
||||
'timeout_rate_weight' => 0.4,
|
||||
'logging_enabled' => true,
|
||||
'strategies' => [
|
||||
'error_rate' => [
|
||||
'enabled' => true,
|
||||
'threshold' => 50.0,
|
||||
'window_size' => 20
|
||||
],
|
||||
'response_time' => [
|
||||
'enabled' => true,
|
||||
'threshold' => 5000.0,
|
||||
'window_size' => 10
|
||||
],
|
||||
'timeout' => [
|
||||
'enabled' => true,
|
||||
'threshold' => 10.0,
|
||||
'window_size' => 30
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration.
|
||||
*/
|
||||
public function getConfig(): array
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set configuration.
|
||||
*/
|
||||
public function setConfig(array $config): void
|
||||
{
|
||||
$this->config = array_merge($this->config, $config);
|
||||
|
||||
// Reinitialize strategies
|
||||
$this->initializeStrategies();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create failure detector instance.
|
||||
*/
|
||||
public static function create(array $config = []): self
|
||||
{
|
||||
return new self($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create for production.
|
||||
*/
|
||||
public static function forProduction(): self
|
||||
{
|
||||
return new self([
|
||||
'window_size' => 200,
|
||||
'prediction_window' => 100,
|
||||
'min_prediction_samples' => 20,
|
||||
'strategies' => [
|
||||
'error_rate' => [
|
||||
'threshold' => 30.0,
|
||||
'window_size' => 50
|
||||
],
|
||||
'response_time' => [
|
||||
'threshold' => 2000.0,
|
||||
'window_size' => 25
|
||||
],
|
||||
'timeout' => [
|
||||
'threshold' => 5.0,
|
||||
'window_size' => 40
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create for development.
|
||||
*/
|
||||
public static function forDevelopment(): self
|
||||
{
|
||||
return new self([
|
||||
'window_size' => 50,
|
||||
'prediction_window' => 25,
|
||||
'min_prediction_samples' => 5,
|
||||
'logging_enabled' => true,
|
||||
'strategies' => [
|
||||
'error_rate' => [
|
||||
'threshold' => 70.0
|
||||
],
|
||||
'response_time' => [
|
||||
'threshold' => 10000.0
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,874 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fendx\Service\CircuitBreaker\Monitor;
|
||||
|
||||
use Fendx\Service\CircuitBreaker\Monitor\Collector\MetricsCollector;
|
||||
use Fendx\Service\CircuitBreaker\Monitor\Analyzer\StateAnalyzer;
|
||||
use Fendx\Service\CircuitBreaker\Monitor\Alert\AlertManager;
|
||||
|
||||
class CircuitMonitor
|
||||
{
|
||||
protected array $config = [];
|
||||
protected MetricsCollector $collector;
|
||||
protected StateAnalyzer $analyzer;
|
||||
protected AlertManager $alertManager;
|
||||
protected array $circuitStates = [];
|
||||
protected array $monitoringData = [];
|
||||
protected array $alerts = [];
|
||||
protected bool $isRunning = false;
|
||||
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = array_merge($this->getDefaultConfig(), $config);
|
||||
$this->collector = new MetricsCollector($this->config);
|
||||
$this->analyzer = new StateAnalyzer($this->config);
|
||||
$this->alertManager = new AlertManager($this->config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start monitoring for a circuit breaker.
|
||||
*/
|
||||
public function start(string $circuitBreakerName): void
|
||||
{
|
||||
$this->circuitStates[$circuitBreakerName] = [
|
||||
'name' => $circuitBreakerName,
|
||||
'started_at' => microtime(true),
|
||||
'last_update' => microtime(true),
|
||||
'state_transitions' => [],
|
||||
'metrics' => [],
|
||||
'alerts' => []
|
||||
];
|
||||
|
||||
if (!$this->isRunning) {
|
||||
$this->isRunning = true;
|
||||
$this->startMonitoringLoop();
|
||||
}
|
||||
|
||||
$this->logInfo("Started monitoring for circuit breaker: {$circuitBreakerName}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop monitoring for a circuit breaker.
|
||||
*/
|
||||
public function stop(string $circuitBreakerName): void
|
||||
{
|
||||
if (!isset($this->circuitStates[$circuitBreakerName])) {
|
||||
return;
|
||||
}
|
||||
|
||||
unset($this->circuitStates[$circuitBreakerName]);
|
||||
|
||||
if (empty($this->circuitStates)) {
|
||||
$this->isRunning = false;
|
||||
}
|
||||
|
||||
$this->logInfo("Stopped monitoring for circuit breaker: {$circuitBreakerName}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Record state transition.
|
||||
*/
|
||||
public function recordStateTransition(string $circuitBreakerName, string $fromState, string $toState): void
|
||||
{
|
||||
if (!isset($this->circuitStates[$circuitBreakerName])) {
|
||||
$this->start($circuitBreakerName);
|
||||
}
|
||||
|
||||
$transition = [
|
||||
'timestamp' => microtime(true),
|
||||
'from' => $fromState,
|
||||
'to' => $toState,
|
||||
'duration' => 0
|
||||
];
|
||||
|
||||
$this->circuitStates[$circuitBreakerName]['state_transitions'][] = $transition;
|
||||
$this->circuitStates[$circuitBreakerName]['last_update'] = microtime(true);
|
||||
|
||||
// Analyze transition
|
||||
$this->analyzeTransition($circuitBreakerName, $transition);
|
||||
|
||||
// Check for alerts
|
||||
$this->checkTransitionAlerts($circuitBreakerName, $transition);
|
||||
|
||||
$this->logInfo("State transition recorded for {$circuitBreakerName}: {$fromState} -> {$toState}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Record successful execution.
|
||||
*/
|
||||
public function recordSuccess(string $circuitBreakerName, float $duration): void
|
||||
{
|
||||
if (!isset($this->circuitStates[$circuitBreakerName])) {
|
||||
$this->start($circuitBreakerName);
|
||||
}
|
||||
|
||||
$metric = [
|
||||
'timestamp' => microtime(true),
|
||||
'type' => 'success',
|
||||
'duration' => $duration
|
||||
];
|
||||
|
||||
$this->circuitStates[$circuitBreakerName]['metrics'][] = $metric;
|
||||
$this->circuitStates[$circuitBreakerName]['last_update'] = microtime(true);
|
||||
|
||||
// Collect metrics
|
||||
$this->collector->record($circuitBreakerName, $metric);
|
||||
|
||||
// Check performance alerts
|
||||
$this->checkPerformanceAlerts($circuitBreakerName, $metric);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record failed execution.
|
||||
*/
|
||||
public function recordFailure(string $circuitBreakerName, \Exception $exception, float $duration): void
|
||||
{
|
||||
if (!isset($this->circuitStates[$circuitBreakerName])) {
|
||||
$this->start($circuitBreakerName);
|
||||
}
|
||||
|
||||
$metric = [
|
||||
'timestamp' => microtime(true),
|
||||
'type' => 'failure',
|
||||
'duration' => $duration,
|
||||
'exception' => [
|
||||
'class' => get_class($exception),
|
||||
'message' => $exception->getMessage(),
|
||||
'code' => $exception->getCode()
|
||||
]
|
||||
];
|
||||
|
||||
$this->circuitStates[$circuitBreakerName]['metrics'][] = $metric;
|
||||
$this->circuitStates[$circuitBreakerName]['last_update'] = microtime(true);
|
||||
|
||||
// Collect metrics
|
||||
$this->collector->record($circuitBreakerName, $metric);
|
||||
|
||||
// Check failure alerts
|
||||
$this->checkFailureAlerts($circuitBreakerName, $metric);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get monitoring status.
|
||||
*/
|
||||
public function getStatus(): array
|
||||
{
|
||||
return [
|
||||
'is_running' => $this->isRunning,
|
||||
'monitored_circuits' => count($this->circuitStates),
|
||||
'circuit_names' => array_keys($this->circuitStates),
|
||||
'total_metrics' => $this->getTotalMetricsCount(),
|
||||
'total_transitions' => $this->getTotalTransitionsCount(),
|
||||
'active_alerts' => count($this->alerts)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get circuit breaker monitoring data.
|
||||
*/
|
||||
public function getCircuitData(string $circuitBreakerName): ?array
|
||||
{
|
||||
if (!isset($this->circuitStates[$circuitBreakerName])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$circuitData = $this->circuitStates[$circuitBreakerName];
|
||||
|
||||
// Add calculated metrics
|
||||
$circuitData['calculated_metrics'] = $this->calculateCircuitMetrics($circuitBreakerName);
|
||||
|
||||
// Add analysis results
|
||||
$circuitData['analysis'] = $this->analyzer->analyze($circuitBreakerName, $circuitData);
|
||||
|
||||
return $circuitData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all circuit breaker data.
|
||||
*/
|
||||
public function getAllCircuitData(): array
|
||||
{
|
||||
$allData = [];
|
||||
|
||||
foreach (array_keys($this->circuitStates) as $circuitName) {
|
||||
$allData[$circuitName] = $this->getCircuitData($circuitName);
|
||||
}
|
||||
|
||||
return $allData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get monitoring dashboard data.
|
||||
*/
|
||||
public function getDashboardData(): array
|
||||
{
|
||||
$dashboard = [
|
||||
'overview' => [
|
||||
'total_circuits' => count($this->circuitStates),
|
||||
'healthy_circuits' => 0,
|
||||
'degraded_circuits' => 0,
|
||||
'failed_circuits' => 0,
|
||||
'total_requests' => 0,
|
||||
'success_rate' => 0,
|
||||
'average_response_time' => 0
|
||||
],
|
||||
'circuits' => [],
|
||||
'alerts' => $this->getRecentAlerts(10),
|
||||
'trends' => $this->getTrends()
|
||||
];
|
||||
|
||||
foreach ($this->circuitStates as $circuitName => $circuitData) {
|
||||
$metrics = $this->calculateCircuitMetrics($circuitName);
|
||||
$analysis = $this->analyzer->analyze($circuitName, $circuitData);
|
||||
|
||||
$circuitInfo = [
|
||||
'name' => $circuitName,
|
||||
'status' => $analysis['health_status'],
|
||||
'success_rate' => $metrics['success_rate'],
|
||||
'average_response_time' => $metrics['average_response_time'],
|
||||
'last_state_change' => $this->getLastStateChange($circuitName),
|
||||
'uptime_percentage' => $metrics['uptime_percentage'],
|
||||
'error_rate' => $metrics['error_rate']
|
||||
];
|
||||
|
||||
$dashboard['circuits'][] = $circuitInfo;
|
||||
|
||||
// Update overview
|
||||
switch ($analysis['health_status']) {
|
||||
case 'healthy':
|
||||
$dashboard['overview']['healthy_circuits']++;
|
||||
break;
|
||||
case 'degraded':
|
||||
$dashboard['overview']['degraded_circuits']++;
|
||||
break;
|
||||
case 'failed':
|
||||
$dashboard['overview']['failed_circuits']++;
|
||||
break;
|
||||
}
|
||||
|
||||
$dashboard['overview']['total_requests'] += $metrics['total_requests'];
|
||||
}
|
||||
|
||||
// Calculate overview averages
|
||||
if (count($this->circuitStates) > 0) {
|
||||
$totalSuccessRate = 0;
|
||||
$totalResponseTime = 0;
|
||||
|
||||
foreach ($dashboard['circuits'] as $circuit) {
|
||||
$totalSuccessRate += $circuit['success_rate'];
|
||||
$totalResponseTime += $circuit['average_response_time'];
|
||||
}
|
||||
|
||||
$dashboard['overview']['success_rate'] = $totalSuccessRate / count($dashboard['circuits']);
|
||||
$dashboard['overview']['average_response_time'] = $totalResponseTime / count($dashboard['circuits']);
|
||||
}
|
||||
|
||||
return $dashboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent alerts.
|
||||
*/
|
||||
public function getRecentAlerts(int $limit = 50): array
|
||||
{
|
||||
$alerts = $this->alertManager->getRecentAlerts($limit);
|
||||
|
||||
// Add circuit-specific alerts
|
||||
foreach ($this->circuitStates as $circuitName => $circuitData) {
|
||||
$circuitAlerts = $circuitData['alerts'] ?? [];
|
||||
foreach ($circuitAlerts as $alert) {
|
||||
$alerts[] = array_merge($alert, ['circuit' => $circuitName]);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by timestamp
|
||||
usort($alerts, function($a, $b) {
|
||||
return $b['timestamp'] <=> $a['timestamp'];
|
||||
});
|
||||
|
||||
return array_slice($alerts, 0, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get trends data.
|
||||
*/
|
||||
public function getTrends(): array
|
||||
{
|
||||
$trends = [
|
||||
'success_rate_trend' => [],
|
||||
'response_time_trend' => [],
|
||||
'error_rate_trend' => [],
|
||||
'state_change_frequency' => []
|
||||
];
|
||||
|
||||
foreach ($this->circuitStates as $circuitName => $circuitData) {
|
||||
$circuitTrends = $this->calculateCircuitTrends($circuitName);
|
||||
|
||||
foreach ($trends as $trendType => &$trendData) {
|
||||
if (isset($circuitTrends[$trendType])) {
|
||||
$trendData[$circuitName] = $circuitTrends[$trendType];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $trends;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get performance report.
|
||||
*/
|
||||
public function getPerformanceReport(string $circuitBreakerName = null): array
|
||||
{
|
||||
if ($circuitBreakerName) {
|
||||
return $this->generateCircuitReport($circuitBreakerName);
|
||||
}
|
||||
|
||||
$report = [
|
||||
'generated_at' => date('Y-m-d H:i:s'),
|
||||
'period' => 'last_24_hours',
|
||||
'summary' => $this->generateSummaryReport(),
|
||||
'circuits' => []
|
||||
];
|
||||
|
||||
foreach (array_keys($this->circuitStates) as $circuitName) {
|
||||
$report['circuits'][$circuitName] = $this->generateCircuitReport($circuitName);
|
||||
}
|
||||
|
||||
return $report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set alert threshold.
|
||||
*/
|
||||
public function setAlertThreshold(string $metric, float $value): void
|
||||
{
|
||||
$this->alertManager->setThreshold($metric, $value);
|
||||
|
||||
$this->logInfo("Alert threshold set for {$metric}: {$value}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable alert.
|
||||
*/
|
||||
public function setAlertEnabled(string $alertType, bool $enabled): void
|
||||
{
|
||||
$this->alertManager->setEnabled($alertType, $enabled);
|
||||
|
||||
$this->logInfo("Alert '{$alertType}' " . ($enabled ? 'enabled' : 'disabled'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear monitoring data.
|
||||
*/
|
||||
public function clearData(string $circuitBreakerName = null): void
|
||||
{
|
||||
if ($circuitBreakerName) {
|
||||
if (isset($this->circuitStates[$circuitBreakerName])) {
|
||||
$this->circuitStates[$circuitBreakerName]['metrics'] = [];
|
||||
$this->circuitStates[$circuitBreakerName]['state_transitions'] = [];
|
||||
$this->circuitStates[$circuitBreakerName]['alerts'] = [];
|
||||
}
|
||||
} else {
|
||||
foreach ($this->circuitStates as $circuitName => &$circuitData) {
|
||||
$circuitData['metrics'] = [];
|
||||
$circuitData['state_transitions'] = [];
|
||||
$circuitData['alerts'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
$this->logInfo("Monitoring data cleared" . ($circuitBreakerName ? " for {$circuitBreakerName}" : ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Export monitoring data.
|
||||
*/
|
||||
public function exportData(string $format = 'json'): string
|
||||
{
|
||||
$data = [
|
||||
'config' => $this->config,
|
||||
'circuit_states' => $this->circuitStates,
|
||||
'alerts' => $this->alerts,
|
||||
'dashboard' => $this->getDashboardData(),
|
||||
'exported_at' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
switch ($format) {
|
||||
case 'json':
|
||||
return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
case 'php':
|
||||
return '<?php return ' . var_export($data, true) . ';';
|
||||
default:
|
||||
throw new \InvalidArgumentException("Unsupported export format: {$format}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate circuit metrics.
|
||||
*/
|
||||
protected function calculateCircuitMetrics(string $circuitBreakerName): array
|
||||
{
|
||||
if (!isset($this->circuitStates[$circuitBreakerName])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$metrics = $this->circuitStates[$circuitBreakerName]['metrics'];
|
||||
|
||||
if (empty($metrics)) {
|
||||
return [
|
||||
'total_requests' => 0,
|
||||
'success_count' => 0,
|
||||
'failure_count' => 0,
|
||||
'success_rate' => 100,
|
||||
'error_rate' => 0,
|
||||
'average_response_time' => 0,
|
||||
'min_response_time' => 0,
|
||||
'max_response_time' => 0,
|
||||
'uptime_percentage' => 100
|
||||
];
|
||||
}
|
||||
|
||||
$totalRequests = count($metrics);
|
||||
$successCount = 0;
|
||||
$failureCount = 0;
|
||||
$totalResponseTime = 0;
|
||||
$minResponseTime = PHP_FLOAT_MAX;
|
||||
$maxResponseTime = 0;
|
||||
|
||||
foreach ($metrics as $metric) {
|
||||
$responseTime = $metric['duration'];
|
||||
|
||||
if ($metric['type'] === 'success') {
|
||||
$successCount++;
|
||||
} else {
|
||||
$failureCount++;
|
||||
}
|
||||
|
||||
$totalResponseTime += $responseTime;
|
||||
$minResponseTime = min($minResponseTime, $responseTime);
|
||||
$maxResponseTime = max($maxResponseTime, $responseTime);
|
||||
}
|
||||
|
||||
return [
|
||||
'total_requests' => $totalRequests,
|
||||
'success_count' => $successCount,
|
||||
'failure_count' => $failureCount,
|
||||
'success_rate' => ($successCount / $totalRequests) * 100,
|
||||
'error_rate' => ($failureCount / $totalRequests) * 100,
|
||||
'average_response_time' => $totalResponseTime / $totalRequests,
|
||||
'min_response_time' => $minResponseTime === PHP_FLOAT_MAX ? 0 : $minResponseTime,
|
||||
'max_response_time' => $maxResponseTime,
|
||||
'uptime_percentage' => ($successCount / $totalRequests) * 100
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate circuit trends.
|
||||
*/
|
||||
protected function calculateCircuitTrends(string $circuitBreakerName): array
|
||||
{
|
||||
if (!isset($this->circuitStates[$circuitBreakerName])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$metrics = $this->circuitStates[$circuitBreakerName]['metrics'];
|
||||
|
||||
// Group metrics by time windows (e.g., hourly)
|
||||
$timeWindows = [];
|
||||
$windowSize = 3600; // 1 hour
|
||||
|
||||
foreach ($metrics as $metric) {
|
||||
$window = floor($metric['timestamp'] / $windowSize) * $windowSize;
|
||||
|
||||
if (!isset($timeWindows[$window])) {
|
||||
$timeWindows[$window] = [];
|
||||
}
|
||||
|
||||
$timeWindows[$window][] = $metric;
|
||||
}
|
||||
|
||||
// Calculate trends for each window
|
||||
$trends = [
|
||||
'success_rate_trend' => [],
|
||||
'response_time_trend' => [],
|
||||
'error_rate_trend' => []
|
||||
];
|
||||
|
||||
ksort($timeWindows);
|
||||
|
||||
foreach ($timeWindows as $window => $windowMetrics) {
|
||||
$successCount = 0;
|
||||
$totalResponseTime = 0;
|
||||
|
||||
foreach ($windowMetrics as $metric) {
|
||||
if ($metric['type'] === 'success') {
|
||||
$successCount++;
|
||||
}
|
||||
$totalResponseTime += $metric['duration'];
|
||||
}
|
||||
|
||||
$totalCount = count($windowMetrics);
|
||||
$successRate = ($successCount / $totalCount) * 100;
|
||||
$averageResponseTime = $totalResponseTime / $totalCount;
|
||||
$errorRate = 100 - $successRate;
|
||||
|
||||
$trends['success_rate_trend'][] = [
|
||||
'timestamp' => $window,
|
||||
'value' => $successRate
|
||||
];
|
||||
|
||||
$trends['response_time_trend'][] = [
|
||||
'timestamp' => $window,
|
||||
'value' => $averageResponseTime
|
||||
];
|
||||
|
||||
$trends['error_rate_trend'][] = [
|
||||
'timestamp' => $window,
|
||||
'value' => $errorRate
|
||||
];
|
||||
}
|
||||
|
||||
// Calculate state change frequency
|
||||
$transitions = $this->circuitStates[$circuitBreakerName]['state_transitions'];
|
||||
$trends['state_change_frequency'] = count($transitions);
|
||||
|
||||
return $trends;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze state transition.
|
||||
*/
|
||||
protected function analyzeTransition(string $circuitBreakerName, array $transition): void
|
||||
{
|
||||
$analysis = [
|
||||
'circuit' => $circuitBreakerName,
|
||||
'transition' => $transition,
|
||||
'analysis' => []
|
||||
];
|
||||
|
||||
// Analyze transition patterns
|
||||
$transitions = $this->circuitStates[$circuitBreakerName]['state_transitions'];
|
||||
|
||||
if (count($transitions) > 1) {
|
||||
$previousTransition = $transitions[count($transitions) - 2];
|
||||
$timeSincePrevious = $transition['timestamp'] - $previousTransition['timestamp'];
|
||||
|
||||
$analysis['analysis']['time_since_previous'] = $timeSincePrevious;
|
||||
|
||||
// Check for rapid transitions
|
||||
if ($timeSincePrevious < 60) { // Less than 1 minute
|
||||
$analysis['analysis']['rapid_transition'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Store analysis
|
||||
$this->monitoringData[] = $analysis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check transition alerts.
|
||||
*/
|
||||
protected function checkTransitionAlerts(string $circuitBreakerName, array $transition): void
|
||||
{
|
||||
// Check for frequent state changes
|
||||
$transitions = $this->circuitStates[$circuitBreakerName]['state_transitions'];
|
||||
|
||||
if (count($transitions) >= 10) { // 10 transitions in monitoring period
|
||||
$this->createAlert($circuitBreakerName, 'frequent_state_changes', [
|
||||
'transition_count' => count($transitions),
|
||||
'latest_transition' => $transition
|
||||
]);
|
||||
}
|
||||
|
||||
// Check for unwanted transitions (e.g., closed -> open)
|
||||
if ($transition['from'] === 'closed' && $transition['to'] === 'open') {
|
||||
$this->createAlert($circuitBreakerName, 'circuit_opened', [
|
||||
'transition' => $transition
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check performance alerts.
|
||||
*/
|
||||
protected function checkPerformanceAlerts(string $circuitBreakerName, array $metric): void
|
||||
{
|
||||
// Check response time threshold
|
||||
$responseTimeThreshold = $this->config['alerts']['response_time_threshold'] ?? 5000; // 5 seconds
|
||||
|
||||
if ($metric['duration'] > $responseTimeThreshold) {
|
||||
$this->createAlert($circuitBreakerName, 'slow_response', [
|
||||
'duration' => $metric['duration'],
|
||||
'threshold' => $responseTimeThreshold
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check failure alerts.
|
||||
*/
|
||||
protected function checkFailureAlerts(string $circuitBreakerName, array $metric): void
|
||||
{
|
||||
// Check error rate threshold
|
||||
$metrics = $this->circuitStates[$circuitBreakerName]['metrics'];
|
||||
$recentMetrics = array_slice($metrics, -100); // Last 100 requests
|
||||
|
||||
if (count($recentMetrics) >= 10) {
|
||||
$failureCount = 0;
|
||||
foreach ($recentMetrics as $recentMetric) {
|
||||
if ($recentMetric['type'] === 'failure') {
|
||||
$failureCount++;
|
||||
}
|
||||
}
|
||||
|
||||
$errorRate = ($failureCount / count($recentMetrics)) * 100;
|
||||
$errorRateThreshold = $this->config['alerts']['error_rate_threshold'] ?? 50; // 50%
|
||||
|
||||
if ($errorRate > $errorRateThreshold) {
|
||||
$this->createAlert($circuitBreakerName, 'high_error_rate', [
|
||||
'error_rate' => $errorRate,
|
||||
'threshold' => $errorRateThreshold,
|
||||
'sample_size' => count($recentMetrics)
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create alert.
|
||||
*/
|
||||
protected function createAlert(string $circuitBreakerName, string $type, array $data): void
|
||||
{
|
||||
$alert = [
|
||||
'id' => uniqid('alert_'),
|
||||
'circuit' => $circuitBreakerName,
|
||||
'type' => $type,
|
||||
'timestamp' => microtime(true),
|
||||
'data' => $data,
|
||||
'acknowledged' => false
|
||||
];
|
||||
|
||||
$this->alerts[] = $alert;
|
||||
$this->circuitStates[$circuitBreakerName]['alerts'][] = $alert;
|
||||
|
||||
// Send to alert manager
|
||||
$this->alertManager->process($alert);
|
||||
|
||||
$this->logInfo("Alert created for {$circuitBreakerName}: {$type}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last state change.
|
||||
*/
|
||||
protected function getLastStateChange(string $circuitBreakerName): ?float
|
||||
{
|
||||
if (!isset($this->circuitStates[$circuitBreakerName])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$transitions = $this->circuitStates[$circuitBreakerName]['state_transitions'];
|
||||
|
||||
if (empty($transitions)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$lastTransition = end($transitions);
|
||||
return $lastTransition['timestamp'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate circuit report.
|
||||
*/
|
||||
protected function generateCircuitReport(string $circuitBreakerName): array
|
||||
{
|
||||
$circuitData = $this->getCircuitData($circuitBreakerName);
|
||||
|
||||
return [
|
||||
'circuit' => $circuitBreakerName,
|
||||
'period' => [
|
||||
'start' => $circuitData['started_at'],
|
||||
'end' => microtime(true)
|
||||
],
|
||||
'metrics' => $circuitData['calculated_metrics'],
|
||||
'analysis' => $circuitData['analysis'],
|
||||
'state_transitions' => $circuitData['state_transitions'],
|
||||
'alerts' => $circuitData['alerts']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate summary report.
|
||||
*/
|
||||
protected function generateSummaryReport(): array
|
||||
{
|
||||
$summary = [
|
||||
'total_circuits' => count($this->circuitStates),
|
||||
'healthy_circuits' => 0,
|
||||
'degraded_circuits' => 0,
|
||||
'failed_circuits' => 0,
|
||||
'total_requests' => 0,
|
||||
'total_successes' => 0,
|
||||
'total_failures' => 0,
|
||||
'overall_success_rate' => 0,
|
||||
'overall_average_response_time' => 0
|
||||
];
|
||||
|
||||
foreach ($this->circuitStates as $circuitName => $circuitData) {
|
||||
$metrics = $this->calculateCircuitMetrics($circuitName);
|
||||
$analysis = $this->analyzer->analyze($circuitName, $circuitData);
|
||||
|
||||
switch ($analysis['health_status']) {
|
||||
case 'healthy':
|
||||
$summary['healthy_circuits']++;
|
||||
break;
|
||||
case 'degraded':
|
||||
$summary['degraded_circuits']++;
|
||||
break;
|
||||
case 'failed':
|
||||
$summary['failed_circuits']++;
|
||||
break;
|
||||
}
|
||||
|
||||
$summary['total_requests'] += $metrics['total_requests'];
|
||||
$summary['total_successes'] += $metrics['success_count'];
|
||||
$summary['total_failures'] += $metrics['failure_count'];
|
||||
}
|
||||
|
||||
if ($summary['total_requests'] > 0) {
|
||||
$summary['overall_success_rate'] = ($summary['total_successes'] / $summary['total_requests']) * 100;
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total metrics count.
|
||||
*/
|
||||
protected function getTotalMetricsCount(): int
|
||||
{
|
||||
$total = 0;
|
||||
|
||||
foreach ($this->circuitStates as $circuitData) {
|
||||
$total += count($circuitData['metrics']);
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total transitions count.
|
||||
*/
|
||||
protected function getTotalTransitionsCount(): int
|
||||
{
|
||||
$total = 0;
|
||||
|
||||
foreach ($this->circuitStates as $circuitData) {
|
||||
$total += count($circuitData['state_transitions']);
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start monitoring loop.
|
||||
*/
|
||||
protected function startMonitoringLoop(): void
|
||||
{
|
||||
// This would typically run as a background process
|
||||
// For now, we'll just log that it would start
|
||||
$this->logInfo("Monitoring loop started");
|
||||
}
|
||||
|
||||
/**
|
||||
* Log info message.
|
||||
*/
|
||||
protected function logInfo(string $message): void
|
||||
{
|
||||
if ($this->config['logging_enabled']) {
|
||||
error_log("[CircuitMonitor] {$message}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default configuration.
|
||||
*/
|
||||
protected function getDefaultConfig(): array
|
||||
{
|
||||
return [
|
||||
'metrics_retention' => 86400, // 24 hours
|
||||
'alerts_retention' => 604800, // 7 days
|
||||
'monitoring_interval' => 60, // 1 minute
|
||||
'logging_enabled' => true,
|
||||
'alerts' => [
|
||||
'response_time_threshold' => 5000, // 5 seconds
|
||||
'error_rate_threshold' => 50, // 50%
|
||||
'state_change_threshold' => 10, // 10 transitions
|
||||
'enabled' => true
|
||||
],
|
||||
'dashboard' => [
|
||||
'refresh_interval' => 30, // 30 seconds
|
||||
'trend_window_size' => 24 // 24 hours
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration.
|
||||
*/
|
||||
public function getConfig(): array
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set configuration.
|
||||
*/
|
||||
public function setConfig(array $config): void
|
||||
{
|
||||
$this->config = array_merge($this->config, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create circuit monitor instance.
|
||||
*/
|
||||
public static function create(array $config = []): self
|
||||
{
|
||||
return new self($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create for production.
|
||||
*/
|
||||
public static function forProduction(): self
|
||||
{
|
||||
return new self([
|
||||
'metrics_retention' => 604800, // 7 days
|
||||
'alerts_retention' => 2592000, // 30 days
|
||||
'monitoring_interval' => 30, // 30 seconds
|
||||
'logging_enabled' => false,
|
||||
'alerts' => [
|
||||
'response_time_threshold' => 2000, // 2 seconds
|
||||
'error_rate_threshold' => 30, // 30%
|
||||
'enabled' => true
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create for development.
|
||||
*/
|
||||
public static function forDevelopment(): self
|
||||
{
|
||||
return new self([
|
||||
'metrics_retention' => 3600, // 1 hour
|
||||
'alerts_retention' => 86400, // 24 hours
|
||||
'monitoring_interval' => 10, // 10 seconds
|
||||
'logging_enabled' => true,
|
||||
'alerts' => [
|
||||
'response_time_threshold' => 10000, // 10 seconds
|
||||
'error_rate_threshold' => 70, // 70%
|
||||
'enabled' => true
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,718 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fendx\Service\CircuitBreaker\Recovery;
|
||||
|
||||
use Fendx\Service\CircuitBreaker\Recovery\Strategy\ExponentialBackoffStrategy;
|
||||
use Fendx\Service\CircuitBreaker\Recovery\Strategy\LinearBackoffStrategy;
|
||||
use Fendx\Service\CircuitBreaker\Recovery\Strategy\FixedIntervalStrategy;
|
||||
use Fendx\Service\CircuitBreaker\Recovery\Strategy\AdaptiveStrategy;
|
||||
|
||||
class AutoRecovery
|
||||
{
|
||||
protected array $config = [];
|
||||
protected array $strategies = [];
|
||||
protected array $recoveryHistory = [];
|
||||
protected array $activeRecoveries = [];
|
||||
protected array $recoveryMetrics = [];
|
||||
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = array_merge($this->getDefaultConfig(), $config);
|
||||
$this->initializeStrategies();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start auto recovery for a circuit breaker.
|
||||
*/
|
||||
public function startRecovery(string $circuitBreakerName, array $context = []): string
|
||||
{
|
||||
$recoveryId = $this->generateRecoveryId($circuitBreakerName);
|
||||
|
||||
$recovery = [
|
||||
'id' => $recoveryId,
|
||||
'circuit_breaker' => $circuitBreakerName,
|
||||
'strategy' => $context['strategy'] ?? $this->config['default_strategy'],
|
||||
'started_at' => microtime(true),
|
||||
'attempts' => 0,
|
||||
'max_attempts' => $context['max_attempts'] ?? $this->config['max_attempts'],
|
||||
'context' => $context,
|
||||
'status' => 'active',
|
||||
'next_attempt' => null,
|
||||
'last_attempt' => null,
|
||||
'success_count' => 0,
|
||||
'failure_count' => 0
|
||||
];
|
||||
|
||||
$this->activeRecoveries[$recoveryId] = $recovery;
|
||||
|
||||
// Schedule first attempt
|
||||
$this->scheduleNextAttempt($recoveryId);
|
||||
|
||||
$this->logInfo("Started auto recovery for {$circuitBreakerName} (ID: {$recoveryId})");
|
||||
|
||||
return $recoveryId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop auto recovery.
|
||||
*/
|
||||
public function stopRecovery(string $recoveryId): bool
|
||||
{
|
||||
if (!isset($this->activeRecoveries[$recoveryId])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$recovery = $this->activeRecoveries[$recoveryId];
|
||||
$recovery['status'] = 'stopped';
|
||||
$recovery['stopped_at'] = microtime(true);
|
||||
|
||||
// Move to history
|
||||
$this->recoveryHistory[] = $recovery;
|
||||
unset($this->activeRecoveries[$recoveryId]);
|
||||
|
||||
$this->logInfo("Stopped recovery {$recoveryId} for {$recovery['circuit_breaker']}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record recovery attempt result.
|
||||
*/
|
||||
public function recordAttempt(string $recoveryId, bool $success, array $result = []): void
|
||||
{
|
||||
if (!isset($this->activeRecoveries[$recoveryId])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$recovery = &$this->activeRecoveries[$recoveryId];
|
||||
$recovery['attempts']++;
|
||||
$recovery['last_attempt'] = microtime(true);
|
||||
|
||||
if ($success) {
|
||||
$recovery['success_count']++;
|
||||
|
||||
// Check if recovery is complete
|
||||
if ($this->isRecoveryComplete($recovery)) {
|
||||
$this->completeRecovery($recoveryId, $result);
|
||||
} else {
|
||||
// Schedule next attempt
|
||||
$this->scheduleNextAttempt($recoveryId);
|
||||
}
|
||||
} else {
|
||||
$recovery['failure_count']++;
|
||||
|
||||
// Check if max attempts reached
|
||||
if ($recovery['attempts'] >= $recovery['max_attempts']) {
|
||||
$this->failRecovery($recoveryId, $result);
|
||||
} else {
|
||||
// Schedule next attempt with backoff
|
||||
$this->scheduleNextAttempt($recoveryId);
|
||||
}
|
||||
}
|
||||
|
||||
// Record metrics
|
||||
$this->recordRecoveryMetrics($recoveryId, $success, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active recoveries.
|
||||
*/
|
||||
public function getActiveRecoveries(): array
|
||||
{
|
||||
return $this->activeRecoveries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recovery history.
|
||||
*/
|
||||
public function getRecoveryHistory(int $limit = 100): array
|
||||
{
|
||||
return array_slice($this->recoveryHistory, -$limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recovery by ID.
|
||||
*/
|
||||
public function getRecovery(string $recoveryId): ?array
|
||||
{
|
||||
return $this->activeRecoveries[$recoveryId] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recoveries for circuit breaker.
|
||||
*/
|
||||
public function getRecoveriesForCircuit(string $circuitBreakerName): array
|
||||
{
|
||||
$recoveries = [];
|
||||
|
||||
foreach ($this->activeRecoveries as $recoveryId => $recovery) {
|
||||
if ($recovery['circuit_breaker'] === $circuitBreakerName) {
|
||||
$recoveries[$recoveryId] = $recovery;
|
||||
}
|
||||
}
|
||||
|
||||
return $recoveries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recovery statistics.
|
||||
*/
|
||||
public function getStatistics(): array
|
||||
{
|
||||
$stats = [
|
||||
'active_recoveries' => count($this->activeRecoveries),
|
||||
'total_recoveries' => count($this->recoveryHistory) + count($this->activeRecoveries),
|
||||
'completed_recoveries' => 0,
|
||||
'failed_recoveries' => 0,
|
||||
'stopped_recoveries' => 0,
|
||||
'success_rate' => 0,
|
||||
'average_attempts' => 0,
|
||||
'average_duration' => 0,
|
||||
'strategy_performance' => []
|
||||
];
|
||||
|
||||
$totalAttempts = 0;
|
||||
$totalDuration = 0;
|
||||
$completedCount = 0;
|
||||
|
||||
// Analyze history
|
||||
foreach ($this->recoveryHistory as $recovery) {
|
||||
switch ($recovery['status']) {
|
||||
case 'completed':
|
||||
$stats['completed_recoveries']++;
|
||||
$completedCount++;
|
||||
break;
|
||||
case 'failed':
|
||||
$stats['failed_recoveries']++;
|
||||
break;
|
||||
case 'stopped':
|
||||
$stats['stopped_recoveries']++;
|
||||
break;
|
||||
}
|
||||
|
||||
$totalAttempts += $recovery['attempts'];
|
||||
|
||||
if (isset($recovery['completed_at']) || isset($recovery['failed_at'])) {
|
||||
$endTime = $recovery['completed_at'] ?? $recovery['failed_at'];
|
||||
$duration = $endTime - $recovery['started_at'];
|
||||
$totalDuration += $duration;
|
||||
}
|
||||
|
||||
// Strategy performance
|
||||
$strategy = $recovery['strategy'];
|
||||
if (!isset($stats['strategy_performance'][$strategy])) {
|
||||
$stats['strategy_performance'][$strategy] = [
|
||||
'total' => 0,
|
||||
'completed' => 0,
|
||||
'failed' => 0
|
||||
];
|
||||
}
|
||||
|
||||
$stats['strategy_performance'][$strategy]['total']++;
|
||||
|
||||
if ($recovery['status'] === 'completed') {
|
||||
$stats['strategy_performance'][$strategy]['completed']++;
|
||||
} elseif ($recovery['status'] === 'failed') {
|
||||
$stats['strategy_performance'][$strategy]['failed']++;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate averages and rates
|
||||
if ($stats['total_recoveries'] > 0) {
|
||||
$stats['success_rate'] = ($stats['completed_recoveries'] / $stats['total_recoveries']) * 100;
|
||||
}
|
||||
|
||||
if ($completedCount > 0) {
|
||||
$stats['average_attempts'] = $totalAttempts / $completedCount;
|
||||
$stats['average_duration'] = $totalDuration / $completedCount;
|
||||
}
|
||||
|
||||
// Calculate strategy success rates
|
||||
foreach ($stats['strategy_performance'] as $strategy => &$performance) {
|
||||
if ($performance['total'] > 0) {
|
||||
$performance['success_rate'] = ($performance['completed'] / $performance['total']) * 100;
|
||||
} else {
|
||||
$performance['success_rate'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recovery metrics.
|
||||
*/
|
||||
public function getRecoveryMetrics(string $circuitBreakerName = null): array
|
||||
{
|
||||
if ($circuitBreakerName) {
|
||||
return $this->recoveryMetrics[$circuitBreakerName] ?? [];
|
||||
}
|
||||
|
||||
return $this->recoveryMetrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if recovery should be triggered.
|
||||
*/
|
||||
public function shouldTriggerRecovery(string $circuitBreakerName, array $context = []): bool
|
||||
{
|
||||
// Check if there's already an active recovery
|
||||
$activeRecoveries = $this->getRecoveriesForCircuit($circuitBreakerName);
|
||||
if (!empty($activeRecoveries)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check cooldown period
|
||||
$lastRecovery = $this->getLastRecovery($circuitBreakerName);
|
||||
if ($lastRecovery && isset($lastRecovery['completed_at'])) {
|
||||
$timeSinceLastRecovery = microtime(true) - $lastRecovery['completed_at'];
|
||||
$cooldownPeriod = $context['cooldown_period'] ?? $this->config['cooldown_period'];
|
||||
|
||||
if ($timeSinceLastRecovery < $cooldownPeriod) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check failure conditions
|
||||
return $this->checkFailureConditions($circuitBreakerName, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger automatic recovery if conditions are met.
|
||||
*/
|
||||
public function triggerAutoRecovery(string $circuitBreakerName, array $context = []): ?string
|
||||
{
|
||||
if (!$this->shouldTriggerRecovery($circuitBreakerName, $context)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Select best strategy
|
||||
$strategy = $this->selectBestStrategy($circuitBreakerName, $context);
|
||||
$context['strategy'] = $strategy;
|
||||
|
||||
return $this->startRecovery($circuitBreakerName, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom recovery strategy.
|
||||
*/
|
||||
public function addStrategy(string $name, callable $strategy): void
|
||||
{
|
||||
$this->strategies[$name] = $strategy;
|
||||
|
||||
$this->logInfo("Added custom recovery strategy: {$name}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next attempt time for recovery.
|
||||
*/
|
||||
public function getNextAttemptTime(string $recoveryId): ?float
|
||||
{
|
||||
if (!isset($this->activeRecoveries[$recoveryId])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->activeRecoveries[$recoveryId]['next_attempt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get time until next attempt.
|
||||
*/
|
||||
public function getTimeUntilNextAttempt(string $recoveryId): ?float
|
||||
{
|
||||
$nextAttempt = $this->getNextAttemptTime($recoveryId);
|
||||
|
||||
if ($nextAttempt === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return max(0, $nextAttempt - microtime(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all recoveries for a circuit breaker.
|
||||
*/
|
||||
public function cancelAllRecoveries(string $circuitBreakerName): int
|
||||
{
|
||||
$cancelled = 0;
|
||||
|
||||
foreach ($this->activeRecoveries as $recoveryId => $recovery) {
|
||||
if ($recovery['circuit_breaker'] === $circuitBreakerName) {
|
||||
$this->stopRecovery($recoveryId);
|
||||
$cancelled++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->logInfo("Cancelled {$cancelled} recoveries for {$circuitBreakerName}");
|
||||
|
||||
return $cancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all recovery state.
|
||||
*/
|
||||
public function reset(): void
|
||||
{
|
||||
// Stop all active recoveries
|
||||
foreach (array_keys($this->activeRecoveries) as $recoveryId) {
|
||||
$this->stopRecovery($recoveryId);
|
||||
}
|
||||
|
||||
$this->recoveryHistory = [];
|
||||
$this->recoveryMetrics = [];
|
||||
|
||||
$this->logInfo("Auto recovery reset");
|
||||
}
|
||||
|
||||
/**
|
||||
* Export recovery data.
|
||||
*/
|
||||
public function exportData(): array
|
||||
{
|
||||
return [
|
||||
'config' => $this->config,
|
||||
'active_recoveries' => $this->activeRecoveries,
|
||||
'recovery_history' => $this->recoveryHistory,
|
||||
'recovery_metrics' => $this->recoveryMetrics,
|
||||
'statistics' => $this->getStatistics(),
|
||||
'exported_at' => date('Y-m-d H:i:s')
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule next recovery attempt.
|
||||
*/
|
||||
protected function scheduleNextAttempt(string $recoveryId): void
|
||||
{
|
||||
if (!isset($this->activeRecoveries[$recoveryId])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$recovery = &$this->activeRecoveries[$recoveryId];
|
||||
$strategy = $recovery['strategy'];
|
||||
|
||||
if (!isset($this->strategies[$strategy])) {
|
||||
$this->logError("Unknown recovery strategy: {$strategy}");
|
||||
return;
|
||||
}
|
||||
|
||||
$delay = $this->strategies[$strategy]($recovery['attempts'], $recovery['context']);
|
||||
$recovery['next_attempt'] = microtime(true) + $delay;
|
||||
|
||||
$this->logInfo("Scheduled next attempt for {$recoveryId} in {$delay} seconds");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if recovery is complete.
|
||||
*/
|
||||
protected function isRecoveryComplete(array $recovery): bool
|
||||
{
|
||||
$requiredSuccesses = $recovery['context']['required_successes'] ??
|
||||
$this->config['required_successes'];
|
||||
|
||||
return $recovery['success_count'] >= $requiredSuccesses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete recovery successfully.
|
||||
*/
|
||||
protected function completeRecovery(string $recoveryId, array $result): void
|
||||
{
|
||||
if (!isset($this->activeRecoveries[$recoveryId])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$recovery = &$this->activeRecoveries[$recoveryId];
|
||||
$recovery['status'] = 'completed';
|
||||
$recovery['completed_at'] = microtime(true);
|
||||
$recovery['result'] = $result;
|
||||
|
||||
// Move to history
|
||||
$this->recoveryHistory[] = $recovery;
|
||||
unset($this->activeRecoveries[$recoveryId]);
|
||||
|
||||
$this->logInfo("Recovery {$recoveryId} completed successfully for {$recovery['circuit_breaker']}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark recovery as failed.
|
||||
*/
|
||||
protected function failRecovery(string $recoveryId, array $result): void
|
||||
{
|
||||
if (!isset($this->activeRecoveries[$recoveryId])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$recovery = &$this->activeRecoveries[$recoveryId];
|
||||
$recovery['status'] = 'failed';
|
||||
$recovery['failed_at'] = microtime(true);
|
||||
$recovery['result'] = $result;
|
||||
|
||||
// Move to history
|
||||
$this->recoveryHistory[] = $recovery;
|
||||
unset($this->activeRecoveries[$recoveryId]);
|
||||
|
||||
$this->logInfo("Recovery {$recoveryId} failed for {$recovery['circuit_breaker']}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Record recovery metrics.
|
||||
*/
|
||||
protected function recordRecoveryMetrics(string $recoveryId, bool $success, array $result): void
|
||||
{
|
||||
if (!isset($this->activeRecoveries[$recoveryId])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$recovery = $this->activeRecoveries[$recoveryId];
|
||||
$circuitBreakerName = $recovery['circuit_breaker'];
|
||||
|
||||
if (!isset($this->recoveryMetrics[$circuitBreakerName])) {
|
||||
$this->recoveryMetrics[$circuitBreakerName] = [
|
||||
'total_attempts' => 0,
|
||||
'successful_attempts' => 0,
|
||||
'failed_attempts' => 0,
|
||||
'last_attempt' => null,
|
||||
'average_attempt_duration' => 0
|
||||
];
|
||||
}
|
||||
|
||||
$metrics = &$this->recoveryMetrics[$circuitBreakerName];
|
||||
$metrics['total_attempts']++;
|
||||
$metrics['last_attempt'] = microtime(true);
|
||||
|
||||
if ($success) {
|
||||
$metrics['successful_attempts']++;
|
||||
} else {
|
||||
$metrics['failed_attempts']++;
|
||||
}
|
||||
|
||||
// Update average duration if provided
|
||||
if (isset($result['duration'])) {
|
||||
$currentAvg = $metrics['average_attempt_duration'];
|
||||
$totalAttempts = $metrics['total_attempts'];
|
||||
$metrics['average_attempt_duration'] =
|
||||
(($currentAvg * ($totalAttempts - 1)) + $result['duration']) / $totalAttempts;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last recovery for circuit breaker.
|
||||
*/
|
||||
protected function getLastRecovery(string $circuitBreakerName): ?array
|
||||
{
|
||||
// Check active recoveries first
|
||||
foreach ($this->activeRecoveries as $recovery) {
|
||||
if ($recovery['circuit_breaker'] === $circuitBreakerName) {
|
||||
return $recovery;
|
||||
}
|
||||
}
|
||||
|
||||
// Check history
|
||||
for ($i = count($this->recoveryHistory) - 1; $i >= 0; $i--) {
|
||||
if ($this->recoveryHistory[$i]['circuit_breaker'] === $circuitBreakerName) {
|
||||
return $this->recoveryHistory[$i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check failure conditions.
|
||||
*/
|
||||
protected function checkFailureConditions(string $circuitBreakerName, array $context): bool
|
||||
{
|
||||
$metrics = $this->recoveryMetrics[$circuitBreakerName] ?? [];
|
||||
|
||||
// Check failure rate
|
||||
$failureRateThreshold = $context['failure_rate_threshold'] ?? 80.0;
|
||||
if ($metrics['total_attempts'] > 0) {
|
||||
$failureRate = ($metrics['failed_attempts'] / $metrics['total_attempts']) * 100;
|
||||
if ($failureRate < $failureRateThreshold) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check minimum attempts
|
||||
$minAttempts = $context['min_attempts'] ?? $this->config['min_attempts'];
|
||||
if ($metrics['total_attempts'] < $minAttempts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select best recovery strategy.
|
||||
*/
|
||||
protected function selectBestStrategy(string $circuitBreakerName, array $context): string
|
||||
{
|
||||
$metrics = $this->recoveryMetrics[$circuitBreakerName] ?? [];
|
||||
|
||||
// Analyze past performance
|
||||
$strategyPerformance = [];
|
||||
|
||||
foreach ($this->recoveryHistory as $recovery) {
|
||||
if ($recovery['circuit_breaker'] === $circuitBreakerName) {
|
||||
$strategy = $recovery['strategy'];
|
||||
if (!isset($strategyPerformance[$strategy])) {
|
||||
$strategyPerformance[$strategy] = [
|
||||
'total' => 0,
|
||||
'successful' => 0
|
||||
];
|
||||
}
|
||||
|
||||
$strategyPerformance[$strategy]['total']++;
|
||||
if ($recovery['status'] === 'completed') {
|
||||
$strategyPerformance[$strategy]['successful']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Select strategy with best success rate
|
||||
$bestStrategy = $this->config['default_strategy'];
|
||||
$bestSuccessRate = 0;
|
||||
|
||||
foreach ($strategyPerformance as $strategy => $performance) {
|
||||
if ($performance['total'] > 0) {
|
||||
$successRate = ($performance['successful'] / $performance['total']) * 100;
|
||||
if ($successRate > $bestSuccessRate) {
|
||||
$bestSuccessRate = $successRate;
|
||||
$bestStrategy = $strategy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $bestStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate unique recovery ID.
|
||||
*/
|
||||
protected function generateRecoveryId(string $circuitBreakerName): string
|
||||
{
|
||||
return $circuitBreakerName . '_' . uniqid() . '_' . time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize built-in strategies.
|
||||
*/
|
||||
protected function initializeStrategies(): void
|
||||
{
|
||||
$this->strategies['exponential_backoff'] = new ExponentialBackoffStrategy($this->config);
|
||||
$this->strategies['linear_backoff'] = new LinearBackoffStrategy($this->config);
|
||||
$this->strategies['fixed_interval'] = new FixedIntervalStrategy($this->config);
|
||||
$this->strategies['adaptive'] = new AdaptiveStrategy($this->config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log error message.
|
||||
*/
|
||||
protected function logError(string $message): void
|
||||
{
|
||||
if ($this->config['logging_enabled']) {
|
||||
error_log("[AutoRecovery] ERROR: {$message}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log info message.
|
||||
*/
|
||||
protected function logInfo(string $message): void
|
||||
{
|
||||
if ($this->config['logging_enabled']) {
|
||||
error_log("[AutoRecovery] {$message}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default configuration.
|
||||
*/
|
||||
protected function getDefaultConfig(): array
|
||||
{
|
||||
return [
|
||||
'default_strategy' => 'exponential_backoff',
|
||||
'max_attempts' => 5,
|
||||
'required_successes' => 3,
|
||||
'cooldown_period' => 300, // 5 minutes
|
||||
'min_attempts' => 3,
|
||||
'logging_enabled' => true,
|
||||
'strategies' => [
|
||||
'exponential_backoff' => [
|
||||
'base_delay' => 1.0,
|
||||
'max_delay' => 300.0,
|
||||
'multiplier' => 2.0
|
||||
],
|
||||
'linear_backoff' => [
|
||||
'base_delay' => 5.0,
|
||||
'max_delay' => 300.0,
|
||||
'increment' => 10.0
|
||||
],
|
||||
'fixed_interval' => [
|
||||
'delay' => 30.0
|
||||
],
|
||||
'adaptive' => [
|
||||
'min_delay' => 1.0,
|
||||
'max_delay' => 300.0,
|
||||
'success_factor' => 0.8,
|
||||
'failure_factor' => 1.5
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration.
|
||||
*/
|
||||
public function getConfig(): array
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set configuration.
|
||||
*/
|
||||
public function setConfig(array $config): void
|
||||
{
|
||||
$this->config = array_merge($this->config, $config);
|
||||
$this->initializeStrategies();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create auto recovery instance.
|
||||
*/
|
||||
public static function create(array $config = []): self
|
||||
{
|
||||
return new self($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create for production.
|
||||
*/
|
||||
public static function forProduction(): self
|
||||
{
|
||||
return new self([
|
||||
'max_attempts' => 10,
|
||||
'required_successes' => 5,
|
||||
'cooldown_period' => 600, // 10 minutes
|
||||
'min_attempts' => 5,
|
||||
'logging_enabled' => false
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create for development.
|
||||
*/
|
||||
public static function forDevelopment(): self
|
||||
{
|
||||
return new self([
|
||||
'max_attempts' => 3,
|
||||
'required_successes' => 2,
|
||||
'cooldown_period' => 60, // 1 minute
|
||||
'min_attempts' => 2,
|
||||
'logging_enabled' => true
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user