Add tracing to IDENTIFY
This commit is contained in:
parent
654e07bbf5
commit
230248d6f4
@ -56,6 +56,11 @@ import {
|
|||||||
checkToken,
|
checkToken,
|
||||||
emitEvent,
|
emitEvent,
|
||||||
getDatabase,
|
getDatabase,
|
||||||
|
TraceNode,
|
||||||
|
TraceRoot,
|
||||||
|
Stopwatch,
|
||||||
|
timePromise,
|
||||||
|
ElapsedTime,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { check } from "./instanceOf";
|
import { check } from "./instanceOf";
|
||||||
|
|
||||||
@ -71,7 +76,10 @@ const tryGetUserFromToken = async (...args: Parameters<typeof checkToken>) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function onIdentify(this: WebSocket, data: Payload) {
|
export async function onIdentify(this: WebSocket, data: Payload) {
|
||||||
const startTime = Date.now();
|
const totalSw = Stopwatch.startNew();
|
||||||
|
const taskSw = Stopwatch.startNew();
|
||||||
|
const gatewayShardName = `sb-gateway`;
|
||||||
|
|
||||||
if (this.user_id) {
|
if (this.user_id) {
|
||||||
// we've already identified
|
// we've already identified
|
||||||
return this.close(CLOSECODES.Already_authenticated);
|
return this.close(CLOSECODES.Already_authenticated);
|
||||||
@ -85,6 +93,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
|
|
||||||
this.capabilities = new Capabilities(identify.capabilities || 0);
|
this.capabilities = new Capabilities(identify.capabilities || 0);
|
||||||
this.large_threshold = identify.large_threshold || 250;
|
this.large_threshold = identify.large_threshold || 250;
|
||||||
|
const parseAndValidateTime = taskSw.getElapsedAndReset();
|
||||||
|
|
||||||
const user = await tryGetUserFromToken(identify.token, {
|
const user = await tryGetUserFromToken(identify.token, {
|
||||||
relations: ["relationships", "relationships.to", "settings"],
|
relations: ["relationships", "relationships.to", "settings"],
|
||||||
@ -92,10 +101,11 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
});
|
});
|
||||||
if (!user) return this.close(CLOSECODES.Authentication_failed);
|
if (!user) return this.close(CLOSECODES.Authentication_failed);
|
||||||
this.user_id = user.id;
|
this.user_id = user.id;
|
||||||
const userQueryTime = Date.now();
|
const userQueryTime = taskSw.getElapsedAndReset();
|
||||||
|
|
||||||
// Check intents
|
// Check intents
|
||||||
if (!identify.intents) identify.intents = 0b11011111111111111111111111111111111n; // TODO: what is this number?
|
if (!identify.intents)
|
||||||
|
identify.intents = 0b11011111111111111111111111111111111n; // TODO: what is this number?
|
||||||
this.intents = new Intents(identify.intents);
|
this.intents = new Intents(identify.intents);
|
||||||
|
|
||||||
// TODO: actually do intent things.
|
// TODO: actually do intent things.
|
||||||
@ -119,6 +129,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
return this.close(CLOSECODES.Invalid_shard);
|
return this.close(CLOSECODES.Invalid_shard);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const validateIntentsAndShardingTime = taskSw.getElapsedAndReset();
|
||||||
|
|
||||||
// Generate a new gateway session ( id is already made, just save it in db )
|
// Generate a new gateway session ( id is already made, just save it in db )
|
||||||
const session = Session.create({
|
const session = Session.create({
|
||||||
@ -133,23 +144,30 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
client_status: {},
|
client_status: {},
|
||||||
activities: identify.presence?.activities, // TODO: validation
|
activities: identify.presence?.activities, // TODO: validation
|
||||||
});
|
});
|
||||||
|
const createSessionTime = taskSw.getElapsedAndReset();
|
||||||
const createSessionTime = Date.now();
|
|
||||||
|
|
||||||
// Get from database:
|
// Get from database:
|
||||||
// * the users read states
|
// * the users read states
|
||||||
// * guild members for this user
|
// * guild members for this user
|
||||||
// * recipients ( dm channels )
|
// * recipients ( dm channels )
|
||||||
// * the bot application, if it exists
|
// * the bot application, if it exists
|
||||||
const [, application, read_states, members, recipients] = await Promise.all(
|
const [
|
||||||
[
|
{ elapsed: sessionSaveTime },
|
||||||
session.save(),
|
{ result: application, elapsed: applicationQueryTime },
|
||||||
|
{ result: read_states, elapsed: read_statesQueryTime },
|
||||||
|
{ result: members, elapsed: membersQueryTime },
|
||||||
|
{ result: recipients, elapsed: recipientsQueryTime },
|
||||||
|
] = await Promise.all([
|
||||||
|
timePromise(() => session.save()),
|
||||||
|
|
||||||
|
timePromise(() =>
|
||||||
Application.findOne({
|
Application.findOne({
|
||||||
where: { id: this.user_id },
|
where: { id: this.user_id },
|
||||||
select: ["id", "flags"],
|
select: ["id", "flags"],
|
||||||
}),
|
}),
|
||||||
|
),
|
||||||
|
|
||||||
|
timePromise(() =>
|
||||||
ReadState.find({
|
ReadState.find({
|
||||||
where: { user_id: this.user_id },
|
where: { user_id: this.user_id },
|
||||||
select: [
|
select: [
|
||||||
@ -160,7 +178,9 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
"mention_count",
|
"mention_count",
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
),
|
||||||
|
|
||||||
|
timePromise(() =>
|
||||||
Member.find({
|
Member.find({
|
||||||
where: { id: this.user_id },
|
where: { id: this.user_id },
|
||||||
select: {
|
select: {
|
||||||
@ -194,7 +214,9 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
// "user",
|
// "user",
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
),
|
||||||
|
|
||||||
|
timePromise(() =>
|
||||||
Recipient.find({
|
Recipient.find({
|
||||||
where: { user_id: this.user_id, closed: false },
|
where: { user_id: this.user_id, closed: false },
|
||||||
relations: [
|
relations: [
|
||||||
@ -226,17 +248,19 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
),
|
||||||
);
|
]);
|
||||||
|
|
||||||
const query1Time = Date.now();
|
const totalQueryTime = taskSw.getElapsedAndReset();
|
||||||
|
|
||||||
// We forgot to migrate user settings from the JSON column of `users`
|
// We forgot to migrate user settings from the JSON column of `users`
|
||||||
// to the `user_settings` table theyre in now,
|
// to the `user_settings` table theyre in now,
|
||||||
// so for instances that migrated, users may not have a `user_settings` row.
|
// so for instances that migrated, users may not have a `user_settings` row.
|
||||||
|
let createUserSettingsTime: ElapsedTime | undefined = undefined;
|
||||||
if (!user.settings) {
|
if (!user.settings) {
|
||||||
user.settings = new UserSettings();
|
user.settings = new UserSettings();
|
||||||
await user.settings.save();
|
await user.settings.save();
|
||||||
|
createUserSettingsTime = taskSw.getElapsedAndReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate merged_members
|
// Generate merged_members
|
||||||
@ -262,8 +286,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
const mergedMembersTime = taskSw.getElapsedAndReset();
|
||||||
const mergedMembersTime = Date.now();
|
|
||||||
|
|
||||||
// Populated with guilds 'unavailable' currently
|
// Populated with guilds 'unavailable' currently
|
||||||
// Just for bots
|
// Just for bots
|
||||||
@ -307,6 +330,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
threads: [],
|
threads: [],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
const generateGuildsListTime = taskSw.getElapsedAndReset();
|
||||||
|
|
||||||
// Generate user_guild_settings
|
// Generate user_guild_settings
|
||||||
const user_guild_settings_entries: ReadyUserGuildSettingsEntries[] =
|
const user_guild_settings_entries: ReadyUserGuildSettingsEntries[] =
|
||||||
@ -321,6 +345,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
channel_id: y[0],
|
channel_id: y[0],
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
const generateUserGuildSettingsTime = taskSw.getElapsedAndReset();
|
||||||
|
|
||||||
// Popultaed with users from private channels, relationships.
|
// Popultaed with users from private channels, relationships.
|
||||||
// Uses a set to dedupe for us.
|
// Uses a set to dedupe for us.
|
||||||
@ -355,11 +380,11 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
is_spam: false, // TODO
|
is_spam: false, // TODO
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
const generateDmChannelsTime = taskSw.getElapsedAndReset();
|
||||||
|
|
||||||
// From user relationships ( friends ), also append to `users` list
|
// From user relationships ( friends ), also append to `users` list
|
||||||
user.relationships.forEach((x) => users.add(x.to.toPublicUser()));
|
user.relationships.forEach((x) => users.add(x.to.toPublicUser()));
|
||||||
|
const appendRelationshipsTime = taskSw.getElapsedAndReset();
|
||||||
const remapDataTime = Date.now();
|
|
||||||
|
|
||||||
// Send SESSIONS_REPLACE and PRESENCE_UPDATE
|
// Send SESSIONS_REPLACE and PRESENCE_UPDATE
|
||||||
const allSessions = (
|
const allSessions = (
|
||||||
@ -376,25 +401,31 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
session_id: x.session_id, // TODO: discord.com sends 'all', what is that???
|
session_id: x.session_id, // TODO: discord.com sends 'all', what is that???
|
||||||
status: x.status,
|
status: x.status,
|
||||||
}));
|
}));
|
||||||
|
const findAndGenerateSessionReplaceTime = taskSw.getElapsedAndReset();
|
||||||
|
|
||||||
const sessionReplaceTime = Date.now();
|
const [
|
||||||
|
{ elapsed: emitSessionsReplaceTime },
|
||||||
Promise.all([
|
{ elapsed: emitPresenceUpdateTime },
|
||||||
emitEvent({
|
] = await Promise.all([
|
||||||
event: "SESSIONS_REPLACE",
|
timePromise(() =>
|
||||||
user_id: this.user_id,
|
emitEvent({
|
||||||
data: allSessions,
|
event: "SESSIONS_REPLACE",
|
||||||
} as SessionsReplace),
|
user_id: this.user_id,
|
||||||
emitEvent({
|
data: allSessions,
|
||||||
event: "PRESENCE_UPDATE",
|
} as SessionsReplace),
|
||||||
user_id: this.user_id,
|
),
|
||||||
data: {
|
timePromise(() =>
|
||||||
user: user.toPublicUser(),
|
emitEvent({
|
||||||
activities: session.activities,
|
event: "PRESENCE_UPDATE",
|
||||||
client_status: session.client_status,
|
user_id: this.user_id,
|
||||||
status: session.status,
|
data: {
|
||||||
},
|
user: user.toPublicUser(),
|
||||||
} as PresenceUpdateEvent),
|
activities: session.activities,
|
||||||
|
client_status: session.client_status,
|
||||||
|
status: session.status,
|
||||||
|
},
|
||||||
|
} as PresenceUpdateEvent),
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Build READY
|
// Build READY
|
||||||
@ -402,6 +433,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
read_states.forEach((x) => {
|
read_states.forEach((x) => {
|
||||||
x.id = x.channel_id;
|
x.id = x.channel_id;
|
||||||
});
|
});
|
||||||
|
const remapReadStateIdsTime = taskSw.getElapsedAndReset();
|
||||||
|
|
||||||
const d: ReadyEventData = {
|
const d: ReadyEventData = {
|
||||||
v: 9,
|
v: 9,
|
||||||
@ -462,7 +494,49 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
// ????
|
// ????
|
||||||
flags: 0,
|
flags: 0,
|
||||||
},
|
},
|
||||||
|
game_relationships: [],
|
||||||
};
|
};
|
||||||
|
const buildReadyEventDataTime = taskSw.getElapsedAndReset();
|
||||||
|
const _trace = [
|
||||||
|
gatewayShardName,
|
||||||
|
{
|
||||||
|
micros: totalSw.elapsed().totalMicroseconds,
|
||||||
|
calls: [],
|
||||||
|
},
|
||||||
|
] as TraceRoot;
|
||||||
|
const times = {
|
||||||
|
parseAndValidateTime,
|
||||||
|
userQueryTime,
|
||||||
|
validateIntentsAndShardingTime,
|
||||||
|
createSessionTime,
|
||||||
|
sessionSaveTime,
|
||||||
|
applicationQueryTime,
|
||||||
|
read_statesQueryTime,
|
||||||
|
membersQueryTime,
|
||||||
|
recipientsQueryTime,
|
||||||
|
totalQueryTime,
|
||||||
|
createUserSettingsTime,
|
||||||
|
mergedMembersTime,
|
||||||
|
generateGuildsListTime,
|
||||||
|
generateUserGuildSettingsTime,
|
||||||
|
generateDmChannelsTime,
|
||||||
|
appendRelationshipsTime,
|
||||||
|
findAndGenerateSessionReplaceTime,
|
||||||
|
emitSessionsReplaceTime,
|
||||||
|
emitPresenceUpdateTime,
|
||||||
|
remapReadStateIdsTime,
|
||||||
|
buildReadyEventDataTime,
|
||||||
|
};
|
||||||
|
for (const [key, value] of Object.entries(times)) {
|
||||||
|
if (value) {
|
||||||
|
_trace![1].calls.push(key, { micros: value.totalMicroseconds });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_trace![1].calls.push(
|
||||||
|
"buildTraceTime",
|
||||||
|
{ micros: taskSw.elapsed().totalMicroseconds },
|
||||||
|
);
|
||||||
|
d._trace = [JSON.stringify(_trace)];
|
||||||
|
|
||||||
// Send READY
|
// Send READY
|
||||||
await Send(this, {
|
await Send(this, {
|
||||||
@ -547,16 +621,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
const setupListenerTime = Date.now();
|
const setupListenerTime = Date.now();
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`[Gateway] IDENTIFY ${this.user_id} in ${Date.now() - startTime}ms`,
|
`[Gateway] IDENTIFY ${this.user_id} in ${totalSw.elapsed().totalMilliseconds}ms`,
|
||||||
{
|
JSON.stringify(d._trace, null, 2),
|
||||||
userQueryTime: Date.now() - userQueryTime,
|
|
||||||
createSessionTime: Date.now() - createSessionTime,
|
|
||||||
query1Time: Date.now() - query1Time,
|
|
||||||
remapDataTime: Date.now() - remapDataTime,
|
|
||||||
sessionReplaceTime: Date.now() - sessionReplaceTime,
|
|
||||||
readyTime: Date.now() - readyTime,
|
|
||||||
pendingGuildsTime: Date.now() - pendingGuildsTime,
|
|
||||||
readySupplementalTime: Date.now() - readySupplementalTime,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,6 +41,11 @@ export enum OPCODES {
|
|||||||
Stream_Ping = 21,
|
Stream_Ping = 21,
|
||||||
Stream_Set_Paused = 22,
|
Stream_Set_Paused = 22,
|
||||||
Request_Application_Commands = 24,
|
Request_Application_Commands = 24,
|
||||||
|
// We don't know the names for these:
|
||||||
|
ThirtySix = 36, // this is just a guild id?
|
||||||
|
Guild_Subscriptions_Bulk = 37, // Already implemented it seems?
|
||||||
|
SetQoS = 40,
|
||||||
|
ClientInitSession = 41,
|
||||||
}
|
}
|
||||||
export enum CLOSECODES {
|
export enum CLOSECODES {
|
||||||
Unknown_error = 4000,
|
Unknown_error = 4000,
|
||||||
|
|||||||
@ -182,7 +182,7 @@ export class ReadyGuildDTO implements IReadyGuildDTO {
|
|||||||
1: 5,
|
1: 5,
|
||||||
2: 2,
|
2: 2,
|
||||||
3: 2,
|
3: 2,
|
||||||
}; // ?????
|
}; // ????? // emma: this appears to always be an empty attrset...
|
||||||
this.channels = guild.channels;
|
this.channels = guild.channels;
|
||||||
this.data_mode = "full";
|
this.data_mode = "full";
|
||||||
this.emojis = guild.emojis;
|
this.emojis = guild.emojis;
|
||||||
|
|||||||
@ -132,8 +132,17 @@ export interface ReadyEventData {
|
|||||||
notification_settings: {
|
notification_settings: {
|
||||||
flags: number;
|
flags: number;
|
||||||
};
|
};
|
||||||
|
game_relationships: never[]; // what is this?
|
||||||
|
_trace?: string[]; // trace of the request, used for debugging
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TraceNode =
|
||||||
|
| { micros: number; calls: TraceNode[] }
|
||||||
|
| { micros: number }
|
||||||
|
| string;
|
||||||
|
|
||||||
|
export type TraceRoot = [string, { micros: number; calls: TraceNode[] }];
|
||||||
|
|
||||||
export interface ReadyEvent extends Event {
|
export interface ReadyEvent extends Event {
|
||||||
event: "READY";
|
event: "READY";
|
||||||
data: ReadyEventData;
|
data: ReadyEventData;
|
||||||
@ -159,7 +168,10 @@ export interface ChannelPinsUpdateEvent extends Event {
|
|||||||
data: {
|
data: {
|
||||||
guild_id?: string;
|
guild_id?: string;
|
||||||
channel_id: string;
|
channel_id: string;
|
||||||
last_pin_timestamp?: number;
|
/**
|
||||||
|
* @format ISO8601
|
||||||
|
*/
|
||||||
|
last_pin_timestamp?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export * from "./Regex";
|
|||||||
export * from "./Rights";
|
export * from "./Rights";
|
||||||
export * from "./Sentry";
|
export * from "./Sentry";
|
||||||
export * from "./Snowflake";
|
export * from "./Snowflake";
|
||||||
|
export * from "./Stopwatch";
|
||||||
export * from "./String";
|
export * from "./String";
|
||||||
export * from "./Token";
|
export * from "./Token";
|
||||||
export * from "./TraverseDirectory";
|
export * from "./TraverseDirectory";
|
||||||
|
|||||||
Reference in New Issue
Block a user