Compare commits

..

No commits in common. "4786c58400af0490be703f07008dfb7adfb8c586" and "8fc1d89d9e8d34460ff19990b434b2448aadd993" have entirely different histories.

2 changed files with 24 additions and 46 deletions

View File

@ -274,8 +274,8 @@ class FragmentFD(FileDownloader):
progress.thread_reset() progress.thread_reset()
state['downloaded_bytes'] = ctx['complete_frags_downloaded_bytes'] = progress.downloaded state['downloaded_bytes'] = ctx['complete_frags_downloaded_bytes'] = progress.downloaded
state['speed'] = ctx['speed'] = progress.speed.smooth ctx['speed'] = state['speed'] = progress.smooth_speed
state['eta'] = progress.eta.smooth state['eta'] = progress.eta
self._hook_progress(state, info_dict) self._hook_progress(state, info_dict)

View File

@ -6,24 +6,21 @@ import time
class ProgressCalculator: class ProgressCalculator:
# Time to calculate the speed over (seconds) # Time to calculate the speed over (in nanoseconds)
SAMPLING_WINDOW = 3 SAMPLING_WINDOW = 1_000_000_000
# Minimum timeframe before to sample next downloaded bytes (seconds) # Factor for the exponential moving average (from 0 = prev to 1 = current)
SAMPLING_RATE = 0.05 SMOOTHING_FACTOR = 0.3
# Time before showing eta (seconds)
GRACE_PERIOD = 1
def __init__(self, initial: int): def __init__(self, initial: int):
self._initial = initial or 0 self.downloaded = initial or 0
self.downloaded = self._initial
self.elapsed: float = 0 self.elapsed: float = 0
self.speed = SmoothValue(0, smoothing=0.7) self.speed: float = 0
self.eta = SmoothValue(None, smoothing=0.9) self.smooth_speed: int = 0
self.eta: float | None = None
self._total = 0 self._total = None
self._start_time = time.monotonic() self._start_time = time.monotonic_ns()
self._last_update = self._start_time
self._lock = threading.Lock() self._lock = threading.Lock()
self._thread_sizes: dict[int, int] = {} self._thread_sizes: dict[int, int] = {}
@ -38,7 +35,9 @@ class ProgressCalculator:
@total.setter @total.setter
def total(self, value: int | None): def total(self, value: int | None):
with self._lock: with self._lock:
if value is not None and value < self.downloaded: if not value:
value = None
elif value < self.downloaded:
value = self.downloaded value = self.downloaded
self._total = value self._total = value
@ -60,50 +59,29 @@ class ProgressCalculator:
self._update(size - last_size) self._update(size - last_size)
def _update(self, size: int): def _update(self, size: int):
current_time = time.monotonic() current_time = time.monotonic_ns()
self.downloaded += size self.downloaded += size
self.elapsed = current_time - self._start_time self.elapsed = (current_time - self._start_time) / 1_000_000_000
if self.total is not None and self.downloaded > self.total: if self.total is not None and self.downloaded > self.total:
self._total = self.downloaded self._total = self.downloaded
if self._last_update + self.SAMPLING_RATE > current_time:
return
self._last_update = current_time
self._times.append(current_time) self._times.append(current_time)
self._downloaded.append(self.downloaded) self._downloaded.append(self.downloaded)
offset = bisect.bisect_left(self._times, current_time - self.SAMPLING_WINDOW) offset = bisect.bisect_left(self._times, current_time - self.SAMPLING_WINDOW)
del self._times[:offset] del self._times[:offset]
del self._downloaded[:offset] del self._downloaded[:offset]
if len(self._times) < 2:
self.speed.reset()
self.eta.reset()
return
download_time = current_time - self._times[0] download_time = current_time - self._times[0]
if not download_time: if not download_time:
return return
downloaded_bytes = self.downloaded - self._downloaded[0]
self.speed.set((self.downloaded - self._downloaded[0]) / download_time) self.speed = downloaded_bytes * 1_000_000_000 / download_time
if self.total and self.speed.value and self.elapsed > self.GRACE_PERIOD: self.smooth_speed = int(self.SMOOTHING_FACTOR * self.speed + (1 - self.SMOOTHING_FACTOR) * self.smooth_speed)
self.eta.set((self.total - self.downloaded) / self.speed.value)
else:
self.eta.reset()
if not self.total:
class SmoothValue: self.eta = None
def __init__(self, initial: float | None, smoothing: float): elif self.speed:
self.value = self.smooth = self._initial = initial self.eta = (self.total - self.downloaded) / self.speed
self._smoothing = smoothing
def set(self, value: float):
self.value = value
if self.smooth is None:
self.smooth = self.value
else:
self.smooth = (1 - self._smoothing) * value + self._smoothing * self.smooth
def reset(self):
self.value = self.smooth = self._initial