mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: add usage tracking package (#19095)
Not used in coderd yet, see stack. Adds two new packages: - `coderd/usage`: provides an interface for the "Collector" as well as a stub implementation for AGPL - `enterprise/coderd/usage`: provides an interface for the "Publisher" as well as a Tallyman implementation Relates to https://github.com/coder/internal/issues/814
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
-- name: InsertUsageEvent :exec
|
||||
-- Duplicate events are ignored intentionally to allow for multiple replicas to
|
||||
-- publish heartbeat events.
|
||||
INSERT INTO
|
||||
usage_events (
|
||||
id,
|
||||
event_type,
|
||||
event_data,
|
||||
created_at,
|
||||
publish_started_at,
|
||||
published_at,
|
||||
failure_message
|
||||
)
|
||||
VALUES
|
||||
(@id, @event_type, @event_data, @created_at, NULL, NULL, NULL)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- name: SelectUsageEventsForPublishing :many
|
||||
WITH usage_events AS (
|
||||
UPDATE
|
||||
usage_events
|
||||
SET
|
||||
publish_started_at = @now::timestamptz
|
||||
WHERE
|
||||
id IN (
|
||||
SELECT
|
||||
potential_event.id
|
||||
FROM
|
||||
usage_events potential_event
|
||||
WHERE
|
||||
-- Do not publish events that have already been published or
|
||||
-- have permanently failed to publish.
|
||||
potential_event.published_at IS NULL
|
||||
-- Do not publish events that are already being published by
|
||||
-- another replica.
|
||||
AND (
|
||||
potential_event.publish_started_at IS NULL
|
||||
-- If the event has publish_started_at set, it must be older
|
||||
-- than an hour ago. This is so we can retry publishing
|
||||
-- events where the replica exited or couldn't update the
|
||||
-- row.
|
||||
-- The parenthesis around @now::timestamptz are necessary to
|
||||
-- avoid sqlc from generating an extra argument.
|
||||
OR potential_event.publish_started_at < (@now::timestamptz) - INTERVAL '1 hour'
|
||||
)
|
||||
-- Do not publish events older than 30 days. Tallyman will
|
||||
-- always permanently reject these events anyways. This is to
|
||||
-- avoid duplicate events being billed to customers, as
|
||||
-- Metronome will only deduplicate events within 34 days.
|
||||
-- Also, the same parenthesis thing here as above.
|
||||
AND potential_event.created_at > (@now::timestamptz) - INTERVAL '30 days'
|
||||
ORDER BY potential_event.created_at ASC
|
||||
FOR UPDATE SKIP LOCKED
|
||||
LIMIT 100
|
||||
)
|
||||
RETURNING *
|
||||
)
|
||||
SELECT *
|
||||
-- Note that this selects from the CTE, not the original table. The CTE is named
|
||||
-- the same as the original table to trick sqlc into reusing the existing struct
|
||||
-- for the table.
|
||||
FROM usage_events
|
||||
-- The CTE and the reorder is required because UPDATE doesn't guarantee order.
|
||||
ORDER BY created_at ASC;
|
||||
|
||||
-- name: UpdateUsageEventsPostPublish :exec
|
||||
UPDATE
|
||||
usage_events
|
||||
SET
|
||||
publish_started_at = NULL,
|
||||
published_at = CASE WHEN input.set_published_at THEN @now::timestamptz ELSE NULL END,
|
||||
failure_message = NULLIF(input.failure_message, '')
|
||||
FROM (
|
||||
SELECT
|
||||
UNNEST(@ids::text[]) AS id,
|
||||
UNNEST(@failure_messages::text[]) AS failure_message,
|
||||
UNNEST(@set_published_ats::boolean[]) AS set_published_at
|
||||
) input
|
||||
WHERE
|
||||
input.id = usage_events.id
|
||||
-- If the number of ids, failure messages, and set published ats are not the
|
||||
-- same, do not do anything. Unfortunately you can't really throw from a
|
||||
-- query without writing a function or doing some jank like dividing by
|
||||
-- zero, so this is the best we can do.
|
||||
AND cardinality(@ids::text[]) = cardinality(@failure_messages::text[])
|
||||
AND cardinality(@ids::text[]) = cardinality(@set_published_ats::boolean[]);
|
||||
Reference in New Issue
Block a user