Init
This commit is contained in:
84
app/Temporal/SystemMonitor/SystemMonitorActivity.php
Normal file
84
app/Temporal/SystemMonitor/SystemMonitorActivity.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Temporal\SystemMonitor;
|
||||
|
||||
use App\Models\ImportJob;
|
||||
use App\Models\Order;
|
||||
use App\Models\OrderItem;
|
||||
use App\Models\Product;
|
||||
use App\Models\User;
|
||||
use App\Temporal\Shared\FaultSimulator;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Temporal\Activity;
|
||||
|
||||
class SystemMonitorActivity implements SystemMonitorActivityInterface
|
||||
{
|
||||
public function runHealthCheck(array $simulationConfig = []): array
|
||||
{
|
||||
FaultSimulator::maybeApply($simulationConfig, 'runHealthCheck');
|
||||
|
||||
$checks = [];
|
||||
$issues = 0;
|
||||
|
||||
// 1. DB connectivity
|
||||
try {
|
||||
DB::select('SELECT 1');
|
||||
$checks['db_connected'] = true;
|
||||
} catch (\Throwable) {
|
||||
$checks['db_connected'] = false;
|
||||
$issues += 3; // critical
|
||||
}
|
||||
|
||||
Activity::heartbeat(['step' => 'db_connectivity']);
|
||||
|
||||
// 2. Table row counts
|
||||
$checks['product_count'] = Product::count();
|
||||
$checks['order_count'] = Order::count();
|
||||
$checks['order_item_count'] = OrderItem::count();
|
||||
$checks['user_count'] = User::count();
|
||||
$checks['import_job_count'] = ImportJob::count();
|
||||
|
||||
Activity::heartbeat(['step' => 'row_counts']);
|
||||
|
||||
// 3. Stale data check — pending orders older than 24h
|
||||
$checks['pending_stale_orders'] = Order::where('status', 'pending')
|
||||
->where('created_at', '<', now()->subHours(24))
|
||||
->count();
|
||||
if ($checks['pending_stale_orders'] > 0) {
|
||||
$issues++;
|
||||
}
|
||||
|
||||
Activity::heartbeat(['step' => 'stale_data']);
|
||||
|
||||
// 4. Stock alerts — active products with zero stock
|
||||
$checks['out_of_stock_products'] = Product::where('stock_quantity', 0)
|
||||
->where('status', 'active')
|
||||
->count();
|
||||
if ($checks['out_of_stock_products'] > 5) {
|
||||
$issues++;
|
||||
}
|
||||
|
||||
Activity::heartbeat(['step' => 'stock_alerts']);
|
||||
|
||||
// 5. Stuck import jobs — started but not updated in 30 min
|
||||
$checks['stuck_import_jobs'] = ImportJob::where('status', 'started')
|
||||
->where('updated_at', '<', now()->subMinutes(30))
|
||||
->count();
|
||||
if ($checks['stuck_import_jobs'] > 0) {
|
||||
$issues++;
|
||||
}
|
||||
|
||||
Activity::heartbeat(['step' => 'stuck_jobs']);
|
||||
|
||||
// Compute health score (100 = perfect, deduct per issue)
|
||||
$healthScore = max(0, 100 - ($issues * 10));
|
||||
|
||||
return [
|
||||
'timestamp' => now()->toISOString(),
|
||||
'checks' => $checks,
|
||||
'healthScore' => $healthScore,
|
||||
'issues' => $issues,
|
||||
'attempt' => Activity::getInfo()->attempt,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Temporal\SystemMonitor;
|
||||
|
||||
use Temporal\Activity\ActivityInterface;
|
||||
use Temporal\Activity\ActivityMethod;
|
||||
|
||||
#[ActivityInterface]
|
||||
interface SystemMonitorActivityInterface
|
||||
{
|
||||
#[ActivityMethod]
|
||||
public function runHealthCheck(array $simulationConfig = []): array;
|
||||
}
|
||||
129
app/Temporal/SystemMonitor/SystemMonitorWorkflow.php
Normal file
129
app/Temporal/SystemMonitor/SystemMonitorWorkflow.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace App\Temporal\SystemMonitor;
|
||||
|
||||
use Carbon\CarbonInterval;
|
||||
use Temporal\Activity\ActivityOptions;
|
||||
use Temporal\Common\RetryOptions;
|
||||
use Temporal\Workflow;
|
||||
|
||||
class SystemMonitorWorkflow implements SystemMonitorWorkflowInterface
|
||||
{
|
||||
private bool $stopped = false;
|
||||
private int $iteration = 0;
|
||||
private int $totalIterations = 0;
|
||||
private array $checkHistory = [];
|
||||
private string $startedAt = '';
|
||||
private string $lastCheckAt = '';
|
||||
private float $healthScore = 100;
|
||||
private string $status = 'initializing';
|
||||
|
||||
/** @var SystemMonitorActivityInterface */
|
||||
private $activityStub;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->activityStub = Workflow::newActivityStub(
|
||||
SystemMonitorActivityInterface::class,
|
||||
ActivityOptions::new()
|
||||
->withStartToCloseTimeout(CarbonInterval::seconds(60))
|
||||
->withHeartbeatTimeout(CarbonInterval::seconds(15))
|
||||
->withRetryOptions(
|
||||
RetryOptions::new()->withMaximumAttempts(3)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function monitor(int $intervalSeconds = 60, int $maxIterations = 30, array $state = [], array $simulationConfig = []): \Generator
|
||||
{
|
||||
// Restore state from previous Continue-As-New run, or initialize
|
||||
if (!empty($state)) {
|
||||
$this->iteration = $state['iteration'] ?? 0;
|
||||
$this->totalIterations = $state['totalIterations'] ?? $maxIterations;
|
||||
$this->checkHistory = $state['checkHistory'] ?? [];
|
||||
$this->startedAt = $state['startedAt'] ?? Workflow::now()->format('c');
|
||||
$this->healthScore = $state['healthScore'] ?? 100;
|
||||
} else {
|
||||
$this->totalIterations = $maxIterations;
|
||||
$this->startedAt = Workflow::now()->format('c');
|
||||
}
|
||||
|
||||
$this->status = 'monitoring';
|
||||
|
||||
try {
|
||||
while ($this->iteration < $this->totalIterations && !$this->stopped) {
|
||||
// Run health check activity
|
||||
$result = yield $this->activityStub->runHealthCheck($simulationConfig);
|
||||
|
||||
// Store in check history (keep last 10)
|
||||
$this->lastCheckAt = $result['timestamp'] ?? Workflow::now()->format('c');
|
||||
$this->checkHistory[] = $result;
|
||||
if (count($this->checkHistory) > 10) {
|
||||
$this->checkHistory = array_slice($this->checkHistory, -10);
|
||||
}
|
||||
|
||||
// Update health score (rolling average of last checks)
|
||||
$scores = array_column($this->checkHistory, 'healthScore');
|
||||
$this->healthScore = count($scores) > 0 ? round(array_sum($scores) / count($scores), 1) : 100;
|
||||
|
||||
$this->iteration++;
|
||||
|
||||
// Check if we should Continue-As-New
|
||||
if (Workflow::getInfo()->shouldContinueAsNew || ($this->iteration % 15 === 0 && $this->iteration < $this->totalIterations)) {
|
||||
$continueStub = Workflow::newContinueAsNewStub(SystemMonitorWorkflowInterface::class);
|
||||
|
||||
return yield $continueStub->monitor(
|
||||
$intervalSeconds,
|
||||
$this->totalIterations,
|
||||
[
|
||||
'iteration' => $this->iteration,
|
||||
'totalIterations' => $this->totalIterations,
|
||||
'checkHistory' => $this->checkHistory,
|
||||
'startedAt' => $this->startedAt,
|
||||
'healthScore' => $this->healthScore,
|
||||
],
|
||||
$simulationConfig
|
||||
);
|
||||
}
|
||||
|
||||
// Don't sleep after the last iteration or if stopped
|
||||
if ($this->iteration < $this->totalIterations && !$this->stopped) {
|
||||
yield Workflow::timer($intervalSeconds);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$this->status = 'failed';
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->status = $this->stopped ? 'stopped' : 'completed';
|
||||
|
||||
return [
|
||||
'status' => $this->status,
|
||||
'totalChecks' => $this->iteration,
|
||||
'avgHealthScore' => $this->healthScore,
|
||||
'lastCheckAt' => $this->lastCheckAt,
|
||||
'startedAt' => $this->startedAt,
|
||||
'checkHistory' => $this->checkHistory,
|
||||
];
|
||||
}
|
||||
|
||||
public function stop(): void
|
||||
{
|
||||
$this->stopped = true;
|
||||
}
|
||||
|
||||
public function getStatus(): array
|
||||
{
|
||||
return [
|
||||
'status' => $this->status,
|
||||
'stopped' => $this->stopped,
|
||||
'iteration' => $this->iteration,
|
||||
'totalIterations' => $this->totalIterations,
|
||||
'healthScore' => $this->healthScore,
|
||||
'startedAt' => $this->startedAt,
|
||||
'lastCheckAt' => $this->lastCheckAt,
|
||||
'checkHistory' => $this->checkHistory,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Temporal\SystemMonitor;
|
||||
|
||||
use Temporal\Workflow\QueryMethod;
|
||||
use Temporal\Workflow\SignalMethod;
|
||||
use Temporal\Workflow\WorkflowInterface;
|
||||
use Temporal\Workflow\WorkflowMethod;
|
||||
|
||||
#[WorkflowInterface]
|
||||
interface SystemMonitorWorkflowInterface
|
||||
{
|
||||
#[WorkflowMethod]
|
||||
public function monitor(int $intervalSeconds = 60, int $maxIterations = 30, array $state = [], array $simulationConfig = []);
|
||||
|
||||
#[SignalMethod]
|
||||
public function stop(): void;
|
||||
|
||||
#[QueryMethod]
|
||||
public function getStatus(): array;
|
||||
}
|
||||
Reference in New Issue
Block a user