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:
parent
d45d77bedf
commit
408a269114
6 changed files with 84 additions and 45 deletions
|
@ -5,12 +5,13 @@ import { SupabaseClient } from '@supabase/supabase-js'
|
||||||
import {
|
import {
|
||||||
RoomSnapshot,
|
RoomSnapshot,
|
||||||
TLServer,
|
TLServer,
|
||||||
|
TLServerEvent,
|
||||||
TLSyncRoom,
|
TLSyncRoom,
|
||||||
type DBLoadResult,
|
type DBLoadResult,
|
||||||
type PersistedRoomSnapshotForSupabase,
|
type PersistedRoomSnapshotForSupabase,
|
||||||
type RoomState,
|
type RoomState,
|
||||||
} from '@tldraw/tlsync'
|
} from '@tldraw/tlsync'
|
||||||
import { assert, assertExists } from '@tldraw/utils'
|
import { assert, assertExists, exhaustiveSwitchError } from '@tldraw/utils'
|
||||||
import { IRequest, Router } from 'itty-router'
|
import { IRequest, Router } from 'itty-router'
|
||||||
import Toucan from 'toucan-js'
|
import Toucan from 'toucan-js'
|
||||||
import { AlarmScheduler } from './AlarmScheduler'
|
import { AlarmScheduler } from './AlarmScheduler'
|
||||||
|
@ -255,38 +256,42 @@ export class TLDrawDurableObject extends TLServer {
|
||||||
return new Response(null, { status: 101, webSocket: clientWebSocket })
|
return new Response(null, { status: 101, webSocket: clientWebSocket })
|
||||||
}
|
}
|
||||||
|
|
||||||
logEvent(
|
private writeEvent(
|
||||||
event:
|
name: string,
|
||||||
| {
|
{ blobs, indexes, doubles }: { blobs?: string[]; indexes?: [string]; doubles?: number[] }
|
||||||
type: 'client'
|
|
||||||
roomId: string
|
|
||||||
name: string
|
|
||||||
clientId: string
|
|
||||||
instanceId: string
|
|
||||||
localClientId: string
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'room'
|
|
||||||
roomId: string
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
|
this.measure?.writeDataPoint({
|
||||||
|
blobs: [name, this.env.WORKER_NAME ?? 'development-tldraw-multiplayer', ...(blobs ?? [])],
|
||||||
|
doubles,
|
||||||
|
indexes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
logEvent(event: TLServerEvent) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case 'room': {
|
case 'room': {
|
||||||
this.measure?.writeDataPoint({
|
// we would add user/connection ids here if we could
|
||||||
blobs: [event.name, event.roomId], // we would add user/connection ids here if we could
|
this.writeEvent(event.name, { blobs: [event.roomId] })
|
||||||
})
|
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'client': {
|
case 'client': {
|
||||||
this.measure?.writeDataPoint({
|
// we would add user/connection ids here if we could
|
||||||
blobs: [event.name, event.roomId, event.clientId, event.instanceId], // we would add user/connection ids here if we could
|
this.writeEvent(event.name, {
|
||||||
|
blobs: [event.roomId, event.clientId, event.instanceId],
|
||||||
indexes: [event.localClientId],
|
indexes: [event.localClientId],
|
||||||
})
|
})
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case 'send_message': {
|
||||||
|
this.writeEvent(event.type, {
|
||||||
|
blobs: [event.roomId, event.messageType],
|
||||||
|
doubles: [event.messageLength],
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
exhaustiveSwitchError(event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,4 +26,5 @@ export interface Environment {
|
||||||
TLDRAW_ENV: string | undefined
|
TLDRAW_ENV: string | undefined
|
||||||
SENTRY_DSN: string | undefined
|
SENTRY_DSN: string | undefined
|
||||||
IS_LOCAL: string | undefined
|
IS_LOCAL: string | undefined
|
||||||
|
WORKER_NAME: string | undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export { TLServer, type DBLoadResult } from './lib/TLServer'
|
export { TLServer, type DBLoadResult, type TLServerEvent } from './lib/TLServer'
|
||||||
export {
|
export {
|
||||||
TLSyncClient,
|
TLSyncClient,
|
||||||
type TLPersistentClientSocket,
|
type TLPersistentClientSocket,
|
||||||
|
|
|
@ -3,18 +3,25 @@ import ws from 'ws'
|
||||||
import { TLRoomSocket } from './TLSyncRoom'
|
import { TLRoomSocket } from './TLSyncRoom'
|
||||||
import { TLSocketServerSentEvent } from './protocol'
|
import { TLSocketServerSentEvent } from './protocol'
|
||||||
|
|
||||||
|
type ServerSocketAdapterOptions = {
|
||||||
|
readonly ws: WebSocket | ws.WebSocket
|
||||||
|
readonly logSendMessage: (type: string, size: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export class ServerSocketAdapter<R extends UnknownRecord> implements TLRoomSocket<R> {
|
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
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
get isOpen(): boolean {
|
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
|
// see TLRoomSocket for details on why this accepts a union and not just arrays
|
||||||
sendMessage(msg: TLSocketServerSentEvent<R>) {
|
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() {
|
close() {
|
||||||
this.ws.close()
|
this.opts.ws.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,32 @@ export type DBLoadResult =
|
||||||
type: 'room_not_found'
|
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.
|
* This class manages rooms for a websocket server.
|
||||||
*
|
*
|
||||||
|
@ -116,7 +142,19 @@ export abstract class TLServer {
|
||||||
const clientId = nanoid()
|
const clientId = nanoid()
|
||||||
const [roomState, roomOpenKind] = await this.getInitialRoomState(persistenceKey)
|
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') {
|
if (roomOpenKind === 'new' || roomOpenKind === 'reopen') {
|
||||||
// Record that the room is now active
|
// Record that the room is now active
|
||||||
|
@ -223,22 +261,7 @@ export abstract class TLServer {
|
||||||
* @param event - The event to log.
|
* @param event - The event to log.
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
abstract logEvent(
|
abstract logEvent(event: TLServerEvent): void
|
||||||
event:
|
|
||||||
| {
|
|
||||||
type: 'client'
|
|
||||||
roomId: string
|
|
||||||
name: string
|
|
||||||
clientId: string
|
|
||||||
instanceId: string
|
|
||||||
localClientId: string
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'room'
|
|
||||||
roomId: string
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
): void
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a room by its id.
|
* Get a room by its id.
|
||||||
|
|
|
@ -185,6 +185,7 @@ name = "${previewId}-tldraw-assets"`
|
||||||
|
|
||||||
let didUpdateTlsyncWorker = false
|
let didUpdateTlsyncWorker = false
|
||||||
async function deployTlsyncWorker({ dryRun }: { dryRun: boolean }) {
|
async function deployTlsyncWorker({ dryRun }: { dryRun: boolean }) {
|
||||||
|
const workerId = `${previewId ?? env.TLDRAW_ENV}-tldraw-multiplayer`
|
||||||
if (previewId && !didUpdateTlsyncWorker) {
|
if (previewId && !didUpdateTlsyncWorker) {
|
||||||
appendFileSync(
|
appendFileSync(
|
||||||
join(worker, 'wrangler.toml'),
|
join(worker, 'wrangler.toml'),
|
||||||
|
@ -212,6 +213,8 @@ name = "${previewId}-tldraw-multiplayer"`
|
||||||
`TLDRAW_ENV:${env.TLDRAW_ENV}`,
|
`TLDRAW_ENV:${env.TLDRAW_ENV}`,
|
||||||
'--var',
|
'--var',
|
||||||
`APP_ORIGIN:${env.APP_ORIGIN}`,
|
`APP_ORIGIN:${env.APP_ORIGIN}`,
|
||||||
|
'--var',
|
||||||
|
`WORKER_NAME:${workerId}`,
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
pwd: worker,
|
pwd: worker,
|
||||||
|
|
Loading…
Reference in a new issue