Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sync status eta #1701

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 110 additions & 3 deletions cw_core/lib/sync_status.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
abstract class SyncStatus {
const SyncStatus();
double progress();

String formattedProgress() {
return "${(progress() * 100).toStringAsFixed(2)}%";
}
}

class StartingScanSyncStatus extends SyncStatus {
Expand All @@ -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;
Expand All @@ -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<DateTime, int> 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<DateTime> timestamps = blockHistory.keys.toList();
List<int> 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 {
Expand Down
14 changes: 11 additions & 3 deletions lib/core/sync_status_title.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading