From 9bbe39dd163dbb3278f24076f785a5b961afe466 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 23 Jun 2026 21:56:03 +0100 Subject: [PATCH 1/2] refactor(usage): drop the Usage pass-through facade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Usage was a pure delegating layer: every method just forwarded to the adapter with an identical signature, duplicating Adapter's docblocks verbatim and forcing the adapters to depend on Usage for their own TYPE_* constants (a backwards dependency). The only behaviour it added was a type guard in sum(). - Delete Usage; Accumulator and Tenant now wrap an Adapter directly. - Move TYPE_EVENT/TYPE_GAUGE onto Adapter (where they belong) and add a single Adapter::assertType() that replaces the 5 duplicated event/gauge guards across Accumulator, ClickHouse and Database. - getAdapter() is gone — the adapter is the entry point. Net negative LoC, no behaviour change. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 48 ++-- src/Usage/Accumulator.php | 22 +- src/Usage/Adapter.php | 21 +- src/Usage/Adapter/ClickHouse.php | 86 +++--- src/Usage/Adapter/Database.php | 23 +- src/Usage/Tenant.php | 36 +-- src/Usage/Usage.php | 254 ------------------ tests/Benchmark/BenchmarkBase.php | 6 +- tests/Benchmark/EventsBench.php | 24 +- tests/Benchmark/GaugesBench.php | 10 +- tests/Usage/AccumulatorTest.php | 55 ++-- .../Adapter/ClickHouseDimRoutingTest.php | 22 +- .../Adapter/ClickHouseGaugeDimRoutingTest.php | 18 +- tests/Usage/Adapter/ClickHouseRoutingTest.php | 40 +-- tests/Usage/Adapter/ClickHouseSchemaTest.php | 7 +- tests/Usage/Adapter/ClickHouseTest.php | 208 +++++++------- tests/Usage/Adapter/DatabaseTest.php | 32 +-- tests/Usage/TenantTest.php | 21 +- tests/Usage/UsageBase.php | 94 +++---- 19 files changed, 389 insertions(+), 638 deletions(-) delete mode 100644 src/Usage/Usage.php diff --git a/README.md b/README.md index 9591632..08eadb0 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,13 @@ composer require utopia-php/usage ```php setup(); // Creates events, gauges, and daily MV tables ``` @@ -57,19 +57,17 @@ $usage->setup(); // Creates events, gauges, and daily MV tables ```php setup(); ``` ## Multi-tenancy -`Usage` is stateless: every query/mutation takes the tenant as its first +The adapter is stateless: every query/mutation takes the tenant as its first argument, and `addBatch` carries a `tenant` on each metric row (so one batch can -span tenants). This makes a single `Usage` instance safe to share across +span tenants). This makes a single adapter instance safe to share across tenants and coroutines. ```php @@ -77,12 +75,12 @@ $usage->getTotal('project_123', 'bandwidth'); // read for one tenant $usage->addBatch([ ['tenant' => 'project_123', 'metric' => 'bandwidth', 'value' => 5000, 'tags' => []], ['tenant' => 'project_456', 'metric' => 'bandwidth', 'value' => 2000, 'tags' => []], -], Usage::TYPE_EVENT); +], Adapter::TYPE_EVENT); ``` Callers that only ever touch one tenant can bind it once with the `Tenant` -decorator, which forwards to `Usage` with the tenant pre-filled (and stamps it -onto every `addBatch` row): +decorator, which forwards to the adapter with the tenant pre-filled (and stamps +it onto every `addBatch` row): ```php use Utopia\Usage\Tenant; @@ -91,7 +89,7 @@ $tenant = new Tenant($usage, 'project_123'); $tenant->getTotal('bandwidth'); // no tenant argument $tenant->addBatch([ ['metric' => 'bandwidth', 'value' => 5000, 'tags' => []], // tenant stamped automatically -], Usage::TYPE_EVENT); +], Adapter::TYPE_EVENT); ``` ## Metric Types @@ -114,7 +112,7 @@ use Utopia\Usage\Accumulator; $accumulator = new Accumulator($usage); // Collect events — tenant first; values accumulate in-memory (summed per tenant+metric) -$accumulator->collect('project_123', 'bandwidth', 5000, Usage::TYPE_EVENT, [ +$accumulator->collect('project_123', 'bandwidth', 5000, Adapter::TYPE_EVENT, [ 'path' => '/v1/storage/files', 'method' => 'POST', 'status' => '201', @@ -149,8 +147,8 @@ Gauge-specific columns (see `Metric::GAUGE_COLUMNS`): `teamId`, ```php // Collect gauges — last value wins per tenant+metric in buffer -$accumulator->collect('project_123', 'users', 1500, Usage::TYPE_GAUGE); -$accumulator->collect('project_123', 'storage.size', 1048576, Usage::TYPE_GAUGE, [ +$accumulator->collect('project_123', 'users', 1500, Adapter::TYPE_GAUGE); +$accumulator->collect('project_123', 'storage.size', 1048576, Adapter::TYPE_GAUGE, [ 'teamId' => 'team_x', 'teamInternalId' => '7', 'resourceId' => 'abc123', @@ -178,11 +176,11 @@ if ($accumulator->count() >= 5000 || $accumulator->elapsedSeconds() >= 10) { $usage->addBatch([ ['tenant' => 'project_123', 'metric' => 'requests', 'value' => 100, 'tags' => ['path' => '/v1/users', 'method' => 'GET']], ['tenant' => 'project_123', 'metric' => 'bandwidth', 'value' => 50000, 'tags' => ['country' => 'DE', 'region' => 'fra']], -], Usage::TYPE_EVENT); +], Adapter::TYPE_EVENT); $usage->addBatch([ ['tenant' => 'project_123', 'metric' => 'users', 'value' => 42, 'tags' => ['teamId' => 'team_x']], -], Usage::TYPE_GAUGE); +], Adapter::TYPE_GAUGE); ``` ## Querying Metrics @@ -199,12 +197,12 @@ $metrics = $usage->find('project_123', [ Query::greaterThanEqual('time', '2026-01-01'), Query::orderDesc('time'), Query::limit(100), -], Usage::TYPE_EVENT); +], Adapter::TYPE_EVENT); // Find gauges $gauges = $usage->find('project_123', [ Query::equal('metric', ['users', 'storage.size']), -], Usage::TYPE_GAUGE); +], Adapter::TYPE_GAUGE); // Query both tables (type = null) $all = $usage->find('project_123', [ @@ -218,14 +216,14 @@ $all = $usage->find('project_123', [ // Get total for a single metric (SUM for events, latest for gauges) $total = $usage->getTotal('project_123', 'bandwidth', [ Query::greaterThanEqual('time', '2026-03-01'), -], Usage::TYPE_EVENT); +], Adapter::TYPE_EVENT); // Batch totals — single query with GROUP BY $totals = $usage->getTotalBatch( 'project_123', ['bandwidth', 'executions', 'requests'], [Query::greaterThanEqual('time', '2026-03-01')], - Usage::TYPE_EVENT + Adapter::TYPE_EVENT ); ``` @@ -241,7 +239,7 @@ $series = $usage->getTimeSeries( startDate: '2026-03-01', endDate: '2026-04-01', zeroFill: true, // Fill gaps with zeros - type: Usage::TYPE_EVENT + type: Adapter::TYPE_EVENT ); // Returns: ['bandwidth' => ['total' => 5000000, 'data' => [['value' => 100, 'date' => '...'], ...]]] @@ -280,10 +278,10 @@ $rows = $usage->findDaily('project_123', [ // Purge all event metrics older than 90 days $usage->purge('project_123', [ Query::lessThan('time', '2026-01-01'), -], Usage::TYPE_EVENT); +], Adapter::TYPE_EVENT); // Purge all gauge metrics -$usage->purge('project_123', [], Usage::TYPE_GAUGE); +$usage->purge('project_123', [], Adapter::TYPE_GAUGE); ``` ## Architecture diff --git a/src/Usage/Accumulator.php b/src/Usage/Accumulator.php index 965c229..3d4a339 100644 --- a/src/Usage/Accumulator.php +++ b/src/Usage/Accumulator.php @@ -5,7 +5,7 @@ /** * In-memory metric accumulator. * - * Buffers collect() calls and flushes them to a Usage instance in batches. + * Buffers collect() calls and flushes them to an Adapter in batches. * Events are summed per metric+tags; gauges use last-write-wins. * * The accumulator exposes raw signals — count() and elapsedSeconds() — so @@ -13,7 +13,7 @@ */ class Accumulator { - private Usage $usage; + private Adapter $adapter; /** * In-memory buffer for metrics. @@ -29,11 +29,11 @@ class Accumulator private float $flushedAt; /** - * @param Usage $usage The Usage instance to flush buffered metrics to + * @param Adapter $adapter The adapter to flush buffered metrics to */ - public function __construct(Usage $usage) + public function __construct(Adapter $adapter) { - $this->usage = $usage; + $this->adapter = $adapter; $this->flushedAt = microtime(true); } @@ -64,9 +64,7 @@ public function collect(string $tenant, string $metric, int $value, string $type if ($value < 0) { throw new \InvalidArgumentException('Value cannot be negative'); } - if ($type !== Usage::TYPE_EVENT && $type !== Usage::TYPE_GAUGE) { - throw new \InvalidArgumentException("Invalid metric type '{$type}'. Allowed: " . Usage::TYPE_EVENT . ', ' . Usage::TYPE_GAUGE); - } + Adapter::assertType($type); // Hash the full identity so distinct (tenant, metric, type, tags) // tuples never collide on the key — a raw `:`-join would let @@ -76,7 +74,7 @@ public function collect(string $tenant, string $metric, int $value, string $type ksort($canonicalTags); $key = md5(json_encode([$tenant, $metric, $type, $canonicalTags], JSON_THROW_ON_ERROR)); - if ($type === Usage::TYPE_EVENT && isset($this->buffer[$key])) { + if ($type === Adapter::TYPE_EVENT && isset($this->buffer[$key])) { // Additive: sum values for the same tenant + metric + tags combination $this->buffer[$key]['value'] += $value; } else { @@ -123,7 +121,7 @@ public function flush(): bool $gauges = []; foreach ($this->buffer as $key => $entry) { - if ($entry['type'] === Usage::TYPE_EVENT) { + if ($entry['type'] === Adapter::TYPE_EVENT) { $events[] = $entry; $eventKeys[] = $key; } else { @@ -136,7 +134,7 @@ public function flush(): bool // Flush events — clear buffer entries only on success. if (!empty($events)) { - if ($this->usage->addBatch($events, Usage::TYPE_EVENT)) { + if ($this->adapter->addBatch($events, Adapter::TYPE_EVENT)) { foreach ($eventKeys as $key) { unset($this->buffer[$key]); } @@ -147,7 +145,7 @@ public function flush(): bool // Flush gauges — clear buffer entries only on success. if (!empty($gauges)) { - if ($this->usage->addBatch($gauges, Usage::TYPE_GAUGE)) { + if ($this->adapter->addBatch($gauges, Adapter::TYPE_GAUGE)) { foreach ($gaugeKeys as $key) { unset($this->buffer[$key]); } diff --git a/src/Usage/Adapter.php b/src/Usage/Adapter.php index ddbf659..092c212 100644 --- a/src/Usage/Adapter.php +++ b/src/Usage/Adapter.php @@ -4,6 +4,25 @@ abstract class Adapter { + public const TYPE_EVENT = 'event'; + public const TYPE_GAUGE = 'gauge'; + + /** + * Assert that $type is a valid metric type, throwing otherwise. + * + * Single source of truth for the event/gauge guard shared by the adapters + * and the Accumulator. $prefix lets batch validators add positional context + * (e.g. "Metric #3: ") to the message. + * + * @throws \InvalidArgumentException + */ + public static function assertType(string $type, string $prefix = ''): void + { + if ($type !== self::TYPE_EVENT && $type !== self::TYPE_GAUGE) { + throw new \InvalidArgumentException($prefix . "Invalid type '{$type}'. Allowed: " . self::TYPE_EVENT . ', ' . self::TYPE_GAUGE); + } + } + /** * Get adapter name */ @@ -130,7 +149,7 @@ abstract public function count(string $tenant, array $queries = [], ?string $typ * @param string $type Metric type: 'event' or 'gauge' * @return int */ - abstract public function sum(string $tenant, array $queries = [], string $attribute = 'value', string $type = Usage::TYPE_EVENT): int; + abstract public function sum(string $tenant, array $queries = [], string $attribute = 'value', string $type = self::TYPE_EVENT): int; /** * Find event metrics from the pre-aggregated daily table. diff --git a/src/Usage/Adapter/ClickHouse.php b/src/Usage/Adapter/ClickHouse.php index 9cf0cd5..a633f2b 100644 --- a/src/Usage/Adapter/ClickHouse.php +++ b/src/Usage/Adapter/ClickHouse.php @@ -13,7 +13,6 @@ use Utopia\Psr7\Request\Factory as RequestFactory; use Utopia\Query\Query; use Utopia\Usage\Metric; -use Utopia\Usage\Usage; use Utopia\Usage\UsageQuery; use Utopia\Validator\Hostname; @@ -417,7 +416,7 @@ private function getEventsDailyTableName(): string */ private function getTableForType(string $type): string { - return $type === Usage::TYPE_GAUGE ? $this->getGaugesTableName() : $this->getEventsTableName(); + return $type === self::TYPE_GAUGE ? $this->getGaugesTableName() : $this->getEventsTableName(); } /** @@ -1031,7 +1030,7 @@ private function validateAttributeName(string $attributeName, string $type = 'ev */ private function validateGroupByAttribute(string $attribute, string $type): bool { - $allowed = $type === Usage::TYPE_GAUGE ? Metric::GAUGE_COLUMNS : Metric::EVENT_COLUMNS; + $allowed = $type === self::TYPE_GAUGE ? Metric::GAUGE_COLUMNS : Metric::EVENT_COLUMNS; if (in_array($attribute, $allowed, true)) { return true; @@ -1203,9 +1202,7 @@ private function validateMetricData(string $metric, int $value, string $type, ar throw new Exception($prefix . 'Value cannot be negative'); } - if ($type !== Usage::TYPE_EVENT && $type !== Usage::TYPE_GAUGE) { - throw new \InvalidArgumentException($prefix . "Invalid type '{$type}'. Allowed: " . Usage::TYPE_EVENT . ', ' . Usage::TYPE_GAUGE); - } + self::assertType($type, $prefix); } /** @@ -1378,11 +1375,11 @@ public function find(string $tenant, array $queries = [], ?string $type = null): // up to 2N rows. Slice the merged result back down to the user's // requested limit. Tables whose schema doesn't support every filter // attribute (e.g. `path` on a gauge query) are skipped. - $events = $this->queriesMatchType($queries, Usage::TYPE_EVENT) - ? $this->findFromTable($tenant, $queries, Usage::TYPE_EVENT) + $events = $this->queriesMatchType($queries, self::TYPE_EVENT) + ? $this->findFromTable($tenant, $queries, self::TYPE_EVENT) : []; - $gauges = $this->queriesMatchType($queries, Usage::TYPE_GAUGE) - ? $this->findFromTable($tenant, $queries, Usage::TYPE_GAUGE) + $gauges = $this->queriesMatchType($queries, self::TYPE_GAUGE) + ? $this->findFromTable($tenant, $queries, self::TYPE_GAUGE) : []; $merged = array_merge($events, $gauges); @@ -1526,7 +1523,7 @@ private function findAggregatedFromTable(array $parsed, string $fromTable, strin $hasInterval = isset($parsed['groupByInterval']); // Choose aggregation function based on metric type - $valueExpr = $type === Usage::TYPE_GAUGE + $valueExpr = $type === self::TYPE_GAUGE ? 'argMax(value, time) as value' : 'SUM(value) as value'; @@ -1689,11 +1686,11 @@ public function count(string $tenant, array $queries = [], ?string $type = null, // capped at $max, so naively summing them could yield up to 2*$max. // Cap the combined total at $max in PHP to honour the contract. // Skip a table when its schema can't satisfy every filter attribute. - $events = $this->queriesMatchType($queries, Usage::TYPE_EVENT) - ? $this->countFromTable($tenant, $queries, Usage::TYPE_EVENT, $max) + $events = $this->queriesMatchType($queries, self::TYPE_EVENT) + ? $this->countFromTable($tenant, $queries, self::TYPE_EVENT, $max) : 0; - $gauges = $this->queriesMatchType($queries, Usage::TYPE_GAUGE) - ? $this->countFromTable($tenant, $queries, Usage::TYPE_GAUGE, $max) + $gauges = $this->queriesMatchType($queries, self::TYPE_GAUGE) + ? $this->countFromTable($tenant, $queries, self::TYPE_GAUGE, $max) : 0; $total = $events + $gauges; @@ -1764,11 +1761,12 @@ private function countFromTable(string $tenant, array $queries, string $type, ?i * @return int * @throws Exception */ - public function sum(string $tenant, array $queries = [], string $attribute = 'value', string $type = Usage::TYPE_EVENT): int + public function sum(string $tenant, array $queries = [], string $attribute = 'value', string $type = self::TYPE_EVENT): int { + self::assertType($type); $this->setOperationContext('sum()'); - if ($type === Usage::TYPE_EVENT && $attribute === 'value') { + if ($type === self::TYPE_EVENT && $attribute === 'value') { return $this->routedSum($tenant, $queries, 'sum'); } @@ -1799,7 +1797,7 @@ private function routedSum(string $tenant, array $queries, string $operation): i return $total; } - return $this->sumFromTable($tenant, $queries, 'value', Usage::TYPE_EVENT); + return $this->sumFromTable($tenant, $queries, 'value', self::TYPE_EVENT); } /** @@ -2356,7 +2354,7 @@ private function maybeDualRead(string $tenant, array $queries, string $route, ar } try { - $rawTotal = $this->sumFromTable($tenant, $queries, 'value', Usage::TYPE_EVENT); + $rawTotal = $this->sumFromTable($tenant, $queries, 'value', self::TYPE_EVENT); } catch (Throwable $e) { return; } @@ -2396,9 +2394,9 @@ private function sumHybridDailyAndRaw(string $tenant, array $queries, array $pla $eventsTable = $this->buildTableReference($this->getEventsTableName()); $split = $this->splitTimeQueries($queries); - $parsed = $this->parseQueries($tenant, $queries, Usage::TYPE_EVENT); + $parsed = $this->parseQueries($tenant, $queries, self::TYPE_EVENT); $dailyParsed = $this->prefixParsedParams( - $this->parseQueries($tenant, $split['nonTime'], Usage::TYPE_EVENT), + $this->parseQueries($tenant, $split['nonTime'], self::TYPE_EVENT), 'd_' ); @@ -2491,7 +2489,7 @@ public function findDaily(string $tenant, array $queries = []): array $this->validateDailyAttributeName($attr); } } - $parsed = $this->parseQueries($tenant, $queries, Usage::TYPE_EVENT); + $parsed = $this->parseQueries($tenant, $queries, self::TYPE_EVENT); $whereData = $this->buildWhereClause($parsed['filters'], $parsed['params']); $groupByColumns = $this->sharedTables ? ['tenant'] : []; @@ -2516,7 +2514,7 @@ public function findDaily(string $tenant, array $queries = []): array $sql = "SELECT {$selectColumns} FROM {$fromTable}{$whereData['clause']} GROUP BY {$groupBySql}{$orderClause}{$limitClause}{$offsetClause} FORMAT JSON"; - return $this->parseResults($this->query($sql, $whereData['params']), Usage::TYPE_EVENT); + return $this->parseResults($this->query($sql, $whereData['params']), self::TYPE_EVENT); } /** @@ -2553,7 +2551,7 @@ private function sumDailyTotal(string $tenant, array $queries, string $attribute $this->validateDailyAttributeName($attr); } } - $parsed = $this->parseQueries($tenant, $queries, Usage::TYPE_EVENT); + $parsed = $this->parseQueries($tenant, $queries, self::TYPE_EVENT); $whereData = $this->buildWhereClause($parsed['filters'], $parsed['params']); $sql = "SELECT sum({$escapedAttribute}) as total FROM {$fromTable}{$whereData['clause']} FORMAT JSON"; @@ -2601,7 +2599,7 @@ public function sumDailyBatch(string $tenant, array $metrics, array $queries = [ } $metricInClause = implode(', ', $metricPlaceholders); - $parsed = $this->parseQueries($tenant, $queries, Usage::TYPE_EVENT); + $parsed = $this->parseQueries($tenant, $queries, self::TYPE_EVENT); $params = array_merge($metricParams, $parsed['params']); $whereData = $this->buildWhereClause($parsed['filters'], $params); @@ -2668,11 +2666,11 @@ public function getTimeSeries(string $tenant, array $metrics, string $interval, } $typesToQuery = []; - if ($type === Usage::TYPE_EVENT || $type === null) { - $typesToQuery[] = Usage::TYPE_EVENT; + if ($type === self::TYPE_EVENT || $type === null) { + $typesToQuery[] = self::TYPE_EVENT; } - if ($type === Usage::TYPE_GAUGE || $type === null) { - $typesToQuery[] = Usage::TYPE_GAUGE; + if ($type === self::TYPE_GAUGE || $type === null) { + $typesToQuery[] = self::TYPE_GAUGE; } foreach ($typesToQuery as $queryType) { @@ -2758,7 +2756,7 @@ private function getTimeSeriesFromTable(string $tenant, array $metrics, string $ $additionalWhere = ' AND ' . implode(' AND ', $additionalFilters); } - $valueExpr = $type === Usage::TYPE_EVENT + $valueExpr = $type === self::TYPE_EVENT ? 'SUM(value) as agg_value' : 'argMax(value, time) as agg_value'; @@ -2868,11 +2866,11 @@ public function getTotal(string $tenant, string $metric, array $queries = [], ?s { $this->setOperationContext('getTotal()'); - if ($type === Usage::TYPE_EVENT) { + if ($type === self::TYPE_EVENT) { return $this->getTotalFromEvents($tenant, $metric, $queries); } - if ($type === Usage::TYPE_GAUGE) { + if ($type === self::TYPE_GAUGE) { return $this->getTotalFromGauges($tenant, $metric, $queries); } @@ -2924,7 +2922,7 @@ private function getTotalFromGauges(string $tenant, string $metric, array $queri $tableName = $this->getGaugesTableName(); $fromTable = $this->buildTableReference($tableName); - $parsed = $this->parseQueries($tenant, $queries, Usage::TYPE_GAUGE); + $parsed = $this->parseQueries($tenant, $queries, self::TYPE_GAUGE); $whereData = $this->buildWhereClause($parsed['filters'], $parsed['params']); $sql = " @@ -2973,11 +2971,11 @@ public function getTotalBatch(string $tenant, array $metrics, array $queries = [ $contributingType = []; $typesToQuery = []; - if ($type === Usage::TYPE_EVENT || $type === null) { - $typesToQuery[] = Usage::TYPE_EVENT; + if ($type === self::TYPE_EVENT || $type === null) { + $typesToQuery[] = self::TYPE_EVENT; } - if ($type === Usage::TYPE_GAUGE || $type === null) { - $typesToQuery[] = Usage::TYPE_GAUGE; + if ($type === self::TYPE_GAUGE || $type === null) { + $typesToQuery[] = self::TYPE_GAUGE; } foreach ($typesToQuery as $queryType) { @@ -3006,7 +3004,7 @@ public function getTotalBatch(string $tenant, array $metrics, array $queries = [ ? $whereClause . ' AND ' . $metricFilter : ' WHERE ' . $metricFilter; - $valueExpr = $queryType === Usage::TYPE_EVENT + $valueExpr = $queryType === self::TYPE_EVENT ? 'SUM(value) as agg_val' : 'argMax(value, time) as agg_val'; @@ -3696,11 +3694,11 @@ public function purge(string $tenant, array $queries = [], ?string $type = null) $this->setOperationContext('purge()'); $typesToPurge = []; - if ($type === Usage::TYPE_EVENT || $type === null) { - $typesToPurge[] = Usage::TYPE_EVENT; + if ($type === self::TYPE_EVENT || $type === null) { + $typesToPurge[] = self::TYPE_EVENT; } - if ($type === Usage::TYPE_GAUGE || $type === null) { - $typesToPurge[] = Usage::TYPE_GAUGE; + if ($type === self::TYPE_GAUGE || $type === null) { + $typesToPurge[] = self::TYPE_GAUGE; } foreach ($typesToPurge as $purgeType) { @@ -3719,7 +3717,7 @@ public function purge(string $tenant, array $queries = [], ?string $type = null) $sql = "DELETE FROM {$escapedTable}{$whereClause}"; $this->query($sql, $params); - if ($purgeType === Usage::TYPE_EVENT) { + if ($purgeType === self::TYPE_EVENT) { $this->purgeDaily($tenant, $queries); } } @@ -3813,7 +3811,7 @@ private function purgeDaily(string $tenant, array $queries): void // forward to the rollup. $dailyTable = $this->buildTableReference($this->getEventsDailyTableName()); - $parsed = $this->parseQueries($tenant, $dailyQueries, Usage::TYPE_EVENT); + $parsed = $this->parseQueries($tenant, $dailyQueries, self::TYPE_EVENT); $whereData = $this->buildWhereClause($parsed['filters'], $parsed['params']); $whereClause = $whereData['clause']; diff --git a/src/Usage/Adapter/Database.php b/src/Usage/Adapter/Database.php index 4d25bdb..17cc707 100644 --- a/src/Usage/Adapter/Database.php +++ b/src/Usage/Adapter/Database.php @@ -7,7 +7,6 @@ use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Query as DatabaseQuery; use Utopia\Usage\Metric; -use Utopia\Usage\Usage; use Utopia\Usage\UsageQuery; use Utopia\Query\Query; @@ -133,13 +132,11 @@ protected function getColumnDefinition(string $id, string $type = 'event'): stri */ public function addBatch(array $metrics, string $type, int $batchSize = 1000): bool { + self::assertType($type); + $this->db->getAuthorization()->skip(function () use ($metrics, $type, $batchSize) { $entries = []; foreach ($metrics as $metric) { - if ($type !== Usage::TYPE_EVENT && $type !== Usage::TYPE_GAUGE) { - throw new \InvalidArgumentException("Invalid type '{$type}'. Allowed: event, gauge"); - } - if ($metric['value'] < 0) { throw new \InvalidArgumentException('Value cannot be negative'); } @@ -214,7 +211,7 @@ public function getTotal(string $tenant, string $metric, array $queries = [], ?s Query::equal('metric', [$metric]), ]); - if ($type === Usage::TYPE_GAUGE) { + if ($type === self::TYPE_GAUGE) { // For gauge, return the most recent value by time. find() does // not guarantee any ordering, so we explicitly sort + limit // here instead of relying on insertion order. @@ -237,7 +234,7 @@ public function getTotal(string $tenant, string $metric, array $queries = [], ?s return 0; } - if ($type === Usage::TYPE_EVENT) { + if ($type === self::TYPE_EVENT) { // For events, SUM all values $sum = 0; foreach ($results as $result) { @@ -250,7 +247,7 @@ public function getTotal(string $tenant, string $metric, array $queries = [], ?s $eventResults = []; $gaugeResults = []; foreach ($results as $result) { - if ($result->getType() === Usage::TYPE_GAUGE) { + if ($result->getType() === self::TYPE_GAUGE) { $gaugeResults[] = $result; } else { $eventResults[] = $result; @@ -318,7 +315,7 @@ public function getTotalBatch(string $tenant, array $metrics, array $queries = [ * @param string $type 'event' or 'gauge' * @return int */ - public function sum(string $tenant, array $queries = [], string $attribute = 'value', string $type = Usage::TYPE_EVENT): int + public function sum(string $tenant, array $queries = [], string $attribute = 'value', string $type = self::TYPE_EVENT): int { /** @var array $results */ $results = $this->find($tenant, $queries, $type); @@ -340,7 +337,7 @@ public function sum(string $tenant, array $queries = [], string $attribute = 'va */ public function findDaily(string $tenant, array $queries = []): array { - return $this->find($tenant, $queries, Usage::TYPE_EVENT); + return $this->find($tenant, $queries, self::TYPE_EVENT); } /** @@ -370,7 +367,7 @@ public function sumDailyBatch(string $tenant, array $metrics, array $queries = [ */ public function sumDaily(string $tenant, array $queries = [], string $attribute = 'value'): int { - return $this->sum($tenant, $queries, $attribute, Usage::TYPE_EVENT); + return $this->sum($tenant, $queries, $attribute, self::TYPE_EVENT); } /** @@ -632,9 +629,7 @@ private function withTypeFilter(array $queries, ?string $type): array return $queries; } - if ($type !== Usage::TYPE_EVENT && $type !== Usage::TYPE_GAUGE) { - throw new \InvalidArgumentException("Invalid type '{$type}'. Allowed: " . Usage::TYPE_EVENT . ', ' . Usage::TYPE_GAUGE); - } + self::assertType($type); return array_merge($queries, [Query::equal('type', [$type])]); } diff --git a/src/Usage/Tenant.php b/src/Usage/Tenant.php index fe5a6f6..a5ad784 100644 --- a/src/Usage/Tenant.php +++ b/src/Usage/Tenant.php @@ -3,23 +3,23 @@ namespace Utopia\Usage; /** - * Tenant-scoped view over a Usage instance. + * Tenant-scoped view over an Adapter. * * Binds a tenant once at construction and forwards every query/mutation to the - * underlying Usage with that tenant pre-filled — so callers that only ever + * underlying Adapter with that tenant pre-filled — so callers that only ever * touch one tenant don't repeat it on every call. */ class Tenant { - private Usage $usage; + private Adapter $adapter; private string $tenant; /** - * @param Usage $usage The underlying (tenant-agnostic) Usage instance + * @param Adapter $adapter The underlying (tenant-agnostic) adapter * @param string $tenant Tenant this view is scoped to (non-empty) */ - public function __construct(Usage $usage, string $tenant) + public function __construct(Adapter $adapter, string $tenant) { // Reject '' at construction: an empty scope would silently read/write // the empty tenant in shared-tables mode. ("0" is a valid id.) @@ -27,7 +27,7 @@ public function __construct(Usage $usage, string $tenant) throw new \InvalidArgumentException('Tenant cannot be empty'); } - $this->usage = $usage; + $this->adapter = $adapter; $this->tenant = $tenant; } @@ -47,7 +47,7 @@ public function addBatch(array $metrics, string $type, int $batchSize = 1000): b } unset($metric); - return $this->usage->addBatch($metrics, $type, $batchSize); + return $this->adapter->addBatch($metrics, $type, $batchSize); } /** @@ -58,7 +58,7 @@ public function addBatch(array $metrics, string $type, int $batchSize = 1000): b */ public function getTimeSeries(array $metrics, string $interval, string $startDate, string $endDate, array $queries = [], bool $zeroFill = true, ?string $type = null): array { - return $this->usage->getTimeSeries($this->tenant, $metrics, $interval, $startDate, $endDate, $queries, $zeroFill, $type); + return $this->adapter->getTimeSeries($this->tenant, $metrics, $interval, $startDate, $endDate, $queries, $zeroFill, $type); } /** @@ -67,7 +67,7 @@ public function getTimeSeries(array $metrics, string $interval, string $startDat */ public function getTotal(string $metric, array $queries = [], ?string $type = null): int { - return $this->usage->getTotal($this->tenant, $metric, $queries, $type); + return $this->adapter->getTotal($this->tenant, $metric, $queries, $type); } /** @@ -78,7 +78,7 @@ public function getTotal(string $metric, array $queries = [], ?string $type = nu */ public function getTotalBatch(array $metrics, array $queries = [], ?string $type = null): array { - return $this->usage->getTotalBatch($this->tenant, $metrics, $queries, $type); + return $this->adapter->getTotalBatch($this->tenant, $metrics, $queries, $type); } /** @@ -87,7 +87,7 @@ public function getTotalBatch(array $metrics, array $queries = [], ?string $type */ public function purge(array $queries = [], ?string $type = null): bool { - return $this->usage->purge($this->tenant, $queries, $type); + return $this->adapter->purge($this->tenant, $queries, $type); } /** @@ -97,7 +97,7 @@ public function purge(array $queries = [], ?string $type = null): bool */ public function find(array $queries = [], ?string $type = null): array { - return $this->usage->find($this->tenant, $queries, $type); + return $this->adapter->find($this->tenant, $queries, $type); } /** @@ -106,16 +106,16 @@ public function find(array $queries = [], ?string $type = null): array */ public function count(array $queries = [], ?string $type = null, ?int $max = null): int { - return $this->usage->count($this->tenant, $queries, $type, $max); + return $this->adapter->count($this->tenant, $queries, $type, $max); } /** * @param array<\Utopia\Query\Query> $queries * @throws \Exception */ - public function sum(array $queries = [], string $attribute = 'value', string $type = Usage::TYPE_EVENT): int + public function sum(array $queries = [], string $attribute = 'value', string $type = Adapter::TYPE_EVENT): int { - return $this->usage->sum($this->tenant, $queries, $attribute, $type); + return $this->adapter->sum($this->tenant, $queries, $attribute, $type); } /** @@ -125,7 +125,7 @@ public function sum(array $queries = [], string $attribute = 'value', string $ty */ public function findDaily(array $queries = []): array { - return $this->usage->findDaily($this->tenant, $queries); + return $this->adapter->findDaily($this->tenant, $queries); } /** @@ -134,7 +134,7 @@ public function findDaily(array $queries = []): array */ public function sumDaily(array $queries = [], string $attribute = 'value'): int { - return $this->usage->sumDaily($this->tenant, $queries, $attribute); + return $this->adapter->sumDaily($this->tenant, $queries, $attribute); } /** @@ -145,6 +145,6 @@ public function sumDaily(array $queries = [], string $attribute = 'value'): int */ public function sumDailyBatch(array $metrics, array $queries = []): array { - return $this->usage->sumDailyBatch($this->tenant, $metrics, $queries); + return $this->adapter->sumDailyBatch($this->tenant, $metrics, $queries); } } diff --git a/src/Usage/Usage.php b/src/Usage/Usage.php deleted file mode 100644 index 7897158..0000000 --- a/src/Usage/Usage.php +++ /dev/null @@ -1,254 +0,0 @@ -adapter = $adapter; - } - - /** - * Get the current adapter. - */ - public function getAdapter(): Adapter - { - return $this->adapter; - } - - /** - * Check adapter health and connection status. - * - * @return array Health check result with 'healthy' bool and additional adapter-specific information - */ - public function healthCheck(): array - { - return $this->adapter->healthCheck(); - } - - /** - * Setup the usage metrics storage. - * - * @throws \Exception - */ - public function setup(): void - { - $this->adapter->setup(); - } - - /** - * Add metrics in batch (raw append). - * - * Callers must explicitly pass the metric type so event and gauge - * writes are never confused at the call site. - * - * Each metric carries its own `tenant` (shared-tables mode), so a single - * batch may span multiple tenants. - * - * @param array}> $metrics - * @param string $type Metric type: 'event' or 'gauge' - * @param int $batchSize Maximum number of metrics per INSERT statement - * @return bool - * @throws \Exception - */ - public function addBatch(array $metrics, string $type, int $batchSize = 1000): bool - { - return $this->adapter->addBatch($metrics, $type, $batchSize); - } - - /** - * Get time series data for metrics. - * - * @param string $tenant Tenant scope (shared-tables mode) - * @param array $metrics List of metric names - * @param string $interval '1h' or '1d' - * @param string $startDate Start datetime - * @param string $endDate End datetime - * @param array<\Utopia\Query\Query> $queries Additional filters - * @param bool $zeroFill Whether to fill gaps with zero values - * @param string|null $type Metric type: 'event', 'gauge', or null (query both) - * @return array}> - * @throws \Exception - */ - public function getTimeSeries(string $tenant, array $metrics, string $interval, string $startDate, string $endDate, array $queries = [], bool $zeroFill = true, ?string $type = null): array - { - return $this->adapter->getTimeSeries($tenant, $metrics, $interval, $startDate, $endDate, $queries, $zeroFill, $type); - } - - /** - * Get total value for a single metric. - * - * @param string $tenant Tenant scope (shared-tables mode) - * @param string $metric Metric name - * @param array<\Utopia\Query\Query> $queries Additional filters - * @param string|null $type Metric type: 'event', 'gauge', or null (query both) - * @return int - * @throws \Exception - */ - public function getTotal(string $tenant, string $metric, array $queries = [], ?string $type = null): int - { - return $this->adapter->getTotal($tenant, $metric, $queries, $type); - } - - /** - * Get totals for multiple metrics in a single query. - * - * @param string $tenant Tenant scope (shared-tables mode) - * @param array $metrics List of metric names - * @param array<\Utopia\Query\Query> $queries Additional filters - * @param string|null $type Metric type: 'event', 'gauge', or null (query both) - * @return array - * @throws \Exception - */ - public function getTotalBatch(string $tenant, array $metrics, array $queries = [], ?string $type = null): array - { - return $this->adapter->getTotalBatch($tenant, $metrics, $queries, $type); - } - - /** - * Purge usage metrics matching the given queries. - * When no queries are provided, all metrics are deleted. - * - * @param string $tenant Tenant scope (shared-tables mode) - * @param array<\Utopia\Query\Query> $queries - * @param string|null $type Metric type: 'event', 'gauge', or null (purge both) - * @throws \Exception - */ - public function purge(string $tenant, array $queries = [], ?string $type = null): bool - { - return $this->adapter->purge($tenant, $queries, $type); - } - - /** - * Find metrics using Query objects. - * - * @param string $tenant Tenant scope (shared-tables mode) - * @param array<\Utopia\Query\Query> $queries - * @param string|null $type Metric type: 'event', 'gauge', or null (query both) - * @return array - * @throws \Exception - */ - public function find(string $tenant, array $queries = [], ?string $type = null): array - { - return $this->adapter->find($tenant, $queries, $type); - } - - /** - * Count metrics using Query objects. - * - * When $max is non-null the count is bounded at the database level. - * Callers that only need a capped total (e.g. to render "5000+") should - * pass $max so the adapter can short-circuit the count for large tables. - * - * @param string $tenant Tenant scope (shared-tables mode) - * @param array<\Utopia\Query\Query> $queries - * @param string|null $type Metric type: 'event', 'gauge', or null (count both) - * @param int|null $max Optional upper bound for the count (inclusive) - * @return int - * @throws \Exception - */ - public function count(string $tenant, array $queries = [], ?string $type = null, ?int $max = null): int - { - return $this->adapter->count($tenant, $queries, $type, $max); - } - - /** - * Sum metric values using Query objects. - * - * Defaults to events because summing gauges (point-in-time snapshots) - * is semantically meaningless — it averages/accumulates snapshots rather - * than producing a useful total. Callers that truly want a gauge sum - * must opt in explicitly. - * - * @param string $tenant Tenant scope (shared-tables mode) - * @param array<\Utopia\Query\Query> $queries - * @param string $attribute Attribute to sum (default: 'value') - * @param string $type Metric type: 'event' or 'gauge' - * @return int - * @throws \Exception - */ - public function sum(string $tenant, array $queries = [], string $attribute = 'value', string $type = self::TYPE_EVENT): int - { - if ($type !== self::TYPE_EVENT && $type !== self::TYPE_GAUGE) { - throw new \InvalidArgumentException("Invalid type '{$type}'. Allowed: " . self::TYPE_EVENT . ', ' . self::TYPE_GAUGE); - } - - return $this->adapter->sum($tenant, $queries, $attribute, $type); - } - - /** - * Find event metrics from the pre-aggregated daily table. - * - * Queries the SummingMergeTree daily MV for fast billing/analytics. - * - * Note: Daily MV only stores event metrics. This method always queries - * the daily events table — gauges are never pre-aggregated. - * - * @param string $tenant Tenant scope (shared-tables mode) - * @param array<\Utopia\Query\Query> $queries - * @return array - * @throws \Exception - */ - public function findDaily(string $tenant, array $queries = []): array - { - return $this->adapter->findDaily($tenant, $queries); - } - - /** - * Sum event metric values from the pre-aggregated daily table. - * - * Use this for billing queries — reads pre-aggregated daily rows - * instead of scanning billions of raw events. - * - * Note: Daily MV only stores event metrics. This method always queries - * the daily events table — gauges are never pre-aggregated. - * - * @param string $tenant Tenant scope (shared-tables mode) - * @param array<\Utopia\Query\Query> $queries - * @param string $attribute Attribute to sum (default: 'value') - * @return int - * @throws \Exception - */ - public function sumDaily(string $tenant, array $queries = [], string $attribute = 'value'): int - { - return $this->adapter->sumDaily($tenant, $queries, $attribute); - } - - /** - * Sum multiple event metrics from the pre-aggregated daily table in one query. - * - * Note: Daily MV only stores event metrics. This method always queries - * the daily events table — gauges are never pre-aggregated. - * - * @param string $tenant Tenant scope (shared-tables mode) - * @param array $metrics List of metric names - * @param array<\Utopia\Query\Query> $queries Additional filters (e.g. date range) - * @return array Metric name => sum value - * @throws \Exception - */ - public function sumDailyBatch(string $tenant, array $metrics, array $queries = []): array - { - return $this->adapter->sumDailyBatch($tenant, $metrics, $queries); - } -} diff --git a/tests/Benchmark/BenchmarkBase.php b/tests/Benchmark/BenchmarkBase.php index 5500431..83d28c2 100644 --- a/tests/Benchmark/BenchmarkBase.php +++ b/tests/Benchmark/BenchmarkBase.php @@ -2,12 +2,12 @@ namespace Utopia\Tests\Benchmark; +use Utopia\Usage\Adapter; use PHPUnit\Framework\TestCase; use ReflectionClass; use RuntimeException; use Throwable; use Utopia\Usage\Adapter\ClickHouse as ClickHouseAdapter; -use Utopia\Usage\Usage; /** * Base class for ClickHouse benchmarks. @@ -22,7 +22,7 @@ */ abstract class BenchmarkBase extends TestCase { - protected Usage $usage; + protected Adapter $usage; protected ClickHouseAdapter $adapter; @@ -66,7 +66,7 @@ protected function setUp(): void sharedTables: true, ); - $this->usage = new Usage($this->adapter); + $this->usage = $this->adapter; $this->usage->setup(); $rowsEnv = getenv('BENCH_ROWS'); diff --git a/tests/Benchmark/EventsBench.php b/tests/Benchmark/EventsBench.php index 1e4c1e3..5528aaf 100644 --- a/tests/Benchmark/EventsBench.php +++ b/tests/Benchmark/EventsBench.php @@ -5,7 +5,7 @@ use DateTime; use DateTimeZone; use Utopia\Query\Query; -use Utopia\Usage\Usage; +use Utopia\Usage\Adapter; use Utopia\Usage\UsageQuery; class EventsBench extends BenchmarkBase @@ -29,7 +29,7 @@ public function testBenchmarks(): void Query::equal('metric', [$this->metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); }); $this->runBench('bench_events_timeseries_30d_1h', function (string $queryId) use ($start, $end): void { @@ -40,7 +40,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(5000), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); }); $this->runBench('bench_events_count_max_5k', function (string $queryId) use ($start, $end): void { @@ -49,7 +49,7 @@ public function testBenchmarks(): void Query::equal('metric', [$this->metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Usage::TYPE_EVENT, 5000); + ], Adapter::TYPE_EVENT, 5000); }); $this->runBench('bench_insert_10k', function (string $queryId): void { @@ -68,7 +68,7 @@ public function testBenchmarks(): void ]; } $this->adapter->setNextQueryId($queryId); - $this->usage->addBatch($batch, Usage::TYPE_EVENT); + $this->usage->addBatch($batch, Adapter::TYPE_EVENT); }, 3); $this->runBench('bench_events_topN_path_30d', function (string $queryId) use ($start, $end): void { @@ -79,7 +79,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(500), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); }); $this->runBench('bench_events_topN_country_30d', function (string $queryId) use ($start, $end): void { @@ -90,7 +90,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(200), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); }); $this->runBench('bench_events_topN_service_30d', function (string $queryId) use ($start, $end): void { @@ -101,7 +101,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(200), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); }); $todayStart = (new DateTime('today', new DateTimeZone('UTC')))->format('Y-m-d H:i:s'); @@ -114,7 +114,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $todayStart), Query::lessThanEqual('time', $todayEnd), Query::limit(500), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); }); $this->runBench('bench_events_topN_path_30d_filtered_resource', function (string $queryId) use ($start, $end): void { @@ -126,7 +126,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(500), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); }); $this->runBench('bench_events_topN_path_country', function (string $queryId) use ($start, $end): void { @@ -138,7 +138,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(500), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); }); $this->runBench('bench_insert_with_projections', function (string $queryId): void { @@ -158,7 +158,7 @@ public function testBenchmarks(): void ]; } $this->adapter->setNextQueryId($queryId); - $this->usage->addBatch($batch, Usage::TYPE_EVENT); + $this->usage->addBatch($batch, Adapter::TYPE_EVENT); }, 3); $this->assertNotEmpty($this->results, 'Benchmark scenarios must record results'); diff --git a/tests/Benchmark/GaugesBench.php b/tests/Benchmark/GaugesBench.php index f84eee2..f040a20 100644 --- a/tests/Benchmark/GaugesBench.php +++ b/tests/Benchmark/GaugesBench.php @@ -4,7 +4,7 @@ use DateTime; use Utopia\Query\Query; -use Utopia\Usage\Usage; +use Utopia\Usage\Adapter; use Utopia\Usage\UsageQuery; class GaugesBench extends BenchmarkBase @@ -34,7 +34,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start30d), Query::lessThanEqual('time', $endPartial), ], - Usage::TYPE_GAUGE + Adapter::TYPE_GAUGE ); }); @@ -46,7 +46,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start30d), Query::lessThanEqual('time', $endClosed), Query::limit(10), - ], Usage::TYPE_GAUGE); + ], Adapter::TYPE_GAUGE); }); $this->runBench('bench_gauges_topN_resource_30d', function (string $queryId) use ($start30d, $endClosed): void { @@ -57,7 +57,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start30d), Query::lessThanEqual('time', $endClosed), Query::limit(10), - ], Usage::TYPE_GAUGE); + ], Adapter::TYPE_GAUGE); }); $this->runBench('bench_gauges_topN_service_today_partial', function (string $queryId) use ($start30d, $endPartial): void { @@ -68,7 +68,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start30d), Query::lessThanEqual('time', $endPartial), Query::limit(10), - ], Usage::TYPE_GAUGE); + ], Adapter::TYPE_GAUGE); }); $this->assertNotEmpty($this->results, 'Benchmark scenarios must record results'); diff --git a/tests/Usage/AccumulatorTest.php b/tests/Usage/AccumulatorTest.php index f63808a..1fd6f83 100644 --- a/tests/Usage/AccumulatorTest.php +++ b/tests/Usage/AccumulatorTest.php @@ -5,7 +5,6 @@ use PHPUnit\Framework\TestCase; use Utopia\Usage\Accumulator; use Utopia\Usage\Adapter; -use Utopia\Usage\Usage; /** * Records addBatch() calls so the Accumulator can be tested without a backend. @@ -86,7 +85,7 @@ public function count(string $tenant, array $queries = [], ?string $type = null, return 0; } - public function sum(string $tenant, array $queries = [], string $attribute = 'value', string $type = Usage::TYPE_EVENT): int + public function sum(string $tenant, array $queries = [], string $attribute = 'value', string $type = Adapter::TYPE_EVENT): int { return 0; } @@ -122,14 +121,14 @@ class AccumulatorTest extends TestCase protected function setUp(): void { $this->adapter = new RecordingAdapter(); - $this->accumulator = new Accumulator(new Usage($this->adapter)); + $this->accumulator = new Accumulator($this->adapter); } public function testEventsSumByKey(): void { - $this->accumulator->collect('t1', 'requests', 10, Usage::TYPE_EVENT); - $this->accumulator->collect('t1', 'requests', 20, Usage::TYPE_EVENT); - $this->accumulator->collect('t1', 'requests', 30, Usage::TYPE_EVENT); + $this->accumulator->collect('t1', 'requests', 10, Adapter::TYPE_EVENT); + $this->accumulator->collect('t1', 'requests', 20, Adapter::TYPE_EVENT); + $this->accumulator->collect('t1', 'requests', 30, Adapter::TYPE_EVENT); // Same metric + tags = 1 entry, values summed $this->assertEquals(1, $this->accumulator->count()); @@ -137,14 +136,14 @@ public function testEventsSumByKey(): void $this->assertTrue($this->accumulator->flush()); $this->assertCount(1, $this->adapter->batches); - $this->assertEquals(Usage::TYPE_EVENT, $this->adapter->batches[0]['type']); + $this->assertEquals(Adapter::TYPE_EVENT, $this->adapter->batches[0]['type']); $this->assertEquals(60, $this->adapter->batches[0]['metrics'][0]['value']); } public function testTagsPartitionEntries(): void { - $this->accumulator->collect('t1', 'requests', 10, Usage::TYPE_EVENT, ['region' => 'us']); - $this->accumulator->collect('t1', 'requests', 20, Usage::TYPE_EVENT, ['region' => 'eu']); + $this->accumulator->collect('t1', 'requests', 10, Adapter::TYPE_EVENT, ['region' => 'us']); + $this->accumulator->collect('t1', 'requests', 20, Adapter::TYPE_EVENT, ['region' => 'eu']); // Distinct tags = distinct entries $this->assertEquals(2, $this->accumulator->count()); @@ -153,8 +152,8 @@ public function testTagsPartitionEntries(): void public function testTenantPartitionsEntries(): void { // Same metric + tags but different tenants must not collapse - $this->accumulator->collect('t1', 'requests', 10, Usage::TYPE_EVENT); - $this->accumulator->collect('t2', 'requests', 20, Usage::TYPE_EVENT); + $this->accumulator->collect('t1', 'requests', 10, Adapter::TYPE_EVENT); + $this->accumulator->collect('t2', 'requests', 20, Adapter::TYPE_EVENT); $this->assertEquals(2, $this->accumulator->count()); @@ -167,30 +166,30 @@ public function testTenantPartitionsEntries(): void public function testGaugesUseLastWriteWins(): void { - $this->accumulator->collect('t1', 'storage', 100, Usage::TYPE_GAUGE); - $this->accumulator->collect('t1', 'storage', 200, Usage::TYPE_GAUGE); - $this->accumulator->collect('t1', 'storage', 300, Usage::TYPE_GAUGE); + $this->accumulator->collect('t1', 'storage', 100, Adapter::TYPE_GAUGE); + $this->accumulator->collect('t1', 'storage', 200, Adapter::TYPE_GAUGE); + $this->accumulator->collect('t1', 'storage', 300, Adapter::TYPE_GAUGE); $this->assertEquals(1, $this->accumulator->count()); $this->assertTrue($this->accumulator->flush()); - $this->assertEquals(Usage::TYPE_GAUGE, $this->adapter->batches[0]['type']); + $this->assertEquals(Adapter::TYPE_GAUGE, $this->adapter->batches[0]['type']); $this->assertEquals(300, $this->adapter->batches[0]['metrics'][0]['value']); } public function testFlushSeparatesEventsAndGauges(): void { - $this->accumulator->collect('t1', 'requests', 10, Usage::TYPE_EVENT); - $this->accumulator->collect('t1', 'storage', 100, Usage::TYPE_GAUGE); + $this->accumulator->collect('t1', 'requests', 10, Adapter::TYPE_EVENT); + $this->accumulator->collect('t1', 'storage', 100, Adapter::TYPE_GAUGE); $this->assertTrue($this->accumulator->flush()); // One batch per type $this->assertCount(2, $this->adapter->batches); $types = [$this->adapter->batches[0]['type'], $this->adapter->batches[1]['type']]; - $this->assertContains(Usage::TYPE_EVENT, $types); - $this->assertContains(Usage::TYPE_GAUGE, $types); + $this->assertContains(Adapter::TYPE_EVENT, $types); + $this->assertContains(Adapter::TYPE_GAUGE, $types); // Buffer cleared on success $this->assertEquals(0, $this->accumulator->count()); @@ -198,7 +197,7 @@ public function testFlushSeparatesEventsAndGauges(): void public function testFailedFlushRetainsBuffer(): void { - $this->accumulator->collect('t1', 'requests', 10, Usage::TYPE_EVENT); + $this->accumulator->collect('t1', 'requests', 10, Adapter::TYPE_EVENT); $this->adapter->succeed = false; $this->assertFalse($this->accumulator->flush()); @@ -233,13 +232,13 @@ public function testEmptyTenantThrows(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Tenant cannot be empty'); - $this->accumulator->collect('', 'requests', 10, Usage::TYPE_EVENT); + $this->accumulator->collect('', 'requests', 10, Adapter::TYPE_EVENT); } public function testTenantZeroIsAccepted(): void { // "0" is a valid tenant id even though empty("0") is true in PHP - $this->accumulator->collect('0', 'requests', 10, Usage::TYPE_EVENT); + $this->accumulator->collect('0', 'requests', 10, Adapter::TYPE_EVENT); $this->assertEquals(1, $this->accumulator->count()); @@ -251,14 +250,14 @@ public function testEmptyMetricNameThrows(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Metric name cannot be empty'); - $this->accumulator->collect('t1', '', 10, Usage::TYPE_EVENT); + $this->accumulator->collect('t1', '', 10, Adapter::TYPE_EVENT); } public function testKeyDistinguishesAmbiguousTenantMetricSplits(): void { // tenant "a" + metric "b:c" must not collide with tenant "a:b" + metric "c" - $this->accumulator->collect('a', 'b:c', 10, Usage::TYPE_EVENT); - $this->accumulator->collect('a:b', 'c', 20, Usage::TYPE_EVENT); + $this->accumulator->collect('a', 'b:c', 10, Adapter::TYPE_EVENT); + $this->accumulator->collect('a:b', 'c', 20, Adapter::TYPE_EVENT); $this->assertEquals(2, $this->accumulator->count()); } @@ -266,8 +265,8 @@ public function testKeyDistinguishesAmbiguousTenantMetricSplits(): void public function testTagOrderDoesNotSplitEntries(): void { // Same logical tags in different insertion order must sum into one entry - $this->accumulator->collect('t1', 'requests', 10, Usage::TYPE_EVENT, ['teamId' => 't', 'resourceId' => 'r']); - $this->accumulator->collect('t1', 'requests', 20, Usage::TYPE_EVENT, ['resourceId' => 'r', 'teamId' => 't']); + $this->accumulator->collect('t1', 'requests', 10, Adapter::TYPE_EVENT, ['teamId' => 't', 'resourceId' => 'r']); + $this->accumulator->collect('t1', 'requests', 20, Adapter::TYPE_EVENT, ['resourceId' => 'r', 'teamId' => 't']); $this->assertEquals(1, $this->accumulator->count()); @@ -279,7 +278,7 @@ public function testNegativeValueThrows(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Value cannot be negative'); - $this->accumulator->collect('t1', 'requests', -1, Usage::TYPE_EVENT); + $this->accumulator->collect('t1', 'requests', -1, Adapter::TYPE_EVENT); } public function testInvalidTypeThrows(): void diff --git a/tests/Usage/Adapter/ClickHouseDimRoutingTest.php b/tests/Usage/Adapter/ClickHouseDimRoutingTest.php index b3b6912..915b8ad 100644 --- a/tests/Usage/Adapter/ClickHouseDimRoutingTest.php +++ b/tests/Usage/Adapter/ClickHouseDimRoutingTest.php @@ -8,7 +8,7 @@ use Utopia\Query\Query; use Utopia\Tests\Usage\Adapter\ClickHouseTestCase; use Utopia\Usage\Adapter\ClickHouse as ClickHouseAdapter; -use Utopia\Usage\Usage; +use Utopia\Usage\Adapter; use Utopia\Usage\UsageQuery; /** @@ -20,7 +20,7 @@ */ class ClickHouseDimRoutingTest extends ClickHouseTestCase { - private Usage $usage; + private Adapter $usage; private ClickHouseAdapter $adapter; @@ -38,7 +38,7 @@ protected function setUp(): void database: getenv('CLICKHOUSE_DATABASE') ?: 'default', sharedTables: true, ); - $this->usage = new Usage($this->adapter); + $this->usage = $this->adapter; $this->usage->setup(); $this->usage->purge('1'); @@ -48,7 +48,7 @@ protected function setUp(): void $this->seedHistoricalRow($this->metric, 40, '-3 days', ['path' => '/v1/c', 'method' => 'POST', 'status' => '500', 'service' => 'functions', 'country' => 'fr']); $this->usage->addBatch([ ['tenant' => '1', 'metric' => $this->metric, 'value' => 5, 'tags' => ['path' => '/v1/a', 'method' => 'GET', 'status' => '200', 'service' => 'storage', 'country' => 'us']], - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); } protected function tearDown(): void @@ -118,7 +118,7 @@ public function testTopNGroupedQueryRoutesToMatchingProjection(array $dims, stri $queryId = bin2hex(random_bytes(8)); $this->adapter->setNextQueryId($queryId); - $rolled = $this->usage->find('1', $queries, Usage::TYPE_EVENT); + $rolled = $this->usage->find('1', $queries, Adapter::TYPE_EVENT); $this->assertSame($this->rawTotal($start, $end), $this->totalOf($rolled)); $this->assertProjectionUsed($queryId, $expectedProjection); @@ -137,7 +137,7 @@ public function testMultiDimNotInAnyProjectionFallsBackToTable(): void Query::equal('metric', [$this->metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertNoProjectionUsed($queryId); } @@ -158,7 +158,7 @@ public function testFilterOnExtraColumnStillRoutesToProjectionWhenColumnPresent( Query::equal('resource', ['function']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertNoProjectionUsed($queryId); } @@ -180,7 +180,7 @@ public function testSubDayIntervalStillRoutesToProjection(): void Query::equal('metric', [$this->metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertProjectionUsed($queryId, 'p_by_path'); } @@ -198,7 +198,7 @@ public function testWindowStraddlesTodayStillRoutesToProjection(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(50), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertSame($this->rawTotal($start, $end), $this->totalOf($rolled)); // Projections are derived in the same write transaction as the @@ -210,7 +210,7 @@ public function testWindowStraddlesTodayStillRoutesToProjection(): void /** * @param array $queries */ - private function routeFor(array $queries, string $type = Usage::TYPE_EVENT): string + private function routeFor(array $queries, string $type = Adapter::TYPE_EVENT): string { $reflection = new ReflectionClass($this->adapter); $extract = $reflection->getMethod('extractRoutingPlan'); @@ -231,7 +231,7 @@ private function rawTotal(string $start, string $end): int Query::equal('metric', [$this->metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); return is_int($result) ? $result : 0; } diff --git a/tests/Usage/Adapter/ClickHouseGaugeDimRoutingTest.php b/tests/Usage/Adapter/ClickHouseGaugeDimRoutingTest.php index 562a81b..6840751 100644 --- a/tests/Usage/Adapter/ClickHouseGaugeDimRoutingTest.php +++ b/tests/Usage/Adapter/ClickHouseGaugeDimRoutingTest.php @@ -7,7 +7,7 @@ use Utopia\Query\Query; use Utopia\Tests\Usage\Adapter\ClickHouseTestCase; use Utopia\Usage\Adapter\ClickHouse as ClickHouseAdapter; -use Utopia\Usage\Usage; +use Utopia\Usage\Adapter; use Utopia\Usage\UsageQuery; /** @@ -18,7 +18,7 @@ */ class ClickHouseGaugeDimRoutingTest extends ClickHouseTestCase { - private Usage $usage; + private Adapter $usage; private ClickHouseAdapter $adapter; @@ -36,7 +36,7 @@ protected function setUp(): void database: getenv('CLICKHOUSE_DATABASE') ?: 'default', sharedTables: true, ); - $this->usage = new Usage($this->adapter); + $this->usage = $this->adapter; $this->usage->setup(); $this->usage->purge('1'); @@ -47,7 +47,7 @@ protected function setUp(): void $this->usage->addBatch([ ['tenant' => '1', 'metric' => $this->metric, 'value' => 999, 'tags' => ['service' => 'storage', 'resource' => 'file', 'resourceId' => 'f1']], - ], Usage::TYPE_GAUGE); + ], Adapter::TYPE_GAUGE); } protected function tearDown(): void @@ -118,7 +118,7 @@ public function testTopGaugesGroupedQueryRoutesToMatchingProjection(array $dims, $queryId = bin2hex(random_bytes(8)); $this->adapter->setNextQueryId($queryId); - $this->usage->find('1', $queries, Usage::TYPE_GAUGE); + $this->usage->find('1', $queries, Adapter::TYPE_GAUGE); $this->assertProjectionUsed($queryId, $expectedProjection); } @@ -138,7 +138,7 @@ public function testGaugesSubDayIntervalStillRoutesToProjection(): void Query::equal('metric', [$this->metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Usage::TYPE_GAUGE); + ], Adapter::TYPE_GAUGE); $this->assertProjectionUsed($queryId, 'p_by_service'); } @@ -156,7 +156,7 @@ public function testGaugesFilterOnNonProjectionColumnFallsBackToBaseTable(): voi Query::equal('resourceId', ['x']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Usage::TYPE_GAUGE); + ], Adapter::TYPE_GAUGE); $this->assertNoProjectionUsed($queryId); } @@ -172,7 +172,7 @@ public function testGaugesUngroupedFallsBackToBaseTable(): void Query::equal('metric', [$this->metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Usage::TYPE_GAUGE); + ], Adapter::TYPE_GAUGE); $this->assertNoProjectionUsed($queryId); } @@ -190,7 +190,7 @@ public function testGaugesWindowStraddlesTodayStillRoutesToProjection(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(50), - ], Usage::TYPE_GAUGE); + ], Adapter::TYPE_GAUGE); $this->assertProjectionUsed($queryId, 'p_by_service'); } diff --git a/tests/Usage/Adapter/ClickHouseRoutingTest.php b/tests/Usage/Adapter/ClickHouseRoutingTest.php index 862eee4..e52fd59 100644 --- a/tests/Usage/Adapter/ClickHouseRoutingTest.php +++ b/tests/Usage/Adapter/ClickHouseRoutingTest.php @@ -9,12 +9,12 @@ use Utopia\Query\Query; use Utopia\Tests\Usage\Adapter\ClickHouseTestCase; use Utopia\Usage\Adapter\ClickHouse as ClickHouseAdapter; -use Utopia\Usage\Usage; +use Utopia\Usage\Adapter; use Utopia\Usage\UsageQuery; class ClickHouseRoutingTest extends ClickHouseTestCase { - private Usage $usage; + private Adapter $usage; private ClickHouseAdapter $adapter; @@ -30,7 +30,7 @@ protected function setUp(): void database: getenv('CLICKHOUSE_DATABASE') ?: 'default', sharedTables: true, ); - $this->usage = new Usage($this->adapter); + $this->usage = $this->adapter; $this->usage->setup(); $this->usage->purge('1'); @@ -38,7 +38,7 @@ protected function setUp(): void $this->seedHistoricalRow('routed.metric', 200, '-3 days', ['path' => '/v1/b']); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'routed.metric', 'value' => 50, 'tags' => ['path' => '/v1/c']], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); } protected function tearDown(): void @@ -87,7 +87,7 @@ public function testClosedDayWindowRoutesToDaily(): void Query::equal('metric', ['routed.metric']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -110,7 +110,7 @@ public function testInclusiveMidnightUpperBoundExcludesEndDayOnDailyRoute(): voi Query::equal('metric', ['routed.metric']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -134,7 +134,7 @@ public function testMidDayClosedWindowFallsBackToRaw(): void Query::equal('metric', ['routed.metric']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -154,7 +154,7 @@ public function testWindowStraddlesTodayRoutesHybrid(): void Query::equal('metric', ['routed.metric']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -175,7 +175,7 @@ public function testHybridSumKeepsParamsDistinctWhenMetricFollowsTimeBounds(): v Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::equal('metric', ['routed.metric']), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -195,7 +195,7 @@ public function testDimensionPresentForcesRaw(): void Query::equal('path', ['/v1/a']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -214,7 +214,7 @@ public function testFilterOnNonDailyColumnForcesRaw(): void Query::equal('country', ['us']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -235,7 +235,7 @@ public function testMidDayStartWithHybridWindowFallsBackToRaw(): void Query::equal('metric', [$metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -255,7 +255,7 @@ public function testIntervalPresentForcesRaw(): void Query::equal('metric', ['routed.metric']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -277,7 +277,7 @@ public function testDuplicateTimeFiltersTakeTightestBound(): void Query::greaterThanEqual('time', $startTighter), Query::lessThanEqual('time', $endLoose), Query::lessThanEqual('time', $endTighter), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -295,7 +295,7 @@ public function testOpenEndedWindowForcesRaw(): void $this->usage->sum('1', [ Query::equal('metric', ['routed.metric']), Query::greaterThanEqual('time', $start), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -311,7 +311,7 @@ public function testMalformedTimeStringForcesRaw(): void Query::equal('metric', ['routed.metric']), Query::greaterThanEqual('time', 'not-a-date'), Query::lessThanEqual('time', 'not-a-date-either'), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); } catch (Exception $e) { } @@ -346,14 +346,14 @@ public function testNarrowPurgeWithNoTimeBoundDoesNotWipeDailyMv(): void // DELETE WHERE 1=1 and wipe both metrics' daily rows. $this->usage->purge('1', [ Query::equal('path', ['/v1/remove']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->adapter->clearRouteLog(); $keepSum = $this->usage->sum('1', [ Query::equal('metric', ['purge.keep']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $this->assertSame(100, $keepSum, 'unrelated daily rows must survive a narrow purge'); } @@ -380,7 +380,7 @@ public function testValueFilterPurgeDoesNotMatchAggregateDailyRows(): void $this->usage->purge('1', [ Query::equal('value', [10]), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); // value is raw-only, so the routed sum stays on raw — it // sees the still-present rows. The point of this test is the @@ -403,7 +403,7 @@ private function sumRaw(string $metric, string $start, string $end): int Query::equal('metric', [$metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $this->adapter->clearRouteLog(); return is_int($result) ? $result : 0; } diff --git a/tests/Usage/Adapter/ClickHouseSchemaTest.php b/tests/Usage/Adapter/ClickHouseSchemaTest.php index 37c9e84..7ded98f 100644 --- a/tests/Usage/Adapter/ClickHouseSchemaTest.php +++ b/tests/Usage/Adapter/ClickHouseSchemaTest.php @@ -4,7 +4,6 @@ use Utopia\Tests\Usage\Adapter\ClickHouseTestCase; use Utopia\Usage\Adapter\ClickHouse as ClickHouseAdapter; -use Utopia\Usage\Usage; /** * Asserts the events / gauges / daily / dim-rollup tables emerge from @@ -27,7 +26,7 @@ protected function setUp(): void database: getenv('CLICKHOUSE_DATABASE') ?: 'default', sharedTables: true, ); - $usage = new Usage($this->adapter); + $usage = $this->adapter; $usage->setup(); } @@ -72,7 +71,7 @@ public function testDailyTableMatchesPrePrSchema(): void $this->queryRaw($this->adapter, "DROP TABLE IF EXISTS {$mvName}"); $this->queryRaw($this->adapter, "DROP TABLE IF EXISTS {$fullName}"); - $usage = new Usage($this->adapter); + $usage = $this->adapter; $usage->setup(); $ddl = $this->showCreate($dailyTable); @@ -144,7 +143,7 @@ public function testSetupBackfillsServiceResourceOnLegacyGaugesTable(): void SETTINGS allow_nullable_key = 1 "); - $usage = new Usage($legacyAdapter); + $usage = $legacyAdapter; $usage->setup(); $rawString = $this->queryRaw($legacyAdapter, "SHOW CREATE TABLE {$fullName} FORMAT JSON"); diff --git a/tests/Usage/Adapter/ClickHouseTest.php b/tests/Usage/Adapter/ClickHouseTest.php index aa7e031..77bd60a 100644 --- a/tests/Usage/Adapter/ClickHouseTest.php +++ b/tests/Usage/Adapter/ClickHouseTest.php @@ -6,7 +6,7 @@ use Utopia\Query\Query; use Utopia\Tests\Usage\UsageBase; use Utopia\Usage\Adapter\ClickHouse as ClickHouseAdapter; -use Utopia\Usage\Usage; +use Utopia\Usage\Adapter; use Utopia\Usage\UsageQuery; class ClickHouseTest extends TestCase @@ -32,7 +32,7 @@ protected function initializeUsage(): void database: getenv('CLICKHOUSE_DATABASE') ?: 'default', ); - $this->usage = new Usage($adapter); + $this->usage = $adapter; $this->usage->setup(); } @@ -55,7 +55,7 @@ public function testPerMetricTenantInBatch(): void sharedTables: true, ); - $usage = new Usage($adapter); + $usage = $adapter; $usage->setup(); $usage->purge('2'); @@ -69,19 +69,19 @@ public function testPerMetricTenantInBatch(): void ], ]; - $this->assertTrue($usage->addBatch($metrics, Usage::TYPE_EVENT)); + $this->assertTrue($usage->addBatch($metrics, Adapter::TYPE_EVENT)); // Reading under tenant '2' returns the row; tenant '1' must not see it. $results = $usage->find('2', [ \Utopia\Query\Query::equal('metric', ['tenant-override']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(1, $results); $this->assertEquals('2', $results[0]->getTenant()); $this->assertCount(0, $usage->find('1', [ \Utopia\Query\Query::equal('metric', ['tenant-override']), - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $usage->purge('2'); } @@ -99,10 +99,10 @@ public function testAddBatchWithBatchSize(): void ]; // Process with batch size of 2 - $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT, 2)); + $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT, 2)); // Verify all metrics were inserted - $results = $this->usage->find('1', [], Usage::TYPE_EVENT); + $results = $this->usage->find('1', [], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(4, count($results)); } @@ -118,10 +118,10 @@ public function testAddBatchGaugeWithBatchSize(): void ]; // Process with batch size of 2 - $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_GAUGE, 2)); + $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_GAUGE, 2)); // Verify gauge metrics were inserted - $results = $this->usage->find('1', [], Usage::TYPE_GAUGE); + $results = $this->usage->find('1', [], Adapter::TYPE_GAUGE); $this->assertGreaterThanOrEqual(3, count($results)); } @@ -140,12 +140,12 @@ public function testLargeBatchWithSmallBatchSize(): void ]; } - $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT, 10)); + $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT, 10)); // Verify metrics were processed $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['large-batch-metric']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); } @@ -154,7 +154,7 @@ public function testLargeBatchWithSmallBatchSize(): void */ public function testGaugeMetricsLastValueWins(): void { - $this->usage->purge('1', [], Usage::TYPE_GAUGE); + $this->usage->purge('1', [], Adapter::TYPE_GAUGE); $metrics = [ ['tenant' => '1', 'metric' => 'gauge-test', 'value' => 5, 'tags' => []], @@ -162,10 +162,10 @@ public function testGaugeMetricsLastValueWins(): void ['tenant' => '1', 'metric' => 'gauge-test', 'value' => 15, 'tags' => []], ]; - $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_GAUGE)); + $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_GAUGE)); // Gauge total returns argMax (latest value) - $total = $this->usage->getTotal('1', 'gauge-test', [], Usage::TYPE_GAUGE); + $total = $this->usage->getTotal('1', 'gauge-test', [], Adapter::TYPE_GAUGE); $this->assertGreaterThanOrEqual(5, $total); } @@ -174,7 +174,7 @@ public function testGaugeMetricsLastValueWins(): void */ public function testEventMetricsAggregate(): void { - $this->usage->purge('1', [], Usage::TYPE_EVENT); + $this->usage->purge('1', [], Adapter::TYPE_EVENT); $metrics = [ ['tenant' => '1', 'metric' => 'agg-test', 'value' => 5, 'tags' => []], @@ -182,10 +182,10 @@ public function testEventMetricsAggregate(): void ['tenant' => '1', 'metric' => 'agg-test', 'value' => 15, 'tags' => []], ]; - $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT)); + $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT)); // Event metrics should sum: 5 + 10 + 15 = 30 - $total = $this->usage->getTotal('1', 'agg-test', [], Usage::TYPE_EVENT); + $total = $this->usage->getTotal('1', 'agg-test', [], Adapter::TYPE_EVENT); $this->assertEquals(30, $total); } @@ -194,7 +194,7 @@ public function testEventMetricsAggregate(): void */ public function testEmptyBatchClickHouse(): void { - $this->assertTrue($this->usage->addBatch([], Usage::TYPE_EVENT)); + $this->assertTrue($this->usage->addBatch([], Adapter::TYPE_EVENT)); } /** @@ -208,11 +208,11 @@ public function testBatchWithTagsClickHouse(): void ['tenant' => '1', 'metric' => 'tagged', 'value' => 15, 'tags' => ['region' => 'eu-west', 'path' => '/v1/test', 'method' => 'GET', 'status' => '200']], ]; - $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT)); + $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['tagged']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); } @@ -223,7 +223,7 @@ public function testBatchWithTagsClickHouse(): void */ public function testEventColumnsExtractedFromTags(): void { - $this->usage->purge('1', [], Usage::TYPE_EVENT); + $this->usage->purge('1', [], Adapter::TYPE_EVENT); $metrics = [ [ @@ -259,11 +259,11 @@ public function testEventColumnsExtractedFromTags(): void ], ]; - $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT)); + $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['event-cols-test']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(1, $results); $metric = $results[0]; @@ -300,7 +300,7 @@ public function testEventColumnsExtractedFromTags(): void */ public function testGaugeColumnsRoundTrip(): void { - $this->usage->purge('1', [], Usage::TYPE_GAUGE); + $this->usage->purge('1', [], Adapter::TYPE_GAUGE); $this->assertTrue($this->usage->addBatch([ [ @@ -316,11 +316,11 @@ public function testGaugeColumnsRoundTrip(): void 'resourceInternalId' => '42', ], ], - ], Usage::TYPE_GAUGE)); + ], Adapter::TYPE_GAUGE)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['gauge-cols-test']), - ], Usage::TYPE_GAUGE); + ], Adapter::TYPE_GAUGE); $this->assertCount(1, $results); $metric = $results[0]; @@ -338,19 +338,19 @@ public function testUnknownTagKeyThrows(): void $this->expectExceptionMessageMatches("/Unknown column 'bogus'/"); $this->usage->addBatch([ ['tenant' => '1', 'metric' => 'x', 'value' => 1, 'tags' => ['bogus' => 'v']], - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); } public function testCountryLowercased(): void { - $this->usage->purge('1', [], Usage::TYPE_EVENT); + $this->usage->purge('1', [], Adapter::TYPE_EVENT); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'lc-country', 'value' => 1, 'tags' => ['country' => 'US']], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['lc-country']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(1, $results); $this->assertSame('us', $results[0]->getCountry()); @@ -358,14 +358,14 @@ public function testCountryLowercased(): void public function testRegionLowercased(): void { - $this->usage->purge('1', [], Usage::TYPE_EVENT); + $this->usage->purge('1', [], Adapter::TYPE_EVENT); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'lc-region', 'value' => 1, 'tags' => ['region' => 'FR']], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['lc-region']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(1, $results); $this->assertSame('fr', $results[0]->getRegion()); @@ -373,14 +373,14 @@ public function testRegionLowercased(): void public function testEmptyStringCoercedToNull(): void { - $this->usage->purge('1', [], Usage::TYPE_EVENT); + $this->usage->purge('1', [], Adapter::TYPE_EVENT); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'empty-string', 'value' => 1, 'tags' => ['osName' => '']], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['empty-string']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(1, $results); $this->assertNull($results[0]->getOsName()); @@ -392,7 +392,7 @@ public function testEmptyStringCoercedToNull(): void */ public function testEventsSchemaPersistsAllNewColumns(): void { - $this->usage->purge('1', [], Usage::TYPE_EVENT); + $this->usage->purge('1', [], Adapter::TYPE_EVENT); $tags = [ 'path' => '/v1/x', 'method' => 'GET', 'status' => '200', @@ -410,7 +410,7 @@ public function testEventsSchemaPersistsAllNewColumns(): void $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'schema-roundtrip', 'value' => 1, 'tags' => $tags], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); // Filtering on each indexed dimension should be schema-valid. foreach (['service', 'resourceInternalId', 'teamId', 'teamInternalId', 'region', 'hostname', 'osName', 'clientName', 'deviceName'] as $col) { @@ -419,7 +419,7 @@ public function testEventsSchemaPersistsAllNewColumns(): void $rows = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['schema-roundtrip']), \Utopia\Query\Query::equal($col, [$expected]), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($rows), "Filter on {$col} returned no rows"); } } @@ -429,44 +429,44 @@ public function testEventsSchemaPersistsAllNewColumns(): void */ public function testQueryEventsByColumns(): void { - $this->usage->purge('1', [], Usage::TYPE_EVENT); + $this->usage->purge('1', [], Adapter::TYPE_EVENT); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'req', 'value' => 10, 'tags' => ['path' => '/v1/storage', 'method' => 'GET', 'status' => '200', 'resource' => 'project', 'resourceId' => 'p1']], ['tenant' => '1', 'metric' => 'req', 'value' => 20, 'tags' => ['path' => '/v1/databases', 'method' => 'POST', 'status' => '201', 'resource' => 'database', 'resourceId' => 'db1']], ['tenant' => '1', 'metric' => 'req', 'value' => 30, 'tags' => ['path' => '/v1/storage', 'method' => 'GET', 'status' => '404', 'resource' => 'project', 'resourceId' => 'p1']], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); // Filter by path $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('path', ['/v1/storage']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(2, $results); // Filter by method $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('method', ['POST']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(1, $results); $this->assertEquals(20, $results[0]->getValue()); // Filter by status $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('status', ['404']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(1, $results); $this->assertEquals(30, $results[0]->getValue()); // Filter by resource $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('resource', ['database']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(1, $results); // Filter by resourceId $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('resourceId', ['db1']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(1, $results); } @@ -475,15 +475,15 @@ public function testQueryEventsByColumns(): void */ public function testGaugeTableSimpleSchema(): void { - $this->usage->purge('1', [], Usage::TYPE_GAUGE); + $this->usage->purge('1', [], Adapter::TYPE_GAUGE); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'gauge-simple', 'value' => 500, 'tags' => ['resourceId' => 'r1']], - ], Usage::TYPE_GAUGE)); + ], Adapter::TYPE_GAUGE)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['gauge-simple']), - ], Usage::TYPE_GAUGE); + ], Adapter::TYPE_GAUGE); $this->assertCount(1, $results); $this->assertEquals(500, $results[0]->getValue()); @@ -506,11 +506,11 @@ public function testFindBothTables(): void $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'both-test-event', 'value' => 10, 'tags' => []], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'both-test-gauge', 'value' => 100, 'tags' => []], - ], Usage::TYPE_GAUGE)); + ], Adapter::TYPE_GAUGE)); // Find from both tables $results = $this->usage->find('1', [], null); @@ -536,11 +536,11 @@ public function testBatchSizeAtMaximum(): void ]; } - $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT, 1000)); + $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT, 1000)); $sum = $this->usage->sum('1', [ \Utopia\Query\Query::equal('metric', ['boundary-test']), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $this->assertEquals(500, $sum); } @@ -555,10 +555,10 @@ public function testBatchSizeOfOne(): void ['tenant' => '1', 'metric' => 'size-one-3', 'value' => 30, 'tags' => []], ]; - $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT, 1)); + $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT, 1)); // All metrics should be inserted - $results = $this->usage->find('1', [], Usage::TYPE_EVENT); + $results = $this->usage->find('1', [], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(3, count($results)); } @@ -578,11 +578,11 @@ public function testDefaultBatchSize(): void } // Use default batch size - $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT)); + $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT)); $sum = $this->usage->sum('1', [ \Utopia\Query\Query::equal('metric', ['default-batch-test']), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $this->assertEquals(50, $sum); } @@ -594,11 +594,11 @@ public function testMetricsWithSpecialCharacters(): void $specialVal = "Text with \n newline, \t tab, \"quote\", and unicode \u{1F600}"; $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'special-metric', 'value' => 1, 'tags' => ['hostname' => $specialVal]], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['special-metric']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertEquals(1, count($results)); $this->assertEquals('special-metric', $results[0]->getMetric()); @@ -616,21 +616,21 @@ public function testFindComprehensive(): void // Setup test data $this->usage->addBatch([ ['tenant' => '1', 'metric' => 'metric-A', 'value' => 10, 'tags' => ['service' => 'cat1']], - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->usage->addBatch([ ['tenant' => '1', 'metric' => 'metric-B', 'value' => 20, 'tags' => ['service' => 'cat2']], - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); // 1. Array Equal (IN) $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['metric-A', 'metric-B']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(2, count($results)); // 2. Scalar Equal $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('value', [20]), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); $this->assertEquals(20, $results[0]->getValue()); @@ -638,7 +638,7 @@ public function testFindComprehensive(): void $results = $this->usage->find('1', [ \Utopia\Query\Query::lessThan('value', 20), \Utopia\Query\Query::equal('metric', ['metric-A']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); $this->assertEquals(10, $results[0]->getValue()); @@ -646,7 +646,7 @@ public function testFindComprehensive(): void $results = $this->usage->find('1', [ \Utopia\Query\Query::greaterThan('value', 10), \Utopia\Query\Query::equal('metric', ['metric-B']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); $this->assertEquals(20, $results[0]->getValue()); @@ -654,13 +654,13 @@ public function testFindComprehensive(): void $results = $this->usage->find('1', [ \Utopia\Query\Query::between('value', 5, 25), \Utopia\Query\Query::equal('metric', ['metric-A', 'metric-B']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(2, count($results)); // 6. Contains (IN alias) $results = $this->usage->find('1', [ \Utopia\Query\Query::contains('metric', ['metric-A']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); // 7. Order Desc @@ -668,7 +668,7 @@ public function testFindComprehensive(): void \Utopia\Query\Query::equal('metric', ['metric-A', 'metric-B']), \Utopia\Query\Query::orderDesc('value'), \Utopia\Query\Query::limit(2), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(2, count($results)); $this->assertTrue($results[0]->getValue() >= $results[1]->getValue()); @@ -677,7 +677,7 @@ public function testFindComprehensive(): void \Utopia\Query\Query::equal('metric', ['metric-A', 'metric-B']), \Utopia\Query\Query::orderAsc('value'), \Utopia\Query\Query::limit(2), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(2, count($results)); $this->assertTrue($results[0]->getValue() <= $results[1]->getValue()); } @@ -687,7 +687,7 @@ public function testFindComprehensive(): void */ public function testHealthCheck(): void { - $adapter = $this->usage->getAdapter(); + $adapter = $this->usage; $health = $adapter->healthCheck(); @@ -763,7 +763,7 @@ public function testConnectionPooling(): void database: getenv('CLICKHOUSE_DATABASE') ?: 'default', ); - $usage = new Usage($adapter); + $usage = $adapter; $usage->setup(); // Connection reuse is always on (the transport client holds the cURL @@ -777,9 +777,9 @@ public function testConnectionPooling(): void // Make some requests $usage->addBatch([ ['tenant' => '1', 'metric' => 'pooling.test', 'value' => 100, 'tags' => ['service' => 'value']], - ], Usage::TYPE_EVENT); - $usage->find('1', [], Usage::TYPE_EVENT); - $usage->count('1', [], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); + $usage->find('1', [], Adapter::TYPE_EVENT); + $usage->count('1', [], Adapter::TYPE_EVENT); // Verify request count increased $newStats = $adapter->getConnectionStats(); @@ -808,11 +808,11 @@ public function testErrorMessagesIncludeContext(): void database: 'nonexistent_db_for_testing_errors_12345', ); - $usage = new Usage($adapter); + $usage = $adapter; try { // This should fail because database doesn't exist - $usage->find('1', [], Usage::TYPE_EVENT); + $usage->find('1', [], Adapter::TYPE_EVENT); $this->fail('Expected exception was not thrown'); } catch (\Exception $e) { $errorMessage = $e->getMessage(); @@ -859,15 +859,15 @@ public function testAsyncInsertConfiguration(): void $this->assertTrue($stats['async_insert_wait']); // Verify it works with async inserts enabled - $usage = new Usage($adapter); + $usage = $adapter; $usage->setup(); $usage->purge('1'); $this->assertTrue($usage->addBatch([ ['tenant' => '1', 'metric' => 'async-test', 'value' => 42, 'tags' => []], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); - $total = $usage->getTotal('1', 'async-test', [], Usage::TYPE_EVENT); + $total = $usage->getTotal('1', 'async-test', [], Adapter::TYPE_EVENT); $this->assertEquals(42, $total); // Fire-and-forget mode: async on, no wait for confirmation. @@ -892,13 +892,13 @@ public function testCursorAfterPaginatesEvents(): void ['tenant' => '1', 'metric' => 'cursor-events', 'value' => 3, 'tags' => []], ['tenant' => '1', 'metric' => 'cursor-events', 'value' => 4, 'tags' => []], ['tenant' => '1', 'metric' => 'cursor-events', 'value' => 5, 'tags' => []], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $page1 = $this->usage->find('1', [ Query::equal('metric', ['cursor-events']), Query::orderAsc('id'), Query::limit(2), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(2, $page1); @@ -909,7 +909,7 @@ public function testCursorAfterPaginatesEvents(): void Query::orderAsc('id'), Query::limit(2), Query::cursorAfter($cursor), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(2, $page2); $this->assertNotEquals($page1[0]->getId(), $page2[0]->getId()); @@ -925,13 +925,13 @@ public function testCursorBeforeReversesPagination(): void ['tenant' => '1', 'metric' => 'cursor-before', 'value' => 2, 'tags' => []], ['tenant' => '1', 'metric' => 'cursor-before', 'value' => 3, 'tags' => []], ['tenant' => '1', 'metric' => 'cursor-before', 'value' => 4, 'tags' => []], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $all = $this->usage->find('1', [ Query::equal('metric', ['cursor-before']), Query::orderAsc('id'), Query::limit(10), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(4, $all); @@ -940,7 +940,7 @@ public function testCursorBeforeReversesPagination(): void Query::orderAsc('id'), Query::limit(2), Query::cursorBefore($all[3]), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(2, $before); $this->assertEquals($all[1]->getId(), $before[0]->getId()); @@ -955,20 +955,20 @@ public function testCursorAcceptsAssociativeArray(): void ['tenant' => '1', 'metric' => 'cursor-array', 'value' => 1, 'tags' => []], ['tenant' => '1', 'metric' => 'cursor-array', 'value' => 2, 'tags' => []], ['tenant' => '1', 'metric' => 'cursor-array', 'value' => 3, 'tags' => []], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $all = $this->usage->find('1', [ Query::equal('metric', ['cursor-array']), Query::orderAsc('id'), Query::limit(10), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $page = $this->usage->find('1', [ Query::equal('metric', ['cursor-array']), Query::orderAsc('id'), Query::limit(10), Query::cursorAfter(['id' => $all[0]->getId()]), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(2, $page); $this->assertEquals($all[1]->getId(), $page[0]->getId()); @@ -995,7 +995,7 @@ public function testCursorWithGroupByIntervalThrows(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::cursorAfter(['id' => 'whatever']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); } public function testGroupByServiceDailyAggregates(): void @@ -1006,7 +1006,7 @@ public function testGroupByServiceDailyAggregates(): void ['tenant' => '1', 'metric' => 'gb-service', 'value' => 10, 'tags' => ['service' => 'storage']], ['tenant' => '1', 'metric' => 'gb-service', 'value' => 25, 'tags' => ['service' => 'storage']], ['tenant' => '1', 'metric' => 'gb-service', 'value' => 5, 'tags' => ['service' => 'databases']], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $start = (new \DateTime())->modify('-1 day')->format('Y-m-d\TH:i:s'); $end = (new \DateTime())->modify('+1 day')->format('Y-m-d\TH:i:s'); @@ -1017,7 +1017,7 @@ public function testGroupByServiceDailyAggregates(): void Query::equal('metric', ['gb-service']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(2, count($results)); @@ -1041,7 +1041,7 @@ public function testGroupByMultipleDimensionsHourly(): void ['tenant' => '1', 'metric' => 'gb-multi', 'value' => 2, 'tags' => ['service' => 'storage', 'path' => '/v1/a']], ['tenant' => '1', 'metric' => 'gb-multi', 'value' => 4, 'tags' => ['service' => 'storage', 'path' => '/v1/b']], ['tenant' => '1', 'metric' => 'gb-multi', 'value' => 8, 'tags' => ['service' => 'databases', 'path' => '/v1/a']], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $start = (new \DateTime())->modify('-1 hour')->format('Y-m-d\TH:i:s'); $end = (new \DateTime())->modify('+1 hour')->format('Y-m-d\TH:i:s'); @@ -1053,7 +1053,7 @@ public function testGroupByMultipleDimensionsHourly(): void Query::equal('metric', ['gb-multi']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(3, count($results)); @@ -1076,7 +1076,7 @@ public function testNotEqualQuery(): void // Fixture: requests x2, bandwidth x1 in events $results = $this->usage->find('1', [ Query::notEqual('metric', 'requests'), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); // bandwidth row only $this->assertGreaterThanOrEqual(1, count($results)); foreach ($results as $row) { @@ -1088,7 +1088,7 @@ public function testNotContainsQuery(): void { $results = $this->usage->find('1', [ Query::notContains('metric', ['requests', 'bandwidth']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(0, $results); } @@ -1099,7 +1099,7 @@ public function testNotBetweenQuery(): void $results = $this->usage->find('1', [ Query::notBetween('time', $oldPast, $past), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(3, count($results)); } @@ -1109,13 +1109,13 @@ public function testIsNullAndIsNotNullQueries(): void // so depending on how addBatch persists tags, country may be null or empty. $isNotNull = $this->usage->find('1', [ Query::isNotNull('metric'), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(3, count($isNotNull)); // metric is required so isNull returns nothing $isNull = $this->usage->find('1', [ Query::isNull('metric'), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(0, $isNull); } @@ -1124,12 +1124,12 @@ public function testStartsWithAndEndsWithQueries(): void // Fixture: paths /v1/storage, /v1/databases, /v1/storage/files $startsWith = $this->usage->find('1', [ Query::startsWith('path', '/v1/'), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(3, count($startsWith)); $endsWith = $this->usage->find('1', [ Query::endsWith('path', '/files'), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($endsWith)); foreach ($endsWith as $row) { $path = $row->getAttribute('path', ''); @@ -1145,7 +1145,7 @@ public function testContainsRejectsEmptyValues(): void $this->usage->find('1', [ Query::contains('metric', []), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); } public function testNotContainsRejectsEmptyValues(): void @@ -1155,7 +1155,7 @@ public function testNotContainsRejectsEmptyValues(): void $this->usage->find('1', [ Query::notContains('metric', []), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); } public function testEqualRejectsEmptyValues(): void @@ -1165,6 +1165,6 @@ public function testEqualRejectsEmptyValues(): void $this->usage->find('1', [ new Query(Query::TYPE_EQUAL, 'metric', []), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); } } diff --git a/tests/Usage/Adapter/DatabaseTest.php b/tests/Usage/Adapter/DatabaseTest.php index 5c0ee1e..48afe74 100644 --- a/tests/Usage/Adapter/DatabaseTest.php +++ b/tests/Usage/Adapter/DatabaseTest.php @@ -11,7 +11,7 @@ use Utopia\Database\Exception\Duplicate; use Utopia\Tests\Usage\UsageBase; use Utopia\Usage\Adapter\Database as AdapterDatabase; -use Utopia\Usage\Usage; +use Utopia\Usage\Adapter; class DatabaseTest extends TestCase { @@ -36,7 +36,7 @@ protected function initializeUsage(): void $this->database->setDatabase('utopiaTests'); $this->database->setNamespace('utopia_usage'); - $this->usage = new Usage(new AdapterDatabase($this->database)); + $this->usage = new AdapterDatabase($this->database); // Create database if missing try { @@ -64,7 +64,7 @@ public function testEventColumnsExtractedFromTags(): void $this->markTestSkipped('pdo_mysql extension is not installed'); } - $this->usage->purge('1', [], Usage::TYPE_EVENT); + $this->usage->purge('1', [], Adapter::TYPE_EVENT); $this->assertTrue($this->usage->addBatch([ [ @@ -89,11 +89,11 @@ public function testEventColumnsExtractedFromTags(): void 'deviceName' => 'smartphone', ], ], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['event-cols-db']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(1, $results); $metric = $results[0]; @@ -119,7 +119,7 @@ public function testGaugeColumnsRoundTrip(): void $this->markTestSkipped('pdo_mysql extension is not installed'); } - $this->usage->purge('1', [], Usage::TYPE_GAUGE); + $this->usage->purge('1', [], Adapter::TYPE_GAUGE); $this->assertTrue($this->usage->addBatch([ [ @@ -133,11 +133,11 @@ public function testGaugeColumnsRoundTrip(): void 'resourceInternalId' => '42', ], ], - ], Usage::TYPE_GAUGE)); + ], Adapter::TYPE_GAUGE)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['gauge-cols-db']), - ], Usage::TYPE_GAUGE); + ], Adapter::TYPE_GAUGE); $this->assertCount(1, $results); $metric = $results[0]; @@ -157,7 +157,7 @@ public function testUnknownTagKeyThrows(): void $this->expectExceptionMessageMatches("/Unknown column 'bogus'/"); $this->usage->addBatch([ ['tenant' => '1', 'metric' => 'x', 'value' => 1, 'tags' => ['bogus' => 'v']], - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); } public function testCountryAndRegionLowercased(): void @@ -166,14 +166,14 @@ public function testCountryAndRegionLowercased(): void $this->markTestSkipped('pdo_mysql extension is not installed'); } - $this->usage->purge('1', [], Usage::TYPE_EVENT); + $this->usage->purge('1', [], Adapter::TYPE_EVENT); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'lc-db', 'value' => 1, 'tags' => ['country' => 'US', 'region' => 'FR']], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['lc-db']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(1, $results); $this->assertSame('us', $results[0]->getCountry()); @@ -186,14 +186,14 @@ public function testEmptyStringCoercedToNull(): void $this->markTestSkipped('pdo_mysql extension is not installed'); } - $this->usage->purge('1', [], Usage::TYPE_EVENT); + $this->usage->purge('1', [], Adapter::TYPE_EVENT); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'empty-db', 'value' => 1, 'tags' => ['osName' => '']], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['empty-db']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertCount(1, $results); $this->assertNull($results[0]->getOsName()); @@ -204,7 +204,7 @@ public function testEmptyStringCoercedToNull(): void */ public function testHealthCheck(): void { - $adapter = $this->usage->getAdapter(); + $adapter = $this->usage; $health = $adapter->healthCheck(); diff --git a/tests/Usage/TenantTest.php b/tests/Usage/TenantTest.php index 4f813cd..69b144c 100644 --- a/tests/Usage/TenantTest.php +++ b/tests/Usage/TenantTest.php @@ -5,7 +5,6 @@ use PHPUnit\Framework\TestCase; use Utopia\Usage\Adapter; use Utopia\Usage\Tenant; -use Utopia\Usage\Usage; /** * Records the tenant passed to each method so the Tenant decorator can be @@ -89,7 +88,7 @@ public function count(string $tenant, array $queries = [], ?string $type = null, return 0; } - public function sum(string $tenant, array $queries = [], string $attribute = 'value', string $type = Usage::TYPE_EVENT): int + public function sum(string $tenant, array $queries = [], string $attribute = 'value', string $type = Adapter::TYPE_EVENT): int { $this->lastTenant = $tenant; return 0; @@ -129,14 +128,14 @@ class TenantTest extends TestCase protected function setUp(): void { $this->adapter = new TenantRecordingAdapter(); - $this->tenant = new Tenant(new Usage($this->adapter), 'p1'); + $this->tenant = new Tenant($this->adapter, 'p1'); } public function testEmptyTenantThrows(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Tenant cannot be empty'); - new Tenant(new Usage($this->adapter), ''); + new Tenant($this->adapter, ''); } public function testAddBatchStampsBoundTenantOntoEveryMetric(): void @@ -144,7 +143,7 @@ public function testAddBatchStampsBoundTenantOntoEveryMetric(): void $this->tenant->addBatch([ ['metric' => 'requests', 'value' => 10, 'tags' => []], ['metric' => 'bandwidth', 'value' => 20, 'tags' => []], - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertEquals('p1', $this->adapter->lastMetrics[0]['tenant']); $this->assertEquals('p1', $this->adapter->lastMetrics[1]['tenant']); @@ -152,31 +151,31 @@ public function testAddBatchStampsBoundTenantOntoEveryMetric(): void public function testFindForwardsBoundTenant(): void { - $this->tenant->find([], Usage::TYPE_EVENT); + $this->tenant->find([], Adapter::TYPE_EVENT); $this->assertEquals('p1', $this->adapter->lastTenant); } public function testCountForwardsBoundTenant(): void { - $this->tenant->count([], Usage::TYPE_EVENT); + $this->tenant->count([], Adapter::TYPE_EVENT); $this->assertEquals('p1', $this->adapter->lastTenant); } public function testSumForwardsBoundTenant(): void { - $this->tenant->sum([], 'value', Usage::TYPE_EVENT); + $this->tenant->sum([], 'value', Adapter::TYPE_EVENT); $this->assertEquals('p1', $this->adapter->lastTenant); } public function testGetTotalForwardsBoundTenant(): void { - $this->tenant->getTotal('requests', [], Usage::TYPE_EVENT); + $this->tenant->getTotal('requests', [], Adapter::TYPE_EVENT); $this->assertEquals('p1', $this->adapter->lastTenant); } public function testGetTotalBatchForwardsBoundTenant(): void { - $this->tenant->getTotalBatch(['requests'], [], Usage::TYPE_EVENT); + $this->tenant->getTotalBatch(['requests'], [], Adapter::TYPE_EVENT); $this->assertEquals('p1', $this->adapter->lastTenant); } @@ -188,7 +187,7 @@ public function testGetTimeSeriesForwardsBoundTenant(): void public function testPurgeForwardsBoundTenant(): void { - $this->tenant->purge([], Usage::TYPE_EVENT); + $this->tenant->purge([], Adapter::TYPE_EVENT); $this->assertEquals('p1', $this->adapter->lastTenant); } diff --git a/tests/Usage/UsageBase.php b/tests/Usage/UsageBase.php index a104b7e..2981a57 100644 --- a/tests/Usage/UsageBase.php +++ b/tests/Usage/UsageBase.php @@ -3,12 +3,12 @@ namespace Utopia\Tests\Usage; use Utopia\Query\Query; -use Utopia\Usage\Usage; +use Utopia\Usage\Adapter; use Utopia\Usage\UsageQuery; trait UsageBase { - protected Usage $usage; + protected Adapter $usage; abstract protected function initializeUsage(): void; @@ -30,12 +30,12 @@ public function createUsageMetrics(): void ['tenant' => '1', 'metric' => 'requests', 'value' => 100, 'tags' => ['region' => 'us-east', 'path' => '/v1/storage', 'method' => 'GET', 'status' => '200', 'resource' => 'project', 'resourceId' => 'p1']], ['tenant' => '1', 'metric' => 'requests', 'value' => 150, 'tags' => ['region' => 'us-west', 'path' => '/v1/databases', 'method' => 'POST', 'status' => '201', 'resource' => 'database', 'resourceId' => 'db1']], ['tenant' => '1', 'metric' => 'bandwidth', 'value' => 5000, 'tags' => ['region' => 'us-east', 'path' => '/v1/storage/files', 'method' => 'POST', 'status' => '201', 'resource' => 'bucket', 'resourceId' => 'b1']], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); // Gauges: point-in-time snapshots $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'storage', 'value' => 10000, 'tags' => ['resourceId' => 'p1']], - ], Usage::TYPE_GAUGE)); + ], Adapter::TYPE_GAUGE)); } public function testAddBatchEvent(): void @@ -46,11 +46,11 @@ public function testAddBatchEvent(): void $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'add-metric', 'value' => 10, 'tags' => []], ['tenant' => '1', 'metric' => 'add-metric', 'value' => 5, 'tags' => []], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $sum = $this->usage->sum('1', [ Query::equal('metric', ['add-metric']), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $this->assertEquals(15, $sum); } @@ -62,10 +62,10 @@ public function testAddBatchGauge(): void $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'gauge-metric', 'value' => 100, 'tags' => []], ['tenant' => '1', 'metric' => 'gauge-metric', 'value' => 200, 'tags' => []], - ], Usage::TYPE_GAUGE)); + ], Adapter::TYPE_GAUGE)); // getTotal for gauge returns latest value (argMax) - $total = $this->usage->getTotal('1', 'gauge-metric', [], Usage::TYPE_GAUGE); + $total = $this->usage->getTotal('1', 'gauge-metric', [], Adapter::TYPE_GAUGE); $this->assertGreaterThanOrEqual(100, $total); } @@ -79,11 +79,11 @@ public function testAddBatchWithBatchSize(): void ['tenant' => '1', 'metric' => 'batch-bandwidth', 'value' => 3000, 'tags' => ['region' => 'eu-west']], ]; - $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT, 2)); + $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT, 2)); $results = $this->usage->find('1', [ Query::equal('metric', ['batch-requests']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); } @@ -91,7 +91,7 @@ public function testFind(): void { $results = $this->usage->find('1', [ Query::equal('metric', ['requests']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); } @@ -103,7 +103,7 @@ public function testFindWithTimeRange(): void $results = $this->usage->find('1', [ Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(0, count($results)); } @@ -111,7 +111,7 @@ public function testCount(): void { $count = $this->usage->count('1', [ Query::equal('metric', ['requests']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, $count); } @@ -119,23 +119,23 @@ public function testSum(): void { $sum = $this->usage->sum('1', [ Query::equal('metric', ['requests']), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $this->assertEquals(250, $sum); // 100 + 150 } public function testGetTotal(): void { - $total = $this->usage->getTotal('1', 'requests', [], Usage::TYPE_EVENT); + $total = $this->usage->getTotal('1', 'requests', [], Adapter::TYPE_EVENT); $this->assertEquals(250, $total); // event: SUM - $total = $this->usage->getTotal('1', 'storage', [], Usage::TYPE_GAUGE); + $total = $this->usage->getTotal('1', 'storage', [], Adapter::TYPE_GAUGE); $this->assertEquals(10000, $total); // gauge: argMax (latest) } public function testGetTotalBatch(): void { // Event metrics batch - $totals = $this->usage->getTotalBatch('1', ['requests', 'bandwidth'], [], Usage::TYPE_EVENT); + $totals = $this->usage->getTotalBatch('1', ['requests', 'bandwidth'], [], Adapter::TYPE_EVENT); $this->assertArrayHasKey('requests', $totals); $this->assertArrayHasKey('bandwidth', $totals); @@ -144,13 +144,13 @@ public function testGetTotalBatch(): void $this->assertEquals(5000, $totals['bandwidth']); // Gauge metrics batch - $gaugeTotals = $this->usage->getTotalBatch('1', ['storage'], [], Usage::TYPE_GAUGE); + $gaugeTotals = $this->usage->getTotalBatch('1', ['storage'], [], Adapter::TYPE_GAUGE); $this->assertEquals(10000, $gaugeTotals['storage']); } public function testGetTotalBatchWithMissingMetric(): void { - $totals = $this->usage->getTotalBatch('1', ['requests', 'nonexistent-metric'], [], Usage::TYPE_EVENT); + $totals = $this->usage->getTotalBatch('1', ['requests', 'nonexistent-metric'], [], Adapter::TYPE_EVENT); $this->assertEquals(250, $totals['requests']); $this->assertEquals(0, $totals['nonexistent-metric']); @@ -175,7 +175,7 @@ public function testGetTimeSeries(): void $end, [], true, - Usage::TYPE_EVENT, + Adapter::TYPE_EVENT, ); $this->assertArrayHasKey('requests', $results); @@ -197,7 +197,7 @@ public function testGetTimeSeriesMultipleMetrics(): void $end, [], true, - Usage::TYPE_EVENT, + Adapter::TYPE_EVENT, ); $this->assertArrayHasKey('requests', $results); @@ -209,7 +209,7 @@ public function testEqualWithArrayValues(): void // Test equal query with array of values (IN clause) $results = $this->usage->find('1', [ Query::equal('metric', ['requests', 'bandwidth']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); // Should find all metrics matching either 'requests' or 'bandwidth' $this->assertGreaterThanOrEqual(2, count($results)); @@ -220,7 +220,7 @@ public function testContainsQuery(): void // Test contains query with multiple values from events $results = $this->usage->find('1', [ Query::contains('metric', ['requests', 'bandwidth']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); // Should find all metrics matching either 'requests' or 'bandwidth' $this->assertGreaterThanOrEqual(2, count($results)); @@ -231,7 +231,7 @@ public function testLessThanEqualQuery(): void $now = (new \DateTime())->format('Y-m-d\TH:i:s'); $results = $this->usage->find('1', [ Query::lessThanEqual('time', $now), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(0, count($results)); } @@ -241,7 +241,7 @@ public function testGreaterThanEqualQuery(): void $past = (new \DateTime())->modify('-24 hours')->format('Y-m-d\TH:i:s'); $results = $this->usage->find('1', [ Query::greaterThanEqual('time', $past), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(0, count($results)); } @@ -252,16 +252,16 @@ public function testPurge(): void $this->usage->addBatch([ ['tenant' => '1', 'metric' => 'purge-test', 'value' => 999, 'tags' => []], - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); sleep(2); - $status = $this->usage->purge('1', [], Usage::TYPE_EVENT); + $status = $this->usage->purge('1', [], Adapter::TYPE_EVENT); $this->assertTrue($status); $results = $this->usage->find('1', [ Query::equal('metric', ['purge-test']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertEquals(0, count($results)); } @@ -272,24 +272,24 @@ public function testPurgeWithQueries(): void $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'purge-keep', 'value' => 10, 'tags' => []], ['tenant' => '1', 'metric' => 'purge-remove', 'value' => 20, 'tags' => []], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); // Purge only the 'purge-remove' metric $status = $this->usage->purge('1', [ Query::equal('metric', ['purge-remove']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertTrue($status); // 'purge-remove' should be gone $sum = $this->usage->sum('1', [ Query::equal('metric', ['purge-remove']), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $this->assertEquals(0, $sum); // 'purge-keep' should still exist $sum = $this->usage->sum('1', [ Query::equal('metric', ['purge-keep']), - ], 'value', Usage::TYPE_EVENT); + ], 'value', Adapter::TYPE_EVENT); $this->assertEquals(10, $sum); } @@ -298,7 +298,7 @@ public function testWithQueries(): void $results = $this->usage->find('1', [ Query::equal('metric', ['requests']), Query::limit(1), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertEquals(1, count($results)); @@ -306,14 +306,14 @@ public function testWithQueries(): void Query::equal('metric', ['requests']), Query::limit(1), Query::offset(1), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertLessThanOrEqual(1, count($results2)); } public function testEmptyBatch(): void { - $this->assertTrue($this->usage->addBatch([], Usage::TYPE_EVENT)); + $this->assertTrue($this->usage->addBatch([], Adapter::TYPE_EVENT)); } public function testAddBatchWithTags(): void @@ -324,11 +324,11 @@ public function testAddBatchWithTags(): void ['tenant' => '1', 'metric' => 'tagged', 'value' => 15, 'tags' => ['region' => 'eu-west']], ]; - $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT)); + $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT)); $results = $this->usage->find('1', [ Query::equal('metric', ['tagged']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); } @@ -343,7 +343,7 @@ public function testGroupByIntervalHourly(): void ['tenant' => '1', 'metric' => 'gbi-requests', 'value' => 100, 'tags' => []], ['tenant' => '1', 'metric' => 'gbi-requests', 'value' => 50, 'tags' => []], ['tenant' => '1', 'metric' => 'gbi-bandwidth', 'value' => 3000, 'tags' => []], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $start = (clone $now)->modify('-1 hour')->format('Y-m-d\TH:i:s'); $end = (clone $now)->modify('+1 hour')->format('Y-m-d\TH:i:s'); @@ -353,7 +353,7 @@ public function testGroupByIntervalHourly(): void Query::equal('metric', ['gbi-requests']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); @@ -376,7 +376,7 @@ public function testGroupByIntervalDaily(): void $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'gbi-daily', 'value' => 200, 'tags' => []], ['tenant' => '1', 'metric' => 'gbi-daily', 'value' => 300, 'tags' => []], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $start = (new \DateTime())->modify('-1 day')->format('Y-m-d\TH:i:s'); $end = (new \DateTime())->modify('+1 day')->format('Y-m-d\TH:i:s'); @@ -386,7 +386,7 @@ public function testGroupByIntervalDaily(): void Query::equal('metric', ['gbi-daily']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); @@ -407,7 +407,7 @@ public function testGroupByIntervalGauge(): void ['tenant' => '1', 'metric' => 'gbi-storage', 'value' => 1000, 'tags' => []], ['tenant' => '1', 'metric' => 'gbi-storage', 'value' => 2000, 'tags' => []], ['tenant' => '1', 'metric' => 'gbi-storage', 'value' => 3000, 'tags' => []], - ], Usage::TYPE_GAUGE)); + ], Adapter::TYPE_GAUGE)); $start = (new \DateTime())->modify('-1 hour')->format('Y-m-d\TH:i:s'); $end = (new \DateTime())->modify('+1 hour')->format('Y-m-d\TH:i:s'); @@ -417,7 +417,7 @@ public function testGroupByIntervalGauge(): void Query::equal('metric', ['gbi-storage']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Usage::TYPE_GAUGE); + ], Adapter::TYPE_GAUGE); $this->assertGreaterThanOrEqual(1, count($results)); @@ -441,7 +441,7 @@ public function testGroupByIntervalWithLimitOffset(): void $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'gbi-limit', 'value' => 10, 'tags' => []], ['tenant' => '1', 'metric' => 'gbi-limit', 'value' => 20, 'tags' => []], - ], Usage::TYPE_EVENT)); + ], Adapter::TYPE_EVENT)); $start = (new \DateTime())->modify('-1 hour')->format('Y-m-d\TH:i:s'); $end = (new \DateTime())->modify('+1 hour')->format('Y-m-d\TH:i:s'); @@ -452,7 +452,7 @@ public function testGroupByIntervalWithLimitOffset(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(10), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); } @@ -466,7 +466,7 @@ public function testGroupByUnknownAttributeThrows(): void UsageQuery::groupByInterval('time', '1h'), UsageQuery::groupBy('not_a_column'), Query::equal('metric', ['gbi-requests']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); } public function testGroupByWithoutGroupByIntervalReturnsDimOnlyAggregate(): void @@ -477,7 +477,7 @@ public function testGroupByWithoutGroupByIntervalReturnsDimOnlyAggregate(): void $rows = $this->usage->find('1', [ UsageQuery::groupBy('service'), Query::equal('metric', ['gbi-requests']), - ], Usage::TYPE_EVENT); + ], Adapter::TYPE_EVENT); foreach ($rows as $row) { $this->assertArrayNotHasKey('time', $row->getArrayCopy()); From 1153fbd503185fb3e791df0757ad1a2045f3e11f Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 23 Jun 2026 22:00:58 +0100 Subject: [PATCH 2/2] refactor(usage): rename the Adapter base class to Usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the pass-through facade gone, the abstract contract every backend implements is the public entry point — so `Usage` is the right name for it: `new ClickHouse(...)` is a `Usage`. Concrete backends stay under the `Adapter\` namespace (ClickHouse, Database, SQL), which now describes where the implementations live rather than the type callers hold. - src/Usage/Adapter.php -> src/Usage/Usage.php (abstract class Usage) - SQL, Accumulator, Tenant and all tests reference Usage / Usage::TYPE_* - Accumulator/Tenant hold a $usage (was $adapter) Pure rename, no behaviour change. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 32 +-- src/Usage/Accumulator.php | 20 +- src/Usage/Adapter/SQL.php | 4 +- src/Usage/Tenant.php | 36 ++-- src/Usage/{Adapter.php => Usage.php} | 2 +- tests/Benchmark/BenchmarkBase.php | 4 +- tests/Benchmark/EventsBench.php | 24 +-- tests/Benchmark/GaugesBench.php | 10 +- tests/Usage/AccumulatorTest.php | 56 ++--- .../Adapter/ClickHouseDimRoutingTest.php | 20 +- .../Adapter/ClickHouseGaugeDimRoutingTest.php | 16 +- tests/Usage/Adapter/ClickHouseRoutingTest.php | 38 ++-- tests/Usage/Adapter/ClickHouseTest.php | 196 +++++++++--------- tests/Usage/Adapter/DatabaseTest.php | 28 +-- tests/Usage/TenantTest.php | 20 +- tests/Usage/UsageBase.php | 94 ++++----- 16 files changed, 300 insertions(+), 300 deletions(-) rename src/Usage/{Adapter.php => Usage.php} (99%) diff --git a/README.md b/README.md index 08eadb0..c235105 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ composer require utopia-php/usage ```php getTotal('project_123', 'bandwidth'); // read for one tenant $usage->addBatch([ ['tenant' => 'project_123', 'metric' => 'bandwidth', 'value' => 5000, 'tags' => []], ['tenant' => 'project_456', 'metric' => 'bandwidth', 'value' => 2000, 'tags' => []], -], Adapter::TYPE_EVENT); +], Usage::TYPE_EVENT); ``` Callers that only ever touch one tenant can bind it once with the `Tenant` @@ -89,7 +89,7 @@ $tenant = new Tenant($usage, 'project_123'); $tenant->getTotal('bandwidth'); // no tenant argument $tenant->addBatch([ ['metric' => 'bandwidth', 'value' => 5000, 'tags' => []], // tenant stamped automatically -], Adapter::TYPE_EVENT); +], Usage::TYPE_EVENT); ``` ## Metric Types @@ -112,7 +112,7 @@ use Utopia\Usage\Accumulator; $accumulator = new Accumulator($usage); // Collect events — tenant first; values accumulate in-memory (summed per tenant+metric) -$accumulator->collect('project_123', 'bandwidth', 5000, Adapter::TYPE_EVENT, [ +$accumulator->collect('project_123', 'bandwidth', 5000, Usage::TYPE_EVENT, [ 'path' => '/v1/storage/files', 'method' => 'POST', 'status' => '201', @@ -147,8 +147,8 @@ Gauge-specific columns (see `Metric::GAUGE_COLUMNS`): `teamId`, ```php // Collect gauges — last value wins per tenant+metric in buffer -$accumulator->collect('project_123', 'users', 1500, Adapter::TYPE_GAUGE); -$accumulator->collect('project_123', 'storage.size', 1048576, Adapter::TYPE_GAUGE, [ +$accumulator->collect('project_123', 'users', 1500, Usage::TYPE_GAUGE); +$accumulator->collect('project_123', 'storage.size', 1048576, Usage::TYPE_GAUGE, [ 'teamId' => 'team_x', 'teamInternalId' => '7', 'resourceId' => 'abc123', @@ -176,11 +176,11 @@ if ($accumulator->count() >= 5000 || $accumulator->elapsedSeconds() >= 10) { $usage->addBatch([ ['tenant' => 'project_123', 'metric' => 'requests', 'value' => 100, 'tags' => ['path' => '/v1/users', 'method' => 'GET']], ['tenant' => 'project_123', 'metric' => 'bandwidth', 'value' => 50000, 'tags' => ['country' => 'DE', 'region' => 'fra']], -], Adapter::TYPE_EVENT); +], Usage::TYPE_EVENT); $usage->addBatch([ ['tenant' => 'project_123', 'metric' => 'users', 'value' => 42, 'tags' => ['teamId' => 'team_x']], -], Adapter::TYPE_GAUGE); +], Usage::TYPE_GAUGE); ``` ## Querying Metrics @@ -197,12 +197,12 @@ $metrics = $usage->find('project_123', [ Query::greaterThanEqual('time', '2026-01-01'), Query::orderDesc('time'), Query::limit(100), -], Adapter::TYPE_EVENT); +], Usage::TYPE_EVENT); // Find gauges $gauges = $usage->find('project_123', [ Query::equal('metric', ['users', 'storage.size']), -], Adapter::TYPE_GAUGE); +], Usage::TYPE_GAUGE); // Query both tables (type = null) $all = $usage->find('project_123', [ @@ -216,14 +216,14 @@ $all = $usage->find('project_123', [ // Get total for a single metric (SUM for events, latest for gauges) $total = $usage->getTotal('project_123', 'bandwidth', [ Query::greaterThanEqual('time', '2026-03-01'), -], Adapter::TYPE_EVENT); +], Usage::TYPE_EVENT); // Batch totals — single query with GROUP BY $totals = $usage->getTotalBatch( 'project_123', ['bandwidth', 'executions', 'requests'], [Query::greaterThanEqual('time', '2026-03-01')], - Adapter::TYPE_EVENT + Usage::TYPE_EVENT ); ``` @@ -239,7 +239,7 @@ $series = $usage->getTimeSeries( startDate: '2026-03-01', endDate: '2026-04-01', zeroFill: true, // Fill gaps with zeros - type: Adapter::TYPE_EVENT + type: Usage::TYPE_EVENT ); // Returns: ['bandwidth' => ['total' => 5000000, 'data' => [['value' => 100, 'date' => '...'], ...]]] @@ -278,10 +278,10 @@ $rows = $usage->findDaily('project_123', [ // Purge all event metrics older than 90 days $usage->purge('project_123', [ Query::lessThan('time', '2026-01-01'), -], Adapter::TYPE_EVENT); +], Usage::TYPE_EVENT); // Purge all gauge metrics -$usage->purge('project_123', [], Adapter::TYPE_GAUGE); +$usage->purge('project_123', [], Usage::TYPE_GAUGE); ``` ## Architecture @@ -353,7 +353,7 @@ $usage->purge('project_123', [], Adapter::TYPE_GAUGE); ### Creating Custom Adapters -Extend `Utopia\Usage\Adapter` and implement: +Extend `Utopia\Usage\Usage` and implement: - `getName()`, `setup()`, `healthCheck()` - `addBatch(array $metrics, string $type, int $batchSize): bool` (each metric carries its own `tenant`) diff --git a/src/Usage/Accumulator.php b/src/Usage/Accumulator.php index 3d4a339..f200bc5 100644 --- a/src/Usage/Accumulator.php +++ b/src/Usage/Accumulator.php @@ -5,7 +5,7 @@ /** * In-memory metric accumulator. * - * Buffers collect() calls and flushes them to an Adapter in batches. + * Buffers collect() calls and flushes them to a Usage instance in batches. * Events are summed per metric+tags; gauges use last-write-wins. * * The accumulator exposes raw signals — count() and elapsedSeconds() — so @@ -13,7 +13,7 @@ */ class Accumulator { - private Adapter $adapter; + private Usage $usage; /** * In-memory buffer for metrics. @@ -29,11 +29,11 @@ class Accumulator private float $flushedAt; /** - * @param Adapter $adapter The adapter to flush buffered metrics to + * @param Usage $usage The Usage instance to flush buffered metrics to */ - public function __construct(Adapter $adapter) + public function __construct(Usage $usage) { - $this->adapter = $adapter; + $this->usage = $usage; $this->flushedAt = microtime(true); } @@ -64,7 +64,7 @@ public function collect(string $tenant, string $metric, int $value, string $type if ($value < 0) { throw new \InvalidArgumentException('Value cannot be negative'); } - Adapter::assertType($type); + Usage::assertType($type); // Hash the full identity so distinct (tenant, metric, type, tags) // tuples never collide on the key — a raw `:`-join would let @@ -74,7 +74,7 @@ public function collect(string $tenant, string $metric, int $value, string $type ksort($canonicalTags); $key = md5(json_encode([$tenant, $metric, $type, $canonicalTags], JSON_THROW_ON_ERROR)); - if ($type === Adapter::TYPE_EVENT && isset($this->buffer[$key])) { + if ($type === Usage::TYPE_EVENT && isset($this->buffer[$key])) { // Additive: sum values for the same tenant + metric + tags combination $this->buffer[$key]['value'] += $value; } else { @@ -121,7 +121,7 @@ public function flush(): bool $gauges = []; foreach ($this->buffer as $key => $entry) { - if ($entry['type'] === Adapter::TYPE_EVENT) { + if ($entry['type'] === Usage::TYPE_EVENT) { $events[] = $entry; $eventKeys[] = $key; } else { @@ -134,7 +134,7 @@ public function flush(): bool // Flush events — clear buffer entries only on success. if (!empty($events)) { - if ($this->adapter->addBatch($events, Adapter::TYPE_EVENT)) { + if ($this->usage->addBatch($events, Usage::TYPE_EVENT)) { foreach ($eventKeys as $key) { unset($this->buffer[$key]); } @@ -145,7 +145,7 @@ public function flush(): bool // Flush gauges — clear buffer entries only on success. if (!empty($gauges)) { - if ($this->adapter->addBatch($gauges, Adapter::TYPE_GAUGE)) { + if ($this->usage->addBatch($gauges, Usage::TYPE_GAUGE)) { foreach ($gaugeKeys as $key) { unset($this->buffer[$key]); } diff --git a/src/Usage/Adapter/SQL.php b/src/Usage/Adapter/SQL.php index f7f2d6b..ec77674 100644 --- a/src/Usage/Adapter/SQL.php +++ b/src/Usage/Adapter/SQL.php @@ -2,7 +2,7 @@ namespace Utopia\Usage\Adapter; -use Utopia\Usage\Adapter; +use Utopia\Usage\Usage; use Utopia\Usage\Metric; use Utopia\Database\Document; @@ -12,7 +12,7 @@ * This is an abstract base class for SQL-based adapters (Database, ClickHouse, etc.) * It provides common functionality and references schema definitions from the Metric class. */ -abstract class SQL extends Adapter +abstract class SQL extends Usage { public const COLLECTION = 'usage'; diff --git a/src/Usage/Tenant.php b/src/Usage/Tenant.php index a5ad784..fe5a6f6 100644 --- a/src/Usage/Tenant.php +++ b/src/Usage/Tenant.php @@ -3,23 +3,23 @@ namespace Utopia\Usage; /** - * Tenant-scoped view over an Adapter. + * Tenant-scoped view over a Usage instance. * * Binds a tenant once at construction and forwards every query/mutation to the - * underlying Adapter with that tenant pre-filled — so callers that only ever + * underlying Usage with that tenant pre-filled — so callers that only ever * touch one tenant don't repeat it on every call. */ class Tenant { - private Adapter $adapter; + private Usage $usage; private string $tenant; /** - * @param Adapter $adapter The underlying (tenant-agnostic) adapter + * @param Usage $usage The underlying (tenant-agnostic) Usage instance * @param string $tenant Tenant this view is scoped to (non-empty) */ - public function __construct(Adapter $adapter, string $tenant) + public function __construct(Usage $usage, string $tenant) { // Reject '' at construction: an empty scope would silently read/write // the empty tenant in shared-tables mode. ("0" is a valid id.) @@ -27,7 +27,7 @@ public function __construct(Adapter $adapter, string $tenant) throw new \InvalidArgumentException('Tenant cannot be empty'); } - $this->adapter = $adapter; + $this->usage = $usage; $this->tenant = $tenant; } @@ -47,7 +47,7 @@ public function addBatch(array $metrics, string $type, int $batchSize = 1000): b } unset($metric); - return $this->adapter->addBatch($metrics, $type, $batchSize); + return $this->usage->addBatch($metrics, $type, $batchSize); } /** @@ -58,7 +58,7 @@ public function addBatch(array $metrics, string $type, int $batchSize = 1000): b */ public function getTimeSeries(array $metrics, string $interval, string $startDate, string $endDate, array $queries = [], bool $zeroFill = true, ?string $type = null): array { - return $this->adapter->getTimeSeries($this->tenant, $metrics, $interval, $startDate, $endDate, $queries, $zeroFill, $type); + return $this->usage->getTimeSeries($this->tenant, $metrics, $interval, $startDate, $endDate, $queries, $zeroFill, $type); } /** @@ -67,7 +67,7 @@ public function getTimeSeries(array $metrics, string $interval, string $startDat */ public function getTotal(string $metric, array $queries = [], ?string $type = null): int { - return $this->adapter->getTotal($this->tenant, $metric, $queries, $type); + return $this->usage->getTotal($this->tenant, $metric, $queries, $type); } /** @@ -78,7 +78,7 @@ public function getTotal(string $metric, array $queries = [], ?string $type = nu */ public function getTotalBatch(array $metrics, array $queries = [], ?string $type = null): array { - return $this->adapter->getTotalBatch($this->tenant, $metrics, $queries, $type); + return $this->usage->getTotalBatch($this->tenant, $metrics, $queries, $type); } /** @@ -87,7 +87,7 @@ public function getTotalBatch(array $metrics, array $queries = [], ?string $type */ public function purge(array $queries = [], ?string $type = null): bool { - return $this->adapter->purge($this->tenant, $queries, $type); + return $this->usage->purge($this->tenant, $queries, $type); } /** @@ -97,7 +97,7 @@ public function purge(array $queries = [], ?string $type = null): bool */ public function find(array $queries = [], ?string $type = null): array { - return $this->adapter->find($this->tenant, $queries, $type); + return $this->usage->find($this->tenant, $queries, $type); } /** @@ -106,16 +106,16 @@ public function find(array $queries = [], ?string $type = null): array */ public function count(array $queries = [], ?string $type = null, ?int $max = null): int { - return $this->adapter->count($this->tenant, $queries, $type, $max); + return $this->usage->count($this->tenant, $queries, $type, $max); } /** * @param array<\Utopia\Query\Query> $queries * @throws \Exception */ - public function sum(array $queries = [], string $attribute = 'value', string $type = Adapter::TYPE_EVENT): int + public function sum(array $queries = [], string $attribute = 'value', string $type = Usage::TYPE_EVENT): int { - return $this->adapter->sum($this->tenant, $queries, $attribute, $type); + return $this->usage->sum($this->tenant, $queries, $attribute, $type); } /** @@ -125,7 +125,7 @@ public function sum(array $queries = [], string $attribute = 'value', string $ty */ public function findDaily(array $queries = []): array { - return $this->adapter->findDaily($this->tenant, $queries); + return $this->usage->findDaily($this->tenant, $queries); } /** @@ -134,7 +134,7 @@ public function findDaily(array $queries = []): array */ public function sumDaily(array $queries = [], string $attribute = 'value'): int { - return $this->adapter->sumDaily($this->tenant, $queries, $attribute); + return $this->usage->sumDaily($this->tenant, $queries, $attribute); } /** @@ -145,6 +145,6 @@ public function sumDaily(array $queries = [], string $attribute = 'value'): int */ public function sumDailyBatch(array $metrics, array $queries = []): array { - return $this->adapter->sumDailyBatch($this->tenant, $metrics, $queries); + return $this->usage->sumDailyBatch($this->tenant, $metrics, $queries); } } diff --git a/src/Usage/Adapter.php b/src/Usage/Usage.php similarity index 99% rename from src/Usage/Adapter.php rename to src/Usage/Usage.php index 092c212..86d7ad0 100644 --- a/src/Usage/Adapter.php +++ b/src/Usage/Usage.php @@ -2,7 +2,7 @@ namespace Utopia\Usage; -abstract class Adapter +abstract class Usage { public const TYPE_EVENT = 'event'; public const TYPE_GAUGE = 'gauge'; diff --git a/tests/Benchmark/BenchmarkBase.php b/tests/Benchmark/BenchmarkBase.php index 83d28c2..d31cbac 100644 --- a/tests/Benchmark/BenchmarkBase.php +++ b/tests/Benchmark/BenchmarkBase.php @@ -2,7 +2,7 @@ namespace Utopia\Tests\Benchmark; -use Utopia\Usage\Adapter; +use Utopia\Usage\Usage; use PHPUnit\Framework\TestCase; use ReflectionClass; use RuntimeException; @@ -22,7 +22,7 @@ */ abstract class BenchmarkBase extends TestCase { - protected Adapter $usage; + protected Usage $usage; protected ClickHouseAdapter $adapter; diff --git a/tests/Benchmark/EventsBench.php b/tests/Benchmark/EventsBench.php index 5528aaf..1e4c1e3 100644 --- a/tests/Benchmark/EventsBench.php +++ b/tests/Benchmark/EventsBench.php @@ -5,7 +5,7 @@ use DateTime; use DateTimeZone; use Utopia\Query\Query; -use Utopia\Usage\Adapter; +use Utopia\Usage\Usage; use Utopia\Usage\UsageQuery; class EventsBench extends BenchmarkBase @@ -29,7 +29,7 @@ public function testBenchmarks(): void Query::equal('metric', [$this->metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); }); $this->runBench('bench_events_timeseries_30d_1h', function (string $queryId) use ($start, $end): void { @@ -40,7 +40,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(5000), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); }); $this->runBench('bench_events_count_max_5k', function (string $queryId) use ($start, $end): void { @@ -49,7 +49,7 @@ public function testBenchmarks(): void Query::equal('metric', [$this->metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Adapter::TYPE_EVENT, 5000); + ], Usage::TYPE_EVENT, 5000); }); $this->runBench('bench_insert_10k', function (string $queryId): void { @@ -68,7 +68,7 @@ public function testBenchmarks(): void ]; } $this->adapter->setNextQueryId($queryId); - $this->usage->addBatch($batch, Adapter::TYPE_EVENT); + $this->usage->addBatch($batch, Usage::TYPE_EVENT); }, 3); $this->runBench('bench_events_topN_path_30d', function (string $queryId) use ($start, $end): void { @@ -79,7 +79,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(500), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); }); $this->runBench('bench_events_topN_country_30d', function (string $queryId) use ($start, $end): void { @@ -90,7 +90,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(200), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); }); $this->runBench('bench_events_topN_service_30d', function (string $queryId) use ($start, $end): void { @@ -101,7 +101,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(200), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); }); $todayStart = (new DateTime('today', new DateTimeZone('UTC')))->format('Y-m-d H:i:s'); @@ -114,7 +114,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $todayStart), Query::lessThanEqual('time', $todayEnd), Query::limit(500), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); }); $this->runBench('bench_events_topN_path_30d_filtered_resource', function (string $queryId) use ($start, $end): void { @@ -126,7 +126,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(500), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); }); $this->runBench('bench_events_topN_path_country', function (string $queryId) use ($start, $end): void { @@ -138,7 +138,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(500), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); }); $this->runBench('bench_insert_with_projections', function (string $queryId): void { @@ -158,7 +158,7 @@ public function testBenchmarks(): void ]; } $this->adapter->setNextQueryId($queryId); - $this->usage->addBatch($batch, Adapter::TYPE_EVENT); + $this->usage->addBatch($batch, Usage::TYPE_EVENT); }, 3); $this->assertNotEmpty($this->results, 'Benchmark scenarios must record results'); diff --git a/tests/Benchmark/GaugesBench.php b/tests/Benchmark/GaugesBench.php index f040a20..f84eee2 100644 --- a/tests/Benchmark/GaugesBench.php +++ b/tests/Benchmark/GaugesBench.php @@ -4,7 +4,7 @@ use DateTime; use Utopia\Query\Query; -use Utopia\Usage\Adapter; +use Utopia\Usage\Usage; use Utopia\Usage\UsageQuery; class GaugesBench extends BenchmarkBase @@ -34,7 +34,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start30d), Query::lessThanEqual('time', $endPartial), ], - Adapter::TYPE_GAUGE + Usage::TYPE_GAUGE ); }); @@ -46,7 +46,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start30d), Query::lessThanEqual('time', $endClosed), Query::limit(10), - ], Adapter::TYPE_GAUGE); + ], Usage::TYPE_GAUGE); }); $this->runBench('bench_gauges_topN_resource_30d', function (string $queryId) use ($start30d, $endClosed): void { @@ -57,7 +57,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start30d), Query::lessThanEqual('time', $endClosed), Query::limit(10), - ], Adapter::TYPE_GAUGE); + ], Usage::TYPE_GAUGE); }); $this->runBench('bench_gauges_topN_service_today_partial', function (string $queryId) use ($start30d, $endPartial): void { @@ -68,7 +68,7 @@ public function testBenchmarks(): void Query::greaterThanEqual('time', $start30d), Query::lessThanEqual('time', $endPartial), Query::limit(10), - ], Adapter::TYPE_GAUGE); + ], Usage::TYPE_GAUGE); }); $this->assertNotEmpty($this->results, 'Benchmark scenarios must record results'); diff --git a/tests/Usage/AccumulatorTest.php b/tests/Usage/AccumulatorTest.php index 1fd6f83..a4951ed 100644 --- a/tests/Usage/AccumulatorTest.php +++ b/tests/Usage/AccumulatorTest.php @@ -4,14 +4,14 @@ use PHPUnit\Framework\TestCase; use Utopia\Usage\Accumulator; -use Utopia\Usage\Adapter; +use Utopia\Usage\Usage; /** * Records addBatch() calls so the Accumulator can be tested without a backend. * addBatch() returns whatever $succeed is set to, letting tests drive the * partial-failure path. */ -class RecordingAdapter extends Adapter +class RecordingAdapter extends Usage { /** @var array>, type: string}> */ public array $batches = []; @@ -85,7 +85,7 @@ public function count(string $tenant, array $queries = [], ?string $type = null, return 0; } - public function sum(string $tenant, array $queries = [], string $attribute = 'value', string $type = Adapter::TYPE_EVENT): int + public function sum(string $tenant, array $queries = [], string $attribute = 'value', string $type = Usage::TYPE_EVENT): int { return 0; } @@ -126,9 +126,9 @@ protected function setUp(): void public function testEventsSumByKey(): void { - $this->accumulator->collect('t1', 'requests', 10, Adapter::TYPE_EVENT); - $this->accumulator->collect('t1', 'requests', 20, Adapter::TYPE_EVENT); - $this->accumulator->collect('t1', 'requests', 30, Adapter::TYPE_EVENT); + $this->accumulator->collect('t1', 'requests', 10, Usage::TYPE_EVENT); + $this->accumulator->collect('t1', 'requests', 20, Usage::TYPE_EVENT); + $this->accumulator->collect('t1', 'requests', 30, Usage::TYPE_EVENT); // Same metric + tags = 1 entry, values summed $this->assertEquals(1, $this->accumulator->count()); @@ -136,14 +136,14 @@ public function testEventsSumByKey(): void $this->assertTrue($this->accumulator->flush()); $this->assertCount(1, $this->adapter->batches); - $this->assertEquals(Adapter::TYPE_EVENT, $this->adapter->batches[0]['type']); + $this->assertEquals(Usage::TYPE_EVENT, $this->adapter->batches[0]['type']); $this->assertEquals(60, $this->adapter->batches[0]['metrics'][0]['value']); } public function testTagsPartitionEntries(): void { - $this->accumulator->collect('t1', 'requests', 10, Adapter::TYPE_EVENT, ['region' => 'us']); - $this->accumulator->collect('t1', 'requests', 20, Adapter::TYPE_EVENT, ['region' => 'eu']); + $this->accumulator->collect('t1', 'requests', 10, Usage::TYPE_EVENT, ['region' => 'us']); + $this->accumulator->collect('t1', 'requests', 20, Usage::TYPE_EVENT, ['region' => 'eu']); // Distinct tags = distinct entries $this->assertEquals(2, $this->accumulator->count()); @@ -152,8 +152,8 @@ public function testTagsPartitionEntries(): void public function testTenantPartitionsEntries(): void { // Same metric + tags but different tenants must not collapse - $this->accumulator->collect('t1', 'requests', 10, Adapter::TYPE_EVENT); - $this->accumulator->collect('t2', 'requests', 20, Adapter::TYPE_EVENT); + $this->accumulator->collect('t1', 'requests', 10, Usage::TYPE_EVENT); + $this->accumulator->collect('t2', 'requests', 20, Usage::TYPE_EVENT); $this->assertEquals(2, $this->accumulator->count()); @@ -166,30 +166,30 @@ public function testTenantPartitionsEntries(): void public function testGaugesUseLastWriteWins(): void { - $this->accumulator->collect('t1', 'storage', 100, Adapter::TYPE_GAUGE); - $this->accumulator->collect('t1', 'storage', 200, Adapter::TYPE_GAUGE); - $this->accumulator->collect('t1', 'storage', 300, Adapter::TYPE_GAUGE); + $this->accumulator->collect('t1', 'storage', 100, Usage::TYPE_GAUGE); + $this->accumulator->collect('t1', 'storage', 200, Usage::TYPE_GAUGE); + $this->accumulator->collect('t1', 'storage', 300, Usage::TYPE_GAUGE); $this->assertEquals(1, $this->accumulator->count()); $this->assertTrue($this->accumulator->flush()); - $this->assertEquals(Adapter::TYPE_GAUGE, $this->adapter->batches[0]['type']); + $this->assertEquals(Usage::TYPE_GAUGE, $this->adapter->batches[0]['type']); $this->assertEquals(300, $this->adapter->batches[0]['metrics'][0]['value']); } public function testFlushSeparatesEventsAndGauges(): void { - $this->accumulator->collect('t1', 'requests', 10, Adapter::TYPE_EVENT); - $this->accumulator->collect('t1', 'storage', 100, Adapter::TYPE_GAUGE); + $this->accumulator->collect('t1', 'requests', 10, Usage::TYPE_EVENT); + $this->accumulator->collect('t1', 'storage', 100, Usage::TYPE_GAUGE); $this->assertTrue($this->accumulator->flush()); // One batch per type $this->assertCount(2, $this->adapter->batches); $types = [$this->adapter->batches[0]['type'], $this->adapter->batches[1]['type']]; - $this->assertContains(Adapter::TYPE_EVENT, $types); - $this->assertContains(Adapter::TYPE_GAUGE, $types); + $this->assertContains(Usage::TYPE_EVENT, $types); + $this->assertContains(Usage::TYPE_GAUGE, $types); // Buffer cleared on success $this->assertEquals(0, $this->accumulator->count()); @@ -197,7 +197,7 @@ public function testFlushSeparatesEventsAndGauges(): void public function testFailedFlushRetainsBuffer(): void { - $this->accumulator->collect('t1', 'requests', 10, Adapter::TYPE_EVENT); + $this->accumulator->collect('t1', 'requests', 10, Usage::TYPE_EVENT); $this->adapter->succeed = false; $this->assertFalse($this->accumulator->flush()); @@ -232,13 +232,13 @@ public function testEmptyTenantThrows(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Tenant cannot be empty'); - $this->accumulator->collect('', 'requests', 10, Adapter::TYPE_EVENT); + $this->accumulator->collect('', 'requests', 10, Usage::TYPE_EVENT); } public function testTenantZeroIsAccepted(): void { // "0" is a valid tenant id even though empty("0") is true in PHP - $this->accumulator->collect('0', 'requests', 10, Adapter::TYPE_EVENT); + $this->accumulator->collect('0', 'requests', 10, Usage::TYPE_EVENT); $this->assertEquals(1, $this->accumulator->count()); @@ -250,14 +250,14 @@ public function testEmptyMetricNameThrows(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Metric name cannot be empty'); - $this->accumulator->collect('t1', '', 10, Adapter::TYPE_EVENT); + $this->accumulator->collect('t1', '', 10, Usage::TYPE_EVENT); } public function testKeyDistinguishesAmbiguousTenantMetricSplits(): void { // tenant "a" + metric "b:c" must not collide with tenant "a:b" + metric "c" - $this->accumulator->collect('a', 'b:c', 10, Adapter::TYPE_EVENT); - $this->accumulator->collect('a:b', 'c', 20, Adapter::TYPE_EVENT); + $this->accumulator->collect('a', 'b:c', 10, Usage::TYPE_EVENT); + $this->accumulator->collect('a:b', 'c', 20, Usage::TYPE_EVENT); $this->assertEquals(2, $this->accumulator->count()); } @@ -265,8 +265,8 @@ public function testKeyDistinguishesAmbiguousTenantMetricSplits(): void public function testTagOrderDoesNotSplitEntries(): void { // Same logical tags in different insertion order must sum into one entry - $this->accumulator->collect('t1', 'requests', 10, Adapter::TYPE_EVENT, ['teamId' => 't', 'resourceId' => 'r']); - $this->accumulator->collect('t1', 'requests', 20, Adapter::TYPE_EVENT, ['resourceId' => 'r', 'teamId' => 't']); + $this->accumulator->collect('t1', 'requests', 10, Usage::TYPE_EVENT, ['teamId' => 't', 'resourceId' => 'r']); + $this->accumulator->collect('t1', 'requests', 20, Usage::TYPE_EVENT, ['resourceId' => 'r', 'teamId' => 't']); $this->assertEquals(1, $this->accumulator->count()); @@ -278,7 +278,7 @@ public function testNegativeValueThrows(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Value cannot be negative'); - $this->accumulator->collect('t1', 'requests', -1, Adapter::TYPE_EVENT); + $this->accumulator->collect('t1', 'requests', -1, Usage::TYPE_EVENT); } public function testInvalidTypeThrows(): void diff --git a/tests/Usage/Adapter/ClickHouseDimRoutingTest.php b/tests/Usage/Adapter/ClickHouseDimRoutingTest.php index 915b8ad..c916a68 100644 --- a/tests/Usage/Adapter/ClickHouseDimRoutingTest.php +++ b/tests/Usage/Adapter/ClickHouseDimRoutingTest.php @@ -8,7 +8,7 @@ use Utopia\Query\Query; use Utopia\Tests\Usage\Adapter\ClickHouseTestCase; use Utopia\Usage\Adapter\ClickHouse as ClickHouseAdapter; -use Utopia\Usage\Adapter; +use Utopia\Usage\Usage; use Utopia\Usage\UsageQuery; /** @@ -20,7 +20,7 @@ */ class ClickHouseDimRoutingTest extends ClickHouseTestCase { - private Adapter $usage; + private Usage $usage; private ClickHouseAdapter $adapter; @@ -48,7 +48,7 @@ protected function setUp(): void $this->seedHistoricalRow($this->metric, 40, '-3 days', ['path' => '/v1/c', 'method' => 'POST', 'status' => '500', 'service' => 'functions', 'country' => 'fr']); $this->usage->addBatch([ ['tenant' => '1', 'metric' => $this->metric, 'value' => 5, 'tags' => ['path' => '/v1/a', 'method' => 'GET', 'status' => '200', 'service' => 'storage', 'country' => 'us']], - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); } protected function tearDown(): void @@ -118,7 +118,7 @@ public function testTopNGroupedQueryRoutesToMatchingProjection(array $dims, stri $queryId = bin2hex(random_bytes(8)); $this->adapter->setNextQueryId($queryId); - $rolled = $this->usage->find('1', $queries, Adapter::TYPE_EVENT); + $rolled = $this->usage->find('1', $queries, Usage::TYPE_EVENT); $this->assertSame($this->rawTotal($start, $end), $this->totalOf($rolled)); $this->assertProjectionUsed($queryId, $expectedProjection); @@ -137,7 +137,7 @@ public function testMultiDimNotInAnyProjectionFallsBackToTable(): void Query::equal('metric', [$this->metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertNoProjectionUsed($queryId); } @@ -158,7 +158,7 @@ public function testFilterOnExtraColumnStillRoutesToProjectionWhenColumnPresent( Query::equal('resource', ['function']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertNoProjectionUsed($queryId); } @@ -180,7 +180,7 @@ public function testSubDayIntervalStillRoutesToProjection(): void Query::equal('metric', [$this->metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertProjectionUsed($queryId, 'p_by_path'); } @@ -198,7 +198,7 @@ public function testWindowStraddlesTodayStillRoutesToProjection(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(50), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertSame($this->rawTotal($start, $end), $this->totalOf($rolled)); // Projections are derived in the same write transaction as the @@ -210,7 +210,7 @@ public function testWindowStraddlesTodayStillRoutesToProjection(): void /** * @param array $queries */ - private function routeFor(array $queries, string $type = Adapter::TYPE_EVENT): string + private function routeFor(array $queries, string $type = Usage::TYPE_EVENT): string { $reflection = new ReflectionClass($this->adapter); $extract = $reflection->getMethod('extractRoutingPlan'); @@ -231,7 +231,7 @@ private function rawTotal(string $start, string $end): int Query::equal('metric', [$this->metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); return is_int($result) ? $result : 0; } diff --git a/tests/Usage/Adapter/ClickHouseGaugeDimRoutingTest.php b/tests/Usage/Adapter/ClickHouseGaugeDimRoutingTest.php index 6840751..efe0d07 100644 --- a/tests/Usage/Adapter/ClickHouseGaugeDimRoutingTest.php +++ b/tests/Usage/Adapter/ClickHouseGaugeDimRoutingTest.php @@ -7,7 +7,7 @@ use Utopia\Query\Query; use Utopia\Tests\Usage\Adapter\ClickHouseTestCase; use Utopia\Usage\Adapter\ClickHouse as ClickHouseAdapter; -use Utopia\Usage\Adapter; +use Utopia\Usage\Usage; use Utopia\Usage\UsageQuery; /** @@ -18,7 +18,7 @@ */ class ClickHouseGaugeDimRoutingTest extends ClickHouseTestCase { - private Adapter $usage; + private Usage $usage; private ClickHouseAdapter $adapter; @@ -47,7 +47,7 @@ protected function setUp(): void $this->usage->addBatch([ ['tenant' => '1', 'metric' => $this->metric, 'value' => 999, 'tags' => ['service' => 'storage', 'resource' => 'file', 'resourceId' => 'f1']], - ], Adapter::TYPE_GAUGE); + ], Usage::TYPE_GAUGE); } protected function tearDown(): void @@ -118,7 +118,7 @@ public function testTopGaugesGroupedQueryRoutesToMatchingProjection(array $dims, $queryId = bin2hex(random_bytes(8)); $this->adapter->setNextQueryId($queryId); - $this->usage->find('1', $queries, Adapter::TYPE_GAUGE); + $this->usage->find('1', $queries, Usage::TYPE_GAUGE); $this->assertProjectionUsed($queryId, $expectedProjection); } @@ -138,7 +138,7 @@ public function testGaugesSubDayIntervalStillRoutesToProjection(): void Query::equal('metric', [$this->metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Adapter::TYPE_GAUGE); + ], Usage::TYPE_GAUGE); $this->assertProjectionUsed($queryId, 'p_by_service'); } @@ -156,7 +156,7 @@ public function testGaugesFilterOnNonProjectionColumnFallsBackToBaseTable(): voi Query::equal('resourceId', ['x']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Adapter::TYPE_GAUGE); + ], Usage::TYPE_GAUGE); $this->assertNoProjectionUsed($queryId); } @@ -172,7 +172,7 @@ public function testGaugesUngroupedFallsBackToBaseTable(): void Query::equal('metric', [$this->metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Adapter::TYPE_GAUGE); + ], Usage::TYPE_GAUGE); $this->assertNoProjectionUsed($queryId); } @@ -190,7 +190,7 @@ public function testGaugesWindowStraddlesTodayStillRoutesToProjection(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(50), - ], Adapter::TYPE_GAUGE); + ], Usage::TYPE_GAUGE); $this->assertProjectionUsed($queryId, 'p_by_service'); } diff --git a/tests/Usage/Adapter/ClickHouseRoutingTest.php b/tests/Usage/Adapter/ClickHouseRoutingTest.php index e52fd59..8db2e0c 100644 --- a/tests/Usage/Adapter/ClickHouseRoutingTest.php +++ b/tests/Usage/Adapter/ClickHouseRoutingTest.php @@ -9,12 +9,12 @@ use Utopia\Query\Query; use Utopia\Tests\Usage\Adapter\ClickHouseTestCase; use Utopia\Usage\Adapter\ClickHouse as ClickHouseAdapter; -use Utopia\Usage\Adapter; +use Utopia\Usage\Usage; use Utopia\Usage\UsageQuery; class ClickHouseRoutingTest extends ClickHouseTestCase { - private Adapter $usage; + private Usage $usage; private ClickHouseAdapter $adapter; @@ -38,7 +38,7 @@ protected function setUp(): void $this->seedHistoricalRow('routed.metric', 200, '-3 days', ['path' => '/v1/b']); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'routed.metric', 'value' => 50, 'tags' => ['path' => '/v1/c']], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); } protected function tearDown(): void @@ -87,7 +87,7 @@ public function testClosedDayWindowRoutesToDaily(): void Query::equal('metric', ['routed.metric']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -110,7 +110,7 @@ public function testInclusiveMidnightUpperBoundExcludesEndDayOnDailyRoute(): voi Query::equal('metric', ['routed.metric']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -134,7 +134,7 @@ public function testMidDayClosedWindowFallsBackToRaw(): void Query::equal('metric', ['routed.metric']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -154,7 +154,7 @@ public function testWindowStraddlesTodayRoutesHybrid(): void Query::equal('metric', ['routed.metric']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -175,7 +175,7 @@ public function testHybridSumKeepsParamsDistinctWhenMetricFollowsTimeBounds(): v Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::equal('metric', ['routed.metric']), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -195,7 +195,7 @@ public function testDimensionPresentForcesRaw(): void Query::equal('path', ['/v1/a']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -214,7 +214,7 @@ public function testFilterOnNonDailyColumnForcesRaw(): void Query::equal('country', ['us']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -235,7 +235,7 @@ public function testMidDayStartWithHybridWindowFallsBackToRaw(): void Query::equal('metric', [$metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -255,7 +255,7 @@ public function testIntervalPresentForcesRaw(): void Query::equal('metric', ['routed.metric']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -277,7 +277,7 @@ public function testDuplicateTimeFiltersTakeTightestBound(): void Query::greaterThanEqual('time', $startTighter), Query::lessThanEqual('time', $endLoose), Query::lessThanEqual('time', $endTighter), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -295,7 +295,7 @@ public function testOpenEndedWindowForcesRaw(): void $this->usage->sum('1', [ Query::equal('metric', ['routed.metric']), Query::greaterThanEqual('time', $start), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $log = $this->adapter->getRouteLog(); $this->assertCount(1, $log); @@ -311,7 +311,7 @@ public function testMalformedTimeStringForcesRaw(): void Query::equal('metric', ['routed.metric']), Query::greaterThanEqual('time', 'not-a-date'), Query::lessThanEqual('time', 'not-a-date-either'), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); } catch (Exception $e) { } @@ -346,14 +346,14 @@ public function testNarrowPurgeWithNoTimeBoundDoesNotWipeDailyMv(): void // DELETE WHERE 1=1 and wipe both metrics' daily rows. $this->usage->purge('1', [ Query::equal('path', ['/v1/remove']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->adapter->clearRouteLog(); $keepSum = $this->usage->sum('1', [ Query::equal('metric', ['purge.keep']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $this->assertSame(100, $keepSum, 'unrelated daily rows must survive a narrow purge'); } @@ -380,7 +380,7 @@ public function testValueFilterPurgeDoesNotMatchAggregateDailyRows(): void $this->usage->purge('1', [ Query::equal('value', [10]), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); // value is raw-only, so the routed sum stays on raw — it // sees the still-present rows. The point of this test is the @@ -403,7 +403,7 @@ private function sumRaw(string $metric, string $start, string $end): int Query::equal('metric', [$metric]), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $this->adapter->clearRouteLog(); return is_int($result) ? $result : 0; } diff --git a/tests/Usage/Adapter/ClickHouseTest.php b/tests/Usage/Adapter/ClickHouseTest.php index 77bd60a..9a7710c 100644 --- a/tests/Usage/Adapter/ClickHouseTest.php +++ b/tests/Usage/Adapter/ClickHouseTest.php @@ -6,7 +6,7 @@ use Utopia\Query\Query; use Utopia\Tests\Usage\UsageBase; use Utopia\Usage\Adapter\ClickHouse as ClickHouseAdapter; -use Utopia\Usage\Adapter; +use Utopia\Usage\Usage; use Utopia\Usage\UsageQuery; class ClickHouseTest extends TestCase @@ -69,19 +69,19 @@ public function testPerMetricTenantInBatch(): void ], ]; - $this->assertTrue($usage->addBatch($metrics, Adapter::TYPE_EVENT)); + $this->assertTrue($usage->addBatch($metrics, Usage::TYPE_EVENT)); // Reading under tenant '2' returns the row; tenant '1' must not see it. $results = $usage->find('2', [ \Utopia\Query\Query::equal('metric', ['tenant-override']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(1, $results); $this->assertEquals('2', $results[0]->getTenant()); $this->assertCount(0, $usage->find('1', [ \Utopia\Query\Query::equal('metric', ['tenant-override']), - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $usage->purge('2'); } @@ -99,10 +99,10 @@ public function testAddBatchWithBatchSize(): void ]; // Process with batch size of 2 - $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT, 2)); + $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT, 2)); // Verify all metrics were inserted - $results = $this->usage->find('1', [], Adapter::TYPE_EVENT); + $results = $this->usage->find('1', [], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(4, count($results)); } @@ -118,10 +118,10 @@ public function testAddBatchGaugeWithBatchSize(): void ]; // Process with batch size of 2 - $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_GAUGE, 2)); + $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_GAUGE, 2)); // Verify gauge metrics were inserted - $results = $this->usage->find('1', [], Adapter::TYPE_GAUGE); + $results = $this->usage->find('1', [], Usage::TYPE_GAUGE); $this->assertGreaterThanOrEqual(3, count($results)); } @@ -140,12 +140,12 @@ public function testLargeBatchWithSmallBatchSize(): void ]; } - $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT, 10)); + $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT, 10)); // Verify metrics were processed $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['large-batch-metric']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); } @@ -154,7 +154,7 @@ public function testLargeBatchWithSmallBatchSize(): void */ public function testGaugeMetricsLastValueWins(): void { - $this->usage->purge('1', [], Adapter::TYPE_GAUGE); + $this->usage->purge('1', [], Usage::TYPE_GAUGE); $metrics = [ ['tenant' => '1', 'metric' => 'gauge-test', 'value' => 5, 'tags' => []], @@ -162,10 +162,10 @@ public function testGaugeMetricsLastValueWins(): void ['tenant' => '1', 'metric' => 'gauge-test', 'value' => 15, 'tags' => []], ]; - $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_GAUGE)); + $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_GAUGE)); // Gauge total returns argMax (latest value) - $total = $this->usage->getTotal('1', 'gauge-test', [], Adapter::TYPE_GAUGE); + $total = $this->usage->getTotal('1', 'gauge-test', [], Usage::TYPE_GAUGE); $this->assertGreaterThanOrEqual(5, $total); } @@ -174,7 +174,7 @@ public function testGaugeMetricsLastValueWins(): void */ public function testEventMetricsAggregate(): void { - $this->usage->purge('1', [], Adapter::TYPE_EVENT); + $this->usage->purge('1', [], Usage::TYPE_EVENT); $metrics = [ ['tenant' => '1', 'metric' => 'agg-test', 'value' => 5, 'tags' => []], @@ -182,10 +182,10 @@ public function testEventMetricsAggregate(): void ['tenant' => '1', 'metric' => 'agg-test', 'value' => 15, 'tags' => []], ]; - $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT)); + $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT)); // Event metrics should sum: 5 + 10 + 15 = 30 - $total = $this->usage->getTotal('1', 'agg-test', [], Adapter::TYPE_EVENT); + $total = $this->usage->getTotal('1', 'agg-test', [], Usage::TYPE_EVENT); $this->assertEquals(30, $total); } @@ -194,7 +194,7 @@ public function testEventMetricsAggregate(): void */ public function testEmptyBatchClickHouse(): void { - $this->assertTrue($this->usage->addBatch([], Adapter::TYPE_EVENT)); + $this->assertTrue($this->usage->addBatch([], Usage::TYPE_EVENT)); } /** @@ -208,11 +208,11 @@ public function testBatchWithTagsClickHouse(): void ['tenant' => '1', 'metric' => 'tagged', 'value' => 15, 'tags' => ['region' => 'eu-west', 'path' => '/v1/test', 'method' => 'GET', 'status' => '200']], ]; - $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT)); + $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['tagged']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); } @@ -223,7 +223,7 @@ public function testBatchWithTagsClickHouse(): void */ public function testEventColumnsExtractedFromTags(): void { - $this->usage->purge('1', [], Adapter::TYPE_EVENT); + $this->usage->purge('1', [], Usage::TYPE_EVENT); $metrics = [ [ @@ -259,11 +259,11 @@ public function testEventColumnsExtractedFromTags(): void ], ]; - $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT)); + $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['event-cols-test']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(1, $results); $metric = $results[0]; @@ -300,7 +300,7 @@ public function testEventColumnsExtractedFromTags(): void */ public function testGaugeColumnsRoundTrip(): void { - $this->usage->purge('1', [], Adapter::TYPE_GAUGE); + $this->usage->purge('1', [], Usage::TYPE_GAUGE); $this->assertTrue($this->usage->addBatch([ [ @@ -316,11 +316,11 @@ public function testGaugeColumnsRoundTrip(): void 'resourceInternalId' => '42', ], ], - ], Adapter::TYPE_GAUGE)); + ], Usage::TYPE_GAUGE)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['gauge-cols-test']), - ], Adapter::TYPE_GAUGE); + ], Usage::TYPE_GAUGE); $this->assertCount(1, $results); $metric = $results[0]; @@ -338,19 +338,19 @@ public function testUnknownTagKeyThrows(): void $this->expectExceptionMessageMatches("/Unknown column 'bogus'/"); $this->usage->addBatch([ ['tenant' => '1', 'metric' => 'x', 'value' => 1, 'tags' => ['bogus' => 'v']], - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); } public function testCountryLowercased(): void { - $this->usage->purge('1', [], Adapter::TYPE_EVENT); + $this->usage->purge('1', [], Usage::TYPE_EVENT); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'lc-country', 'value' => 1, 'tags' => ['country' => 'US']], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['lc-country']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(1, $results); $this->assertSame('us', $results[0]->getCountry()); @@ -358,14 +358,14 @@ public function testCountryLowercased(): void public function testRegionLowercased(): void { - $this->usage->purge('1', [], Adapter::TYPE_EVENT); + $this->usage->purge('1', [], Usage::TYPE_EVENT); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'lc-region', 'value' => 1, 'tags' => ['region' => 'FR']], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['lc-region']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(1, $results); $this->assertSame('fr', $results[0]->getRegion()); @@ -373,14 +373,14 @@ public function testRegionLowercased(): void public function testEmptyStringCoercedToNull(): void { - $this->usage->purge('1', [], Adapter::TYPE_EVENT); + $this->usage->purge('1', [], Usage::TYPE_EVENT); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'empty-string', 'value' => 1, 'tags' => ['osName' => '']], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['empty-string']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(1, $results); $this->assertNull($results[0]->getOsName()); @@ -392,7 +392,7 @@ public function testEmptyStringCoercedToNull(): void */ public function testEventsSchemaPersistsAllNewColumns(): void { - $this->usage->purge('1', [], Adapter::TYPE_EVENT); + $this->usage->purge('1', [], Usage::TYPE_EVENT); $tags = [ 'path' => '/v1/x', 'method' => 'GET', 'status' => '200', @@ -410,7 +410,7 @@ public function testEventsSchemaPersistsAllNewColumns(): void $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'schema-roundtrip', 'value' => 1, 'tags' => $tags], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); // Filtering on each indexed dimension should be schema-valid. foreach (['service', 'resourceInternalId', 'teamId', 'teamInternalId', 'region', 'hostname', 'osName', 'clientName', 'deviceName'] as $col) { @@ -419,7 +419,7 @@ public function testEventsSchemaPersistsAllNewColumns(): void $rows = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['schema-roundtrip']), \Utopia\Query\Query::equal($col, [$expected]), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($rows), "Filter on {$col} returned no rows"); } } @@ -429,44 +429,44 @@ public function testEventsSchemaPersistsAllNewColumns(): void */ public function testQueryEventsByColumns(): void { - $this->usage->purge('1', [], Adapter::TYPE_EVENT); + $this->usage->purge('1', [], Usage::TYPE_EVENT); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'req', 'value' => 10, 'tags' => ['path' => '/v1/storage', 'method' => 'GET', 'status' => '200', 'resource' => 'project', 'resourceId' => 'p1']], ['tenant' => '1', 'metric' => 'req', 'value' => 20, 'tags' => ['path' => '/v1/databases', 'method' => 'POST', 'status' => '201', 'resource' => 'database', 'resourceId' => 'db1']], ['tenant' => '1', 'metric' => 'req', 'value' => 30, 'tags' => ['path' => '/v1/storage', 'method' => 'GET', 'status' => '404', 'resource' => 'project', 'resourceId' => 'p1']], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); // Filter by path $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('path', ['/v1/storage']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(2, $results); // Filter by method $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('method', ['POST']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(1, $results); $this->assertEquals(20, $results[0]->getValue()); // Filter by status $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('status', ['404']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(1, $results); $this->assertEquals(30, $results[0]->getValue()); // Filter by resource $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('resource', ['database']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(1, $results); // Filter by resourceId $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('resourceId', ['db1']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(1, $results); } @@ -475,15 +475,15 @@ public function testQueryEventsByColumns(): void */ public function testGaugeTableSimpleSchema(): void { - $this->usage->purge('1', [], Adapter::TYPE_GAUGE); + $this->usage->purge('1', [], Usage::TYPE_GAUGE); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'gauge-simple', 'value' => 500, 'tags' => ['resourceId' => 'r1']], - ], Adapter::TYPE_GAUGE)); + ], Usage::TYPE_GAUGE)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['gauge-simple']), - ], Adapter::TYPE_GAUGE); + ], Usage::TYPE_GAUGE); $this->assertCount(1, $results); $this->assertEquals(500, $results[0]->getValue()); @@ -506,11 +506,11 @@ public function testFindBothTables(): void $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'both-test-event', 'value' => 10, 'tags' => []], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'both-test-gauge', 'value' => 100, 'tags' => []], - ], Adapter::TYPE_GAUGE)); + ], Usage::TYPE_GAUGE)); // Find from both tables $results = $this->usage->find('1', [], null); @@ -536,11 +536,11 @@ public function testBatchSizeAtMaximum(): void ]; } - $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT, 1000)); + $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT, 1000)); $sum = $this->usage->sum('1', [ \Utopia\Query\Query::equal('metric', ['boundary-test']), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $this->assertEquals(500, $sum); } @@ -555,10 +555,10 @@ public function testBatchSizeOfOne(): void ['tenant' => '1', 'metric' => 'size-one-3', 'value' => 30, 'tags' => []], ]; - $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT, 1)); + $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT, 1)); // All metrics should be inserted - $results = $this->usage->find('1', [], Adapter::TYPE_EVENT); + $results = $this->usage->find('1', [], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(3, count($results)); } @@ -578,11 +578,11 @@ public function testDefaultBatchSize(): void } // Use default batch size - $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT)); + $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT)); $sum = $this->usage->sum('1', [ \Utopia\Query\Query::equal('metric', ['default-batch-test']), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $this->assertEquals(50, $sum); } @@ -594,11 +594,11 @@ public function testMetricsWithSpecialCharacters(): void $specialVal = "Text with \n newline, \t tab, \"quote\", and unicode \u{1F600}"; $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'special-metric', 'value' => 1, 'tags' => ['hostname' => $specialVal]], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['special-metric']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertEquals(1, count($results)); $this->assertEquals('special-metric', $results[0]->getMetric()); @@ -616,21 +616,21 @@ public function testFindComprehensive(): void // Setup test data $this->usage->addBatch([ ['tenant' => '1', 'metric' => 'metric-A', 'value' => 10, 'tags' => ['service' => 'cat1']], - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->usage->addBatch([ ['tenant' => '1', 'metric' => 'metric-B', 'value' => 20, 'tags' => ['service' => 'cat2']], - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); // 1. Array Equal (IN) $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['metric-A', 'metric-B']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(2, count($results)); // 2. Scalar Equal $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('value', [20]), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); $this->assertEquals(20, $results[0]->getValue()); @@ -638,7 +638,7 @@ public function testFindComprehensive(): void $results = $this->usage->find('1', [ \Utopia\Query\Query::lessThan('value', 20), \Utopia\Query\Query::equal('metric', ['metric-A']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); $this->assertEquals(10, $results[0]->getValue()); @@ -646,7 +646,7 @@ public function testFindComprehensive(): void $results = $this->usage->find('1', [ \Utopia\Query\Query::greaterThan('value', 10), \Utopia\Query\Query::equal('metric', ['metric-B']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); $this->assertEquals(20, $results[0]->getValue()); @@ -654,13 +654,13 @@ public function testFindComprehensive(): void $results = $this->usage->find('1', [ \Utopia\Query\Query::between('value', 5, 25), \Utopia\Query\Query::equal('metric', ['metric-A', 'metric-B']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(2, count($results)); // 6. Contains (IN alias) $results = $this->usage->find('1', [ \Utopia\Query\Query::contains('metric', ['metric-A']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); // 7. Order Desc @@ -668,7 +668,7 @@ public function testFindComprehensive(): void \Utopia\Query\Query::equal('metric', ['metric-A', 'metric-B']), \Utopia\Query\Query::orderDesc('value'), \Utopia\Query\Query::limit(2), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(2, count($results)); $this->assertTrue($results[0]->getValue() >= $results[1]->getValue()); @@ -677,7 +677,7 @@ public function testFindComprehensive(): void \Utopia\Query\Query::equal('metric', ['metric-A', 'metric-B']), \Utopia\Query\Query::orderAsc('value'), \Utopia\Query\Query::limit(2), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(2, count($results)); $this->assertTrue($results[0]->getValue() <= $results[1]->getValue()); } @@ -777,9 +777,9 @@ public function testConnectionPooling(): void // Make some requests $usage->addBatch([ ['tenant' => '1', 'metric' => 'pooling.test', 'value' => 100, 'tags' => ['service' => 'value']], - ], Adapter::TYPE_EVENT); - $usage->find('1', [], Adapter::TYPE_EVENT); - $usage->count('1', [], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); + $usage->find('1', [], Usage::TYPE_EVENT); + $usage->count('1', [], Usage::TYPE_EVENT); // Verify request count increased $newStats = $adapter->getConnectionStats(); @@ -812,7 +812,7 @@ public function testErrorMessagesIncludeContext(): void try { // This should fail because database doesn't exist - $usage->find('1', [], Adapter::TYPE_EVENT); + $usage->find('1', [], Usage::TYPE_EVENT); $this->fail('Expected exception was not thrown'); } catch (\Exception $e) { $errorMessage = $e->getMessage(); @@ -865,9 +865,9 @@ public function testAsyncInsertConfiguration(): void $this->assertTrue($usage->addBatch([ ['tenant' => '1', 'metric' => 'async-test', 'value' => 42, 'tags' => []], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); - $total = $usage->getTotal('1', 'async-test', [], Adapter::TYPE_EVENT); + $total = $usage->getTotal('1', 'async-test', [], Usage::TYPE_EVENT); $this->assertEquals(42, $total); // Fire-and-forget mode: async on, no wait for confirmation. @@ -892,13 +892,13 @@ public function testCursorAfterPaginatesEvents(): void ['tenant' => '1', 'metric' => 'cursor-events', 'value' => 3, 'tags' => []], ['tenant' => '1', 'metric' => 'cursor-events', 'value' => 4, 'tags' => []], ['tenant' => '1', 'metric' => 'cursor-events', 'value' => 5, 'tags' => []], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $page1 = $this->usage->find('1', [ Query::equal('metric', ['cursor-events']), Query::orderAsc('id'), Query::limit(2), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(2, $page1); @@ -909,7 +909,7 @@ public function testCursorAfterPaginatesEvents(): void Query::orderAsc('id'), Query::limit(2), Query::cursorAfter($cursor), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(2, $page2); $this->assertNotEquals($page1[0]->getId(), $page2[0]->getId()); @@ -925,13 +925,13 @@ public function testCursorBeforeReversesPagination(): void ['tenant' => '1', 'metric' => 'cursor-before', 'value' => 2, 'tags' => []], ['tenant' => '1', 'metric' => 'cursor-before', 'value' => 3, 'tags' => []], ['tenant' => '1', 'metric' => 'cursor-before', 'value' => 4, 'tags' => []], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $all = $this->usage->find('1', [ Query::equal('metric', ['cursor-before']), Query::orderAsc('id'), Query::limit(10), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(4, $all); @@ -940,7 +940,7 @@ public function testCursorBeforeReversesPagination(): void Query::orderAsc('id'), Query::limit(2), Query::cursorBefore($all[3]), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(2, $before); $this->assertEquals($all[1]->getId(), $before[0]->getId()); @@ -955,20 +955,20 @@ public function testCursorAcceptsAssociativeArray(): void ['tenant' => '1', 'metric' => 'cursor-array', 'value' => 1, 'tags' => []], ['tenant' => '1', 'metric' => 'cursor-array', 'value' => 2, 'tags' => []], ['tenant' => '1', 'metric' => 'cursor-array', 'value' => 3, 'tags' => []], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $all = $this->usage->find('1', [ Query::equal('metric', ['cursor-array']), Query::orderAsc('id'), Query::limit(10), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $page = $this->usage->find('1', [ Query::equal('metric', ['cursor-array']), Query::orderAsc('id'), Query::limit(10), Query::cursorAfter(['id' => $all[0]->getId()]), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(2, $page); $this->assertEquals($all[1]->getId(), $page[0]->getId()); @@ -995,7 +995,7 @@ public function testCursorWithGroupByIntervalThrows(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::cursorAfter(['id' => 'whatever']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); } public function testGroupByServiceDailyAggregates(): void @@ -1006,7 +1006,7 @@ public function testGroupByServiceDailyAggregates(): void ['tenant' => '1', 'metric' => 'gb-service', 'value' => 10, 'tags' => ['service' => 'storage']], ['tenant' => '1', 'metric' => 'gb-service', 'value' => 25, 'tags' => ['service' => 'storage']], ['tenant' => '1', 'metric' => 'gb-service', 'value' => 5, 'tags' => ['service' => 'databases']], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $start = (new \DateTime())->modify('-1 day')->format('Y-m-d\TH:i:s'); $end = (new \DateTime())->modify('+1 day')->format('Y-m-d\TH:i:s'); @@ -1017,7 +1017,7 @@ public function testGroupByServiceDailyAggregates(): void Query::equal('metric', ['gb-service']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(2, count($results)); @@ -1041,7 +1041,7 @@ public function testGroupByMultipleDimensionsHourly(): void ['tenant' => '1', 'metric' => 'gb-multi', 'value' => 2, 'tags' => ['service' => 'storage', 'path' => '/v1/a']], ['tenant' => '1', 'metric' => 'gb-multi', 'value' => 4, 'tags' => ['service' => 'storage', 'path' => '/v1/b']], ['tenant' => '1', 'metric' => 'gb-multi', 'value' => 8, 'tags' => ['service' => 'databases', 'path' => '/v1/a']], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $start = (new \DateTime())->modify('-1 hour')->format('Y-m-d\TH:i:s'); $end = (new \DateTime())->modify('+1 hour')->format('Y-m-d\TH:i:s'); @@ -1053,7 +1053,7 @@ public function testGroupByMultipleDimensionsHourly(): void Query::equal('metric', ['gb-multi']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(3, count($results)); @@ -1076,7 +1076,7 @@ public function testNotEqualQuery(): void // Fixture: requests x2, bandwidth x1 in events $results = $this->usage->find('1', [ Query::notEqual('metric', 'requests'), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); // bandwidth row only $this->assertGreaterThanOrEqual(1, count($results)); foreach ($results as $row) { @@ -1088,7 +1088,7 @@ public function testNotContainsQuery(): void { $results = $this->usage->find('1', [ Query::notContains('metric', ['requests', 'bandwidth']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(0, $results); } @@ -1099,7 +1099,7 @@ public function testNotBetweenQuery(): void $results = $this->usage->find('1', [ Query::notBetween('time', $oldPast, $past), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(3, count($results)); } @@ -1109,13 +1109,13 @@ public function testIsNullAndIsNotNullQueries(): void // so depending on how addBatch persists tags, country may be null or empty. $isNotNull = $this->usage->find('1', [ Query::isNotNull('metric'), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(3, count($isNotNull)); // metric is required so isNull returns nothing $isNull = $this->usage->find('1', [ Query::isNull('metric'), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(0, $isNull); } @@ -1124,12 +1124,12 @@ public function testStartsWithAndEndsWithQueries(): void // Fixture: paths /v1/storage, /v1/databases, /v1/storage/files $startsWith = $this->usage->find('1', [ Query::startsWith('path', '/v1/'), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(3, count($startsWith)); $endsWith = $this->usage->find('1', [ Query::endsWith('path', '/files'), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($endsWith)); foreach ($endsWith as $row) { $path = $row->getAttribute('path', ''); @@ -1145,7 +1145,7 @@ public function testContainsRejectsEmptyValues(): void $this->usage->find('1', [ Query::contains('metric', []), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); } public function testNotContainsRejectsEmptyValues(): void @@ -1155,7 +1155,7 @@ public function testNotContainsRejectsEmptyValues(): void $this->usage->find('1', [ Query::notContains('metric', []), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); } public function testEqualRejectsEmptyValues(): void @@ -1165,6 +1165,6 @@ public function testEqualRejectsEmptyValues(): void $this->usage->find('1', [ new Query(Query::TYPE_EQUAL, 'metric', []), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); } } diff --git a/tests/Usage/Adapter/DatabaseTest.php b/tests/Usage/Adapter/DatabaseTest.php index 48afe74..a927028 100644 --- a/tests/Usage/Adapter/DatabaseTest.php +++ b/tests/Usage/Adapter/DatabaseTest.php @@ -11,7 +11,7 @@ use Utopia\Database\Exception\Duplicate; use Utopia\Tests\Usage\UsageBase; use Utopia\Usage\Adapter\Database as AdapterDatabase; -use Utopia\Usage\Adapter; +use Utopia\Usage\Usage; class DatabaseTest extends TestCase { @@ -64,7 +64,7 @@ public function testEventColumnsExtractedFromTags(): void $this->markTestSkipped('pdo_mysql extension is not installed'); } - $this->usage->purge('1', [], Adapter::TYPE_EVENT); + $this->usage->purge('1', [], Usage::TYPE_EVENT); $this->assertTrue($this->usage->addBatch([ [ @@ -89,11 +89,11 @@ public function testEventColumnsExtractedFromTags(): void 'deviceName' => 'smartphone', ], ], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['event-cols-db']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(1, $results); $metric = $results[0]; @@ -119,7 +119,7 @@ public function testGaugeColumnsRoundTrip(): void $this->markTestSkipped('pdo_mysql extension is not installed'); } - $this->usage->purge('1', [], Adapter::TYPE_GAUGE); + $this->usage->purge('1', [], Usage::TYPE_GAUGE); $this->assertTrue($this->usage->addBatch([ [ @@ -133,11 +133,11 @@ public function testGaugeColumnsRoundTrip(): void 'resourceInternalId' => '42', ], ], - ], Adapter::TYPE_GAUGE)); + ], Usage::TYPE_GAUGE)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['gauge-cols-db']), - ], Adapter::TYPE_GAUGE); + ], Usage::TYPE_GAUGE); $this->assertCount(1, $results); $metric = $results[0]; @@ -157,7 +157,7 @@ public function testUnknownTagKeyThrows(): void $this->expectExceptionMessageMatches("/Unknown column 'bogus'/"); $this->usage->addBatch([ ['tenant' => '1', 'metric' => 'x', 'value' => 1, 'tags' => ['bogus' => 'v']], - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); } public function testCountryAndRegionLowercased(): void @@ -166,14 +166,14 @@ public function testCountryAndRegionLowercased(): void $this->markTestSkipped('pdo_mysql extension is not installed'); } - $this->usage->purge('1', [], Adapter::TYPE_EVENT); + $this->usage->purge('1', [], Usage::TYPE_EVENT); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'lc-db', 'value' => 1, 'tags' => ['country' => 'US', 'region' => 'FR']], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['lc-db']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(1, $results); $this->assertSame('us', $results[0]->getCountry()); @@ -186,14 +186,14 @@ public function testEmptyStringCoercedToNull(): void $this->markTestSkipped('pdo_mysql extension is not installed'); } - $this->usage->purge('1', [], Adapter::TYPE_EVENT); + $this->usage->purge('1', [], Usage::TYPE_EVENT); $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'empty-db', 'value' => 1, 'tags' => ['osName' => '']], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $results = $this->usage->find('1', [ \Utopia\Query\Query::equal('metric', ['empty-db']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertCount(1, $results); $this->assertNull($results[0]->getOsName()); diff --git a/tests/Usage/TenantTest.php b/tests/Usage/TenantTest.php index 69b144c..3cb26f7 100644 --- a/tests/Usage/TenantTest.php +++ b/tests/Usage/TenantTest.php @@ -3,14 +3,14 @@ namespace Utopia\Tests\Usage; use PHPUnit\Framework\TestCase; -use Utopia\Usage\Adapter; +use Utopia\Usage\Usage; use Utopia\Usage\Tenant; /** * Records the tenant passed to each method so the Tenant decorator can be * tested without a backend. */ -class TenantRecordingAdapter extends Adapter +class TenantRecordingAdapter extends Usage { public ?string $lastTenant = null; @@ -88,7 +88,7 @@ public function count(string $tenant, array $queries = [], ?string $type = null, return 0; } - public function sum(string $tenant, array $queries = [], string $attribute = 'value', string $type = Adapter::TYPE_EVENT): int + public function sum(string $tenant, array $queries = [], string $attribute = 'value', string $type = Usage::TYPE_EVENT): int { $this->lastTenant = $tenant; return 0; @@ -143,7 +143,7 @@ public function testAddBatchStampsBoundTenantOntoEveryMetric(): void $this->tenant->addBatch([ ['metric' => 'requests', 'value' => 10, 'tags' => []], ['metric' => 'bandwidth', 'value' => 20, 'tags' => []], - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertEquals('p1', $this->adapter->lastMetrics[0]['tenant']); $this->assertEquals('p1', $this->adapter->lastMetrics[1]['tenant']); @@ -151,31 +151,31 @@ public function testAddBatchStampsBoundTenantOntoEveryMetric(): void public function testFindForwardsBoundTenant(): void { - $this->tenant->find([], Adapter::TYPE_EVENT); + $this->tenant->find([], Usage::TYPE_EVENT); $this->assertEquals('p1', $this->adapter->lastTenant); } public function testCountForwardsBoundTenant(): void { - $this->tenant->count([], Adapter::TYPE_EVENT); + $this->tenant->count([], Usage::TYPE_EVENT); $this->assertEquals('p1', $this->adapter->lastTenant); } public function testSumForwardsBoundTenant(): void { - $this->tenant->sum([], 'value', Adapter::TYPE_EVENT); + $this->tenant->sum([], 'value', Usage::TYPE_EVENT); $this->assertEquals('p1', $this->adapter->lastTenant); } public function testGetTotalForwardsBoundTenant(): void { - $this->tenant->getTotal('requests', [], Adapter::TYPE_EVENT); + $this->tenant->getTotal('requests', [], Usage::TYPE_EVENT); $this->assertEquals('p1', $this->adapter->lastTenant); } public function testGetTotalBatchForwardsBoundTenant(): void { - $this->tenant->getTotalBatch(['requests'], [], Adapter::TYPE_EVENT); + $this->tenant->getTotalBatch(['requests'], [], Usage::TYPE_EVENT); $this->assertEquals('p1', $this->adapter->lastTenant); } @@ -187,7 +187,7 @@ public function testGetTimeSeriesForwardsBoundTenant(): void public function testPurgeForwardsBoundTenant(): void { - $this->tenant->purge([], Adapter::TYPE_EVENT); + $this->tenant->purge([], Usage::TYPE_EVENT); $this->assertEquals('p1', $this->adapter->lastTenant); } diff --git a/tests/Usage/UsageBase.php b/tests/Usage/UsageBase.php index 2981a57..a104b7e 100644 --- a/tests/Usage/UsageBase.php +++ b/tests/Usage/UsageBase.php @@ -3,12 +3,12 @@ namespace Utopia\Tests\Usage; use Utopia\Query\Query; -use Utopia\Usage\Adapter; +use Utopia\Usage\Usage; use Utopia\Usage\UsageQuery; trait UsageBase { - protected Adapter $usage; + protected Usage $usage; abstract protected function initializeUsage(): void; @@ -30,12 +30,12 @@ public function createUsageMetrics(): void ['tenant' => '1', 'metric' => 'requests', 'value' => 100, 'tags' => ['region' => 'us-east', 'path' => '/v1/storage', 'method' => 'GET', 'status' => '200', 'resource' => 'project', 'resourceId' => 'p1']], ['tenant' => '1', 'metric' => 'requests', 'value' => 150, 'tags' => ['region' => 'us-west', 'path' => '/v1/databases', 'method' => 'POST', 'status' => '201', 'resource' => 'database', 'resourceId' => 'db1']], ['tenant' => '1', 'metric' => 'bandwidth', 'value' => 5000, 'tags' => ['region' => 'us-east', 'path' => '/v1/storage/files', 'method' => 'POST', 'status' => '201', 'resource' => 'bucket', 'resourceId' => 'b1']], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); // Gauges: point-in-time snapshots $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'storage', 'value' => 10000, 'tags' => ['resourceId' => 'p1']], - ], Adapter::TYPE_GAUGE)); + ], Usage::TYPE_GAUGE)); } public function testAddBatchEvent(): void @@ -46,11 +46,11 @@ public function testAddBatchEvent(): void $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'add-metric', 'value' => 10, 'tags' => []], ['tenant' => '1', 'metric' => 'add-metric', 'value' => 5, 'tags' => []], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $sum = $this->usage->sum('1', [ Query::equal('metric', ['add-metric']), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $this->assertEquals(15, $sum); } @@ -62,10 +62,10 @@ public function testAddBatchGauge(): void $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'gauge-metric', 'value' => 100, 'tags' => []], ['tenant' => '1', 'metric' => 'gauge-metric', 'value' => 200, 'tags' => []], - ], Adapter::TYPE_GAUGE)); + ], Usage::TYPE_GAUGE)); // getTotal for gauge returns latest value (argMax) - $total = $this->usage->getTotal('1', 'gauge-metric', [], Adapter::TYPE_GAUGE); + $total = $this->usage->getTotal('1', 'gauge-metric', [], Usage::TYPE_GAUGE); $this->assertGreaterThanOrEqual(100, $total); } @@ -79,11 +79,11 @@ public function testAddBatchWithBatchSize(): void ['tenant' => '1', 'metric' => 'batch-bandwidth', 'value' => 3000, 'tags' => ['region' => 'eu-west']], ]; - $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT, 2)); + $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT, 2)); $results = $this->usage->find('1', [ Query::equal('metric', ['batch-requests']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); } @@ -91,7 +91,7 @@ public function testFind(): void { $results = $this->usage->find('1', [ Query::equal('metric', ['requests']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); } @@ -103,7 +103,7 @@ public function testFindWithTimeRange(): void $results = $this->usage->find('1', [ Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(0, count($results)); } @@ -111,7 +111,7 @@ public function testCount(): void { $count = $this->usage->count('1', [ Query::equal('metric', ['requests']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, $count); } @@ -119,23 +119,23 @@ public function testSum(): void { $sum = $this->usage->sum('1', [ Query::equal('metric', ['requests']), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $this->assertEquals(250, $sum); // 100 + 150 } public function testGetTotal(): void { - $total = $this->usage->getTotal('1', 'requests', [], Adapter::TYPE_EVENT); + $total = $this->usage->getTotal('1', 'requests', [], Usage::TYPE_EVENT); $this->assertEquals(250, $total); // event: SUM - $total = $this->usage->getTotal('1', 'storage', [], Adapter::TYPE_GAUGE); + $total = $this->usage->getTotal('1', 'storage', [], Usage::TYPE_GAUGE); $this->assertEquals(10000, $total); // gauge: argMax (latest) } public function testGetTotalBatch(): void { // Event metrics batch - $totals = $this->usage->getTotalBatch('1', ['requests', 'bandwidth'], [], Adapter::TYPE_EVENT); + $totals = $this->usage->getTotalBatch('1', ['requests', 'bandwidth'], [], Usage::TYPE_EVENT); $this->assertArrayHasKey('requests', $totals); $this->assertArrayHasKey('bandwidth', $totals); @@ -144,13 +144,13 @@ public function testGetTotalBatch(): void $this->assertEquals(5000, $totals['bandwidth']); // Gauge metrics batch - $gaugeTotals = $this->usage->getTotalBatch('1', ['storage'], [], Adapter::TYPE_GAUGE); + $gaugeTotals = $this->usage->getTotalBatch('1', ['storage'], [], Usage::TYPE_GAUGE); $this->assertEquals(10000, $gaugeTotals['storage']); } public function testGetTotalBatchWithMissingMetric(): void { - $totals = $this->usage->getTotalBatch('1', ['requests', 'nonexistent-metric'], [], Adapter::TYPE_EVENT); + $totals = $this->usage->getTotalBatch('1', ['requests', 'nonexistent-metric'], [], Usage::TYPE_EVENT); $this->assertEquals(250, $totals['requests']); $this->assertEquals(0, $totals['nonexistent-metric']); @@ -175,7 +175,7 @@ public function testGetTimeSeries(): void $end, [], true, - Adapter::TYPE_EVENT, + Usage::TYPE_EVENT, ); $this->assertArrayHasKey('requests', $results); @@ -197,7 +197,7 @@ public function testGetTimeSeriesMultipleMetrics(): void $end, [], true, - Adapter::TYPE_EVENT, + Usage::TYPE_EVENT, ); $this->assertArrayHasKey('requests', $results); @@ -209,7 +209,7 @@ public function testEqualWithArrayValues(): void // Test equal query with array of values (IN clause) $results = $this->usage->find('1', [ Query::equal('metric', ['requests', 'bandwidth']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); // Should find all metrics matching either 'requests' or 'bandwidth' $this->assertGreaterThanOrEqual(2, count($results)); @@ -220,7 +220,7 @@ public function testContainsQuery(): void // Test contains query with multiple values from events $results = $this->usage->find('1', [ Query::contains('metric', ['requests', 'bandwidth']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); // Should find all metrics matching either 'requests' or 'bandwidth' $this->assertGreaterThanOrEqual(2, count($results)); @@ -231,7 +231,7 @@ public function testLessThanEqualQuery(): void $now = (new \DateTime())->format('Y-m-d\TH:i:s'); $results = $this->usage->find('1', [ Query::lessThanEqual('time', $now), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(0, count($results)); } @@ -241,7 +241,7 @@ public function testGreaterThanEqualQuery(): void $past = (new \DateTime())->modify('-24 hours')->format('Y-m-d\TH:i:s'); $results = $this->usage->find('1', [ Query::greaterThanEqual('time', $past), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(0, count($results)); } @@ -252,16 +252,16 @@ public function testPurge(): void $this->usage->addBatch([ ['tenant' => '1', 'metric' => 'purge-test', 'value' => 999, 'tags' => []], - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); sleep(2); - $status = $this->usage->purge('1', [], Adapter::TYPE_EVENT); + $status = $this->usage->purge('1', [], Usage::TYPE_EVENT); $this->assertTrue($status); $results = $this->usage->find('1', [ Query::equal('metric', ['purge-test']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertEquals(0, count($results)); } @@ -272,24 +272,24 @@ public function testPurgeWithQueries(): void $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'purge-keep', 'value' => 10, 'tags' => []], ['tenant' => '1', 'metric' => 'purge-remove', 'value' => 20, 'tags' => []], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); // Purge only the 'purge-remove' metric $status = $this->usage->purge('1', [ Query::equal('metric', ['purge-remove']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertTrue($status); // 'purge-remove' should be gone $sum = $this->usage->sum('1', [ Query::equal('metric', ['purge-remove']), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $this->assertEquals(0, $sum); // 'purge-keep' should still exist $sum = $this->usage->sum('1', [ Query::equal('metric', ['purge-keep']), - ], 'value', Adapter::TYPE_EVENT); + ], 'value', Usage::TYPE_EVENT); $this->assertEquals(10, $sum); } @@ -298,7 +298,7 @@ public function testWithQueries(): void $results = $this->usage->find('1', [ Query::equal('metric', ['requests']), Query::limit(1), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertEquals(1, count($results)); @@ -306,14 +306,14 @@ public function testWithQueries(): void Query::equal('metric', ['requests']), Query::limit(1), Query::offset(1), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertLessThanOrEqual(1, count($results2)); } public function testEmptyBatch(): void { - $this->assertTrue($this->usage->addBatch([], Adapter::TYPE_EVENT)); + $this->assertTrue($this->usage->addBatch([], Usage::TYPE_EVENT)); } public function testAddBatchWithTags(): void @@ -324,11 +324,11 @@ public function testAddBatchWithTags(): void ['tenant' => '1', 'metric' => 'tagged', 'value' => 15, 'tags' => ['region' => 'eu-west']], ]; - $this->assertTrue($this->usage->addBatch($metrics, Adapter::TYPE_EVENT)); + $this->assertTrue($this->usage->addBatch($metrics, Usage::TYPE_EVENT)); $results = $this->usage->find('1', [ Query::equal('metric', ['tagged']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); } @@ -343,7 +343,7 @@ public function testGroupByIntervalHourly(): void ['tenant' => '1', 'metric' => 'gbi-requests', 'value' => 100, 'tags' => []], ['tenant' => '1', 'metric' => 'gbi-requests', 'value' => 50, 'tags' => []], ['tenant' => '1', 'metric' => 'gbi-bandwidth', 'value' => 3000, 'tags' => []], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $start = (clone $now)->modify('-1 hour')->format('Y-m-d\TH:i:s'); $end = (clone $now)->modify('+1 hour')->format('Y-m-d\TH:i:s'); @@ -353,7 +353,7 @@ public function testGroupByIntervalHourly(): void Query::equal('metric', ['gbi-requests']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); @@ -376,7 +376,7 @@ public function testGroupByIntervalDaily(): void $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'gbi-daily', 'value' => 200, 'tags' => []], ['tenant' => '1', 'metric' => 'gbi-daily', 'value' => 300, 'tags' => []], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $start = (new \DateTime())->modify('-1 day')->format('Y-m-d\TH:i:s'); $end = (new \DateTime())->modify('+1 day')->format('Y-m-d\TH:i:s'); @@ -386,7 +386,7 @@ public function testGroupByIntervalDaily(): void Query::equal('metric', ['gbi-daily']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); @@ -407,7 +407,7 @@ public function testGroupByIntervalGauge(): void ['tenant' => '1', 'metric' => 'gbi-storage', 'value' => 1000, 'tags' => []], ['tenant' => '1', 'metric' => 'gbi-storage', 'value' => 2000, 'tags' => []], ['tenant' => '1', 'metric' => 'gbi-storage', 'value' => 3000, 'tags' => []], - ], Adapter::TYPE_GAUGE)); + ], Usage::TYPE_GAUGE)); $start = (new \DateTime())->modify('-1 hour')->format('Y-m-d\TH:i:s'); $end = (new \DateTime())->modify('+1 hour')->format('Y-m-d\TH:i:s'); @@ -417,7 +417,7 @@ public function testGroupByIntervalGauge(): void Query::equal('metric', ['gbi-storage']), Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), - ], Adapter::TYPE_GAUGE); + ], Usage::TYPE_GAUGE); $this->assertGreaterThanOrEqual(1, count($results)); @@ -441,7 +441,7 @@ public function testGroupByIntervalWithLimitOffset(): void $this->assertTrue($this->usage->addBatch([ ['tenant' => '1', 'metric' => 'gbi-limit', 'value' => 10, 'tags' => []], ['tenant' => '1', 'metric' => 'gbi-limit', 'value' => 20, 'tags' => []], - ], Adapter::TYPE_EVENT)); + ], Usage::TYPE_EVENT)); $start = (new \DateTime())->modify('-1 hour')->format('Y-m-d\TH:i:s'); $end = (new \DateTime())->modify('+1 hour')->format('Y-m-d\TH:i:s'); @@ -452,7 +452,7 @@ public function testGroupByIntervalWithLimitOffset(): void Query::greaterThanEqual('time', $start), Query::lessThanEqual('time', $end), Query::limit(10), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); $this->assertGreaterThanOrEqual(1, count($results)); } @@ -466,7 +466,7 @@ public function testGroupByUnknownAttributeThrows(): void UsageQuery::groupByInterval('time', '1h'), UsageQuery::groupBy('not_a_column'), Query::equal('metric', ['gbi-requests']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); } public function testGroupByWithoutGroupByIntervalReturnsDimOnlyAggregate(): void @@ -477,7 +477,7 @@ public function testGroupByWithoutGroupByIntervalReturnsDimOnlyAggregate(): void $rows = $this->usage->find('1', [ UsageQuery::groupBy('service'), Query::equal('metric', ['gbi-requests']), - ], Adapter::TYPE_EVENT); + ], Usage::TYPE_EVENT); foreach ($rows as $row) { $this->assertArrayNotHasKey('time', $row->getArrayCopy());