Skip to content

Commit ca50537

Browse files
Merge pull request #19 from utopia-php/feat/queued-time-resource-type-ip-fill
feat: resourceType rename + queued time + ip dim + gauge fill
2 parents ad898f9 + 03a4483 commit ca50537

19 files changed

Lines changed: 585 additions & 132 deletions

CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,40 @@
11
# Changelog
22

3+
## Unreleased — resourceType rename + queued time + ip dim + gauge fill
4+
5+
### Breaking
6+
7+
- Renamed the `resource` dimension to `resourceType` across the
8+
events, gauges, and events-daily tables. All Metric column
9+
constants, schema definitions, indexes, projections
10+
(`p_by_resource*``p_by_resourceType*`), materialized-view
11+
dimension list, and the `Metric::getResource()` getter (now
12+
`Metric::getResourceType()`) follow the new name. Callers must
13+
rename the `resource` tag key on writes and any queries that
14+
filter/group by the old name. A one-time ClickHouse column rename
15+
plus data backfill is required for existing deployments; that
16+
migration lives in the cloud repo, not in this library.
17+
18+
### Added
19+
20+
- `Accumulator::collect()` accepts an optional `\DateTime $time`
21+
argument. When provided, the row is written at that moment; when
22+
omitted the ClickHouse adapter still stamps `now()`. Events with
23+
the same (tenant, metric, tags) fold into the earliest supplied
24+
time; gauges follow last-write-wins on both value and time.
25+
- `Usage::addBatch()` payloads may carry a `time` field per row
26+
(`DateTime|string`) — the ClickHouse adapter threads it through
27+
`formatDateTime()`.
28+
- New event-only `ip` dimension in `Metric::EVENT_COLUMNS` and
29+
`Metric::getEventSchema()`. Stored as
30+
`LowCardinality(Nullable(String))` in ClickHouse, indexed with a
31+
`bloom_filter`, size 45 chars (IPv6 max). Gauges are unchanged.
32+
`Metric::getIp()` is a typed accessor. The base-table primary key
33+
is intentionally not extended with `ip`.
34+
- Gauge time-series reads now carry the last observed value forward
35+
across empty buckets inside the window (LOCF) instead of collapsing
36+
to zero. Events keep zero-fill. Return shape is unchanged.
37+
338
## 0.4.0 — Per-dimension ClickHouse projections and auto-routing
439

540
This release accelerates grouped reads against the events and gauges

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ $accumulator->collect('project_123', 'bandwidth', 5000, Usage::TYPE_EVENT, [
119119
'method' => 'POST',
120120
'status' => '201',
121121
'service' => 'storage',
122-
'resource' => 'bucket',
122+
'resourceType' => 'bucket',
123123
'resourceId' => 'abc123',
124124
'resourceInternalId' => '42',
125125
'teamId' => 'team_x',

src/Usage/Accumulator.php

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,7 @@ class Accumulator
1616
private Usage $usage;
1717

1818
/**
19-
* In-memory buffer for metrics.
20-
* Keyed by "{tenant}:{metric}:{type}:{tagsHash}" — events are summed, gauges
21-
* use last-write-wins. Tenant is part of the key so metrics for different
22-
* tenants never collapse into one entry.
23-
*
24-
* @var array<string, array{tenant: string, metric: string, value: int, type: string, tags: array<string, mixed>}>
19+
* @var array<string, array{tenant: string, metric: string, value: int, type: string, tags: array<string, mixed>, time?: \DateTime}>
2520
*/
2621
private array $buffer = [];
2722

@@ -40,18 +35,12 @@ public function __construct(Usage $usage)
4035
/**
4136
* Collect a metric into the in-memory buffer for deferred flushing.
4237
*
43-
* For event type: multiple collect() calls for the same metric are summed.
44-
* For gauge type: last-write-wins semantics.
45-
* No period fan-out — raw timestamps are used.
38+
* Events fold additively; earliest non-null time wins on merge.
39+
* Gauges use last-write-wins.
4640
*
47-
* @param string $tenant Tenant scope (shared-tables mode)
48-
* @param string $metric Metric name
49-
* @param int $value Value
50-
* @param string $type Metric type: 'event' or 'gauge'
51-
* @param array<string,mixed> $tags Optional tags
52-
* @return self
41+
* @param array<string,mixed> $tags
5342
*/
54-
public function collect(string $tenant, string $metric, int $value, string $type, array $tags = []): self
43+
public function collect(string $tenant, string $metric, int $value, string $type, array $tags = [], ?\DateTime $time = null): self
5544
{
5645
// Compare against '' rather than empty(): the string "0" is a valid
5746
// tenant/metric id but empty("0") is true in PHP.
@@ -77,17 +66,23 @@ public function collect(string $tenant, string $metric, int $value, string $type
7766
$key = md5(json_encode([$tenant, $metric, $type, $canonicalTags], JSON_THROW_ON_ERROR));
7867

7968
if ($type === Usage::TYPE_EVENT && isset($this->buffer[$key])) {
80-
// Additive: sum values for the same tenant + metric + tags combination
69+
// earliest time wins on merge
8170
$this->buffer[$key]['value'] += $value;
71+
if ($time !== null && (!isset($this->buffer[$key]['time']) || $time < $this->buffer[$key]['time'])) {
72+
$this->buffer[$key]['time'] = $time;
73+
}
8274
} else {
83-
// New event entry, or gauge (last-write-wins)
84-
$this->buffer[$key] = [
75+
$entry = [
8576
'tenant' => $tenant,
8677
'metric' => $metric,
8778
'value' => $value,
8879
'type' => $type,
8980
'tags' => $tags,
9081
];
82+
if ($time !== null) {
83+
$entry['time'] = $time;
84+
}
85+
$this->buffer[$key] = $entry;
9186
}
9287

9388
return $this;

src/Usage/Adapter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ abstract public function sum(string $tenant, array $queries = [], string $attrib
141141
* the daily events table — gauges are never pre-aggregated.
142142
*
143143
* @param string $tenant Tenant scope (shared-tables mode)
144-
* @param array<\Utopia\Query\Query> $queries Filters (metric, time range, resource, etc.)
144+
* @param array<\Utopia\Query\Query> $queries Filters (metric, time range, resourceType, etc.)
145145
* @return array<Metric>
146146
*/
147147
abstract public function findDaily(string $tenant, array $queries = []): array;

0 commit comments

Comments
 (0)