ZhengyangZhang commited on
Commit
6f19040
·
verified ·
1 Parent(s): e4b9d1e

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lib/python3.12/site-packages/nvidia/cufft/include/__pycache__/__init__.cpython-312.pyc +0 -0
  2. lib/python3.12/site-packages/nvidia/cufft/include/cufftXt.h +259 -0
  3. lib/python3.12/site-packages/nvidia/cufft/lib/__init__.py +0 -0
  4. lib/python3.12/site-packages/nvidia/cufft/lib/__pycache__/__init__.cpython-312.pyc +0 -0
  5. lib/python3.12/site-packages/ray/_common/__init__.py +0 -0
  6. lib/python3.12/site-packages/ray/_common/__pycache__/__init__.cpython-312.pyc +0 -0
  7. lib/python3.12/site-packages/ray/_common/__pycache__/constants.cpython-312.pyc +0 -0
  8. lib/python3.12/site-packages/ray/_common/__pycache__/deprecation.cpython-312.pyc +0 -0
  9. lib/python3.12/site-packages/ray/_common/__pycache__/filters.cpython-312.pyc +0 -0
  10. lib/python3.12/site-packages/ray/_common/__pycache__/formatters.cpython-312.pyc +0 -0
  11. lib/python3.12/site-packages/ray/_common/__pycache__/network_utils.cpython-312.pyc +0 -0
  12. lib/python3.12/site-packages/ray/_common/__pycache__/pydantic_compat.cpython-312.pyc +0 -0
  13. lib/python3.12/site-packages/ray/_common/__pycache__/ray_constants.cpython-312.pyc +0 -0
  14. lib/python3.12/site-packages/ray/_common/__pycache__/ray_option_utils.cpython-312.pyc +0 -0
  15. lib/python3.12/site-packages/ray/_common/__pycache__/retry.cpython-312.pyc +0 -0
  16. lib/python3.12/site-packages/ray/_common/__pycache__/serialization.cpython-312.pyc +0 -0
  17. lib/python3.12/site-packages/ray/_common/__pycache__/signature.cpython-312.pyc +0 -0
  18. lib/python3.12/site-packages/ray/_common/__pycache__/test_utils.cpython-312.pyc +0 -0
  19. lib/python3.12/site-packages/ray/_common/__pycache__/utils.cpython-312.pyc +0 -0
  20. lib/python3.12/site-packages/ray/_common/constants.py +7 -0
  21. lib/python3.12/site-packages/ray/_common/deprecation.py +141 -0
  22. lib/python3.12/site-packages/ray/_common/filters.py +55 -0
  23. lib/python3.12/site-packages/ray/_common/formatters.py +120 -0
  24. lib/python3.12/site-packages/ray/_common/network_utils.py +111 -0
  25. lib/python3.12/site-packages/ray/_common/pydantic_compat.py +108 -0
  26. lib/python3.12/site-packages/ray/_common/ray_constants.py +5 -0
  27. lib/python3.12/site-packages/ray/_common/ray_option_utils.py +384 -0
  28. lib/python3.12/site-packages/ray/_common/retry.py +82 -0
  29. lib/python3.12/site-packages/ray/_common/serialization.py +34 -0
  30. lib/python3.12/site-packages/ray/_common/signature.py +163 -0
  31. lib/python3.12/site-packages/ray/_common/test_utils.py +249 -0
  32. lib/python3.12/site-packages/ray/_common/usage/__init__.py +0 -0
  33. lib/python3.12/site-packages/ray/_common/usage/__pycache__/__init__.cpython-312.pyc +0 -0
  34. lib/python3.12/site-packages/ray/_common/usage/__pycache__/usage_constants.cpython-312.pyc +0 -0
  35. lib/python3.12/site-packages/ray/_common/usage/__pycache__/usage_lib.cpython-312.pyc +0 -0
  36. lib/python3.12/site-packages/ray/_common/usage/usage_constants.py +63 -0
  37. lib/python3.12/site-packages/ray/_common/usage/usage_lib.py +1016 -0
  38. lib/python3.12/site-packages/ray/_common/utils.py +368 -0
  39. lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/__init__.cpython-312.pyc +0 -0
  40. lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/_clonevirtualenv.cpython-312.pyc +0 -0
  41. lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/conda.cpython-312.pyc +0 -0
  42. lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/conda_utils.cpython-312.pyc +0 -0
  43. lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/constants.cpython-312.pyc +0 -0
  44. lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/context.cpython-312.pyc +0 -0
  45. lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/default_impl.cpython-312.pyc +0 -0
  46. lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/dependency_utils.cpython-312.pyc +0 -0
  47. lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/image_uri.cpython-312.pyc +0 -0
  48. lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/java_jars.cpython-312.pyc +0 -0
  49. lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/nsight.cpython-312.pyc +0 -0
  50. lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/packaging.cpython-312.pyc +0 -0
lib/python3.12/site-packages/nvidia/cufft/include/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (199 Bytes). View file
 
lib/python3.12/site-packages/nvidia/cufft/include/cufftXt.h ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ /* Copyright 2005-2021 NVIDIA Corporation. All rights reserved.
3
+ *
4
+ * NOTICE TO LICENSEE:
5
+ *
6
+ * The source code and/or documentation ("Licensed Deliverables") are
7
+ * subject to NVIDIA intellectual property rights under U.S. and
8
+ * international Copyright laws.
9
+ *
10
+ * The Licensed Deliverables contained herein are PROPRIETARY and
11
+ * CONFIDENTIAL to NVIDIA and are being provided under the terms and
12
+ * conditions of a form of NVIDIA software license agreement by and
13
+ * between NVIDIA and Licensee ("License Agreement") or electronically
14
+ * accepted by Licensee. Notwithstanding any terms or conditions to
15
+ * the contrary in the License Agreement, reproduction or disclosure
16
+ * of the Licensed Deliverables to any third party without the express
17
+ * written consent of NVIDIA is prohibited.
18
+ *
19
+ * NOTWITHSTANDING ANY TERMS OR CONDITIONS TO THE CONTRARY IN THE
20
+ * LICENSE AGREEMENT, NVIDIA MAKES NO REPRESENTATION ABOUT THE
21
+ * SUITABILITY OF THESE LICENSED DELIVERABLES FOR ANY PURPOSE. THEY ARE
22
+ * PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND.
23
+ * NVIDIA DISCLAIMS ALL WARRANTIES WITH REGARD TO THESE LICENSED
24
+ * DELIVERABLES, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY,
25
+ * NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE.
26
+ * NOTWITHSTANDING ANY TERMS OR CONDITIONS TO THE CONTRARY IN THE
27
+ * LICENSE AGREEMENT, IN NO EVENT SHALL NVIDIA BE LIABLE FOR ANY
28
+ * SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
29
+ * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30
+ * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
31
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
32
+ * OF THESE LICENSED DELIVERABLES.
33
+ *
34
+ * U.S. Government End Users. These Licensed Deliverables are a
35
+ * "commercial item" as that term is defined at 48 C.F.R. 2.101 (OCT
36
+ * 1995), consisting of "commercial computer software" and "commercial
37
+ * computer software documentation" as such terms are used in 48
38
+ * C.F.R. 12.212 (SEPT 1995) and are provided to the U.S. Government
39
+ * only as a commercial end item. Consistent with 48 C.F.R.12.212 and
40
+ * 48 C.F.R. 227.7202-1 through 227.7202-4 (JUNE 1995), all
41
+ * U.S. Government End Users acquire the Licensed Deliverables with
42
+ * only those rights set forth herein.
43
+ *
44
+ * Any use of the Licensed Deliverables in individual and commercial
45
+ * software must include, in the user documentation and internal
46
+ * comments to the code, the above Disclaimer and U.S. Government End
47
+ * Users Notice.
48
+ */
49
+
50
+ /*!
51
+ * \file cufftXt.h
52
+ * \brief Public header file for the NVIDIA CUDA FFT library (CUFFT)
53
+ */
54
+
55
+ #ifndef _CUFFTXT_H_
56
+ #define _CUFFTXT_H_
57
+ #include "cudalibxt.h"
58
+ #include "cufft.h"
59
+
60
+
61
+ #ifndef CUFFTAPI
62
+ #ifdef _WIN32
63
+ #define CUFFTAPI __stdcall
64
+ #else
65
+ #define CUFFTAPI
66
+ #endif
67
+ #endif
68
+
69
+ #ifdef __cplusplus
70
+ extern "C" {
71
+ #endif
72
+
73
+ //
74
+ // cufftXtSubFormat identifies the data layout of
75
+ // a memory descriptor owned by cufft.
76
+ // note that multi GPU cufft does not yet support out-of-place transforms
77
+ //
78
+
79
+ typedef enum cufftXtSubFormat_t {
80
+ CUFFT_XT_FORMAT_INPUT = 0x00, //by default input is in linear order across GPUs
81
+ CUFFT_XT_FORMAT_OUTPUT = 0x01, //by default output is in scrambled order depending on transform
82
+ CUFFT_XT_FORMAT_INPLACE = 0x02, //by default inplace is input order, which is linear across GPUs
83
+ CUFFT_XT_FORMAT_INPLACE_SHUFFLED = 0x03, //shuffled output order after execution of the transform
84
+ CUFFT_XT_FORMAT_1D_INPUT_SHUFFLED = 0x04, //shuffled input order prior to execution of 1D transforms
85
+ CUFFT_XT_FORMAT_DISTRIBUTED_INPUT = 0x05,
86
+ CUFFT_XT_FORMAT_DISTRIBUTED_OUTPUT = 0x06,
87
+ CUFFT_FORMAT_UNDEFINED = 0x07
88
+ } cufftXtSubFormat;
89
+
90
+ //
91
+ // cufftXtCopyType specifies the type of copy for cufftXtMemcpy
92
+ //
93
+ typedef enum cufftXtCopyType_t {
94
+ CUFFT_COPY_HOST_TO_DEVICE = 0x00,
95
+ CUFFT_COPY_DEVICE_TO_HOST = 0x01,
96
+ CUFFT_COPY_DEVICE_TO_DEVICE = 0x02,
97
+ CUFFT_COPY_UNDEFINED = 0x03
98
+ } cufftXtCopyType;
99
+
100
+ //
101
+ // cufftXtQueryType specifies the type of query for cufftXtQueryPlan
102
+ //
103
+ typedef enum cufftXtQueryType_t {
104
+ CUFFT_QUERY_1D_FACTORS = 0x00,
105
+ CUFFT_QUERY_UNDEFINED = 0x01
106
+ } cufftXtQueryType;
107
+
108
+ typedef struct cufftXt1dFactors_t {
109
+ long long int size;
110
+ long long int stringCount;
111
+ long long int stringLength;
112
+ long long int substringLength;
113
+ long long int factor1;
114
+ long long int factor2;
115
+ long long int stringMask;
116
+ long long int substringMask;
117
+ long long int factor1Mask;
118
+ long long int factor2Mask;
119
+ int stringShift;
120
+ int substringShift;
121
+ int factor1Shift;
122
+ int factor2Shift;
123
+ } cufftXt1dFactors;
124
+
125
+ //
126
+ // cufftXtWorkAreaPolicy specifies policy for cufftXtSetWorkAreaPolicy
127
+ //
128
+ typedef enum cufftXtWorkAreaPolicy_t {
129
+ CUFFT_WORKAREA_MINIMAL = 0, /* maximum reduction */
130
+ CUFFT_WORKAREA_USER = 1, /* use workSize parameter as limit */
131
+ CUFFT_WORKAREA_PERFORMANCE = 2, /* default - 1x overhead or more, maximum performance */
132
+ } cufftXtWorkAreaPolicy;
133
+
134
+ // multi-GPU routines
135
+ cufftResult CUFFTAPI cufftXtSetGPUs(cufftHandle handle, int nGPUs, int *whichGPUs);
136
+
137
+ cufftResult CUFFTAPI cufftXtMalloc(cufftHandle plan,
138
+ cudaLibXtDesc ** descriptor,
139
+ cufftXtSubFormat format);
140
+
141
+ cufftResult CUFFTAPI cufftXtMemcpy(cufftHandle plan,
142
+ void *dstPointer,
143
+ void *srcPointer,
144
+ cufftXtCopyType type);
145
+
146
+ cufftResult CUFFTAPI cufftXtFree(cudaLibXtDesc *descriptor);
147
+
148
+ cufftResult CUFFTAPI cufftXtSetWorkArea(cufftHandle plan, void **workArea);
149
+
150
+ cufftResult CUFFTAPI cufftXtExecDescriptorC2C(cufftHandle plan,
151
+ cudaLibXtDesc *input,
152
+ cudaLibXtDesc *output,
153
+ int direction);
154
+
155
+ cufftResult CUFFTAPI cufftXtExecDescriptorR2C(cufftHandle plan,
156
+ cudaLibXtDesc *input,
157
+ cudaLibXtDesc *output);
158
+
159
+ cufftResult CUFFTAPI cufftXtExecDescriptorC2R(cufftHandle plan,
160
+ cudaLibXtDesc *input,
161
+ cudaLibXtDesc *output);
162
+
163
+ cufftResult CUFFTAPI cufftXtExecDescriptorZ2Z(cufftHandle plan,
164
+ cudaLibXtDesc *input,
165
+ cudaLibXtDesc *output,
166
+ int direction);
167
+
168
+ cufftResult CUFFTAPI cufftXtExecDescriptorD2Z(cufftHandle plan,
169
+ cudaLibXtDesc *input,
170
+ cudaLibXtDesc *output);
171
+
172
+ cufftResult CUFFTAPI cufftXtExecDescriptorZ2D(cufftHandle plan,
173
+ cudaLibXtDesc *input,
174
+ cudaLibXtDesc *output);
175
+
176
+ // Utility functions
177
+
178
+ cufftResult CUFFTAPI cufftXtQueryPlan(cufftHandle plan, void *queryStruct, cufftXtQueryType queryType);
179
+
180
+
181
+ // callbacks
182
+
183
+
184
+ typedef enum cufftXtCallbackType_t {
185
+ CUFFT_CB_LD_COMPLEX = 0x0,
186
+ CUFFT_CB_LD_COMPLEX_DOUBLE = 0x1,
187
+ CUFFT_CB_LD_REAL = 0x2,
188
+ CUFFT_CB_LD_REAL_DOUBLE = 0x3,
189
+ CUFFT_CB_ST_COMPLEX = 0x4,
190
+ CUFFT_CB_ST_COMPLEX_DOUBLE = 0x5,
191
+ CUFFT_CB_ST_REAL = 0x6,
192
+ CUFFT_CB_ST_REAL_DOUBLE = 0x7,
193
+ CUFFT_CB_UNDEFINED = 0x8
194
+
195
+ } cufftXtCallbackType;
196
+
197
+ typedef cufftComplex (*cufftCallbackLoadC)(void *dataIn, size_t offset, void *callerInfo, void *sharedPointer);
198
+ typedef cufftDoubleComplex (*cufftCallbackLoadZ)(void *dataIn, size_t offset, void *callerInfo, void *sharedPointer);
199
+ typedef cufftReal (*cufftCallbackLoadR)(void *dataIn, size_t offset, void *callerInfo, void *sharedPointer);
200
+ typedef cufftDoubleReal(*cufftCallbackLoadD)(void *dataIn, size_t offset, void *callerInfo, void *sharedPointer);
201
+
202
+ typedef void (*cufftCallbackStoreC)(void *dataOut, size_t offset, cufftComplex element, void *callerInfo, void *sharedPointer);
203
+ typedef void (*cufftCallbackStoreZ)(void *dataOut, size_t offset, cufftDoubleComplex element, void *callerInfo, void *sharedPointer);
204
+ typedef void (*cufftCallbackStoreR)(void *dataOut, size_t offset, cufftReal element, void *callerInfo, void *sharedPointer);
205
+ typedef void (*cufftCallbackStoreD)(void *dataOut, size_t offset, cufftDoubleReal element, void *callerInfo, void *sharedPointer);
206
+
207
+
208
+ cufftResult CUFFTAPI cufftXtSetCallback(cufftHandle plan, void **callback_routine, cufftXtCallbackType cbType, void **caller_info);
209
+ cufftResult CUFFTAPI cufftXtClearCallback(cufftHandle plan, cufftXtCallbackType cbType);
210
+ cufftResult CUFFTAPI cufftXtSetCallbackSharedSize(cufftHandle plan, cufftXtCallbackType cbType, size_t sharedSize);
211
+
212
+ cufftResult CUFFTAPI cufftXtMakePlanMany(cufftHandle plan,
213
+ int rank,
214
+ long long int *n,
215
+ long long int *inembed,
216
+ long long int istride,
217
+ long long int idist,
218
+ cudaDataType inputtype,
219
+ long long int *onembed,
220
+ long long int ostride,
221
+ long long int odist,
222
+ cudaDataType outputtype,
223
+ long long int batch,
224
+ size_t *workSize,
225
+ cudaDataType executiontype);
226
+
227
+ cufftResult CUFFTAPI cufftXtGetSizeMany(cufftHandle plan,
228
+ int rank,
229
+ long long int *n,
230
+ long long int *inembed,
231
+ long long int istride,
232
+ long long int idist,
233
+ cudaDataType inputtype,
234
+ long long int *onembed,
235
+ long long int ostride,
236
+ long long int odist,
237
+ cudaDataType outputtype,
238
+ long long int batch,
239
+ size_t *workSize,
240
+ cudaDataType executiontype);
241
+
242
+
243
+ cufftResult CUFFTAPI cufftXtExec(cufftHandle plan,
244
+ void *input,
245
+ void *output,
246
+ int direction);
247
+
248
+ cufftResult CUFFTAPI cufftXtExecDescriptor(cufftHandle plan,
249
+ cudaLibXtDesc *input,
250
+ cudaLibXtDesc *output,
251
+ int direction);
252
+
253
+ cufftResult CUFFTAPI cufftXtSetWorkAreaPolicy(cufftHandle plan, cufftXtWorkAreaPolicy policy, size_t *workSize);
254
+
255
+ #ifdef __cplusplus
256
+ }
257
+ #endif
258
+
259
+ #endif
lib/python3.12/site-packages/nvidia/cufft/lib/__init__.py ADDED
File without changes
lib/python3.12/site-packages/nvidia/cufft/lib/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (195 Bytes). View file
 
