Skip to content

Commit cdda6b3

Browse files
committed
Merge branch 'main' into ray-subsystembenchmark-checkpointing-save
# Conflicts: # gcsfs/tests/perf/subsystembenchmarks/tests/test_run_groups.py
2 parents 275d8ee + 7a5b4e7 commit cdda6b3

71 files changed

Lines changed: 4464 additions & 255 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ jobs:
7575
- name: Run Release Automation Tests
7676
run: |
7777
pytest -vv .github/release/
78+
- name: Run Cloud Build Test Report Unit Tests
79+
run: |
80+
pytest -vv cloudbuild/test_summarize_test_results.py
7881
- name: Run all tests (Standard, Zonal & HNS Enabled) with default ON extended feature support
7982
run: |
8083
export GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/gcsfs/tests/fake-service-account-credentials.json
@@ -108,13 +111,15 @@ jobs:
108111
cache-dependency-path: |
109112
gcsfs/tests/perf/subsystembenchmarks/dataloading/huggingface_datasets/requirements.txt
110113
gcsfs/tests/perf/subsystembenchmarks/dataloading/webdataset/requirements.txt
114+
gcsfs/tests/perf/subsystembenchmarks/dataloading/ray_data/requirements.txt
111115
- name: Install subsystem benchmark dependencies
112116
# Resolve dependencies together to catch version conflicts.
113117
run: |
114118
python -m pip install -e .
115119
python -m pip install \
116120
-r gcsfs/tests/perf/subsystembenchmarks/dataloading/huggingface_datasets/requirements.txt \
117-
-r gcsfs/tests/perf/subsystembenchmarks/dataloading/webdataset/requirements.txt
121+
-r gcsfs/tests/perf/subsystembenchmarks/dataloading/webdataset/requirements.txt \
122+
-r gcsfs/tests/perf/subsystembenchmarks/dataloading/ray_data/requirements.txt
118123
- name: Run subsystem benchmark unit tests
119124
run: pytest gcsfs/tests/perf/subsystembenchmarks --run-benchmarks-infra
120125

cloudbuild/benchmarks/benchmarks-cloudbuild.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ steps:
262262
echo '--- Installing pip packages ---'
263263
pip install --upgrade pip > /dev/null
264264
pip install pytest pytest-timeout pytest-subtests pytest-asyncio fusepy google-cloud-storage > /dev/null
265-
pip install --upgrade 'git+https://github.com/googleapis/google-cloud-python.git@main#subdirectory=packages/google-cloud-storage'
265+
266266
echo '--- Installing GCSFS packages ---'
267267
# Manual builds via 'gcloud builds submit' don't upload .git directory,
268268
# causing hatch-vcs to fail. We use a dummy version in that case.

cloudbuild/benchmarks/benchmarks_schema.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
{"name": "seq_probability", "type": "STRING"},
3636
{"name": "engine", "type": "STRING"},
3737
{"name": "method", "type": "STRING"},
38+
{"name": "batch_size", "type": "INTEGER"},
39+
{"name": "max_gap", "type": "INTEGER"},
40+
{"name": "num_ranges", "type": "INTEGER"},
41+
{"name": "total_bytes", "type": "INTEGER"},
3842
{"name": "min", "type": "FLOAT"},
3943
{"name": "max", "type": "FLOAT"},
4044
{"name": "mean", "type": "FLOAT"},

cloudbuild/e2e-tests-cloudbuild.yaml

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,45 @@ steps:
186186
waitFor: ["setup-vm", "create-buckets"]
187187
allowFailure: true
188188

