@@ -21,6 +21,7 @@ import { computeSSHProperties, sshSupportsSetEnv } from "./sshSupport"
2121import { Storage } from "./storage"
2222import { AuthorityPrefix , expandPath , parseRemoteAuthority } from "./util"
2323import { WorkspaceMonitor } from "./workspaceMonitor"
24+ import { getMemoryLogger } from "./memoryLogger"
2425
2526export interface RemoteDetails extends vscode . Disposable {
2627url :string
@@ -688,9 +689,20 @@ export class Remote {
688689// showNetworkUpdates finds the SSH process ID that is being used by this
689690// workspace and reads the file being created by the Coder CLI.
690691private showNetworkUpdates ( sshPid :number ) :vscode . Disposable {
692+ const logger = getMemoryLogger ( )
693+ logger . trackResourceCreated ( "NetworkStatusBar" )
694+ logger . info ( `Starting network updates monitor for SSH PID:${ sshPid } ` )
695+
691696const networkStatus = vscode . window . createStatusBarItem ( vscode . StatusBarAlignment . Left , 1000 )
697+ networkStatus . name = "Coder Workspace Update"
692698const networkInfoFile = path . join ( this . storage . getNetworkInfoPath ( ) , `${ sshPid } .json` )
693699
700+ logger . debug ( `Network info file path:${ networkInfoFile } ` )
701+
702+ let refreshCount = 0
703+ let disposed = false
704+ let lastFileSize = 0
705+
694706const updateStatus = ( network :{
695707p2p :boolean
696708latency :number
@@ -699,78 +711,117 @@ export class Remote {
699711upload_bytes_sec :number
700712download_bytes_sec :number
701713} ) => {
702- let statusText = "$(globe) "
703- if ( network . p2p ) {
704- statusText += "Direct "
705- networkStatus . tooltip = "You're connected peer-to-peer ✨."
706- } else {
707- statusText += network . preferred_derp + " "
708- networkStatus . tooltip =
709- "You're connected through a relay 🕵.\nWe'll switch over to peer-to-peer when available."
710- }
711- networkStatus . tooltip +=
712- "\n\nDownload ↓ " +
713- prettyBytes ( network . download_bytes_sec , {
714- bits :true ,
715- } ) +
716- "/s • Upload ↑ " +
717- prettyBytes ( network . upload_bytes_sec , {
718- bits :true ,
719- } ) +
720- "/s\n"
721-
722- if ( ! network . p2p ) {
723- const derpLatency = network . derp_latency [ network . preferred_derp ]
724-
725- networkStatus . tooltip += `You ↔${ derpLatency . toFixed ( 2 ) } ms ↔${ network . preferred_derp } ↔${ ( network . latency - derpLatency ) . toFixed ( 2 ) } ms ↔ Workspace`
726-
727- let first = true
728- Object . keys ( network . derp_latency ) . forEach ( ( region ) => {
729- if ( region === network . preferred_derp ) {
730- return
731- }
732- if ( first ) {
733- networkStatus . tooltip += `\n\nOther regions:`
734- first = false
735- }
736- networkStatus . tooltip += `\n${ region } :${ Math . round ( network . derp_latency [ region ] * 100 ) / 100 } ms`
737- } )
738- }
714+ try {
715+ let statusText = "$(globe) "
716+ if ( network . p2p ) {
717+ statusText += "Direct "
718+ networkStatus . tooltip = "You're connected peer-to-peer ✨."
719+ } else {
720+ statusText += network . preferred_derp + " "
721+ networkStatus . tooltip =
722+ "You're connected through a relay 🕵.\nWe'll switch over to peer-to-peer when available."
723+ }
724+ networkStatus . tooltip +=
725+ "\n\nDownload ↓ " +
726+ prettyBytes ( network . download_bytes_sec , {
727+ bits :true ,
728+ } ) +
729+ "/s • Upload ↑ " +
730+ prettyBytes ( network . upload_bytes_sec , {
731+ bits :true ,
732+ } ) +
733+ "/s\n"
734+
735+ if ( ! network . p2p ) {
736+ const derpLatency = network . derp_latency [ network . preferred_derp ]
737+
738+ networkStatus . tooltip += `You ↔${ derpLatency . toFixed ( 2 ) } ms ↔${ network . preferred_derp } ↔${ (
739+ network . latency - derpLatency
740+ ) . toFixed ( 2 ) } ms ↔ Workspace`
741+
742+ let first = true
743+ Object . keys ( network . derp_latency ) . forEach ( ( region ) => {
744+ if ( region === network . preferred_derp ) {
745+ return
746+ }
747+ if ( first ) {
748+ networkStatus . tooltip += `\n\nOther regions:`
749+ first = false
750+ }
751+ networkStatus . tooltip += `\n${ region } :${ Math . round ( network . derp_latency [ region ] * 100 ) / 100 } ms`
752+ } )
753+ }
739754
740- statusText += "(" + network . latency . toFixed ( 2 ) + "ms)"
741- networkStatus . text = statusText
742- networkStatus . show ( )
755+ statusText += "(" + network . latency . toFixed ( 2 ) + "ms)"
756+ networkStatus . text = statusText
757+ networkStatus . show ( )
758+
759+ // Log occasional network stats updates (every 20 refreshes)
760+ if ( refreshCount % 20 === 0 ) {
761+ logger . debug (
762+ `Network stats update #${ refreshCount } : p2p=${ network . p2p } , latency=${ network . latency . toFixed ( 2 ) } ms` ,
763+ )
764+ }
765+ } catch ( ex ) {
766+ // Replace silent error ignoring with proper logging
767+ logger . error ( "Error updating network status" , ex )
768+ }
743769}
744- let disposed = false
770+
745771const periodicRefresh = ( ) => {
746772if ( disposed ) {
773+ logger . debug ( "Network updates: Skipping refresh as disposed=true" )
747774return
748775}
776+
777+ refreshCount ++
778+
779+ // Log every 100 refreshes to track long-term operation
780+ if ( refreshCount % 100 === 0 ) {
781+ logger . info ( `Network updates: Completed${ refreshCount } refresh cycles for SSH PID:${ sshPid } ` )
782+ logger . logMemoryUsage ( "NETWORK_REFRESH" )
783+ }
784+
749785fs . readFile ( networkInfoFile , "utf8" )
750786. then ( ( content ) => {
787+ const currentSize = content . length
788+ if ( lastFileSize !== currentSize ) {
789+ logger . debug ( `Network info file size changed:${ lastFileSize } ->${ currentSize } bytes` )
790+ lastFileSize = currentSize
791+ }
751792return JSON . parse ( content )
752793} )
753794. then ( ( parsed ) => {
754795try {
755796updateStatus ( parsed )
756797} catch ( ex ) {
757- // Ignore
798+ logger . error ( `Failed to update status from parsed network info` , ex )
758799}
759800} )
760- . catch ( ( ) => {
761- // TODO: Log a failure here!
801+ . catch ( ( error ) => {
802+ // Replace empty catch with proper error logging
803+ logger . error ( `Failed to read or parse network info file:${ networkInfoFile } ` , error )
762804} )
763805. finally ( ( ) => {
764806// This matches the write interval of `coder vscodessh`.
765- setTimeout ( periodicRefresh , 3000 )
807+ if ( ! disposed ) {
808+ setTimeout ( periodicRefresh , 3000 )
809+ }
766810} )
767811}
812+
813+ // Log the first refresh
814+ logger . debug ( `Starting initial network refresh for SSH PID:${ sshPid } ` )
768815periodicRefresh ( )
769816
770817return {
771818dispose :( ) => {
772- disposed = true
773- networkStatus . dispose ( )
819+ if ( ! disposed ) {
820+ logger . info ( `Disposing network updates monitor for SSH PID:${ sshPid } after${ refreshCount } refreshes` )
821+ disposed = true
822+ networkStatus . dispose ( )
823+ logger . trackResourceDisposed ( "NetworkStatusBar" )
824+ }
774825} ,
775826}
776827}