lib/python3.12/site-packages/ray/_common/__init__.py ADDED
File without changes
lib/python3.12/site-packages/ray/_common/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (190 Bytes). View file
 
lib/python3.12/site-packages/ray/_common/__pycache__/constants.cpython-312.pyc ADDED
Binary file (378 Bytes). View file
 
lib/python3.12/site-packages/ray/_common/__pycache__/deprecation.cpython-312.pyc ADDED
Binary file (5.13 kB). View file
 
lib/python3.12/site-packages/ray/_common/__pycache__/filters.cpython-312.pyc ADDED
Binary file (3.09 kB). View file
 
lib/python3.12/site-packages/ray/_common/__pycache__/formatters.cpython-312.pyc ADDED
Binary file (6.51 kB). View file
 
lib/python3.12/site-packages/ray/_common/__pycache__/network_utils.cpython-312.pyc ADDED
Binary file (4.26 kB). View file
 
lib/python3.12/site-packages/ray/_common/__pycache__/pydantic_compat.cpython-312.pyc ADDED
Binary file (2.92 kB). View file
 
lib/python3.12/site-packages/ray/_common/__pycache__/ray_constants.cpython-312.pyc ADDED
Binary file (319 Bytes). View file
 
lib/python3.12/site-packages/ray/_common/__pycache__/ray_option_utils.cpython-312.pyc ADDED
Binary file (15.8 kB). View file
 
lib/python3.12/site-packages/ray/_common/__pycache__/retry.cpython-312.pyc ADDED
Binary file (3.71 kB). View file
 
lib/python3.12/site-packages/ray/_common/__pycache__/serialization.cpython-312.pyc ADDED
Binary file (1.71 kB). View file
 
lib/python3.12/site-packages/ray/_common/__pycache__/signature.cpython-312.pyc ADDED
Binary file (6.94 kB). View file
 
lib/python3.12/site-packages/ray/_common/__pycache__/test_utils.cpython-312.pyc ADDED
Binary file (12.2 kB). View file
 
lib/python3.12/site-packages/ray/_common/__pycache__/utils.cpython-312.pyc ADDED
Binary file (14.8 kB). View file
 
