From 8faa123eb3b052d96f379fdb149c005a02dd5dcf Mon Sep 17 00:00:00 2001 From: fossephate Date: Mon, 23 Sep 2024 17:02:43 -0700 Subject: [PATCH 1/2] sync status eta --- cw_core/lib/sync_status.dart | 113 ++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 3 deletions(-) diff --git a/cw_core/lib/sync_status.dart b/cw_core/lib/sync_status.dart index 788309d8c6..fb9ec30027 100644 --- a/cw_core/lib/sync_status.dart +++ b/cw_core/lib/sync_status.dart @@ -1,6 +1,10 @@ abstract class SyncStatus { const SyncStatus(); double progress(); + + String formattedProgress() { + return "${(progress() * 100).toStringAsFixed(2)}%"; + } } class StartingScanSyncStatus extends SyncStatus { @@ -12,10 +16,12 @@ class StartingScanSyncStatus extends SyncStatus { } class SyncingSyncStatus extends SyncStatus { - SyncingSyncStatus(this.blocksLeft, this.ptc); + SyncingSyncStatus(this.blocksLeft, this.ptc) { + updateEtaHistory(blocksLeft); + } - final double ptc; - final int blocksLeft; + double ptc; + int blocksLeft; @override double progress() => ptc; @@ -28,10 +34,111 @@ class SyncingSyncStatus extends SyncStatus { final diff = track - (chainTip - syncHeight); final ptc = diff <= 0 ? 0.0 : diff / track; final left = chainTip - syncHeight; + updateEtaHistory(left + 1); // sum 1 because if at the chain tip, will say "0 blocks left" return SyncingSyncStatus(left + 1, ptc); } + + static void updateEtaHistory(int blocksLeft) { + blockHistory[DateTime.now()] = blocksLeft; + // keep only the last 25 entries + while (blockHistory.length > 25) { + blockHistory.remove(blockHistory.keys.first); + } + } + + static Map blockHistory = {}; + static Duration? lastEtaDuration; + + DateTime calculateEta() { + double rate = _calculateBlockRate(); + if (rate == 0) { + return DateTime.now().add(const Duration(days: 2)); + } + int remainingBlocks = this.blocksLeft; + double timeRemainingSeconds = remainingBlocks / rate; + return DateTime.now().add(Duration(seconds: timeRemainingSeconds.round())); + } + + Duration getEtaDuration() { + DateTime now = DateTime.now(); + DateTime? completionTime = calculateEta(); + return completionTime.difference(now); + } + + String? getFormattedEta() { + // throw out any entries that are more than a minute old: + blockHistory.removeWhere( + (key, value) => key.isBefore(DateTime.now().subtract(const Duration(minutes: 1)))); + + // don't show eta if we don't have enough data: + if (blockHistory.length < 3) { + return null; + } + + Duration? duration = getEtaDuration(); + + // just show the block count if it's really long: + if (duration.inDays > 0) { + return null; + } + + // show the blocks count if the eta is less than a minute or we only have a few blocks left: + if (duration.inMinutes < 1 || blocksLeft < 1000) { + return null; + } + + // if our new eta is more than a minute off from the last one, only update the by 1 minute so it doesn't jump all over the place + if (lastEtaDuration != null) { + bool isIncreasing = duration.inSeconds > lastEtaDuration!.inSeconds; + bool diffMoreThanOneMinute = (duration.inSeconds - lastEtaDuration!.inSeconds).abs() > 60; + bool diffMoreThanOneHour = (duration.inSeconds - lastEtaDuration!.inSeconds).abs() > 3600; + if (diffMoreThanOneHour) { + duration = Duration(minutes: lastEtaDuration!.inMinutes + (isIncreasing ? 1 : -1)); + } else if (diffMoreThanOneMinute) { + duration = Duration(seconds: lastEtaDuration!.inSeconds + (isIncreasing ? 1 : -1)); + } else { + // if the diff is less than a minute don't change it: + duration = lastEtaDuration!; + } + } + + lastEtaDuration = duration; + + String twoDigits(int n) => n.toString().padLeft(2, '0'); + + final hours = twoDigits(duration.inHours); + final minutes = twoDigits(duration.inMinutes.remainder(60)); + final seconds = twoDigits(duration.inSeconds.remainder(60)); + if (hours == '00') { + return '${minutes}m${seconds}s'; + } + return '${hours}h${minutes}m${seconds}s'; + } + + // Calculate the rate of block processing (blocks per second) + double _calculateBlockRate() { + List timestamps = blockHistory.keys.toList(); + List blockCounts = blockHistory.values.toList(); + + double totalTime = 0; + int totalBlocksProcessed = 0; + + for (int i = 0; i < blockCounts.length - 1; i++) { + int blocksProcessed = blockCounts[i] - blockCounts[i + 1]; + Duration timeDifference = timestamps[i + 1].difference(timestamps[i]); + totalTime += timeDifference.inMicroseconds; + totalBlocksProcessed += blocksProcessed; + } + + if (totalTime == 0 || totalBlocksProcessed == 0) { + return 0; + } + + double blocksPerSecond = totalBlocksProcessed / (totalTime / 1000000); + return blocksPerSecond; + } } class SyncedSyncStatus extends SyncStatus { From 26267ad200b5b195c03d3f786c5de0a131a9a47a Mon Sep 17 00:00:00 2001 From: fossephate Date: Tue, 24 Sep 2024 08:38:22 -0700 Subject: [PATCH 2/2] other necessary changes --- lib/core/sync_status_title.dart | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/core/sync_status_title.dart b/lib/core/sync_status_title.dart index 4582f7b1ff..f86d88c945 100644 --- a/lib/core/sync_status_title.dart +++ b/lib/core/sync_status_title.dart @@ -3,9 +3,17 @@ import 'package:cw_core/sync_status.dart'; String syncStatusTitle(SyncStatus syncStatus) { if (syncStatus is SyncingSyncStatus) { - return syncStatus.blocksLeft == 1 - ? S.current.block_remaining - : S.current.Blocks_remaining('${syncStatus.blocksLeft}'); + if (syncStatus.blocksLeft == 1) { + return S.current.block_remaining; + } + + String eta = syncStatus.getFormattedEta() ?? ''; + + if (eta.isEmpty) { + return S.current.Blocks_remaining('${syncStatus.blocksLeft}'); + } else { + return "${syncStatus.formattedProgress()} - $eta"; + } } if (syncStatus is SyncedTipSyncStatus) {