189+
# Test Report
190+
# GitHub shows only the first ~65k chars of the build log, so the test steps write their full output to
191+
# files on the VM. This step prints a size-capped summary (counts + failure tracebacks) first, then the
192+
# full logs. It always exits 0 so that cleanup still runs.
193+
- name: "gcr.io/google.com/cloudsdktool/cloud-sdk"
194+
id: "test-report"
195+
entrypoint: "bash"
196+
args:
197+
- "-c"
198+
- |
199+
mkdir -p /workspace/test-results
200+
if gcloud compute instances describe gcsfs-test-vm-${_SHORT_BUILD_ID} --zone=${_ZONE} &>/dev/null; then
201+
gcloud compute scp --recurse gcsfs-test-vm-${_SHORT_BUILD_ID}:~/test-results /workspace/ \
202+
--zone=${_ZONE} \
203+
--internal-ip \
204+
--ssh-key-file=/workspace/.ssh/google_compute_engine > /dev/null || echo "⚠️ Could not copy test results from the VM"
205+
fi
206+
207+
python3 cloudbuild/summarize_test_results.py /workspace/test-results \
208+
--suites standard zonal zonal-core hns \
209+
--failed-steps /workspace/FAILED \
210+
--max-chars 30000 || echo "⚠️ Could not summarize test results"
211+
212+
for suite in standard zonal zonal-core hns; do
213+
if [[ -f /workspace/test-results/$${suite}.log ]]; then
214+
echo "===== FULL LOG: $${suite} ====="
215+
cat /workspace/test-results/$${suite}.log
216+
fi
217+
done
218+
exit 0
219+
waitFor:
220+
[
221+
"run-standard-tests",
222+
"run-zonal-tests",
223+
"run-hns-tests",
224+
"run-zonal-core-tests",
225+
]
226+
allowFailure: true
227+
189228
# Cleanup
190229
- name: "gcr.io/google.com/cloudsdktool/cloud-sdk"
191230
id: "cleanup"
@@ -202,13 +241,7 @@ steps:
202241
- "WORKERS_HNS=${_WORKERS_HNS}"
203242
- "WORKERS_ZONAL=${_WORKERS_ZONAL}"
204243
- "WORKERS_ZONAL_CORE=${_WORKERS_ZONAL_CORE}"
205-
waitFor:
206-
[
207-
"run-standard-tests",
208-
"run-zonal-tests",
209-
"run-hns-tests",
210-
"run-zonal-core-tests",
211-
]
244+
waitFor: ["test-report"]
212245

213246
# Fail the build if any previous step failed
214247
- name: "gcr.io/google.com/cloudsdktool/cloud-sdk"

cloudbuild/run_tests.sh