lib/python3.12/site-packages/ray/_common/constants.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Prefix for the node id resource that is automatically added to each node.
2
+ # For example, a node may have id `node:172.23.42.1`.
3
+ NODE_ID_PREFIX = "node:"
4
+ # The system resource that head node has.
5
+ HEAD_NODE_RESOURCE_NAME = NODE_ID_PREFIX + "__internal_head__"
6
+
7
+ RAY_WARN_BLOCKING_GET_INSIDE_ASYNC_ENV_VAR = "RAY_WARN_BLOCKING_GET_INSIDE_ASYNC"
lib/python3.12/site-packages/ray/_common/deprecation.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import inspect
2
+ import logging
3
+ from typing import Optional, Union
4
+
5
+ from ray.util import log_once
6
+ from ray.util.annotations import _mark_annotated
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ # A constant to use for any configuration that should be deprecated
11
+ # (to check, whether this config has actually been assigned a proper value or
12
+ # not).
13
+ DEPRECATED_VALUE = -1
14
+
15
+
16
+ def deprecation_warning(
17
+ old: str,
18
+ new: Optional[str] = None,
19
+ *,
20
+ help: Optional[str] = None,
21
+ error: Optional[Union[bool, Exception]] = None,
22
+ stacklevel: int = 2,
23
+ ) -> None:
24
+ """Warns (via the `logger` object) or throws a deprecation warning/error.
25
+
26
+ Args:
27
+ old: A description of the "thing" that is to be deprecated.
28
+ new: A description of the new "thing" that replaces it.
29
+ help: An optional help text to tell the user, what to
30
+ do instead of using `old`.
31
+ error: Whether or which exception to raise. If True, raise ValueError.
32
+ If False, just warn. If `error` is-a subclass of Exception,
33
+ raise that Exception.
34
+ stacklevel: The stacklevel to use for the warning message.
35
+ Use 2 to point to where this function is called, 3+ to point
36
+ further up the stack.
37
+
38
+ Raises:
39
+ ValueError: If `error=True`.
40
+ Exception: Of type `error`, iff `error` is a sub-class of `Exception`.
41
+ """
42
+ msg = "`{}` has been deprecated.{}".format(
43
+ old, (" Use `{}` instead.".format(new) if new else f" {help}" if help else "")
44
+ )
45
+
46
+ if error:
47
+ if not isinstance(error, bool) and issubclass(error, Exception):
48
+ # error is an Exception
49
+ raise error(msg)
50
+ else:
51
+ # error is a boolean, construct ValueError ourselves
52
+ raise ValueError(msg)
53
+ else:
54
+ logger.warning(
55
+ "DeprecationWarning: " + msg + " This will raise an error in the future!",
56
+ stacklevel=stacklevel,
57
+ )
58
+
59
+
60
+ def Deprecated(old=None, *, new=None, help=None, error):
61
+ """Decorator for documenting a deprecated class, method, or function.
62
+
63
+ Automatically adds a `deprecation.deprecation_warning(old=...,
64
+ error=False)` to not break existing code at this point to the decorated
65
+ class' constructor, method, or function.
66
+
67
+ In a next major release, this warning should then be made an error
68
+ (by setting error=True), which means at this point that the
69
+ class/method/function is no longer supported, but will still inform
70
+ the user about the deprecation event.
71
+
72
+ In a further major release, the class, method, function should be erased
73
+ entirely from the codebase.
74
+
75
+
76
+ .. testcode::
77
+ :skipif: True
78
+
79
+ from ray._common.deprecation import Deprecated
80
+ # Deprecated class: Patches the constructor to warn if the class is
81
+ # used.
82
+ @Deprecated(new="NewAndMuchCoolerClass", error=False)
83
+ class OldAndUncoolClass:
84
+ ...
85
+
86
+ # Deprecated class method: Patches the method to warn if called.
87
+ class StillCoolClass:
88
+ ...
89
+ @Deprecated(new="StillCoolClass.new_and_much_cooler_method()",
90
+ error=False)
91
+ def old_and_uncool_method(self, uncool_arg):
92
+ ...
93
+
94
+ # Deprecated function: Patches the function to warn if called.
95
+ @Deprecated(new="new_and_much_cooler_function", error=False)
96
+ def old_and_uncool_function(*uncool_args):
97
+ ...
98
+ """
99
+
100
+ def _inner(obj):
101
+ # A deprecated class.
102
+ if inspect.isclass(obj):
103
+ # Patch the class' init method to raise the warning/error.
104
+ obj_init = obj.__init__
105
+
106
+ def patched_init(*args, **kwargs):
107
+ if log_once(old or obj.__name__):
108
+ deprecation_warning(
109
+ old=old or obj.__name__,
110
+ new=new,
111
+ help=help,
112
+ error=error,
113
+ stacklevel=3,
114
+ )
115
+ return obj_init(*args, **kwargs)
116
+
117
+ obj.__init__ = patched_init
118
+ _mark_annotated(obj)
119
+ # Return the patched class (with the warning/error when
120
+ # instantiated).
121
+ return obj
122
+
123
+ # A deprecated class method or function.
124
+ # Patch with the warning/error at the beginning.
125
+ def _ctor(*args, **kwargs):
126
+ if log_once(old or obj.__name__):
127
+ deprecation_warning(
128
+ old=old or obj.__name__,
129
+ new=new,
130
+ help=help,
131
+ error=error,
132
+ stacklevel=3,
133
+ )
134
+ # Call the deprecated method/function.
135
+ return obj(*args, **kwargs)
136
+
137
+ # Return the patched class method/function.
138
+ return _ctor
139
+
140
+ # Return the prepared decorator.
141
+ return _inner
lib/python3.12/site-packages/ray/_common/filters.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import Any, Dict
3
+
4
+ import ray
5
+ from ray._private.ray_logging.constants import LogKey
6
+
7
+
8
+ class CoreContextFilter(logging.Filter):
9
+ TASK_LEVEL_LOG_KEYS = [
10
+ LogKey.TASK_ID.value,
11
+ LogKey.TASK_NAME.value,
12
+ LogKey.TASK_FUNCTION_NAME.value,
13
+ ]
14
+
15
+ @classmethod
16
+ def get_ray_core_logging_context(cls) -> Dict[str, Any]:
17
+ """
18
+ Get the ray core logging context as a dict.
19
+ Only use this function if you need include the attributes to the log record
20
+ yourself by bypassing the filter.
21
+ """
22
+ if not ray.is_initialized():
23
+ # There is no additional context if ray is not initialized
24
+ return {}
25
+
26
+ runtime_context = ray.get_runtime_context()
27
+ ray_core_logging_context = {
28
+ LogKey.JOB_ID.value: runtime_context.get_job_id(),
29
+ LogKey.WORKER_ID.value: runtime_context.get_worker_id(),
30
+ LogKey.NODE_ID.value: runtime_context.get_node_id(),
31
+ }
32
+ if runtime_context.worker.mode == ray.WORKER_MODE:
33
+ ray_core_logging_context[
34
+ LogKey.ACTOR_ID.value
35
+ ] = runtime_context.get_actor_id()
36
+ ray_core_logging_context[
37
+ LogKey.TASK_ID.value
38
+ ] = runtime_context.get_task_id()
39
+ ray_core_logging_context[
40
+ LogKey.TASK_NAME.value
41
+ ] = runtime_context.get_task_name()
42
+ ray_core_logging_context[
43
+ LogKey.TASK_FUNCTION_NAME.value
44
+ ] = runtime_context.get_task_function_name()
45
+ ray_core_logging_context[
46
+ LogKey.ACTOR_NAME.value
47
+ ] = runtime_context.get_actor_name()
48
+ return ray_core_logging_context
49
+
50
+ def filter(self, record):
51
+ context = self.get_ray_core_logging_context()
52
+ for key, value in context.items():
53
+ if value is not None:
54
+ setattr(record, key, value)
55
+ return True
lib/python3.12/site-packages/ray/_common/formatters.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import logging
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, Dict, List
5
+
6
+ from ray._private.log import INTERNAL_TIMESTAMP_LOG_KEY
7
+ from ray._private.ray_constants import LOGGER_FORMAT
8
+ from ray._private.ray_logging.constants import (
9
+ LOGGER_FLATTEN_KEYS,
10
+ LOGRECORD_STANDARD_ATTRS,
11
+ LogKey,
12
+ )
13
+
14
+
15
+ def _append_flatten_attributes(formatted_attrs: Dict[str, Any], key: str, value: Any):
16
+ """Flatten the dictionary values for special keys and append the values in place.
17
+
18
+ If the key is in `LOGGER_FLATTEN_KEYS`, the value will be flattened and appended
19
+ to the `formatted_attrs` dictionary. Otherwise, the key-value pair will be appended
20
+ directly.
21
+ """
22
+ if key in LOGGER_FLATTEN_KEYS:
23
+ if not isinstance(value, dict):
24
+ raise ValueError(
25
+ f"Expected a dictionary passing into {key}, but got {type(value)}"
26
+ )
27
+ for k, v in value.items():
28
+ if k in formatted_attrs:
29
+ raise KeyError(f"Found duplicated key in the log record: {k}")
30
+ formatted_attrs[k] = v
31
+ else:
32
+ formatted_attrs[key] = value
33
+
34
+
35
+ class AbstractFormatter(logging.Formatter, ABC):
36
+ def __init__(self, fmt=None, datefmt=None, style="%", validate=True) -> None:
37
+ super().__init__(fmt, datefmt, style, validate)
38
+ self._additional_log_standard_attrs = []
39
+
40
+ def set_additional_log_standard_attrs(
41
+ self, additional_log_standard_attrs: List[str]
42
+ ) -> None:
43
+ self._additional_log_standard_attrs = additional_log_standard_attrs
44
+
45
+ @property
46
+ def additional_log_standard_attrs(self) -> List[str]:
47
+ return self._additional_log_standard_attrs
48
+
49
+ def generate_record_format_attrs(
50
+ self,
51
+ record: logging.LogRecord,
52
+ exclude_default_standard_attrs,
53
+ ) -> dict:
54
+ record_format_attrs = {}
55
+
56
+ # If `exclude_default_standard_attrs` is False, include the standard attributes.
57
+ # Otherwise, include only Ray and user-provided context.
58
+ if not exclude_default_standard_attrs:
59
+ record_format_attrs.update(
60
+ {
61
+ LogKey.ASCTIME.value: self.formatTime(record),
62
+ LogKey.LEVELNAME.value: record.levelname,
63
+ LogKey.MESSAGE.value: record.getMessage(),
64
+ LogKey.FILENAME.value: record.filename,
65
+ LogKey.LINENO.value: record.lineno,
66
+ LogKey.PROCESS.value: record.process,
67
+ }
68
+ )
69
+ if record.exc_info:
70
+ if not record.exc_text:
71
+ record.exc_text = self.formatException(record.exc_info)
72
+ record_format_attrs[LogKey.EXC_TEXT.value] = record.exc_text
73
+
74
+ # Add the user specified additional standard attributes.
75
+ for key in self._additional_log_standard_attrs:
76
+ _append_flatten_attributes(
77
+ record_format_attrs, key, getattr(record, key, None)
78
+ )
79
+
80
+ for key, value in record.__dict__.items():
81
+ # Both Ray and user-provided context are stored in `record_format`.
82
+ if key not in LOGRECORD_STANDARD_ATTRS:
83
+ _append_flatten_attributes(record_format_attrs, key, value)
84
+
85
+ # Format the internal timestamp to the standardized `timestamp_ns` key.
86
+ if INTERNAL_TIMESTAMP_LOG_KEY in record_format_attrs:
87
+ record_format_attrs[LogKey.TIMESTAMP_NS.value] = record_format_attrs.pop(
88
+ INTERNAL_TIMESTAMP_LOG_KEY
89
+ )
90
+
91
+ return record_format_attrs
92
+
93
+ @abstractmethod
94
+ def format(self, record: logging.LogRecord) -> str:
95
+ pass
96
+
97
+
98
+ class JSONFormatter(AbstractFormatter):
99
+ def format(self, record: logging.LogRecord) -> str:
100
+ record_format_attrs = self.generate_record_format_attrs(
101
+ record, exclude_default_standard_attrs=False
102
+ )
103
+ return json.dumps(record_format_attrs)
104
+
105
+
106
+ class TextFormatter(AbstractFormatter):
107
+ def __init__(self, fmt=None, datefmt=None, style="%", validate=True) -> None:
108
+ super().__init__(fmt, datefmt, style, validate)
109
+ self._inner_formatter = logging.Formatter(LOGGER_FORMAT)
110
+
111
+ def format(self, record: logging.LogRecord) -> str:
112
+ s = self._inner_formatter.format(record)
113
+ record_format_attrs = self.generate_record_format_attrs(
114
+ record, exclude_default_standard_attrs=True
115
+ )
116
+
117
+ additional_attrs = " ".join(
118
+ [f"{key}={value}" for key, value in record_format_attrs.items()]
119
+ )
120
+ return f"{s} {additional_attrs}"
lib/python3.12/site-packages/ray/_common/network_utils.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import socket
2
+ from contextlib import closing
3
+ from functools import lru_cache
4
+ from typing import Optional, Tuple, Union
5
+
6
+ from ray._raylet import (
7
+ build_address as _build_address,
8
+ is_ipv6 as _is_ipv6,
9
+ node_ip_address_from_perspective as _node_ip_address_from_perspective,
10
+ parse_address as _parse_address,
11
+ )
12
+
13
+
14
+ def parse_address(address: str) -> Optional[Tuple[str, str]]:
15
+ """Parse a network address string into host and port.
16
+
17
+ Args:
18
+ address: The address string to parse (e.g., "localhost:8000", "[::1]:8000").
19
+
20
+ Returns:
21
+ Tuple with (host, port) if port found, None if no colon separator.
22
+ """
23
+ return _parse_address(address)
24
+
25
+
26
+ def build_address(host: str, port: Union[int, str]) -> str:
27
+ """Build a network address string from host and port.
28
+
29
+ Args:
30
+ host: The hostname or IP address.
31
+ port: The port number (int or string).
32
+
33
+ Returns:
34
+ Formatted address string (e.g., "localhost:8000" or "[::1]:8000").
35
+ """
36
+ return _build_address(host, port)
37
+
38
+
39
+ def node_ip_address_from_perspective(address: Optional[str] = None) -> str:
40
+ """IP address by which the local node can be reached *from* the `address`.
41
+
42
+ If no address is given, defaults to public DNS servers for detection.
43
+
44
+ Args:
45
+ address: The IP address and port of any known live service on the
46
+ network you care about.
47
+
48
+ Returns:
49
+ The IP address by which the local node can be reached from the address.
50
+ """
51
+ return _node_ip_address_from_perspective(address)
52
+
53
+
54
+ def is_ipv6(host: str) -> bool:
55
+ """Check if a host is resolved to IPv6.
56
+
57
+ Args:
58
+ host: The IP or domain name to check (must be without port).
59
+
60
+ Returns:
61
+ True if the host is resolved to IPv6, False if IPv4.
62
+ """
63
+ return _is_ipv6(host)
64
+
65
+
66
+ @lru_cache(maxsize=1)
67
+ def get_localhost_ip() -> str:
68
+ """Get localhost loopback ip with IPv4/IPv6 support.
69
+
70
+ Returns:
71
+ The localhost loopback IP.
72
+ """
73
+ # Try IPv4 first, then IPv6 localhost resolution
74
+ for family in [socket.AF_INET, socket.AF_INET6]:
75
+ try:
76
+ dns_result = socket.getaddrinfo(
77
+ "localhost", None, family, socket.SOCK_STREAM
78
+ )
79
+ return dns_result[0][4][0]
80
+ except Exception:
81
+ continue
82
+
83
+ # Final fallback to IPv4 loopback
84
+ return "127.0.0.1"
85
+
86
+
87
+ def is_localhost(host: str) -> bool:
88
+ """Check if the given host string represents a localhost address.
89
+
90
+ Args:
91
+ host: The hostname or IP address to check.
92
+
93
+ Returns:
94
+ True if the host is a localhost address, False otherwise.
95
+ """
96
+ return host in ("localhost", "127.0.0.1", "::1")
97
+
98
+
99
+ def find_free_port(family: socket.AddressFamily = socket.AF_INET) -> int:
100
+ """Find a free port on the local machine.
101
+
102
+ Args:
103
+ family: The socket address family (AF_INET for IPv4, AF_INET6 for IPv6).
104
+ Defaults to AF_INET.
105
+
106
+ Returns:
107
+ An available port number.
108
+ """
109
+ with closing(socket.socket(family, socket.SOCK_STREAM)) as s:
110
+ s.bind(("", 0))
111
+ return s.getsockname()[1]
lib/python3.12/site-packages/ray/_common/pydantic_compat.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ruff: noqa
2
+ import packaging.version
3
+
4
+ # Pydantic is a dependency of `ray["default"]` but not the minimal installation,
5
+ # so handle the case where it isn't installed.
6
+ try:
7
+ import pydantic
8
+
9
+ PYDANTIC_INSTALLED = True
10
+ except ImportError:
11
+ pydantic = None
12
+ PYDANTIC_INSTALLED = False
13
+
14
+
15
+ if not PYDANTIC_INSTALLED:
16
+ IS_PYDANTIC_2 = False
17
+ BaseModel = None
18
+ Extra = None
19
+ Field = None
20
+ NonNegativeFloat = None
21
+ NonNegativeInt = None
22
+ PositiveFloat = None
23
+ PositiveInt = None
24
+ PrivateAttr = None
25
+ StrictInt = None
26
+ ValidationError = None
27
+ root_validator = None
28
+ validator = None
29
+ is_subclass_of_base_model = lambda obj: False
30
+ # In pydantic <1.9.0, __version__ attribute is missing, issue ref:
31
+ # https://github.com/pydantic/pydantic/issues/2572, so we need to check
32
+ # the existence prior to comparison.
33
+ elif not hasattr(pydantic, "__version__") or packaging.version.parse(
34
+ pydantic.__version__
35
+ ) < packaging.version.parse("2.0"):
36
+ IS_PYDANTIC_2 = False
37
+ from pydantic import (
38
+ BaseModel,
39
+ Extra,
40
+ Field,
41
+ NonNegativeFloat,
42
+ NonNegativeInt,
43
+ PositiveFloat,
44
+ PositiveInt,
45
+ PrivateAttr,
46
+ StrictInt,
47
+ ValidationError,
48
+ root_validator,
49
+ validator,
50
+ )
51
+
52
+ def is_subclass_of_base_model(obj):
53
+ return issubclass(obj, BaseModel)
54
+
55
+ else:
56
+ IS_PYDANTIC_2 = True
57
+ from pydantic.v1 import (
58
+ BaseModel,
59
+ Extra,
60
+ Field,
61
+ NonNegativeFloat,
62
+ NonNegativeInt,
63
+ PositiveFloat,
64
+ PositiveInt,
65
+ PrivateAttr,
66
+ StrictInt,
67
+ ValidationError,
68
+ root_validator,
69
+ validator,
70
+ )
71
+
72
+ def is_subclass_of_base_model(obj):
73
+ from pydantic import BaseModel as BaseModelV2
74
+ from pydantic.v1 import BaseModel as BaseModelV1
75
+
76
+ return issubclass(obj, BaseModelV1) or issubclass(obj, BaseModelV2)
77
+
78
+
79
+ def register_pydantic_serializers(serialization_context):
80
+ if not PYDANTIC_INSTALLED:
81
+ return
82
+
83
+ if IS_PYDANTIC_2:
84
+ # TODO(edoakes): compare against the version that has the fixes.
85
+ from pydantic.v1.fields import ModelField
86
+ else:
87
+ from pydantic.fields import ModelField
88
+
89
+ # Pydantic's Cython validators are not serializable.
90
+ # https://github.com/cloudpipe/cloudpickle/issues/408
91
+ serialization_context._register_cloudpickle_serializer(
92
+ ModelField,
93
+ custom_serializer=lambda o: {
94
+ "name": o.name,
95
+ # outer_type_ is the original type for ModelFields,
96
+ # while type_ can be updated later with the nested type
97
+ # like int for List[int].
98
+ "type_": o.outer_type_,
99
+ "class_validators": o.class_validators,
100
+ "model_config": o.model_config,
101
+ "default": o.default,
102
+ "default_factory": o.default_factory,
103
+ "required": o.required,
104
+ "alias": o.alias,
105
+ "field_info": o.field_info,
106
+ },
107
+ custom_deserializer=lambda kwargs: ModelField(**kwargs),
108
+ )
lib/python3.12/site-packages/ray/_common/ray_constants.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # Default max_concurrency option in @ray.remote for async actors.
2
+ DEFAULT_MAX_CONCURRENCY_ASYNC = 1000
3
+
4
+ LOGGING_ROTATE_BYTES = 512 * 1024 * 1024 # 512MB.
5
+ LOGGING_ROTATE_BACKUP_COUNT = 5 # 5 Backup files at max.
lib/python3.12/site-packages/ray/_common/ray_option_utils.py ADDED
@@ -0,0 +1,384 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Manage, parse and validate options for Ray tasks, actors and actor methods."""
2
+ import warnings
3
+ from dataclasses import dataclass
4
+ from typing import Any, Callable, Dict, Optional, Tuple, Union
5
+
6
+ import ray
7
+ from ray._private import ray_constants
8
+ from ray._private.label_utils import (
9
+ validate_fallback_strategy,
10
+ validate_label_selector,
11
+ )
12
+ from ray._private.utils import get_ray_doc_version
13
+ from ray.util.placement_group import PlacementGroup
14
+ from ray.util.scheduling_strategies import (
15
+ NodeAffinitySchedulingStrategy,
16
+ NodeLabelSchedulingStrategy,
17
+ PlacementGroupSchedulingStrategy,
18
+ )
19
+
20
+
21
+ @dataclass
22
+ class Option:
23
+ # Type constraint of an option.
24
+ type_constraint: Optional[Union[type, Tuple[type]]] = None
25
+ # Value constraint of an option.
26
+ # The callable should return None if there is no error.
27
+ # Otherwise, return the error message.
28
+ value_constraint: Optional[Callable[[Any], Optional[str]]] = None
29
+ # Default value.
30
+ default_value: Any = None
31
+
32
+ def validate(self, keyword: str, value: Any):
33
+ """Validate the option."""
34
+ if self.type_constraint is not None:
35
+ if not isinstance(value, self.type_constraint):
36
+ raise TypeError(
37
+ f"The type of keyword '{keyword}' must be {self.type_constraint}, "
38
+ f"but received type {type(value)}"
39
+ )
40
+ if self.value_constraint is not None:
41
+ possible_error_message = self.value_constraint(value)
42
+ if possible_error_message:
43
+ raise ValueError(possible_error_message)
44
+
45
+
46
+ def _counting_option(name: str, infinite: bool = True, default_value: Any = None):
47
+ """This is used for positive and discrete options.
48
+
49
+ Args:
50
+ name: The name of the option keyword.
51
+ infinite: If True, user could use -1 to represent infinity.
52
+ default_value: The default value for this option.
53
+
54
+ Returns:
55
+ An Option object.
56
+ """
57
+ if infinite:
58
+ return Option(
59
+ (int, type(None)),
60
+ lambda x: None
61
+ if (x is None or x >= -1)
62
+ else f"The keyword '{name}' only accepts None, 0, -1"
63
+ " or a positive integer, where -1 represents infinity.",
64
+ default_value=default_value,
65
+ )
66
+ return Option(
67
+ (int, type(None)),
68
+ lambda x: None
69
+ if (x is None or x >= 0)
70
+ else f"The keyword '{name}' only accepts None, 0 or a positive integer.",
71
+ default_value=default_value,
72
+ )
73
+
74
+
75
+ def _validate_resource_quantity(name, quantity):
76
+ if quantity < 0:
77
+ return f"The quantity of resource {name} cannot be negative"
78
+ if (
79
+ isinstance(quantity, float)
80
+ and quantity != 0.0
81
+ and int(quantity * ray._raylet.RESOURCE_UNIT_SCALING) == 0
82
+ ):
83
+ return (
84
+ f"The precision of the fractional quantity of resource {name}"
85
+ " cannot go beyond 0.0001"
86
+ )
87
+ resource_name = "GPU" if name == "num_gpus" else name
88
+ if resource_name in ray._private.accelerators.get_all_accelerator_resource_names():
89
+ (
90
+ valid,
91
+ error_message,
92
+ ) = ray._private.accelerators.get_accelerator_manager_for_resource(
93
+ resource_name
94
+ ).validate_resource_request_quantity(
95
+ quantity
96
+ )
97
+ if not valid:
98
+ return error_message
99
+ return None
100
+
101
+
102
+ def _resource_option(name: str, default_value: Any = None):
103
+ """This is used for resource related options."""
104
+ return Option(
105
+ (float, int, type(None)),
106
+ lambda x: None if (x is None) else _validate_resource_quantity(name, x),
107
+ default_value=default_value,
108
+ )
109
+
110
+
111
+ def _validate_resources(resources: Optional[Dict[str, float]]) -> Optional[str]:
112
+ if resources is None:
113
+ return None
114
+
115
+ if "CPU" in resources or "GPU" in resources:
116
+ return (
117
+ "Use the 'num_cpus' and 'num_gpus' keyword instead of 'CPU' and 'GPU' "
118
+ "in 'resources' keyword"
119
+ )
120
+
121
+ for name, quantity in resources.items():
122
+ possible_error_message = _validate_resource_quantity(name, quantity)
123
+ if possible_error_message:
124
+ return possible_error_message
125
+
126
+ return None
127
+
128
+
129
+ _common_options = {
130
+ "label_selector": Option((dict, type(None)), lambda x: validate_label_selector(x)),
131
+ "fallback_strategy": Option(
132
+ (list, type(None)), lambda x: validate_fallback_strategy(x)
133
+ ),
134
+ "accelerator_type": Option((str, type(None))),
135
+ "memory": _resource_option("memory"),
136
+ "name": Option((str, type(None))),
137
+ "num_cpus": _resource_option("num_cpus"),
138
+ "num_gpus": _resource_option("num_gpus"),
139
+ "object_store_memory": _counting_option("object_store_memory", False),
140
+ # TODO(suquark): "placement_group", "placement_group_bundle_index"
141
+ # and "placement_group_capture_child_tasks" are deprecated,
142
+ # use "scheduling_strategy" instead.
143
+ "placement_group": Option(
144
+ (type(None), str, PlacementGroup), default_value="default"
145
+ ),
146
+ "placement_group_bundle_index": Option(int, default_value=-1),
147
+ "placement_group_capture_child_tasks": Option((bool, type(None))),
148
+ "resources": Option((dict, type(None)), lambda x: _validate_resources(x)),
149
+ "runtime_env": Option((dict, type(None))),
150
+ "scheduling_strategy": Option(
151
+ (
152
+ type(None),
153
+ str,
154
+ PlacementGroupSchedulingStrategy,
155
+ NodeAffinitySchedulingStrategy,
156
+ NodeLabelSchedulingStrategy,
157
+ )
158
+ ),
159
+ "enable_task_events": Option(bool, default_value=True),
160
+ "_labels": Option((dict, type(None))),
161
+ }
162
+
163
+
164
+ def issubclass_safe(obj: Any, cls_: type) -> bool:
165
+ try:
166
+ return issubclass(obj, cls_)
167
+ except TypeError:
168
+ return False
169
+
170
+
171
+ _task_only_options = {
172
+ "max_calls": _counting_option("max_calls", False, default_value=0),
173
+ # Normal tasks may be retried on failure this many times.
174
+ # TODO(swang): Allow this to be set globally for an application.
175
+ "max_retries": _counting_option(
176
+ "max_retries", default_value=ray_constants.DEFAULT_TASK_MAX_RETRIES
177
+ ),
178
+ # override "_common_options"
179
+ "num_cpus": _resource_option("num_cpus", default_value=1),
180
+ "num_returns": Option(
181
+ (int, str, type(None)),
182
+ lambda x: None
183
+ if (x is None or x == "dynamic" or x == "streaming" or x >= 0)
184
+ else "Default None. When None is passed, "
185
+ "The default value is 1 for a task and actor task, and "
186
+ "'streaming' for generator tasks and generator actor tasks. "
187
+ "The keyword 'num_returns' only accepts None, "
188
+ "a non-negative integer, "
189
+ "'streaming' (for generators), or 'dynamic'. 'dynamic' flag "
190
+ "will be deprecated in the future, and it is recommended to use "
191
+ "'streaming' instead.",
192
+ default_value=None,
193
+ ),
194
+ "object_store_memory": Option( # override "_common_options"
195
+ (int, type(None)),
196
+ lambda x: None
197
+ if (x is None)
198
+ else "Setting 'object_store_memory' is not implemented for tasks",
199
+ ),
200
+ "retry_exceptions": Option(
201
+ (bool, list, tuple),
202
+ lambda x: None
203
+ if (
204
+ isinstance(x, bool)
205
+ or (
206
+ isinstance(x, (list, tuple))
207
+ and all(issubclass_safe(x_, Exception) for x_ in x)
208
+ )
209
+ )
210
+ else "retry_exceptions must be either a boolean or a list of exceptions",
211
+ default_value=False,
212
+ ),
213
+ "_generator_backpressure_num_objects": Option(
214
+ (int, type(None)),
215
+ lambda x: None
216
+ if x != 0
217
+ else (
218
+ "_generator_backpressure_num_objects=0 is not allowed. "
219
+ "Use a value > 0. If the value is equal to 1, the behavior "
220
+ "is identical to Python generator (generator 1 object "
221
+ "whenever `next` is called). Use -1 to disable this feature. "
222
+ ),
223
+ ),
224
+ }
225
+
226
+ _actor_only_options = {
227
+ "concurrency_groups": Option((list, dict, type(None))),
228
+ "enable_tensor_transport": Option((bool, type(None)), default_value=None),
229
+ "lifetime": Option(
230
+ (str, type(None)),
231
+ lambda x: None
232
+ if x in (None, "detached", "non_detached")
233
+ else "actor `lifetime` argument must be one of 'detached', "
234
+ "'non_detached' and 'None'.",
235
+ ),
236
+ "max_concurrency": _counting_option("max_concurrency", False),
237
+ "max_restarts": _counting_option("max_restarts", default_value=0),
238
+ "max_task_retries": _counting_option("max_task_retries", default_value=0),
239
+ "max_pending_calls": _counting_option("max_pending_calls", default_value=-1),
240
+ "namespace": Option((str, type(None))),
241
+ "get_if_exists": Option(bool, default_value=False),
242
+ "allow_out_of_order_execution": Option((bool, type(None))),
243
+ }
244
+
245
+ # Priority is important here because during dictionary update, same key with higher
246
+ # priority overrides the same key with lower priority. We make use of priority
247
+ # to set the correct default value for tasks / actors.
248
+
249
+ # priority: _common_options > _actor_only_options > _task_only_options
250
+ valid_options: Dict[str, Option] = {
251
+ **_task_only_options,
252
+ **_actor_only_options,
253
+ **_common_options,
254
+ }
255
+ # priority: _task_only_options > _common_options
256
+ task_options: Dict[str, Option] = {**_common_options, **_task_only_options}
257
+ # priority: _actor_only_options > _common_options
258
+ actor_options: Dict[str, Option] = {**_common_options, **_actor_only_options}
259
+
260
+ remote_args_error_string = (
261
+ "The @ray.remote decorator must be applied either with no arguments and no "
262
+ "parentheses, for example '@ray.remote', or it must be applied using some of "
263
+ f"the arguments in the list {list(valid_options.keys())}, for example "
264
+ "'@ray.remote(num_returns=2, resources={\"CustomResource\": 1})'."
265
+ )
266
+
267
+
268
+ def _check_deprecate_placement_group(options: Dict[str, Any]):
269
+ """Check if deprecated placement group option exists."""
270
+ placement_group = options.get("placement_group", "default")
271
+ scheduling_strategy = options.get("scheduling_strategy")
272
+ # TODO(suquark): @ray.remote(placement_group=None) is used in
273
+ # "python/ray.data._internal/remote_fn.py" and many other places,
274
+ # while "ray.data.read_api.read_datasource" set "scheduling_strategy=SPREAD".
275
+ # This might be a bug, but it is also ok to allow them co-exist.
276
+ if (placement_group not in ("default", None)) and (scheduling_strategy is not None):
277
+ raise ValueError(
278
+ "Placement groups should be specified via the "
279
+ "scheduling_strategy option. "
280
+ "The placement_group option is deprecated."
281
+ )
282
+
283
+
284
+ def _warn_if_using_deprecated_placement_group(
285
+ options: Dict[str, Any], caller_stacklevel: int
286
+ ):
287
+ placement_group = options["placement_group"]
288
+ placement_group_bundle_index = options["placement_group_bundle_index"]
289
+ placement_group_capture_child_tasks = options["placement_group_capture_child_tasks"]
290
+ if placement_group != "default":
291
+ warnings.warn(
292
+ "placement_group parameter is deprecated. Use "
293
+ "scheduling_strategy=PlacementGroupSchedulingStrategy(...) "
294
+ "instead, see the usage at "
295
+ f"https://docs.ray.io/en/{get_ray_doc_version()}/ray-core/package-ref.html#ray-remote.", # noqa: E501
296
+ DeprecationWarning,
297
+ stacklevel=caller_stacklevel + 1,
298
+ )
299
+ if placement_group_bundle_index != -1:
300
+ warnings.warn(
301
+ "placement_group_bundle_index parameter is deprecated. Use "
302
+ "scheduling_strategy=PlacementGroupSchedulingStrategy(...) "
303
+ "instead, see the usage at "
304
+ f"https://docs.ray.io/en/{get_ray_doc_version()}/ray-core/package-ref.html#ray-remote.", # noqa: E501
305
+ DeprecationWarning,
306
+ stacklevel=caller_stacklevel + 1,
307
+ )
308
+ if placement_group_capture_child_tasks:
309
+ warnings.warn(
310
+ "placement_group_capture_child_tasks parameter is deprecated. Use "
311
+ "scheduling_strategy=PlacementGroupSchedulingStrategy(...) "
312
+ "instead, see the usage at "
313
+ f"https://docs.ray.io/en/{get_ray_doc_version()}/ray-core/package-ref.html#ray-remote.", # noqa: E501
314
+ DeprecationWarning,
315
+ stacklevel=caller_stacklevel + 1,
316
+ )
317
+
318
+
319
+ def validate_task_options(options: Dict[str, Any], in_options: bool):
320
+ """Options check for Ray tasks.
321
+
322
+ Args:
323
+ options: Options for Ray tasks.
324
+ in_options: If True, we are checking the options under the context of
325
+ ".options()".
326
+ """
327
+ for k, v in options.items():
328
+ if k not in task_options:
329
+ raise ValueError(
330
+ f"Invalid option keyword {k} for remote functions. "
331
+ f"Valid ones are {list(task_options)}."
332
+ )
333
+ task_options[k].validate(k, v)
334
+ if in_options and "max_calls" in options:
335
+ raise ValueError("Setting 'max_calls' is not supported in '.options()'.")
336
+ _check_deprecate_placement_group(options)
337
+
338
+
339
+ def validate_actor_options(options: Dict[str, Any], in_options: bool):
340
+ """Options check for Ray actors.
341
+
342
+ Args:
343
+ options: Options for Ray actors.
344
+ in_options: If True, we are checking the options under the context of
345
+ ".options()".
346
+ """
347
+ for k, v in options.items():
348
+ if k not in actor_options:
349
+ raise ValueError(
350
+ f"Invalid option keyword {k} for actors. "
351
+ f"Valid ones are {list(actor_options)}."
352
+ )
353
+ actor_options[k].validate(k, v)
354
+
355
+ if in_options and "concurrency_groups" in options:
356
+ raise ValueError(
357
+ "Setting 'concurrency_groups' is not supported in '.options()'."
358
+ )
359
+
360
+ if options.get("get_if_exists") and not options.get("name"):
361
+ raise ValueError("The actor name must be specified to use `get_if_exists`.")
362
+
363
+ if "object_store_memory" in options:
364
+ warnings.warn(
365
+ "Setting 'object_store_memory'"
366
+ " for actors is deprecated since it doesn't actually"
367
+ " reserve the required object store memory."
368
+ f" Use object spilling that's enabled by default (https://docs.ray.io/en/{get_ray_doc_version()}/ray-core/objects/object-spilling.html) " # noqa: E501
369
+ "instead to bypass the object store memory size limitation.",
370
+ DeprecationWarning,
371
+ stacklevel=1,
372
+ )
373
+
374
+ _check_deprecate_placement_group(options)
375
+
376
+
377
+ def update_options(
378
+ original_options: Dict[str, Any], new_options: Dict[str, Any]
379
+ ) -> Dict[str, Any]:
380
+ """Update original options with new options and return.
381
+ The returned updated options contain shallow copy of original options.
382
+ """
383
+
384
+ return {**original_options, **new_options}
lib/python3.12/site-packages/ray/_common/retry.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import functools
2
+ import logging
3
+ import random
4
+ import time
5
+ from typing import Any, Callable, List, Optional
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ def call_with_retry(
11
+ f: Callable,
12
+ description: str,
13
+ match: Optional[List[str]] = None,
14
+ max_attempts: int = 10,
15
+ max_backoff_s: int = 32,
16
+ *args,
17
+ **kwargs,
18
+ ) -> Any:
19
+ """Retry a function with exponential backoff.
20
+
21
+ Args:
22
+ f: The function to retry.
23
+ description: An imperative description of the function being retried. For
24
+ example, "open the file".
25
+ match: A list of strings to match in the exception message. If ``None``, any
26
+ error is retried.
27
+ max_attempts: The maximum number of attempts to retry.
28
+ max_backoff_s: The maximum number of seconds to backoff.
29
+ *args: Arguments to pass to the function.
30
+ **kwargs: Keyword arguments to pass to the function.
31
+
32
+ Returns:
33
+ The result of the function.
34
+ """
35
+ # TODO: consider inverse match and matching exception type
36
+ assert max_attempts >= 1, f"`max_attempts` must be positive. Got {max_attempts}."
37
+
38
+ for i in range(max_attempts):
39
+ try:
40
+ return f(*args, **kwargs)
41
+ except Exception as e:
42
+ exception_str = str(e)
43
+ is_retryable = match is None or any(
44
+ pattern in exception_str for pattern in match
45
+ )
46
+ if is_retryable and i + 1 < max_attempts:
47
+ # Retry with binary exponential backoff with 20% random jitter.
48
+ backoff = min(2**i, max_backoff_s) * (random.uniform(0.8, 1.2))
49
+ logger.debug(
50
+ f"Retrying {i+1} attempts to {description} after {backoff} seconds."
51
+ )
52
+ time.sleep(backoff)
53
+ else:
54
+ if is_retryable:
55
+ logger.debug(
56
+ f"Failed to {description} after {max_attempts} attempts. Raising."
57
+ )
58
+ else:
59
+ logger.debug(
60
+ f"Did not find a match for {exception_str}. Raising after {i+1} attempts."
61
+ )
62
+ raise e from None
63
+
64
+
65
+ def retry(
66
+ description: str,
67
+ match: Optional[List[str]] = None,
68
+ max_attempts: int = 10,
69
+ max_backoff_s: int = 32,
70
+ ) -> Callable:
71
+ """Decorator-based version of call_with_retry."""
72
+
73
+ def decorator(func: Callable) -> Callable:
74
+ @functools.wraps(func)
75
+ def inner(*args, **kwargs):
76
+ return call_with_retry(
77
+ func, description, match, max_attempts, max_backoff_s, *args, **kwargs
78
+ )
79
+
80
+ return inner
81
+
82
+ return decorator
lib/python3.12/site-packages/ray/_common/serialization.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import logging
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ if TYPE_CHECKING:
6
+ pass
7
+
8
+
9
+ import ray._private.utils
10
+ import ray.cloudpickle as pickle
11
+ import ray.exceptions
12
+ from ray._private import ray_constants
13
+ from ray.util import inspect_serializability
14
+
15
+ logger = logging.getLogger(__name__)
16
+ ALLOW_OUT_OF_BAND_OBJECT_REF_SERIALIZATION = ray_constants.env_bool(
17
+ "RAY_allow_out_of_band_object_ref_serialization", True
18
+ )
19
+
20
+
21
+ def pickle_dumps(obj: Any, error_msg: str):
22
+ """Wrap cloudpickle.dumps to provide better error message
23
+ when the object is not serializable.
24
+ """
25
+ try:
26
+ return pickle.dumps(obj)
27
+ except (TypeError, ray.exceptions.OufOfBandObjectRefSerializationException) as e:
28
+ sio = io.StringIO()
29
+ inspect_serializability(obj, print_file=sio)
30
+ msg = f"{error_msg}:\n{sio.getvalue()}"
31
+ if isinstance(e, TypeError):
32
+ raise TypeError(msg) from e
33
+ else:
34
+ raise ray.exceptions.OufOfBandObjectRefSerializationException(msg)
lib/python3.12/site-packages/ray/_common/signature.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import inspect
2
+ import logging
3
+ from inspect import Parameter
4
+ from typing import Any, Dict, List, Tuple
5
+
6
+ from ray._private.inspect_util import is_cython
7
+
8
+ # Logger for this module. It should be configured at the entry point
9
+ # into the program using Ray. Ray provides a default configuration at
10
+ # entry/init points.
11
+ logger = logging.getLogger(__name__)
12
+
13
+ # This dummy type is also defined in ArgumentsBuilder.java. Please keep it
14
+ # synced.
15
+ DUMMY_TYPE = b"__RAY_DUMMY__"
16
+
17
+
18
+ def get_signature(func: Any) -> inspect.Signature:
19
+ """Get signature parameters.
20
+
21
+ Support Cython functions by grabbing relevant attributes from the Cython
22
+ function and attaching to a no-op function. This is somewhat brittle, since
23
+ inspect may change, but given that inspect is written to a PEP, we hope
24
+ it is relatively stable. Future versions of Python may allow overloading
25
+ the inspect 'isfunction' and 'ismethod' functions / create ABC for Python
26
+ functions. Until then, it appears that Cython won't do anything about
27
+ compatability with the inspect module.
28
+
29
+ Args:
30
+ func: The function whose signature should be checked.
31
+
32
+ Returns:
33
+ A function signature object, which includes the names of the keyword
34
+ arguments as well as their default values.
35
+
36
+ Raises:
37
+ TypeError: A type error if the signature is not supported
38
+ """
39
+ # The first condition for Cython functions, the latter for Cython instance
40
+ # methods
41
+ if is_cython(func):
42
+ attrs = ["__code__", "__annotations__", "__defaults__", "__kwdefaults__"]
43
+
44
+ if all(hasattr(func, attr) for attr in attrs):
45
+ original_func = func
46
+
47
+ def func():
48
+ return
49
+
50
+ for attr in attrs:
51
+ setattr(func, attr, getattr(original_func, attr))
52
+ else:
53
+ raise TypeError(f"{func!r} is not a Python function we can process")
54
+
55
+ return inspect.signature(func)
56
+
57
+
58
+ def extract_signature(func: Any, ignore_first: bool = False) -> List[Parameter]:
59
+ """Extract the function signature from the function.
60
+
61
+ Args:
62
+ func: The function whose signature should be extracted.
63
+ ignore_first: True if the first argument should be ignored. This should
64
+ be used when func is a method of a class.
65
+
66
+ Returns:
67
+ List of Parameter objects representing the function signature.
68
+ """
69
+ signature_parameters = list(get_signature(func).parameters.values())
70
+
71
+ if ignore_first:
72
+ if len(signature_parameters) == 0:
73
+ raise ValueError(
74
+ "Methods must take a 'self' argument, but the "
75
+ f"method '{func.__name__}' does not have one."
76
+ )
77
+ signature_parameters = signature_parameters[1:]
78
+
79
+ return signature_parameters
80
+
81
+
82
+ def validate_args(
83
+ signature_parameters: List[Parameter], args: Tuple[Any, ...], kwargs: Dict[str, Any]
84
+ ) -> None:
85
+ """Validates the arguments against the signature.
86
+
87
+ Args:
88
+ signature_parameters: The list of Parameter objects
89
+ representing the function signature, obtained from
90
+ `extract_signature`.
91
+ args: The positional arguments passed into the function.
92
+ kwargs: The keyword arguments passed into the function.
93
+
94
+ Raises:
95
+ TypeError: Raised if arguments do not fit in the function signature.
96
+ """
97
+ reconstructed_signature = inspect.Signature(parameters=signature_parameters)
98
+ try:
99
+ reconstructed_signature.bind(*args, **kwargs)
100
+ except TypeError as exc: # capture a friendlier stacktrace
101
+ raise TypeError(str(exc)) from None
102
+
103
+
104
+ def flatten_args(
105
+ signature_parameters: List[Parameter], args: Tuple[Any, ...], kwargs: Dict[str, Any]
106
+ ) -> List[Any]:
107
+ """Validates the arguments against the signature and flattens them.
108
+
109
+ The flat list representation is a serializable format for arguments.
110
+ Since the flatbuffer representation of function arguments is a list, we
111
+ combine both keyword arguments and positional arguments. We represent
112
+ this with two entries per argument value - [DUMMY_TYPE, x] for positional
113
+ arguments and [KEY, VALUE] for keyword arguments. See the below example.
114
+ See `recover_args` for logic restoring the flat list back to args/kwargs.
115
+
116
+ Args:
117
+ signature_parameters: The list of Parameter objects
118
+ representing the function signature, obtained from
119
+ `extract_signature`.
120
+ args: The positional arguments passed into the function.
121
+ kwargs: The keyword arguments passed into the function.
122
+
123
+ Returns:
124
+ List of args and kwargs. Non-keyword arguments are prefixed
125
+ by internal enum DUMMY_TYPE.
126
+
127
+ Raises:
128
+ TypeError: Raised if arguments do not fit in the function signature.
129
+ """
130
+ validate_args(signature_parameters, args, kwargs)
131
+ list_args = []
132
+ for arg in args:
133
+ list_args += [DUMMY_TYPE, arg]
134
+
135
+ for keyword, arg in kwargs.items():
136
+ list_args += [keyword, arg]
137
+ return list_args
138
+
139
+
140
+ def recover_args(flattened_args: List[Any]) -> Tuple[List[Any], Dict[str, Any]]:
141
+ """Recreates `args` and `kwargs` from the flattened arg list.
142
+
143
+ Args:
144
+ flattened_args: List of args and kwargs. This should be the output of
145
+ `flatten_args`.
146
+
147
+ Returns:
148
+ args: The non-keyword arguments passed into the function.
149
+ kwargs: The keyword arguments passed into the function.
150
+ """
151
+ assert (
152
+ len(flattened_args) % 2 == 0
153
+ ), "Flattened arguments need to be even-numbered. See `flatten_args`."
154
+ args = []
155
+ kwargs = {}
156
+ for name_index in range(0, len(flattened_args), 2):
157
+ name, arg = flattened_args[name_index], flattened_args[name_index + 1]
158
+ if name == DUMMY_TYPE:
159
+ args.append(arg)
160
+ else:
161
+ kwargs[name] = arg
162
+
163
+ return args, kwargs
lib/python3.12/site-packages/ray/_common/test_utils.py ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Test utilities for Ray.
2
+
3
+ This module contains test utility classes that are distributed with the Ray package
4
+ and can be used by external libraries and tests. These utilities must remain in
5
+ _common/ (not in tests/) to be accessible in the Ray package distribution.
6
+ """
7
+
8
+ import asyncio
9
+ import inspect
10
+ import os
11
+ import time
12
+ import traceback
13
+ import uuid
14
+ from collections.abc import Awaitable
15
+ from contextlib import contextmanager
16
+ from enum import Enum
17
+ from typing import Any, Callable, Dict, Iterator, List, Optional, Set
18
+
19
+ import ray
20
+ import ray._common.usage.usage_lib as ray_usage_lib
21
+ import ray._private.utils
22
+ from ray._common.network_utils import build_address
23
+
24
+
25
+ @ray.remote(num_cpus=0)
26
+ class SignalActor:
27
+ """A Ray actor for coordinating test execution through signals.
28
+
29
+ Useful for testing async coordination, waiting for specific states,
30
+ and synchronizing multiple actors or tasks in tests.
31
+ """
32
+
33
+ def __init__(self):
34
+ self.ready_event = asyncio.Event()
35
+ self.num_waiters = 0
36
+
37
+ def send(self, clear: bool = False):
38
+ self.ready_event.set()
39
+ if clear:
40
+ self.ready_event.clear()
41
+
42
+ async def wait(self, should_wait: bool = True):
43
+ if should_wait:
44
+ self.num_waiters += 1
45
+ await self.ready_event.wait()
46
+ self.num_waiters -= 1
47
+
48
+ async def cur_num_waiters(self) -> int:
49
+ return self.num_waiters
50
+
51
+
52
+ @ray.remote(num_cpus=0)
53
+ class Semaphore:
54
+ """A Ray actor implementing a semaphore for test coordination.
55
+
56
+ Useful for testing resource limiting, concurrency control,
57
+ and coordination between multiple actors or tasks.
58
+ """
59
+
60
+ def __init__(self, value: int = 1):
61
+ self._sema = asyncio.Semaphore(value=value)
62
+
63
+ async def acquire(self):
64
+ await self._sema.acquire()
65
+
66
+ async def release(self):
67
+ self._sema.release()
68
+
69
+ async def locked(self) -> bool:
70
+ return self._sema.locked()
71
+
72
+
73
+ __all__ = ["SignalActor", "Semaphore"]
74
+
75
+
76
+ def wait_for_condition(
77
+ condition_predictor: Callable[..., bool],
78
+ timeout: float = 10,
79
+ retry_interval_ms: float = 100,
80
+ raise_exceptions: bool = False,
81
+ **kwargs: Any,
82
+ ):
83
+ """Wait until a condition is met or time out with an exception.
84
+
85
+ Args:
86
+ condition_predictor: A function that predicts the condition.
87
+ timeout: Maximum timeout in seconds.
88
+ retry_interval_ms: Retry interval in milliseconds.
89
+ raise_exceptions: If true, exceptions that occur while executing
90
+ condition_predictor won't be caught and instead will be raised.
91
+ **kwargs: Arguments to pass to the condition_predictor.
92
+
93
+ Returns:
94
+ None: Returns when the condition is met.
95
+
96
+ Raises:
97
+ RuntimeError: If the condition is not met before the timeout expires.
98
+ """
99
+ start = time.time()
100
+ last_ex = None
101
+ while time.time() - start <= timeout:
102
+ try:
103
+ if condition_predictor(**kwargs):
104
+ return
105
+ except Exception:
106
+ if raise_exceptions:
107
+ raise
108
+ last_ex = ray._private.utils.format_error_message(traceback.format_exc())
109
+ time.sleep(retry_interval_ms / 1000.0)
110
+ message = "The condition wasn't met before the timeout expired."
111
+ if last_ex is not None:
112
+ message += f" Last exception: {last_ex}"
113
+ raise RuntimeError(message)
114
+
115
+
116
+ async def async_wait_for_condition(
117
+ condition_predictor: Callable[..., Awaitable[bool]],
118
+ timeout: float = 10,
119
+ retry_interval_ms: float = 100,
120
+ **kwargs: Any,
121
+ ):
122
+ """Wait until a condition is met or time out with an exception.
123
+
124
+ Args:
125
+ condition_predictor: A function that predicts the condition.
126
+ timeout: Maximum timeout in seconds.
127
+ retry_interval_ms: Retry interval in milliseconds.
128
+ **kwargs: Arguments to pass to the condition_predictor.
129
+
130
+ Returns:
131
+ None: Returns when the condition is met.
132
+
133
+ Raises:
134
+ RuntimeError: If the condition is not met before the timeout expires.
135
+ """
136
+ start = time.time()
137
+ last_ex = None
138
+ while time.time() - start <= timeout:
139
+ try:
140
+ if inspect.iscoroutinefunction(condition_predictor):
141
+ if await condition_predictor(**kwargs):
142
+ return
143
+ else:
144
+ if condition_predictor(**kwargs):
145
+ return
146
+ except Exception as ex:
147
+ last_ex = ex
148
+ await asyncio.sleep(retry_interval_ms / 1000.0)
149
+ message = "The condition wasn't met before the timeout expired."
150
+ if last_ex is not None:
151
+ message += f" Last exception: {last_ex}"
152
+ raise RuntimeError(message)
153
+
154
+
155
+ @contextmanager
156
+ def simulate_s3_bucket(
157
+ port: int = 5002,
158
+ region: str = "us-west-2",
159
+ ) -> Iterator[str]:
160
+ """Context manager that simulates an S3 bucket and yields the URI.
161
+
162
+ Args:
163
+ port: The port of the localhost endpoint where S3 is being served.
164
+ region: The S3 region.
165
+
166
+ Yields:
167
+ str: URI for the simulated S3 bucket.
168
+ """
169
+ from moto.server import ThreadedMotoServer
170
+
171
+ old_env = os.environ
172
+ os.environ["AWS_ACCESS_KEY_ID"] = "testing"
173
+ os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
174
+ os.environ["AWS_SECURITY_TOKEN"] = "testing"
175
+ os.environ["AWS_SESSION_TOKEN"] = "testing"
176
+
177
+ s3_server = f"http://{build_address('localhost', port)}"
178
+ server = ThreadedMotoServer(port=port)
179
+ server.start()
180
+ url = f"s3://{uuid.uuid4().hex}?region={region}&endpoint_override={s3_server}"
181
+ yield url
182
+ server.stop()
183
+ os.environ = old_env
184
+
185
+
186
+ class TelemetryCallsite(Enum):
187
+ DRIVER = "driver"
188
+ ACTOR = "actor"
189
+ TASK = "task"
190
+
191
+
192
+ def _get_library_usages() -> Set[str]:
193
+ return set(
194
+ ray_usage_lib.get_library_usages_to_report(
195
+ ray.experimental.internal_kv.internal_kv_get_gcs_client()
196
+ )
197
+ )
198
+
199
+
200
+ def _get_extra_usage_tags() -> Dict[str, str]:
201
+ return ray_usage_lib.get_extra_usage_tags_to_report(
202
+ ray.experimental.internal_kv.internal_kv_get_gcs_client()
203
+ )
204
+
205
+
206
+ def check_library_usage_telemetry(
207
+ use_lib_fn: Callable[[], None],
208
+ *,
209
+ callsite: TelemetryCallsite,
210
+ expected_library_usages: List[Set[str]],
211
+ expected_extra_usage_tags: Optional[Dict[str, str]] = None,
212
+ ):
213
+ """Helper for writing tests to validate library usage telemetry.
214
+
215
+ `use_lib_fn` is a callable that will be called from the provided callsite.
216
+ After calling it, the telemetry data to export will be validated against
217
+ expected_library_usages and expected_extra_usage_tags.
218
+ """
219
+ assert len(_get_library_usages()) == 0, _get_library_usages()
220
+
221
+ if callsite == TelemetryCallsite.DRIVER:
222
+ use_lib_fn()
223
+ elif callsite == TelemetryCallsite.ACTOR:
224
+
225
+ @ray.remote
226
+ class A:
227
+ def __init__(self):
228
+ use_lib_fn()
229
+
230
+ a = A.remote()
231
+ ray.get(a.__ray_ready__.remote())
232
+ elif callsite == TelemetryCallsite.TASK:
233
+
234
+ @ray.remote
235
+ def f():
236
+ use_lib_fn()
237
+
238
+ ray.get(f.remote())
239
+ else:
240
+ assert False, f"Unrecognized callsite: {callsite}"
241
+
242
+ library_usages = _get_library_usages()
243
+ extra_usage_tags = _get_extra_usage_tags()
244
+
245
+ assert library_usages in expected_library_usages, library_usages
246
+ if expected_extra_usage_tags:
247
+ assert all(
248
+ [extra_usage_tags[k] == v for k, v in expected_extra_usage_tags.items()]
249
+ ), extra_usage_tags
lib/python3.12/site-packages/ray/_common/usage/__init__.py ADDED
File without changes
lib/python3.12/site-packages/ray/_common/usage/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (196 Bytes). View file
 
