mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
[web] Socket diagnostics updates (#22951)
* Increase threshold for "latency in red color" * Fix online status in Chrome and Safari * Add "Auto ping" checkbox * Put `/socket-diagnostics` behind `AuthenticationController.requireLogin` * Set logs to `logger.info` when debugging * Add `publicId` and `clientId` to logs * Fix disconnect logs when debugging * Refresh UI every second. Display red "Ping Count" if unanswered for 3s * Update services/web/frontend/js/features/socket-diagnostics/components/socket-diagnostics.tsx Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com> * Update services/web/frontend/js/features/socket-diagnostics/components/socket-diagnostics.tsx Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com> * `npm run format:fix` --------- Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com> GitOrigin-RevId: 9faf2abdac51fa4b87c67d8fe89c4125d01d826f
This commit is contained in:
@@ -127,7 +127,10 @@ module.exports = Router = {
|
||||
|
||||
if (client) {
|
||||
client.on('error', function (err) {
|
||||
logger.err({ clientErr: err }, 'socket.io client error')
|
||||
logger.err(
|
||||
{ clientErr: err, publicId: client.publicId, clientId: client.id },
|
||||
'socket.io client error'
|
||||
)
|
||||
if (client.connected) {
|
||||
client.emit('reconnectGracefully')
|
||||
client.disconnect()
|
||||
@@ -174,6 +177,7 @@ module.exports = Router = {
|
||||
|
||||
if (isDebugging) {
|
||||
client.connectedAt = Date.now()
|
||||
client.isDebugging = true
|
||||
}
|
||||
|
||||
if (!isDebugging) {
|
||||
@@ -205,10 +209,17 @@ module.exports = Router = {
|
||||
})
|
||||
metrics.gauge('socket-io.clients', io.sockets.clients().length)
|
||||
|
||||
logger.debug(
|
||||
{ session, clientId: client.id, isDebugging },
|
||||
'client connected'
|
||||
)
|
||||
const info = {
|
||||
session,
|
||||
publicId: client.publicId,
|
||||
clientId: client.id,
|
||||
isDebugging,
|
||||
}
|
||||
if (isDebugging) {
|
||||
logger.info(info, 'client connected')
|
||||
} else {
|
||||
logger.debug(info, 'client connected')
|
||||
}
|
||||
|
||||
let user
|
||||
if (session && session.passport && session.passport.user) {
|
||||
@@ -237,7 +248,10 @@ module.exports = Router = {
|
||||
return Router._handleInvalidArguments(client, 'debug', arguments)
|
||||
}
|
||||
|
||||
logger.debug({ clientId: client.id }, 'received debug message')
|
||||
logger.info(
|
||||
{ publicId: client.publicId, clientId: client.id },
|
||||
'received debug message'
|
||||
)
|
||||
|
||||
const response = {
|
||||
serverTime: Date.now(),
|
||||
@@ -281,7 +295,10 @@ module.exports = Router = {
|
||||
if (client.isDebugging) {
|
||||
const duration = Date.now() - client.connectedAt
|
||||
metrics.timing('socket-io.debugging.duration', duration)
|
||||
logger.debug({ duration }, 'debug client disconnected')
|
||||
logger.info(
|
||||
{ duration, publicId: client.publicId, clientId: client.id },
|
||||
'debug client disconnected'
|
||||
)
|
||||
}
|
||||
|
||||
WebsocketController.leaveProject(io, client, function (err) {
|
||||
|
||||
@@ -232,7 +232,11 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||
|
||||
webRouter.get('/account-suspended', UserPagesController.accountSuspended)
|
||||
|
||||
webRouter.get('/socket-diagnostics', SocketDiagnostics.index)
|
||||
webRouter.get(
|
||||
'/socket-diagnostics',
|
||||
AuthenticationController.requireLogin(),
|
||||
SocketDiagnostics.index
|
||||
)
|
||||
|
||||
if (Settings.enableLegacyLogin) {
|
||||
AuthenticationController.addEndpointToLoginWhitelist('/login/legacy')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import type { ConnectionStatus } from './types'
|
||||
import { useSocketManager } from './use-socket-manager'
|
||||
import {
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from './diagnostic-component'
|
||||
import { Container } from 'react-bootstrap-5'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox'
|
||||
|
||||
type NetworkInformation = {
|
||||
downlink: number
|
||||
@@ -38,9 +39,26 @@ const NavigatorInfo = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const useCurrentTime = () => {
|
||||
const [time, setTime] = React.useState(new Date())
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => setTime(new Date()), 1000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
return time
|
||||
}
|
||||
|
||||
export const SocketDiagnostics = () => {
|
||||
const { socketState, debugInfo, disconnectSocket, forceReconnect, socket } =
|
||||
useSocketManager()
|
||||
const {
|
||||
socketState,
|
||||
debugInfo,
|
||||
disconnectSocket,
|
||||
forceReconnect,
|
||||
socket,
|
||||
autoping,
|
||||
setAutoping,
|
||||
} = useSocketManager()
|
||||
const now = useCurrentTime()
|
||||
|
||||
const getConnectionState = (): ConnectionStatus => {
|
||||
if (socketState.connected) return 'connected'
|
||||
@@ -49,9 +67,13 @@ export const SocketDiagnostics = () => {
|
||||
}
|
||||
|
||||
const lastReceivedS = debugInfo.lastReceived
|
||||
? Math.round((Date.now() - debugInfo.lastReceived) / 1000)
|
||||
? Math.round((now.getTime() - debugInfo.lastReceived) / 1000)
|
||||
: null
|
||||
|
||||
const isLate =
|
||||
!!debugInfo.unansweredSince &&
|
||||
now.getTime() - debugInfo.unansweredSince >= 3000
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<h1>Socket Diagnostics</h1>
|
||||
@@ -78,6 +100,12 @@ export const SocketDiagnostics = () => {
|
||||
<MaterialIcon type="speed" /> Connection Stats
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
<OLFormCheckbox
|
||||
label="Auto ping"
|
||||
id="autoping"
|
||||
checked={autoping}
|
||||
onChange={e => setAutoping(e.target.checked)}
|
||||
/>
|
||||
<DiagnosticItem
|
||||
icon="network_ping"
|
||||
label="Ping Count"
|
||||
@@ -92,13 +120,7 @@ export const SocketDiagnostics = () => {
|
||||
)}
|
||||
</>
|
||||
}
|
||||
type={
|
||||
lastReceivedS !== null
|
||||
? lastReceivedS < 4
|
||||
? 'success'
|
||||
: 'danger'
|
||||
: undefined
|
||||
}
|
||||
type={isLate === null ? undefined : isLate ? 'danger' : 'success'}
|
||||
/>
|
||||
|
||||
<DiagnosticItem
|
||||
@@ -117,7 +139,7 @@ export const SocketDiagnostics = () => {
|
||||
}
|
||||
type={
|
||||
debugInfo.latency
|
||||
? debugInfo.latency < 150
|
||||
? debugInfo.latency < 450
|
||||
? 'success'
|
||||
: 'danger'
|
||||
: undefined
|
||||
@@ -149,7 +171,7 @@ export const SocketDiagnostics = () => {
|
||||
<DiagnosticItem
|
||||
icon="schedule"
|
||||
label="Current time"
|
||||
value={new Date().toUTCString()}
|
||||
value={now.toUTCString()}
|
||||
/>
|
||||
<DiagnosticItem
|
||||
icon="hourglass"
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface DebugInfo {
|
||||
clockDelta: number | null
|
||||
onLine: boolean | null
|
||||
client: Client | null
|
||||
unansweredSince: number | null
|
||||
lastReceived: number | null
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { DebugInfo, SocketState } from './types'
|
||||
|
||||
export function useSocketManager() {
|
||||
const [socket, setSocket] = useState<Socket | null>(null)
|
||||
const [autoping, setAutoping] = useState<boolean>(false)
|
||||
|
||||
const [socketState, setSocketState] = useState<SocketState>({
|
||||
connected: false,
|
||||
@@ -20,6 +21,7 @@ export function useSocketManager() {
|
||||
onLine: null,
|
||||
clockDelta: null,
|
||||
client: null,
|
||||
unansweredSince: null,
|
||||
lastReceived: null,
|
||||
})
|
||||
|
||||
@@ -65,30 +67,46 @@ export function useSocketManager() {
|
||||
connectSocket()
|
||||
}, [connectSocket])
|
||||
|
||||
const sendPing = useCallback(() => {
|
||||
if (socket?.socket.connected) {
|
||||
const time = Date.now()
|
||||
setDebugInfo(prev => ({
|
||||
...prev,
|
||||
sent: prev.sent + 1,
|
||||
unansweredSince: prev.unansweredSince ?? time,
|
||||
}))
|
||||
socket.emit('debug', { time }, (info: any) => {
|
||||
const beforeTime = info.data.time
|
||||
const now = Date.now()
|
||||
const latency = now - beforeTime
|
||||
const clockDelta = (beforeTime + beforeTime) / 2 - info.serverTime
|
||||
setDebugInfo(prev => ({
|
||||
...prev,
|
||||
received: prev.received + 1,
|
||||
latency,
|
||||
maxLatency: Math.max(prev.maxLatency ?? 0, latency),
|
||||
clockDelta,
|
||||
client: info.client,
|
||||
lastReceived: now,
|
||||
unansweredSince: null,
|
||||
}))
|
||||
})
|
||||
}
|
||||
}, [socket])
|
||||
|
||||
useEffect(() => {
|
||||
if (!socket || !autoping) return
|
||||
|
||||
const statsInterval = setInterval(sendPing, 2000)
|
||||
|
||||
return () => {
|
||||
clearInterval(statsInterval)
|
||||
}
|
||||
}, [socket, autoping, sendPing])
|
||||
|
||||
useEffect(() => {
|
||||
if (!socket) return
|
||||
|
||||
const statsInterval = setInterval(() => {
|
||||
if (socket.socket.connected) {
|
||||
setDebugInfo(prev => ({ ...prev, sent: prev.sent + 1 }))
|
||||
socket.emit('debug', { time: Date.now() }, (info: any) => {
|
||||
const beforeTime = info.data.time
|
||||
const now = Date.now()
|
||||
const latency = now - beforeTime
|
||||
const clockDelta = (beforeTime + beforeTime) / 2 - info.serverTime
|
||||
setDebugInfo(prev => ({
|
||||
...prev,
|
||||
received: prev.received + 1,
|
||||
latency,
|
||||
maxLatency: Math.max(prev.maxLatency ?? 0, latency),
|
||||
clockDelta,
|
||||
client: info.client,
|
||||
lastReceived: now,
|
||||
}))
|
||||
})
|
||||
}
|
||||
}, 2000)
|
||||
|
||||
socket.on('connect', () => {
|
||||
setSocketState(prev => ({
|
||||
...prev,
|
||||
@@ -97,6 +115,7 @@ export function useSocketManager() {
|
||||
lastSuccess: Date.now(),
|
||||
lastError: '',
|
||||
}))
|
||||
sendPing()
|
||||
})
|
||||
|
||||
socket.on('disconnect', (reason: string) => {
|
||||
@@ -119,16 +138,13 @@ export function useSocketManager() {
|
||||
socket.socket.connect()
|
||||
|
||||
return () => {
|
||||
clearInterval(statsInterval)
|
||||
socket.disconnect()
|
||||
}
|
||||
}, [socket])
|
||||
}, [sendPing, socket])
|
||||
|
||||
useEffect(() => {
|
||||
const updateNetworkInfo = () => {
|
||||
if ('connection' in navigator) {
|
||||
setDebugInfo(prev => ({ ...prev, onLine: navigator.onLine }))
|
||||
}
|
||||
setDebugInfo(prev => ({ ...prev, onLine: navigator.onLine }))
|
||||
}
|
||||
|
||||
window.addEventListener('online', updateNetworkInfo)
|
||||
@@ -148,5 +164,7 @@ export function useSocketManager() {
|
||||
disconnectSocket,
|
||||
forceReconnect,
|
||||
socket,
|
||||
autoping,
|
||||
setAutoping,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user