Lines changed: 67 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,60 @@ if ! [[ "${PYTEST_XDIST_WORKERS}" =~ ^[0-9]+$ ]] || (( PYTEST_XDIST_WORKERS <= 0
2424
fi
2525
ARGS+=(-n "${PYTEST_XDIST_WORKERS}")
2626

27+
# Full pytest output goes to files instead of the build log. GitHub shows only the first ~65k chars
28+
# of the Cloud Build log, so the test-report step prints a summary from the JUnit XML before these logs.
29+
RESULTS_DIR="${HOME}/test-results"
30+
RESULTS_LOG="${RESULTS_DIR}/${TEST_SUITE}.log"
31+
mkdir -p "${RESULTS_DIR}"
32+
ARGS+=("--junitxml=${RESULTS_DIR}/${TEST_SUITE}.xml")
33+
34+
# While pytest runs, print the tests still in progress every HEARTBEAT_SECS. A hung test is failed by
35+
# pytest-timeout and a killed pytest leaves its log tail for test-report, but if the whole build times out
36+
# (or the VM is lost) test-report never runs, so these lines keep a stuck test visible in the step log.
37+
HEARTBEAT_SECS="${HEARTBEAT_SECS:-300}"
38+
if ! [[ "${HEARTBEAT_SECS}" =~ ^[0-9]+$ ]] || (( HEARTBEAT_SECS <= 0 )); then
39+
HEARTBEAT_SECS=300
40+
fi
41+
42+
in_progress_tests() {
43+
# With -vv, xdist logs "<nodeid>" when a test starts and "[gwN] <OUTCOME> <nodeid>" when it finishes.
44+
awk '
45+
{ sub(/ <- .*/, ""); sub(/[ \t]+$/, "") }
46+
/^\[gw[0-9]+\] [A-Z]+ / { sub(/^\[gw[0-9]+\] [A-Z]+ /, ""); delete running[$0]; next }
47+
/^[^ ]+\.py::/ { running[$0] = 1 }
48+
END { for (t in running) printf "%s%s", (n++ ? ", " : ""), t; if (!n) printf "none" }
49+
' "${RESULTS_LOG}" 2>/dev/null | cut -c1-500
50+
}
51+
52+
heartbeat() {
53+
local elapsed=0
54+
# Sleep in 1s steps so stopping the heartbeat never leaves a long sleep holding the ssh session open.
55+
while sleep 1; do
56+
elapsed=$((elapsed + 1))
57+
if (( elapsed % HEARTBEAT_SECS == 0 )); then
58+
echo "--- ${TEST_SUITE}: still running after $((elapsed / 60))m; in progress: $(in_progress_tests) ---"
59+
fi
60+
done
61+
}
62+
63+
run_pytest() {
64+
local status=0
65+
heartbeat &
66+
local heartbeat_pid=$!
67+
pytest "$@" > "${RESULTS_LOG}" 2>&1 || status=$?
68+
{ kill "${heartbeat_pid}" && wait "${heartbeat_pid}"; } 2>/dev/null
69+
return "${status}"
70+
}
71+
STATUS=0
72+
2773
echo "--- Running Test Suite: ${TEST_SUITE} ---"
2874

2975
case "$TEST_SUITE" in
3076
"standard")
3177
export GCSFS_TEST_BUCKET="gcsfs-test-standard-${SHORT_BUILD_ID}"
3278
export GCSFS_TEST_VERSIONED_BUCKET="gcsfs-test-versioned-${SHORT_BUILD_ID}"
3379
export GCSFS_TEST_REQ_PAYS_BUCKET="gcsfs-test-standard-req-pay-${SHORT_BUILD_ID}"
34-
pytest "${ARGS[@]}" gcsfs/ --deselect gcsfs/tests/test_core.py::test_sign
80+
run_pytest "${ARGS[@]}" gcsfs/ --deselect gcsfs/tests/test_core.py::test_sign || STATUS=$?
3581
;;
3682

3783
"zonal")
@@ -44,13 +90,13 @@ case "$TEST_SUITE" in
4490
export GCSFS_RUN_RAPID_TESTS="true"
4591
export GCSFS_EXPERIMENTAL_ZB_HNS_SUPPORT='true'
4692
# Excludes tests related to requster pays as Zonal buckets do not support requester pays feature
47-
pytest "${ARGS[@]}" \
48-
gcsfs/tests/test_extended_gcsfs.py \
93+
run_pytest "${ARGS[@]}" \
94+
gcsfs/tests/test_zonal.py \
4995
gcsfs/tests/test_zonal_file.py \
50-
gcsfs/tests/integration/test_async_gcsfs.py \
51-
gcsfs/tests/integration/test_extended_hns.py \
52-
--deselect gcsfs/tests/integration/test_extended_hns.py::TestExtendedGcsFileSystemHnsRequesterPays::test_hns_mkdir_fails_without_quota_project \
53-
--deselect gcsfs/tests/integration/test_extended_hns.py::TestExtendedGcsFileSystemHnsRequesterPays::test_hns_bucket_type_detection_with_req_pays
96+
gcsfs/tests/test_async.py \
97+
gcsfs/tests/test_hns.py \
98+
--deselect gcsfs/tests/test_hns.py::TestExtendedGcsFileSystemHnsRequesterPays::test_hns_mkdir_fails_without_quota_project \
99+
--deselect gcsfs/tests/test_hns.py::TestExtendedGcsFileSystemHnsRequesterPays::test_hns_bucket_type_detection_with_req_pays || STATUS=$?
54100
;;
55101