lib/python3.12/site-packages/ray/_common/usage/__pycache__/usage_constants.cpython-312.pyc ADDED
Binary file (2.49 kB). View file
 
lib/python3.12/site-packages/ray/_common/usage/__pycache__/usage_lib.cpython-312.pyc ADDED
Binary file (41.6 kB). View file
 
lib/python3.12/site-packages/ray/_common/usage/usage_constants.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ SCHEMA_VERSION = "0.1"
2
+
3
+ # The key to store / obtain cluster metadata.
4
+ CLUSTER_METADATA_KEY = b"CLUSTER_METADATA"
5
+
6
+ # The name of a json file where usage stats will be written.
7
+ USAGE_STATS_FILE = "usage_stats.json"
8
+
9
+ USAGE_STATS_ENABLED_ENV_VAR = "RAY_USAGE_STATS_ENABLED"
10
+
11
+ USAGE_STATS_SOURCE_ENV_VAR = "RAY_USAGE_STATS_SOURCE"
12
+
13
+ USAGE_STATS_SOURCE_OSS = "OSS"
14
+
15
+ USAGE_STATS_ENABLED_FOR_CLI_MESSAGE = (
16
+ "Usage stats collection is enabled. To disable this, add `--disable-usage-stats` "
17
+ "to the command that starts the cluster, or run the following command:"
18
+ " `ray disable-usage-stats` before starting the cluster. "
19
+ "See https://docs.ray.io/en/master/cluster/usage-stats.html for more details."
20
+ )
21
+
22
+ USAGE_STATS_ENABLED_FOR_RAY_INIT_MESSAGE = (
23
+ "Usage stats collection is enabled. To disable this, run the following command:"
24
+ " `ray disable-usage-stats` before starting Ray. "
25
+ "See https://docs.ray.io/en/master/cluster/usage-stats.html for more details."
26
+ )
27
+
28
+ USAGE_STATS_DISABLED_MESSAGE = "Usage stats collection is disabled."
29
+
30
+ USAGE_STATS_ENABLED_BY_DEFAULT_FOR_CLI_MESSAGE = (
31
+ "Usage stats collection is enabled by default without user confirmation "
32
+ "because this terminal is detected to be non-interactive. "
33
+ "To disable this, add `--disable-usage-stats` to the command that starts "
34
+ "the cluster, or run the following command:"
35
+ " `ray disable-usage-stats` before starting the cluster. "
36
+ "See https://docs.ray.io/en/master/cluster/usage-stats.html for more details."
37
+ )
38
+
39
+ USAGE_STATS_ENABLED_BY_DEFAULT_FOR_RAY_INIT_MESSAGE = (
40
+ "Usage stats collection is enabled by default for nightly wheels. "
41
+ "To disable this, run the following command:"
42
+ " `ray disable-usage-stats` before starting Ray. "
43
+ "See https://docs.ray.io/en/master/cluster/usage-stats.html for more details."
44
+ )
45
+
46
+ USAGE_STATS_CONFIRMATION_MESSAGE = (
47
+ "Enable usage stats collection? "
48
+ "This prompt will auto-proceed in 10 seconds to avoid blocking cluster startup."
49
+ )
50
+
51
+ LIBRARY_USAGE_SET_NAME = "library_usage_"
52
+
53
+ HARDWARE_USAGE_SET_NAME = "hardware_usage_"
54
+
55
+ # Keep in-sync with the same constants defined in usage_stats_client.h
56
+ EXTRA_USAGE_TAG_PREFIX = "extra_usage_tag_"
57
+ USAGE_STATS_NAMESPACE = "usage_stats"
58
+
59
+ KUBERNETES_SERVICE_HOST_ENV = "KUBERNETES_SERVICE_HOST"
60
+ KUBERAY_ENV = "RAY_USAGE_STATS_KUBERAY_IN_USE"
61
+
62
+ PROVIDER_KUBERNETES_GENERIC = "kubernetes"
63
+ PROVIDER_KUBERAY = "kuberay"
lib/python3.12/site-packages/ray/_common/usage/usage_lib.py ADDED
@@ -0,0 +1,1016 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """This is the module that is in charge of Ray usage report (telemetry) APIs.
2
+
3
+ NOTE: Ray's usage report is currently "on by default".
4
+ One could opt-out, see details at https://docs.ray.io/en/master/cluster/usage-stats.html. # noqa
5
+
6
+ Ray usage report follows the specification from
7
+ https://docs.ray.io/en/master/cluster/usage-stats.html#usage-stats-collection # noqa
8
+
9
+ # Module
10
+
11
+ The module consists of 2 parts.
12
+
13
+ ## Public API
14
+ It contains public APIs to obtain usage report information.
15
+ APIs will be added before the usage report becomes opt-in by default.
16
+
17
+ ## Internal APIs for usage processing/report
18
+ The telemetry report consists of 5 components. This module is in charge of the top 2 layers.
19
+
20
+ Report -> usage_lib
21
+ ---------------------
22
+ Usage data processing -> usage_lib
23
+ ---------------------
24
+ Data storage -> Ray API server
25
+ ---------------------
26
+ Aggregation -> Ray API server (currently a dashboard server)
27
+ ---------------------
28
+ Usage data collection -> Various components (Ray agent, GCS, etc.) + usage_lib (cluster metadata).
29
+
30
+ Usage report is currently "off by default". You can enable the report by setting an environment variable
31
+ RAY_USAGE_STATS_ENABLED=1. For example, `RAY_USAGE_STATS_ENABLED=1 ray start --head`.
32
+ Or `RAY_USAGE_STATS_ENABLED=1 python [drivers with ray.init()]`.
33
+
34
+ "Ray API server (currently a dashboard server)" reports the usage data to https://usage-stats.ray.io/.
35
+
36
+ Data is reported every hour by default.
37
+
38
+ Note that it is also possible to configure the interval using the environment variable,
39
+ `RAY_USAGE_STATS_REPORT_INTERVAL_S`.
40
+
41
+ To see collected/reported data, see `usage_stats.json` inside a temp
42
+ folder (e.g., /tmp/ray/session_[id]/*).
43
+ """
44
+ import json
45
+ import logging
46
+ import os
47
+ import platform
48
+ import sys
49
+ import threading
50
+ import time
51
+ from dataclasses import asdict, dataclass
52
+ from enum import Enum, auto
53
+ from pathlib import Path
54
+ from typing import Dict, List, Optional, Set
55
+
56
+ import requests
57
+ import yaml
58
+
59
+ import ray
60
+ import ray._common.usage.usage_constants as usage_constant
61
+ import ray._private.ray_constants as ray_constants
62
+ from ray._raylet import GcsClient
63
+ from ray.core.generated import gcs_pb2, usage_pb2
64
+ from ray.experimental.internal_kv import (
65
+ _internal_kv_initialized,
66
+ _internal_kv_put,
67
+ )
68
+
69
+ logger = logging.getLogger(__name__)
70
+ TagKey = usage_pb2.TagKey
71
+
72
+ #################
73
+ # Internal APIs #
74
+ #################
75
+
76
+
77
+ @dataclass(init=True)
78
+ class ClusterConfigToReport:
79
+ cloud_provider: Optional[str] = None
80
+ min_workers: Optional[int] = None
81
+ max_workers: Optional[int] = None
82
+ head_node_instance_type: Optional[str] = None
83
+ worker_node_instance_types: Optional[List[str]] = None
84
+
85
+
86
+ @dataclass(init=True)
87
+ class ClusterStatusToReport:
88
+ total_num_cpus: Optional[int] = None
89
+ total_num_gpus: Optional[int] = None
90
+ total_memory_gb: Optional[float] = None
91
+ total_object_store_memory_gb: Optional[float] = None
92
+
93
+
94
+ @dataclass(init=True)
95
+ class UsageStatsToReport:
96
+ """Usage stats to report"""
97
+
98
+ #: The schema version of the report.
99
+ schema_version: str
100
+ #: The source of the data (i.e. OSS).
101
+ source: str
102
+ #: When the data is collected and reported.
103
+ collect_timestamp_ms: int
104
+ #: The total number of successful reports for the lifetime of the cluster.
105
+ total_success: Optional[int] = None
106
+ #: The total number of failed reports for the lifetime of the cluster.
107
+ total_failed: Optional[int] = None
108
+ #: The sequence number of the report.
109
+ seq_number: Optional[int] = None
110
+ #: The Ray version in use.
111
+ ray_version: Optional[str] = None
112
+ #: The Python version in use.
113
+ python_version: Optional[str] = None
114
+ #: A random id of the cluster session.
115
+ session_id: Optional[str] = None
116
+ #: The git commit hash of Ray (i.e. ray.__commit__).
117
+ git_commit: Optional[str] = None
118
+ #: The operating system in use.
119
+ os: Optional[str] = None
120
+ #: When the cluster is started.
121
+ session_start_timestamp_ms: Optional[int] = None
122
+ #: The cloud provider found in the cluster.yaml file (e.g., aws).
123
+ cloud_provider: Optional[str] = None
124
+ #: The min_workers found in the cluster.yaml file.
125
+ min_workers: Optional[int] = None
126
+ #: The max_workers found in the cluster.yaml file.
127
+ max_workers: Optional[int] = None
128
+ #: The head node instance type found in the cluster.yaml file (e.g., i3.8xlarge).
129
+ head_node_instance_type: Optional[str] = None
130
+ #: The worker node instance types found in the cluster.yaml file (e.g., i3.8xlarge).
131
+ worker_node_instance_types: Optional[List[str]] = None
132
+ #: The total num of cpus in the cluster.
133
+ total_num_cpus: Optional[int] = None
134
+ #: The total num of gpus in the cluster.
135
+ total_num_gpus: Optional[int] = None
136
+ #: The total size of memory in the cluster.
137
+ total_memory_gb: Optional[float] = None
138
+ #: The total size of object store memory in the cluster.
139
+ total_object_store_memory_gb: Optional[float] = None
140
+ #: The Ray libraries that are used (e.g., rllib).
141
+ library_usages: Optional[List[str]] = None
142
+ #: The extra tags to report when specified by an
143
+ # environment variable RAY_USAGE_STATS_EXTRA_TAGS
144
+ extra_usage_tags: Optional[Dict[str, str]] = None
145
+ #: The number of alive nodes when the report is generated.
146
+ total_num_nodes: Optional[int] = None
147
+ #: The total number of running jobs excluding internal ones
148
+ # when the report is generated.
149
+ total_num_running_jobs: Optional[int] = None
150
+ #: The libc version in the OS.
151
+ libc_version: Optional[str] = None
152
+ #: The hardwares that are used (e.g. Intel Xeon).
153
+ hardware_usages: Optional[List[str]] = None
154
+
155
+
156
+ @dataclass(init=True)
157
+ class UsageStatsToWrite:
158
+ """Usage stats to write to `USAGE_STATS_FILE`
159
+
160
+ We are writing extra metadata such as the status of report
161
+ to this file.
162
+ """
163
+
164
+ usage_stats: UsageStatsToReport
165
+ # Whether or not the last report succeeded.
166
+ success: bool
167
+ # The error message of the last report if it happens.
168
+ error: str
169
+
170
+
171
+ class UsageStatsEnabledness(Enum):
172
+ ENABLED_EXPLICITLY = auto()
173
+ DISABLED_EXPLICITLY = auto()
174
+ ENABLED_BY_DEFAULT = auto()
175
+
176
+
177
+ _recorded_library_usages = set()
178
+ _recorded_library_usages_lock = threading.Lock()
179
+ _recorded_extra_usage_tags = dict()
180
+ _recorded_extra_usage_tags_lock = threading.Lock()
181
+
182
+
183
+ def _add_to_usage_set(set_name: str, value: str):
184
+ assert _internal_kv_initialized()
185
+ try:
186
+ _internal_kv_put(
187
+ f"{set_name}{value}".encode(),
188
+ b"",
189
+ namespace=usage_constant.USAGE_STATS_NAMESPACE.encode(),
190
+ )
191
+ except Exception as e:
192
+ logger.debug(f"Failed to add {value} to usage set {set_name}, {e}")
193
+
194
+
195
+ def _get_usage_set(gcs_client, set_name: str) -> Set[str]:
196
+ try:
197
+ result = set()
198
+ usages = gcs_client.internal_kv_keys(
199
+ set_name.encode(),
200
+ namespace=usage_constant.USAGE_STATS_NAMESPACE.encode(),
201
+ )
202
+ for usage in usages:
203
+ usage = usage.decode("utf-8")
204
+ result.add(usage[len(set_name) :])
205
+
206
+ return result
207
+ except Exception as e:
208
+ logger.debug(f"Failed to get usage set {set_name}, {e}")
209
+ return set()
210
+
211
+
212
+ def _put_library_usage(library_usage: str):
213
+ _add_to_usage_set(usage_constant.LIBRARY_USAGE_SET_NAME, library_usage)
214
+
215
+
216
+ def _put_hardware_usage(hardware_usage: str):
217
+ _add_to_usage_set(usage_constant.HARDWARE_USAGE_SET_NAME, hardware_usage)
218
+
219
+
220
+ def record_extra_usage_tag(
221
+ key: TagKey, value: str, gcs_client: Optional[GcsClient] = None
222
+ ):
223
+ """Record extra kv usage tag.
224
+
225
+ If the key already exists, the value will be overwritten.
226
+
227
+ To record an extra tag, first add the key to the TagKey enum and
228
+ then call this function.
229
+ It will make a synchronous call to the internal kv store if the tag is updated.
230
+
231
+ Args:
232
+ key: The key of the tag.
233
+ value: The value of the tag.
234
+ gcs_client: The GCS client to perform KV operation PUT. Defaults to None.
235
+ When None, it will try to get the global client from the internal_kv.
236
+
237
+ Returns:
238
+ None
239
+ """
240
+ key = TagKey.Name(key).lower()
241
+ with _recorded_extra_usage_tags_lock:
242
+ if _recorded_extra_usage_tags.get(key) == value:
243
+ return
244
+ _recorded_extra_usage_tags[key] = value
245
+
246
+ if not _internal_kv_initialized() and gcs_client is None:
247
+ # This happens if the record is before ray.init and
248
+ # no GCS client is used for recording explicitly.
249
+ return
250
+
251
+ _put_extra_usage_tag(key, value, gcs_client)
252
+
253
+
254
+ def _put_extra_usage_tag(key: str, value: str, gcs_client: Optional[GcsClient] = None):
255
+ try:
256
+ key = f"{usage_constant.EXTRA_USAGE_TAG_PREFIX}{key}".encode()
257
+ val = value.encode()
258
+ namespace = usage_constant.USAGE_STATS_NAMESPACE.encode()
259
+ if gcs_client is not None:
260
+ # Use the GCS client.
261
+ gcs_client.internal_kv_put(key, val, namespace=namespace)
262
+ else:
263
+ # Use internal kv.
264
+ assert _internal_kv_initialized()
265
+ _internal_kv_put(key, val, namespace=namespace)
266
+ except Exception as e:
267
+ logger.debug(f"Failed to put extra usage tag, {e}")
268
+
269
+
270
+ def record_hardware_usage(hardware_usage: str):
271
+ """Record hardware usage (e.g. which CPU model is used)"""
272
+ assert _internal_kv_initialized()
273
+ _put_hardware_usage(hardware_usage)
274
+
275
+
276
+ def record_library_usage(library_usage: str):
277
+ """Record library usage (e.g. which library is used)"""
278
+ with _recorded_library_usages_lock:
279
+ if library_usage in _recorded_library_usages:
280
+ return
281
+ _recorded_library_usages.add(library_usage)
282
+
283
+ if not _internal_kv_initialized():
284
+ # This happens if the library is imported before ray.init
285
+ return
286
+
287
+ # Only report lib usage for driver / ray client / workers. Otherwise,
288
+ # it can be reported if the library is imported from
289
+ # e.g., API server.
290
+ if (
291
+ ray._private.worker.global_worker.mode == ray.SCRIPT_MODE
292
+ or ray._private.worker.global_worker.mode == ray.WORKER_MODE
293
+ or ray.util.client.ray.is_connected()
294
+ ):
295
+ _put_library_usage(library_usage)
296
+
297
+
298
+ def _put_pre_init_library_usages():
299
+ assert _internal_kv_initialized()
300
+ # NOTE: When the lib is imported from a worker, ray should
301
+ # always be initialized, so there's no need to register the
302
+ # pre init hook.
303
+ if not (
304
+ ray._private.worker.global_worker.mode == ray.SCRIPT_MODE
305
+ or ray.util.client.ray.is_connected()
306
+ ):
307
+ return
308
+
309
+ for library_usage in _recorded_library_usages:
310
+ _put_library_usage(library_usage)
311
+
312
+
313
+ def _put_pre_init_extra_usage_tags():
314
+ assert _internal_kv_initialized()
315
+ for k, v in _recorded_extra_usage_tags.items():
316
+ _put_extra_usage_tag(k, v)
317
+
318
+
319
+ def put_pre_init_usage_stats():
320
+ _put_pre_init_library_usages()
321
+ _put_pre_init_extra_usage_tags()
322
+
323
+
324
+ def reset_global_state():
325
+ global _recorded_library_usages, _recorded_extra_usage_tags
326
+
327
+ with _recorded_library_usages_lock:
328
+ _recorded_library_usages = set()
329
+ with _recorded_extra_usage_tags_lock:
330
+ _recorded_extra_usage_tags = dict()
331
+
332
+
333
+ ray._private.worker._post_init_hooks.append(put_pre_init_usage_stats)
334
+
335
+
336
+ def _usage_stats_report_url():
337
+ # The usage collection server URL.
338
+ # The environment variable is testing-purpose only.
339
+ return os.getenv("RAY_USAGE_STATS_REPORT_URL", "https://usage-stats.ray.io/")
340
+
341
+
342
+ def _usage_stats_report_interval_s():
343
+ return int(os.getenv("RAY_USAGE_STATS_REPORT_INTERVAL_S", 3600))
344
+
345
+
346
+ def _usage_stats_config_path():
347
+ return os.getenv(
348
+ "RAY_USAGE_STATS_CONFIG_PATH", os.path.expanduser("~/.ray/config.json")
349
+ )
350
+
351
+
352
+ def _usage_stats_enabledness() -> UsageStatsEnabledness:
353
+ # Env var has higher priority than config file.
354
+ usage_stats_enabled_env_var = os.getenv(usage_constant.USAGE_STATS_ENABLED_ENV_VAR)
355
+ if usage_stats_enabled_env_var == "0":
356
+ return UsageStatsEnabledness.DISABLED_EXPLICITLY
357
+ elif usage_stats_enabled_env_var == "1":
358
+ return UsageStatsEnabledness.ENABLED_EXPLICITLY
359
+ elif usage_stats_enabled_env_var is not None:
360
+ raise ValueError(
361
+ f"Valid value for {usage_constant.USAGE_STATS_ENABLED_ENV_VAR} "
362
+ f"env var is 0 or 1, but got {usage_stats_enabled_env_var}"
363
+ )
364
+
365
+ usage_stats_enabled_config_var = None
366
+ try:
367
+ with open(_usage_stats_config_path()) as f:
368
+ config = json.load(f)
369
+ usage_stats_enabled_config_var = config.get("usage_stats")
370
+ except FileNotFoundError:
371
+ pass
372
+ except Exception as e:
373
+ logger.debug(f"Failed to load usage stats config {e}")
374
+
375
+ if usage_stats_enabled_config_var is False:
376
+ return UsageStatsEnabledness.DISABLED_EXPLICITLY
377
+ elif usage_stats_enabled_config_var is True:
378
+ return UsageStatsEnabledness.ENABLED_EXPLICITLY
379
+ elif usage_stats_enabled_config_var is not None:
380
+ raise ValueError(
381
+ f"Valid value for 'usage_stats' in {_usage_stats_config_path()}"
382
+ f" is true or false, but got {usage_stats_enabled_config_var}"
383
+ )
384
+
385
+ # Usage stats is enabled by default.
386
+ return UsageStatsEnabledness.ENABLED_BY_DEFAULT
387
+
388
+
389
+ def is_nightly_wheel() -> bool:
390
+ return ray.__commit__ != "{{RAY_COMMIT_SHA}}" and "dev" in ray.__version__
391
+
392
+
393
+ def usage_stats_enabled() -> bool:
394
+ return _usage_stats_enabledness() is not UsageStatsEnabledness.DISABLED_EXPLICITLY
395
+
396
+
397
+ def usage_stats_prompt_enabled():
398
+ return int(os.getenv("RAY_USAGE_STATS_PROMPT_ENABLED", "1")) == 1
399
+
400
+
401
+ def _generate_cluster_metadata(*, ray_init_cluster: bool):
402
+ """Return a dictionary of cluster metadata.
403
+
404
+ Params:
405
+ ray_init_cluster: Whether the cluster is started by ray.init()
406
+
407
+ Returns:
408
+ A dictionary of cluster metadata.
409
+ """
410
+ ray_version, python_version = ray._private.utils.compute_version_info()
411
+ # These two metadata is necessary although usage report is not enabled
412
+ # to check version compatibility.
413
+ metadata = {
414
+ "ray_version": ray_version,
415
+ "python_version": python_version,
416
+ "ray_init_cluster": ray_init_cluster,
417
+ }
418
+ # Additional metadata is recorded only when usage stats are enabled.
419
+ if usage_stats_enabled():
420
+ metadata.update(
421
+ {
422
+ "git_commit": ray.__commit__,
423
+ "os": sys.platform,
424
+ "session_start_timestamp_ms": int(time.time() * 1000),
425
+ }
426
+ )
427
+ if sys.platform == "linux":
428
+ # Record llibc version
429
+ (lib, ver) = platform.libc_ver()
430
+ if not lib:
431
+ metadata.update({"libc_version": "NA"})
432
+ else:
433
+ metadata.update({"libc_version": f"{lib}:{ver}"})
434
+ return metadata
435
+
436
+
437
+ def show_usage_stats_prompt(cli: bool) -> None:
438
+ if not usage_stats_prompt_enabled():
439
+ return
440
+
441
+ from ray.autoscaler._private.cli_logger import cli_logger
442
+
443
+ prompt_print = cli_logger.print if cli else print
444
+
445
+ usage_stats_enabledness = _usage_stats_enabledness()
446
+ if usage_stats_enabledness is UsageStatsEnabledness.DISABLED_EXPLICITLY:
447
+ prompt_print(usage_constant.USAGE_STATS_DISABLED_MESSAGE)
448
+ elif usage_stats_enabledness is UsageStatsEnabledness.ENABLED_BY_DEFAULT:
449
+ if not cli:
450
+ prompt_print(
451
+ usage_constant.USAGE_STATS_ENABLED_BY_DEFAULT_FOR_RAY_INIT_MESSAGE
452
+ )
453
+ elif cli_logger.interactive:
454
+ enabled = cli_logger.confirm(
455
+ False,
456
+ usage_constant.USAGE_STATS_CONFIRMATION_MESSAGE,
457
+ _default=True,
458
+ _timeout_s=10,
459
+ )
460
+ set_usage_stats_enabled_via_env_var(enabled)
461
+ # Remember user's choice.
462
+ try:
463
+ set_usage_stats_enabled_via_config(enabled)
464
+ except Exception as e:
465
+ logger.debug(
466
+ f"Failed to persist usage stats choice for future clusters: {e}"
467
+ )
468
+ if enabled:
469
+ prompt_print(usage_constant.USAGE_STATS_ENABLED_FOR_CLI_MESSAGE)
470
+ else:
471
+ prompt_print(usage_constant.USAGE_STATS_DISABLED_MESSAGE)
472
+ else:
473
+ prompt_print(
474
+ usage_constant.USAGE_STATS_ENABLED_BY_DEFAULT_FOR_CLI_MESSAGE,
475
+ )
476
+ else:
477
+ assert usage_stats_enabledness is UsageStatsEnabledness.ENABLED_EXPLICITLY
478
+ prompt_print(
479
+ usage_constant.USAGE_STATS_ENABLED_FOR_CLI_MESSAGE
480
+ if cli
481
+ else usage_constant.USAGE_STATS_ENABLED_FOR_RAY_INIT_MESSAGE
482
+ )
483
+
484
+
485
+ def set_usage_stats_enabled_via_config(enabled) -> None:
486
+ config = {}
487
+ try:
488
+ with open(_usage_stats_config_path()) as f:
489
+ config = json.load(f)
490
+ if not isinstance(config, dict):
491
+ logger.debug(
492
+ f"Invalid ray config file, should be a json dict but got {type(config)}"
493
+ )
494
+ config = {}
495
+ except FileNotFoundError:
496
+ pass
497
+ except Exception as e:
498
+ logger.debug(f"Failed to load ray config file {e}")
499
+
500
+ config["usage_stats"] = enabled
501
+
502
+ try:
503
+ os.makedirs(os.path.dirname(_usage_stats_config_path()), exist_ok=True)
504
+ with open(_usage_stats_config_path(), "w") as f:
505
+ json.dump(config, f)
506
+ except Exception as e:
507
+ raise Exception(
508
+ "Failed to "
509
+ f'{"enable" if enabled else "disable"}'
510
+ ' usage stats by writing {"usage_stats": '
511
+ f'{"true" if enabled else "false"}'
512
+ "} to "
513
+ f"{_usage_stats_config_path()}"
514
+ ) from e
515
+
516
+
517
+ def set_usage_stats_enabled_via_env_var(enabled) -> None:
518
+ os.environ[usage_constant.USAGE_STATS_ENABLED_ENV_VAR] = "1" if enabled else "0"
519
+
520
+
521
+ def put_cluster_metadata(gcs_client: GcsClient, *, ray_init_cluster: bool) -> dict:
522
+ """Generate the cluster metadata and store it to GCS.
523
+
524
+ It is a blocking API.
525
+
526
+ Params:
527
+ gcs_client: The GCS client to perform KV operation PUT.
528
+ ray_init_cluster: Whether the cluster is started by ray.init()
529
+
530
+ Raises:
531
+ gRPC exceptions: If PUT fails.
532
+
533
+ Returns:
534
+ The cluster metadata.
535
+ """
536
+ metadata = _generate_cluster_metadata(ray_init_cluster=ray_init_cluster)
537
+ gcs_client.internal_kv_put(
538
+ usage_constant.CLUSTER_METADATA_KEY,
539
+ json.dumps(metadata).encode(),
540
+ overwrite=True,
541
+ namespace=ray_constants.KV_NAMESPACE_CLUSTER,
542
+ )
543
+ return metadata
544
+
545
+
546
+ def get_total_num_running_jobs_to_report(gcs_client) -> Optional[int]:
547
+ """Return the total number of running jobs in the cluster excluding internal ones"""
548
+ try:
549
+ result = gcs_client.get_all_job_info(
550
+ skip_submission_job_info_field=True, skip_is_running_tasks_field=True
551
+ )
552
+ total_num_running_jobs = 0
553
+ for job_info in result.values():
554
+ if not job_info.is_dead and not job_info.config.ray_namespace.startswith(
555
+ "_ray_internal"
556
+ ):
557
+ total_num_running_jobs += 1
558
+ return total_num_running_jobs
559
+ except Exception as e:
560
+ logger.info(f"Failed to query number of running jobs in the cluster: {e}")
561
+ return None
562
+
563
+
564
+ def get_total_num_alive_nodes_to_report(gcs_client, timeout=None) -> Optional[int]:
565
+ """Return the total number of alive nodes in the cluster"""
566
+ try:
567
+ result = gcs_client.get_all_node_info(
568
+ timeout=timeout, state_filter=gcs_pb2.GcsNodeInfo.GcsNodeState.ALIVE
569
+ )
570
+ return len(result.items())
571
+ except Exception as e:
572
+ logger.info(f"Failed to query number of nodes in the cluster: {e}")
573
+ return None
574
+
575
+
576
+ def get_library_usages_to_report(gcs_client) -> List[str]:
577
+ return list(_get_usage_set(gcs_client, usage_constant.LIBRARY_USAGE_SET_NAME))
578
+
579
+
580
+ def get_hardware_usages_to_report(gcs_client) -> List[str]:
581
+ return list(_get_usage_set(gcs_client, usage_constant.HARDWARE_USAGE_SET_NAME))
582
+
583
+
584
+ def get_extra_usage_tags_to_report(gcs_client: GcsClient) -> Dict[str, str]:
585
+ """Get the extra usage tags from env var and gcs kv store.
586
+
587
+ The env var should be given this way; key=value;key=value.
588
+ If parsing is failed, it will return the empty data.
589
+
590
+ Params:
591
+ gcs_client: The GCS client.
592
+
593
+ Returns:
594
+ Extra usage tags as kv pairs.
595
+ """
596
+ extra_usage_tags = dict()
597
+
598
+ extra_usage_tags_env_var = os.getenv("RAY_USAGE_STATS_EXTRA_TAGS", None)
599
+ if extra_usage_tags_env_var:
600
+ try:
601
+ kvs = extra_usage_tags_env_var.strip(";").split(";")
602
+ for kv in kvs:
603
+ k, v = kv.split("=")
604
+ extra_usage_tags[k] = v
605
+ except Exception as e:
606
+ logger.info(f"Failed to parse extra usage tags env var: {e}")
607
+
608
+ valid_tag_keys = [tag_key.lower() for tag_key in TagKey.keys()]
609
+ try:
610
+ keys = gcs_client.internal_kv_keys(
611
+ usage_constant.EXTRA_USAGE_TAG_PREFIX.encode(),
612
+ namespace=usage_constant.USAGE_STATS_NAMESPACE.encode(),
613
+ )
614
+ kv = gcs_client.internal_kv_multi_get(
615
+ keys, namespace=usage_constant.USAGE_STATS_NAMESPACE.encode()
616
+ )
617
+ for key, value in kv.items():
618
+ key = key.decode("utf-8")
619
+ key = key[len(usage_constant.EXTRA_USAGE_TAG_PREFIX) :]
620
+ assert key in valid_tag_keys
621
+ extra_usage_tags[key] = value.decode("utf-8")
622
+ except Exception as e:
623
+ logger.info(f"Failed to get extra usage tags from kv store: {e}")
624
+ return extra_usage_tags
625
+
626
+
627
+ def _get_cluster_status_to_report_v2(gcs_client: GcsClient) -> ClusterStatusToReport:
628
+ """
629
+ Get the current status of this cluster. A temporary proxy for the
630
+ autoscaler v2 API.
631
+
632
+ It is a blocking API.
633
+
634
+ Params:
635
+ gcs_client: The GCS client.
636
+
637
+ Returns:
638
+ The current cluster status or empty ClusterStatusToReport
639
+ if it fails to get that information.
640
+ """
641
+ from ray.autoscaler.v2.sdk import get_cluster_status
642
+
643
+ result = ClusterStatusToReport()
644
+ try:
645
+ cluster_status = get_cluster_status(gcs_client.address)
646
+ total_resources = cluster_status.total_resources()
647
+ result.total_num_cpus = int(total_resources.get("CPU", 0))
648
+ result.total_num_gpus = int(total_resources.get("GPU", 0))
649
+
650
+ to_GiB = 1 / 2**30
651
+ result.total_memory_gb = total_resources.get("memory", 0) * to_GiB
652
+ result.total_object_store_memory_gb = (
653
+ total_resources.get("object_store_memory", 0) * to_GiB
654
+ )
655
+ except Exception as e:
656
+ logger.info(f"Failed to get cluster status to report {e}")
657
+ finally:
658
+ return result
659
+
660
+
661
+ def get_cluster_status_to_report(gcs_client: GcsClient) -> ClusterStatusToReport:
662
+ """Get the current status of this cluster.
663
+
664
+ It is a blocking API.
665
+
666
+ Params:
667
+ gcs_client: The GCS client to perform KV operation GET.
668
+
669
+ Returns:
670
+ The current cluster status or empty if it fails to get that information.
671
+ """
672
+ try:
673
+
674
+ from ray.autoscaler.v2.utils import is_autoscaler_v2
675
+
676
+ if is_autoscaler_v2():
677
+ return _get_cluster_status_to_report_v2(gcs_client)
678
+
679
+ cluster_status = gcs_client.internal_kv_get(
680
+ ray._private.ray_constants.DEBUG_AUTOSCALING_STATUS.encode(),
681
+ namespace=None,
682
+ )
683
+ if not cluster_status:
684
+ return ClusterStatusToReport()
685
+
686
+ result = ClusterStatusToReport()
687
+ to_GiB = 1 / 2**30
688
+ cluster_status = json.loads(cluster_status.decode("utf-8"))
689
+ if (
690
+ "load_metrics_report" not in cluster_status
691
+ or "usage" not in cluster_status["load_metrics_report"]
692
+ ):
693
+ return ClusterStatusToReport()
694
+
695
+ usage = cluster_status["load_metrics_report"]["usage"]
696
+ # usage is a map from resource to (used, total) pair
697
+ if "CPU" in usage:
698
+ result.total_num_cpus = int(usage["CPU"][1])
699
+ if "GPU" in usage:
700
+ result.total_num_gpus = int(usage["GPU"][1])
701
+ if "memory" in usage:
702
+ result.total_memory_gb = usage["memory"][1] * to_GiB
703
+ if "object_store_memory" in usage:
704
+ result.total_object_store_memory_gb = (
705
+ usage["object_store_memory"][1] * to_GiB
706
+ )
707
+ return result
708
+ except Exception as e:
709
+ logger.info(f"Failed to get cluster status to report {e}")
710
+ return ClusterStatusToReport()
711
+
712
+
713
+ def get_cloud_from_metadata_requests() -> str:
714
+ def cloud_metadata_request(url: str, headers: Optional[Dict[str, str]]) -> bool:
715
+ try:
716
+ res = requests.get(url, headers=headers, timeout=1)
717
+ # Only accept successful responses (200 OK) to avoid false positives like 400 - Bad Request
718
+ # when multiple cloud providers use the same IP (169.254.169.254)
719
+ if res.status_code == 200:
720
+ return True
721
+ # ConnectionError is a superclass of ConnectTimeout
722
+ except requests.exceptions.ConnectionError:
723
+ pass
724
+ except Exception as e:
725
+ logger.info(
726
+ f"Unexpected exception when making cloud provider metadata request: {e}"
727
+ )
728
+ return False
729
+
730
+ AZURE_METADATA_URL = (
731
+ "http://169.254.169.254/metadata/instance?api-version=2021-12-13"
732
+ )
733
+ AZURE_METADATA_HEADERS = {"Metadata": "true"}
734
+ GCP_METADATA_URL = "http://metadata.google.internal/computeMetadata/v1"
735
+ GCP_METADATA_HEADERS = {"Metadata-Flavor": "Google"}
736
+ AWS_METADATA_URL = "http://169.254.169.254/latest/meta-data/"
737
+ AWS_METADATA_HEADERS = None
738
+
739
+ if cloud_metadata_request(AZURE_METADATA_URL, AZURE_METADATA_HEADERS):
740
+ return "azure"
741
+ elif cloud_metadata_request(GCP_METADATA_URL, GCP_METADATA_HEADERS):
742
+ return "gcp"
743
+ elif cloud_metadata_request(AWS_METADATA_URL, AWS_METADATA_HEADERS):
744
+ return "aws"
745
+ else:
746
+ return "unknown"
747
+
748
+
749
+ def get_cluster_config_to_report(
750
+ cluster_config_file_path: str,
751
+ ) -> ClusterConfigToReport:
752
+ """Get the static cluster (autoscaler) config used to launch this cluster.
753
+
754
+ Params:
755
+ cluster_config_file_path: The file path to the cluster config file.
756
+
757
+ Returns:
758
+ The cluster (autoscaler) config or empty if it fails to get that information.
759
+ """
760
+
761
+ def get_instance_type(node_config):
762
+ if not node_config:
763
+ return None
764
+ if "InstanceType" in node_config:
765
+ # aws
766
+ return node_config["InstanceType"]
767
+ if "machineType" in node_config:
768
+ # gcp
769
+ return node_config["machineType"]
770
+ if (
771
+ "azure_arm_parameters" in node_config
772
+ and "vmSize" in node_config["azure_arm_parameters"]
773
+ ):
774
+ return node_config["azure_arm_parameters"]["vmSize"]
775
+ return None
776
+
777
+ try:
778
+ with open(cluster_config_file_path) as f:
779
+ config = yaml.safe_load(f)
780
+ result = ClusterConfigToReport()
781
+ if "min_workers" in config:
782
+ result.min_workers = config["min_workers"]
783
+ if "max_workers" in config:
784
+ result.max_workers = config["max_workers"]
785
+
786
+ if "provider" in config and "type" in config["provider"]:
787
+ result.cloud_provider = config["provider"]["type"]
788
+
789
+ if "head_node_type" not in config:
790
+ return result
791
+ if "available_node_types" not in config:
792
+ return result
793
+ head_node_type = config["head_node_type"]
794
+ available_node_types = config["available_node_types"]
795
+ for available_node_type in available_node_types:
796
+ if available_node_type == head_node_type:
797
+ head_node_instance_type = get_instance_type(
798
+ available_node_types[available_node_type].get("node_config")
799
+ )
800
+ if head_node_instance_type:
801
+ result.head_node_instance_type = head_node_instance_type
802
+ else:
803
+ worker_node_instance_type = get_instance_type(
804
+ available_node_types[available_node_type].get("node_config")
805
+ )
806
+ if worker_node_instance_type:
807
+ result.worker_node_instance_types = (
808
+ result.worker_node_instance_types or set()
809
+ )
810
+ result.worker_node_instance_types.add(worker_node_instance_type)
811
+ if result.worker_node_instance_types:
812
+ result.worker_node_instance_types = list(
813
+ result.worker_node_instance_types
814
+ )
815
+ return result
816
+ except FileNotFoundError:
817
+ # It's a manually started cluster or k8s cluster
818
+ result = ClusterConfigToReport()
819
+
820
+ # Check if we're on Kubernetes
821
+ if usage_constant.KUBERNETES_SERVICE_HOST_ENV in os.environ:
822
+ # Check if we're using KubeRay >= 0.4.0.
823
+ if usage_constant.KUBERAY_ENV in os.environ:
824
+ result.cloud_provider = usage_constant.PROVIDER_KUBERAY
825
+ # Else, we're on Kubernetes but not in either of the above categories.
826
+ else:
827
+ result.cloud_provider = usage_constant.PROVIDER_KUBERNETES_GENERIC
828
+
829
+ # if kubernetes was not set as cloud_provider vs. was set before
830
+ if result.cloud_provider is None:
831
+ result.cloud_provider = get_cloud_from_metadata_requests()
832
+ else:
833
+ result.cloud_provider += f"_${get_cloud_from_metadata_requests()}"
834
+
835
+ return result
836
+ except Exception as e:
837
+ logger.info(f"Failed to get cluster config to report {e}")
838
+ return ClusterConfigToReport()
839
+
840
+
841
+ def get_cluster_metadata(gcs_client: GcsClient) -> dict:
842
+ """Get the cluster metadata from GCS.
843
+
844
+ It is a blocking API.
845
+
846
+ This will return None if `put_cluster_metadata` was never called.
847
+
848
+ Params:
849
+ gcs_client: The GCS client to perform KV operation GET.
850
+
851
+ Returns:
852
+ The cluster metadata in a dictionary.
853
+
854
+ Raises:
855
+ RuntimeError: If it fails to obtain cluster metadata from GCS.
856
+ """
857
+ return json.loads(
858
+ gcs_client.internal_kv_get(
859
+ usage_constant.CLUSTER_METADATA_KEY,
860
+ namespace=ray_constants.KV_NAMESPACE_CLUSTER,
861
+ ).decode("utf-8")
862
+ )
863
+
864
+
865
+ def is_ray_init_cluster(gcs_client: ray._raylet.GcsClient) -> bool:
866
+ """Return whether the cluster is started by ray.init()"""
867
+ cluster_metadata = get_cluster_metadata(gcs_client)
868
+ return cluster_metadata["ray_init_cluster"]
869
+
870
+
871
+ def generate_disabled_report_data() -> UsageStatsToReport:
872
+ """Generate the report data indicating usage stats is disabled"""
873
+ data = UsageStatsToReport(
874
+ schema_version=usage_constant.SCHEMA_VERSION,
875
+ source=os.getenv(
876
+ usage_constant.USAGE_STATS_SOURCE_ENV_VAR,
877
+ usage_constant.USAGE_STATS_SOURCE_OSS,
878
+ ),
879
+ collect_timestamp_ms=int(time.time() * 1000),
880
+ )
881
+ return data
882
+
883
+
884
+ def generate_report_data(
885
+ cluster_config_to_report: ClusterConfigToReport,
886
+ total_success: int,
887
+ total_failed: int,
888
+ seq_number: int,
889
+ gcs_address: str,
890
+ cluster_id: str,
891
+ ) -> UsageStatsToReport:
892
+ """Generate the report data.
893
+
894
+ Params:
895
+ cluster_config_to_report: The cluster (autoscaler)
896
+ config generated by `get_cluster_config_to_report`.
897
+ total_success: The total number of successful report
898
+ for the lifetime of the cluster.
899
+ total_failed: The total number of failed report
900
+ for the lifetime of the cluster.
901
+ seq_number: The sequence number that's incremented whenever
902
+ a new report is sent.
903
+ gcs_address: the address of gcs to get data to report.
904
+ cluster_id: hex id of the cluster.
905
+
906
+ Returns:
907
+ UsageStats
908
+ """
909
+ assert cluster_id
910
+
911
+ gcs_client = ray._raylet.GcsClient(address=gcs_address, cluster_id=cluster_id)
912
+
913
+ cluster_metadata = get_cluster_metadata(gcs_client)
914
+ cluster_status_to_report = get_cluster_status_to_report(gcs_client)
915
+
916
+ data = UsageStatsToReport(
917
+ schema_version=usage_constant.SCHEMA_VERSION,
918
+ source=os.getenv(
919
+ usage_constant.USAGE_STATS_SOURCE_ENV_VAR,
920
+ usage_constant.USAGE_STATS_SOURCE_OSS,
921
+ ),
922
+ collect_timestamp_ms=int(time.time() * 1000),
923
+ total_success=total_success,
924
+ total_failed=total_failed,
925
+ seq_number=seq_number,
926
+ ray_version=cluster_metadata["ray_version"],
927
+ python_version=cluster_metadata["python_version"],
928
+ session_id=cluster_id,
929
+ git_commit=cluster_metadata["git_commit"],
930
+ os=cluster_metadata["os"],
931
+ session_start_timestamp_ms=cluster_metadata["session_start_timestamp_ms"],
932
+ cloud_provider=cluster_config_to_report.cloud_provider,
933
+ min_workers=cluster_config_to_report.min_workers,
934
+ max_workers=cluster_config_to_report.max_workers,
935
+ head_node_instance_type=cluster_config_to_report.head_node_instance_type,
936
+ worker_node_instance_types=cluster_config_to_report.worker_node_instance_types,
937
+ total_num_cpus=cluster_status_to_report.total_num_cpus,
938
+ total_num_gpus=cluster_status_to_report.total_num_gpus,
939
+ total_memory_gb=cluster_status_to_report.total_memory_gb,
940
+ total_object_store_memory_gb=cluster_status_to_report.total_object_store_memory_gb, # noqa: E501
941
+ library_usages=get_library_usages_to_report(gcs_client),
942
+ extra_usage_tags=get_extra_usage_tags_to_report(gcs_client),
943
+ total_num_nodes=get_total_num_alive_nodes_to_report(gcs_client),
944
+ total_num_running_jobs=get_total_num_running_jobs_to_report(gcs_client),
945
+ libc_version=cluster_metadata.get("libc_version"),
946
+ hardware_usages=get_hardware_usages_to_report(gcs_client),
947
+ )
948
+ return data
949
+
950
+
951
+ def generate_write_data(
952
+ usage_stats: UsageStatsToReport,
953
+ error: str,
954
+ ) -> UsageStatsToWrite:
955
+ """Generate the report data.
956
+
957
+ Params:
958
+ usage_stats: The usage stats that were reported.
959
+ error: The error message of failed reports.
960
+
961
+ Returns:
962
+ UsageStatsToWrite
963
+ """
964
+ data = UsageStatsToWrite(
965
+ usage_stats=usage_stats,
966
+ success=error is None,
967
+ error=error,
968
+ )
969
+ return data
970
+
971
+
972
+ class UsageReportClient:
973
+ """The client implementation for usage report.
974
+
975
+ It is in charge of writing usage stats to the directory
976
+ and report usage stats.
977
+ """
978
+
979
+ def write_usage_data(self, data: UsageStatsToWrite, dir_path: str) -> None:
980
+ """Write the usage data to the directory.
981
+
982
+ Params:
983
+ data: Data to report
984
+ dir_path: The path to the directory to write usage data.
985
+ """
986
+ # Atomically update the file.
987
+ dir_path = Path(dir_path)
988
+ destination = dir_path / usage_constant.USAGE_STATS_FILE
989
+ temp = dir_path / f"{usage_constant.USAGE_STATS_FILE}.tmp"
990
+ with temp.open(mode="w") as json_file:
991
+ json_file.write(json.dumps(asdict(data)))
992
+ if sys.platform == "win32":
993
+ # Windows 32 doesn't support atomic renaming, so we should delete
994
+ # the file first.
995
+ destination.unlink(missing_ok=True)
996
+ temp.rename(destination)
997
+
998
+ def report_usage_data(self, url: str, data: UsageStatsToReport) -> None:
999
+ """Report the usage data to the usage server.
1000
+
1001
+ Params:
1002
+ url: The URL to update resource usage.
1003
+ data: Data to report.
1004
+
1005
+ Raises:
1006
+ requests.HTTPError: If requests fails.
1007
+ """
1008
+ r = requests.request(
1009
+ "POST",
1010
+ url,
1011
+ headers={"Content-Type": "application/json"},
1012
+ json=asdict(data),
1013
+ timeout=10,
1014
+ )
1015
+ r.raise_for_status()
1016
+ return r
lib/python3.12/site-packages/ray/_common/utils.py ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import binascii
3
+ import errno
4
+ import importlib
5
+ import inspect
6
+ import os
7
+ import random
8
+ import string
9
+ import sys
10
+ import tempfile
11
+ from inspect import signature
12
+ from types import ModuleType
13
+ from typing import Any, Coroutine, Dict, Optional, Tuple
14
+
15
+ import psutil
16
+
17
+
18
+ def import_module_and_attr(
19
+ full_path: str, *, reload_module: bool = False
20
+ ) -> Tuple[ModuleType, Any]:
21
+ """Given a full import path to a module attr, return the imported module and attr.
22
+
23
+ If `reload_module` is set, the module will be reloaded using `importlib.reload`.
24
+
25
+ Args:
26
+ full_path: The full import path to the module and attr.
27
+ reload_module: Whether to reload the module.
28
+
29
+ Returns:
30
+ A tuple of the imported module and attr.
31
+ """
32
+ if ":" in full_path:
33
+ if full_path.count(":") > 1:
34
+ raise ValueError(
35
+ f'Got invalid import path "{full_path}". An '
36
+ "import path may have at most one colon."
37
+ )
38
+ module_name, attr_name = full_path.split(":")
39
+ else:
40
+ last_period_idx = full_path.rfind(".")
41
+ module_name = full_path[:last_period_idx]
42
+ attr_name = full_path[last_period_idx + 1 :]
43
+ module = importlib.import_module(module_name)
44
+ if reload_module:
45
+ importlib.reload(module)
46
+ return module, getattr(module, attr_name)
47
+
48
+
49
+ def import_attr(full_path: str, *, reload_module: bool = False) -> Any:
50
+ """Given a full import path to a module attr, return the imported attr.
51
+
52
+ If `reload_module` is set, the module will be reloaded using `importlib.reload`.
53
+
54
+ For example, the following are equivalent:
55
+ MyClass = import_attr("module.submodule:MyClass")
56
+ MyClass = import_attr("module.submodule.MyClass")
57
+ from module.submodule import MyClass
58
+
59
+ Returns:
60
+ Imported attr
61
+ """
62
+ return import_module_and_attr(full_path, reload_module=reload_module)[1]
63
+
64
+
65
+ def get_or_create_event_loop() -> asyncio.AbstractEventLoop:
66
+ """Get a running async event loop if one exists, otherwise create one.
67
+
68
+ This function serves as a proxy for the deprecating get_event_loop().
69
+ It tries to get the running loop first, and if no running loop
70
+ could be retrieved:
71
+ - For python version <3.10: it falls back to the get_event_loop
72
+ call.
73
+ - For python version >= 3.10: it uses the same python implementation
74
+ of _get_event_loop() at asyncio/events.py.
75
+
76
+ Ideally, one should use high level APIs like asyncio.run() with python
77
+ version >= 3.7, if not possible, one should create and manage the event
78
+ loops explicitly.
79
+ """
80
+ vers_info = sys.version_info
81
+ if vers_info.major >= 3 and vers_info.minor >= 10:
82
+ # This follows the implementation of the deprecating `get_event_loop`
83
+ # in python3.10's asyncio. See python3.10/asyncio/events.py
84
+ # _get_event_loop()
85
+ try:
86
+ loop = asyncio.get_running_loop()
87
+ assert loop is not None
88
+ return loop
89
+ except RuntimeError as e:
90
+ # No running loop, relying on the error message as for now to
91
+ # differentiate runtime errors.
92
+ assert "no running event loop" in str(e)
93
+ return asyncio.get_event_loop_policy().get_event_loop()
94
+
95
+ return asyncio.get_event_loop()
96
+
97
+
98
+ _BACKGROUND_TASKS = set()
99
+
100
+
101
+ def run_background_task(coroutine: Coroutine) -> asyncio.Task:
102
+ """Schedule a task reliably to the event loop.
103
+
104
+ This API is used when you don't want to cache the reference of `asyncio.Task`.
105
+ For example,
106
+
107
+ ```
108
+ get_event_loop().create_task(coroutine(*args))
109
+ ```
110
+
111
+ The above code doesn't guarantee to schedule the coroutine to the event loops
112
+
113
+ When using create_task in a "fire and forget" way, we should keep the references
114
+ alive for the reliable execution. This API is used to fire and forget
115
+ asynchronous execution.
116
+
117
+ https://docs.python.org/3/library/asyncio-task.html#creating-tasks
118
+ """
119
+ task = get_or_create_event_loop().create_task(coroutine)
120
+ # Add task to the set. This creates a strong reference.
121
+ _BACKGROUND_TASKS.add(task)
122
+
123
+ # To prevent keeping references to finished tasks forever,
124
+ # make each task remove its own reference from the set after
125
+ # completion:
126
+ task.add_done_callback(_BACKGROUND_TASKS.discard)
127
+ return task
128
+
129
+
130
+ # Used in gpu detection
131
+ RESOURCE_CONSTRAINT_PREFIX = "accelerator_type:"
132
+ PLACEMENT_GROUP_BUNDLE_RESOURCE_NAME = "bundle"
133
+
134
+
135
+ def resources_from_ray_options(options_dict: Dict[str, Any]) -> Dict[str, Any]:
136
+ """Determine a task's resource requirements.
137
+
138
+ Args:
139
+ options_dict: The dictionary that contains resources requirements.
140
+
141
+ Returns:
142
+ A dictionary of the resource requirements for the task.
143
+ """
144
+ resources = (options_dict.get("resources") or {}).copy()
145
+
146
+ if "CPU" in resources or "GPU" in resources:
147
+ raise ValueError(
148
+ "The resources dictionary must not contain the key 'CPU' or 'GPU'"
149
+ )
150
+ elif "memory" in resources or "object_store_memory" in resources:
151
+ raise ValueError(
152
+ "The resources dictionary must not "
153
+ "contain the key 'memory' or 'object_store_memory'"
154
+ )
155
+ elif PLACEMENT_GROUP_BUNDLE_RESOURCE_NAME in resources:
156
+ raise ValueError(
157
+ "The resource should not include `bundle` which "
158
+ f"is reserved for Ray. resources: {resources}"
159
+ )
160
+
161
+ num_cpus = options_dict.get("num_cpus")
162
+ num_gpus = options_dict.get("num_gpus")
163
+ memory = options_dict.get("memory")
164
+ object_store_memory = options_dict.get("object_store_memory")
165
+ accelerator_type = options_dict.get("accelerator_type")
166
+
167
+ if num_cpus is not None:
168
+ resources["CPU"] = num_cpus
169
+ if num_gpus is not None:
170
+ resources["GPU"] = num_gpus
171
+ if memory is not None:
172
+ resources["memory"] = int(memory)
173
+ if object_store_memory is not None:
174
+ resources["object_store_memory"] = object_store_memory
175
+ if accelerator_type is not None:
176
+ resources[f"{RESOURCE_CONSTRAINT_PREFIX}{accelerator_type}"] = 0.001
177
+
178
+ return resources
179
+
180
+
181
+ # Match the standard alphabet used for UUIDs.
182
+ RANDOM_STRING_ALPHABET = string.ascii_lowercase + string.digits
183
+
184
+
185
+ def get_random_alphanumeric_string(length: int):
186
+ """Generates random string of length consisting exclusively of
187
+ - Lower-case ASCII chars
188
+ - Digits
189
+ """
190
+ return "".join(random.choices(RANDOM_STRING_ALPHABET, k=length))
191
+
192
+
193
+ _PRINTED_WARNING = set()
194
+
195
+
196
+ def get_call_location(back: int = 1):
197
+ """
198
+ Get the location (filename and line number) of a function caller, `back`
199
+ frames up the stack.
200
+
201
+ Args:
202
+ back: The number of frames to go up the stack, not including this
203
+ function.
204
+
205
+ Returns:
206
+ A string with the filename and line number of the caller.
207
+ For example, "myfile.py:123".
208
+ """
209
+ stack = inspect.stack()
210
+ try:
211
+ frame = stack[back + 1]
212
+ return f"{frame.filename}:{frame.lineno}"
213
+ except IndexError:
214
+ return "UNKNOWN"
215
+
216
+
217
+ def get_user_temp_dir():
218
+ if "RAY_TMPDIR" in os.environ:
219
+ return os.environ["RAY_TMPDIR"]
220
+ elif sys.platform.startswith("linux") and "TMPDIR" in os.environ:
221
+ return os.environ["TMPDIR"]
222
+ elif sys.platform.startswith("darwin") or sys.platform.startswith("linux"):
223
+ # Ideally we wouldn't need this fallback, but keep it for now for
224
+ # for compatibility
225
+ tempdir = os.path.join(os.sep, "tmp")
226
+ else:
227
+ tempdir = tempfile.gettempdir()
228
+ return tempdir
229
+
230
+
231
+ def get_ray_temp_dir():
232
+ return os.path.join(get_user_temp_dir(), "ray")
233
+
234
+
235
+ def get_ray_address_file(temp_dir: Optional[str]):
236
+ if temp_dir is None:
237
+ temp_dir = get_ray_temp_dir()
238
+ return os.path.join(temp_dir, "ray_current_cluster")
239
+
240
+
241
+ def reset_ray_address(temp_dir: Optional[str] = None):
242
+ address_file = get_ray_address_file(temp_dir)
243
+ if os.path.exists(address_file):
244
+ try:
245
+ os.remove(address_file)
246
+ except OSError:
247
+ pass
248
+
249
+
250
+ def load_class(path):
251
+ """Load a class at runtime given a full path.
252
+
253
+ Example of the path: mypkg.mysubpkg.myclass
254
+ """
255
+ class_data = path.split(".")
256
+ if len(class_data) < 2:
257
+ raise ValueError("You need to pass a valid path like mymodule.provider_class")
258
+ module_path = ".".join(class_data[:-1])
259
+ class_str = class_data[-1]
260
+ module = importlib.import_module(module_path)
261
+ return getattr(module, class_str)
262
+
263
+
264
+ def get_system_memory(
265
+ # For cgroups v1:
266
+ memory_limit_filename: str = "/sys/fs/cgroup/memory/memory.limit_in_bytes",
267
+ # For cgroups v2:
268
+ memory_limit_filename_v2: str = "/sys/fs/cgroup/memory.max",
269
+ ):
270
+ """Return the total amount of system memory in bytes.
271
+
272
+ Args:
273
+ memory_limit_filename: The path to the file that contains the memory
274
+ limit for the Docker container. Defaults to
275
+ /sys/fs/cgroup/memory/memory.limit_in_bytes.
276
+ memory_limit_filename_v2: The path to the file that contains the memory
277
+ limit for the Docker container in cgroups v2. Defaults to
278
+ /sys/fs/cgroup/memory.max.
279
+
280
+ Returns:
281
+ The total amount of system memory in bytes.
282
+ """
283
+ # Try to accurately figure out the memory limit if we are in a docker
284
+ # container. Note that this file is not specific to Docker and its value is
285
+ # often much larger than the actual amount of memory.
286
+ docker_limit = None
287
+ if os.path.exists(memory_limit_filename):
288
+ with open(memory_limit_filename, "r") as f:
289
+ docker_limit = int(f.read().strip())
290
+ elif os.path.exists(memory_limit_filename_v2):
291
+ with open(memory_limit_filename_v2, "r") as f:
292
+ # Don't forget to strip() the newline:
293
+ max_file = f.read().strip()
294
+ if max_file.isnumeric():
295
+ docker_limit = int(max_file)
296
+ else:
297
+ # max_file is "max", i.e. is unset.
298
+ docker_limit = None
299
+
300
+ # Use psutil if it is available.
301
+ psutil_memory_in_bytes = psutil.virtual_memory().total
302
+
303
+ if docker_limit is not None:
304
+ # We take the min because the cgroup limit is very large if we aren't
305
+ # in Docker.
306
+ return min(docker_limit, psutil_memory_in_bytes)
307
+
308
+ return psutil_memory_in_bytes
309
+
310
+
311
+ def binary_to_hex(identifier):
312
+ hex_identifier = binascii.hexlify(identifier)
313
+ hex_identifier = hex_identifier.decode()
314
+ return hex_identifier
315
+
316
+
317
+ def hex_to_binary(hex_identifier):
318
+ return binascii.unhexlify(hex_identifier)
319
+
320
+
321
+ def try_make_directory_shared(directory_path):
322
+ try:
323
+ os.chmod(directory_path, 0o0777)
324
+ except OSError as e:
325
+ # Silently suppress the PermissionError that is thrown by the chmod.
326
+ # This is done because the user attempting to change the permissions
327
+ # on a directory may not own it. The chmod is attempted whether the
328
+ # directory is new or not to avoid race conditions.
329
+ # ray-project/ray/#3591
330
+ if e.errno in [errno.EACCES, errno.EPERM]:
331
+ pass
332
+ else:
333
+ raise
334
+
335
+
336
+ def try_to_create_directory(directory_path):
337
+ # Attempt to create a directory that is globally readable/writable.
338
+ directory_path = os.path.expanduser(directory_path)
339
+ os.makedirs(directory_path, exist_ok=True)
340
+ # Change the log directory permissions so others can use it. This is
341
+ # important when multiple people are using the same machine.
342
+ try_make_directory_shared(directory_path)
343
+
344
+
345
+ def get_function_args(callable):
346
+ all_parameters = frozenset(signature(callable).parameters)
347
+ return list(all_parameters)
348
+
349
+
350
+ def decode(byte_str: str, allow_none: bool = False, encode_type: str = "utf-8"):
351
+ """Make this unicode in Python 3, otherwise leave it as bytes.
352
+
353
+ Args:
354
+ byte_str: The byte string to decode.
355
+ allow_none: If true, then we will allow byte_str to be None in which
356
+ case we will return an empty string. TODO(rkn): Remove this flag.
357
+ This is only here to simplify upgrading to flatbuffers 1.10.0.
358
+ encode_type: The encoding type to use for decoding. Defaults to "utf-8".
359
+
360
+ Returns:
361
+ A byte string in Python 2 and a unicode string in Python 3.
362
+ """
363
+ if byte_str is None and allow_none:
364
+ return ""
365
+
366
+ if not isinstance(byte_str, bytes):
367
+ raise ValueError(f"The argument {byte_str} must be a bytes object.")
368
+ return byte_str.decode(encode_type)
lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (288 Bytes). View file
 
lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/_clonevirtualenv.cpython-312.pyc ADDED
Binary file (16.4 kB). View file
 
lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/conda.cpython-312.pyc ADDED
Binary file (17 kB). View file
 
lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/conda_utils.cpython-312.pyc ADDED
Binary file (12.1 kB). View file
 
lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/constants.cpython-312.pyc ADDED
Binary file (735 Bytes). View file
 
lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/context.cpython-312.pyc ADDED
Binary file (5 kB). View file
 
lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/default_impl.cpython-312.pyc ADDED
Binary file (415 Bytes). View file
 
lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/dependency_utils.cpython-312.pyc ADDED
Binary file (5.93 kB). View file
 
lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/image_uri.cpython-312.pyc ADDED
Binary file (8.5 kB). View file
 
lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/java_jars.cpython-312.pyc ADDED
Binary file (5.34 kB). View file
 
lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/nsight.cpython-312.pyc ADDED
Binary file (6.5 kB). View file
 
lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/packaging.cpython-312.pyc ADDED
Binary file (40.1 kB). View file