log message size in worker analytics (#3274)

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
This commit is contained in:
alex 2024-03-27 11:33:47 +00:00 committed by GitHub
parent d45d77bedf
commit 408a269114
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 84 additions and 45 deletions

View file

@ -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)
}
}
}

View file

@ -26,4 +26,5 @@ export interface Environment {
TLDRAW_ENV: string | undefined
SENTRY_DSN: string | undefined
IS_LOCAL: string | undefined
WORKER_NAME: string | undefined
}

View file

@ -1,4 +1,4 @@
export { TLServer, type DBLoadResult } from './lib/TLServer'
export { TLServer, type DBLoadResult, type TLServerEvent } from './lib/TLServer'
export {
TLSyncClient,
type TLPersistentClientSocket,

View file

@ -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<R extends UnknownRecord> implements TLRoomSocket<R> {
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<R>) {
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()
}
}

View file

@ -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.

View file

@ -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,