Skip to content

Commit 15112a0

Browse files
lohanidamodarclaude
andcommitted
feat: add TYPE_EVENT/TYPE_GAUGE constants, daily materialized view
- Add Usage::TYPE_EVENT and Usage::TYPE_GAUGE constants - Use constants in validation and type comparisons - Add SummingMergeTree daily aggregation MV for events (toStartOfDay) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ab599d5 commit 15112a0

2 files changed

Lines changed: 56 additions & 9 deletions

File tree

src/Usage/Adapter/ClickHouse.php

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Utopia\Query\Query;
77
use Utopia\Fetch\Client;
88
use Utopia\Usage\Metric;
9+
use Utopia\Usage\Usage;
910
use Utopia\Validator\Hostname;
1011

1112
/**
@@ -1006,6 +1007,49 @@ public function setup(): void
10061007
";
10071008

10081009
$this->query($createBillingMvSql);
1010+
1011+
// Create daily aggregation target table (SummingMergeTree) for events
1012+
$dailyTableName = $tableName . '_daily';
1013+
$escapedDailyTable = $this->escapeIdentifier($this->database) . '.' . $this->escapeIdentifier($dailyTableName);
1014+
1015+
$dailyColumns = [
1016+
'metric String',
1017+
'tenant Nullable(UInt64)',
1018+
'value Int64',
1019+
'time DateTime64(3)',
1020+
];
1021+
1022+
$dailyColumnDefs = implode(",\n ", $dailyColumns);
1023+
1024+
$createDailyTableSql = "
1025+
CREATE TABLE IF NOT EXISTS {$escapedDailyTable} (
1026+
{$dailyColumnDefs}
1027+
)
1028+
ENGINE = SummingMergeTree()
1029+
ORDER BY (tenant, metric, time)
1030+
SETTINGS allow_nullable_key = 1
1031+
";
1032+
1033+
$this->query($createDailyTableSql);
1034+
1035+
// Create materialized view for daily event aggregation
1036+
$dailyMvName = $tableName . '_daily_mv';
1037+
$escapedDailyMv = $this->escapeIdentifier($this->database) . '.' . $this->escapeIdentifier($dailyMvName);
1038+
1039+
$createDailyMvSql = "
1040+
CREATE MATERIALIZED VIEW IF NOT EXISTS {$escapedDailyMv}
1041+
TO {$escapedDailyTable}
1042+
AS SELECT
1043+
metric,
1044+
{$tenantSelect},
1045+
sum(value) as value,
1046+
toStartOfDay(time) as time
1047+
FROM {$escapedDatabaseAndTable}
1048+
WHERE type = 'event'
1049+
GROUP BY metric, tenant, time
1050+
";
1051+
1052+
$this->query($createDailyMvSql);
10091053
}
10101054

10111055
/**
@@ -1127,8 +1171,8 @@ private function validateMetricData(string $metric, int $value, string $type, ar
11271171
throw new Exception($prefix . 'Value cannot be negative');
11281172
}
11291173

1130-
if ($type !== 'event' && $type !== 'gauge') {
1131-
throw new \InvalidArgumentException($prefix . "Invalid type '{$type}'. Allowed: event, gauge");
1174+
if ($type !== Usage::TYPE_EVENT && $type !== Usage::TYPE_GAUGE) {
1175+
throw new \InvalidArgumentException($prefix . "Invalid type '{$type}'. Allowed: " . Usage::TYPE_EVENT . ', ' . Usage::TYPE_GAUGE);
11321176
}
11331177

11341178
if (!is_array($tags)) {
@@ -1501,7 +1545,7 @@ public function getTimeSeries(array $metrics, string $interval, string $startDat
15011545
$metricName = $row['metric'] ?? '';
15021546
$type = $row['type'] ?? 'event';
15031547
$bucketTime = $row['bucket'] ?? '';
1504-
$value = ($type === 'event') ? (int) ($row['sum_value'] ?? 0) : (int) ($row['last_value'] ?? 0);
1548+
$value = ($type === Usage::TYPE_EVENT) ? (int) ($row['sum_value'] ?? 0) : (int) ($row['last_value'] ?? 0);
15051549

15061550
if (!isset($output[$metricName])) {
15071551
continue;
@@ -1630,9 +1674,9 @@ public function getTotal(string $metric, array $queries = []): int
16301674

16311675
foreach ($json['data'] as $row) {
16321676
$type = $row['type'] ?? 'event';
1633-
if ($type === 'event') {
1677+
if ($type === Usage::TYPE_EVENT) {
16341678
return (int) ($row['sum_val'] ?? 0);
1635-
} elseif ($type === 'gauge') {
1679+
} elseif ($type === Usage::TYPE_GAUGE) {
16361680
return (int) ($row['last_val'] ?? 0);
16371681
}
16381682
}
@@ -1709,9 +1753,9 @@ public function getTotalBatch(array $metrics, array $queries = []): array
17091753
continue;
17101754
}
17111755

1712-
if ($type === 'event') {
1756+
if ($type === Usage::TYPE_EVENT) {
17131757
$totals[$metricName] = (int) ($row['sum_val'] ?? 0);
1714-
} elseif ($type === 'gauge') {
1758+
} elseif ($type === Usage::TYPE_GAUGE) {
17151759
$totals[$metricName] = (int) ($row['last_val'] ?? 0);
17161760
}
17171761
}

src/Usage/Usage.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
*/
1414
class Usage
1515
{
16+
public const TYPE_EVENT = 'event';
17+
public const TYPE_GAUGE = 'gauge';
18+
1619
private const DEFAULT_FLUSH_THRESHOLD = 10_000;
1720
private const DEFAULT_FLUSH_INTERVAL = 20;
1821

@@ -234,8 +237,8 @@ public function setSharedTables(bool $sharedTables): self
234237
*/
235238
public function collect(string $metric, int $value, string $type, array $tags = []): self
236239
{
237-
if ($type !== 'event' && $type !== 'gauge') {
238-
throw new \InvalidArgumentException("Invalid metric type '{$type}'. Allowed: event, gauge");
240+
if ($type !== self::TYPE_EVENT && $type !== self::TYPE_GAUGE) {
241+
throw new \InvalidArgumentException("Invalid metric type '{$type}'. Allowed: " . self::TYPE_EVENT . ', ' . self::TYPE_GAUGE);
239242
}
240243

241244
$key = $metric . ':' . $type;

0 commit comments

Comments
 (0)