From c06d4565274699af9668b9768801c77270faddfd Mon Sep 17 00:00:00 2001 From: Bryan Massoth Date: Fri, 8 May 2026 13:52:49 -0700 Subject: [PATCH] Integrate Xprof subprocess trace collection into PyGrain workers. PiperOrigin-RevId: 912680701 --- CHANGELOG.md | 3 + grain/_src/core/BUILD | 13 +- grain/_src/core/profiler.py | 136 ++++++++++++++++-- grain/_src/core/profiler_test.py | 89 ++++++++++-- grain/_src/python/dataset/BUILD | 2 + .../transformations/process_prefetch.py | 33 ++++- pyproject.toml | 1 + test_requirements.in | 1 + test_requirements_lock_3_11.txt | 27 ++++ test_requirements_lock_3_12.txt | 27 ++++ test_requirements_lock_3_13.txt | 27 ++++ test_requirements_lock_3_14.txt | 27 ++++ 12 files changed, 361 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07a2ffcdd..bfa0f5c6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ changes. Best viewed [here](https://google-grain.readthedocs.io/en/latest/change * Adds experimental `ShapeDtypeStructProtocol` and `ShapeDtypeStruct` to represent dataset element specs. * Updates TfMixtureIndexSampler to support datasets with weights of 0. + * Adds profiling of multiprocess workers when using XProf profiler. To enable, + set flag `grain_enable_multiprocess_worker_profiling=true` and add + `"profile_subprocesses" = True` in advanced profiler options. * Breaking changes: diff --git a/grain/_src/core/BUILD b/grain/_src/core/BUILD index 7bbb7972b..0e820b107 100644 --- a/grain/_src/core/BUILD +++ b/grain/_src/core/BUILD @@ -181,7 +181,10 @@ py_library( name = "profiler", srcs = ["profiler.py"], srcs_version = "PY3", - deps = ["@abseil-py//absl/logging"], + deps = [ + "@abseil-py//absl/flags", + "@abseil-py//absl/logging", + ], ) py_test( @@ -194,7 +197,11 @@ py_test( srcs_version = "PY3", deps = [ ":profiler", + "@abseil-py//absl/flags", "@abseil-py//absl/testing:absltest", + "@abseil-py//absl/testing:flagsaver", + "@pypi//cloudpickle:pkg", + "@pypi//portpicker:pkg", ], ) @@ -208,8 +215,12 @@ py_test( srcs_version = "PY3", deps = [ ":profiler", + "@abseil-py//absl/flags", "@abseil-py//absl/testing:absltest", + "@abseil-py//absl/testing:flagsaver", + "@pypi//cloudpickle:pkg", "@pypi//jax:pkg", # buildcleaner: keep + "@pypi//portpicker:pkg", ], ) diff --git a/grain/_src/core/profiler.py b/grain/_src/core/profiler.py index b9a6ab02a..cb269e05d 100644 --- a/grain/_src/core/profiler.py +++ b/grain/_src/core/profiler.py @@ -13,29 +13,139 @@ # limitations under the License. """Import wrapper for framework specific profilers.""" +import functools from typing import Callable +from absl import flags from absl import logging +_GRAIN_ENABLE_MULTIPROCESS_WORKER_PROFILING = flags.DEFINE_bool( + "grain_enable_multiprocess_worker_profiling", + False, + "If True, starts profiler servers on spawned worker processes to be" + " profiled alongside the main process (when requested).", +) + +# Internal constants. _NO_FRAMEWORK = "NO_FRAMEWORK" -is_enabled: Callable[[], bool] = lambda: False -TraceAnnotation = None # pylint: disable=invalid-name -start_server: Callable[[int], None] = lambda port: None -stop_server: Callable[[], None] = lambda: None -framework = _NO_FRAMEWORK +_framework = _NO_FRAMEWORK +_subprocess_hooks_loaded = False + + +class TraceAnnotation(object): + """No-op trace annotation for when the profiler is not loaded.""" + + def __init__(self, *args, **kwargs): + del args, kwargs + pass + + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_value, traceback): + del exc_type, exc_value, traceback + pass + + +def is_enabled() -> bool: + """Returns whether the profiler is enabled.""" + return False + + +def is_loaded() -> bool: + """Returns whether the profiler is loaded.""" + return _framework != _NO_FRAMEWORK + + +def is_worker_profiling_supported() -> bool: + """Returns whether worker profiling is supported.""" + return is_loaded() and _subprocess_hooks_loaded + + +def is_worker_profiling_enabled() -> bool: + """Returns whether worker profiling is enabled.""" + return ( + is_worker_profiling_supported() + and _GRAIN_ENABLE_MULTIPROCESS_WORKER_PROFILING.value + ) + + +def get_framework() -> str: + """Returns the framework used for profiling.""" + return _framework + + +def start_server(port: int) -> None: + """Starts the profiler server.""" + del port + pass + + +def stop_server() -> None: + """Stops the profiler server.""" + pass + + +def register_subprocess(pid: int, port: int) -> Callable[[], None]: + """Registers a subprocess with its xprof port. + + Args: + pid: The process ID of the subprocess. + port: The port of the profiler server in the subprocess. + + Returns: + A function that can be called to unregister the subprocess. + + Raises: + RuntimeError: If the subprocess fails to be registered. + """ + del pid, port + raise RuntimeError( + "Subprocess profiler registration is not supported in this environment." + ) + + +def get_worker_init_fn(port: int) -> Callable[[], None]: + """Start the profiler server in a worker process.""" + if not is_worker_profiling_enabled(): + return lambda: None + + def _worker_init_fn() -> None: + try: + start_server(port) + except Exception as e: # pylint: disable=broad-except + logging.warning("Failed to start profiler server: %s", str(e)) + + return _worker_init_fn + try: - if framework == _NO_FRAMEWORK: + if _framework == _NO_FRAMEWORK: from jax import profiler # pylint: disable=g-import-not-at-top # pytype: disable=import-error + from jax._src.lib import jaxlib_extension_version # pylint: disable=g-import-not-at-top # pytype: disable=import-error TraceAnnotation = profiler.TraceAnnotation is_enabled = profiler.TraceAnnotation.is_enabled - start_server = profiler.start_server - stop_server = profiler.stop_server - framework = "jax" -except ImportError: - logging.warning("Failed to load jax profiler") + # jaxlib_extension_version >=448 has subprocess profiling hooks enabled. + if jaxlib_extension_version >= 448: + # multiprocess workers will crash when attempting to connect to a backend + # so we skip using requires_backend=False. + start_server = functools.partial( + profiler.start_server, requires_backend=False + ) + register_subprocess = profiler.register_subprocess + _subprocess_hooks_loaded = True + else: + start_server = profiler.start_server + _subprocess_hooks_loaded = False + logging.warning( + "Grain multiprocess worker profiling requires jaxlib extension" + " version 448 or later (jaxlib >= 0.11.0). Current version: %s.", + jaxlib_extension_version, + ) + stop_server = profiler.stop_server -def is_loaded(): - return framework != _NO_FRAMEWORK + _framework = "jax" +except ImportError as e: + logging.warning("Failed to load jax profiler: %s", e) diff --git a/grain/_src/core/profiler_test.py b/grain/_src/core/profiler_test.py index cdfbd19f6..5be6dfa64 100644 --- a/grain/_src/core/profiler_test.py +++ b/grain/_src/core/profiler_test.py @@ -2,27 +2,41 @@ import os import socket +import time +from absl import flags from absl.testing import absltest +from absl.testing import flagsaver +import cloudpickle +import multiprocessing as mp from grain._src.core import profiler +import portpicker + + +def _worker_main(worker_init_fn: bytes): + """Helper function to start a profiler server in a subprocess.""" + worker_init_fn = cloudpickle.loads(worker_init_fn) + worker_init_fn() + time.sleep(10) class ProfilerTest(absltest.TestCase): def test_framework(self): - expected_framework = os.environ.get("EXPECTED_FRAMEWORK") or "jax" - self.assertEqual(profiler.framework, expected_framework) + expected_framework = os.environ.get("EXPECTED_FRAMEWORK") + self.assertEqual(profiler.get_framework(), expected_framework) def test_trace_annotation(self): - if profiler.framework == profiler._NO_FRAMEWORK: - self.assertIsNone(profiler.TraceAnnotation) - else: - self.assertIsNotNone(profiler.TraceAnnotation) - with profiler.TraceAnnotation("test"): - passes = True - self.assertTrue(passes) + self.assertIsNotNone(profiler.TraceAnnotation) + with profiler.TraceAnnotation("test"): + passes = True + self.assertTrue(passes) + + def test_is_enabled(self): + self.assertIsNotNone(profiler.is_enabled) + self.assertFalse(profiler.is_enabled()) def test_profiler_server(self): - if profiler.framework == profiler._NO_FRAMEWORK: + if not profiler.is_loaded(): self.assertIsNone(profiler.start_server(1234)) else: port = 1234 @@ -32,6 +46,61 @@ def test_profiler_server(self): self.assertEqual(result, 0) profiler.stop_server() + @flagsaver.flagsaver(grain_enable_multiprocess_worker_profiling=True) + def test_register_unregister_subprocess(self): + port = portpicker.pick_unused_port() + mp_context = mp.get_context("spawn") + subprocess = mp_context.Process( + target=_worker_main, + kwargs=dict({ + "worker_init_fn": cloudpickle.dumps( + profiler.get_worker_init_fn(port) + ) + }), + daemon=True, + ) + subprocess.start() + if not profiler.is_worker_profiling_supported(): + with self.assertRaises(RuntimeError): + profiler.register_subprocess(subprocess.pid, port) + else: + unregister_fn = profiler.register_subprocess(subprocess.pid, port) + unregister_fn() + subprocess.kill() + + @absltest.skipUnless( + profiler.is_worker_profiling_supported(), + "Worker profiling is not supported.", + ) + @flagsaver.flagsaver(grain_enable_multiprocess_worker_profiling=True) + def test_get_worker_init_fn_starts_server(self): + port = portpicker.pick_unused_port() + worker_init_fn = profiler.get_worker_init_fn(port) + worker_init_fn() + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + result = sock.connect_ex(("localhost", port)) + self.assertEqual(result, 0) + profiler.stop_server() + + @flagsaver.flagsaver(grain_enable_multiprocess_worker_profiling=False) + def test_get_worker_init_fn_does_not_start_server(self): + port = portpicker.pick_unused_port() + worker_init_fn = profiler.get_worker_init_fn(port) + worker_init_fn() + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + result = sock.connect_ex(("localhost", port)) + self.assertNotEqual(result, 0) + + @flagsaver.flagsaver + def test_worker_profiling_enabled_flag(self): + flags.FLAGS.grain_enable_multiprocess_worker_profiling = False + self.assertFalse(profiler.is_worker_profiling_enabled()) + flags.FLAGS.grain_enable_multiprocess_worker_profiling = True + self.assertEqual( + profiler.is_worker_profiling_enabled(), + profiler.is_worker_profiling_supported(), + ) + if __name__ == "__main__": absltest.main() diff --git a/grain/_src/python/dataset/BUILD b/grain/_src/python/dataset/BUILD index 0b1aa92ee..acbafa81a 100644 --- a/grain/_src/python/dataset/BUILD +++ b/grain/_src/python/dataset/BUILD @@ -48,6 +48,7 @@ py_library( "//grain/_src/core:config", "//grain/_src/core:exceptions", "//grain/_src/core:monitoring", + "//grain/_src/core:profiler", "//grain/_src/core:sharding", "//grain/_src/core:traceback_util", "//grain/_src/core:transforms", @@ -64,6 +65,7 @@ py_library( "@pypi//cloudpickle:pkg", "@pypi//etils:pkg", "@pypi//numpy:pkg", + "@pypi//portpicker:pkg", ], ) diff --git a/grain/_src/python/dataset/transformations/process_prefetch.py b/grain/_src/python/dataset/transformations/process_prefetch.py index 2505b28ef..1bf3c8a1a 100644 --- a/grain/_src/python/dataset/transformations/process_prefetch.py +++ b/grain/_src/python/dataset/transformations/process_prefetch.py @@ -27,7 +27,9 @@ import weakref from absl import flags +from absl import logging import cloudpickle +from grain._src.core import profiler from grain._src.core import traceback_util from grain._src.core.config import config # pylint: disable=g-importing-member import multiprocessing as mp @@ -39,6 +41,7 @@ from grain._src.python.dataset.transformations import prefetch from grain._src.python.ipc import queue as grain_queue from grain._src.python.ipc import shared_memory_array +import portpicker T = TypeVar("T") @@ -165,10 +168,22 @@ def __str__(self) -> str: return f"ProcessPrefetchIterDataset(buffer_size={self._buffer_size})" def __iter__(self) -> dataset.DatasetIterator[T]: + worker_init_fn = self._worker_init_fn + worker_profiler_port = None + if profiler.is_worker_profiling_enabled() and profiler.is_loaded(): + worker_profiler_port = portpicker.pick_unused_port() + profiler_init_fn = profiler.get_worker_init_fn(worker_profiler_port) + if worker_init_fn is None: + worker_init_fn = profiler_init_fn + else: + worker_init_fn = functools.partial( + _run_all, [worker_init_fn, profiler_init_fn] + ) return ProcessPrefetchDatasetIterator( self._parent, self._buffer_size, - self._worker_init_fn, + worker_init_fn, + worker_profiler_port=worker_profiler_port, ) @property @@ -324,11 +339,14 @@ def __init__( parent: dataset.IterDataset[T], buffer_size: int, worker_init_fn: Callable[[], None] | None = None, + worker_profiler_port: int | None = None, ): super().__init__() self._iter_parent = parent self._buffer_size = buffer_size self._worker_init_fn = worker_init_fn + self._worker_profiler_port = worker_profiler_port + self._unregister_fn = None self._keep_workers_after_stop_iteration = False # Since the parent iterator is going to be created in each subprocess, and # the options are propagated during iterator creation, we need to manually @@ -456,6 +474,16 @@ def start_prefetch(self) -> None: ) self._prefetch_process.start() shared_memory_array.SharedMemoryArray.enable_async_del(1) + if ( + self._prefetch_process.is_alive() + and self._worker_profiler_port is not None + ): + try: + self._unregister_fn = profiler.register_subprocess( + self._prefetch_process.pid, self._worker_profiler_port + ) + except Exception as e: # pylint: disable=broad-except + logging.warning("Failed to register subprocess: %s", str(e)) def _process_failed(self) -> bool: if self._prefetch_process is None: @@ -531,6 +559,9 @@ def _stop_prefetch(self): if self._prefetch_process is None: return + if self._unregister_fn is not None: + self._unregister_fn() + self._prefetch_should_stop.set() _clear_queue_and_maybe_unlink_shm(self._buffer) self._clear_set_state_queue() diff --git a/pyproject.toml b/pyproject.toml index 66451c372..306946962 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ dependencies = [ "cloudpickle", "etils[epath,epy]", "numpy", + "portpicker", "protobuf>=5.28.3", ] readme = "README.md" diff --git a/test_requirements.in b/test_requirements.in index f99e31e55..fdd357333 100644 --- a/test_requirements.in +++ b/test_requirements.in @@ -25,5 +25,6 @@ numpy attrs pyarrow pytest +portpicker parameterized typing_extensions diff --git a/test_requirements_lock_3_11.txt b/test_requirements_lock_3_11.txt index 7c6ba5873..3f40bc9c0 100644 --- a/test_requirements_lock_3_11.txt +++ b/test_requirements_lock_3_11.txt @@ -214,6 +214,33 @@ pluggy==1.6.0 \ --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 # via pytest +portpicker==1.6.0 \ + --hash=sha256:b2787a41404cf7edbe29b07b9e0ed863b09f2665dcc01c1eb0c2261c1e7d0755 \ + --hash=sha256:bd507fd6f96f65ee02781f2e674e9dc6c99bbfa6e3c39992e3916204c9d431fa + # via -r test_requirements.in +psutil==7.2.2 \ + --hash=sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372 \ + --hash=sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9 \ + --hash=sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841 \ + --hash=sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63 \ + --hash=sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979 \ + --hash=sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a \ + --hash=sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b \ + --hash=sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9 \ + --hash=sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee \ + --hash=sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312 \ + --hash=sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b \ + --hash=sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9 \ + --hash=sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e \ + --hash=sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc \ + --hash=sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1 \ + --hash=sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf \ + --hash=sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea \ + --hash=sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988 \ + --hash=sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486 \ + --hash=sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00 \ + --hash=sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8 + # via portpicker pyarrow==22.0.0 \ --hash=sha256:001ea83a58024818826a9e3f89bf9310a114f7e26dfe404a4c32686f97bd7901 \ --hash=sha256:00626d9dc0f5ef3a75fe63fd68b9c7c8302d2b5bbc7f74ecaedba83447a24f84 \ diff --git a/test_requirements_lock_3_12.txt b/test_requirements_lock_3_12.txt index 69ed4395d..f0470c085 100644 --- a/test_requirements_lock_3_12.txt +++ b/test_requirements_lock_3_12.txt @@ -214,6 +214,33 @@ pluggy==1.6.0 \ --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 # via pytest +portpicker==1.6.0 \ + --hash=sha256:b2787a41404cf7edbe29b07b9e0ed863b09f2665dcc01c1eb0c2261c1e7d0755 \ + --hash=sha256:bd507fd6f96f65ee02781f2e674e9dc6c99bbfa6e3c39992e3916204c9d431fa + # via -r test_requirements.in +psutil==7.2.2 \ + --hash=sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372 \ + --hash=sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9 \ + --hash=sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841 \ + --hash=sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63 \ + --hash=sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979 \ + --hash=sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a \ + --hash=sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b \ + --hash=sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9 \ + --hash=sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee \ + --hash=sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312 \ + --hash=sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b \ + --hash=sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9 \ + --hash=sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e \ + --hash=sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc \ + --hash=sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1 \ + --hash=sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf \ + --hash=sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea \ + --hash=sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988 \ + --hash=sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486 \ + --hash=sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00 \ + --hash=sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8 + # via portpicker pyarrow==22.0.0 \ --hash=sha256:001ea83a58024818826a9e3f89bf9310a114f7e26dfe404a4c32686f97bd7901 \ --hash=sha256:00626d9dc0f5ef3a75fe63fd68b9c7c8302d2b5bbc7f74ecaedba83447a24f84 \ diff --git a/test_requirements_lock_3_13.txt b/test_requirements_lock_3_13.txt index d620f9aaa..bd6161c78 100644 --- a/test_requirements_lock_3_13.txt +++ b/test_requirements_lock_3_13.txt @@ -214,6 +214,33 @@ pluggy==1.6.0 \ --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 # via pytest +portpicker==1.6.0 \ + --hash=sha256:b2787a41404cf7edbe29b07b9e0ed863b09f2665dcc01c1eb0c2261c1e7d0755 \ + --hash=sha256:bd507fd6f96f65ee02781f2e674e9dc6c99bbfa6e3c39992e3916204c9d431fa + # via -r test_requirements.in +psutil==7.2.2 \ + --hash=sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372 \ + --hash=sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9 \ + --hash=sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841 \ + --hash=sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63 \ + --hash=sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979 \ + --hash=sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a \ + --hash=sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b \ + --hash=sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9 \ + --hash=sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee \ + --hash=sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312 \ + --hash=sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b \ + --hash=sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9 \ + --hash=sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e \ + --hash=sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc \ + --hash=sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1 \ + --hash=sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf \ + --hash=sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea \ + --hash=sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988 \ + --hash=sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486 \ + --hash=sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00 \ + --hash=sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8 + # via portpicker pyarrow==22.0.0 \ --hash=sha256:001ea83a58024818826a9e3f89bf9310a114f7e26dfe404a4c32686f97bd7901 \ --hash=sha256:00626d9dc0f5ef3a75fe63fd68b9c7c8302d2b5bbc7f74ecaedba83447a24f84 \ diff --git a/test_requirements_lock_3_14.txt b/test_requirements_lock_3_14.txt index 04d39cdfb..abfab8ef6 100644 --- a/test_requirements_lock_3_14.txt +++ b/test_requirements_lock_3_14.txt @@ -214,6 +214,33 @@ pluggy==1.6.0 \ --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 # via pytest +portpicker==1.6.0 \ + --hash=sha256:b2787a41404cf7edbe29b07b9e0ed863b09f2665dcc01c1eb0c2261c1e7d0755 \ + --hash=sha256:bd507fd6f96f65ee02781f2e674e9dc6c99bbfa6e3c39992e3916204c9d431fa + # via -r test_requirements.in +psutil==7.2.2 \ + --hash=sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372 \ + --hash=sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9 \ + --hash=sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841 \ + --hash=sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63 \ + --hash=sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979 \ + --hash=sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a \ + --hash=sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b \ + --hash=sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9 \ + --hash=sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee \ + --hash=sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312 \ + --hash=sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b \ + --hash=sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9 \ + --hash=sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e \ + --hash=sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc \ + --hash=sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1 \ + --hash=sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf \ + --hash=sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea \ + --hash=sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988 \ + --hash=sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486 \ + --hash=sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00 \ + --hash=sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8 + # via portpicker pyarrow==22.0.0 \ --hash=sha256:001ea83a58024818826a9e3f89bf9310a114f7e26dfe404a4c32686f97bd7901 \ --hash=sha256:00626d9dc0f5ef3a75fe63fd68b9c7c8302d2b5bbc7f74ecaedba83447a24f84 \