From 408a26911447c37a3c3be033ffdff336d9c3573e Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 27 Mar 2024 11:33:47 +0000 Subject: [PATCH] log message size in worker analytics (#3274) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds logging of message size in worker analytics. This also adds the environment to worker analytics as `blob2`. We need this because previously, all the analytics from all environments were going to the same place with no ability to tell them apart, which means we can't easily compare analytics on e.g. a particular PR. This means that all the other blobs get shifted along one, so we won't be able to query across the boundary of when this gets released for those properties. I think this is fine though - it's things like `roomId` that I don't think we were querying on anyway. You can query the analytics through grafana - [docs here](https://www.notion.so/tldraw/How-to-11fce2ed0be5480bb8e711c7ff1a0488?pvs=4#a66fae7bfcfe4ffe9d5348504598c6a0) ### Change Type - [x] `internal` — Does not affect user-facing stuff - [x] `chore` — Updating dependencies, other boring stuff --- .../src/lib/TLDrawDurableObject.ts | 51 +++++++++-------- apps/dotcom-worker/src/lib/types.ts | 1 + packages/tlsync/src/index.ts | 2 +- .../tlsync/src/lib/ServerSocketAdapter.ts | 15 +++-- packages/tlsync/src/lib/TLServer.ts | 57 +++++++++++++------ scripts/deploy.ts | 3 + 6 files changed, 84 insertions(+), 45 deletions(-) diff --git a/apps/dotcom-worker/src/lib/TLDrawDurableObject.ts b/apps/dotcom-worker/src/lib/TLDrawDurableObject.ts index 8cad7356e..ed6a37d8e 100644 --- a/apps/dotcom-worker/src/lib/TLDrawDurableObject.ts +++ b/apps/dotcom-worker/src/lib/TLDrawDurableObject.ts @@ -5,12 +5,13 @@ import { SupabaseClient } from '@supabase/supabase-js' import { RoomSnapshot, TLServer, + TLServerEvent, TLSyncRoom, type DBLoadResult, type PersistedRoomSnapshotForSupabase, type RoomState, } from '@tldraw/tlsync' -import { assert, assertExists } from '@tldraw/utils' +import { assert, assertExists, exhaustiveSwitchError } from '@tldraw/utils' import { IRequest, Router } from 'itty-router' import Toucan from 'toucan-js' import { AlarmScheduler } from './AlarmScheduler' @@ -255,38 +256,42 @@ export class TLDrawDurableObject extends TLServer { return new Response(null, { status: 101, webSocket: clientWebSocket }) } - logEvent( - event: - | { - type: 'client' - roomId: string - name: string - clientId: string - instanceId: string - localClientId: string - } - | { - type: 'room' - roomId: string - name: string - } + private writeEvent( + name: string, + { blobs, indexes, doubles }: { blobs?: string[]; indexes?: [string]; doubles?: number[] } ) { + this.measure?.writeDataPoint({ + blobs: [name, this.env.WORKER_NAME ?? 'development-tldraw-multiplayer', ...(blobs ?? [])], + doubles, + indexes, + }) + } + + logEvent(event: TLServerEvent) { switch (event.type) { case 'room': { - this.measure?.writeDataPoint({ - blobs: [event.name, event.roomId], // we would add user/connection ids here if we could - }) - + // we would add user/connection ids here if we could + this.writeEvent(event.name, { blobs: [event.roomId] }) break } case 'client': { - this.measure?.writeDataPoint({ - blobs: [event.name, event.roomId, event.clientId, event.instanceId], // we would add user/connection ids here if we could + // we would add user/connection ids here if we could + this.writeEvent(event.name, { + blobs: [event.roomId, event.clientId, event.instanceId], indexes: [event.localClientId], }) - break } + case 'send_message': { + this.writeEvent(event.type, { + blobs: [event.roomId, event.messageType], + doubles: [event.messageLength], + }) + break + } + default: { + exhaustiveSwitchError(event) + } } } diff --git a/apps/dotcom-worker/src/lib/types.ts b/apps/dotcom-worker/src/lib/types.ts index b454677d6..3505ad5c6 100644 --- a/apps/dotcom-worker/src/lib/types.ts +++ b/apps/dotcom-worker/src/lib/types.ts @@ -26,4 +26,5 @@ export interface Environment { TLDRAW_ENV: string | undefined SENTRY_DSN: string | undefined IS_LOCAL: string | undefined + WORKER_NAME: string | undefined } diff --git a/packages/tlsync/src/index.ts b/packages/tlsync/src/index.ts index 57f5aaab9..fd95e08d4 100644 --- a/packages/tlsync/src/index.ts +++ b/packages/tlsync/src/index.ts @@ -1,4 +1,4 @@ -export { TLServer, type DBLoadResult } from './lib/TLServer' +export { TLServer, type DBLoadResult, type TLServerEvent } from './lib/TLServer' export { TLSyncClient, type TLPersistentClientSocket, diff --git a/packages/tlsync/src/lib/ServerSocketAdapter.ts b/packages/tlsync/src/lib/ServerSocketAdapter.ts index c91e75223..8407dfcc7 100644 --- a/packages/tlsync/src/lib/ServerSocketAdapter.ts +++ b/packages/tlsync/src/lib/ServerSocketAdapter.ts @@ -3,18 +3,25 @@ import ws from 'ws' import { TLRoomSocket } from './TLSyncRoom' import { TLSocketServerSentEvent } from './protocol' +type ServerSocketAdapterOptions = { + readonly ws: WebSocket | ws.WebSocket + readonly logSendMessage: (type: string, size: number) => void +} + /** @public */ export class ServerSocketAdapter implements TLRoomSocket { - constructor(public readonly ws: WebSocket | ws.WebSocket) {} + constructor(public readonly opts: ServerSocketAdapterOptions) {} // eslint-disable-next-line no-restricted-syntax get isOpen(): boolean { - return this.ws.readyState === 1 // ready state open + return this.opts.ws.readyState === 1 // ready state open } // see TLRoomSocket for details on why this accepts a union and not just arrays sendMessage(msg: TLSocketServerSentEvent) { - this.ws.send(JSON.stringify(msg)) + const message = JSON.stringify(msg) + this.opts.logSendMessage(msg.type, message.length) + this.opts.ws.send(message) } close() { - this.ws.close() + this.opts.ws.close() } } diff --git a/packages/tlsync/src/lib/TLServer.ts b/packages/tlsync/src/lib/TLServer.ts index 5389a3410..ecafaab18 100644 --- a/packages/tlsync/src/lib/TLServer.ts +++ b/packages/tlsync/src/lib/TLServer.ts @@ -20,6 +20,32 @@ export type DBLoadResult = type: 'room_not_found' } +export type TLServerEvent = + | { + type: 'client' + name: 'room_create' | 'room_reopen' | 'enter' | 'leave' | 'last_out' + roomId: string + clientId: string + instanceId: string + localClientId: string + } + | { + type: 'room' + name: + | 'failed_load_from_db' + | 'failed_persist_to_db' + | 'room_empty' + | 'fail_persist' + | 'room_start' + roomId: string + } + | { + type: 'send_message' + roomId: string + messageType: string + messageLength: number + } + /** * This class manages rooms for a websocket server. * @@ -116,7 +142,19 @@ export abstract class TLServer { const clientId = nanoid() const [roomState, roomOpenKind] = await this.getInitialRoomState(persistenceKey) - roomState.room.handleNewSession(sessionKey, new ServerSocketAdapter(socket)) + roomState.room.handleNewSession( + sessionKey, + new ServerSocketAdapter({ + ws: socket, + logSendMessage: (messageType, messageLength) => + this.logEvent({ + type: 'send_message', + roomId: persistenceKey, + messageType, + messageLength, + }), + }) + ) if (roomOpenKind === 'new' || roomOpenKind === 'reopen') { // Record that the room is now active @@ -223,22 +261,7 @@ export abstract class TLServer { * @param event - The event to log. * @public */ - abstract logEvent( - event: - | { - type: 'client' - roomId: string - name: string - clientId: string - instanceId: string - localClientId: string - } - | { - type: 'room' - roomId: string - name: string - } - ): void + abstract logEvent(event: TLServerEvent): void /** * Get a room by its id. diff --git a/scripts/deploy.ts b/scripts/deploy.ts index d550ac988..4c39f387d 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -185,6 +185,7 @@ name = "${previewId}-tldraw-assets"` let didUpdateTlsyncWorker = false async function deployTlsyncWorker({ dryRun }: { dryRun: boolean }) { + const workerId = `${previewId ?? env.TLDRAW_ENV}-tldraw-multiplayer` if (previewId && !didUpdateTlsyncWorker) { appendFileSync( join(worker, 'wrangler.toml'), @@ -212,6 +213,8 @@ name = "${previewId}-tldraw-multiplayer"` `TLDRAW_ENV:${env.TLDRAW_ENV}`, '--var', `APP_ORIGIN:${env.APP_ORIGIN}`, + '--var', + `WORKER_NAME:${workerId}`, ], { pwd: worker,