56102
"hns")
@@ -62,20 +108,20 @@ case "$TEST_SUITE" in
62108
export GCSFS_RUN_HNS_TESTS="true"
63109
export GCSFS_EXPERIMENTAL_ZB_HNS_SUPPORT='true'
64110
# Excludes tests that are not applicable to HNS buckets:
65-
# - test_extended_gcsfs.py, test_zonal_file.py: Zonal bucket specific tests which won't work on HNS bucket.
66-
# - test_extended_gcsfs_unit.py: Unit tests for zonal bucket features.
67-
# - test_core_versioned.py: HNS buckets do not support versioning.
111+
# - test_zonal.py, test_zonal_file.py: Zonal bucket specific tests which won't work on HNS bucket.
112+
# - test_zonal_unit.py: Unit tests for zonal bucket features.
113+
# - test_flat_versioned.py: HNS buckets do not support versioning.
68114
# - test_core.py::test_sign: Current Cloud Build auth setup does not support this.
69115
# - test_core.py::test_mv_file_cache: Integration test only applicable for regional buckets.
70116
# - test_core.py::test_rm_wildcards_non_recursive: HNS buckets have different behavior for non-recursive wildcard deletion.
71-
pytest "${ARGS[@]}" gcsfs/ \
72-
--deselect gcsfs/tests/test_extended_gcsfs.py \
117+
run_pytest "${ARGS[@]}" gcsfs/ \
118+
--deselect gcsfs/tests/test_zonal.py \
73119
--deselect gcsfs/tests/test_zonal_file.py \
74-
--deselect gcsfs/tests/test_extended_gcsfs_unit.py \
75-
--deselect gcsfs/tests/test_core_versioned.py \
120+
--deselect gcsfs/tests/test_zonal_unit.py \
121+
--deselect gcsfs/tests/test_flat_versioned.py \
76122
--deselect gcsfs/tests/test_core.py::test_sign \
77123
--deselect gcsfs/tests/test_core.py::test_mv_file_cache \
78-
--deselect gcsfs/tests/test_core.py::test_rm_wildcards_non_recursive
124+
--deselect gcsfs/tests/test_core.py::test_rm_wildcards_non_recursive || STATUS=$?
79125
;;
80126

81127
"zonal-core")
@@ -137,24 +183,16 @@ case "$TEST_SUITE" in
137183
"--deselect=gcsfs/tests/test_core.py::test_put_file_resumable_upload_cleanup_on_chunk_failure"
138184
)
139185

140-
# The prefetcher engine is not integrated for zonal in this bucket.
141-
# It will be integrated in a separate PR, after which this will be removed.
142-
ZONAL_DESELECTS+=(
143-
"--deselect=gcsfs/tests/test_core.py::test_cat_file_routing_and_thresholds"
144-
"--deselect=gcsfs/tests/test_core.py::test_cat_file_concurrent_data_integrity"
145-
"--deselect=gcsfs/tests/test_core.py::test_cat_file_concurrent_exception_cancellation"
146-
"--deselect=gcsfs/tests/test_core.py::test_gcsfile_prefetch_disabled_fallback"
147-
"--deselect=gcsfs/tests/test_core.py::test_gcsfile_prefetch_sequential_integrity"
148-
"--deselect=gcsfs/tests/test_core.py::test_gcsfile_prefetch_random_seek_integrity"
149-
"--deselect=gcsfs/tests/test_core.py::test_gcsfile_multithreaded_read_integrity"
150-
"--deselect=gcsfs/tests/test_core.py::test_gcsfile_not_satisfiable_range"
151-
)
152-
153186
# Zonal buckets do not support the requester pays feature
154187
ZONAL_DESELECTS+=(
155188
"--deselect=gcsfs/tests/test_core.py::test_requester_pays_fails_without_user_project"
156189
)
157190

158-
pytest "${ARGS[@]}" "${ZONAL_DESELECTS[@]}" gcsfs/tests/test_core.py
191+
run_pytest "${ARGS[@]}" "${ZONAL_DESELECTS[@]}" gcsfs/tests/test_core.py || STATUS=$?
159192
;;
160193
esac
194+
195+
# Pytest's final "=== N passed, M skipped in Xs ===" line, so the step log still shows the counts.
196+
RESULT_LINE=$(grep -E '^=+ .* in [0-9.]+s' "${RESULTS_LOG}" 2>/dev/null | tail -n 1 | sed -E 's/^=+ | =+$//g')
197+
echo "--- ${TEST_SUITE}: pytest exit ${STATUS} ${RESULT_LINE} ---"
198+
exit "${STATUS}"

