Skip to content

Commit 397c28f

Browse files
authored
Merge pull request #303 from Duke-GCB/302-upload-progress-bar-speed-v2
Show speed in upload progress
2 parents 259f337 + e831a39 commit 397c28f

6 files changed

Lines changed: 68 additions & 40 deletions

File tree

ddsc/core/download.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import queue
88
from ddsc.core.localstore import HashUtil
99
from ddsc.core.ddsapi import DDS_TOTAL_HEADER
10-
from ddsc.core.util import humanize_bytes
10+
from ddsc.core.util import humanize_bytes, transfer_speed_str
1111

1212
SWIFT_EXPIRED_STATUS_CODE = 401
1313
S3_EXPIRED_STATUS_CODE = 403
@@ -324,11 +324,11 @@ def make_spinner_char(self, current_time):
324324
return self.spinner_chars[half_seconds % 4]
325325

326326
def make_download_speed(self, current_time, total_bytes_downloaded):
327-
elapsed_seconds = current_time - self.start_time
328-
if elapsed_seconds > 0 and total_bytes_downloaded > 0:
329-
bytes_per_second = float(total_bytes_downloaded) / (elapsed_seconds + 0.5)
330-
return '@ {}/s'.format(humanize_bytes(bytes_per_second))
331-
return ''
327+
return transfer_speed_str(
328+
current_time=current_time,
329+
start_time=self.start_time,
330+
transferred_bytes=total_bytes_downloaded
331+
)
332332

333333
def get_download_progress(self):
334334
files_downloaded = 0

ddsc/core/fileuploader.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ def send(self):
403403
while sent_chunks != self.num_chunks_to_send:
404404
chunk = infile.read(self.chunk_size)
405405
self._send_chunk(chunk, chunk_num)
406-
self.progress_queue.processed(1)
406+
self.progress_queue.processed((1, len(chunk)))
407407
chunk_num += 1
408408
sent_chunks += 1
409409

ddsc/core/projectuploader.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ def after_run(self, remote_file_data):
453453
self.local_file.set_remote_values_after_send(remote_file_id,
454454
remote_hash_dict['algorithm'],
455455
remote_hash_dict['value'])
456-
self.settings.watcher.transferring_item(self.local_file)
456+
self.settings.watcher.transferring_item(self.local_file, transferred_bytes=self.local_file.size)
457457
else:
458458
self.settings.watcher.increment_progress()
459459

ddsc/core/tests/test_projectuploader.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,4 +373,4 @@ def test_after_run_when_file_is_sent(self):
373373
cmd.after_run(remote_file_data)
374374
cmd.file_upload_post_processor.run.assert_called_with(cmd.settings.data_service, remote_file_data)
375375
cmd.local_file.set_remote_values_after_send.assert_called_with('abc123', 'md5', 'abcdefg')
376-
cmd.settings.watcher.transferring_item.assert_called_with(cmd.local_file)
376+
cmd.settings.watcher.transferring_item.assert_called_with(cmd.local_file, transferred_bytes=cmd.local_file.size)

ddsc/core/tests/test_util.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,66 +26,70 @@ def test_verify_terminal_encoding_none_is_ok(self):
2626

2727
class TestProgressBar(TestCase):
2828
@patch('ddsc.core.util.sys.stdout')
29-
def test_show_no_waiting(self, mock_stdout):
29+
@patch('ddsc.core.util.transfer_speed_str')
30+
def test_show_no_waiting(self, mock_transfer_speed_str, mock_stdout):
3031
progress_bar = ProgressBar()
32+
mock_transfer_speed_str.return_value = ' @ 100 MB/s'
3133

3234
# replace line with our progress
33-
progress_bar.update(percent_done=0, details='sending really_long_filename.txt')
35+
progress_bar.update(percent_done=0, details='sending really_long_filename.txt', transferred_bytes=0)
3436
progress_bar.show()
35-
expected = '\rProgress: 0% - sending really_long_filename.txt'
37+
expected = '\rProgress: 0% @ 100 MB/s - sending really_long_filename.txt'
3638
mock_stdout.write.assert_called_with(expected)
3739

3840
# replace line with our progress (make sure it is long enough to blank out previous line)
39-
progress_bar.update(percent_done=10, details='sending short.txt')
41+
progress_bar.update(percent_done=10, details='sending short.txt', transferred_bytes=100)
4042
progress_bar.show()
41-
expected = '\rProgress: 10% - sending short.txt '
43+
expected = '\rProgress: 10% @ 100 MB/s - sending short.txt '
4244
mock_stdout.write.assert_called_with(expected)
4345

44-
progress_bar.update(percent_done=15, details='sending short.txt')
46+
progress_bar.update(percent_done=15, details='sending short.txt', transferred_bytes=50)
4547
progress_bar.show()
46-
expected = '\rProgress: 15% - sending short.txt '
48+
expected = '\rProgress: 15% @ 100 MB/s - sending short.txt '
4749
mock_stdout.write.assert_called_with(expected)
4850

