Repository navigation
feat(telemetry): extend framework telemetry to ExtendedGcsFileSystem and ZonalFile, add e2e tests - #1140
feat(telemetry): extend framework telemetry to ExtendedGcsFileSystem and ZonalFile, add e2e tests#1140Mahalaxmibejugam wants to merge 11 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces lightweight, automatic caller framework detection (telemetry) to identify upstream libraries like Pandas, PyTorch, and Lightning interacting with Google Cloud Storage via gcsfs. It includes documentation, environment updates, and extensive integration and end-to-end tests. The feedback highlights an issue in the VM setup script where a failure to install the CPU-only torch wheel would cause a subsequent lightning installation to pull down the massive CUDA-enabled torch wheel from PyPI, defeating the optimization. A conditional installation approach was suggested to resolve this.
7502f41 to
d9b4f5f
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1140 +/- ##
==========================================
+ Coverage 92.51% 92.59% +0.08%
==========================================
Files 20 20
Lines 4086 4091 +5
==========================================
+ Hits 3780 3788 +8
+ Misses 306 303 -3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
/gcbrun |
6489c20 to
9727c62
Compare
…emetry tests
- Apply mirror_gcs_methods to ExtendedGcsFileSystem (the default exported class) so its
own sync/async overrides (mv, rm, mkdir, makedirs, rmdir, merge, _cat_file, _open, ...)
detect the framework on the caller thread; use _gcs_sync_wrapper for
_sync_lookup_bucket_type and sync_process_limits_to_offset_and_length.
- Apply wrap_file_methods to ZonalFile so its overrides keep the file's caller framework.
- Read the framework value without copying the context dict on every file method call.
- core: resolve default_usage_tracker via the manager module so tests can patch one place;
move mirror_gcs_methods(GCSFileSystem) directly after the class body.
- Tests:
- Coverage test no longer pre-sets context; uses a caller-thread-only detector, runs on
both GCSFileSystem and ExtendedGcsFileSystem, and asserts detection never runs on the
fsspecIO loop thread (regression guard against per-request stack walks).
- Add Extended/Zonal wrapping and ZonalFile propagation tests.
- Replace the vacuous eager-property test with one that mirrors a fresh subclass.
- Harness: Core/Extended param, exact fsspec registry restore, batch-delete mock,
skip credential refresh (HTTP is mocked).
- Real framework tests run on both classes; Lightning writes to tmp_path.
- CI: add pandas, pytorch-cpu and lightning so real framework tests run.
- Docs: 64-frame cap, runtime opt-out, note that gRPC (zonal) requests lack the token.
…ing names Replace the hardcoded method list with checks derived from the class: any sync function wrapping a coroutine must be a _gcs_sync_wrapper, and every async method targeted by mirror_gcs_methods (and its public sync counterpart) must be wrapped. Runs on both GCSFileSystem and ExtendedGcsFileSystem.
Benchmarking showed it was slower than get_telemetry_context().get() (~205ns vs ~156ns per call; Enum isinstance/.value cost outweighs the saved 1-entry dict copy), so keep the existing API.
…ild e2e Add gcsfs/tests/test_telemetry_e2e.py: real pandas (to_csv/read_csv), torch DataLoader reads, and Lightning save_checkpoint against the GCS endpoint with no gcsfs mocking. The User-Agent of every request to the GCS host is recorded at the aiohttp layer and must carry the framework token. A Rapid (zonal) variant checks the round trip and that any remaining JSON API requests are tagged (gRPC is not yet). - Runs in GitHub CI against the fake GCS emulator, and in the e2e standard/hns suites (which run gcsfs/) against real GCS; added explicitly to the zonal suite. - setup_vm.sh installs CPU-only torch, pandas and lightning on the e2e VM.
…/HNS) Rapid (zonal) buckets use gRPC bidi APIs for data and never go through the HTTP User-Agent path, so the zonal e2e test could not verify anything. Drop it and its explicit addition to the zonal suite. The regional (standard suite) and HNS e2e suites already run gcsfs/ and therefore these tests against real GCS.
- Add `with_telemetry_context` and apply it to GCSFileSystem._open and ExtendedGcsFileSystem._open (plain sync methods not covered by mirror_gcs_methods). Opening a Rapid (zonal) file for read issues a JSON API `_info` request from ZonalFile.__init__ (MRD pool setup) before caller_framework is set; it previously went out without the framework token and ran detection on the fsspecIO thread. Bucket lookup, metadata requests and GCSFile.__init__ now share one detection. - Test: zonal open sends fw token and detects only on the caller thread. - Fix the Extended mirror comment (`_open` is not mirrored). - setup_vm.sh: make framework installs non-fatal (tests importorskip them). - Harness: clear the fsspec instance cache on teardown; comment the private registry use. - e2e: guard the private aiohttp `_request` hook with a clear assertion. - Coverage test: use fsspec's loop thread name instead of hardcoding "fsspecIO". - Replace the eager-property test with a plain init check (the trap could never fire).
If the CPU torch install failed, installing lightning would resolve torch from PyPI and download the multi-GB CUDA build. Install pandas separately and chain lightning on a successful CPU torch install.
Mock _lookup_bucket_layout with gcsfs.tests.layouts instead of the removed BucketType/_lookup_bucket_type.
…ileSystem._open GCSFile.__init__ already caches caller_framework before its own info() call (the wrapped GCSFile.details property reuses it), so the decorator on core GCSFileSystem._open was redundant. Keep it on ExtendedGcsFileSystem._open, where the bucket-layout lookup and ZonalFile's MRD pool _info request run before caller_framework exists. Extend the zonal test to a full file lifecycle (open, read, seek, read, info, close) asserting a single caller-thread detection and that every operation carries the framework.
mv, mkdir, makedirs, rmdir, rm and merge were defined as asyn.sync_wrapper(...) in the ExtendedGcsFileSystem class body, but mirror_gcs_methods(ExtendedGcsFileSystem) always overwrites them at import with telemetry-aware _gcs_sync_wrapper(...), so the class-body values never took effect. mv/mkdir/makedirs/rmdir/rm are still mirrored via AbstractFileSystem, and merge via the inherited GCSFileSystem.merge; class attributes, dispatch and docstrings are unchanged.
object.__new__(ZonalFile) raises 'not safe, use ZonalFile.__new__()' on Python 3.10/3.11 for io.IOBase subclasses (allowed from 3.12), which broke the GitHub CI matrix (all jobs run 3.11).
9727c62 to
59f5714
Compare
zhixiangli
left a comment
There was a problem hiding this comment.
LGTM. One small docs nit inline.
| The framework token is attached to JSON API (HTTP) requests. gRPC data-path requests to Rapid (zonal) buckets | ||
| use a ``User-Agent`` fixed when the gRPC client is created and do not yet carry the framework token. |
There was a problem hiding this comment.
nit: gRPC requests without the token go beyond Rapid data-path ones. RCU reads also go to ZonalFile/MRD (_open, is_rcu). Storage Control calls (bucket layout lookup, HNS mkdir/rmdir/mv folder ops) use a fixed grpc.primary_user_agent too. Suggest:
| The framework token is attached to JSON API (HTTP) requests. gRPC data-path requests to Rapid (zonal) buckets | |
| use a ``User-Agent`` fixed when the gRPC client is created and do not yet carry the framework token. | |
| The framework token is attached to JSON API (HTTP) requests only. gRPC requests use a ``User-Agent`` fixed | |
| when the gRPC client is created and do not yet carry the framework token. This includes Rapid (zonal) reads | |
| and writes, Rapid Cache Ultra reads, and Storage Control API calls (bucket layout lookups and HNS folder | |
| operations such as ``mkdir``, ``rmdir`` and ``mv``). |
| Rapid (zonal) buckets are intentionally not covered: their data path uses gRPC | ||
| bidi streaming APIs, which don't go through the HTTP User-Agent path at all. |
There was a problem hiding this comment.
nit: same here. Rapid Cache Ultra reads also use the gRPC data path, so maybe say "Rapid (zonal) and Rapid Cache Ultra buckets are intentionally not covered".
Summary (follow-up to #1054)
Follow-up to #1054. It extends caller-framework telemetry to
ExtendedGcsFileSystem(the classgcsfs.GCSFileSystemexports by default) and toZonalFile. It also strengthens the telemetry tests and adds real end-to-end telemetry tests to the Cloud Build e2e pipeline.Changes
Telemetry coverage
mirror_gcs_methods(ExtendedGcsFileSystem)wraps Extended's own sync and async overrides (mv,rm,mkdir,makedirs,rmdir,merge,_cat_file,_find,_pipe_file,_put_file, …). These previously went through plainasyn.sync_wrapper, so their requests lacked the framework token and ran stack detection on thefsspecIOthread for every request._sync_lookup_bucket_layoutandsync_process_limits_to_offset_and_lengthnow use_gcs_sync_wrapper.wrap_file_methods(ZonalFile)wraps ZonalFile's own overrides (_fetch_range,_async_fetch_range,write,flush,commit, …).with_telemetry_contextdecorator onExtendedGcsFileSystem._open. The bucket-layout lookup, and for Rapid (zonal) reads the MRD pool's JSON API_inforequest inZonalFile.__init__, run beforeGCSFile.__init__cachescaller_framework. Previously that_inforequest went out without the token, and detection ran on the fsspecIO thread. The framework is now detected once on the caller thread, andGCSFile.__init__reuses it.core.pyreadstelemetry_manager.default_usage_trackerfrom the module, so tests only need to patch one place.Tests
GCSFileSystemandExtendedGcsFileSystem. It also asserts detection never runs on the fsspec loop thread._gcs_sync_wrapper, and every async methodmirror_gcs_methodstargets, along with its public sync counterpart, must be wrapped.ZonalFileis wrapped;_async_fetch_rangeandwritecarry the file's framework;mock_gcs_harness):test_upstream_frameworks_real.py): they run on both classes, and Lightning writes totmp_path.test_telemetry_e2e.py: real pandas (to_csv/read_csv), torchDataLoaderreads, and Lightningsave_checkpointagainst a GCS endpoint with no mocking. TheUser-Agentof every request to the GCS host is recorded at the aiohttp layer, and each one must carry the framework token.standard(regional) andhnssuites, which rungcsfs/, and against the fake emulator in GitHub CI.User-Agentpath.CI / e2e
environment_gcsfs.yaml: addspandas,pytorch-cpuandlightning. Previously the real-framework tests were always skipped in CI; now they actually run.cloudbuild/setup_vm.sh: installs CPU-only torch from download.pytorch.org, pluspandasandlightning. The installs are non-fatal because the testsimportorskipthese packages.Docs
telemetry.rstnow states the 64-frame detection cap, says detection runs on the caller thread, explains that the opt-out env vars are read on every call, and notes that gRPC (zonal) data-path requests don't carry the framework token yet.Testing
main(after feat(telemetry): integrate upstream framework telemetry into User-Agent header (3/3) #1054 and refactor(extended_gcsfs): replace BucketType with orthogonal BucketLayout and routing helpers #1133):GCSFS_EXPERIMENTAL_ZB_HNS_SUPPORT=false: 754 passed.GCSFS_EXPERIMENTAL_ZB_HNS_SUPPORT=true: 1104 passed. The only failures are 3test_hns_unit.py::test_get_control_plane_client_endpointcases, a local mTLS environment issue that also fails onmain.test_telemetry_e2e.pypassed against real GCS in both thestandardandhnssuites.mirror_gcs_methods(ExtendedGcsFileSystem),wrap_file_methods(ZonalFile)or@with_telemetry_contextonExtendedGcsFileSystem._openeach makes the corresponding tests fail;GCSFS_NO_TELEMETRY=1, all e2e tests fail withN/N GCS requests missing fw/<framework>.Notes for reviewers
pytorch-cpuandlightningin GitHub CI. Expect a slower environment setup.ci.ymluses${{ matrix.PY }}, but the matrix key ispython-version, so every matrix job actually runs Python 3.11 fromenvironment_gcsfs.yaml.GCSFS_EXPERIMENTAL_ZB_HNS_SUPPORT=false. On the emulator, Extended is covered by the mocked harness, not bytest_telemetry_e2e.py.