|
32 | 32 | from sky import task as task_lib
|
33 | 33 | from sky.backends import backend_utils
|
34 | 34 | from sky.backends import wheel_utils
|
35 |
| -from sky.skylet import autostop_lib, job_lib, log_lib |
| 35 | +from sky.skylet import autostop_lib, job_lib, log_lib, log_utils |
36 | 36 |
|
37 | 37 | if typing.TYPE_CHECKING:
|
38 | 38 | from sky import dag
|
|
56 | 56 | _LOCK_FILENAME = '~/.sky/.{}.lock'
|
57 | 57 | _FILELOCK_TIMEOUT_SECONDS = 10
|
58 | 58 |
|
| 59 | +_RSYNC_DISPLAY_OPTION = '-Pavz' |
| 60 | +_RSYNC_FILTER_OPTION = '--filter=\'dir-merge,- .gitignore\'' |
| 61 | +_RSYNC_EXCLUDE_OPTION = '--exclude-from=.git/info/exclude' |
| 62 | + |
59 | 63 |
|
60 | 64 | def _check_cluster_name_is_valid(cluster_name: str) -> None:
|
61 | 65 | """Errors out on invalid cluster names not supported by cloud providers.
|
@@ -100,43 +104,18 @@ def _get_task_demands_dict(
|
100 | 104 | return accelerator_dict
|
101 | 105 |
|
102 | 106 |
|
103 |
| -def _path_size_megabytes(path: str, exclude_gitignore: bool = False) -> int: |
104 |
| - """Returns the size of 'path' (directory or file) in megabytes. |
105 |
| -
|
106 |
| - Args: |
107 |
| - path: The path to check. |
108 |
| - exclude_gitignore: If True, excludes files matched in .gitignore. |
109 |
| -
|
110 |
| - Returns: |
111 |
| - The size of 'path' in megabytes. |
112 |
| - """ |
113 |
| - if exclude_gitignore: |
114 |
| - try: |
115 |
| - # FIXME: add git index size (du -hsc .git) in this computation. |
116 |
| - awk_program = '{ sum += $1 } END { print sum }' |
117 |
| - return int( |
118 |
| - subprocess.check_output( |
119 |
| - f'( git status --short {path} | ' |
120 |
| - 'grep "^?" | cut -d " " -f2- ' |
121 |
| - f'&& git ls-files {path} ) | ' |
122 |
| - 'xargs -n 1000 du -hsk | ' |
123 |
| - f'awk {awk_program!r}', |
124 |
| - shell=True, |
125 |
| - stderr=subprocess.DEVNULL)) // (2**10) |
126 |
| - except (subprocess.CalledProcessError, ValueError): |
127 |
| - # If git is not installed, or if the user is not in a git repo. |
128 |
| - # Fall back to du -shk if it is not a git repo (size does not |
129 |
| - # consider .gitignore). |
130 |
| - logger.debug('Failed to get size with .gitignore exclusion, ' |
131 |
| - 'falling back to du -shk') |
132 |
| - pass |
133 |
| - return int( |
134 |
| - subprocess.check_output([ |
135 |
| - 'du', |
136 |
| - '-sh', |
137 |
| - '-k', |
138 |
| - path, |
139 |
| - ]).split()[0].decode('utf-8')) // (2**10) |
| 107 | +def _path_size_megabytes(path: str) -> int: |
| 108 | + """Returns the size of 'path' (directory or file) in megabytes.""" |
| 109 | + git_exclude_filter = '' |
| 110 | + if (pathlib.Path(path) / '.git/info/exclude').exists(): |
| 111 | + git_exclude_filter = f' {_RSYNC_EXCLUDE_OPTION}' |
| 112 | + rsync_output = str( |
| 113 | + subprocess.check_output( |
| 114 | + f'rsync {_RSYNC_DISPLAY_OPTION} {_RSYNC_FILTER_OPTION}' |
| 115 | + f'{git_exclude_filter} --dry-run {path}', |
| 116 | + shell=True).splitlines()[-1]) |
| 117 | + total_bytes = rsync_output.split(' ')[3].replace(',', '') |
| 118 | + return int(total_bytes) // 10**6 |
140 | 119 |
|
141 | 120 |
|
142 | 121 | class RayCodeGen:
|
@@ -201,7 +180,7 @@ def add_prologue(self, job_id: int) -> None:
|
201 | 180 | import ray
|
202 | 181 | import ray.util as ray_util
|
203 | 182 |
|
204 |
| - from sky.skylet import job_lib |
| 183 | + from sky.skylet import job_lib, log_utils |
205 | 184 |
|
206 | 185 | SKY_REMOTE_WORKDIR = {log_lib.SKY_REMOTE_WORKDIR!r}
|
207 | 186 | job_lib.set_status({job_id!r}, job_lib.JobStatus.PENDING)
|
@@ -946,7 +925,7 @@ def ray_up(start_streaming_at):
|
946 | 925 | log_abs_path,
|
947 | 926 | stream_logs=False,
|
948 | 927 | start_streaming_at=start_streaming_at,
|
949 |
| - parse_ray_up_logs=True, |
| 928 | + line_processor=log_utils.RayUpLineProcessor(), |
950 | 929 | # Reduce BOTO_MAX_RETRIES from 12 to 5 to avoid long hanging
|
951 | 930 | # time during 'ray up' if insufficient capacity occurs.
|
952 | 931 | env=dict(os.environ, BOTO_MAX_RETRIES='5'),
|
@@ -1397,15 +1376,13 @@ def sync_workdir(self, handle: ResourceHandle, workdir: Path) -> None:
|
1397 | 1376 | workdir = os.path.join(workdir, '') # Adds trailing / if needed.
|
1398 | 1377 |
|
1399 | 1378 | # Raise warning if directory is too large
|
1400 |
| - dir_size = _path_size_megabytes(full_workdir, exclude_gitignore=True) |
| 1379 | + dir_size = _path_size_megabytes(full_workdir) |
1401 | 1380 | if dir_size >= _PATH_SIZE_MEGABYTES_WARN_THRESHOLD:
|
1402 | 1381 | logger.warning(
|
1403 | 1382 | f'{fore.YELLOW}The size of workdir {workdir!r} '
|
1404 | 1383 | f'is {dir_size} MB. Try to keep workdir small or use '
|
1405 |
| - '.gitignore to exclude large files, as ' |
1406 |
| - 'large sizes will slow down rsync. If you use .gitignore but ' |
1407 |
| - 'the path is not initialized in git, you can ignore this ' |
1408 |
| - f'warning.{style.RESET_ALL}') |
| 1384 | + '.gitignore to exclude large files, as large sizes will slow ' |
| 1385 | + 'down rsync. {style.RESET_ALL}') |
1409 | 1386 |
|
1410 | 1387 | log_path = os.path.join(self.log_dir, 'workdir_sync.log')
|
1411 | 1388 |
|
@@ -1512,16 +1489,13 @@ def _sync_node(ip):
|
1512 | 1489 | full_src = os.path.abspath(os.path.expanduser(src))
|
1513 | 1490 | # Checked during Task.set_file_mounts().
|
1514 | 1491 | assert os.path.exists(full_src), f'{full_src} does not exist.'
|
1515 |
| - src_size = _path_size_megabytes(full_src, |
1516 |
| - exclude_gitignore=True) |
| 1492 | + src_size = _path_size_megabytes(full_src) |
1517 | 1493 | if src_size >= _PATH_SIZE_MEGABYTES_WARN_THRESHOLD:
|
1518 | 1494 | logger.warning(
|
1519 | 1495 | f'{fore.YELLOW}The size of file mount src {src!r} '
|
1520 | 1496 | f'is {src_size} MB. Try to keep src small or use '
|
1521 |
| - '.gitignore to exclude large files, as ' |
1522 |
| - 'large sizes will slow down rsync. If you use ' |
1523 |
| - '.gitignore but the path is not initialized in git, you' |
1524 |
| - f' can ignore this warning.{style.RESET_ALL}') |
| 1497 | + '.gitignore to exclude large files, as large sizes ' |
| 1498 | + f'will slow down rsync. {style.RESET_ALL}') |
1525 | 1499 | if os.path.islink(full_src):
|
1526 | 1500 | logger.warning(
|
1527 | 1501 | f'{fore.YELLOW}Source path {src!r} is a symlink. '
|
@@ -2171,19 +2145,19 @@ def _rsync_up(
|
2171 | 2145 | # shooting a lot of messages to the output. --info=progress2 is used
|
2172 | 2146 | # to get a total progress bar, but it requires rsync>=3.1.0 and Mac
|
2173 | 2147 | # OS has a default rsync==2.6.9 (16 years old).
|
2174 |
| - rsync_command = ['rsync', '-Pavz'] |
| 2148 | + rsync_command = ['rsync', _RSYNC_DISPLAY_OPTION] |
2175 | 2149 | # Legend
|
2176 | 2150 | # dir-merge: ignore file can appear in any subdir, applies to that
|
2177 | 2151 | # subdir downwards
|
2178 | 2152 | # Note that "-" is mandatory for rsync and means all patterns in the
|
2179 | 2153 | # ignore files are treated as *exclude* patterns. Non-exclude
|
2180 | 2154 | # patterns, e.g., "! do_not_exclude" doesn't work, even though git
|
2181 | 2155 | # allows it.
|
2182 |
| - rsync_command.append('--filter=\'dir-merge,- .gitignore\'') |
| 2156 | + rsync_command.append(_RSYNC_FILTER_OPTION) |
2183 | 2157 | git_exclude = '.git/info/exclude'
|
2184 | 2158 | if (pathlib.Path(source) / git_exclude).exists():
|
2185 | 2159 | # Ensure file exists; otherwise, rsync will error out.
|
2186 |
| - rsync_command.append('--exclude-from=.git/info/exclude') |
| 2160 | + rsync_command.append(_RSYNC_EXCLUDE_OPTION) |
2187 | 2161 |
|
2188 | 2162 | ssh_options = ' '.join(
|
2189 | 2163 | backend_utils.ssh_options_list(ssh_key,
|
|
0 commit comments