4951
# we finish uploading(go to newline)
5052
progress_bar.set_state(ProgressBar.STATE_DONE)
5153
progress_bar.show()
52-
expected = '\rDone: 100% \n'
54+
expected = '\rDone: 100% @ 100 MB/s \n'
5355
mock_stdout.write.assert_called_with(expected)
5456

5557
@patch('ddsc.core.util.sys.stdout')
56-
def test_show_with_waiting(self, mock_stdout):
58+
@patch('ddsc.core.util.transfer_speed_str')
59+
def test_show_with_waiting(self, mock_transfer_speed_str, mock_stdout):
5760
progress_bar = ProgressBar()
61+
mock_transfer_speed_str.return_value = ' @ 100 MB/s'
5862

5963
# we make some progress
60-
progress_bar.update(percent_done=10, details='sending short.txt')
64+
progress_bar.update(percent_done=10, details='sending short.txt', transferred_bytes=100)
6165
progress_bar.show()
62-
expected = '\rProgress: 10% - sending short.txt'
66+
expected = '\rProgress: 10% @ 100 MB/s - sending short.txt'
6367
mock_stdout.write.assert_called_with(expected)
6468

6569
# we get stuck waiting
6670
progress_bar.wait_msg = "Waiting for project"
6771
progress_bar.set_state(ProgressBar.STATE_WAITING)
6872
progress_bar.show()
69-
expected = '\rProgress: 10% - Waiting for project'
73+
expected = '\rProgress: 10% @ 100 MB/s - Waiting for project'
7074
mock_stdout.write.assert_called_with(expected)
7175

7276
# waiting takes priority over progress updates
7377
# (we may be able to upload some folders while waiting to upload files)
74-
progress_bar.update(percent_done=15, details='sending short.txt')
78+
progress_bar.update(percent_done=15, details='sending short.txt', transferred_bytes=50)
7579
progress_bar.show()
76-
expected = '\rProgress: 15% - Waiting for project'
80+
expected = '\rProgress: 15% @ 100 MB/s - Waiting for project'
7781
mock_stdout.write.assert_called_with(expected)
7882

7983
# we are not longer waiting
8084
progress_bar.set_state(ProgressBar.STATE_RUNNING)
8185
progress_bar.show()
82-
expected = '\rProgress: 15% - sending short.txt '
86+
expected = '\rProgress: 15% @ 100 MB/s - sending short.txt '
8387
mock_stdout.write.assert_called_with(expected)
8488

8589
# we finish uploading(go to newline)
8690
progress_bar.set_state(ProgressBar.STATE_DONE)
8791
progress_bar.show()
88-
expected = '\rDone: 100% \n'
92+
expected = '\rDone: 100% @ 100 MB/s \n'
8993
mock_stdout.write.assert_called_with(expected)
9094

9195
@patch('ddsc.core.util.sys.stdout')
@@ -111,20 +115,20 @@ def test_show_waiting(self, mock_stdout):
111115

112116
class TestProgressPrinter(TestCase):
113117
@patch('ddsc.core.util.ProgressBar')
114-
def test_stuff(self, mock_progress_bar):
118+
def test_general_functionality(self, mock_progress_bar):
115119
progress_printer = ProgressPrinter(total=10, msg_verb='sending')
116120

117121
# pretend we just created a project
118122
mock_project = Mock(kind=KindType.project_str, path='')
119-
progress_printer.transferring_item(item=mock_project, increment_amt=1)
120-
mock_progress_bar.return_value.update.assert_called_with(10, 'sending project')
123+
progress_printer.transferring_item(item=mock_project, increment_amt=1, transferred_bytes=100)
124+
mock_progress_bar.return_value.update.assert_called_with(10, 100, 'sending project')
121125
mock_progress_bar.return_value.show.assert_called()
122126
mock_progress_bar.reset_mock()
123127

124128
# pretend we just created a folder
125129
mock_project = Mock(kind=KindType.folder_str, path='/data')
126-
progress_printer.transferring_item(item=mock_project, increment_amt=2)
127-
mock_progress_bar.return_value.update.assert_called_with(30, 'sending data')
130+
progress_printer.transferring_item(item=mock_project, increment_amt=2, transferred_bytes=200)
131+
mock_progress_bar.return_value.update.assert_called_with(30, 300, 'sending data')
128132
mock_progress_bar.return_value.show.assert_called()
129133
mock_progress_bar.reset_mock()
130134

@@ -141,7 +145,7 @@ def test_stuff(self, mock_progress_bar):
141145
# pretend we uploaded a file
142146
mock_project = Mock(kind=KindType.file_str, path='/data/log.txt')
143147
progress_printer.transferring_item(item=mock_project, increment_amt=2)
144-
mock_progress_bar.return_value.update.assert_called_with(50, 'sending log.txt')
148+
mock_progress_bar.return_value.update.assert_called_with(50, 300, 'sending log.txt')
145149
mock_progress_bar.return_value.show.assert_called()
146150
mock_progress_bar.reset_mock()
147151

