Stream car usage aggregation
This commit is contained in:
+116
-88
@@ -204,24 +204,10 @@ function monthly_data_usage(PDO $pdo): array
|
||||
$monthStart = $now->modify('first day of this month')->setTime(0, 0, 0);
|
||||
$nextMonthStart = $monthStart->modify('first day of next month');
|
||||
|
||||
$stmt = $pdo->prepare(""
|
||||
. "SELECT ts, source, cmd, sent_bytes, received_bytes, total_bytes, tcp_ok, tcp_error "
|
||||
. "FROM car_data_usage "
|
||||
. "WHERE id='car' AND ts >= :month_start AND ts < :next_month_start "
|
||||
. "ORDER BY ts ASC"
|
||||
);
|
||||
$stmt->execute([
|
||||
':month_start' => $monthStart->format('Y-m-d H:i:s'),
|
||||
':next_month_start' => $nextMonthStart->format('Y-m-d H:i:s'),
|
||||
]);
|
||||
$usageRows = $stmt->fetchAll();
|
||||
$usage = calibrated_usage_rows($usageRows);
|
||||
$todayStart = $now->setTime(0, 0, 0);
|
||||
$tomorrowStart = $todayStart->modify('+1 day');
|
||||
$todayUsage = calibrated_usage_rows(array_values(array_filter($usageRows, function (array $row) use ($todayStart, $tomorrowStart): bool {
|
||||
$ts = strtotime((string)($row['ts'] ?? ''));
|
||||
return $ts !== false && $ts >= $todayStart->getTimestamp() && $ts < $tomorrowStart->getTimestamp();
|
||||
})));
|
||||
$usage = calibrated_usage_range($pdo, $monthStart, $nextMonthStart);
|
||||
$todayUsage = calibrated_usage_range($pdo, $todayStart, $tomorrowStart);
|
||||
|
||||
$sentBytes = $usage['sent_bytes'];
|
||||
$receivedBytes = $usage['received_bytes'];
|
||||
@@ -339,51 +325,57 @@ function floor_krw_10(float $value): int
|
||||
return (int)floor(max(0.0, $value) / 10) * 10;
|
||||
}
|
||||
|
||||
function calibrated_usage_rows(array $rows): array
|
||||
function usage_success_averages(PDO $pdo, DateTimeInterface $start, DateTimeInterface $end): array
|
||||
{
|
||||
$sentBytes = 0;
|
||||
$receivedBytes = 0;
|
||||
$totalBytes = 0;
|
||||
$successfulTotalBytes = 0;
|
||||
$successfulCount = 0;
|
||||
$successByCmd = [];
|
||||
$firstUsageTs = null;
|
||||
$lastUsageTs = null;
|
||||
$avgByCmd = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$stmt = $pdo->prepare(""
|
||||
. "SELECT cmd, SUM(total_bytes) AS total_bytes, COUNT(*) AS sample_count "
|
||||
. "FROM car_data_usage "
|
||||
. "WHERE id='car' AND ts >= :start_ts AND ts < :end_ts "
|
||||
. "AND tcp_ok = 1 AND total_bytes > 0 "
|
||||
. "GROUP BY cmd"
|
||||
);
|
||||
$stmt->execute([
|
||||
':start_ts' => $start->format('Y-m-d H:i:s'),
|
||||
':end_ts' => $end->format('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
while ($row = $stmt->fetch()) {
|
||||
$cmd = (string)($row['cmd'] ?? '');
|
||||
$sent = (int)($row['sent_bytes'] ?? 0);
|
||||
$received = (int)($row['received_bytes'] ?? 0);
|
||||
$total = (int)($row['total_bytes'] ?? 0);
|
||||
$tcpOk = (int)($row['tcp_ok'] ?? 0) === 1;
|
||||
|
||||
$sentBytes += $sent;
|
||||
$receivedBytes += $received;
|
||||
$totalBytes += $total;
|
||||
|
||||
if ($firstUsageTs === null && !empty($row['ts'])) {
|
||||
$firstUsageTs = (string)$row['ts'];
|
||||
}
|
||||
if (!empty($row['ts'])) {
|
||||
$lastUsageTs = (string)$row['ts'];
|
||||
$count = (int)($row['sample_count'] ?? 0);
|
||||
if ($count <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tcpOk && $total > 0) {
|
||||
$successfulTotalBytes += $total;
|
||||
$successfulCount++;
|
||||
if (!isset($successByCmd[$cmd])) {
|
||||
$successByCmd[$cmd] = ['total' => 0, 'count' => 0];
|
||||
}
|
||||
$successByCmd[$cmd]['total'] += $total;
|
||||
$successByCmd[$cmd]['count']++;
|
||||
}
|
||||
$successfulTotalBytes += $total;
|
||||
$successfulCount += $count;
|
||||
$avgByCmd[$cmd] = (int)round($total / $count);
|
||||
}
|
||||
|
||||
$globalAverage = $successfulCount > 0 ? (int)round($successfulTotalBytes / $successfulCount) : 0;
|
||||
$avgByCmd = [];
|
||||
foreach ($successByCmd as $cmd => $stat) {
|
||||
$avgByCmd[$cmd] = $stat['count'] > 0 ? (int)round($stat['total'] / $stat['count']) : $globalAverage;
|
||||
}
|
||||
|
||||
return [
|
||||
'avg_by_cmd' => $avgByCmd,
|
||||
'global_average' => $globalAverage,
|
||||
'success_count' => $successfulCount,
|
||||
];
|
||||
}
|
||||
|
||||
function calibrated_usage_range(PDO $pdo, DateTimeInterface $start, DateTimeInterface $end): array
|
||||
{
|
||||
$averages = usage_success_averages($pdo, $start, $end);
|
||||
$avgByCmd = $averages['avg_by_cmd'];
|
||||
$globalAverage = $averages['global_average'];
|
||||
|
||||
$sentBytes = 0;
|
||||
$receivedBytes = 0;
|
||||
$totalBytes = 0;
|
||||
$firstUsageTs = null;
|
||||
$lastUsageTs = null;
|
||||
|
||||
$adjustedTotalBytes = 0;
|
||||
$adjustedFailureBytes = 0;
|
||||
@@ -391,47 +383,83 @@ function calibrated_usage_rows(array $rows): array
|
||||
$connectFailureCount = 0;
|
||||
$receiveGapExcludedCount = 0;
|
||||
$lastSuccessfulTs = null;
|
||||
$sampleCount = 0;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$cmd = (string)($row['cmd'] ?? '');
|
||||
$sent = (int)($row['sent_bytes'] ?? 0);
|
||||
$total = (int)($row['total_bytes'] ?? 0);
|
||||
$tcpOk = (int)($row['tcp_ok'] ?? 0) === 1;
|
||||
$rowTs = strtotime((string)($row['ts'] ?? ''));
|
||||
$stmt = null;
|
||||
$previousBuffered = $pdo->getAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY);
|
||||
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
|
||||
|
||||
if ($tcpOk) {
|
||||
$adjustedTotalBytes += $total;
|
||||
if ($rowTs !== false) {
|
||||
$lastSuccessfulTs = $rowTs;
|
||||
try {
|
||||
$stmt = $pdo->prepare(""
|
||||
. "SELECT ts, cmd, sent_bytes, received_bytes, total_bytes, tcp_ok "
|
||||
. "FROM car_data_usage "
|
||||
. "WHERE id='car' AND ts >= :start_ts AND ts < :end_ts "
|
||||
. "ORDER BY ts ASC"
|
||||
);
|
||||
$stmt->execute([
|
||||
':start_ts' => $start->format('Y-m-d H:i:s'),
|
||||
':end_ts' => $end->format('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
while ($row = $stmt->fetch()) {
|
||||
$sampleCount++;
|
||||
$cmd = (string)($row['cmd'] ?? '');
|
||||
$sent = (int)($row['sent_bytes'] ?? 0);
|
||||
$received = (int)($row['received_bytes'] ?? 0);
|
||||
$total = (int)($row['total_bytes'] ?? 0);
|
||||
$tcpOk = (int)($row['tcp_ok'] ?? 0) === 1;
|
||||
$rowTs = strtotime((string)($row['ts'] ?? ''));
|
||||
|
||||
$sentBytes += $sent;
|
||||
$receivedBytes += $received;
|
||||
$totalBytes += $total;
|
||||
|
||||
if ($firstUsageTs === null && !empty($row['ts'])) {
|
||||
$firstUsageTs = (string)$row['ts'];
|
||||
}
|
||||
continue;
|
||||
if (!empty($row['ts'])) {
|
||||
$lastUsageTs = (string)$row['ts'];
|
||||
}
|
||||
|
||||
if ($tcpOk) {
|
||||
$adjustedTotalBytes += $total;
|
||||
if ($rowTs !== false) {
|
||||
$lastSuccessfulTs = $rowTs;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($sent <= 0) {
|
||||
// TCP 연결 자체가 실패한 건은 모뎀까지 명령이 전달되지 않은 것으로 보고 과금 추정에서 제외한다.
|
||||
$connectFailureCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$connectedFailureCount++;
|
||||
|
||||
if (
|
||||
$lastSuccessfulTs === null ||
|
||||
$rowTs === false ||
|
||||
($rowTs - $lastSuccessfulTs) > RECEIVE_GAP_LIMIT_SEC
|
||||
) {
|
||||
$receiveGapExcludedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$estimatedBytes = $avgByCmd[$cmd] ?? $globalAverage;
|
||||
|
||||
if ($estimatedBytes <= 0) {
|
||||
$estimatedBytes = max($sent, $total);
|
||||
}
|
||||
|
||||
$adjustedTotalBytes += $estimatedBytes;
|
||||
$adjustedFailureBytes += $estimatedBytes;
|
||||
}
|
||||
|
||||
if ($sent <= 0) {
|
||||
// TCP 연결 자체가 실패한 건은 모뎀까지 명령이 전달되지 않은 것으로 보고 과금 추정에서 제외한다.
|
||||
$connectFailureCount++;
|
||||
continue;
|
||||
} finally {
|
||||
if ($stmt instanceof PDOStatement) {
|
||||
$stmt->closeCursor();
|
||||
}
|
||||
|
||||
$connectedFailureCount++;
|
||||
|
||||
if (
|
||||
$lastSuccessfulTs === null ||
|
||||
$rowTs === false ||
|
||||
($rowTs - $lastSuccessfulTs) > RECEIVE_GAP_LIMIT_SEC
|
||||
) {
|
||||
$receiveGapExcludedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$estimatedBytes = $avgByCmd[$cmd] ?? $globalAverage;
|
||||
|
||||
if ($estimatedBytes <= 0) {
|
||||
$estimatedBytes = max($sent, $total);
|
||||
}
|
||||
|
||||
$adjustedTotalBytes += $estimatedBytes;
|
||||
$adjustedFailureBytes += $estimatedBytes;
|
||||
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $previousBuffered);
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -443,8 +471,8 @@ function calibrated_usage_rows(array $rows): array
|
||||
'connected_failure_count' => $connectedFailureCount,
|
||||
'connect_failure_count' => $connectFailureCount,
|
||||
'receive_gap_excluded_count' => $receiveGapExcludedCount,
|
||||
'sample_count' => count($rows),
|
||||
'success_count' => $successfulCount,
|
||||
'sample_count' => $sampleCount,
|
||||
'success_count' => $averages['success_count'],
|
||||
'success_average_bytes' => $globalAverage,
|
||||
'first_usage_ts' => $firstUsageTs,
|
||||
'last_usage_ts' => $lastUsageTs,
|
||||
|
||||
Reference in New Issue
Block a user