상태 캐시 의미 테이블 전환
This commit is contained in:
@@ -72,7 +72,9 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를
|
|||||||
|
|
||||||
- `control_state`: 팬 모드와 PWM 상태
|
- `control_state`: 팬 모드와 PWM 상태
|
||||||
- `sensor_logs`: 온도, RPM, PWM, load, memory, disk, uptime. 과거 배터리 컬럼은 호환을 위해 유지하지만 UPS 제거 상태에서는 새 배터리값을 기록하지 않습니다.
|
- `sensor_logs`: 온도, RPM, PWM, load, memory, disk, uptime. 과거 배터리 컬럼은 호환을 위해 유지하지만 UPS 제거 상태에서는 새 배터리값을 기록하지 않습니다.
|
||||||
- `app_json_store`: 기존 JSON 파일 저장소를 대체하는 DB 기반 JSON 상태/캐시 저장 테이블. 저전압/스로틀링 episode 상태, 배터리 프로파일 캐시, 사용자 서비스 캐시를 저장합니다.
|
- `power_event_states`: 저전압/스로틀링 episode 상태, 최근 지속시간, 최근 이벤트 이력
|
||||||
|
- `battery_profile_cache`: 배터리 잔여시간 계산용 장기 프로파일 캐시
|
||||||
|
- `systemd_service_cache`: 사용자 생성 systemd 서비스 목록과 최근 journal 로그의 짧은 TTL 캐시
|
||||||
- `system_notice_state`: notice 활성 상태와 기준값
|
- `system_notice_state`: notice 활성 상태와 기준값
|
||||||
- `system_notice_logs`: notice 발생 이력
|
- `system_notice_logs`: notice 발생 이력
|
||||||
- `wifi_observed_sessions`: 5G WiFi/LAN client의 최초/마지막 감지 시간
|
- `wifi_observed_sessions`: 5G WiFi/LAN client의 최초/마지막 감지 시간
|
||||||
@@ -186,11 +188,11 @@ control-wifi-observe.service
|
|||||||
## 주요 함수/모듈
|
## 주요 함수/모듈
|
||||||
|
|
||||||
- `collect_snapshot()`: 센서와 fan 상태 snapshot 생성
|
- `collect_snapshot()`: 센서와 fan 상태 snapshot 생성
|
||||||
- `custom_systemd_services()`: `/etc/systemd/system/*.service` 단위를 조회해 사용자 생성 서비스 상태와 최근 journal 로그를 구성하고 `app_json_store`에 짧은 TTL 캐시로 저장
|
- `custom_systemd_services()`: `/etc/systemd/system/*.service` 단위를 조회해 사용자 생성 서비스 상태와 최근 journal 로그를 구성하고 `systemd_service_cache`에 짧은 TTL 캐시로 저장
|
||||||
- `systemd_service_logs()`: 서비스별 최근 journal 로그 조회
|
- `systemd_service_logs()`: 서비스별 최근 journal 로그 조회
|
||||||
- `read_throttled_flags()`: `vcgencmd get_throttled`를 직접 조회하고, 권한 문제로 실패하면 sudo 경유 조회를 시도
|
- `read_throttled_flags()`: `vcgencmd get_throttled`를 직접 조회하고, 권한 문제로 실패하면 sudo 경유 조회를 시도
|
||||||
- `throttled_event_status()`: 저전압/스로틀링 episode 이력, 복구 중/반복 발생 판정, 최근 10분 통계를 계산
|
- `throttled_event_status()`: 저전압/스로틀링 episode 이력, 복구 중/반복 발생 판정, 최근 10분 통계를 계산
|
||||||
- `throttled_statuses()`: 라즈베리파이 throttled flag를 읽고 저전압/스로틀링 감지 이력을 `app_json_store`로 추적
|
- `throttled_statuses()`: 라즈베리파이 throttled flag를 읽고 저전압/스로틀링 감지 이력을 `power_event_states`로 추적
|
||||||
- `apply_fan_policy()`: 팬 목표값 계산과 적용
|
- `apply_fan_policy()`: 팬 목표값 계산과 적용
|
||||||
- `fan_control_temp()`: CPU/RP1 혼합 팬 판단 온도 계산
|
- `fan_control_temp()`: CPU/RP1 혼합 팬 판단 온도 계산
|
||||||
- `json_out()`: API JSON 응답 표준화
|
- `json_out()`: API JSON 응답 표준화
|
||||||
@@ -199,7 +201,7 @@ control-wifi-observe.service
|
|||||||
- `apply_wifi_estimates()`: WiFi 신호/송신 속도/수신 속도 결측값 보정
|
- `apply_wifi_estimates()`: WiFi 신호/송신 속도/수신 속도 결측값 보정
|
||||||
- `battery_status()`: `CONTROL_BATTERY_ENABLED`가 true일 때만 배터리 센서를 읽고, 현재 기본 구성에서는 제거 상태를 반환
|
- `battery_status()`: `CONTROL_BATTERY_ENABLED`가 true일 때만 배터리 센서를 읽고, 현재 기본 구성에서는 제거 상태를 반환
|
||||||
- `battery_trend_history()`: 배터리 기능을 다시 켤 때 최근 24시간 배터리 기록을 1분 단위로 집계
|
- `battery_trend_history()`: 배터리 기능을 다시 켤 때 최근 24시간 배터리 기록을 1분 단위로 집계
|
||||||
- `battery_profile_history()`: 배터리 기능을 다시 켤 때 최대 45일 배터리 기록을 약 5분 간격으로 샘플링하고 파일 캐시로 보관
|
- `battery_profile_history()`: 배터리 기능을 다시 켤 때 최대 45일 배터리 기록을 약 5분 간격으로 샘플링하고 `battery_profile_cache`에 보관
|
||||||
- `weighted_linear_regression()`, `numeric_mad()`: SOC 기울기 계산과 회귀 잔차 튐 제거
|
- `weighted_linear_regression()`, `numeric_mad()`: SOC 기울기 계산과 회귀 잔차 튐 제거
|
||||||
- `battery_regression_trend_candidate()`: 1분 SOC 집계를 강건 회귀로 분석해 정밀 방전 속도 후보 계산
|
- `battery_regression_trend_candidate()`: 1분 SOC 집계를 강건 회귀로 분석해 정밀 방전 속도 후보 계산
|
||||||
- `battery_voltage_trend_candidate()`: 전압 기울기와 동적 하한 전압으로 잔여 시간 후보 계산
|
- `battery_voltage_trend_candidate()`: 전압 기울기와 동적 하한 전압으로 잔여 시간 후보 계산
|
||||||
|
|||||||
+93
-7
@@ -427,20 +427,106 @@ function bootstrap_db(): void
|
|||||||
");
|
");
|
||||||
|
|
||||||
$pdo->exec("
|
$pdo->exec("
|
||||||
CREATE TABLE IF NOT EXISTS app_json_store (
|
CREATE TABLE IF NOT EXISTS power_event_states (
|
||||||
store_key VARCHAR(128) NOT NULL PRIMARY KEY,
|
event_key VARCHAR(32) NOT NULL PRIMARY KEY,
|
||||||
payload LONGTEXT NOT NULL,
|
active TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
expires_at DATETIME NULL,
|
active_since BIGINT NULL,
|
||||||
|
last_detected_at BIGINT NULL,
|
||||||
|
last_cleared_at BIGINT NULL,
|
||||||
|
last_duration_seconds INT NOT NULL DEFAULT 0,
|
||||||
|
event_history LONGTEXT NOT NULL,
|
||||||
|
refreshed_at BIGINT NULL,
|
||||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
ON UPDATE CURRENT_TIMESTAMP,
|
ON UPDATE CURRENT_TIMESTAMP
|
||||||
INDEX idx_expires_at (expires_at),
|
|
||||||
INDEX idx_updated_at (updated_at)
|
|
||||||
) ENGINE=InnoDB
|
) ENGINE=InnoDB
|
||||||
DEFAULT CHARSET=utf8mb4
|
DEFAULT CHARSET=utf8mb4
|
||||||
COLLATE=utf8mb4_unicode_ci
|
COLLATE=utf8mb4_unicode_ci
|
||||||
");
|
");
|
||||||
|
|
||||||
|
$pdo->exec("
|
||||||
|
CREATE TABLE IF NOT EXISTS battery_profile_cache (
|
||||||
|
days SMALLINT UNSIGNED NOT NULL PRIMARY KEY,
|
||||||
|
created_at_epoch BIGINT NOT NULL,
|
||||||
|
sample_rows LONGTEXT NOT NULL,
|
||||||
|
expires_at DATETIME NOT NULL,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_expires_at (expires_at)
|
||||||
|
) ENGINE=InnoDB
|
||||||
|
DEFAULT CHARSET=utf8mb4
|
||||||
|
COLLATE=utf8mb4_unicode_ci
|
||||||
|
");
|
||||||
|
|
||||||
|
$pdo->exec("
|
||||||
|
CREATE TABLE IF NOT EXISTS systemd_service_cache (
|
||||||
|
cache_key TINYINT UNSIGNED NOT NULL PRIMARY KEY,
|
||||||
|
captured_at BIGINT NOT NULL,
|
||||||
|
service_rows LONGTEXT NOT NULL,
|
||||||
|
expires_at DATETIME NOT NULL,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_expires_at (expires_at)
|
||||||
|
) ENGINE=InnoDB
|
||||||
|
DEFAULT CHARSET=utf8mb4
|
||||||
|
COLLATE=utf8mb4_unicode_ci
|
||||||
|
");
|
||||||
|
|
||||||
|
$legacyJsonStoreExists = (bool)$pdo->query("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM INFORMATION_SCHEMA.TABLES
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = 'app_json_store'
|
||||||
|
")->fetchColumn();
|
||||||
|
|
||||||
|
if ($legacyJsonStoreExists) {
|
||||||
|
$pdo->exec("
|
||||||
|
INSERT IGNORE INTO power_event_states
|
||||||
|
(event_key, active, active_since, last_detected_at, last_cleared_at, last_duration_seconds, event_history, refreshed_at)
|
||||||
|
SELECT
|
||||||
|
CASE store_key
|
||||||
|
WHEN 'file:control-low-voltage-state' THEN 'low_voltage'
|
||||||
|
WHEN 'file:control-throttling-state' THEN 'throttling'
|
||||||
|
ELSE store_key
|
||||||
|
END,
|
||||||
|
JSON_UNQUOTE(JSON_EXTRACT(payload, '$.active')) IN ('true', '1'),
|
||||||
|
JSON_EXTRACT(payload, '$.active_since'),
|
||||||
|
JSON_EXTRACT(payload, '$.last_detected_at'),
|
||||||
|
JSON_EXTRACT(payload, '$.last_cleared_at'),
|
||||||
|
COALESCE(JSON_EXTRACT(payload, '$.last_duration_seconds'), 0),
|
||||||
|
COALESCE(JSON_EXTRACT(payload, '$.events'), JSON_ARRAY()),
|
||||||
|
JSON_EXTRACT(payload, '$.updated_at')
|
||||||
|
FROM app_json_store
|
||||||
|
WHERE store_key IN ('file:control-low-voltage-state', 'file:control-throttling-state')
|
||||||
|
");
|
||||||
|
|
||||||
|
$pdo->exec("
|
||||||
|
INSERT IGNORE INTO battery_profile_cache
|
||||||
|
(days, created_at_epoch, sample_rows, expires_at)
|
||||||
|
SELECT
|
||||||
|
CAST(REPLACE(REPLACE(store_key, 'battery_profile:', ''), 'd', '') AS UNSIGNED),
|
||||||
|
COALESCE(JSON_EXTRACT(payload, '$.created_at'), UNIX_TIMESTAMP()),
|
||||||
|
COALESCE(JSON_EXTRACT(payload, '$.rows'), JSON_ARRAY()),
|
||||||
|
COALESCE(expires_at, DATE_ADD(NOW(), INTERVAL 6 HOUR))
|
||||||
|
FROM app_json_store
|
||||||
|
WHERE store_key LIKE 'battery_profile:%'
|
||||||
|
");
|
||||||
|
|
||||||
|
$pdo->exec("
|
||||||
|
INSERT IGNORE INTO systemd_service_cache
|
||||||
|
(cache_key, captured_at, service_rows, expires_at)
|
||||||
|
SELECT
|
||||||
|
1,
|
||||||
|
COALESCE(JSON_EXTRACT(payload, '$.created_at'), UNIX_TIMESTAMP()),
|
||||||
|
COALESCE(JSON_EXTRACT(payload, '$.rows'), JSON_ARRAY()),
|
||||||
|
COALESCE(expires_at, DATE_ADD(NOW(), INTERVAL 5 SECOND))
|
||||||
|
FROM app_json_store
|
||||||
|
WHERE store_key = 'custom_services'
|
||||||
|
");
|
||||||
|
|
||||||
|
$pdo->exec("DROP TABLE app_json_store");
|
||||||
|
}
|
||||||
|
|
||||||
$pdo->exec("
|
$pdo->exec("
|
||||||
CREATE TABLE IF NOT EXISTS wifi_client_aliases (
|
CREATE TABLE IF NOT EXISTS wifi_client_aliases (
|
||||||
mac VARCHAR(17) NOT NULL PRIMARY KEY,
|
mac VARCHAR(17) NOT NULL PRIMARY KEY,
|
||||||
|
|||||||
+138
-98
@@ -121,70 +121,8 @@ function dmesg_log(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function json_store_key_from_path(string $path): string
|
function power_event_state_read(string $eventKey): array
|
||||||
{
|
{
|
||||||
$name = preg_replace('/[^a-zA-Z0-9_.-]+/', '-', basename($path, '.json')) ?: md5($path);
|
|
||||||
return 'file:' . $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
function json_store_read(string $key, ?string $legacyPath = null): ?array
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$stmt = db()->prepare("
|
|
||||||
SELECT payload, expires_at
|
|
||||||
FROM app_json_store
|
|
||||||
WHERE store_key = :store_key
|
|
||||||
AND (expires_at IS NULL OR expires_at >= NOW())
|
|
||||||
LIMIT 1
|
|
||||||
");
|
|
||||||
$stmt->execute([':store_key' => $key]);
|
|
||||||
$row = $stmt->fetch();
|
|
||||||
if ($row) {
|
|
||||||
$decoded = json_decode((string)$row['payload'], true);
|
|
||||||
if (is_array($decoded)) {
|
|
||||||
return $decoded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable) {
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($legacyPath !== null && is_readable($legacyPath)) {
|
|
||||||
$decoded = json_decode((string)@file_get_contents($legacyPath), true);
|
|
||||||
if (is_array($decoded)) {
|
|
||||||
json_store_write($key, $decoded);
|
|
||||||
return $decoded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function json_store_write(string $key, array $payload, ?int $ttlSeconds = null): void
|
|
||||||
{
|
|
||||||
$expiresAt = $ttlSeconds !== null ? date('Y-m-d H:i:s', time() + max(1, $ttlSeconds)) : null;
|
|
||||||
$stmt = db()->prepare("
|
|
||||||
INSERT INTO app_json_store (store_key, payload, expires_at)
|
|
||||||
VALUES (:store_key, :payload, :expires_at)
|
|
||||||
ON DUPLICATE KEY UPDATE
|
|
||||||
payload = VALUES(payload),
|
|
||||||
expires_at = VALUES(expires_at),
|
|
||||||
updated_at = CURRENT_TIMESTAMP
|
|
||||||
");
|
|
||||||
$stmt->execute([
|
|
||||||
':store_key' => $key,
|
|
||||||
':payload' => json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
|
||||||
':expires_at' => $expiresAt,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function throttled_event_status(?int $flags, string $raw, int $activeBit, int $seenBit, string $statePath): array
|
|
||||||
{
|
|
||||||
$now = time();
|
|
||||||
$windowSeconds = (int)setting_value('event.recent_window_seconds');
|
|
||||||
$recoverySeconds = (int)setting_value('event.recovery_seconds');
|
|
||||||
$active = $flags !== null && (bool)($flags & $activeBit);
|
|
||||||
$seen = $flags !== null && (bool)($flags & $seenBit);
|
|
||||||
|
|
||||||
$state = [
|
$state = [
|
||||||
'active' => false,
|
'active' => false,
|
||||||
'active_since' => null,
|
'active_since' => null,
|
||||||
@@ -195,11 +133,69 @@ function throttled_event_status(?int $flags, string $raw, int $activeBit, int $s
|
|||||||
'events' => [],
|
'events' => [],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
SELECT *
|
||||||
|
FROM power_event_states
|
||||||
|
WHERE event_key = :event_key
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([':event_key' => $eventKey]);
|
||||||
|
$row = $stmt->fetch();
|
||||||
|
if (!$row) {
|
||||||
|
return $state;
|
||||||
|
}
|
||||||
|
|
||||||
|
$events = json_decode((string)($row['event_history'] ?? '[]'), true);
|
||||||
|
$state['active'] = (bool)($row['active'] ?? false);
|
||||||
|
$state['active_since'] = is_numeric($row['active_since'] ?? null) ? (int)$row['active_since'] : null;
|
||||||
|
$state['last_detected_at'] = is_numeric($row['last_detected_at'] ?? null) ? (int)$row['last_detected_at'] : null;
|
||||||
|
$state['last_cleared_at'] = is_numeric($row['last_cleared_at'] ?? null) ? (int)$row['last_cleared_at'] : null;
|
||||||
|
$state['last_duration_seconds'] = (int)($row['last_duration_seconds'] ?? 0);
|
||||||
|
$state['updated_at'] = is_numeric($row['refreshed_at'] ?? null) ? (int)$row['refreshed_at'] : null;
|
||||||
|
$state['events'] = is_array($events) ? $events : [];
|
||||||
|
|
||||||
|
return $state;
|
||||||
|
}
|
||||||
|
|
||||||
|
function power_event_state_write(string $eventKey, array $state): void
|
||||||
|
{
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
INSERT INTO power_event_states
|
||||||
|
(event_key, active, active_since, last_detected_at, last_cleared_at, last_duration_seconds, event_history, refreshed_at)
|
||||||
|
VALUES
|
||||||
|
(:event_key, :active, :active_since, :last_detected_at, :last_cleared_at, :last_duration_seconds, :event_history, :refreshed_at)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
active = VALUES(active),
|
||||||
|
active_since = VALUES(active_since),
|
||||||
|
last_detected_at = VALUES(last_detected_at),
|
||||||
|
last_cleared_at = VALUES(last_cleared_at),
|
||||||
|
last_duration_seconds = VALUES(last_duration_seconds),
|
||||||
|
event_history = VALUES(event_history),
|
||||||
|
refreshed_at = VALUES(refreshed_at),
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':event_key' => $eventKey,
|
||||||
|
':active' => !empty($state['active']) ? 1 : 0,
|
||||||
|
':active_since' => is_numeric($state['active_since'] ?? null) ? (int)$state['active_since'] : null,
|
||||||
|
':last_detected_at' => is_numeric($state['last_detected_at'] ?? null) ? (int)$state['last_detected_at'] : null,
|
||||||
|
':last_cleared_at' => is_numeric($state['last_cleared_at'] ?? null) ? (int)$state['last_cleared_at'] : null,
|
||||||
|
':last_duration_seconds' => (int)($state['last_duration_seconds'] ?? 0),
|
||||||
|
':event_history' => json_encode($state['events'] ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
||||||
|
':refreshed_at' => is_numeric($state['updated_at'] ?? null) ? (int)$state['updated_at'] : time(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function throttled_event_status(?int $flags, string $raw, int $activeBit, int $seenBit, string $eventKey): array
|
||||||
|
{
|
||||||
|
$now = time();
|
||||||
|
$windowSeconds = (int)setting_value('event.recent_window_seconds');
|
||||||
|
$recoverySeconds = (int)setting_value('event.recovery_seconds');
|
||||||
|
$active = $flags !== null && (bool)($flags & $activeBit);
|
||||||
|
$seen = $flags !== null && (bool)($flags & $seenBit);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$decoded = json_store_read(json_store_key_from_path($statePath), $statePath);
|
$state = power_event_state_read($eventKey);
|
||||||
if (is_array($decoded)) {
|
|
||||||
$state = array_merge($state, array_intersect_key($decoded, $state));
|
|
||||||
}
|
|
||||||
$state['events'] = array_values(array_filter(
|
$state['events'] = array_values(array_filter(
|
||||||
is_array($state['events'] ?? null) ? $state['events'] : [],
|
is_array($state['events'] ?? null) ? $state['events'] : [],
|
||||||
static fn($event): bool => is_array($event) && is_numeric($event['start'] ?? null)
|
static fn($event): bool => is_array($event) && is_numeric($event['start'] ?? null)
|
||||||
@@ -252,9 +248,18 @@ function throttled_event_status(?int $flags, string $raw, int $activeBit, int $s
|
|||||||
)), -200);
|
)), -200);
|
||||||
|
|
||||||
$state['updated_at'] = $now;
|
$state['updated_at'] = $now;
|
||||||
json_store_write(json_store_key_from_path($statePath), $state);
|
power_event_state_write($eventKey, $state);
|
||||||
}
|
}
|
||||||
} catch (Throwable) {
|
} catch (Throwable) {
|
||||||
|
$state = [
|
||||||
|
'active' => false,
|
||||||
|
'active_since' => null,
|
||||||
|
'last_detected_at' => null,
|
||||||
|
'last_cleared_at' => null,
|
||||||
|
'last_duration_seconds' => 0,
|
||||||
|
'updated_at' => null,
|
||||||
|
'events' => [],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$duration = 0;
|
$duration = 0;
|
||||||
@@ -361,8 +366,8 @@ function throttled_statuses(): array
|
|||||||
$flags = $read['flags'];
|
$flags = $read['flags'];
|
||||||
$source = (string)$read['source'];
|
$source = (string)$read['source'];
|
||||||
|
|
||||||
$lowVoltage = throttled_event_status($flags, $raw, 0x1, 0x10000, '/tmp/control-low-voltage-state.json');
|
$lowVoltage = throttled_event_status($flags, $raw, 0x1, 0x10000, 'low_voltage');
|
||||||
$throttling = throttled_event_status($flags, $raw, 0x4, 0x40000, '/tmp/control-throttling-state.json');
|
$throttling = throttled_event_status($flags, $raw, 0x4, 0x40000, 'throttling');
|
||||||
|
|
||||||
$lowVoltage['source'] = $source;
|
$lowVoltage['source'] = $source;
|
||||||
$lowVoltage['freq_capped'] = $flags !== null && (bool)($flags & 0x2);
|
$lowVoltage['freq_capped'] = $flags !== null && (bool)($flags & 0x2);
|
||||||
@@ -927,8 +932,6 @@ function battery_profile_history(int $days = 45): array
|
|||||||
static $cache = [];
|
static $cache = [];
|
||||||
$cacheKey = (string)$days;
|
$cacheKey = (string)$days;
|
||||||
$now = time();
|
$now = time();
|
||||||
$storeKey = 'battery_profile:' . $days . 'd';
|
|
||||||
$legacyCacheFile = sys_get_temp_dir() . '/control-battery-profile-' . $days . 'd.json';
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isset($cache[$cacheKey])
|
isset($cache[$cacheKey])
|
||||||
@@ -938,18 +941,24 @@ function battery_profile_history(int $days = 45): array
|
|||||||
return $cache[$cacheKey]['rows'];
|
return $cache[$cacheKey]['rows'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$decoded = json_store_read($storeKey, $legacyCacheFile);
|
$stmt = db()->prepare("
|
||||||
if (
|
SELECT created_at_epoch, sample_rows
|
||||||
is_array($decoded)
|
FROM battery_profile_cache
|
||||||
&& isset($decoded['created_at'], $decoded['rows'])
|
WHERE days = :days
|
||||||
&& is_array($decoded['rows'])
|
AND expires_at >= NOW()
|
||||||
&& ($now - (int)$decoded['created_at']) < 21600
|
LIMIT 1
|
||||||
) {
|
");
|
||||||
$cache[$cacheKey] = [
|
$stmt->execute([':days' => $days]);
|
||||||
'time' => $now,
|
$cached = $stmt->fetch();
|
||||||
'rows' => $decoded['rows'],
|
if ($cached) {
|
||||||
];
|
$rows = json_decode((string)$cached['sample_rows'], true);
|
||||||
return $decoded['rows'];
|
if (is_array($rows)) {
|
||||||
|
$cache[$cacheKey] = [
|
||||||
|
'time' => $now,
|
||||||
|
'rows' => $rows,
|
||||||
|
];
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$maxId = (int)db()->query("SELECT MAX(id) FROM sensor_logs")->fetchColumn();
|
$maxId = (int)db()->query("SELECT MAX(id) FROM sensor_logs")->fetchColumn();
|
||||||
@@ -992,11 +1001,22 @@ function battery_profile_history(int $days = 45): array
|
|||||||
'time' => $now,
|
'time' => $now,
|
||||||
'rows' => $rows,
|
'rows' => $rows,
|
||||||
];
|
];
|
||||||
json_store_write($storeKey, [
|
$stmt = db()->prepare("
|
||||||
'created_at' => $now,
|
INSERT INTO battery_profile_cache
|
||||||
'days' => $days,
|
(days, created_at_epoch, sample_rows, expires_at)
|
||||||
'rows' => $rows,
|
VALUES
|
||||||
], 21600);
|
(:days, :created_at_epoch, :sample_rows, DATE_ADD(NOW(), INTERVAL 6 HOUR))
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
created_at_epoch = VALUES(created_at_epoch),
|
||||||
|
sample_rows = VALUES(sample_rows),
|
||||||
|
expires_at = VALUES(expires_at),
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':days' => $days,
|
||||||
|
':created_at_epoch' => $now,
|
||||||
|
':sample_rows' => json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
||||||
|
]);
|
||||||
|
|
||||||
return $rows;
|
return $rows;
|
||||||
}
|
}
|
||||||
@@ -3443,11 +3463,19 @@ function systemd_service_logs(string $unit, int $limit = 12): array
|
|||||||
function custom_systemd_services(): array
|
function custom_systemd_services(): array
|
||||||
{
|
{
|
||||||
$cacheSeconds = (int)setting_value('display.custom_service_cache_seconds');
|
$cacheSeconds = (int)setting_value('display.custom_service_cache_seconds');
|
||||||
$cacheKey = 'custom_services';
|
$stmt = db()->prepare("
|
||||||
$cached = json_store_read($cacheKey, '/tmp/control-custom-services.json');
|
SELECT service_rows
|
||||||
if (is_array($cached) && isset($cached['created_at'], $cached['rows']) && is_array($cached['rows'])) {
|
FROM systemd_service_cache
|
||||||
if (time() - (int)$cached['created_at'] <= $cacheSeconds) {
|
WHERE cache_key = 1
|
||||||
return $cached['rows'];
|
AND expires_at >= NOW()
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute();
|
||||||
|
$cached = $stmt->fetch();
|
||||||
|
if ($cached) {
|
||||||
|
$rows = json_decode((string)$cached['service_rows'], true);
|
||||||
|
if (is_array($rows)) {
|
||||||
|
return $rows;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3510,10 +3538,22 @@ function custom_systemd_services(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
json_store_write($cacheKey, [
|
$stmt = db()->prepare("
|
||||||
'created_at' => time(),
|
INSERT INTO systemd_service_cache
|
||||||
'rows' => $rows,
|
(cache_key, captured_at, service_rows, expires_at)
|
||||||
], $cacheSeconds);
|
VALUES
|
||||||
|
(1, :captured_at, :service_rows, :expires_at)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
captured_at = VALUES(captured_at),
|
||||||
|
service_rows = VALUES(service_rows),
|
||||||
|
expires_at = VALUES(expires_at),
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':captured_at' => time(),
|
||||||
|
':service_rows' => json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
||||||
|
':expires_at' => date('Y-m-d H:i:s', time() + max(1, $cacheSeconds)),
|
||||||
|
]);
|
||||||
|
|
||||||
return $rows;
|
return $rows;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user