@@ -181,10 +185,10 @@ def test_transferring_item_override_msg_verb(self, mock_os, mock_progress_bar):
181185
mock_os.path.basename.return_value = 'somefile.txt'
182186
progress_printer = ProgressPrinter(total=10, msg_verb='sending')
183187
progress_printer.transferring_item(Mock(), increment_amt=0)
184-
progress_bar.update.assert_called_with(0, "sending somefile.txt")
188+
progress_bar.update.assert_called_with(0, 0, "sending somefile.txt")
185189
progress_bar.reset_mock()
186190
progress_printer.transferring_item(Mock(), increment_amt=0, override_msg_verb='checking')
187-
progress_bar.update.assert_called_with(0, "checking somefile.txt")
191+
progress_bar.update.assert_called_with(0, 0, "checking somefile.txt")
188192

189193
def test_increment_progress(self):
190194
progress_printer = ProgressPrinter(total=10, msg_verb='sending')

ddsc/core/util.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
import platform
44
import stat
5+
import time
56

67
TERMINAL_ENCODING_NOT_UTF_ERROR = """
78
ERROR: DukeDSClient requires UTF terminal encoding.
@@ -70,8 +71,10 @@ def __init__(self, total, msg_verb):
7071
self.waiting = False
7172
self.msg_verb = msg_verb
7273
self.progress_bar = ProgressBar()
74+
self.transferred_bytes = 0
75+
self.total_bytes = 0
7376

74-
def transferring_item(self, item, increment_amt=1, override_msg_verb=None):
77+
def transferring_item(self, item, increment_amt=1, override_msg_verb=None, transferred_bytes=0):
7578
"""
7679
Update progress that item is about to be transferred.
7780
:param item: LocalFile, LocalFolder, or LocalContent(project) that is about to be sent.
@@ -87,7 +90,8 @@ def transferring_item(self, item, increment_amt=1, override_msg_verb=None):
8790
msg_verb = self.msg_verb
8891
if override_msg_verb:
8992
msg_verb = override_msg_verb
90-
self.progress_bar.update(percent_done, '{} {}'.format(msg_verb, details))
93+
self.transferred_bytes += transferred_bytes
94+
self.progress_bar.update(percent_done, self.transferred_bytes, '{} {}'.format(msg_verb, details))
9195
self.progress_bar.show()
9296

9397
def increment_progress(self, amt=1):
@@ -138,21 +142,26 @@ def __init__(self):
138142
self.line = ''
139143
self.state = self.STATE_RUNNING
140144
self.wait_msg = 'Waiting'
145+
self.transferred_bytes = 0
146+
self.start_time = time.time()
141147

142-
def update(self, percent_done, details):
148+
def update(self, percent_done, transferred_bytes, details):
143149
self.percent_done = percent_done
144150
self.current_item_details = details
151+
self.transferred_bytes = transferred_bytes
145152

146153
def set_state(self, state):
147154
self.state = state
148155

149156
def _get_line(self):
157+
speed = transfer_speed_str(current_time=time.time(), start_time=self.start_time,
158+
transferred_bytes=self.transferred_bytes)
150159
if self.state == self.STATE_DONE:
151-
return 'Done: 100%'
160+
return 'Done: 100%{}'.format(speed)
152161
details = self.current_item_details
153162
if self.state == self.STATE_WAITING:
154163
details = self.wait_msg
155-
return 'Progress: {}% - {}'.format(self.percent_done, details)
164+
return 'Progress: {}%{} - {}'.format(self.percent_done, speed, details)
156165

157166
def show(self):
158167
line = self._get_line()
@@ -344,8 +353,8 @@ def wait_for_processes(processes, size, progress_queue, watcher, item):
344353
while size > 0:
345354
progress_type, value = progress_queue.get()
346355
if progress_type == ProgressQueue.PROCESSED:
347-
chunk_size = value
348-
watcher.transferring_item(item, increment_amt=chunk_size)
356+
chunk_size, transferred_bytes = value
357+
watcher.transferring_item(item, increment_amt=chunk_size, transferred_bytes=transferred_bytes)
349358
size -= chunk_size
350359
elif progress_type == ProgressQueue.START_WAITING:
351360
watcher.start_waiting()
@@ -451,3 +460,18 @@ def join_with_commas_and_and(items):
451460
return ' and '.join([head_items_str, last_item])
452461
else:
453462
return last_item
463+
464+
465+
def transfer_speed_str(current_time, start_time, transferred_bytes):
466+
"""
467+
Return transfer speed str based
468+
:param current_time: float: current time
469+
:param start_time: float: starting time
470+
:param transferred_bytes: int: bytes transferred
471+
:return: str: end user str
472+
"""
473+
elapsed_seconds = current_time - start_time
474+
if elapsed_seconds > 0 and transferred_bytes > 0:
475+
bytes_per_second = float(transferred_bytes) / (elapsed_seconds + 0.5)
476+
return '@ {}/s'.format(humanize_bytes(bytes_per_second))
477+
return ''

0 commit comments

Comments
 (0)