@@ -25,6 +25,7 @@ import type { Api } from "coder/site/src/api/api";
2525import type { IncomingMessage } from "node:http" ;
2626
2727import type { Logger } from "../logging/logger" ;
28+ import type { TelemetryService } from "../telemetry/service" ;
2829
2930import type { CliCredentialManager } from "./cliCredentialManager" ;
3031import type { PathResolver } from "./pathResolver" ;
@@ -33,13 +34,26 @@ type ResolvedBinary =
3334 | { binPath : string ; stat : Stats ; source : "file-path" | "directory" }
3435 | { binPath : string ; source : "not-found" } ;
3536
37+ type CliDownloadReason = "missing" | "version_mismatch" ;
38+
39+ interface BinaryDownloadResult {
40+ binPath : string ;
41+ downloadedBytes ?: number ;
42+ }
43+
44+ interface DownloadResult {
45+ status : number ;
46+ downloadedBytes : number ;
47+ }
48+
3649export class CliManager {
3750 private readonly binaryLock : BinaryLock ;
3851
3952 constructor (
4053 private readonly output : Logger ,
4154 private readonly pathResolver : PathResolver ,
4255 private readonly cliCredentialManager : CliCredentialManager ,
56+ private readonly telemetry : TelemetryService ,
4357 ) {
4458 this . binaryLock = new BinaryLock ( output ) ;
4559 }
@@ -140,6 +154,8 @@ export class CliManager {
140154 ) ;
141155
142156 let existingVersion : string | null = null ;
157+ const downloadReason : CliDownloadReason =
158+ resolved . source === "not-found" ? "missing" : "version_mismatch" ;
143159 if ( resolved . source !== "not-found" ) {
144160 this . output . debug (
145161 "Existing binary size is" ,
@@ -224,13 +240,28 @@ export class CliManager {
224240 latestVersion = latestParsedVersion ;
225241 }
226242
227- await this . performBinaryDownload (
228- restClient ,
229- latestVersion ,
230- downloadBinPath ,
231- progressLogPath ,
243+ return await this . telemetry . trace (
244+ "cli.download" ,
245+ async ( span ) => {
246+ const recordDownloadedBytes = ( downloadedBytes : number ) : void => {
247+ if ( downloadedBytes > 0 ) {
248+ span . setMeasurement ( "downloadedBytes" , downloadedBytes ) ;
249+ }
250+ } ;
251+ const downloadResult = await this . performBinaryDownload (
252+ restClient ,
253+ latestVersion ,
254+ downloadBinPath ,
255+ progressLogPath ,
256+ recordDownloadedBytes ,
257+ ) ;
258+ if ( downloadResult . downloadedBytes !== undefined ) {
259+ recordDownloadedBytes ( downloadResult . downloadedBytes ) ;
260+ }
261+ return this . renameToFinalPath ( resolved , downloadResult . binPath ) ;
262+ } ,
263+ { reason : downloadReason } ,
232264 ) ;
233- return await this . renameToFinalPath ( resolved , downloadBinPath ) ;
234265 } catch ( error ) {
235266 const fallback = await this . handleAnyBinaryFailure (
236267 error ,
@@ -413,7 +444,8 @@ export class CliManager {
413444 parsedVersion : semver . SemVer ,
414445 binPath : string ,
415446 progressLogPath : string ,
416- ) : Promise < string > {
447+ recordDownloadedBytes : ( downloadedBytes : number ) => void ,
448+ ) : Promise < BinaryDownloadResult > {
417449 const cfg = vscode . workspace . getConfiguration ( "coder" ) ;
418450 const tempFile = tempFilePath ( binPath , "temp" ) ;
419451
@@ -449,6 +481,7 @@ export class CliManager {
449481 bytesDownloaded : number ,
450482 totalBytes : number | null ,
451483 ) => {
484+ recordDownloadedBytes ( bytesDownloaded ) ;
452485 await downloadProgress . writeProgress ( progressLogPath , {
453486 bytesDownloaded,
454487 totalBytes,
@@ -457,7 +490,7 @@ export class CliManager {
457490 } ;
458491
459492 const client = restClient . getAxiosInstance ( ) ;
460- const status = await this . download (
493+ const downloadResult = await this . download (
461494 client ,
462495 binSource ,
463496 writeStream ,
@@ -467,7 +500,7 @@ export class CliManager {
467500 onProgress ,
468501 ) ;
469502
470- switch ( status ) {
503+ switch ( downloadResult . status ) {
471504 case 200 : {
472505 await downloadProgress . writeProgress ( progressLogPath , {
473506 bytesDownloaded : 0 ,
@@ -480,27 +513,32 @@ export class CliManager {
480513 "Skipping binary signature verification due to settings" ,
481514 ) ;
482515 } else {
483- await this . verifyBinarySignatures ( client , tempFile , [
484- // A signature placed at the same level as the binary. It must be
485- // named exactly the same with an appended `.asc` (such as
486- // coder-windows-amd64.exe.asc or coder-linux-amd64.asc).
487- binSource + ".asc" ,
488- // The releases.coder.com bucket does not include the leading "v",
489- // and unlike what we get from buildinfo it uses a truncated version
490- // with only major.minor.patch. The signature name follows the same
491- // rule as above.
492- `https://releases.coder.com/coder-cli/${ parsedVersion . major } .${ parsedVersion . minor } .${ parsedVersion . patch } /${ binName } .asc` ,
493- ] ) ;
516+ await this . telemetry . trace ( "cli.verify" , ( ) =>
517+ this . verifyBinarySignatures ( client , tempFile , [
518+ // A signature placed at the same level as the binary. It must be
519+ // named exactly the same with an appended `.asc` (such as
520+ // coder-windows-amd64.exe.asc or coder-linux-amd64.asc).
521+ binSource + ".asc" ,
522+ // The releases.coder.com bucket does not include the leading "v",
523+ // and unlike what we get from buildinfo it uses a truncated version
524+ // with only major.minor.patch. The signature name follows the same
525+ // rule as above.
526+ `https://releases.coder.com/coder-cli/${ parsedVersion . major } .${ parsedVersion . minor } .${ parsedVersion . patch } /${ binName } .asc` ,
527+ ] ) ,
528+ ) ;
494529 }
495530
496531 // Replace existing binary (handles both renames + Windows lock)
497532 await this . replaceExistingBinary ( binPath , tempFile ) ;
498533
499- return binPath ;
534+ return {
535+ binPath,
536+ downloadedBytes : downloadResult . downloadedBytes ,
537+ } ;
500538 }
501539 case 304 : {
502540 this . output . info ( "Using existing binary since server returned a 304" ) ;
503- return binPath ;
541+ return { binPath } ;
504542 }
505543 case 404 : {
506544 vscode . window
@@ -537,7 +575,7 @@ export class CliManager {
537575 }
538576 const params = new URLSearchParams ( {
539577 title : `Failed to download binary on \`${ cliUtils . goos ( ) } -${ cliUtils . goarch ( ) } \`` ,
540- body : `Received status code \`${ status } \` when downloading the binary.` ,
578+ body : `Received status code \`${ downloadResult . status } \` when downloading the binary.` ,
541579 } ) ;
542580 const uri = vscode . Uri . parse (
543581 `https://github.com/coder/vscode-coder/issues/new?${ params . toString ( ) } ` ,
@@ -565,7 +603,7 @@ export class CliManager {
565603 bytesDownloaded : number ,
566604 totalBytes : number | null ,
567605 ) => Promise < void > ,
568- ) : Promise < number > {
606+ ) : Promise < DownloadResult > {
569607 const baseUrl = client . defaults . baseURL ;
570608
571609 const controller = new AbortController ( ) ;
@@ -583,6 +621,7 @@ export class CliManager {
583621 } ) ;
584622 this . output . info ( "Got status code" , resp . status ) ;
585623
624+ let written = 0 ;
586625 if ( resp . status === 200 ) {
587626 const rawContentLength = ( resp . headers [ "content-length" ] ??
588627 resp . headers [ "x-original-content-length" ] ) as unknown ;
@@ -599,9 +638,6 @@ export class CliManager {
599638 this . output . info ( "Got content length" , prettyBytes ( contentLength ) ) ;
600639 }
601640
602- // Track how many bytes were written.
603- let written = 0 ;
604-
605641 const completed = await vscode . window . withProgress < boolean > (
606642 {
607643 location : vscode . ProgressLocation . Notification ,
@@ -686,7 +722,10 @@ export class CliManager {
686722 this . output . info ( `Downloaded ${ prettyBytes ( written ) } ` ) ;
687723 }
688724
689- return resp . status ;
725+ return {
726+ status : resp . status ,
727+ downloadedBytes : written ,
728+ } ;
690729 }
691730
692731 /**
@@ -776,8 +815,8 @@ export class CliManager {
776815 this . output . info ( "Downloading signature from" , source ) ;
777816 const signaturePath = path . join ( cliPath + ".asc" ) ;
778817 const writeStream = createWriteStream ( signaturePath ) ;
779- const status = await this . download ( client , source , writeStream ) ;
780- if ( status === 200 ) {
818+ const downloadResult = await this . download ( client , source , writeStream ) ;
819+ if ( downloadResult . status === 200 ) {
781820 try {
782821 await pgp . verifySignature (
783822 publicKeys ,
@@ -806,7 +845,7 @@ export class CliManager {
806845 this . output . info ( "Binary will be ran anyway at user request" ) ;
807846 }
808847 }
809- return status ;
848+ return downloadResult . status ;
810849 }
811850
812851 /**
0 commit comments