cloudbuild/subsystembenchmarks/subsystembenchmarks_schema.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"fields": [
1111
{"name": "benchmark_case_id", "type": "STRING", "description": "Stable benchmark case identifier encoding every swept parameter."},
1212
{"name": "benchmark_group", "type": "STRING", "description": "pytest-benchmark's group label, which publish_case_metadata sets to the case scenario: read for dataloading, checkpoint_write for checkpointing. Same value as workload_scenario; filter on workload_family and workload_implementation to tell the loaders within a subsystem apart."},
13-
{"name": "workload_implementation", "type": "STRING", "description": "Library or integration executing the workload, matching the run.py --group suffix: huggingface_datasets, webdataset, or pytorch_lightning."},
13+
{"name": "workload_implementation", "type": "STRING", "description": "Library or integration executing the workload, matching the run.py --group suffix: huggingface_datasets, webdataset, pytorch_lightning, or ray_data."},
1414
{"name": "workload_scenario", "type": "STRING", "description": "Operation exercised within the workload implementation, named by the scenario in the group's configs.yaml: read for dataloading, checkpoint_write for checkpointing."},
1515
{"name": "config_sweep_axis", "type": "STRING", "description": "Configuration axis that produced this case, or baseline for the implicit baseline case."},
1616
{"name": "workload_family", "type": "STRING", "description": "Broad benchmark row family: data_loading or checkpointing. With workload_implementation it identifies the runnable group, which benchmark_group also carries verbatim (note data_loading here against the dataloading directory the group name uses)."},
@@ -25,11 +25,11 @@
2525
{"name": "requirements_resolved", "type": "STRING", "description": "JSON list from pip describing final installed distribution names and resolved versions."},
2626
{"name": "config_sweep_axes_requested", "type": "STRING", "description": "Normalized requested config sweep axes, or all when the complete group was selected; baseline is included automatically."},
2727
{"name": "measurement_round_count", "type": "INTEGER", "description": "Number of measured rounds represented by the round-duration statistics. A round is one timed unit of the workload: one full-corpus iteration for dataloading, one checkpoint operation for checkpointing."},
28-
{"name": "dataloader_num_workers", "type": "INTEGER", "description": "Populated by dataloading. DataLoader worker process count per distributed rank; 0 means in-process loading. Shared name with the macrobenchmarks schema."},
28+
{"name": "dataloader_num_workers", "type": "INTEGER", "description": "Populated by dataloading. DataLoader worker process count per distributed rank (or Ray Data reader task concurrency); 0 means in-process loading or library-default concurrency. Shared name with the macrobenchmarks schema."},
2929
{"name": "batch_size_samples", "type": "INTEGER", "description": "Populated by dataloading. Number of samples yielded per data batch."},
30-
{"name": "sample_sequence_length_tokens", "type": "INTEGER", "description": "Populated by dataloading/huggingface_datasets. Number of tokens in each generated dataset sample."},
30+
{"name": "sample_sequence_length_tokens", "type": "INTEGER", "description": "Populated by dataloading/huggingface_datasets and dataloading/ray_data. Number of tokens in each generated dataset sample."},
3131
{"name": "dataset_format", "type": "STRING", "description": "Populated by dataloading. On-GCS representation of the generated dataset, pairing stored payload with container: pretok_parquet, text_parquet, pretok_jsonl, image_tar_jpeg, image_tar_png, image_tar_npy, or image_tar_gz_jpeg. Image payload encoding rides here rather than in a column of its own, matching how pretok_ and text_ already distinguish payloads sharing a container."},
32-
{"name": "parquet_row_group_size_rows", "type": "INTEGER", "description": "Populated by dataloading/huggingface_datasets. Number of rows per Parquet row group; not applicable to non-Parquet formats."},
32+
{"name": "parquet_row_group_size_rows", "type": "INTEGER", "description": "Populated by dataloading/huggingface_datasets and dataloading/ray_data. Number of rows per Parquet row group; not applicable to non-Parquet formats."},
3333
{"name": "dataset_file_count", "type": "INTEGER", "description": "Populated by dataloading. Number of files in the generated dataset."},
3434
{"name": "dataset_size_bytes", "type": "INTEGER", "description": "Populated by dataloading. Total stored size in bytes of the generated GCS dataset objects. Shared name with the macrobenchmarks schema."},
3535
{"name": "dataset_sample_count", "type": "INTEGER", "description": "Populated by dataloading. Total number of samples in the generated dataset; every measured round must yield this count."},

0 commit comments

Comments
 (0)