@@ -16,7 +16,8 @@ import { hardExitIfEnabled } from "../env/hard-exit.js";
1616import { createLogger , getLogger , setGlobalLogger } from "../util/logger.js" ;
1717import { sanitizeFilename } from "../util/sanitize.js" ;
1818import { attachRawLogger } from "./raw_tap.js" ;
19- import { humanBytes } from "../util/format.js" ;
19+ import { humanBytes , formatDuration } from "../util/format.js" ;
20+ import { formatSummary , humanThroughput } from "../util/transfer-summary.js" ;
2021import { formatProgressLine } from "../util/progress.js" ;
2122import { closeTransport , waitForPeerClose } from "../util/rtc.js" ;
2223import { MAX_STREAM_CHUNK_BYTES } from "../transfer/constants.js" ;
@@ -85,22 +86,27 @@ export async function run(paths, opts, ctx = {}) {
8586 sendNameHint = opts ?. name ? String ( opts . name ) : `${ stem } .tar` ;
8687 getLogger ( ) . debug ( `send: tarball name set to ${ sendNameHint } ` ) ;
8788 }
88- // Progress UI
89- let startedAt = null ;
89+ // Progress UI + metrics
90+ const commandStartedAt = Date . now ( ) ;
91+ let transferStartedAt = null ;
92+ let lastProgressAt = null ;
9093 let lastTick = 0 ;
9194 function onProgress ( sent , total ) {
9295 getLogger ( ) . debug ( `OnProgress: sent=${ sent } total=${ total } ` ) ;
9396 const now = Date . now ( ) ;
94- if ( startedAt == null && sent > 0 ) {
95- startedAt = now ;
97+ if ( transferStartedAt == null && sent > 0 ) {
98+ transferStartedAt = now ;
99+ }
100+ if ( sent > 0 || ( total != null && sent >= total ) ) {
101+ lastProgressAt = now ;
96102 }
97103 if ( now - lastTick < 120 && sent !== total ) return ;
98104 lastTick = now ;
99105 if ( process . stderr . isTTY ) {
100106 const msg = formatProgressLine ( {
101107 doneBytes : sent ,
102108 totalBytes : total ,
103- startedAt : startedAt ?? now ,
109+ startedAt : transferStartedAt ?? commandStartedAt ,
104110 } ) ;
105111 readline . clearLine ( process . stderr , 0 ) ;
106112 readline . cursorTo ( process . stderr , 0 ) ;
@@ -122,6 +128,7 @@ export async function run(paths, opts, ctx = {}) {
122128 // Attach low-level logger BEFORE wiring auth/stream
123129 const detachTap = attachRawLogger ( rtc , { label : "low" } ) ;
124130 getLogger ( ) . debug ( "send: RTC connected" ) ;
131+ let peerCloseObservedAt = null ;
125132 try {
126133 signal . send ?. ( { type : "telemetry" , event : "ice-connected" , sessionId } ) ;
127134 getLogger ( ) . debug ( "send: announced telemetry ice-connected" ) ;
@@ -166,7 +173,7 @@ export async function run(paths, opts, ctx = {}) {
166173 assumeYes : ! ! opts . yes ,
167174 } ) ;
168175 }
169-
176+ lastProgressAt = Date . now ( ) ;
170177 try {
171178 onProgress ( totalBytes , totalBytes ) ;
172179 } catch { }
@@ -201,11 +208,13 @@ export async function run(paths, opts, ctx = {}) {
201208 } else if ( finAckResult === "nack" ) {
202209 finAckError = "send: receiver reported failure while acknowledging FIN" ;
203210 getLogger ( ) . info ( finAckError ) ;
211+ peerCloseObservedAt = Date . now ( ) ;
204212 }
205213 }
206214
207215 if ( finAckResult !== "peer-close" ) {
208216 const peerCloseResult = await waitForPeerClose ( rtc , peerCloseTimeoutMs ) ;
217+ peerCloseObservedAt = Date . now ( ) ;
209218 if (
210219 peerCloseResult === "timeout" &&
211220 Number . isFinite ( peerCloseTimeoutMs ) &&
@@ -223,7 +232,22 @@ export async function run(paths, opts, ctx = {}) {
223232 throw err ;
224233 }
225234
226- process . stderr . write ( "\nDone • " + humanBytes ( totalBytes ) + "\n" ) ;
235+ const completedAt = peerCloseObservedAt ?? Date . now ( ) ;
236+ const dataStart = transferStartedAt ?? commandStartedAt ;
237+ const dataEnd = lastProgressAt ?? completedAt ;
238+ const totalDurationMs = Math . max ( 0 , completedAt - commandStartedAt ) ;
239+ const dataDurationMs = Math . max ( 0 , dataEnd - dataStart ) ;
240+ const handshakeWaitMs = Math . max ( 0 , dataStart - commandStartedAt ) ;
241+ const peerCloseWaitMs = Math . max ( 0 , completedAt - dataEnd ) ;
242+ const summary = formatSummary ( {
243+ rows : [
244+ { label : "Bytes" , value : humanBytes ( totalBytes ) } ,
245+ { label : "Duration" , value : formatDuration ( totalDurationMs ) } ,
246+ { label : "Throughput" , value : humanThroughput ( totalBytes , dataDurationMs ) } ,
247+ { label : "Handshake wait" , value : formatDuration ( handshakeWaitMs ) } ,
248+ ] ,
249+ } ) ;
250+ process . stderr . write ( summary + "\n" ) ;
227251 // --- Silent workaround on success
228252 } finally {
229253 // Hard, handler-safe close sequence
0 commit comments