BryanW commited on
Commit
c13a4d4
·
verified ·
1 Parent(s): b4be5f5

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. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/__init__.py +51 -0
  2. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/accelerator.py +0 -0
  3. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/big_modeling.py +789 -0
  4. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/checkpointing.py +331 -0
  5. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/data_loader.py +1451 -0
  6. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/hooks.py +776 -0
  7. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/inference.py +186 -0
  8. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/launchers.py +307 -0
  9. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/local_sgd.py +106 -0
  10. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/logging.py +126 -0
  11. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/memory_utils.py +22 -0
  12. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/optimizer.py +213 -0
  13. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/parallelism_config.py +378 -0
  14. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/scheduler.py +98 -0
  15. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/state.py +1373 -0
  16. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/tracking.py +1326 -0
  17. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/cuda_pathfinder-1.4.0.dist-info/INSTALLER +1 -0
  18. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/cuda_pathfinder-1.4.0.dist-info/METADATA +47 -0
  19. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/cuda_pathfinder-1.4.0.dist-info/RECORD +47 -0
  20. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/cuda_pathfinder-1.4.0.dist-info/WHEEL +5 -0
  21. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/cuda_pathfinder-1.4.0.dist-info/top_level.txt +1 -0
  22. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/fsspec/caching.py +950 -0
  23. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/fsspec/dircache.py +98 -0
  24. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/fsspec/gui.py +414 -0
  25. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/fsspec/spec.py +1982 -0
  26. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/hf_xet-1.3.2.dist-info/INSTALLER +1 -0
  27. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/hf_xet-1.3.2.dist-info/METADATA +87 -0
  28. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/hf_xet-1.3.2.dist-info/RECORD +9 -0
  29. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/hf_xet-1.3.2.dist-info/WHEEL +5 -0
  30. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/idna-3.10.dist-info/INSTALLER +1 -0
  31. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/idna-3.10.dist-info/LICENSE.md +31 -0
  32. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/idna-3.10.dist-info/METADATA +250 -0
  33. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/idna-3.10.dist-info/RECORD +22 -0
  34. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/idna-3.10.dist-info/WHEEL +4 -0
  35. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/nvidia_nccl_cu12-2.27.5.dist-info/INSTALLER +1 -0
  36. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/nvidia_nccl_cu12-2.27.5.dist-info/METADATA +45 -0
  37. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/nvidia_nccl_cu12-2.27.5.dist-info/RECORD +8 -0
  38. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/nvidia_nccl_cu12-2.27.5.dist-info/WHEEL +6 -0
  39. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/nvidia_nccl_cu12-2.27.5.dist-info/top_level.txt +1 -0
  40. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/__init__.py +2485 -0
  41. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_common.py +861 -0
  42. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_ntuples.py +429 -0
  43. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_psaix.py +546 -0
  44. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_psbsd.py +904 -0
  45. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_pslinux.py +2269 -0
  46. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_psosx.py +549 -0
  47. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_psposix.py +363 -0
  48. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_pssunos.py +704 -0
  49. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_pswindows.py +1096 -0
  50. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/pytablewriter-1.2.1.dist-info/INSTALLER +1 -0
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/__init__.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2020 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ __version__ = "1.12.0"
15
+
16
+ from .accelerator import Accelerator
17
+ from .big_modeling import (
18
+ cpu_offload,
19
+ cpu_offload_with_hook,
20
+ disk_offload,
21
+ dispatch_model,
22
+ init_empty_weights,
23
+ init_on_device,
24
+ load_checkpoint_and_dispatch,
25
+ )
26
+ from .data_loader import skip_first_batches
27
+ from .inference import prepare_pippy
28
+ from .launchers import debug_launcher, notebook_launcher
29
+ from .parallelism_config import ParallelismConfig
30
+ from .state import PartialState
31
+ from .utils import (
32
+ AutocastKwargs,
33
+ DataLoaderConfiguration,
34
+ DDPCommunicationHookType,
35
+ DeepSpeedPlugin,
36
+ DistributedDataParallelKwargs,
37
+ DistributedType,
38
+ FullyShardedDataParallelPlugin,
39
+ GradScalerKwargs,
40
+ InitProcessGroupKwargs,
41
+ ProfileKwargs,
42
+ find_executable_batch_size,
43
+ infer_auto_device_map,
44
+ is_rich_available,
45
+ load_checkpoint_in_model,
46
+ synchronize_rng_states,
47
+ )
48
+
49
+
50
+ if is_rich_available():
51
+ from .utils import rich
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/accelerator.py ADDED
The diff for this file is too large to render. See raw diff
 
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/big_modeling.py ADDED
@@ -0,0 +1,789 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2022 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import logging
16
+ import os
17
+ import re
18
+ from contextlib import contextmanager
19
+ from functools import wraps
20
+ from typing import Optional, Union
21
+
22
+ import torch
23
+ import torch.nn as nn
24
+
25
+ from .hooks import (
26
+ AlignDevicesHook,
27
+ CpuOffload,
28
+ LayerwiseCastingHook,
29
+ UserCpuOffloadHook,
30
+ add_hook_to_module,
31
+ attach_align_device_hook,
32
+ attach_align_device_hook_on_blocks,
33
+ )
34
+ from .utils import (
35
+ OffloadedWeightsLoader,
36
+ check_cuda_p2p_ib_support,
37
+ check_device_map,
38
+ extract_submodules_state_dict,
39
+ find_tied_parameters,
40
+ get_balanced_memory,
41
+ infer_auto_device_map,
42
+ is_bnb_available,
43
+ is_mlu_available,
44
+ is_musa_available,
45
+ is_npu_available,
46
+ is_sdaa_available,
47
+ is_xpu_available,
48
+ load_checkpoint_in_model,
49
+ offload_state_dict,
50
+ parse_flag_from_env,
51
+ retie_parameters,
52
+ )
53
+ from .utils.constants import SUPPORTED_PYTORCH_LAYERS_FOR_UPCASTING
54
+ from .utils.other import recursive_getattr
55
+
56
+
57
+ logger = logging.getLogger(__name__)
58
+
59
+
60
+ @contextmanager
61
+ def init_empty_weights(include_buffers: Optional[bool] = None):
62
+ """
63
+ A context manager under which models are initialized with all parameters on the meta device, therefore creating an
64
+ empty model. Useful when just initializing the model would blow the available RAM.
65
+
66
+ Args:
67
+ include_buffers (`bool`, *optional*):
68
+ Whether or not to also put all buffers on the meta device while initializing.
69
+
70
+ Example:
71
+
72
+ ```python
73
+ import torch.nn as nn
74
+ from accelerate import init_empty_weights
75
+
76
+ # Initialize a model with 100 billions parameters in no time and without using any RAM.
77
+ with init_empty_weights():
78
+ tst = nn.Sequential(*[nn.Linear(10000, 10000) for _ in range(1000)])
79
+ ```
80
+
81
+ <Tip warning={true}>
82
+
83
+ Any model created under this context manager has no weights. As such you can't do something like
84
+ `model.to(some_device)` with it. To load weights inside your empty model, see [`load_checkpoint_and_dispatch`].
85
+ Make sure to overwrite the default device_map param for [`load_checkpoint_and_dispatch`], otherwise dispatch is not
86
+ called.
87
+
88
+ </Tip>
89
+ """
90
+ if include_buffers is None:
91
+ include_buffers = parse_flag_from_env("ACCELERATE_INIT_INCLUDE_BUFFERS", False)
92
+ with init_on_device(torch.device("meta"), include_buffers=include_buffers) as f:
93
+ yield f
94
+
95
+
96
+ @contextmanager
97
+ def init_on_device(device: torch.device, include_buffers: Optional[bool] = None):
98
+ """
99
+ A context manager under which models are initialized with all parameters on the specified device.
100
+
101
+ Args:
102
+ device (`torch.device`):
103
+ Device to initialize all parameters on.
104
+ include_buffers (`bool`, *optional*):
105
+ Whether or not to also put all buffers on the meta device while initializing.
106
+
107
+ Example:
108
+
109
+ ```python
110
+ import torch.nn as nn
111
+ from accelerate import init_on_device
112
+
113
+ with init_on_device(device=torch.device("cuda")):
114
+ tst = nn.Linear(100, 100) # on `cuda` device
115
+ ```
116
+ """
117
+ if include_buffers is None:
118
+ include_buffers = parse_flag_from_env("ACCELERATE_INIT_INCLUDE_BUFFERS", False)
119
+
120
+ if include_buffers:
121
+ with device:
122
+ yield
123
+ return
124
+
125
+ old_register_parameter = nn.Module.register_parameter
126
+ if include_buffers:
127
+ old_register_buffer = nn.Module.register_buffer
128
+
129
+ def register_empty_parameter(module, name, param):
130
+ old_register_parameter(module, name, param)
131
+ if param is not None:
132
+ param_cls = type(module._parameters[name])
133
+ kwargs = module._parameters[name].__dict__
134
+ kwargs["requires_grad"] = param.requires_grad
135
+ module._parameters[name] = param_cls(module._parameters[name].to(device), **kwargs)
136
+
137
+ def register_empty_buffer(module, name, buffer, persistent=True):
138
+ old_register_buffer(module, name, buffer, persistent=persistent)
139
+ if buffer is not None:
140
+ module._buffers[name] = module._buffers[name].to(device)
141
+
142
+ # Patch tensor creation
143
+ if include_buffers:
144
+ tensor_constructors_to_patch = {
145
+ torch_function_name: getattr(torch, torch_function_name)
146
+ for torch_function_name in ["empty", "zeros", "ones", "full"]
147
+ }
148
+ else:
149
+ tensor_constructors_to_patch = {}
150
+
151
+ def patch_tensor_constructor(fn):
152
+ def wrapper(*args, **kwargs):
153
+ kwargs["device"] = device
154
+ return fn(*args, **kwargs)
155
+
156
+ return wrapper
157
+
158
+ try:
159
+ nn.Module.register_parameter = register_empty_parameter
160
+ if include_buffers:
161
+ nn.Module.register_buffer = register_empty_buffer
162
+ for torch_function_name in tensor_constructors_to_patch.keys():
163
+ setattr(torch, torch_function_name, patch_tensor_constructor(getattr(torch, torch_function_name)))
164
+ yield
165
+ finally:
166
+ nn.Module.register_parameter = old_register_parameter
167
+ if include_buffers:
168
+ nn.Module.register_buffer = old_register_buffer
169
+ for torch_function_name, old_torch_function in tensor_constructors_to_patch.items():
170
+ setattr(torch, torch_function_name, old_torch_function)
171
+
172
+
173
+ def cpu_offload(
174
+ model: nn.Module,
175
+ execution_device: Optional[torch.device] = None,
176
+ offload_buffers: bool = False,
177
+ state_dict: Optional[dict[str, torch.Tensor]] = None,
178
+ preload_module_classes: Optional[list[str]] = None,
179
+ ):
180
+ """
181
+ Activates full CPU offload for a model. As a result, all parameters of the model will be offloaded and only one
182
+ copy of the state dict of the model will be kept. During the forward pass, parameters will be extracted from that
183
+ state dict and put on the execution device passed as they are needed, then offloaded again.
184
+
185
+ Args:
186
+ model (`torch.nn.Module`):
187
+ The model to offload.
188
+ execution_device (`torch.device`, *optional*):
189
+ The device on which the forward pass of the model will be executed (should be a GPU). Will default to the
190
+ model first parameter device.
191
+ offload_buffers (`bool`, *optional*, defaults to `False`):
192
+ Whether or not to offload the buffers with the model parameters.
193
+ state_dict (`Dict[str, torch.Tensor]`, *optional*):
194
+ The state dict of the model that will be kept on CPU.
195
+ preload_module_classes (`List[str]`, *optional*):
196
+ A list of classes whose instances should load all their weights (even in the submodules) at the beginning
197
+ of the forward. This should only be used for classes that have submodules which are registered but not
198
+ called directly during the forward, for instance if a `dense` linear layer is registered, but at forward,
199
+ `dense.weight` and `dense.bias` are used in some operations instead of calling `dense` directly.
200
+ """
201
+ if execution_device is None:
202
+ execution_device = next(iter(model.parameters())).device
203
+ if state_dict is None:
204
+ state_dict = {n: p.to("cpu") for n, p in model.state_dict().items()}
205
+
206
+ add_hook_to_module(model, AlignDevicesHook(io_same_device=True), append=True)
207
+ attach_align_device_hook(
208
+ model,
209
+ execution_device=execution_device,
210
+ offload=True,
211
+ offload_buffers=offload_buffers,
212
+ weights_map=state_dict,
213
+ preload_module_classes=preload_module_classes,
214
+ )
215
+
216
+ return model
217
+
218
+
219
+ def cpu_offload_with_hook(
220
+ model: torch.nn.Module,
221
+ execution_device: Optional[Union[int, str, torch.device]] = None,
222
+ prev_module_hook: Optional[UserCpuOffloadHook] = None,
223
+ ):
224
+ """
225
+ Offloads a model on the CPU and puts it back to an execution device when executed. The difference with
226
+ [`cpu_offload`] is that the model stays on the execution device after the forward and is only offloaded again when
227
+ the `offload` method of the returned `hook` is called. Useful for pipelines running a model in a loop.
228
+
229
+ Args:
230
+ model (`torch.nn.Module`):
231
+ The model to offload.
232
+ execution_device(`str`, `int` or `torch.device`, *optional*):
233
+ The device on which the model should be executed. Will default to the MPS device if it's available, then
234
+ GPU 0 if there is a GPU, and finally to the CPU.
235
+ prev_module_hook (`UserCpuOffloadHook`, *optional*):
236
+ The hook sent back by this function for a previous model in the pipeline you are running. If passed, its
237
+ offload method will be called just before the forward of the model to which this hook is attached.
238
+
239
+ Example:
240
+
241
+ ```py
242
+ model_1, hook_1 = cpu_offload_with_hook(model_1, cuda_device)
243
+ model_2, hook_2 = cpu_offload_with_hook(model_2, cuda_device, prev_module_hook=hook_1)
244
+ model_3, hook_3 = cpu_offload_with_hook(model_3, cuda_device, prev_module_hook=hook_2)
245
+
246
+ hid_1 = model_1(input)
247
+ for i in range(50):
248
+ # model1 is offloaded on the CPU at the first iteration, model 2 stays on the GPU for this whole loop.
249
+ hid_2 = model_2(hid_1)
250
+ # model2 is offloaded to the CPU just before this forward.
251
+ hid_3 = model_3(hid_3)
252
+
253
+ # For model3, you need to manually call the hook offload method.
254
+ hook_3.offload()
255
+ ```
256
+ """
257
+ hook = CpuOffload(execution_device=execution_device, prev_module_hook=prev_module_hook)
258
+ add_hook_to_module(model, hook, append=True)
259
+ user_hook = UserCpuOffloadHook(model, hook)
260
+ return model, user_hook
261
+
262
+
263
+ def disk_offload(
264
+ model: nn.Module,
265
+ offload_dir: Union[str, os.PathLike],
266
+ execution_device: Optional[torch.device] = None,
267
+ offload_buffers: bool = False,
268
+ preload_module_classes: Optional[list[str]] = None,
269
+ ):
270
+ """
271
+ Activates full disk offload for a model. As a result, all parameters of the model will be offloaded as
272
+ memory-mapped array in a given folder. During the forward pass, parameters will be accessed from that folder and
273
+ put on the execution device passed as they are needed, then offloaded again.
274
+
275
+ Args:
276
+ model (`torch.nn.Module`): The model to offload.
277
+ offload_dir (`str` or `os.PathLike`):
278
+ The folder in which to offload the model weights (or where the model weights are already offloaded).
279
+ execution_device (`torch.device`, *optional*):
280
+ The device on which the forward pass of the model will be executed (should be a GPU). Will default to the
281
+ model's first parameter device.
282
+ offload_buffers (`bool`, *optional*, defaults to `False`):
283
+ Whether or not to offload the buffers with the model parameters.
284
+ preload_module_classes (`List[str]`, *optional*):
285
+ A list of classes whose instances should load all their weights (even in the submodules) at the beginning
286
+ of the forward. This should only be used for classes that have submodules which are registered but not
287
+ called directly during the forward, for instance if a `dense` linear layer is registered, but at forward,
288
+ `dense.weight` and `dense.bias` are used in some operations instead of calling `dense` directly.
289
+ """
290
+ if not os.path.isdir(offload_dir) or not os.path.isfile(os.path.join(offload_dir, "index.json")):
291
+ offload_state_dict(offload_dir, model.state_dict())
292
+ if execution_device is None:
293
+ execution_device = next(iter(model.parameters())).device
294
+ weights_map = OffloadedWeightsLoader(save_folder=offload_dir)
295
+
296
+ add_hook_to_module(model, AlignDevicesHook(io_same_device=True), append=True)
297
+ attach_align_device_hook(
298
+ model,
299
+ execution_device=execution_device,
300
+ offload=True,
301
+ offload_buffers=offload_buffers,
302
+ weights_map=weights_map,
303
+ preload_module_classes=preload_module_classes,
304
+ )
305
+
306
+ return model
307
+
308
+
309
+ def dispatch_model(
310
+ model: nn.Module,
311
+ device_map: dict[str, Union[str, int, torch.device]],
312
+ main_device: Optional[torch.device] = None,
313
+ state_dict: Optional[dict[str, torch.Tensor]] = None,
314
+ offload_dir: Optional[Union[str, os.PathLike]] = None,
315
+ offload_index: Optional[dict[str, str]] = None,
316
+ offload_buffers: bool = False,
317
+ skip_keys: Optional[Union[str, list[str]]] = None,
318
+ preload_module_classes: Optional[list[str]] = None,
319
+ force_hooks: bool = False,
320
+ ):
321
+ """
322
+ Dispatches a model according to a given device map. Layers of the model might be spread across GPUs, offloaded on
323
+ the CPU or even the disk.
324
+
325
+ Args:
326
+ model (`torch.nn.Module`):
327
+ The model to dispatch.
328
+ device_map (`Dict[str, Union[str, int, torch.device]]`):
329
+ A dictionary mapping module names in the models `state_dict` to the device they should go to. Note that
330
+ `"disk"` is accepted even if it's not a proper value for `torch.device`.
331
+ main_device (`str`, `int` or `torch.device`, *optional*):
332
+ The main execution device. Will default to the first device in the `device_map` different from `"cpu"` or
333
+ `"disk"`.
334
+ state_dict (`Dict[str, torch.Tensor]`, *optional*):
335
+ The state dict of the part of the model that will be kept on CPU.
336
+ offload_dir (`str` or `os.PathLike`):
337
+ The folder in which to offload the model weights (or where the model weights are already offloaded).
338
+ offload_index (`Dict`, *optional*):
339
+ A dictionary from weight name to their information (`dtype`/ `shape` or safetensors filename). Will default
340
+ to the index saved in `save_folder`.
341
+ offload_buffers (`bool`, *optional*, defaults to `False`):
342
+ Whether or not to offload the buffers with the model parameters.
343
+ skip_keys (`str` or `List[str]`, *optional*):
344
+ A list of keys to ignore when moving inputs or outputs between devices.
345
+ preload_module_classes (`List[str]`, *optional*):
346
+ A list of classes whose instances should load all their weights (even in the submodules) at the beginning
347
+ of the forward. This should only be used for classes that have submodules which are registered but not
348
+ called directly during the forward, for instance if a `dense` linear layer is registered, but at forward,
349
+ `dense.weight` and `dense.bias` are used in some operations instead of calling `dense` directly.
350
+ force_hooks (`bool`, *optional*, defaults to `False`):
351
+ Whether or not to force device hooks to be attached to the model even if all layers are dispatched to a
352
+ single device.
353
+ """
354
+ # Error early if the device map is incomplete.
355
+ check_device_map(model, device_map)
356
+
357
+ # We need to force hook for quantized model that can't be moved with to()
358
+ if getattr(model, "quantization_method", "bitsandbytes") == "bitsandbytes":
359
+ # since bnb 0.43.2, we can move 4-bit model
360
+ if getattr(model, "is_loaded_in_8bit", False) or (
361
+ getattr(model, "is_loaded_in_4bit", False) and not is_bnb_available(min_version="0.43.2")
362
+ ):
363
+ force_hooks = True
364
+
365
+ # We attach hooks if the device_map has at least 2 different devices or if
366
+ # force_hooks is set to `True`. Otherwise, the model in already loaded
367
+ # in the unique device and the user can decide where to dispatch the model.
368
+ # If the model is quantized, we always force-dispatch the model
369
+ if (len(set(device_map.values())) > 1) or force_hooks:
370
+ if main_device is None:
371
+ if set(device_map.values()) == {"cpu"} or set(device_map.values()) == {"cpu", "disk"}:
372
+ main_device = "cpu"
373
+ else:
374
+ main_device = [d for d in device_map.values() if d not in ["cpu", "disk"]][0]
375
+
376
+ if main_device != "cpu":
377
+ cpu_modules = [name for name, device in device_map.items() if device == "cpu"]
378
+ if state_dict is None and len(cpu_modules) > 0:
379
+ state_dict = extract_submodules_state_dict(model.state_dict(), cpu_modules)
380
+
381
+ disk_modules = [name for name, device in device_map.items() if device == "disk"]
382
+ if offload_dir is None and offload_index is None and len(disk_modules) > 0:
383
+ raise ValueError(
384
+ "We need an `offload_dir` to dispatch this model according to this `device_map`, the following submodules "
385
+ f"need to be offloaded: {', '.join(disk_modules)}."
386
+ )
387
+ if (
388
+ len(disk_modules) > 0
389
+ and offload_index is None
390
+ and (not os.path.isdir(offload_dir) or not os.path.isfile(os.path.join(offload_dir, "index.json")))
391
+ ):
392
+ disk_state_dict = extract_submodules_state_dict(model.state_dict(), disk_modules)
393
+ offload_state_dict(offload_dir, disk_state_dict)
394
+
395
+ execution_device = {
396
+ name: main_device if device in ["cpu", "disk"] else device for name, device in device_map.items()
397
+ }
398
+ execution_device[""] = main_device
399
+ offloaded_devices = ["disk"] if main_device == "cpu" or main_device == "mps" else ["cpu", "disk"]
400
+ offload = {name: device in offloaded_devices for name, device in device_map.items()}
401
+ save_folder = offload_dir if len(disk_modules) > 0 else None
402
+ if state_dict is not None or save_folder is not None or offload_index is not None:
403
+ device = main_device if offload_index is not None else None
404
+ weights_map = OffloadedWeightsLoader(
405
+ state_dict=state_dict, save_folder=save_folder, index=offload_index, device=device
406
+ )
407
+ else:
408
+ weights_map = None
409
+
410
+ # When dispatching the model's parameters to the devices specified in device_map, we want to avoid allocating memory several times for the
411
+ # tied parameters. The dictionary tied_params_map keeps track of the already allocated data for a given tied parameter (represented by its
412
+ # original pointer) on each devices.
413
+ tied_params = find_tied_parameters(model)
414
+
415
+ tied_params_map = {}
416
+ for group in tied_params:
417
+ for param_name in group:
418
+ # data_ptr() is enough here, as `find_tied_parameters` finds tied params simply by comparing `param1 is param2`, so we don't need
419
+ # to care about views of tensors through storage_offset.
420
+ data_ptr = recursive_getattr(model, param_name).data_ptr()
421
+ tied_params_map[data_ptr] = {}
422
+
423
+ # Note: To handle the disk offloading case, we can not simply use weights_map[param_name].data_ptr() as the reference pointer,
424
+ # as we have no guarantee that safetensors' `file.get_tensor()` will always give the same pointer.
425
+
426
+ attach_align_device_hook_on_blocks(
427
+ model,
428
+ execution_device=execution_device,
429
+ offload=offload,
430
+ offload_buffers=offload_buffers,
431
+ weights_map=weights_map,
432
+ skip_keys=skip_keys,
433
+ preload_module_classes=preload_module_classes,
434
+ tied_params_map=tied_params_map,
435
+ )
436
+
437
+ # warn if there is any params on the meta device
438
+ offloaded_devices_str = " and ".join(
439
+ [device for device in set(device_map.values()) if device in ("cpu", "disk")]
440
+ )
441
+ if len(offloaded_devices_str) > 0:
442
+ logger.warning(
443
+ f"Some parameters are on the meta device because they were offloaded to the {offloaded_devices_str}."
444
+ )
445
+
446
+ # Attaching the hook may break tied weights, so we retie them
447
+ retie_parameters(model, tied_params)
448
+
449
+ # add warning to cuda and to method
450
+ def add_warning(fn, model):
451
+ @wraps(fn)
452
+ def wrapper(*args, **kwargs):
453
+ warning_msg = "You shouldn't move a model that is dispatched using accelerate hooks."
454
+ if str(fn.__name__) == "to":
455
+ to_device = torch._C._nn._parse_to(*args, **kwargs)[0]
456
+ if to_device is not None:
457
+ logger.warning(warning_msg)
458
+ else:
459
+ logger.warning(warning_msg)
460
+ for param in model.parameters():
461
+ if param.device == torch.device("meta"):
462
+ raise RuntimeError("You can't move a model that has some modules offloaded to cpu or disk.")
463
+ return fn(*args, **kwargs)
464
+
465
+ return wrapper
466
+
467
+ # Make sure to update _accelerate_added_attributes in hooks.py if you add any hook
468
+ model.to = add_warning(model.to, model)
469
+ if is_npu_available():
470
+ model.npu = add_warning(model.npu, model)
471
+ elif is_mlu_available():
472
+ model.mlu = add_warning(model.mlu, model)
473
+ elif is_sdaa_available():
474
+ model.sdaa = add_warning(model.sdaa, model)
475
+ elif is_musa_available():
476
+ model.musa = add_warning(model.musa, model)
477
+ elif is_xpu_available():
478
+ model.xpu = add_warning(model.xpu, model)
479
+ else:
480
+ model.cuda = add_warning(model.cuda, model)
481
+
482
+ # Check if we are using multi-gpus with RTX 4000 series
483
+ use_multi_gpu = len([device for device in set(device_map.values()) if device not in ("cpu", "disk")]) > 1
484
+ if use_multi_gpu and not check_cuda_p2p_ib_support():
485
+ logger.warning(
486
+ "We've detected an older driver with an RTX 4000 series GPU. These drivers have issues with P2P. "
487
+ "This can affect the multi-gpu inference when using accelerate device_map."
488
+ "Please make sure to update your driver to the latest version which resolves this."
489
+ )
490
+ else:
491
+ device = list(device_map.values())[0]
492
+ # `torch.Tensor.to(<int num>)` is not supported by `torch_npu` (see this [issue](https://github.com/Ascend/pytorch/issues/16)).
493
+ if is_npu_available() and isinstance(device, int):
494
+ device = f"npu:{device}"
495
+ elif is_mlu_available() and isinstance(device, int):
496
+ device = f"mlu:{device}"
497
+ elif is_sdaa_available() and isinstance(device, int):
498
+ device = f"sdaa:{device}"
499
+ elif is_musa_available() and isinstance(device, int):
500
+ device = f"musa:{device}"
501
+ if device != "disk":
502
+ model.to(device)
503
+ else:
504
+ raise ValueError(
505
+ "You are trying to offload the whole model to the disk. Please use the `disk_offload` function instead."
506
+ )
507
+ # Convert OrderedDict back to dict for easier usage
508
+ model.hf_device_map = dict(device_map)
509
+ return model
510
+
511
+
512
+ def load_checkpoint_and_dispatch(
513
+ model: nn.Module,
514
+ checkpoint: Union[str, os.PathLike],
515
+ device_map: Optional[Union[str, dict[str, Union[int, str, torch.device]]]] = None,
516
+ max_memory: Optional[dict[Union[int, str], Union[int, str]]] = None,
517
+ no_split_module_classes: Optional[list[str]] = None,
518
+ offload_folder: Optional[Union[str, os.PathLike]] = None,
519
+ offload_buffers: bool = False,
520
+ dtype: Optional[Union[str, torch.dtype]] = None,
521
+ offload_state_dict: Optional[bool] = None,
522
+ skip_keys: Optional[Union[str, list[str]]] = None,
523
+ preload_module_classes: Optional[list[str]] = None,
524
+ force_hooks: bool = False,
525
+ strict: bool = False,
526
+ full_state_dict: bool = True,
527
+ broadcast_from_rank0: bool = False,
528
+ ):
529
+ """
530
+ Loads a (potentially sharded) checkpoint inside a model, potentially sending weights to a given device as they are
531
+ loaded and adds the various hooks that will make this model run properly (even if split across devices).
532
+
533
+ Args:
534
+ model (`torch.nn.Module`): The model in which we want to load a checkpoint.
535
+ checkpoint (`str` or `os.PathLike`):
536
+ The folder checkpoint to load. It can be:
537
+ - a path to a file containing a whole model state dict
538
+ - a path to a `.json` file containing the index to a sharded checkpoint
539
+ - a path to a folder containing a unique `.index.json` file and the shards of a checkpoint.
540
+ device_map (`Dict[str, Union[int, str, torch.device]]`, *optional*):
541
+ A map that specifies where each submodule should go. It doesn't need to be refined to each parameter/buffer
542
+ name, once a given module name is inside, every submodule of it will be sent to the same device.
543
+
544
+ To have Accelerate compute the most optimized `device_map` automatically, set `device_map="auto"`. For more
545
+ information about each option see [here](../concept_guides/big_model_inference#designing-a-device-map).
546
+ Defaults to None, which means [`dispatch_model`] will not be called.
547
+ max_memory (`Dict`, *optional*):
548
+ A dictionary device identifier to maximum memory. Will default to the maximum memory available for each GPU
549
+ and the available CPU RAM if unset.
550
+ no_split_module_classes (`List[str]`, *optional*):
551
+ A list of layer class names that should never be split across device (for instance any layer that has a
552
+ residual connection).
553
+ offload_folder (`str` or `os.PathLike`, *optional*):
554
+ If the `device_map` contains any value `"disk"`, the folder where we will offload weights.
555
+ offload_buffers (`bool`, *optional*, defaults to `False`):
556
+ In the layers that are offloaded on the CPU or the hard drive, whether or not to offload the buffers as
557
+ well as the parameters.
558
+ dtype (`str` or `torch.dtype`, *optional*):
559
+ If provided, the weights will be converted to that type when loaded.
560
+ offload_state_dict (`bool`, *optional*):
561
+ If `True`, will temporarily offload the CPU state dict on the hard drive to avoid getting out of CPU RAM if
562
+ the weight of the CPU state dict + the biggest shard does not fit. Will default to `True` if the device map
563
+ picked contains `"disk"` values.
564
+ skip_keys (`str` or `List[str]`, *optional*):
565
+ A list of keys to ignore when moving inputs or outputs between devices.
566
+ preload_module_classes (`List[str]`, *optional*):
567
+ A list of classes whose instances should load all their weights (even in the submodules) at the beginning
568
+ of the forward. This should only be used for classes that have submodules which are registered but not
569
+ called directly during the forward, for instance if a `dense` linear layer is registered, but at forward,
570
+ `dense.weight` and `dense.bias` are used in some operations instead of calling `dense` directly.
571
+ force_hooks (`bool`, *optional*, defaults to `False`):
572
+ Whether or not to force device hooks to be attached to the model even if all layers are dispatched to a
573
+ single device.
574
+ strict (`bool`, *optional*, defaults to `False`):
575
+ Whether to strictly enforce that the keys in the checkpoint state_dict match the keys of the model's
576
+ state_dict.
577
+ full_state_dict (`bool`, *optional*, defaults to `True`): if this is set to `True`, all the tensors in the
578
+ loaded state_dict will be gathered. No ShardedTensor and DTensor will be in the loaded state_dict.
579
+ broadcast_from_rank0 (`False`, *optional*, defaults to `False`): when the option is `True`, a distributed
580
+ `ProcessGroup` must be initialized. rank0 should receive a full state_dict and will broadcast the tensors
581
+ in the state_dict one by one to other ranks. Other ranks will receive the tensors and shard (if applicable)
582
+ according to the local shards in the model.
583
+
584
+ Example:
585
+
586
+ ```python
587
+ >>> from accelerate import init_empty_weights, load_checkpoint_and_dispatch
588
+ >>> from huggingface_hub import hf_hub_download
589
+ >>> from transformers import AutoConfig, AutoModelForCausalLM
590
+
591
+ >>> # Download the Weights
592
+ >>> checkpoint = "EleutherAI/gpt-j-6B"
593
+ >>> weights_location = hf_hub_download(checkpoint, "pytorch_model.bin")
594
+
595
+ >>> # Create a model and initialize it with empty weights
596
+ >>> config = AutoConfig.from_pretrained(checkpoint)
597
+ >>> with init_empty_weights():
598
+ ... model = AutoModelForCausalLM.from_config(config)
599
+
600
+ >>> # Load the checkpoint and dispatch it to the right devices
601
+ >>> model = load_checkpoint_and_dispatch(
602
+ ... model, weights_location, device_map="auto", no_split_module_classes=["GPTJBlock"]
603
+ ... )
604
+ ```
605
+ """
606
+ if isinstance(device_map, str) and device_map not in ["auto", "balanced", "balanced_low_0", "sequential"]:
607
+ raise ValueError(
608
+ "If passing a string for `device_map`, please choose 'auto', 'balanced', 'balanced_low_0' or 'sequential'."
609
+ )
610
+ if isinstance(device_map, str):
611
+ if device_map != "sequential":
612
+ max_memory = get_balanced_memory(
613
+ model,
614
+ max_memory=max_memory,
615
+ no_split_module_classes=no_split_module_classes,
616
+ dtype=dtype,
617
+ low_zero=(device_map == "balanced_low_0"),
618
+ )
619
+ device_map = infer_auto_device_map(
620
+ model,
621
+ max_memory=max_memory,
622
+ no_split_module_classes=no_split_module_classes,
623
+ dtype=dtype,
624
+ offload_buffers=offload_buffers,
625
+ )
626
+ if offload_state_dict is None and device_map is not None and "disk" in device_map.values():
627
+ offload_state_dict = True
628
+ load_checkpoint_in_model(
629
+ model,
630
+ checkpoint,
631
+ device_map=device_map,
632
+ offload_folder=offload_folder,
633
+ dtype=dtype,
634
+ offload_state_dict=offload_state_dict,
635
+ offload_buffers=offload_buffers,
636
+ strict=strict,
637
+ full_state_dict=full_state_dict,
638
+ broadcast_from_rank0=broadcast_from_rank0,
639
+ )
640
+ if device_map is None:
641
+ return model
642
+ return dispatch_model(
643
+ model,
644
+ device_map=device_map,
645
+ offload_dir=offload_folder,
646
+ offload_buffers=offload_buffers,
647
+ skip_keys=skip_keys,
648
+ preload_module_classes=preload_module_classes,
649
+ force_hooks=force_hooks,
650
+ )
651
+
652
+
653
+ def attach_layerwise_casting_hooks(
654
+ module: torch.nn.Module,
655
+ storage_dtype: torch.dtype,
656
+ compute_dtype: torch.dtype,
657
+ skip_modules_pattern: Optional[Union[str, tuple[str, ...]]] = None,
658
+ skip_modules_classes: Optional[tuple[type[torch.nn.Module], ...]] = None,
659
+ non_blocking: bool = False,
660
+ ) -> None:
661
+ r"""
662
+ Applies layerwise casting to a given module. The module expected here is a PyTorch `nn.Module`. This is helpful for
663
+ reducing memory requirements when one doesn't want to fully quantize a model. Model params can be kept in say,
664
+ `torch.float8_e4m3fn` and upcasted to a higher precision like `torch.bfloat16` during forward pass and downcasted
665
+ back to `torch.float8_e4m3fn` to realize memory savings.
666
+
667
+ Args:
668
+ module (`torch.nn.Module`):
669
+ The module whose leaf modules will be cast to a high precision dtype for computation, and to a low
670
+ precision dtype for storage.
671
+ storage_dtype (`torch.dtype`):
672
+ The dtype to cast the module to before/after the forward pass for storage.
673
+ compute_dtype (`torch.dtype`):
674
+ The dtype to cast the module to during the forward pass for computation.
675
+ skip_modules_pattern (`tuple[str, ...]`, defaults to `None`):
676
+ A list of patterns to match the names of the modules to skip during the layerwise casting process. If set
677
+ to `None` alongside `skip_modules_classes` being `None`, the layerwise casting is applied directly to the
678
+ module instead of its internal submodules.
679
+ skip_modules_classes (`tuple[type[torch.nn.Module], ...]`, defaults to `None`):
680
+ A list of module classes to skip during the layerwise casting process.
681
+ non_blocking (`bool`, defaults to `False`):
682
+ If `True`, the weight casting operations are non-blocking.
683
+
684
+ Example:
685
+
686
+ ```python
687
+ >>> from accelerate.hooks import attach_layerwise_casting_hooks
688
+ >>> from transformers import AutoModelForCausalLM
689
+ >>> import torch
690
+
691
+ >>> # Model
692
+ >>> checkpoint = "EleutherAI/gpt-j-6B"
693
+ >>> model = AutoModelForCausalLM.from_pretrained(checkpoint)
694
+
695
+ >>> # Attach hooks and perform inference
696
+ >>> attach_layerwise_casting_hooks(model, storage_dtype=torch.float8_e4m3fn, compute_dtype=torch.bfloat16)
697
+ >>> with torch.no_grad():
698
+ ... model(...)
699
+ ```
700
+
701
+ Users can also pass modules they want to avoid from getting downcasted.
702
+
703
+ ```py
704
+ >>> attach_layerwise_casting_hooks(
705
+ ... model, storage_dtype=torch.float8_e4m3fn, compute_dtype=torch.bfloat16, skip_modules_pattern=["norm"]
706
+ ... )
707
+ ```
708
+ """
709
+ _attach_layerwise_casting_hooks(
710
+ module, storage_dtype, compute_dtype, skip_modules_pattern, skip_modules_classes, non_blocking
711
+ )
712
+
713
+
714
+ def _attach_layerwise_casting_hooks(
715
+ module: torch.nn.Module,
716
+ storage_dtype: torch.dtype,
717
+ compute_dtype: torch.dtype,
718
+ skip_modules_pattern: Optional[Union[str, tuple[str, ...]]] = None,
719
+ skip_modules_classes: Optional[tuple[type[torch.nn.Module], ...]] = None,
720
+ non_blocking: bool = False,
721
+ _prefix: str = "",
722
+ ):
723
+ should_skip = (skip_modules_classes is not None and isinstance(module, skip_modules_classes)) or (
724
+ skip_modules_pattern is not None and any(re.search(pattern, _prefix) for pattern in skip_modules_pattern)
725
+ )
726
+ if should_skip:
727
+ logger.debug(f'Skipping layerwise casting for layer "{_prefix}"')
728
+ return
729
+
730
+ if isinstance(module, SUPPORTED_PYTORCH_LAYERS_FOR_UPCASTING):
731
+ logger.debug(f'Applying layerwise casting to layer "{_prefix}"')
732
+ add_hook_to_module(
733
+ module,
734
+ LayerwiseCastingHook(storage_dtype=storage_dtype, compute_dtype=compute_dtype, non_blocking=non_blocking),
735
+ append=True,
736
+ )
737
+ return
738
+
739
+ for name, submodule in module.named_children():
740
+ layer_name = f"{_prefix}.{name}" if _prefix else name
741
+ _attach_layerwise_casting_hooks(
742
+ submodule,
743
+ storage_dtype,
744
+ compute_dtype,
745
+ skip_modules_pattern,
746
+ skip_modules_classes,
747
+ non_blocking,
748
+ _prefix=layer_name,
749
+ )
750
+
751
+
752
+ def _attach_context_parallel_hooks(
753
+ model: nn.Module,
754
+ ):
755
+ """
756
+ Monkeypatch huggingface's `transformers` model to fix attention mask issues when using context parallelism.
757
+
758
+ This function attaches forward_pre_hooks to each self_attn module of the model, where each hook checks the
759
+ args/kwargs, if they contain an attention mask, if it does, it will remove this mask, check if it is a causal mask,
760
+ if yes, will add a kwarg `is_causal=True`, otherwise will raise an error. This is because context parallelism does
761
+ not support attention masks. This function modifies the model in place.
762
+
763
+ Args:
764
+ model (`nn.Module`):
765
+ The model to attach the hooks to.
766
+
767
+ """
768
+
769
+ def _self_attn_pre_forward_hook(_module, module_args, module_kwargs):
770
+ if "attention_mask" in module_kwargs:
771
+ module_kwargs["attention_mask"] = None
772
+ module_kwargs["is_causal"] = True
773
+
774
+ return module_args, module_kwargs
775
+
776
+ for name, module in model.named_modules():
777
+ # We hope (assume) that if user uses their own model (without this structure which transformers uses), they read the docs saying they can't pass in attention masks
778
+ # Then these cases can happen:
779
+ # 1) some modules end with a `self-attn` module, in which case we attach the hook, but the
780
+ # there's no attention mask kwarg -> hook is a no-op
781
+ # 2) some modules end with a `self-attn` module, in which case we attach the hook, and the
782
+ # attention mask kwarg is passed -> hook will remove the attention mask and add
783
+ # `is_causal=True` kwarg, which either crashes the training or fixes it
784
+ # (training would crash anyway as attention mask isn't supported)
785
+ # 3) no modules end with a `self-attn` module, in which case we don't attach the hook, this is
786
+ # a no-op as well
787
+ if name.endswith("self_attn"):
788
+ # we want the hook to be executed first, to avoid any other hooks doing work on the attention mask
789
+ module.register_forward_pre_hook(_self_attn_pre_forward_hook, with_kwargs=True, prepend=True)
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/checkpointing.py ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2022 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import random
16
+ from pathlib import Path
17
+ from typing import Optional
18
+
19
+ import numpy as np
20
+ import torch
21
+ from safetensors.torch import load_model
22
+
23
+ from .utils import (
24
+ MODEL_NAME,
25
+ OPTIMIZER_NAME,
26
+ RNG_STATE_NAME,
27
+ SAFE_MODEL_NAME,
28
+ SAFE_WEIGHTS_NAME,
29
+ SAMPLER_NAME,
30
+ SCALER_NAME,
31
+ SCHEDULER_NAME,
32
+ WEIGHTS_NAME,
33
+ get_pretty_name,
34
+ is_cuda_available,
35
+ is_hpu_available,
36
+ is_mlu_available,
37
+ is_musa_available,
38
+ is_sdaa_available,
39
+ is_torch_version,
40
+ is_torch_xla_available,
41
+ is_xpu_available,
42
+ load,
43
+ save,
44
+ )
45
+
46
+
47
+ if is_torch_version(">=", "2.4.0"):
48
+ from torch.amp import GradScaler
49
+ else:
50
+ from torch.cuda.amp import GradScaler
51
+
52
+ if is_torch_xla_available():
53
+ import torch_xla.core.xla_model as xm
54
+
55
+ from .logging import get_logger
56
+ from .state import PartialState
57
+
58
+
59
+ logger = get_logger(__name__)
60
+
61
+
62
+ def save_accelerator_state(
63
+ output_dir: str,
64
+ model_states: list[dict],
65
+ optimizers: list,
66
+ schedulers: list,
67
+ dataloaders: list,
68
+ process_index: int,
69
+ step: int,
70
+ scaler: Optional[GradScaler] = None,
71
+ save_on_each_node: bool = False,
72
+ safe_serialization: bool = True,
73
+ ):
74
+ """
75
+ Saves the current states of the models, optimizers, scaler, and RNG generators to a given directory.
76
+
77
+ <Tip>
78
+
79
+ If `safe_serialization` is `True`, models will be saved with `safetensors` while the rest are saved using native
80
+ `pickle`.
81
+
82
+ </Tip>
83
+
84
+ Args:
85
+ output_dir (`str` or `os.PathLike`):
86
+ The name of the folder to save all relevant weights and states.
87
+ model_states (`List[torch.nn.Module]`):
88
+ A list of model states
89
+ optimizers (`List[torch.optim.Optimizer]`):
90
+ A list of optimizer instances
91
+ schedulers (`List[torch.optim.lr_scheduler._LRScheduler]`):
92
+ A list of learning rate schedulers
93
+ dataloaders (`List[torch.utils.data.DataLoader]`):
94
+ A list of dataloader instances to save their sampler states
95
+ process_index (`int`):
96
+ The current process index in the Accelerator state
97
+ step (`int`):
98
+ The current step in the internal step tracker
99
+ scaler (`torch.amp.GradScaler`, *optional*):
100
+ An optional gradient scaler instance to save;
101
+ save_on_each_node (`bool`, *optional*):
102
+ Whether to save on every node, or only the main node.
103
+ safe_serialization (`bool`, *optional*, defaults to `True`):
104
+ Whether to save the model using `safetensors` or the traditional PyTorch way (that uses `pickle`).
105
+ """
106
+ output_dir = Path(output_dir)
107
+ # Model states
108
+ for i, state in enumerate(model_states):
109
+ weights_name = WEIGHTS_NAME if not safe_serialization else SAFE_WEIGHTS_NAME
110
+ if i > 0:
111
+ weights_name = weights_name.replace(".", f"_{i}.")
112
+ output_model_file = output_dir.joinpath(weights_name)
113
+ save(state, output_model_file, save_on_each_node=save_on_each_node, safe_serialization=safe_serialization)
114
+ logger.info(f"Model weights saved in {output_model_file}")
115
+ # Optimizer states
116
+ for i, opt in enumerate(optimizers):
117
+ state = opt.state_dict()
118
+ optimizer_name = f"{OPTIMIZER_NAME}.bin" if i == 0 else f"{OPTIMIZER_NAME}_{i}.bin"
119
+ output_optimizer_file = output_dir.joinpath(optimizer_name)
120
+ save(state, output_optimizer_file, save_on_each_node=save_on_each_node, safe_serialization=False)
121
+ logger.info(f"Optimizer state saved in {output_optimizer_file}")
122
+ # Scheduler states
123
+ for i, scheduler in enumerate(schedulers):
124
+ state = scheduler.state_dict()
125
+ scheduler_name = f"{SCHEDULER_NAME}.bin" if i == 0 else f"{SCHEDULER_NAME}_{i}.bin"
126
+ output_scheduler_file = output_dir.joinpath(scheduler_name)
127
+ save(state, output_scheduler_file, save_on_each_node=save_on_each_node, safe_serialization=False)
128
+ logger.info(f"Scheduler state saved in {output_scheduler_file}")
129
+ # DataLoader states
130
+ for i, dataloader in enumerate(dataloaders):
131
+ sampler_name = f"{SAMPLER_NAME}.bin" if i == 0 else f"{SAMPLER_NAME}_{i}.bin"
132
+ output_sampler_file = output_dir.joinpath(sampler_name)
133
+ # Only save if we have our custom sampler
134
+ from .data_loader import IterableDatasetShard, SeedableRandomSampler
135
+
136
+ if isinstance(dataloader.dataset, IterableDatasetShard):
137
+ sampler = dataloader.get_sampler()
138
+ if isinstance(sampler, SeedableRandomSampler):
139
+ save(sampler, output_sampler_file, save_on_each_node=save_on_each_node, safe_serialization=False)
140
+ if getattr(dataloader, "use_stateful_dataloader", False):
141
+ dataloader_state_dict_name = "dl_state_dict.bin" if i == 0 else f"dl_state_dict_{i}.bin"
142
+ output_dataloader_state_dict_file = output_dir.joinpath(dataloader_state_dict_name)
143
+ state_dict = dataloader.state_dict()
144
+ torch.save(state_dict, output_dataloader_state_dict_file)
145
+ logger.info(f"Sampler state for dataloader {i} saved in {output_sampler_file}")
146
+
147
+ # GradScaler state
148
+ if scaler is not None:
149
+ state = scaler.state_dict()
150
+ output_scaler_file = output_dir.joinpath(SCALER_NAME)
151
+ torch.save(state, output_scaler_file)
152
+ logger.info(f"Gradient scaler state saved in {output_scaler_file}")
153
+ # Random number generator states
154
+ states = {}
155
+ states_name = f"{RNG_STATE_NAME}_{process_index}.pkl"
156
+ states["step"] = step
157
+ states["random_state"] = random.getstate()
158
+ states["numpy_random_seed"] = np.random.get_state()
159
+ states["torch_manual_seed"] = torch.get_rng_state()
160
+ if is_xpu_available():
161
+ states["torch_xpu_manual_seed"] = torch.xpu.get_rng_state_all()
162
+ if is_mlu_available():
163
+ states["torch_mlu_manual_seed"] = torch.mlu.get_rng_state_all()
164
+ elif is_sdaa_available():
165
+ states["torch_sdaa_manual_seed"] = torch.sdaa.get_rng_state_all()
166
+ elif is_musa_available():
167
+ states["torch_musa_manual_seed"] = torch.musa.get_rng_state_all()
168
+ if is_hpu_available():
169
+ states["torch_hpu_manual_seed"] = torch.hpu.get_rng_state_all()
170
+ if is_cuda_available():
171
+ states["torch_cuda_manual_seed"] = torch.cuda.get_rng_state_all()
172
+ if is_torch_xla_available():
173
+ states["xm_seed"] = xm.get_rng_state()
174
+ output_states_file = output_dir.joinpath(states_name)
175
+ torch.save(states, output_states_file)
176
+ logger.info(f"Random states saved in {output_states_file}")
177
+ return output_dir
178
+
179
+
180
+ def load_accelerator_state(
181
+ input_dir,
182
+ models,
183
+ optimizers,
184
+ schedulers,
185
+ dataloaders,
186
+ process_index,
187
+ scaler=None,
188
+ map_location=None,
189
+ load_kwargs=None,
190
+ **load_model_func_kwargs,
191
+ ):
192
+ """
193
+ Loads states of the models, optimizers, scaler, and RNG generators from a given directory.
194
+
195
+ Args:
196
+ input_dir (`str` or `os.PathLike`):
197
+ The name of the folder to load all relevant weights and states.
198
+ models (`List[torch.nn.Module]`):
199
+ A list of model instances
200
+ optimizers (`List[torch.optim.Optimizer]`):
201
+ A list of optimizer instances
202
+ schedulers (`List[torch.optim.lr_scheduler._LRScheduler]`):
203
+ A list of learning rate schedulers
204
+ process_index (`int`):
205
+ The current process index in the Accelerator state
206
+ scaler (`torch.amp.GradScaler`, *optional*):
207
+ An optional *GradScaler* instance to load
208
+ map_location (`str`, *optional*):
209
+ What device to load the optimizer state onto. Should be one of either "cpu" or "on_device".
210
+ load_kwargs (`dict`, *optional*):
211
+ Additional arguments that can be passed to the `load` function.
212
+ load_model_func_kwargs (`dict`, *optional*):
213
+ Additional arguments that can be passed to the model's `load_state_dict` method.
214
+
215
+ Returns:
216
+ `dict`: Contains the `Accelerator` attributes to override while loading the state.
217
+ """
218
+ # stores the `Accelerator` attributes to override
219
+ override_attributes = dict()
220
+ if map_location not in [None, "cpu", "on_device"]:
221
+ raise TypeError(
222
+ "Unsupported optimizer map location passed, please choose one of `None`, `'cpu'`, or `'on_device'`"
223
+ )
224
+ if map_location is None:
225
+ map_location = "cpu"
226
+ elif map_location == "on_device":
227
+ map_location = PartialState().device
228
+
229
+ if load_kwargs is None:
230
+ load_kwargs = {}
231
+
232
+ input_dir = Path(input_dir)
233
+ # Model states
234
+ for i, model in enumerate(models):
235
+ ending = f"_{i}" if i > 0 else ""
236
+ input_model_file = input_dir.joinpath(f"{SAFE_MODEL_NAME}{ending}.safetensors")
237
+ if input_model_file.exists():
238
+ load_model(model, input_model_file, device=str(map_location), **load_model_func_kwargs)
239
+ else:
240
+ # Load with torch
241
+ input_model_file = input_dir.joinpath(f"{MODEL_NAME}{ending}.bin")
242
+ state_dict = load(input_model_file, map_location=map_location)
243
+ model.load_state_dict(state_dict, **load_model_func_kwargs)
244
+ logger.info("All model weights loaded successfully")
245
+
246
+ # Optimizer states
247
+ for i, opt in enumerate(optimizers):
248
+ optimizer_name = f"{OPTIMIZER_NAME}.bin" if i == 0 else f"{OPTIMIZER_NAME}_{i}.bin"
249
+ input_optimizer_file = input_dir.joinpath(optimizer_name)
250
+ optimizer_state = load(input_optimizer_file, map_location=map_location, **load_kwargs)
251
+ optimizers[i].load_state_dict(optimizer_state)
252
+ logger.info("All optimizer states loaded successfully")
253
+
254
+ # Scheduler states
255
+ for i, scheduler in enumerate(schedulers):
256
+ scheduler_name = f"{SCHEDULER_NAME}.bin" if i == 0 else f"{SCHEDULER_NAME}_{i}.bin"
257
+ input_scheduler_file = input_dir.joinpath(scheduler_name)
258
+ scheduler_state = load(input_scheduler_file, **load_kwargs)
259
+ scheduler.load_state_dict(scheduler_state)
260
+ logger.info("All scheduler states loaded successfully")
261
+
262
+ for i, dataloader in enumerate(dataloaders):
263
+ sampler_name = f"{SAMPLER_NAME}.bin" if i == 0 else f"{SAMPLER_NAME}_{i}.bin"
264
+ input_sampler_file = input_dir.joinpath(sampler_name)
265
+ # Only load if we have our custom sampler
266
+ from .data_loader import IterableDatasetShard, SeedableRandomSampler
267
+
268
+ if isinstance(dataloader.dataset, IterableDatasetShard):
269
+ sampler = dataloader.get_sampler()
270
+ if isinstance(sampler, SeedableRandomSampler):
271
+ sampler = dataloader.set_sampler(load(input_sampler_file))
272
+ if getattr(dataloader, "use_stateful_dataloader", False):
273
+ dataloader_state_dict_name = "dl_state_dict.bin" if i == 0 else f"dl_state_dict_{i}.bin"
274
+ input_dataloader_state_dict_file = input_dir.joinpath(dataloader_state_dict_name)
275
+ if input_dataloader_state_dict_file.exists():
276
+ state_dict = load(input_dataloader_state_dict_file, **load_kwargs)
277
+ dataloader.load_state_dict(state_dict)
278
+ logger.info("All dataloader sampler states loaded successfully")
279
+
280
+ # GradScaler state
281
+ if scaler is not None:
282
+ input_scaler_file = input_dir.joinpath(SCALER_NAME)
283
+ scaler_state = load(input_scaler_file)
284
+ scaler.load_state_dict(scaler_state)
285
+ logger.info("GradScaler state loaded successfully")
286
+
287
+ # Random states
288
+ try:
289
+ states = load(input_dir.joinpath(f"{RNG_STATE_NAME}_{process_index}.pkl"))
290
+ if "step" in states:
291
+ override_attributes["step"] = states["step"]
292
+ random.setstate(states["random_state"])
293
+ np.random.set_state(states["numpy_random_seed"])
294
+ torch.set_rng_state(states["torch_manual_seed"])
295
+ if is_xpu_available():
296
+ torch.xpu.set_rng_state_all(states["torch_xpu_manual_seed"])
297
+ if is_mlu_available():
298
+ torch.mlu.set_rng_state_all(states["torch_mlu_manual_seed"])
299
+ elif is_sdaa_available():
300
+ torch.sdaa.set_rng_state_all(states["torch_sdaa_manual_seed"])
301
+ elif is_musa_available():
302
+ torch.musa.set_rng_state_all(states["torch_musa_manual_seed"])
303
+ else:
304
+ torch.cuda.set_rng_state_all(states["torch_cuda_manual_seed"])
305
+ if is_torch_xla_available():
306
+ xm.set_rng_state(states["xm_seed"])
307
+ logger.info("All random states loaded successfully")
308
+ except Exception:
309
+ logger.info("Could not load random states")
310
+
311
+ return override_attributes
312
+
313
+
314
+ def save_custom_state(obj, path, index: int = 0, save_on_each_node: bool = False):
315
+ """
316
+ Saves the state of `obj` to `{path}/custom_checkpoint_{index}.pkl`
317
+ """
318
+ # Should this be the right way to get a qual_name type value from `obj`?
319
+ save_location = Path(path) / f"custom_checkpoint_{index}.pkl"
320
+ logger.info(f"Saving the state of {get_pretty_name(obj)} to {save_location}")
321
+ save(obj.state_dict(), save_location, save_on_each_node=save_on_each_node)
322
+
323
+
324
+ def load_custom_state(obj, path, index: int = 0):
325
+ """
326
+ Loads the state of `obj` at `{path}/custom_checkpoint_{index}.pkl`. Will always set `weights_only=False` when
327
+ loading the state.
328
+ """
329
+ load_location = f"{path}/custom_checkpoint_{index}.pkl"
330
+ logger.info(f"Loading the state of {get_pretty_name(obj)} from {load_location}")
331
+ obj.load_state_dict(load(load_location, map_location="cpu", weights_only=False))
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/data_loader.py ADDED
@@ -0,0 +1,1451 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2021 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import importlib
16
+ import math
17
+ from contextlib import suppress
18
+ from typing import Callable, Optional, Union
19
+
20
+ import torch
21
+ from packaging import version
22
+ from torch.utils.data import BatchSampler, DataLoader, IterableDataset, RandomSampler
23
+
24
+ from .logging import get_logger
25
+ from .state import DistributedType, GradientState, PartialState, is_torch_xla_available
26
+ from .utils import (
27
+ RNGType,
28
+ broadcast,
29
+ broadcast_object_list,
30
+ compare_versions,
31
+ concatenate,
32
+ find_batch_size,
33
+ get_data_structure,
34
+ initialize_tensors,
35
+ is_datasets_available,
36
+ is_torch_version,
37
+ is_torchdata_stateful_dataloader_available,
38
+ send_to_device,
39
+ slice_tensors,
40
+ synchronize_rng_states,
41
+ )
42
+
43
+
44
+ logger = get_logger(__name__)
45
+
46
+ # kwargs of the DataLoader in min version 2.0
47
+ _PYTORCH_DATALOADER_KWARGS = {
48
+ "batch_size": 1,
49
+ "shuffle": False,
50
+ "sampler": None,
51
+ "batch_sampler": None,
52
+ "num_workers": 0,
53
+ "collate_fn": None,
54
+ "pin_memory": False,
55
+ "drop_last": False,
56
+ "timeout": 0,
57
+ "worker_init_fn": None,
58
+ "multiprocessing_context": None,
59
+ "generator": None,
60
+ "prefetch_factor": 2,
61
+ "persistent_workers": False,
62
+ "pin_memory_device": "",
63
+ }
64
+
65
+ # kwargs added after by version
66
+ _PYTORCH_DATALOADER_ADDITIONAL_KWARGS = {"2.6.0": {"in_order": True}}
67
+
68
+ for v, additional_kwargs in _PYTORCH_DATALOADER_ADDITIONAL_KWARGS.items():
69
+ if is_torch_version(">=", v):
70
+ _PYTORCH_DATALOADER_KWARGS.update(additional_kwargs)
71
+
72
+
73
+ class SeedableRandomSampler(RandomSampler):
74
+ """
75
+ Same as a random sampler, except that in `__iter__` a seed can be used.
76
+
77
+ Needed specifically in distributed cases, when the random generator for each GPU needs to start from the same seed
78
+ and be fully reproducible on multiple iterations.
79
+
80
+ If a custom `generator` is passed, it will rely on its initial seed as well as the current iteration it is on
81
+ (stored in `self.epoch`).
82
+ """
83
+
84
+ def __init__(self, *args, **kwargs):
85
+ data_seed = kwargs.pop("data_seed", None)
86
+ super().__init__(*args, **kwargs)
87
+
88
+ self.initial_seed = data_seed if data_seed is not None else torch.random.initial_seed()
89
+ self.epoch = 0
90
+
91
+ def __iter__(self):
92
+ if self.generator is None:
93
+ self.generator = torch.Generator(
94
+ device=torch.get_default_device() if hasattr(torch, "get_default_device") else "cpu"
95
+ )
96
+ self.generator.manual_seed(self.initial_seed)
97
+
98
+ # Allow `self.epoch` to modify the seed of the generator
99
+ seed = self.epoch + self.initial_seed
100
+ # print("Setting seed at epoch", self.epoch, seed)
101
+ self.generator.manual_seed(seed)
102
+ yield from super().__iter__()
103
+ self.set_epoch(self.epoch + 1)
104
+
105
+ def set_epoch(self, epoch: int):
106
+ "Sets the current iteration of the sampler."
107
+ self.epoch = epoch
108
+
109
+
110
+ class BatchSamplerShard(BatchSampler):
111
+ """
112
+ Wraps a PyTorch `BatchSampler` to generate batches for one of the processes only. Instances of this class will
113
+ always yield a number of batches that is a round multiple of `num_processes` and that all have the same size.
114
+ Depending on the value of the `drop_last` attribute of the batch sampler passed, it will either stop the iteration
115
+ at the first batch that would be too small / not present on all processes or loop with indices from the beginning.
116
+
117
+ Args:
118
+ batch_sampler (`torch.utils.data.sampler.BatchSampler`):
119
+ The batch sampler to split in several shards.
120
+ num_processes (`int`, *optional*, defaults to 1):
121
+ The number of processes running concurrently.
122
+ process_index (`int`, *optional*, defaults to 0):
123
+ The index of the current process.
124
+ split_batches (`bool`, *optional*, defaults to `False`):
125
+ Whether the shards should be created by splitting a batch to give a piece of it on each process, or by
126
+ yielding different full batches on each process.
127
+
128
+ On two processes with a sampler of `[[0, 1, 2, 3], [4, 5, 6, 7]]`, this will result in:
129
+
130
+ - the sampler on process 0 to yield `[0, 1, 2, 3]` and the sampler on process 1 to yield `[4, 5, 6, 7]` if
131
+ this argument is set to `False`.
132
+ - the sampler on process 0 to yield `[0, 1]` then `[4, 5]` and the sampler on process 1 to yield `[2, 3]`
133
+ then `[6, 7]` if this argument is set to `True`.
134
+ even_batches (`bool`, *optional*, defaults to `True`):
135
+ Whether or not to loop back at the beginning of the sampler when the number of samples is not a round
136
+ multiple of (original batch size / number of processes).
137
+
138
+ <Tip warning={true}>
139
+
140
+ `BatchSampler`s with varying batch sizes are not enabled by default. To enable this behaviour, set `even_batches`
141
+ equal to `False`
142
+
143
+ </Tip>"""
144
+
145
+ def __init__(
146
+ self,
147
+ batch_sampler: BatchSampler,
148
+ num_processes: int = 1,
149
+ process_index: int = 0,
150
+ split_batches: bool = False,
151
+ even_batches: bool = True,
152
+ ):
153
+ if split_batches and batch_sampler.batch_size % num_processes != 0:
154
+ raise ValueError(
155
+ f"To use `BatchSamplerShard` in `split_batches` mode, the batch size ({batch_sampler.batch_size}) "
156
+ f"needs to be a round multiple of the number of processes ({num_processes})."
157
+ )
158
+ self.batch_sampler = batch_sampler
159
+ self.num_processes = num_processes
160
+ self.process_index = process_index
161
+ self.split_batches = split_batches
162
+ self.even_batches = even_batches
163
+ self.batch_size = getattr(batch_sampler, "batch_size", None)
164
+ self.drop_last = getattr(batch_sampler, "drop_last", False)
165
+ if self.batch_size is None and self.even_batches:
166
+ raise ValueError(
167
+ "You need to use `even_batches=False` when the batch sampler has no batch size. If you "
168
+ "are not calling this method directly, set `accelerator.even_batches=False` instead."
169
+ )
170
+
171
+ @property
172
+ def total_length(self):
173
+ return len(self.batch_sampler)
174
+
175
+ def __len__(self):
176
+ if self.split_batches:
177
+ # Split batches does not change the length of the batch sampler
178
+ return len(self.batch_sampler)
179
+ if len(self.batch_sampler) % self.num_processes == 0:
180
+ # If the length is a round multiple of the number of processes, it's easy.
181
+ return len(self.batch_sampler) // self.num_processes
182
+ length = len(self.batch_sampler) // self.num_processes
183
+ if self.drop_last:
184
+ # Same if we drop the remainder.
185
+ return length
186
+ elif self.even_batches:
187
+ # When we even batches we always get +1
188
+ return length + 1
189
+ else:
190
+ # Otherwise it depends on the process index.
191
+ return length + 1 if self.process_index < len(self.batch_sampler) % self.num_processes else length
192
+
193
+ def __iter__(self):
194
+ return self._iter_with_split() if self.split_batches else self._iter_with_no_split()
195
+
196
+ def _iter_with_split(self):
197
+ initial_data = []
198
+ batch_length = self.batch_sampler.batch_size // self.num_processes
199
+ for idx, batch in enumerate(self.batch_sampler):
200
+ if idx == 0:
201
+ initial_data = batch
202
+ if len(batch) == self.batch_size:
203
+ # If the batch is full, we yield the part of it this process is responsible of.
204
+ yield batch[batch_length * self.process_index : batch_length * (self.process_index + 1)]
205
+
206
+ # If drop_last is True of the last batch was full, iteration is over, otherwise...
207
+ if not self.drop_last and len(initial_data) > 0 and len(batch) < self.batch_size:
208
+ if not self.even_batches:
209
+ if len(batch) > batch_length * self.process_index:
210
+ yield batch[batch_length * self.process_index : batch_length * (self.process_index + 1)]
211
+ else:
212
+ # For degenerate cases where the dataset has less than num_process * batch_size samples
213
+ while len(initial_data) < self.batch_size:
214
+ initial_data += initial_data
215
+ batch = batch + initial_data
216
+ yield batch[batch_length * self.process_index : batch_length * (self.process_index + 1)]
217
+
218
+ def _iter_with_no_split(self):
219
+ initial_data = []
220
+ batch_to_yield = []
221
+ for idx, batch in enumerate(self.batch_sampler):
222
+ # We gather the initial indices in case we need to circle back at the end.
223
+ if not self.drop_last and idx < self.num_processes:
224
+ initial_data += batch
225
+ # We identify the batch to yield but wait until we ar sure every process gets a full batch before actually
226
+ # yielding it.
227
+ if idx % self.num_processes == self.process_index:
228
+ batch_to_yield = batch
229
+ if idx % self.num_processes == self.num_processes - 1 and (
230
+ self.batch_size is None or len(batch) == self.batch_size
231
+ ):
232
+ yield batch_to_yield
233
+ batch_to_yield = []
234
+
235
+ # If drop_last is True, iteration is over, otherwise...
236
+ if not self.drop_last and len(initial_data) > 0:
237
+ if not self.even_batches:
238
+ if len(batch_to_yield) > 0:
239
+ yield batch_to_yield
240
+ else:
241
+ # ... we yield the complete batch we had saved before if it has the proper length
242
+ if len(batch_to_yield) == self.batch_size:
243
+ yield batch_to_yield
244
+
245
+ # For degenerate cases where the dataset has less than num_process * batch_size samples
246
+ while len(initial_data) < self.num_processes * self.batch_size:
247
+ initial_data += initial_data
248
+
249
+ # If the last batch seen was of the proper size, it has been yielded by its process so we move to the next
250
+ if len(batch) == self.batch_size:
251
+ batch = []
252
+ idx += 1
253
+
254
+ # Make sure we yield a multiple of self.num_processes batches
255
+ cycle_index = 0
256
+ while idx % self.num_processes != 0 or len(batch) > 0:
257
+ end_index = cycle_index + self.batch_size - len(batch)
258
+ batch += initial_data[cycle_index:end_index]
259
+ if idx % self.num_processes == self.process_index:
260
+ yield batch
261
+ cycle_index = end_index
262
+ batch = []
263
+ idx += 1
264
+
265
+
266
+ class IterableDatasetShard(IterableDataset):
267
+ """
268
+ Wraps a PyTorch `IterableDataset` to generate samples for one of the processes only. Instances of this class will
269
+ always yield a number of samples that is a round multiple of the actual batch size (depending of the value of
270
+ `split_batches`, this is either `batch_size` or `batch_size x num_processes`). Depending on the value of the
271
+ `drop_last` attribute of the batch sampler passed, it will either stop the iteration at the first batch that would
272
+ be too small or loop with indices from the beginning.
273
+
274
+ Args:
275
+ dataset (`torch.utils.data.dataset.IterableDataset`):
276
+ The batch sampler to split in several shards.
277
+ batch_size (`int`, *optional*, defaults to 1):
278
+ The size of the batches per shard (if `split_batches=False`) or the size of the batches (if
279
+ `split_batches=True`).
280
+ drop_last (`bool`, *optional*, defaults to `False`):
281
+ Whether or not to drop the last incomplete batch or complete the last batches by using the samples from the
282
+ beginning.
283
+ num_processes (`int`, *optional*, defaults to 1):
284
+ The number of processes running concurrently.
285
+ process_index (`int`, *optional*, defaults to 0):
286
+ The index of the current process.
287
+ split_batches (`bool`, *optional*, defaults to `False`):
288
+ Whether the shards should be created by splitting a batch to give a piece of it on each process, or by
289
+ yielding different full batches on each process.
290
+
291
+ On two processes with an iterable dataset yielding of `[0, 1, 2, 3, 4, 5, 6, 7]`, this will result in:
292
+
293
+ - the shard on process 0 to yield `[0, 1, 2, 3]` and the shard on process 1 to yield `[4, 5, 6, 7]` if this
294
+ argument is set to `False`.
295
+ - the shard on process 0 to yield `[0, 1, 4, 5]` and the sampler on process 1 to yield `[2, 3, 6, 7]` if
296
+ this argument is set to `True`.
297
+ """
298
+
299
+ def __init__(
300
+ self,
301
+ dataset: IterableDataset,
302
+ batch_size: int = 1,
303
+ drop_last: bool = False,
304
+ num_processes: int = 1,
305
+ process_index: int = 0,
306
+ split_batches: bool = False,
307
+ ):
308
+ if split_batches and batch_size > 1 and batch_size % num_processes != 0:
309
+ raise ValueError(
310
+ f"To use `IterableDatasetShard` in `split_batches` mode, the batch size ({batch_size}) "
311
+ f"needs to be a round multiple of the number of processes ({num_processes})."
312
+ )
313
+ self.dataset: IterableDataset = dataset
314
+ self.batch_size = batch_size
315
+ self.drop_last = drop_last
316
+ self.num_processes = num_processes
317
+ self.process_index = process_index
318
+ self.split_batches = split_batches
319
+
320
+ def set_epoch(self, epoch):
321
+ self.epoch = epoch
322
+ if hasattr(self.dataset, "set_epoch"):
323
+ self.dataset.set_epoch(epoch)
324
+
325
+ def __len__(self):
326
+ # We will just raise the downstream error if the underlying dataset is not sized
327
+ if self.drop_last:
328
+ return (len(self.dataset) // (self.batch_size * self.num_processes)) * self.batch_size
329
+ else:
330
+ return math.ceil(len(self.dataset) / (self.batch_size * self.num_processes)) * self.batch_size
331
+
332
+ def __iter__(self):
333
+ if (
334
+ not hasattr(self.dataset, "set_epoch")
335
+ and hasattr(self.dataset, "generator")
336
+ and isinstance(self.dataset.generator, torch.Generator)
337
+ ):
338
+ self.dataset.generator.manual_seed(self.epoch)
339
+ real_batch_size = self.batch_size if self.split_batches else (self.batch_size * self.num_processes)
340
+ process_batch_size = (self.batch_size // self.num_processes) if self.split_batches else self.batch_size
341
+ process_slice = range(self.process_index * process_batch_size, (self.process_index + 1) * process_batch_size)
342
+
343
+ first_batch = None
344
+ current_batch = []
345
+ for element in self.dataset:
346
+ current_batch.append(element)
347
+ # Wait to have a full batch before yielding elements.
348
+ if len(current_batch) == real_batch_size:
349
+ for i in process_slice:
350
+ yield current_batch[i]
351
+ if first_batch is None:
352
+ first_batch = current_batch.copy()
353
+ current_batch = []
354
+
355
+ # Finished if drop_last is True, otherwise complete the last batch with elements from the beginning.
356
+ if not self.drop_last and len(current_batch) > 0:
357
+ if first_batch is None:
358
+ first_batch = current_batch.copy()
359
+ while len(current_batch) < real_batch_size:
360
+ current_batch += first_batch
361
+ for i in process_slice:
362
+ yield current_batch[i]
363
+
364
+
365
+ class DataLoaderStateMixin:
366
+ """
367
+ Mixin class that adds a state to a `DataLoader` to keep track of the status inside the dataloader such as at the
368
+ end of the iteration, the number of items in the dataset in the last batch relative to the batch size, and other
369
+ useful information that might be needed.
370
+
371
+ **Available attributes:**
372
+
373
+ - **end_of_dataloader** (`bool`) -- Whether at the last iteration or batch
374
+ - **remainder** (`int`) -- The number of items that are remaining in the last batch, relative to the total
375
+ batch size
376
+
377
+ <Tip warning={true}>
378
+
379
+ Inheriters of this class should ensure that the class creates a `GradientState()` instance, stored in
380
+ `self.gradient_state`.
381
+
382
+ </Tip>
383
+
384
+ """
385
+
386
+ def __init_subclass__(cls, **kwargs):
387
+ cls.end_of_dataloader = False
388
+ cls.remainder = -1
389
+
390
+ def reset(self):
391
+ self.end_of_dataloader = False
392
+ self.remainder = -1
393
+
394
+ def begin(self):
395
+ "Prepares the gradient state for the current dataloader"
396
+ self.reset()
397
+ with suppress(Exception):
398
+ if not self._drop_last:
399
+ length = getattr(self.dataset, "total_dataset_length", len(self.dataset))
400
+ self.remainder = length % self.total_batch_size
401
+ self.gradient_state._add_dataloader(self)
402
+
403
+ def end(self):
404
+ "Cleans up the gradient state after exiting the dataloader"
405
+ self.gradient_state._remove_dataloader(self)
406
+
407
+
408
+ class DataLoaderAdapter:
409
+ """
410
+ A class which wraps around a PyTorch `DataLoader` (or variants of it) to be used with the `Accelerator`. For
411
+ compatibility reasons, this class inherits from the class it wraps around, so it can be used as a drop-in.
412
+ """
413
+
414
+ def __init__(self, dataset, use_stateful_dataloader=False, batch_sampler=None, **kwargs):
415
+ self.use_stateful_dataloader = use_stateful_dataloader
416
+ if is_torchdata_stateful_dataloader_available():
417
+ from torchdata.stateful_dataloader import StatefulDataLoader
418
+
419
+ if use_stateful_dataloader and not is_torchdata_stateful_dataloader_available():
420
+ raise ImportError(
421
+ "StatefulDataLoader is not available. Please install torchdata version 0.8.0 or higher to use it."
422
+ )
423
+ if use_stateful_dataloader:
424
+ torchdata_version = version.parse(importlib.metadata.version("torchdata"))
425
+ if (
426
+ "in_order" in kwargs
427
+ and compare_versions(torchdata_version, "<", "0.11")
428
+ and is_torch_version(">=", "2.6.0")
429
+ ):
430
+ kwargs.pop("in_order")
431
+ self.base_dataloader = StatefulDataLoader(dataset, batch_sampler=batch_sampler, **kwargs)
432
+ else:
433
+ self.base_dataloader = DataLoader(dataset, batch_sampler=batch_sampler, **kwargs)
434
+
435
+ if hasattr(self.base_dataloader, "state_dict"):
436
+ self.dl_state_dict = self.base_dataloader.state_dict()
437
+
438
+ def __getattr__(self, name):
439
+ # Avoid infinite recursion if we try to access a nonexistent base_dataloader attribute.
440
+ if name == "base_dataloader":
441
+ raise AttributeError()
442
+ # Delegate attribute access to the internal dataloader
443
+ return getattr(self.base_dataloader, name)
444
+
445
+ def state_dict(self):
446
+ return self.dl_state_dict
447
+
448
+ def load_state_dict(self, state_dict):
449
+ self.base_dataloader.load_state_dict(state_dict)
450
+
451
+ @property
452
+ def __class__(self):
453
+ """
454
+ In order to maintain backwards compatibility with other code, we need to ensure `isinstance(obj, DataLoader)`
455
+ returns true. This is because some downstream code assumes that the `DataLoader` is the base class of the
456
+ object.
457
+ """
458
+ return self.base_dataloader.__class__
459
+
460
+ def __len__(self):
461
+ return len(self.base_dataloader)
462
+
463
+ def adjust_state_dict_for_prefetch(self):
464
+ """
465
+ Adjusts the state dict for prefetching. Natively, this will adjust all of the iters yielded keys in
466
+ `self.dl_state_dict` by a factor of `num_processes - 1`, however if a custom correction is needed, this can be
467
+ overridden.
468
+
469
+ This should modify `self.dl_state_dict` directly
470
+ """
471
+ # The state dict will be off by a factor of `n-1` batch too many during DDP,
472
+ # so we need to adjust it here
473
+ if PartialState().distributed_type != DistributedType.NO:
474
+ factor = PartialState().num_processes - 1
475
+ if self.dl_state_dict["_sampler_iter_yielded"] > 0:
476
+ self.dl_state_dict["_sampler_iter_yielded"] -= factor
477
+ if self.dl_state_dict["_num_yielded"] > 0:
478
+ self.dl_state_dict["_num_yielded"] -= factor
479
+ if self.dl_state_dict["_index_sampler_state"] is not None:
480
+ if (
481
+ "samples_yielded" in self.dl_state_dict["_index_sampler_state"]
482
+ and self.dl_state_dict["_index_sampler_state"]["samples_yielded"] > 0
483
+ ):
484
+ self.dl_state_dict["_index_sampler_state"]["samples_yielded"] -= self.batch_size * factor
485
+
486
+ def _update_state_dict(self):
487
+ # The state_dict of the underlying base_dataloader may be ahead of what is currently being yielded.
488
+ # E.g. the implementation of DataLoaderShard involves having an underlying iterator 1 element ahead of
489
+ # what it wants to yield.
490
+ #
491
+ # _update_state_dict is called to snapshot the state_dict that would properly recover the DataLoaderAdapter.
492
+ if hasattr(self.base_dataloader, "state_dict"):
493
+ self.dl_state_dict = self.base_dataloader.state_dict()
494
+ # Potentially modify the state_dict to adjust for prefetching
495
+ self.adjust_state_dict_for_prefetch()
496
+ # Then tag if we are at the end of the dataloader
497
+ self.dl_state_dict["_iterator_finished"] = self.end_of_dataloader
498
+
499
+
500
+ class DataLoaderShard(DataLoaderAdapter, DataLoaderStateMixin):
501
+ """
502
+ Subclass of `DataLoaderAdapter` that will deal with device placement and current distributed setup.
503
+
504
+ Args:
505
+ dataset (`torch.utils.data.dataset.Dataset`):
506
+ The dataset to use to build this dataloader.
507
+ device (`torch.device`, *optional*):
508
+ If passed, the device to put all batches on.
509
+ rng_types (list of `str` or [`~utils.RNGType`]):
510
+ The list of random number generators to synchronize at the beginning of each iteration. Should be one or
511
+ several of:
512
+
513
+ - `"torch"`: the base torch random number generator
514
+ - `"cuda"`: the CUDA random number generator (GPU only)
515
+ - `"xla"`: the XLA random number generator (TPU only)
516
+ - `"generator"`: an optional `torch.Generator`
517
+ synchronized_generator (`torch.Generator`, *optional*):
518
+ A random number generator to keep synchronized across processes.
519
+ skip_batches (`int`, *optional*, defaults to 0):
520
+ The number of batches to skip at the beginning.
521
+ use_stateful_dataloader (`bool`, *optional*, defaults to `False`):
522
+ Whether to have this class adapt `StatefulDataLoader` from `torchdata` instead of the regular `DataLoader`.
523
+ **kwargs (additional keyword arguments, *optional*):
524
+ All other keyword arguments to pass to the regular `DataLoader` initialization.
525
+
526
+ **Available attributes:**
527
+
528
+ - **total_batch_size** (`int`) -- Total batch size of the dataloader across all processes.
529
+ Equal to the original batch size when `split_batches=True`; otherwise the original batch size * the total
530
+ number of processes
531
+
532
+ - **total_dataset_length** (`int`) -- Total length of the inner dataset across all processes.
533
+ """
534
+
535
+ def __init__(
536
+ self,
537
+ dataset,
538
+ device=None,
539
+ rng_types=None,
540
+ synchronized_generator=None,
541
+ skip_batches=0,
542
+ use_stateful_dataloader=False,
543
+ _drop_last: bool = False,
544
+ _non_blocking: bool = False,
545
+ torch_device_mesh=None,
546
+ **kwargs,
547
+ ):
548
+ super().__init__(dataset, use_stateful_dataloader=use_stateful_dataloader, **kwargs)
549
+ self.device = device
550
+ self.rng_types = rng_types
551
+ self.synchronized_generator = synchronized_generator
552
+ self.skip_batches = skip_batches
553
+ self.gradient_state = GradientState()
554
+ self._drop_last = _drop_last
555
+ self._non_blocking = _non_blocking
556
+ self.iteration = 0
557
+
558
+ def __iter__(self):
559
+ if self.rng_types is not None:
560
+ synchronize_rng_states(self.rng_types, self.synchronized_generator)
561
+ self.begin()
562
+
563
+ self.set_epoch(self.iteration)
564
+ dataloader_iter = self.base_dataloader.__iter__()
565
+ # We iterate one batch ahead to check when we are at the end
566
+ try:
567
+ current_batch = next(dataloader_iter)
568
+ except StopIteration:
569
+ self.end()
570
+ return
571
+
572
+ batch_index = 0
573
+ while True:
574
+ try:
575
+ # But we still move it to the device so it is done before `StopIteration` is reached
576
+ if self.device is not None:
577
+ current_batch = send_to_device(current_batch, self.device, non_blocking=self._non_blocking)
578
+ self._update_state_dict()
579
+ next_batch = next(dataloader_iter)
580
+ if batch_index >= self.skip_batches:
581
+ yield current_batch
582
+ batch_index += 1
583
+ current_batch = next_batch
584
+ except StopIteration:
585
+ self.end_of_dataloader = True
586
+ self._update_state_dict()
587
+ if batch_index >= self.skip_batches:
588
+ yield current_batch
589
+ break
590
+
591
+ self.iteration += 1
592
+ self.end()
593
+
594
+ def __reduce__(self):
595
+ """
596
+ Define the `__reduce__` method to ensure a `DataLoaderShard` can be pickled and unpickled. This needs to be
597
+ explicitly defined since default pickling behavior is broken by `DataLoaderAdapter` messing with its
598
+ `__class__` member.
599
+ """
600
+ args = super().__reduce__()
601
+ return (DataLoaderShard, *args[1:])
602
+
603
+ def set_epoch(self, epoch: int):
604
+ # In case it is manually passed in, the user can set it to what they like
605
+ if self.iteration != epoch:
606
+ self.iteration = epoch
607
+ if hasattr(self.batch_sampler, "set_epoch"):
608
+ self.batch_sampler.set_epoch(epoch)
609
+ if hasattr(self.batch_sampler, "sampler") and hasattr(self.batch_sampler.sampler, "set_epoch"):
610
+ self.batch_sampler.sampler.set_epoch(epoch)
611
+ if (
612
+ hasattr(self.batch_sampler, "batch_sampler")
613
+ and hasattr(self.batch_sampler.batch_sampler, "sampler")
614
+ and hasattr(self.batch_sampler.batch_sampler.sampler, "set_epoch")
615
+ ):
616
+ self.batch_sampler.batch_sampler.sampler.set_epoch(epoch)
617
+ # We support if a custom `Dataset` implementation has `set_epoch`
618
+ # or in general HF datasets `Datasets`
619
+ elif hasattr(self.dataset, "set_epoch"):
620
+ self.dataset.set_epoch(epoch)
621
+
622
+ @property
623
+ def total_batch_size(self):
624
+ batch_sampler = self.sampler if isinstance(self.sampler, BatchSampler) else self.batch_sampler
625
+ return (
626
+ batch_sampler.batch_size
627
+ if getattr(batch_sampler, "split_batches", False)
628
+ else (batch_sampler.batch_size * getattr(batch_sampler, "num_processes", 1))
629
+ )
630
+
631
+ @property
632
+ def total_dataset_length(self):
633
+ if hasattr(self.dataset, "total_length"):
634
+ return self.dataset.total_length
635
+ else:
636
+ return len(self.dataset)
637
+
638
+ def get_sampler(self):
639
+ return get_sampler(self)
640
+
641
+ def set_sampler(self, sampler):
642
+ sampler_is_batch_sampler = isinstance(self.sampler, BatchSampler)
643
+ if sampler_is_batch_sampler:
644
+ self.sampler.sampler = sampler
645
+ else:
646
+ self.batch_sampler.sampler = sampler
647
+ if hasattr(self.batch_sampler, "batch_sampler"):
648
+ self.batch_sampler.batch_sampler.sampler = sampler
649
+
650
+
651
+ if is_torch_xla_available():
652
+ import torch_xla.distributed.parallel_loader as xpl
653
+
654
+ class MpDeviceLoaderWrapper(xpl.MpDeviceLoader):
655
+ """
656
+ Wrapper for the xpl.MpDeviceLoader class that knows the total batch size.
657
+
658
+ XLA preloading threads will all call DataLoaderShard's __iter__(). Remove rng_types from DataLoaderShard to
659
+ prevent it from using the XLA device in the preloading threads, and synchronize the RNG once from the main
660
+ thread only.
661
+
662
+ **Available attributes:**
663
+
664
+ - **total_batch_size** (`int`) -- Total batch size of the dataloader across all processes.
665
+ Equal to the original batch size when `split_batches=True`; otherwise the original batch size * the total
666
+ number of processes
667
+
668
+ - **total_dataset_length** (`int`) -- Total length of the inner dataset across all processes.
669
+ """
670
+
671
+ def __init__(self, dataloader: DataLoaderShard, device: torch.device):
672
+ super().__init__(dataloader, device)
673
+ self._rng_types = self._loader.rng_types
674
+ self._loader.rng_types = None
675
+ self.device = device
676
+
677
+ def __iter__(self):
678
+ if self._rng_types is not None:
679
+ synchronize_rng_states(self._rng_types, self._loader.synchronized_generator)
680
+
681
+ return super().__iter__()
682
+
683
+ def set_epoch(self, epoch: int):
684
+ if hasattr(self.dataloader, "set_epoch"):
685
+ self.dataloader.set_epoch(epoch)
686
+
687
+ @property
688
+ def total_batch_size(self):
689
+ return self._loader.total_batch_size
690
+
691
+ @property
692
+ def total_dataset_length(self):
693
+ return self._loader.total_dataset_length
694
+
695
+ @property
696
+ def batch_sampler(self):
697
+ return self._loader.batch_sampler
698
+
699
+ @property
700
+ def dataloader(self):
701
+ return self._loader
702
+
703
+
704
+ class DataLoaderDispatcher(DataLoaderAdapter, DataLoaderStateMixin):
705
+ """
706
+ Subclass of `DataLoaderAdapter` that will iterate and preprocess on process 0 only, then dispatch on each process
707
+ their part of the batch.
708
+
709
+ Args:
710
+ split_batches (`bool`, *optional*, defaults to `False`):
711
+ Whether the resulting `DataLoader` should split the batches of the original data loader across devices or
712
+ yield full batches (in which case it will yield batches starting at the `process_index`-th and advancing of
713
+ `num_processes` batches at each iteration). Another way to see this is that the observed batch size will be
714
+ the same as the initial `dataloader` if this option is set to `True`, the batch size of the initial
715
+ `dataloader` multiplied by `num_processes` otherwise. Setting this option to `True` requires that the batch
716
+ size of the `dataloader` is a round multiple of `batch_size`.
717
+ skip_batches (`int`, *optional*, defaults to 0):
718
+ The number of batches to skip at the beginning of an iteration.
719
+ use_stateful_dataloader (`bool`, *optional*, defaults to `False`):
720
+ Whether to have this class adapt `StatefulDataLoader` from `torchdata` instead of the regular `DataLoader`.
721
+
722
+ **Available attributes:**
723
+
724
+ - **total_batch_size** (`int`) -- Total batch size of the dataloader across all processes.
725
+ Equal to the original batch size when `split_batches=True`; otherwise the original batch size * the total
726
+ number of processes
727
+
728
+ - **total_dataset_length** (`int`) -- Total length of the inner dataset across all processes.
729
+ """
730
+
731
+ def __init__(
732
+ self,
733
+ dataset,
734
+ split_batches: bool = False,
735
+ skip_batches=0,
736
+ use_stateful_dataloader=False,
737
+ _drop_last: bool = False,
738
+ _non_blocking: bool = False,
739
+ slice_fn=None,
740
+ torch_device_mesh=None,
741
+ **kwargs,
742
+ ):
743
+ shuffle = False
744
+ from torch.utils.data.datapipes.iter.combinatorics import ShufflerIterDataPipe
745
+
746
+ # We need to save the shuffling state of the DataPipe
747
+ if isinstance(dataset, ShufflerIterDataPipe):
748
+ shuffle = dataset._shuffle_enabled
749
+ super().__init__(dataset, use_stateful_dataloader=use_stateful_dataloader, **kwargs)
750
+ self.split_batches = split_batches
751
+ if shuffle:
752
+ torch.utils.data.graph_settings.apply_shuffle_settings(dataset, shuffle=shuffle)
753
+
754
+ self.gradient_state = GradientState()
755
+ self.state = PartialState()
756
+ self._drop_last = _drop_last
757
+ self._non_blocking = _non_blocking
758
+ self.skip_batches = skip_batches
759
+ self.torch_device_mesh = torch_device_mesh
760
+
761
+ self.slice_fn = slice_tensors if slice_fn is None else slice_fn
762
+ self.iteration = 0
763
+
764
+ # if a device mesh is provided extract each dimension (dp, fsdp, tp)
765
+ # device mesh may hold any number of dimensions, however,
766
+ # below code is for targeted support for dp, fsdp and tp
767
+
768
+ # device mesh will be used only if there is tp involved
769
+ # or any multi-dimensional parallelism involving tp
770
+ # (dp, tp) (fsdp, tp) (dp, fsdp, tp)
771
+ # otherwise the default behaviour not using device mesh should be sufficient
772
+ # since multi dimensional parallelism devoid of tp would anyway need
773
+ # different batches for each process irrespective of dp or fsdp
774
+ self.submesh_tp = None
775
+ self.submesh_dp = None
776
+ self.submesh_fsdp = None
777
+ if self.torch_device_mesh and "tp" in self.torch_device_mesh.mesh_dim_names:
778
+ self.submesh_tp = self.torch_device_mesh["tp"]
779
+ if "dp" in self.torch_device_mesh.mesh_dim_names:
780
+ self.submesh_dp = self.torch_device_mesh["dp"]
781
+ if "fsdp" in self.torch_device_mesh.mesh_dim_names:
782
+ self.submesh_fsdp = self.torch_device_mesh["fsdp"]
783
+ if self.submesh_tp and (self.submesh_dp or self.submesh_fsdp):
784
+ raise ValueError("TP + (DP/FSDP) is not yet supported in dispatch mode")
785
+
786
+ def _fetch_batches(self, iterator):
787
+ batches, batch = None, None
788
+ # On process 0, we gather the batch to dispatch.
789
+ if self.state.process_index == 0:
790
+ # Procedure to support TP only is simpler
791
+ # since we want to dispatch the same batch of samples across all ranks
792
+ # this removes complexity of handling multiple tp rank groups when TP + DP
793
+ # combination is involved.
794
+
795
+ try:
796
+ # for TP case avoid using split_batches
797
+ # since it would mean that the dataloader should be spilling out
798
+ # duplicates of batches.
799
+ if self.split_batches:
800
+ # One batch of the main iterator is dispatched and split.
801
+ if self.submesh_tp:
802
+ logger.warning(
803
+ "Use of split_batches for TP would need the dataloader to produce duplicate batches,"
804
+ "otherwise, use dispatch_batches=True instead."
805
+ )
806
+ self._update_state_dict()
807
+ batch = next(iterator)
808
+ else:
809
+ # num_processes batches of the main iterator are concatenated then dispatched and split.
810
+ # We add the batches one by one so we have the remainder available when drop_last=False.
811
+ batches = []
812
+ if self.submesh_tp:
813
+ # when tp, extract single batch and then replicate
814
+ self._update_state_dict()
815
+ batch = next(iterator)
816
+ batches = [batch] * self.state.num_processes
817
+ else:
818
+ for _ in range(self.state.num_processes):
819
+ self._update_state_dict()
820
+ batches.append(next(iterator))
821
+ try:
822
+ batch = concatenate(batches, dim=0)
823
+ except RuntimeError as e:
824
+ raise RuntimeError(
825
+ "You can't use batches of different size with `dispatch_batches=True` or when using an `IterableDataset`."
826
+ "either pass `dispatch_batches=False` and have each process fetch its own batch "
827
+ " or pass `split_batches=True`. By doing so, the main process will fetch a full batch and "
828
+ "slice it into `num_processes` batches for each process."
829
+ ) from e
830
+ # In both cases, we need to get the structure of the batch that we will broadcast on other
831
+ # processes to initialize the tensors with the right shape.
832
+ # data_structure, stop_iteration
833
+ batch_info = [get_data_structure(batch), False]
834
+ except StopIteration:
835
+ batch_info = [None, True]
836
+ else:
837
+ batch_info = [None, self._stop_iteration]
838
+ # This is inplace, so after this instruction, every process has the same `batch_info` as process 0.
839
+ broadcast_object_list(batch_info)
840
+ self._stop_iteration = batch_info[1]
841
+ if self._stop_iteration:
842
+ # If drop_last is False and split_batches is False, we may have a remainder to take care of.
843
+ if not self.split_batches and not self._drop_last:
844
+ if self.state.process_index == 0 and len(batches) > 0:
845
+ batch = concatenate(batches, dim=0)
846
+ batch_info = [get_data_structure(batch), False]
847
+ else:
848
+ batch_info = [None, True]
849
+ broadcast_object_list(batch_info)
850
+ return batch, batch_info
851
+
852
+ def __iter__(self):
853
+ self.begin()
854
+ self.set_epoch(self.iteration)
855
+ main_iterator = None
856
+ if is_torch_version(">=", "2.0.1"):
857
+ # NOTE PyTorch DataLoader adds forward compatibilities for DataPipes, which broadcasts
858
+ # shared seed to all dist processes. Thus, we need to create iterator for all dist processes.
859
+ # But, we only iterate through the DataLoader on process 0.
860
+ main_iterator = self.base_dataloader.__iter__()
861
+ elif self.state.process_index == 0:
862
+ main_iterator = self.base_dataloader.__iter__()
863
+ stop_iteration = False
864
+ self._stop_iteration = False
865
+ first_batch = None
866
+ next_batch, next_batch_info = self._fetch_batches(main_iterator)
867
+ batch_index = 0
868
+ while not stop_iteration:
869
+ batch, batch_info = next_batch, next_batch_info
870
+
871
+ if self.state.process_index != 0:
872
+ # Initialize tensors on other processes than process 0.
873
+ batch = initialize_tensors(batch_info[0])
874
+ batch = send_to_device(batch, self.state.device, non_blocking=self._non_blocking)
875
+ # Broadcast the batch before splitting it.
876
+ batch = broadcast(batch, from_process=0)
877
+
878
+ if not self._drop_last and first_batch is None:
879
+ # We keep at least num processes elements of the first batch to be able to complete the last batch
880
+ first_batch = self.slice_fn(
881
+ batch,
882
+ slice(0, self.state.num_processes),
883
+ process_index=self.state.process_index,
884
+ num_processes=self.state.num_processes,
885
+ )
886
+
887
+ if batch is None:
888
+ raise ValueError(
889
+ f"Batch does not contain any data (`{batch}`). At the end of all iterable data available before expected stop iteration."
890
+ )
891
+
892
+ observed_batch_size = find_batch_size(batch)
893
+ batch_size = observed_batch_size // self.state.num_processes
894
+
895
+ stop_iteration = self._stop_iteration
896
+ if not stop_iteration:
897
+ # We may still be at the end of the dataloader without knowing it yet: if there is nothing left in
898
+ # the dataloader since the number of batches is a round multiple of the number of processes.
899
+ next_batch, next_batch_info = self._fetch_batches(main_iterator)
900
+ # next_batch_info[0] is None when there are no more batches, otherwise we still need to process them.
901
+ if self._stop_iteration and next_batch_info[0] is None:
902
+ stop_iteration = True
903
+
904
+ if not self._drop_last and stop_iteration and observed_batch_size % self.state.num_processes != 0:
905
+ # If the last batch is not complete, let's add the first batch to it.
906
+ batch = concatenate([batch, first_batch], dim=0)
907
+ # Batch size computation above is wrong, it's off by 1 so we fix it.
908
+ batch_size += 1
909
+
910
+ data_slice = slice(self.state.process_index * batch_size, (self.state.process_index + 1) * batch_size)
911
+ batch = self.slice_fn(
912
+ batch,
913
+ data_slice,
914
+ process_index=self.state.process_index,
915
+ num_processes=self.state.num_processes,
916
+ )
917
+
918
+ if stop_iteration:
919
+ self.end_of_dataloader = True
920
+ self._update_state_dict()
921
+ self.remainder = observed_batch_size
922
+ if batch_index >= self.skip_batches:
923
+ yield batch
924
+ batch_index += 1
925
+ self.iteration += 1
926
+ self.end()
927
+
928
+ def set_epoch(self, epoch: int):
929
+ # In case it is manually passed in, the user can set it to what they like
930
+ if self.iteration != epoch:
931
+ self.iteration = epoch
932
+ if hasattr(self.batch_sampler, "sampler") and hasattr(self.batch_sampler.sampler, "set_epoch"):
933
+ self.batch_sampler.sampler.set_epoch(epoch)
934
+ elif hasattr(self.dataset, "set_epoch"):
935
+ self.dataset.set_epoch(epoch)
936
+
937
+ def __len__(self):
938
+ whole_length = len(self.base_dataloader)
939
+ if self.split_batches:
940
+ return whole_length
941
+ elif self._drop_last:
942
+ return whole_length // self.state.num_processes
943
+ else:
944
+ return math.ceil(whole_length / self.state.num_processes)
945
+
946
+ def __reduce__(self):
947
+ """
948
+ Define the `__reduce__` method to ensure a `DataLoaderDispatcher` can be pickled and unpickled. This needs to
949
+ be explicitly defined since default pickling behavior is broken by `DataLoaderAdapter` messing with its
950
+ `__class__` member.
951
+ """
952
+ args = super().__reduce__()
953
+ return (DataLoaderDispatcher, *args[1:])
954
+
955
+ @property
956
+ def total_batch_size(self):
957
+ return (
958
+ self.dataset.batch_size if self.split_batches else (self.dataset.batch_size * self.dataset.num_processes)
959
+ )
960
+
961
+ @property
962
+ def total_dataset_length(self):
963
+ return len(self.dataset)
964
+
965
+ def get_sampler(self):
966
+ return get_sampler(self)
967
+
968
+ def set_sampler(self, sampler):
969
+ sampler_is_batch_sampler = isinstance(self.sampler, BatchSampler)
970
+ if sampler_is_batch_sampler:
971
+ self.sampler.sampler = sampler
972
+ else:
973
+ self.batch_sampler.sampler = sampler
974
+ if hasattr(self.batch_sampler, "batch_sampler"):
975
+ self.batch_sampler.batch_sampler.sampler = sampler
976
+
977
+
978
+ def get_sampler(dataloader):
979
+ """
980
+ Get the sampler associated to the dataloader
981
+
982
+ Args:
983
+ dataloader (`torch.utils.data.dataloader.DataLoader`):
984
+ The data loader to split across several devices.
985
+ Returns:
986
+ `torch.utils.data.Sampler`: The sampler associated to the dataloader
987
+ """
988
+ sampler_is_batch_sampler = isinstance(dataloader.sampler, BatchSampler)
989
+ if sampler_is_batch_sampler:
990
+ sampler = getattr(dataloader.sampler, "sampler", None)
991
+ else:
992
+ sampler = getattr(dataloader.batch_sampler, "sampler", None)
993
+ return sampler
994
+
995
+
996
+ def prepare_data_loader(
997
+ dataloader: DataLoader,
998
+ device: Optional[torch.device] = None,
999
+ num_processes: Optional[int] = None,
1000
+ process_index: Optional[int] = None,
1001
+ split_batches: bool = False,
1002
+ put_on_device: bool = False,
1003
+ rng_types: Optional[list[Union[str, RNGType]]] = None,
1004
+ dispatch_batches: Optional[bool] = None,
1005
+ even_batches: bool = True,
1006
+ slice_fn_for_dispatch: Optional[Callable] = None,
1007
+ use_seedable_sampler: bool = False,
1008
+ data_seed: Optional[int] = None,
1009
+ non_blocking: bool = False,
1010
+ use_stateful_dataloader: bool = False,
1011
+ torch_device_mesh=None,
1012
+ ) -> DataLoader:
1013
+ """
1014
+ Wraps a PyTorch `DataLoader` to generate batches for one of the processes only.
1015
+
1016
+ Depending on the value of the `drop_last` attribute of the `dataloader` passed, it will either stop the iteration
1017
+ at the first batch that would be too small / not present on all processes or loop with indices from the beginning.
1018
+
1019
+ Args:
1020
+ dataloader (`torch.utils.data.dataloader.DataLoader`):
1021
+ The data loader to split across several devices.
1022
+ device (`torch.device`):
1023
+ The target device for the returned `DataLoader`.
1024
+ num_processes (`int`, *optional*):
1025
+ The number of processes running concurrently. Will default to the value given by [`~state.PartialState`].
1026
+ process_index (`int`, *optional*):
1027
+ The index of the current process. Will default to the value given by [`~state.PartialState`].
1028
+ split_batches (`bool`, *optional*, defaults to `False`):
1029
+ Whether the resulting `DataLoader` should split the batches of the original data loader across devices or
1030
+ yield full batches (in which case it will yield batches starting at the `process_index`-th and advancing of
1031
+ `num_processes` batches at each iteration).
1032
+
1033
+ Another way to see this is that the observed batch size will be the same as the initial `dataloader` if
1034
+ this option is set to `True`, the batch size of the initial `dataloader` multiplied by `num_processes`
1035
+ otherwise.
1036
+
1037
+ Setting this option to `True` requires that the batch size of the `dataloader` is a round multiple of
1038
+ `batch_size`.
1039
+ put_on_device (`bool`, *optional*, defaults to `False`):
1040
+ Whether or not to put the batches on `device` (only works if the batches are nested list, tuples or
1041
+ dictionaries of tensors).
1042
+ rng_types (list of `str` or [`~utils.RNGType`]):
1043
+ The list of random number generators to synchronize at the beginning of each iteration. Should be one or
1044
+ several of:
1045
+
1046
+ - `"torch"`: the base torch random number generator
1047
+ - `"cuda"`: the CUDA random number generator (GPU only)
1048
+ - `"xla"`: the XLA random number generator (TPU only)
1049
+ - `"generator"`: the `torch.Generator` of the sampler (or batch sampler if there is no sampler in your
1050
+ dataloader) or of the iterable dataset (if it exists) if the underlying dataset is of that type.
1051
+
1052
+ dispatch_batches (`bool`, *optional*):
1053
+ If set to `True`, the dataloader prepared is only iterated through on the main process and then the batches
1054
+ are split and broadcast to each process. Will default to `True` when the underlying dataset is an
1055
+ `IterableDataset`, `False` otherwise.
1056
+ even_batches (`bool`, *optional*, defaults to `True`):
1057
+ If set to `True`, in cases where the total batch size across all processes does not exactly divide the
1058
+ dataset, samples at the start of the dataset will be duplicated so the batch can be divided equally among
1059
+ all workers.
1060
+ slice_fn_for_dispatch (`Callable`, *optional*`):
1061
+ If passed, this function will be used to slice tensors across `num_processes`. Will default to
1062
+ [`~utils.slice_tensors`]. This argument is used only when `dispatch_batches` is set to `True` and will be
1063
+ ignored otherwise.
1064
+ use_seedable_sampler (`bool`, *optional*, defaults to `False`):
1065
+ Whether to use the [`~data_loader.SeedableRandomSampler`] instead of a `RandomSampler` for better
1066
+ reproducibility. Comes at a cost of potentially different performances due to different shuffling
1067
+ algorithms but ensures results will be the *exact* same. Should be paired with `set_seed()` at every
1068
+ `self.set_epoch`
1069
+ data_seed (`int`, *optional*, defaults to `None`):
1070
+ The seed to use for the underlying generator when using `use_seedable_sampler`. If `None`, the generator
1071
+ will use the current default seed from torch.
1072
+ non_blocking (`bool`, *optional*, defaults to `False`):
1073
+ If set to `True`, dataloader will utilize non-blocking host-to-device transfers. If the dataloader has
1074
+ `pin_memory` set to `True`, this will help to increase overlap between data transfer and computations.
1075
+ use_stateful_dataloader (`bool`, *optional*, defaults to `False`):
1076
+ "If set to true, the dataloader prepared by the Accelerator will be backed by "
1077
+ "[torchdata.StatefulDataLoader](https://github.com/pytorch/data/tree/main/torchdata/stateful_dataloader).
1078
+ This requires `torchdata` version 0.8.0 or higher that supports StatefulDataLoader to be installed."
1079
+ torch_device_mesh (`torch.distributed.DeviceMesh`, *optional*, defaults to `None`):
1080
+ PyTorch device mesh.
1081
+
1082
+
1083
+ Returns:
1084
+ `torch.utils.data.dataloader.DataLoader`: A new data loader that will yield the portion of the batches
1085
+
1086
+ <Tip warning={true}>
1087
+
1088
+ `BatchSampler`s with varying batch sizes are not enabled by default. To enable this behaviour, set `even_batches`
1089
+ equal to `False`
1090
+
1091
+ </Tip>
1092
+ """
1093
+ if dispatch_batches is None:
1094
+ if not put_on_device:
1095
+ dispatch_batches = False
1096
+ else:
1097
+ dispatch_batches = isinstance(dataloader.dataset, IterableDataset)
1098
+
1099
+ if dispatch_batches and not put_on_device:
1100
+ raise ValueError("Using `dispatch_batches=True` requires `put_on_device=True`.")
1101
+ # Grab defaults from PartialState
1102
+ state = PartialState()
1103
+ if num_processes is None:
1104
+ num_processes = state.num_processes
1105
+
1106
+ if process_index is None:
1107
+ process_index = state.process_index
1108
+
1109
+ if torch_device_mesh:
1110
+ if state.distributed_type == DistributedType.DEEPSPEED:
1111
+ # In DeepSpeed, the optimizer sharing level in DP is determined by the config file.
1112
+ # Only considers "dp" and "tp".
1113
+ # Given a device mesh (dp, tp) = (2, 3):
1114
+ # - From the data parallel perspective, ranks should be structured as: 0 0 0 1 1 1
1115
+ # - Processes with the same DP rank will receive the same batch.
1116
+ submesh_tp_size = 1
1117
+ if "tp" in torch_device_mesh.mesh_dim_names:
1118
+ submesh_tp_size = torch_device_mesh["tp"].size()
1119
+ process_index = process_index // submesh_tp_size
1120
+ num_processes = num_processes // submesh_tp_size
1121
+ else:
1122
+ # when device mesh is used, specifically with TP
1123
+ # then there is need to update process_index and num_processes
1124
+ # to bring in the effect of generating same batch across TP ranks
1125
+ # and different batch across FSDP and DP ranks.
1126
+ # Example:
1127
+ # if device mesh is (dp,fsdp,tp) = (2, 2, 3)
1128
+ # ranks would range from 0...11
1129
+ # from data angle ranks should look like 0 0 0 1 1 1 2 2 2 3 3 3
1130
+ # processes with same ranks/ids would receive the same batch
1131
+ # for CP the same as TP applies
1132
+ submesh_fsdp_size = 1
1133
+ submesh_dp_size = 1
1134
+ submesh_tp_size = 1
1135
+ submesh_cp_size = 1
1136
+ if "tp" in torch_device_mesh.mesh_dim_names:
1137
+ submesh_tp_size = torch_device_mesh["tp"].size()
1138
+ if "cp" in torch_device_mesh.mesh_dim_names:
1139
+ submesh_cp_size = torch_device_mesh["cp"].size()
1140
+ if "dp_replicate" in torch_device_mesh.mesh_dim_names:
1141
+ submesh_dp_size = torch_device_mesh["dp_replicate"].size()
1142
+ if "dp_shard" in torch_device_mesh.mesh_dim_names:
1143
+ submesh_fsdp_size = torch_device_mesh["dp_shard"].size()
1144
+ process_index = process_index // (submesh_tp_size * submesh_cp_size)
1145
+ num_processes = submesh_fsdp_size * submesh_dp_size
1146
+
1147
+ # Sanity check
1148
+ if split_batches:
1149
+ if dataloader.batch_size is not None:
1150
+ batch_size_for_check = dataloader.batch_size
1151
+ else:
1152
+ # For custom batch_sampler
1153
+ if hasattr(dataloader.batch_sampler, "batch_size"):
1154
+ batch_size_for_check = dataloader.batch_sampler.batch_size
1155
+ else:
1156
+ raise ValueError(
1157
+ "In order to use `split_batches==True` you must have a `batch_size` attribute either in the passed "
1158
+ "`dataloader` or `dataloader.batch_sampler` objects, and it has to return a natural number. "
1159
+ "Your `dataloader.batch_size` is None and `dataloader.batch_sampler` "
1160
+ f"(`{type(dataloader.batch_sampler)}`) does not have the `batch_size` attribute set."
1161
+ )
1162
+
1163
+ if batch_size_for_check > 1 and batch_size_for_check % num_processes != 0:
1164
+ raise ValueError(
1165
+ f"To use a `DataLoader` in `split_batches` mode, the batch size ({dataloader.batch_size}) "
1166
+ f"needs to be a round multiple of the number of processes ({num_processes})."
1167
+ )
1168
+
1169
+ new_dataset = dataloader.dataset
1170
+ # Iterable dataset doesn't like batch_sampler, but data_loader creates a default one for it
1171
+ new_batch_sampler = dataloader.batch_sampler if not isinstance(new_dataset, IterableDataset) else None
1172
+ sampler_is_batch_sampler = isinstance(dataloader.sampler, BatchSampler)
1173
+ synchronized_generator = None
1174
+
1175
+ sampler = get_sampler(dataloader)
1176
+ if isinstance(sampler, RandomSampler) and use_seedable_sampler:
1177
+ # When iterating through the dataloader during distributed processes
1178
+ # we want to ensure that on each process we are iterating through the same
1179
+ # samples in the same order if a seed is set. This requires a tweak
1180
+ # to the `torch.utils.data.RandomSampler` class (if used).
1181
+ sampler = SeedableRandomSampler(
1182
+ data_source=sampler.data_source,
1183
+ replacement=sampler.replacement,
1184
+ num_samples=sampler._num_samples,
1185
+ generator=getattr(
1186
+ sampler,
1187
+ "generator",
1188
+ torch.Generator(device=torch.get_default_device() if hasattr(torch, "get_default_device") else "cpu"),
1189
+ ),
1190
+ data_seed=data_seed,
1191
+ )
1192
+
1193
+ if isinstance(dataloader.sampler, RandomSampler) and state.distributed_type == DistributedType.XLA:
1194
+ # isinstance(dataloader.sampler, RandomSampler) indicates the original dataloader has `shuffle` enabled.
1195
+ generator = torch.Generator(
1196
+ device=torch.get_default_device() if hasattr(torch, "get_default_device") else "cpu"
1197
+ )
1198
+ seed = int(torch.empty((), dtype=torch.int64).random_().item())
1199
+ generator.manual_seed(seed)
1200
+ dataloader.generator = generator
1201
+ dataloader.sampler.generator = generator
1202
+ # No change if no multiprocess
1203
+ if (num_processes != 1 or state.distributed_type == DistributedType.MEGATRON_LM) and not dispatch_batches:
1204
+ if is_datasets_available():
1205
+ from datasets import IterableDataset as DatasetsIterableDataset
1206
+ if (
1207
+ is_datasets_available()
1208
+ and isinstance(new_dataset, DatasetsIterableDataset)
1209
+ and not split_batches
1210
+ and new_dataset.n_shards > num_processes
1211
+ ):
1212
+ new_dataset = new_dataset.shard(num_shards=num_processes, index=process_index)
1213
+ elif isinstance(new_dataset, IterableDataset):
1214
+ if getattr(dataloader.dataset, "generator", None) is not None:
1215
+ synchronized_generator = dataloader.dataset.generator
1216
+ new_dataset = IterableDatasetShard(
1217
+ new_dataset,
1218
+ batch_size=dataloader.batch_size,
1219
+ drop_last=dataloader.drop_last,
1220
+ num_processes=num_processes,
1221
+ process_index=process_index,
1222
+ split_batches=split_batches,
1223
+ )
1224
+ else:
1225
+ if not use_seedable_sampler and hasattr(sampler, "generator"):
1226
+ if sampler.generator is None:
1227
+ sampler.generator = torch.Generator(
1228
+ device=torch.get_default_device() if hasattr(torch, "get_default_device") else "cpu"
1229
+ )
1230
+ seed = int(torch.empty((), dtype=torch.int64).random_().item())
1231
+ sampler.generator.manual_seed(seed)
1232
+ synchronized_generator = sampler.generator
1233
+ batch_sampler = dataloader.sampler if sampler_is_batch_sampler else dataloader.batch_sampler
1234
+ new_batch_sampler = BatchSamplerShard(
1235
+ batch_sampler,
1236
+ num_processes=num_processes,
1237
+ process_index=process_index,
1238
+ split_batches=split_batches,
1239
+ even_batches=even_batches,
1240
+ )
1241
+
1242
+ # We ignore all of those since they are all dealt with by our new_batch_sampler
1243
+ ignore_kwargs = [
1244
+ "batch_size",
1245
+ "shuffle",
1246
+ "sampler",
1247
+ "batch_sampler",
1248
+ "drop_last",
1249
+ ]
1250
+
1251
+ if rng_types is not None and synchronized_generator is None and "generator" in rng_types:
1252
+ rng_types.remove("generator")
1253
+
1254
+ kwargs = {
1255
+ k: getattr(dataloader, k, _PYTORCH_DATALOADER_KWARGS[k])
1256
+ for k in _PYTORCH_DATALOADER_KWARGS
1257
+ if k not in ignore_kwargs
1258
+ }
1259
+
1260
+ # Need to provide batch_size as batch_sampler is None for Iterable dataset
1261
+ if new_batch_sampler is None:
1262
+ kwargs["drop_last"] = dataloader.drop_last
1263
+ kwargs["batch_size"] = (
1264
+ dataloader.batch_size // num_processes if split_batches and not dispatch_batches else dataloader.batch_size
1265
+ )
1266
+ if dispatch_batches:
1267
+ kwargs.pop("generator")
1268
+ dataloader = DataLoaderDispatcher(
1269
+ new_dataset,
1270
+ split_batches=split_batches,
1271
+ batch_sampler=new_batch_sampler,
1272
+ _drop_last=dataloader.drop_last,
1273
+ _non_blocking=non_blocking,
1274
+ slice_fn=slice_fn_for_dispatch,
1275
+ use_stateful_dataloader=use_stateful_dataloader,
1276
+ torch_device_mesh=torch_device_mesh,
1277
+ **kwargs,
1278
+ )
1279
+ elif sampler_is_batch_sampler:
1280
+ dataloader = DataLoaderShard(
1281
+ new_dataset,
1282
+ device=device if put_on_device and state.distributed_type != DistributedType.XLA else None,
1283
+ sampler=new_batch_sampler,
1284
+ batch_size=dataloader.batch_size,
1285
+ rng_types=rng_types,
1286
+ _drop_last=dataloader.drop_last,
1287
+ _non_blocking=non_blocking,
1288
+ synchronized_generator=synchronized_generator,
1289
+ use_stateful_dataloader=use_stateful_dataloader,
1290
+ **kwargs,
1291
+ )
1292
+ else:
1293
+ dataloader = DataLoaderShard(
1294
+ new_dataset,
1295
+ device=device if put_on_device and state.distributed_type != DistributedType.XLA else None,
1296
+ batch_sampler=new_batch_sampler,
1297
+ rng_types=rng_types,
1298
+ synchronized_generator=synchronized_generator,
1299
+ _drop_last=dataloader.drop_last,
1300
+ _non_blocking=non_blocking,
1301
+ use_stateful_dataloader=use_stateful_dataloader,
1302
+ **kwargs,
1303
+ )
1304
+
1305
+ if isinstance(sampler, SeedableRandomSampler) and use_seedable_sampler:
1306
+ dataloader.set_sampler(sampler)
1307
+ if state.distributed_type == DistributedType.XLA:
1308
+ return MpDeviceLoaderWrapper(dataloader, device)
1309
+ return dataloader
1310
+
1311
+
1312
+ class SkipBatchSampler(BatchSampler):
1313
+ """
1314
+ A `torch.utils.data.BatchSampler` that skips the first `n` batches of another `torch.utils.data.BatchSampler`.
1315
+ Should not be used if the original dataloader is a `StatefulDataLoader`.
1316
+ """
1317
+
1318
+ def __init__(self, batch_sampler, skip_batches=0):
1319
+ self.batch_sampler = batch_sampler
1320
+ self.skip_batches = skip_batches
1321
+
1322
+ def __iter__(self):
1323
+ for index, samples in enumerate(self.batch_sampler):
1324
+ if index >= self.skip_batches:
1325
+ yield samples
1326
+
1327
+ @property
1328
+ def total_length(self):
1329
+ return len(self.batch_sampler)
1330
+
1331
+ def __len__(self):
1332
+ return len(self.batch_sampler) - self.skip_batches
1333
+
1334
+
1335
+ class SkipDataLoader(DataLoaderAdapter, DataLoaderStateMixin):
1336
+ """
1337
+ Subclass of a PyTorch `DataLoader` that will skip the first batches. Generally it's preferable to use
1338
+ `skip_first_batches`/`torchdata.StatefulDataLoader` instead of this class.
1339
+
1340
+ Args:
1341
+ dataset (`torch.utils.data.dataset.Dataset`):
1342
+ The dataset to use to build this dataloader.
1343
+ skip_batches (`int`, *optional*, defaults to 0):
1344
+ The number of batches to skip at the beginning.
1345
+ kwargs:
1346
+ All other keyword arguments to pass to the regular `DataLoader` initialization.
1347
+ """
1348
+
1349
+ def __init__(self, dataset, skip_batches=0, use_stateful_dataloader=False, **kwargs):
1350
+ super().__init__(dataset, use_stateful_dataloader=use_stateful_dataloader, **kwargs)
1351
+ self.skip_batches = skip_batches
1352
+ self.gradient_state = GradientState()
1353
+
1354
+ def __iter__(self):
1355
+ self.begin()
1356
+ for index, batch in enumerate(self.base_dataloader.__iter__()):
1357
+ if index >= self.skip_batches:
1358
+ self._update_state_dict()
1359
+ yield batch
1360
+ self.end()
1361
+
1362
+ def __len__(self):
1363
+ return len(self.base_dataloader) - self.skip_batches
1364
+
1365
+ def __reduce__(self):
1366
+ """
1367
+ Define the `__reduce__` method to ensure a `SkipDataLoader` can be pickled and unpickled. This needs to be
1368
+ explicitly defined since default pickling behavior is broken by `DataLoaderAdapter` messing with its
1369
+ `__class__` member.
1370
+ """
1371
+ args = super().__reduce__()
1372
+ return (SkipDataLoader, *args[1:])
1373
+
1374
+
1375
+ def skip_first_batches(dataloader, num_batches=0):
1376
+ """
1377
+ Creates a `torch.utils.data.DataLoader` that will efficiently skip the first `num_batches`. Should not be used if
1378
+ the original dataloader is a `StatefulDataLoader`.
1379
+ """
1380
+ state = PartialState()
1381
+ if state.distributed_type == DistributedType.XLA:
1382
+ device = dataloader.device
1383
+ dataloader = dataloader.dataloader
1384
+
1385
+ dataset = dataloader.dataset
1386
+ sampler_is_batch_sampler = False
1387
+ if isinstance(dataset, IterableDataset):
1388
+ new_batch_sampler = None
1389
+ else:
1390
+ sampler_is_batch_sampler = isinstance(dataloader.sampler, BatchSampler)
1391
+ batch_sampler = dataloader.sampler if sampler_is_batch_sampler else dataloader.batch_sampler
1392
+ new_batch_sampler = SkipBatchSampler(batch_sampler, skip_batches=num_batches)
1393
+
1394
+ # We ignore all of those since they are all dealt with by our new_batch_sampler
1395
+ ignore_kwargs = [
1396
+ "batch_size",
1397
+ "shuffle",
1398
+ "sampler",
1399
+ "batch_sampler",
1400
+ "drop_last",
1401
+ ]
1402
+
1403
+ kwargs = {
1404
+ k: getattr(dataloader, k, _PYTORCH_DATALOADER_KWARGS[k])
1405
+ for k in _PYTORCH_DATALOADER_KWARGS
1406
+ if k not in ignore_kwargs
1407
+ }
1408
+
1409
+ # Need to provide batch_size as batch_sampler is None for Iterable dataset
1410
+ if new_batch_sampler is None:
1411
+ kwargs["drop_last"] = dataloader.drop_last
1412
+ kwargs["batch_size"] = dataloader.batch_size
1413
+
1414
+ if isinstance(dataloader, DataLoaderDispatcher):
1415
+ if new_batch_sampler is None:
1416
+ # Need to manually skip batches in the dataloader
1417
+ kwargs["skip_batches"] = num_batches
1418
+ dataloader = DataLoaderDispatcher(
1419
+ dataset,
1420
+ split_batches=dataloader.split_batches,
1421
+ batch_sampler=new_batch_sampler,
1422
+ _drop_last=dataloader._drop_last,
1423
+ **kwargs,
1424
+ )
1425
+ elif isinstance(dataloader, DataLoaderShard):
1426
+ if new_batch_sampler is None:
1427
+ # Need to manually skip batches in the dataloader
1428
+ kwargs["skip_batches"] = num_batches
1429
+ elif sampler_is_batch_sampler:
1430
+ kwargs["sampler"] = new_batch_sampler
1431
+ kwargs["batch_size"] = dataloader.batch_size
1432
+ else:
1433
+ kwargs["batch_sampler"] = new_batch_sampler
1434
+ dataloader = DataLoaderShard(
1435
+ dataset,
1436
+ device=dataloader.device,
1437
+ rng_types=dataloader.rng_types,
1438
+ synchronized_generator=dataloader.synchronized_generator,
1439
+ **kwargs,
1440
+ )
1441
+ else:
1442
+ if new_batch_sampler is None:
1443
+ # Need to manually skip batches in the dataloader
1444
+ dataloader = SkipDataLoader(dataset, skip_batches=num_batches, **kwargs)
1445
+ else:
1446
+ dataloader = DataLoader(dataset, batch_sampler=new_batch_sampler, **kwargs)
1447
+
1448
+ if state.distributed_type == DistributedType.XLA:
1449
+ dataloader = MpDeviceLoaderWrapper(dataloader, device)
1450
+
1451
+ return dataloader
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/hooks.py ADDED
@@ -0,0 +1,776 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2022 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import functools
16
+ from collections.abc import Mapping
17
+ from typing import Optional, Union
18
+
19
+ import torch
20
+ import torch.nn as nn
21
+
22
+ from .state import PartialState
23
+ from .utils import (
24
+ PrefixedDataset,
25
+ find_device,
26
+ named_module_tensors,
27
+ send_to_device,
28
+ set_module_tensor_to_device,
29
+ )
30
+ from .utils.imports import (
31
+ is_mlu_available,
32
+ is_musa_available,
33
+ is_npu_available,
34
+ )
35
+ from .utils.memory import clear_device_cache
36
+ from .utils.modeling import get_non_persistent_buffers
37
+ from .utils.other import recursive_getattr
38
+
39
+
40
+ _accelerate_added_attributes = ["to", "cuda", "npu", "xpu", "mlu", "sdaa", "musa"]
41
+
42
+
43
+ class ModelHook:
44
+ """
45
+ A hook that contains callbacks to be executed just before and after the forward method of a model. The difference
46
+ with PyTorch existing hooks is that they get passed along the kwargs.
47
+
48
+ Class attribute:
49
+ - **no_grad** (`bool`, *optional*, defaults to `False`) -- Whether or not to execute the actual forward pass under
50
+ the `torch.no_grad()` context manager.
51
+ """
52
+
53
+ no_grad = False
54
+
55
+ def init_hook(self, module):
56
+ """
57
+ To be executed when the hook is attached to the module.
58
+
59
+ Args:
60
+ module (`torch.nn.Module`): The module attached to this hook.
61
+ """
62
+ return module
63
+
64
+ def pre_forward(self, module, *args, **kwargs):
65
+ """
66
+ To be executed just before the forward method of the model.
67
+
68
+ Args:
69
+ module (`torch.nn.Module`): The module whose forward pass will be executed just after this event.
70
+ args (`Tuple[Any]`): The positional arguments passed to the module.
71
+ kwargs (`Dict[Str, Any]`): The keyword arguments passed to the module.
72
+
73
+ Returns:
74
+ `Tuple[Tuple[Any], Dict[Str, Any]]`: A tuple with the treated `args` and `kwargs`.
75
+ """
76
+ return args, kwargs
77
+
78
+ def post_forward(self, module, output):
79
+ """
80
+ To be executed just after the forward method of the model.
81
+
82
+ Args:
83
+ module (`torch.nn.Module`): The module whose forward pass been executed just before this event.
84
+ output (`Any`): The output of the module.
85
+
86
+ Returns:
87
+ `Any`: The processed `output`.
88
+ """
89
+ return output
90
+
91
+ def detach_hook(self, module):
92
+ """
93
+ To be executed when the hook is detached from a module.
94
+
95
+ Args:
96
+ module (`torch.nn.Module`): The module detached from this hook.
97
+ """
98
+ return module
99
+
100
+
101
+ class SequentialHook(ModelHook):
102
+ """
103
+ A hook that can contain several hooks and iterates through them at each event.
104
+ """
105
+
106
+ def __init__(self, *hooks):
107
+ self.hooks = hooks
108
+
109
+ def init_hook(self, module):
110
+ for hook in self.hooks:
111
+ module = hook.init_hook(module)
112
+ return module
113
+
114
+ def pre_forward(self, module, *args, **kwargs):
115
+ for hook in self.hooks:
116
+ args, kwargs = hook.pre_forward(module, *args, **kwargs)
117
+ return args, kwargs
118
+
119
+ def post_forward(self, module, output):
120
+ for hook in self.hooks:
121
+ output = hook.post_forward(module, output)
122
+ return output
123
+
124
+ def detach_hook(self, module):
125
+ for hook in self.hooks:
126
+ module = hook.detach_hook(module)
127
+ return module
128
+
129
+
130
+ def add_hook_to_module(module: nn.Module, hook: ModelHook, append: bool = False):
131
+ """
132
+ Adds a hook to a given module. This will rewrite the `forward` method of the module to include the hook, to remove
133
+ this behavior and restore the original `forward` method, use `remove_hook_from_module`.
134
+
135
+ <Tip warning={true}>
136
+
137
+ If the module already contains a hook, this will replace it with the new hook passed by default. To chain two hooks
138
+ together, pass `append=True`, so it chains the current and new hook into an instance of the `SequentialHook` class.
139
+
140
+ </Tip>
141
+
142
+ Args:
143
+ module (`torch.nn.Module`):
144
+ The module to attach a hook to.
145
+ hook (`ModelHook`):
146
+ The hook to attach.
147
+ append (`bool`, *optional*, defaults to `False`):
148
+ Whether the hook should be chained with an existing one (if module already contains a hook) or not.
149
+
150
+ Returns:
151
+ `torch.nn.Module`: The same module, with the hook attached (the module is modified in place, so the result can
152
+ be discarded).
153
+ """
154
+ if append and (getattr(module, "_hf_hook", None) is not None):
155
+ old_hook = module._hf_hook
156
+ remove_hook_from_module(module)
157
+ hook = SequentialHook(old_hook, hook)
158
+
159
+ if hasattr(module, "_hf_hook") and hasattr(module, "_old_forward"):
160
+ # If we already put some hook on this module, we replace it with the new one.
161
+ old_forward = module._old_forward
162
+ else:
163
+ old_forward = module.forward
164
+ module._old_forward = old_forward
165
+
166
+ module = hook.init_hook(module)
167
+ module._hf_hook = hook
168
+
169
+ def new_forward(module, *args, **kwargs):
170
+ args, kwargs = module._hf_hook.pre_forward(module, *args, **kwargs)
171
+ if module._hf_hook.no_grad:
172
+ with torch.no_grad():
173
+ output = module._old_forward(*args, **kwargs)
174
+ else:
175
+ output = module._old_forward(*args, **kwargs)
176
+ return module._hf_hook.post_forward(module, output)
177
+
178
+ # Overriding a GraphModuleImpl forward freezes the forward call and later modifications on the graph will fail.
179
+ # Reference: https://pytorch.slack.com/archives/C3PDTEV8E/p1705929610405409
180
+ if "GraphModuleImpl" in str(type(module)):
181
+ module.__class__.forward = functools.update_wrapper(functools.partial(new_forward, module), old_forward)
182
+ else:
183
+ module.forward = functools.update_wrapper(functools.partial(new_forward, module), old_forward)
184
+
185
+ return module
186
+
187
+
188
+ def remove_hook_from_module(module: nn.Module, recurse=False):
189
+ """
190
+ Removes any hook attached to a module via `add_hook_to_module`.
191
+
192
+ Args:
193
+ module (`torch.nn.Module`): The module to attach a hook to.
194
+ recurse (`bool`, **optional**): Whether to remove the hooks recursively
195
+
196
+ Returns:
197
+ `torch.nn.Module`: The same module, with the hook detached (the module is modified in place, so the result can
198
+ be discarded).
199
+ """
200
+
201
+ if hasattr(module, "_hf_hook"):
202
+ module._hf_hook.detach_hook(module)
203
+ delattr(module, "_hf_hook")
204
+
205
+ if hasattr(module, "_old_forward"):
206
+ # Overriding a GraphModuleImpl forward freezes the forward call and later modifications on the graph will fail.
207
+ # Reference: https://pytorch.slack.com/archives/C3PDTEV8E/p1705929610405409
208
+ if "GraphModuleImpl" in str(type(module)):
209
+ module.__class__.forward = module._old_forward
210
+ else:
211
+ module.forward = module._old_forward
212
+ delattr(module, "_old_forward")
213
+
214
+ # Remove accelerate added warning hooks from dispatch_model
215
+ for attr in _accelerate_added_attributes:
216
+ module.__dict__.pop(attr, None)
217
+
218
+ if recurse:
219
+ for child in module.children():
220
+ remove_hook_from_module(child, recurse)
221
+
222
+ return module
223
+
224
+
225
+ class AlignDevicesHook(ModelHook):
226
+ """
227
+ A generic `ModelHook` that ensures inputs and model weights are on the same device for the forward pass of the
228
+ associated module, potentially offloading the weights after the forward pass.
229
+
230
+ Args:
231
+ execution_device (`torch.device`, *optional*):
232
+ The device on which inputs and model weights should be placed before the forward pass.
233
+ offload (`bool`, *optional*, defaults to `False`):
234
+ Whether or not the weights should be offloaded after the forward pass.
235
+ io_same_device (`bool`, *optional*, defaults to `False`):
236
+ Whether or not the output should be placed on the same device as the input was.
237
+ weights_map (`Mapping[str, torch.Tensor]`, *optional*):
238
+ When the model weights are offloaded, a (potentially lazy) map from param names to the tensor values.
239
+ offload_buffers (`bool`, *optional*, defaults to `False`):
240
+ Whether or not to include the associated module's buffers when offloading.
241
+ place_submodules (`bool`, *optional*, defaults to `False`):
242
+ Whether to place the submodules on `execution_device` during the `init_hook` event.
243
+ """
244
+
245
+ def __init__(
246
+ self,
247
+ execution_device: Optional[Union[int, str, torch.device]] = None,
248
+ offload: bool = False,
249
+ io_same_device: bool = False,
250
+ weights_map: Optional[Mapping] = None,
251
+ offload_buffers: bool = False,
252
+ place_submodules: bool = False,
253
+ skip_keys: Optional[Union[str, list[str]]] = None,
254
+ tied_params_map: Optional[dict[int, dict[torch.device, torch.Tensor]]] = None,
255
+ ):
256
+ self.execution_device = execution_device
257
+ self.offload = offload
258
+ self.io_same_device = io_same_device
259
+ self.weights_map = weights_map
260
+ self.offload_buffers = offload_buffers
261
+ self.place_submodules = place_submodules
262
+ self.skip_keys = skip_keys
263
+
264
+ # Will contain the input device when `io_same_device=True`.
265
+ self.input_device = None
266
+ self.param_original_devices = {}
267
+ self.buffer_original_devices = {}
268
+ self.tied_params_names = set()
269
+
270
+ # The hook pre_forward/post_forward need to have knowledge of this dictionary, as with offloading we want to avoid duplicating memory
271
+ # for tied weights already loaded on the target execution device.
272
+ self.tied_params_map = tied_params_map
273
+
274
+ def __repr__(self):
275
+ return (
276
+ f"AlignDevicesHook(execution_device={self.execution_device}, offload={self.offload}, "
277
+ f"io_same_device={self.io_same_device}, offload_buffers={self.offload_buffers}, "
278
+ f"place_submodules={self.place_submodules}, skip_keys={repr(self.skip_keys)})"
279
+ )
280
+
281
+ def init_hook(self, module):
282
+ # In case the AlignDevicesHook is on meta device, ignore tied weights as data_ptr() is then always zero.
283
+ if self.execution_device == "meta" or self.execution_device == torch.device("meta"):
284
+ self.tied_params_map = None
285
+
286
+ if not self.offload and self.execution_device is not None:
287
+ for name, _ in named_module_tensors(module, recurse=self.place_submodules):
288
+ set_module_tensor_to_device(module, name, self.execution_device, tied_params_map=self.tied_params_map)
289
+ elif self.offload:
290
+ self.original_devices = {
291
+ name: param.device for name, param in named_module_tensors(module, recurse=self.place_submodules)
292
+ }
293
+ if self.weights_map is None:
294
+ self.weights_map = {
295
+ name: param.to("cpu")
296
+ for name, param in named_module_tensors(
297
+ module, include_buffers=self.offload_buffers, recurse=self.place_submodules
298
+ )
299
+ }
300
+ for name, _ in named_module_tensors(
301
+ module, include_buffers=self.offload_buffers, recurse=self.place_submodules, remove_non_persistent=True
302
+ ):
303
+ # When using disk offloading, we can not rely on `weights_map[name].data_ptr()` as the reference pointer,
304
+ # as we have no guarantee that safetensors' `file.get_tensor()` will always give the same pointer.
305
+ # As we have no reliable way to track the shared data pointer of tied weights in this case, we use tied_params_names: List[str]
306
+ # to add on the fly pointers to `tied_params_map` in the pre_forward call.
307
+ if (
308
+ self.tied_params_map is not None
309
+ and recursive_getattr(module, name).data_ptr() in self.tied_params_map
310
+ ):
311
+ self.tied_params_names.add(name)
312
+
313
+ set_module_tensor_to_device(module, name, "meta")
314
+
315
+ if not self.offload_buffers and self.execution_device is not None:
316
+ for name, _ in module.named_buffers(recurse=self.place_submodules):
317
+ set_module_tensor_to_device(
318
+ module, name, self.execution_device, tied_params_map=self.tied_params_map
319
+ )
320
+ elif self.offload_buffers and self.execution_device is not None:
321
+ for name in get_non_persistent_buffers(module, recurse=self.place_submodules):
322
+ set_module_tensor_to_device(
323
+ module, name, self.execution_device, tied_params_map=self.tied_params_map
324
+ )
325
+
326
+ return module
327
+
328
+ def pre_forward(self, module, *args, **kwargs):
329
+ if self.io_same_device:
330
+ self.input_device = find_device([args, kwargs])
331
+ if self.offload:
332
+ self.tied_pointers_to_remove = set()
333
+
334
+ for name, _ in named_module_tensors(
335
+ module,
336
+ include_buffers=self.offload_buffers,
337
+ recurse=self.place_submodules,
338
+ remove_non_persistent=True,
339
+ ):
340
+ fp16_statistics = None
341
+ value = self.weights_map[name]
342
+ if "weight" in name and name.replace("weight", "SCB") in self.weights_map.keys():
343
+ if value.dtype == torch.int8:
344
+ fp16_statistics = self.weights_map[name.replace("weight", "SCB")]
345
+
346
+ # In case we are using offloading with tied weights, we need to keep track of the offloaded weights
347
+ # that are loaded on device at this point, as we will need to remove them as well from the dictionary
348
+ # self.tied_params_map in order to allow to free memory.
349
+ if name in self.tied_params_names and value.data_ptr() not in self.tied_params_map:
350
+ self.tied_params_map[value.data_ptr()] = {}
351
+
352
+ if (
353
+ value is not None
354
+ and self.tied_params_map is not None
355
+ and value.data_ptr() in self.tied_params_map
356
+ and self.execution_device not in self.tied_params_map[value.data_ptr()]
357
+ ):
358
+ self.tied_pointers_to_remove.add((value.data_ptr(), self.execution_device))
359
+
360
+ set_module_tensor_to_device(
361
+ module,
362
+ name,
363
+ self.execution_device,
364
+ value=value,
365
+ fp16_statistics=fp16_statistics,
366
+ tied_params_map=self.tied_params_map,
367
+ )
368
+
369
+ return send_to_device(args, self.execution_device), send_to_device(
370
+ kwargs, self.execution_device, skip_keys=self.skip_keys
371
+ )
372
+
373
+ def post_forward(self, module, output):
374
+ if self.offload:
375
+ for name, _ in named_module_tensors(
376
+ module,
377
+ include_buffers=self.offload_buffers,
378
+ recurse=self.place_submodules,
379
+ remove_non_persistent=True,
380
+ ):
381
+ set_module_tensor_to_device(module, name, "meta")
382
+ if type(module).__name__ == "Linear8bitLt":
383
+ module.state.SCB = None
384
+ module.state.CxB = None
385
+
386
+ # We may have loaded tied weights into self.tied_params_map (avoiding to load them several times in e.g. submodules): remove them from
387
+ # this dictionary to allow the garbage collector to do its job.
388
+ for value_pointer, device in self.tied_pointers_to_remove:
389
+ if isinstance(device, int):
390
+ if is_npu_available():
391
+ device = f"npu:{device}"
392
+ elif is_mlu_available():
393
+ device = f"mlu:{device}"
394
+ elif is_musa_available():
395
+ device = f"musa:{device}"
396
+ if device in self.tied_params_map[value_pointer]:
397
+ del self.tied_params_map[value_pointer][device]
398
+ self.tied_pointers_to_remove = set()
399
+ if self.io_same_device and self.input_device is not None:
400
+ output = send_to_device(output, self.input_device, skip_keys=self.skip_keys)
401
+
402
+ return output
403
+
404
+ def detach_hook(self, module):
405
+ if self.offload:
406
+ for name, device in self.original_devices.items():
407
+ if device != torch.device("meta"):
408
+ set_module_tensor_to_device(module, name, device, value=self.weights_map.get(name, None))
409
+ return module
410
+
411
+
412
+ def attach_execution_device_hook(
413
+ module: torch.nn.Module,
414
+ execution_device: Union[int, str, torch.device],
415
+ skip_keys: Optional[Union[str, list[str]]] = None,
416
+ preload_module_classes: Optional[list[str]] = None,
417
+ tied_params_map: Optional[dict[int, dict[torch.device, torch.Tensor]]] = None,
418
+ ):
419
+ """
420
+ Recursively attaches `AlignDevicesHook` to all submodules of a given model to make sure they have the right
421
+ execution device
422
+
423
+ Args:
424
+ module (`torch.nn.Module`):
425
+ The module where we want to attach the hooks.
426
+ execution_device (`int`, `str` or `torch.device`):
427
+ The device on which inputs and model weights should be placed before the forward pass.
428
+ skip_keys (`str` or `List[str]`, *optional*):
429
+ A list of keys to ignore when moving inputs or outputs between devices.
430
+ preload_module_classes (`List[str]`, *optional*):
431
+ A list of classes whose instances should load all their weights (even in the submodules) at the beginning
432
+ of the forward. This should only be used for classes that have submodules which are registered but not
433
+ called directly during the forward, for instance if a `dense` linear layer is registered, but at forward,
434
+ `dense.weight` and `dense.bias` are used in some operations instead of calling `dense` directly.
435
+ tied_params_map (Optional[Dict[int, Dict[torch.device, torch.Tensor]]], *optional*, defaults to `None`):
436
+ A map of data pointers to dictionaries of devices to already dispatched tied weights. For a given execution
437
+ device, this parameter is useful to reuse the first available pointer of a shared weight for all others,
438
+ instead of duplicating memory.
439
+ """
440
+ if not hasattr(module, "_hf_hook") and len(module.state_dict()) > 0:
441
+ add_hook_to_module(
442
+ module,
443
+ AlignDevicesHook(execution_device, skip_keys=skip_keys, tied_params_map=tied_params_map),
444
+ )
445
+
446
+ # Break the recursion if we get to a preload module.
447
+ if preload_module_classes is not None and module.__class__.__name__ in preload_module_classes:
448
+ return
449
+
450
+ for child in module.children():
451
+ attach_execution_device_hook(
452
+ child,
453
+ execution_device,
454
+ skip_keys=skip_keys,
455
+ preload_module_classes=preload_module_classes,
456
+ tied_params_map=tied_params_map,
457
+ )
458
+
459
+
460
+ def attach_align_device_hook(
461
+ module: torch.nn.Module,
462
+ execution_device: Optional[torch.device] = None,
463
+ offload: bool = False,
464
+ weights_map: Optional[Mapping] = None,
465
+ offload_buffers: bool = False,
466
+ module_name: str = "",
467
+ skip_keys: Optional[Union[str, list[str]]] = None,
468
+ preload_module_classes: Optional[list[str]] = None,
469
+ tied_params_map: Optional[dict[int, dict[torch.device, torch.Tensor]]] = None,
470
+ ):
471
+ """
472
+ Recursively attaches `AlignDevicesHook` to all submodules of a given model that have direct parameters and/or
473
+ buffers.
474
+
475
+ Args:
476
+ module (`torch.nn.Module`):
477
+ The module where we want to attach the hooks.
478
+ execution_device (`torch.device`, *optional*):
479
+ The device on which inputs and model weights should be placed before the forward pass.
480
+ offload (`bool`, *optional*, defaults to `False`):
481
+ Whether or not the weights should be offloaded after the forward pass.
482
+ weights_map (`Mapping[str, torch.Tensor]`, *optional*):
483
+ When the model weights are offloaded, a (potentially lazy) map from param names to the tensor values.
484
+ offload_buffers (`bool`, *optional*, defaults to `False`):
485
+ Whether or not to include the associated module's buffers when offloading.
486
+ module_name (`str`, *optional*, defaults to `""`):
487
+ The name of the module.
488
+ skip_keys (`str` or `List[str]`, *optional*):
489
+ A list of keys to ignore when moving inputs or outputs between devices.
490
+ preload_module_classes (`List[str]`, *optional*):
491
+ A list of classes whose instances should load all their weights (even in the submodules) at the beginning
492
+ of the forward. This should only be used for classes that have submodules which are registered but not
493
+ called directly during the forward, for instance if a `dense` linear layer is registered, but at forward,
494
+ `dense.weight` and `dense.bias` are used in some operations instead of calling `dense` directly.
495
+ tied_params_map (Optional[Dict[int, Dict[torch.device, torch.Tensor]]], *optional*, defaults to `None`):
496
+ A map of data pointers to dictionaries of devices to already dispatched tied weights. For a given execution
497
+ device, this parameter is useful to reuse the first available pointer of a shared weight for all others,
498
+ instead of duplicating memory.
499
+ """
500
+ # Attach the hook on this module if it has any direct tensor.
501
+ directs = named_module_tensors(module)
502
+ full_offload = (
503
+ offload and preload_module_classes is not None and module.__class__.__name__ in preload_module_classes
504
+ )
505
+
506
+ if len(list(directs)) > 0 or full_offload:
507
+ if weights_map is not None:
508
+ prefix = f"{module_name}." if len(module_name) > 0 else ""
509
+ prefixed_weights_map = PrefixedDataset(weights_map, prefix)
510
+ else:
511
+ prefixed_weights_map = None
512
+ hook = AlignDevicesHook(
513
+ execution_device=execution_device,
514
+ offload=offload,
515
+ weights_map=prefixed_weights_map,
516
+ offload_buffers=offload_buffers,
517
+ place_submodules=full_offload,
518
+ skip_keys=skip_keys,
519
+ tied_params_map=tied_params_map,
520
+ )
521
+ add_hook_to_module(module, hook, append=True)
522
+
523
+ # We stop the recursion in case we hit the full offload.
524
+ if full_offload:
525
+ return
526
+
527
+ # Recurse on all children of the module.
528
+ for child_name, child in module.named_children():
529
+ child_name = f"{module_name}.{child_name}" if len(module_name) > 0 else child_name
530
+ attach_align_device_hook(
531
+ child,
532
+ execution_device=execution_device,
533
+ offload=offload,
534
+ weights_map=weights_map,
535
+ offload_buffers=offload_buffers,
536
+ module_name=child_name,
537
+ preload_module_classes=preload_module_classes,
538
+ skip_keys=skip_keys,
539
+ tied_params_map=tied_params_map,
540
+ )
541
+
542
+
543
+ def remove_hook_from_submodules(module: nn.Module):
544
+ """
545
+ Recursively removes all hooks attached on the submodules of a given model.
546
+
547
+ Args:
548
+ module (`torch.nn.Module`): The module on which to remove all hooks.
549
+ """
550
+ remove_hook_from_module(module)
551
+ for child in module.children():
552
+ remove_hook_from_submodules(child)
553
+
554
+
555
+ def attach_align_device_hook_on_blocks(
556
+ module: nn.Module,
557
+ execution_device: Optional[Union[torch.device, dict[str, torch.device]]] = None,
558
+ offload: Union[bool, dict[str, bool]] = False,
559
+ weights_map: Optional[Mapping] = None,
560
+ offload_buffers: bool = False,
561
+ module_name: str = "",
562
+ skip_keys: Optional[Union[str, list[str]]] = None,
563
+ preload_module_classes: Optional[list[str]] = None,
564
+ tied_params_map: Optional[dict[int, dict[torch.device, torch.Tensor]]] = None,
565
+ ):
566
+ """
567
+ Attaches `AlignDevicesHook` to all blocks of a given model as needed.
568
+
569
+ Args:
570
+ module (`torch.nn.Module`):
571
+ The module where we want to attach the hooks.
572
+ execution_device (`torch.device` or `Dict[str, torch.device]`, *optional*):
573
+ The device on which inputs and model weights should be placed before the forward pass. It can be one device
574
+ for the whole module, or a dictionary mapping module name to device.
575
+ offload (`bool`, *optional*, defaults to `False`):
576
+ Whether or not the weights should be offloaded after the forward pass. It can be one boolean for the whole
577
+ module, or a dictionary mapping module name to boolean.
578
+ weights_map (`Mapping[str, torch.Tensor]`, *optional*):
579
+ When the model weights are offloaded, a (potentially lazy) map from param names to the tensor values.
580
+ offload_buffers (`bool`, *optional*, defaults to `False`):
581
+ Whether or not to include the associated module's buffers when offloading.
582
+ module_name (`str`, *optional*, defaults to `""`):
583
+ The name of the module.
584
+ skip_keys (`str` or `List[str]`, *optional*):
585
+ A list of keys to ignore when moving inputs or outputs between devices.
586
+ preload_module_classes (`List[str]`, *optional*):
587
+ A list of classes whose instances should load all their weights (even in the submodules) at the beginning
588
+ of the forward. This should only be used for classes that have submodules which are registered but not
589
+ called directly during the forward, for instance if a `dense` linear layer is registered, but at forward,
590
+ `dense.weight` and `dense.bias` are used in some operations instead of calling `dense` directly.
591
+ tied_params_map (Optional[Dict[int, Dict[torch.device, torch.Tensor]]], *optional*, defaults to `None`):
592
+ A map of data pointers to dictionaries of devices to already dispatched tied weights. For a given execution
593
+ device, this parameter is useful to reuse the first available pointer of a shared weight for all others,
594
+ instead of duplicating memory.
595
+ """
596
+ # If one device and one offload, we've got one hook.
597
+ if not isinstance(execution_device, Mapping) and not isinstance(offload, dict):
598
+ if not offload:
599
+ hook = AlignDevicesHook(
600
+ execution_device=execution_device,
601
+ io_same_device=True,
602
+ skip_keys=skip_keys,
603
+ place_submodules=True,
604
+ tied_params_map=tied_params_map,
605
+ )
606
+ add_hook_to_module(module, hook)
607
+ else:
608
+ attach_align_device_hook(
609
+ module,
610
+ execution_device=execution_device,
611
+ offload=True,
612
+ weights_map=weights_map,
613
+ offload_buffers=offload_buffers,
614
+ module_name=module_name,
615
+ skip_keys=skip_keys,
616
+ tied_params_map=tied_params_map,
617
+ )
618
+ return
619
+
620
+ if not isinstance(execution_device, Mapping):
621
+ execution_device = {key: execution_device for key in offload.keys()}
622
+ if not isinstance(offload, Mapping):
623
+ offload = {key: offload for key in execution_device.keys()}
624
+
625
+ if module_name in execution_device and module_name in offload and not offload[module_name]:
626
+ hook = AlignDevicesHook(
627
+ execution_device=execution_device[module_name],
628
+ offload_buffers=offload_buffers,
629
+ io_same_device=(module_name == ""),
630
+ place_submodules=True,
631
+ skip_keys=skip_keys,
632
+ tied_params_map=tied_params_map,
633
+ )
634
+ add_hook_to_module(module, hook)
635
+ attach_execution_device_hook(
636
+ module, execution_device[module_name], skip_keys=skip_keys, tied_params_map=tied_params_map
637
+ )
638
+ elif module_name in execution_device and module_name in offload:
639
+ attach_align_device_hook(
640
+ module,
641
+ execution_device=execution_device[module_name],
642
+ offload=True,
643
+ weights_map=weights_map,
644
+ offload_buffers=offload_buffers,
645
+ module_name=module_name,
646
+ skip_keys=skip_keys,
647
+ preload_module_classes=preload_module_classes,
648
+ tied_params_map=tied_params_map,
649
+ )
650
+ if not hasattr(module, "_hf_hook"):
651
+ hook = AlignDevicesHook(
652
+ execution_device=execution_device[module_name],
653
+ io_same_device=(module_name == ""),
654
+ skip_keys=skip_keys,
655
+ tied_params_map=tied_params_map,
656
+ )
657
+ add_hook_to_module(module, hook)
658
+ attach_execution_device_hook(
659
+ module,
660
+ execution_device[module_name],
661
+ preload_module_classes=preload_module_classes,
662
+ skip_keys=skip_keys,
663
+ tied_params_map=tied_params_map,
664
+ )
665
+ elif module_name == "":
666
+ hook = AlignDevicesHook(
667
+ execution_device=execution_device.get(""),
668
+ io_same_device=True,
669
+ skip_keys=skip_keys,
670
+ tied_params_map=tied_params_map,
671
+ )
672
+ add_hook_to_module(module, hook)
673
+
674
+ for child_name, child in module.named_children():
675
+ child_name = f"{module_name}.{child_name}" if len(module_name) > 0 else child_name
676
+ attach_align_device_hook_on_blocks(
677
+ child,
678
+ execution_device=execution_device,
679
+ offload=offload,
680
+ weights_map=weights_map,
681
+ offload_buffers=offload_buffers,
682
+ module_name=child_name,
683
+ preload_module_classes=preload_module_classes,
684
+ skip_keys=skip_keys,
685
+ tied_params_map=tied_params_map,
686
+ )
687
+
688
+
689
+ class CpuOffload(ModelHook):
690
+ """
691
+ Offloads a model on the CPU until its forward pass is called. The model will not be offloaded back to the CPU after
692
+ the forward, the user needs to call the `init_hook` method again for this.
693
+
694
+ Args:
695
+ execution_device(`str`, `int` or `torch.device`, *optional*):
696
+ The device on which the model should be executed. Will default to the MPS device if it's available, then
697
+ GPU 0 if there is a GPU, and finally to the CPU.
698
+ prev_module_hook (`UserCpuOffloadHook`, *optional*):
699
+ The hook sent back by [`cpu_offload_with_hook`] for a previous model in the pipeline you are running. If
700
+ passed, its offload method will be called just before the forward of the model to which this hook is
701
+ attached.
702
+ """
703
+
704
+ def __init__(
705
+ self,
706
+ execution_device: Optional[Union[str, int, torch.device]] = None,
707
+ prev_module_hook: Optional["UserCpuOffloadHook"] = None,
708
+ ):
709
+ self.prev_module_hook = prev_module_hook
710
+
711
+ self.execution_device = execution_device if execution_device is not None else PartialState().default_device
712
+
713
+ def init_hook(self, module):
714
+ return module.to("cpu")
715
+
716
+ def pre_forward(self, module, *args, **kwargs):
717
+ if self.prev_module_hook is not None and isinstance(self.prev_module_hook, UserCpuOffloadHook):
718
+ prev_module = self.prev_module_hook.model
719
+ prev_device = next(prev_module.parameters()).device
720
+
721
+ # Only offload the previous module if it is not already on CPU.
722
+ if prev_device != torch.device("cpu"):
723
+ self.prev_module_hook.offload()
724
+ clear_device_cache()
725
+
726
+ # If the current device is already the self.execution_device, we can skip the transfer.
727
+ current_device = next(module.parameters()).device
728
+ if current_device == self.execution_device:
729
+ return args, kwargs
730
+
731
+ module.to(self.execution_device)
732
+ return send_to_device(args, self.execution_device), send_to_device(kwargs, self.execution_device)
733
+
734
+
735
+ class UserCpuOffloadHook:
736
+ """
737
+ A simple hook grouping a model and a `ModelHook`, which provides easy APIs for to call the init method of the hook
738
+ or remove it entirely.
739
+ """
740
+
741
+ def __init__(self, model, hook):
742
+ self.model = model
743
+ self.hook = hook
744
+
745
+ def offload(self):
746
+ self.hook.init_hook(self.model)
747
+
748
+ def remove(self):
749
+ remove_hook_from_module(self.model)
750
+
751
+
752
+ class LayerwiseCastingHook(ModelHook):
753
+ r"""
754
+ A hook that casts the weights of a module to a high precision dtype for computation, and to a low precision dtype
755
+ for storage. This process may lead to quality loss in the output, but can significantly reduce the memory
756
+ footprint.
757
+ """
758
+
759
+ _is_stateful = False
760
+
761
+ def __init__(self, storage_dtype: torch.dtype, compute_dtype: torch.dtype, non_blocking: bool) -> None:
762
+ self.storage_dtype = storage_dtype
763
+ self.compute_dtype = compute_dtype
764
+ self.non_blocking = non_blocking
765
+
766
+ def init_hook(self, module: torch.nn.Module):
767
+ module.to(dtype=self.storage_dtype, non_blocking=self.non_blocking)
768
+ return module
769
+
770
+ def pre_forward(self, module: torch.nn.Module, *args, **kwargs):
771
+ module.to(dtype=self.compute_dtype, non_blocking=self.non_blocking)
772
+ return args, kwargs
773
+
774
+ def post_forward(self, module: torch.nn.Module, output):
775
+ module.to(dtype=self.storage_dtype, non_blocking=self.non_blocking)
776
+ return output
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/inference.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import math
15
+ from types import MethodType
16
+ from typing import Any, Optional, Union
17
+
18
+ from .state import PartialState
19
+ from .utils import (
20
+ calculate_maximum_sizes,
21
+ convert_bytes,
22
+ copy_tensor_to_devices,
23
+ ignorant_find_batch_size,
24
+ infer_auto_device_map,
25
+ is_pippy_available,
26
+ pad_input_tensors,
27
+ send_to_device,
28
+ )
29
+
30
+
31
+ def generate_device_map(
32
+ model, num_processes: int = 1, no_split_module_classes=None, max_memory: Optional[dict] = None
33
+ ):
34
+ """
35
+ Calculates the device map for `model` with an offset for PiPPy
36
+ """
37
+ if num_processes == 1:
38
+ return infer_auto_device_map(model, no_split_module_classes=no_split_module_classes, clean_result=False)
39
+ if max_memory is None:
40
+ model_size, shared = calculate_maximum_sizes(model)
41
+
42
+ # Split into `n` chunks for each GPU
43
+ memory = (model_size + shared[0]) / num_processes
44
+ memory = convert_bytes(memory)
45
+ value, ending = memory.split(" ")
46
+
47
+ # Add a chunk to deal with potential extra shared memory instances
48
+ memory = math.ceil(float(value)) * 1.1
49
+ memory = f"{memory} {ending}"
50
+ max_memory = {i: memory for i in range(num_processes)}
51
+ device_map = infer_auto_device_map(
52
+ model,
53
+ max_memory=max_memory,
54
+ no_split_module_classes=no_split_module_classes,
55
+ clean_result=False,
56
+ )
57
+ return device_map
58
+
59
+
60
+ def find_pippy_batch_size(args, kwargs):
61
+ found_batch_size = None
62
+ if args is not None:
63
+ for arg in args:
64
+ found_batch_size = ignorant_find_batch_size(arg)
65
+ if found_batch_size is not None:
66
+ break
67
+ if kwargs is not None and found_batch_size is None:
68
+ for kwarg in kwargs.values():
69
+ found_batch_size = ignorant_find_batch_size(kwarg)
70
+ if found_batch_size is not None:
71
+ break
72
+ return found_batch_size
73
+
74
+
75
+ def build_pipeline(model, split_points, args, kwargs, num_chunks):
76
+ """
77
+ Attaches the split points to the model based on `self.device_map` and generates a `PipelineStage`. Requires passing
78
+ in needed `args` and `kwargs` as the model needs on the CPU.
79
+
80
+ Users can pass in custom `num_chunks` as an optional hyper-parameter. By default will use
81
+ `AcceleratorState.num_processes`
82
+ """
83
+ # Note: We import here to reduce import time from general modules, and isolate outside dependencies
84
+ from torch.distributed.pipelining import ScheduleGPipe, SplitPoint, pipeline
85
+
86
+ # We need to annotate the split points in the model for PiPPy
87
+ state = PartialState()
88
+ split_spec = {split_point: SplitPoint.BEGINNING for split_point in split_points}
89
+ pipe = pipeline(
90
+ model,
91
+ mb_args=args,
92
+ mb_kwargs=kwargs,
93
+ split_spec=split_spec,
94
+ )
95
+ stage = pipe.build_stage(state.local_process_index, device=state.device)
96
+ schedule = ScheduleGPipe(stage, num_chunks)
97
+
98
+ return schedule
99
+
100
+
101
+ def pippy_forward(forward, num_chunks, gather_output, *args, **kwargs):
102
+ state = PartialState()
103
+ output = None
104
+
105
+ if state.num_processes == 1:
106
+ output = forward(*args, **kwargs)
107
+ elif state.is_local_main_process:
108
+ found_batch_size = find_pippy_batch_size(args, kwargs)
109
+ if found_batch_size is None:
110
+ raise ValueError("Could not find batch size from args or kwargs")
111
+ else:
112
+ if found_batch_size != num_chunks:
113
+ args = pad_input_tensors(args, found_batch_size, num_chunks)
114
+ kwargs = pad_input_tensors(kwargs, found_batch_size, num_chunks)
115
+ forward(*args, **kwargs)
116
+ elif state.is_last_process:
117
+ output = forward()
118
+ else:
119
+ forward()
120
+ if gather_output:
121
+ # Each node will get a copy of the full output which is only on the last GPU
122
+ output = copy_tensor_to_devices(output)
123
+ return output
124
+
125
+
126
+ def prepare_pippy(
127
+ model,
128
+ split_points: Optional[Union[str, list[str]]] = "auto",
129
+ no_split_module_classes: Optional[list[str]] = None,
130
+ example_args: Optional[tuple[Any]] = (),
131
+ example_kwargs: Optional[dict[str, Any]] = None,
132
+ num_chunks: Optional[int] = None,
133
+ gather_output: Optional[bool] = False,
134
+ ):
135
+ """
136
+ Wraps `model` for pipeline parallel inference.
137
+
138
+ Args:
139
+ model (`torch.nn.Module`):
140
+ A model we want to split for pipeline-parallel inference
141
+ split_points (`str` or `List[str]`, defaults to 'auto'):
142
+ How to generate the split points and chunk the model across each GPU. 'auto' will find the best balanced
143
+ split given any model. Should be a list of layer names in the model to split by otherwise.
144
+ no_split_module_classes (`List[str]`):
145
+ A list of class names for layers we don't want to be split.
146
+ example_args (tuple of model inputs):
147
+ The expected inputs for the model that uses order-based inputs for a *single process*. Recommended to use
148
+ this method if possible.
149
+ example_kwargs (dict of model inputs)
150
+ The expected inputs for the model that uses dictionary-based inputs for a *single process*. This is a
151
+ *highly* limiting structure that requires the same keys be present at *all* inference calls. Not
152
+ recommended unless the prior condition is true for all cases.
153
+ num_chunks (`int`, defaults to the number of available GPUs):
154
+ The number of different stages the Pipeline will have. By default it will assign one chunk per GPU, but
155
+ this can be tuned and played with. In general one should have num_chunks >= num_gpus.
156
+ gather_output (`bool`, defaults to `False`):
157
+ If `True`, the output from the last GPU (which holds the true outputs) is sent across to all GPUs.
158
+ """
159
+ if not is_pippy_available():
160
+ raise ImportError("Using `torch.distributed.pipelining` requires PyTorch 2.4.0 or later.")
161
+ state = PartialState()
162
+ example_args = send_to_device(example_args, "cpu")
163
+ example_kwargs = send_to_device(example_kwargs, "cpu")
164
+ if num_chunks is None:
165
+ num_chunks = state.num_processes
166
+ if split_points == "auto":
167
+ device_map = generate_device_map(model, num_chunks, no_split_module_classes=no_split_module_classes)
168
+ split_points = []
169
+ for i in range(1, num_chunks):
170
+ split_points.append(next(k for k, v in device_map.items() if v == i))
171
+ model.hf_split_points = split_points
172
+ stage = build_pipeline(model, split_points, example_args, example_kwargs, num_chunks)
173
+ model._original_forward = model.forward
174
+ model._original_call = model.__call__
175
+ model.pippy_stage = stage
176
+ model.hf_split_points = split_points
177
+
178
+ def forward(*args, **kwargs):
179
+ return pippy_forward(stage.step, num_chunks, gather_output, *args, **kwargs)
180
+
181
+ # To act like a decorator so that it can be popped when doing `extract_model_from_parallel`
182
+ # Note: creates an infinite recursion loop with `generate`
183
+ model_forward = MethodType(forward, model)
184
+ forward.__wrapped__ = model_forward
185
+ model.forward = forward
186
+ return model
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/launchers.py ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2021 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import os
16
+ import sys
17
+ import tempfile
18
+
19
+ import torch
20
+
21
+ from .state import AcceleratorState, PartialState
22
+ from .utils import (
23
+ PrecisionType,
24
+ PrepareForLaunch,
25
+ are_libraries_initialized,
26
+ check_cuda_p2p_ib_support,
27
+ get_current_device_type,
28
+ get_gpu_info,
29
+ is_mps_available,
30
+ is_torch_version,
31
+ patch_environment,
32
+ )
33
+ from .utils.constants import ELASTIC_LOG_LINE_PREFIX_TEMPLATE_PYTORCH_VERSION
34
+
35
+
36
+ def test_launch():
37
+ "Verify a `PartialState` can be initialized."
38
+ _ = PartialState()
39
+
40
+
41
+ def notebook_launcher(
42
+ function,
43
+ args=(),
44
+ num_processes=None,
45
+ mixed_precision="no",
46
+ use_port="29500",
47
+ master_addr="127.0.0.1",
48
+ node_rank=0,
49
+ num_nodes=1,
50
+ rdzv_backend="static",
51
+ rdzv_endpoint="",
52
+ rdzv_conf=None,
53
+ rdzv_id="none",
54
+ max_restarts=0,
55
+ monitor_interval=0.1,
56
+ log_line_prefix_template=None,
57
+ ):
58
+ """
59
+ Launches a training function, using several processes or multiple nodes if it's possible in the current environment
60
+ (TPU with multiple cores for instance).
61
+
62
+ <Tip warning={true}>
63
+
64
+ To use this function absolutely zero calls to a device must be made in the notebook session before calling. If any
65
+ have been made, you will need to restart the notebook and make sure no cells use any device capability.
66
+
67
+ Setting `ACCELERATE_DEBUG_MODE="1"` in your environment will run a test before truly launching to ensure that none
68
+ of those calls have been made.
69
+
70
+ </Tip>
71
+
72
+ Args:
73
+ function (`Callable`):
74
+ The training function to execute. If it accepts arguments, the first argument should be the index of the
75
+ process run.
76
+ args (`Tuple`):
77
+ Tuple of arguments to pass to the function (it will receive `*args`).
78
+ num_processes (`int`, *optional*):
79
+ The number of processes to use for training. Will default to 8 in Colab/Kaggle if a TPU is available, to
80
+ the number of devices available otherwise.
81
+ mixed_precision (`str`, *optional*, defaults to `"no"`):
82
+ If `fp16` or `bf16`, will use mixed precision training on multi-device.
83
+ use_port (`str`, *optional*, defaults to `"29500"`):
84
+ The port to use to communicate between processes when launching a multi-device training.
85
+ master_addr (`str`, *optional*, defaults to `"127.0.0.1"`):
86
+ The address to use for communication between processes.
87
+ node_rank (`int`, *optional*, defaults to 0):
88
+ The rank of the current node.
89
+ num_nodes (`int`, *optional*, defaults to 1):
90
+ The number of nodes to use for training.
91
+ rdzv_backend (`str`, *optional*, defaults to `"static"`):
92
+ The rendezvous method to use, such as 'static' (the default) or 'c10d'
93
+ rdzv_endpoint (`str`, *optional*, defaults to `""`):
94
+ The endpoint of the rdzv sync. storage.
95
+ rdzv_conf (`Dict`, *optional*, defaults to `None`):
96
+ Additional rendezvous configuration.
97
+ rdzv_id (`str`, *optional*, defaults to `"none"`):
98
+ The unique run id of the job.
99
+ max_restarts (`int`, *optional*, defaults to 0):
100
+ The maximum amount of restarts that elastic agent will conduct on workers before failure.
101
+ monitor_interval (`float`, *optional*, defaults to 0.1):
102
+ The interval in seconds that is used by the elastic_agent as a period of monitoring workers.
103
+ log_line_prefix_template (`str`, *optional*, defaults to `None`):
104
+ The prefix template for elastic launch logging. Available from PyTorch 2.2.0.
105
+
106
+ Example:
107
+
108
+ ```python
109
+ # Assume this is defined in a Jupyter Notebook on an instance with two devices
110
+ from accelerate import notebook_launcher
111
+
112
+
113
+ def train(*args):
114
+ # Your training function here
115
+ ...
116
+
117
+
118
+ notebook_launcher(train, args=(arg1, arg2), num_processes=2, mixed_precision="fp16")
119
+ ```
120
+ """
121
+ # Are we in a google colab or a Kaggle Kernel?
122
+ in_colab = False
123
+ in_kaggle = False
124
+ if any(key.startswith("KAGGLE") for key in os.environ.keys()):
125
+ in_kaggle = True
126
+ elif "IPython" in sys.modules:
127
+ in_colab = "google.colab" in str(sys.modules["IPython"].get_ipython())
128
+
129
+ try:
130
+ mixed_precision = PrecisionType(mixed_precision.lower())
131
+ except ValueError:
132
+ raise ValueError(
133
+ f"Unknown mixed_precision mode: {args.mixed_precision.lower()}. Choose between {PrecisionType.list()}."
134
+ )
135
+
136
+ if (in_colab or in_kaggle) and (
137
+ (os.environ.get("TPU_NAME", None) is not None) or (os.environ.get("PJRT_DEVICE", "") == "TPU")
138
+ ):
139
+ # TPU launch
140
+ import torch_xla.distributed.xla_multiprocessing as xmp
141
+
142
+ if len(AcceleratorState._shared_state) > 0:
143
+ raise ValueError(
144
+ "To train on TPU in Colab or Kaggle Kernel, the `Accelerator` should only be initialized inside "
145
+ "your training function. Restart your notebook and make sure no cells initializes an "
146
+ "`Accelerator`."
147
+ )
148
+
149
+ launcher = PrepareForLaunch(function, distributed_type="XLA")
150
+ print("Launching a training on TPU cores.")
151
+ xmp.spawn(launcher, args=args, start_method="fork")
152
+ elif in_colab and get_gpu_info()[1] < 2:
153
+ # No need for a distributed launch otherwise as it's either CPU or one GPU.
154
+ if torch.cuda.is_available():
155
+ print("Launching training on one GPU.")
156
+ else:
157
+ print("Launching training on one CPU.")
158
+ function(*args)
159
+ else:
160
+ if num_processes is None:
161
+ raise ValueError(
162
+ "You have to specify the number of devices you would like to use, add `num_processes=...` to your call."
163
+ )
164
+ if node_rank >= num_nodes:
165
+ raise ValueError("The node_rank must be less than the number of nodes.")
166
+ if num_processes > 1:
167
+ # Multi-device launch
168
+ from torch.distributed.launcher.api import LaunchConfig, elastic_launch
169
+ from torch.multiprocessing import start_processes
170
+ from torch.multiprocessing.spawn import ProcessRaisedException
171
+
172
+ if len(AcceleratorState._shared_state) > 0:
173
+ raise ValueError(
174
+ "To launch a multi-device training from your notebook, the `Accelerator` should only be initialized "
175
+ "inside your training function. Restart your notebook and make sure no cells initializes an "
176
+ "`Accelerator`."
177
+ )
178
+ # Check for specific libraries known to initialize device that users constantly use
179
+ problematic_imports = are_libraries_initialized("bitsandbytes")
180
+ if len(problematic_imports) > 0:
181
+ err = (
182
+ "Could not start distributed process. Libraries known to initialize device upon import have been "
183
+ "imported already. Please keep these imports inside your training function to try and help with this:"
184
+ )
185
+ for lib_name in problematic_imports:
186
+ err += f"\n\t* `{lib_name}`"
187
+ raise RuntimeError(err)
188
+
189
+ patched_env = dict(
190
+ nproc=num_processes,
191
+ node_rank=node_rank,
192
+ world_size=num_nodes * num_processes,
193
+ master_addr=master_addr,
194
+ master_port=use_port,
195
+ mixed_precision=mixed_precision,
196
+ )
197
+
198
+ # Check for CUDA P2P and IB issues
199
+ if not check_cuda_p2p_ib_support():
200
+ patched_env["nccl_p2p_disable"] = "1"
201
+ patched_env["nccl_ib_disable"] = "1"
202
+
203
+ # torch.distributed will expect a few environment variable to be here. We set the ones common to each
204
+ # process here (the other ones will be set be the launcher).
205
+ with patch_environment(**patched_env):
206
+ # First dummy launch
207
+ # Determine device type without initializing any device (which would break fork)
208
+ device_type, distributed_type = get_current_device_type()
209
+ if os.environ.get("ACCELERATE_DEBUG_MODE", "false").lower() == "true":
210
+ launcher = PrepareForLaunch(test_launch, distributed_type=distributed_type)
211
+ try:
212
+ start_processes(launcher, args=(), nprocs=num_processes, start_method="fork")
213
+ except ProcessRaisedException as e:
214
+ err = "An issue was found when verifying a stable environment for the notebook launcher."
215
+ if f"Cannot re-initialize {device_type.upper()} in forked subprocess" in e.args[0]:
216
+ raise RuntimeError(
217
+ f"{err}"
218
+ "This likely stems from an outside import causing issues once the `notebook_launcher()` is called. "
219
+ "Please review your imports and test them when running the `notebook_launcher()` to identify "
220
+ f"which one is problematic and causing {device_type.upper()} to be initialized."
221
+ ) from e
222
+ else:
223
+ raise RuntimeError(f"{err} The following error was raised: {e}") from e
224
+ # Now the actual launch
225
+ launcher = PrepareForLaunch(function, distributed_type=distributed_type)
226
+ print(f"Launching training on {num_processes} {device_type.upper()}s.")
227
+ try:
228
+ if rdzv_conf is None:
229
+ rdzv_conf = {}
230
+ if rdzv_backend == "static":
231
+ rdzv_conf["rank"] = node_rank
232
+ if not rdzv_endpoint:
233
+ rdzv_endpoint = f"{master_addr}:{use_port}"
234
+ launch_config_kwargs = dict(
235
+ min_nodes=num_nodes,
236
+ max_nodes=num_nodes,
237
+ nproc_per_node=num_processes,
238
+ run_id=rdzv_id,
239
+ rdzv_endpoint=rdzv_endpoint,
240
+ rdzv_backend=rdzv_backend,
241
+ rdzv_configs=rdzv_conf,
242
+ max_restarts=max_restarts,
243
+ monitor_interval=monitor_interval,
244
+ start_method="fork",
245
+ )
246
+ if is_torch_version(">=", ELASTIC_LOG_LINE_PREFIX_TEMPLATE_PYTORCH_VERSION):
247
+ launch_config_kwargs["log_line_prefix_template"] = log_line_prefix_template
248
+ elastic_launch(config=LaunchConfig(**launch_config_kwargs), entrypoint=function)(*args)
249
+ except ProcessRaisedException as e:
250
+ if f"Cannot re-initialize {device_type.upper()} in forked subprocess" in e.args[0]:
251
+ raise RuntimeError(
252
+ f"{device_type.upper()} has been initialized before the `notebook_launcher` could create a forked subprocess. "
253
+ "This likely stems from an outside import causing issues once the `notebook_launcher()` is called. "
254
+ "Please review your imports and test them when running the `notebook_launcher()` to identify "
255
+ f"which one is problematic and causing {device_type.upper()} to be initialized."
256
+ ) from e
257
+ else:
258
+ raise RuntimeError(f"An issue was found when launching the training: {e}") from e
259
+
260
+ else:
261
+ # No need for a distributed launch otherwise as it's either CPU, GPU, XPU or MPS.
262
+ if is_mps_available():
263
+ os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
264
+ print("Launching training on MPS.")
265
+ elif torch.cuda.is_available():
266
+ print("Launching training on one GPU.")
267
+ elif torch.xpu.is_available():
268
+ print("Launching training on one XPU.")
269
+ else:
270
+ print("Launching training on CPU.")
271
+ function(*args)
272
+
273
+
274
+ def debug_launcher(function, args=(), num_processes=2):
275
+ """
276
+ Launches a training function using several processes on CPU for debugging purposes.
277
+
278
+ <Tip warning={true}>
279
+
280
+ This function is provided for internal testing and debugging, but it's not intended for real trainings. It will
281
+ only use the CPU.
282
+
283
+ </Tip>
284
+
285
+ Args:
286
+ function (`Callable`):
287
+ The training function to execute.
288
+ args (`Tuple`):
289
+ Tuple of arguments to pass to the function (it will receive `*args`).
290
+ num_processes (`int`, *optional*, defaults to 2):
291
+ The number of processes to use for training.
292
+ """
293
+ from torch.multiprocessing import start_processes
294
+
295
+ with tempfile.NamedTemporaryFile() as tmp_file:
296
+ # torch.distributed will expect a few environment variable to be here. We set the ones common to each
297
+ # process here (the other ones will be set be the launcher).
298
+ with patch_environment(
299
+ world_size=num_processes,
300
+ master_addr="127.0.0.1",
301
+ master_port="29500",
302
+ accelerate_mixed_precision="no",
303
+ accelerate_debug_rdv_file=tmp_file.name,
304
+ accelerate_use_cpu="yes",
305
+ ):
306
+ launcher = PrepareForLaunch(function, debug=True)
307
+ start_processes(launcher, args=args, nprocs=num_processes, start_method="fork")
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/local_sgd.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2023 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import torch
15
+
16
+ from accelerate import Accelerator, DistributedType
17
+
18
+
19
+ class LocalSGD:
20
+ """
21
+ A helper class to support local SGD on top of Accelerator. It simply runs a given number of updates independently
22
+ on each device, and averages model weights every K synchronization step.
23
+
24
+ It should be used only in the multi-GPU (or multi-CPU) setup without extensions such as DeepSpeed. In particular,
25
+ this is a simple implementation that cannot support scenarios such as model parallelism.
26
+
27
+
28
+ Although we are not aware of the true origins of this simple approach, the idea of local SGD is quite old and goes
29
+ back to at least:
30
+
31
+ Zhang, J., De Sa, C., Mitliagkas, I., & Ré, C. (2016). [Parallel SGD: When does averaging help?. arXiv preprint
32
+ arXiv:1606.07365.](https://huggingface.co/papers/1606.07365)
33
+
34
+ We credit the term Local SGD to the following paper (but there might be earlier references we are not aware of).
35
+
36
+ Stich, Sebastian Urban. ["Local SGD Converges Fast and Communicates Little." ICLR 2019-International Conference on
37
+ Learning Representations. No. CONF. 2019.](https://huggingface.co/papers/1805.09767)
38
+
39
+ """
40
+
41
+ def __enter__(self):
42
+ if self.enabled:
43
+ self.model_sync_obj = self.model.no_sync()
44
+ self.model_sync_obj.__enter__()
45
+
46
+ return self
47
+
48
+ def __exit__(self, type, value, tb):
49
+ if self.enabled:
50
+ # Average all models on exit
51
+ self._sync_and_avg_model_params()
52
+ self.model_sync_obj.__exit__(type, value, tb)
53
+
54
+ def __init__(self, accelerator: Accelerator, model: torch.nn.Module, local_sgd_steps: int, enabled: bool = True):
55
+ """
56
+ Constructor.
57
+
58
+ Args:
59
+ model (`torch.nn.Module):
60
+ The model whose parameters we need to average.
61
+ accelerator (`Accelerator`):
62
+ Accelerator object.
63
+ local_sgd_steps (`int`):
64
+ A number of local SGD steps (before model parameters are synchronized).
65
+ enabled (`bool):
66
+ Local SGD is disabled if this parameter set to `False`.
67
+ """
68
+ if accelerator.distributed_type not in [
69
+ DistributedType.NO,
70
+ DistributedType.MULTI_CPU,
71
+ DistributedType.MULTI_GPU,
72
+ DistributedType.MULTI_XPU,
73
+ DistributedType.MULTI_MLU,
74
+ DistributedType.MULTI_HPU,
75
+ DistributedType.MULTI_SDAA,
76
+ DistributedType.MULTI_MUSA,
77
+ DistributedType.MULTI_NPU,
78
+ ]:
79
+ raise NotImplementedError("LocalSGD is supported only for CPUs and GPUs (no DeepSpeed or MegatronLM)")
80
+ self.enabled = enabled and accelerator.distributed_type != DistributedType.NO
81
+ self.num_steps = 0
82
+ if self.enabled:
83
+ self.accelerator = accelerator
84
+ self.model = model
85
+ self.local_sgd_steps = local_sgd_steps
86
+
87
+ def step(self):
88
+ """
89
+ This function makes a "step" and synchronizes model parameters if necessary.
90
+ """
91
+ self.num_steps += 1
92
+ if not self.enabled:
93
+ return
94
+
95
+ if self.num_steps % self.local_sgd_steps == 0:
96
+ self._sync_and_avg_model_params()
97
+
98
+ def _sync_and_avg_model_params(self):
99
+ """
100
+ Synchronize + Average model parameters across all GPUs
101
+ """
102
+
103
+ self.accelerator.wait_for_everyone()
104
+ with self.accelerator.autocast():
105
+ for param in self.model.parameters():
106
+ param.data = self.accelerator.reduce(param.data, reduction="mean")
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/logging.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2022 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import functools
16
+ import logging
17
+ import os
18
+ from typing import Optional
19
+
20
+ from .state import PartialState
21
+
22
+
23
+ class MultiProcessAdapter(logging.LoggerAdapter):
24
+ """
25
+ An adapter to assist with logging in multiprocess.
26
+
27
+ `log` takes in an additional `main_process_only` kwarg, which dictates whether it should be called on all processes
28
+ or only the main executed one. Default is `main_process_only=True`.
29
+
30
+ Does not require an `Accelerator` object to be created first.
31
+ """
32
+
33
+ @staticmethod
34
+ def _should_log(main_process_only):
35
+ "Check if log should be performed"
36
+ state = PartialState()
37
+ return not main_process_only or (main_process_only and state.is_main_process)
38
+
39
+ def log(self, level, msg, *args, **kwargs):
40
+ """
41
+ Delegates logger call after checking if we should log.
42
+
43
+ Accepts a new kwarg of `main_process_only`, which will dictate whether it will be logged across all processes
44
+ or only the main executed one. Default is `True` if not passed
45
+
46
+ Also accepts "in_order", which if `True` makes the processes log one by one, in order. This is much easier to
47
+ read, but comes at the cost of sometimes needing to wait for the other processes. Default is `False` to not
48
+ break with the previous behavior.
49
+
50
+ `in_order` is ignored if `main_process_only` is passed.
51
+ """
52
+ if PartialState._shared_state == {}:
53
+ raise RuntimeError(
54
+ "You must initialize the accelerate state by calling either `PartialState()` or `Accelerator()` before using the logging utility."
55
+ )
56
+ main_process_only = kwargs.pop("main_process_only", True)
57
+ in_order = kwargs.pop("in_order", False)
58
+ # set `stacklevel` to exclude ourself in `Logger.findCaller()` while respecting user's choice
59
+ kwargs.setdefault("stacklevel", 2)
60
+
61
+ if self.isEnabledFor(level):
62
+ if self._should_log(main_process_only):
63
+ msg, kwargs = self.process(msg, kwargs)
64
+ self.logger.log(level, msg, *args, **kwargs)
65
+
66
+ elif in_order:
67
+ state = PartialState()
68
+ for i in range(state.num_processes):
69
+ if i == state.process_index:
70
+ msg, kwargs = self.process(msg, kwargs)
71
+ self.logger.log(level, msg, *args, **kwargs)
72
+ state.wait_for_everyone()
73
+
74
+ @functools.lru_cache(None)
75
+ def warning_once(self, *args, **kwargs):
76
+ """
77
+ This method is identical to `logger.warning()`, but will emit the warning with the same message only once
78
+
79
+ Note: The cache is for the function arguments, so 2 different callers using the same arguments will hit the
80
+ cache. The assumption here is that all warning messages are unique across the code. If they aren't then need to
81
+ switch to another type of cache that includes the caller frame information in the hashing function.
82
+ """
83
+ self.warning(*args, **kwargs)
84
+
85
+
86
+ def get_logger(name: str, log_level: Optional[str] = None):
87
+ """
88
+ Returns a `logging.Logger` for `name` that can handle multiprocessing.
89
+
90
+ If a log should be called on all processes, pass `main_process_only=False` If a log should be called on all
91
+ processes and in order, also pass `in_order=True`
92
+
93
+ Args:
94
+ name (`str`):
95
+ The name for the logger, such as `__file__`
96
+ log_level (`str`, *optional*):
97
+ The log level to use. If not passed, will default to the `LOG_LEVEL` environment variable, or `INFO` if not
98
+
99
+ Example:
100
+
101
+ ```python
102
+ >>> from accelerate.logging import get_logger
103
+ >>> from accelerate import Accelerator
104
+
105
+ >>> logger = get_logger(__name__)
106
+
107
+ >>> accelerator = Accelerator()
108
+ >>> logger.info("My log", main_process_only=False)
109
+ >>> logger.debug("My log", main_process_only=True)
110
+
111
+ >>> logger = get_logger(__name__, log_level="DEBUG")
112
+ >>> logger.info("My log")
113
+ >>> logger.debug("My second log")
114
+
115
+ >>> array = ["a", "b", "c", "d"]
116
+ >>> letter_at_rank = array[accelerator.process_index]
117
+ >>> logger.info(letter_at_rank, in_order=True)
118
+ ```
119
+ """
120
+ if log_level is None:
121
+ log_level = os.environ.get("ACCELERATE_LOG_LEVEL", None)
122
+ logger = logging.getLogger(name)
123
+ if log_level is not None:
124
+ logger.setLevel(log_level.upper())
125
+ logger.root.setLevel(log_level.upper())
126
+ return MultiProcessAdapter(logger, {})
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/memory_utils.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2022 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import warnings
16
+
17
+
18
+ warnings.warn(
19
+ "memory_utils has been reorganized to utils.memory. Import `find_executable_batchsize` from the main `__init__`: "
20
+ "`from accelerate import find_executable_batch_size` to avoid this warning.",
21
+ FutureWarning,
22
+ )
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/optimizer.py ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2021 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import inspect
16
+
17
+ import torch
18
+
19
+ from .state import AcceleratorState, GradientState
20
+ from .utils import DistributedType, honor_type, is_lomo_available, is_torch_xla_available
21
+
22
+
23
+ if is_torch_xla_available():
24
+ import torch_xla.core.xla_model as xm
25
+ import torch_xla.runtime as xr
26
+
27
+
28
+ def move_to_device(state, device):
29
+ if isinstance(state, (list, tuple)):
30
+ return honor_type(state, (move_to_device(t, device) for t in state))
31
+ elif isinstance(state, dict):
32
+ return type(state)({k: move_to_device(v, device) for k, v in state.items()})
33
+ elif isinstance(state, torch.Tensor):
34
+ return state.to(device)
35
+ return state
36
+
37
+
38
+ class AcceleratedOptimizer(torch.optim.Optimizer):
39
+ """
40
+ Internal wrapper around a torch optimizer.
41
+
42
+ Conditionally will perform `step` and `zero_grad` if gradients should be synchronized when performing gradient
43
+ accumulation.
44
+
45
+ Args:
46
+ optimizer (`torch.optim.optimizer.Optimizer`):
47
+ The optimizer to wrap.
48
+ device_placement (`bool`, *optional*, defaults to `True`):
49
+ Whether or not the optimizer should handle device placement. If so, it will place the state dictionary of
50
+ `optimizer` on the right device.
51
+ scaler (`torch.amp.GradScaler` or `torch.cuda.amp.GradScaler`, *optional*):
52
+ The scaler to use in the step function if training with mixed precision.
53
+ """
54
+
55
+ def __init__(self, optimizer, device_placement=True, scaler=None):
56
+ self.optimizer = optimizer
57
+ self.scaler = scaler
58
+ self.accelerator_state = AcceleratorState()
59
+ self.gradient_state = GradientState()
60
+ self.device_placement = device_placement
61
+ self._is_overflow = False
62
+
63
+ if self.scaler is not None:
64
+ self._accelerate_step_called = False
65
+ self._optimizer_original_step_method = self.optimizer.step
66
+ self._optimizer_patched_step_method = patch_optimizer_step(self, self.optimizer.step)
67
+
68
+ # Handle device placement
69
+ if device_placement:
70
+ state_dict = self.optimizer.state_dict()
71
+ if self.accelerator_state.distributed_type == DistributedType.XLA:
72
+ xm.send_cpu_data_to_device(state_dict, self.accelerator_state.device)
73
+ else:
74
+ state_dict = move_to_device(state_dict, self.accelerator_state.device)
75
+ self.optimizer.load_state_dict(state_dict)
76
+
77
+ @property
78
+ def state(self):
79
+ return self.optimizer.state
80
+
81
+ @state.setter
82
+ def state(self, state):
83
+ self.optimizer.state = state
84
+
85
+ @property
86
+ def param_groups(self):
87
+ return self.optimizer.param_groups
88
+
89
+ @param_groups.setter
90
+ def param_groups(self, param_groups):
91
+ self.optimizer.param_groups = param_groups
92
+
93
+ @property
94
+ def defaults(self):
95
+ return self.optimizer.defaults
96
+
97
+ @defaults.setter
98
+ def defaults(self, defaults):
99
+ self.optimizer.defaults = defaults
100
+
101
+ def add_param_group(self, param_group):
102
+ self.optimizer.add_param_group(param_group)
103
+
104
+ def load_state_dict(self, state_dict):
105
+ if self.accelerator_state.distributed_type == DistributedType.XLA and self.device_placement:
106
+ xm.send_cpu_data_to_device(state_dict, self.accelerator_state.device)
107
+ self.optimizer.load_state_dict(state_dict)
108
+
109
+ def state_dict(self):
110
+ return self.optimizer.state_dict()
111
+
112
+ def zero_grad(self, set_to_none=None):
113
+ if self.gradient_state.sync_gradients:
114
+ accept_arg = "set_to_none" in inspect.signature(self.optimizer.zero_grad).parameters
115
+ if accept_arg:
116
+ if set_to_none is None:
117
+ set_to_none = True
118
+ self.optimizer.zero_grad(set_to_none=set_to_none)
119
+ else:
120
+ if set_to_none is not None:
121
+ raise ValueError("`set_to_none` for Optimizer.zero_grad` is not supported by this optimizer.")
122
+ self.optimizer.zero_grad()
123
+
124
+ def train(self):
125
+ """
126
+ Sets the optimizer to "train" mode. Useful for optimizers like `schedule_free`
127
+ """
128
+ if hasattr(self.optimizer, "train") and callable(self.optimizer.train):
129
+ self.optimizer.train()
130
+ elif (
131
+ hasattr(self.optimizer, "optimizer")
132
+ and hasattr(self.optimizer.optimizer, "train")
133
+ and callable(self.optimizer.optimizer.train)
134
+ ):
135
+ # the deepspeed optimizer further wraps the optimizer
136
+ self.optimizer.optimizer.train()
137
+
138
+ def eval(self):
139
+ """
140
+ Sets the optimizer to "eval" mode. Useful for optimizers like `schedule_free`
141
+ """
142
+ if hasattr(self.optimizer, "eval") and callable(self.optimizer.eval):
143
+ self.optimizer.eval()
144
+
145
+ def step(self, closure=None):
146
+ if is_lomo_available():
147
+ from lomo_optim import AdaLomo, Lomo
148
+
149
+ if (
150
+ not self.gradient_state.is_xla_gradients_synced
151
+ and self.accelerator_state.distributed_type == DistributedType.XLA
152
+ ):
153
+ gradients = xm._fetch_gradients(self.optimizer)
154
+ xm.all_reduce("sum", gradients, scale=1.0 / xr.world_size())
155
+ self.gradient_state.is_xla_gradients_synced = True
156
+
157
+ if is_lomo_available():
158
+ # `step` should be a no-op for LOMO optimizers.
159
+ if isinstance(self.optimizer, (Lomo, AdaLomo)):
160
+ return
161
+
162
+ if self.gradient_state.sync_gradients:
163
+ if self.scaler is not None:
164
+ self.optimizer.step = self._optimizer_patched_step_method
165
+
166
+ self.scaler.step(self.optimizer, closure)
167
+ self.scaler.update()
168
+
169
+ if not self._accelerate_step_called:
170
+ # If the optimizer step was skipped, gradient overflow was detected.
171
+ self._is_overflow = True
172
+ else:
173
+ self._is_overflow = False
174
+ # Reset the step method to the original one
175
+ self.optimizer.step = self._optimizer_original_step_method
176
+ # Reset the indicator
177
+ self._accelerate_step_called = False
178
+ else:
179
+ self.optimizer.step(closure)
180
+ if self.accelerator_state.distributed_type == DistributedType.XLA:
181
+ self.gradient_state.is_xla_gradients_synced = False
182
+
183
+ def _switch_parameters(self, parameters_map):
184
+ for param_group in self.optimizer.param_groups:
185
+ param_group["params"] = [parameters_map.get(p, p) for p in param_group["params"]]
186
+
187
+ @property
188
+ def step_was_skipped(self):
189
+ """Whether or not the optimizer step was skipped."""
190
+ return self._is_overflow
191
+
192
+ def __getstate__(self):
193
+ _ignored_keys = [
194
+ "_accelerate_step_called",
195
+ "_optimizer_original_step_method",
196
+ "_optimizer_patched_step_method",
197
+ ]
198
+ return {k: v for k, v in self.__dict__.items() if k not in _ignored_keys}
199
+
200
+ def __setstate__(self, state):
201
+ self.__dict__.update(state)
202
+ if self.scaler is not None:
203
+ self._accelerate_step_called = False
204
+ self._optimizer_original_step_method = self.optimizer.step
205
+ self._optimizer_patched_step_method = patch_optimizer_step(self, self.optimizer.step)
206
+
207
+
208
+ def patch_optimizer_step(accelerated_optimizer: AcceleratedOptimizer, method):
209
+ def patched_step(*args, **kwargs):
210
+ accelerated_optimizer._accelerate_step_called = True
211
+ return method(*args, **kwargs)
212
+
213
+ return patched_step
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/parallelism_config.py ADDED
@@ -0,0 +1,378 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # Copyright 2025 The HuggingFace Inc. team. All rights reserved.
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import os
16
+ import warnings
17
+ from dataclasses import dataclass
18
+ from typing import TYPE_CHECKING, Literal, Optional, Union
19
+
20
+ from accelerate.utils.dataclasses import (
21
+ DeepSpeedSequenceParallelConfig,
22
+ DistributedType,
23
+ TorchContextParallelConfig,
24
+ TorchTensorParallelConfig,
25
+ )
26
+ from accelerate.utils.versions import is_torch_version
27
+
28
+
29
+ if TYPE_CHECKING:
30
+ from accelerate import Accelerator
31
+
32
+
33
+ @dataclass
34
+ class ParallelismConfig:
35
+ """
36
+ A dataclass to configure parallelisms applied to the model. Inspired by torchtitan's `ParallelDims`
37
+ https://github.com/pytorch/torchtitan/blob/main/torchtitan/distributed/parallel_dims.py
38
+
39
+ Args:
40
+ dp_replicate_size (`int`, defaults to `1`):
41
+ The size of the data parallel group. If `dp_replicate_size` is set to 1, the data parallel replication
42
+ group will not be used.
43
+ dp_shard_size (`int`, defaults to `1`):
44
+ The size of the model shard group. If `dp_replicate_size > 1` and `tp_size > 1`, `dp_shard_size` must also
45
+ be greater than 1, as composing DDP + TP is currently not supported.
46
+ tp_size (`int`, defaults to `1`):
47
+ The size of the tensor parallel group. If `tp_size` is set to `1`, the tensor parallel group will not be
48
+ used.
49
+ tp_handler (`~utils.TorchTensorParallelConfig`, defaults to `None`):
50
+ The handler for the tensor parallel group.
51
+ cp_size (`int`, defaults to `1`):
52
+ The size of the context parallel group. Currently not supported, but reserved for future use and enabled
53
+ for downstream libraries.
54
+ cp_backend (`str`, defaults to `torch`):
55
+ Which CP backend to use: `torch` (FSDP2)
56
+ sp_size (`int`, defaults to `1`):
57
+ The size of the sequence parallel group.
58
+ sp_backend (`str`, defaults to `deepspeed`):
59
+ Which SP backend to use:`deepspeed` (ALST/Ulysses)
60
+
61
+ You may obtain different distributed data parallel paradigms by configuring `dp_replicate_size` and `dp_shard_size`
62
+ together:
63
+ - `dp_replicate_size == 1` and `dp_shard_size > 1`, we obtain Fully Sharded Data Parallel (FSDP).
64
+ - `dp_replicate_size > 1` and `dp_shard_size > 1`, we obtain Hybrid Sharded Data Parallel (HSDP).
65
+ - `dp_replicate_size > 1` and `dp_shard_size == 1` is an invalid configuration, to use pure DP, use
66
+ `DistributedDataParallelKwargs` instead.
67
+
68
+ """
69
+
70
+ dp_replicate_size: Optional[int] = None
71
+ dp_shard_size: Optional[int] = None
72
+ tp_size: Optional[int] = None
73
+ cp_size: Optional[int] = None
74
+ cp_backend: Literal["torch"] = None
75
+ sp_size: Optional[int] = None
76
+ sp_backend: Literal["deepspeed"] = None
77
+
78
+ # we use Union because we might support other x parallel plugins (i.e. deepspeed, etc)
79
+ tp_handler: Union[None, TorchTensorParallelConfig] = None
80
+ cp_handler: Union[None, TorchContextParallelConfig] = None
81
+ sp_handler: Union[None, DeepSpeedSequenceParallelConfig] = None
82
+
83
+ device_mesh = None
84
+
85
+ def __repr__(self):
86
+ return (
87
+ "ParallelismConfig(\n "
88
+ f"\tdp_replicate_size={self.dp_replicate_size},\n"
89
+ f"\tdp_shard_size={self.dp_shard_size},\n"
90
+ f"\ttp_size={self.tp_size},\n"
91
+ f"\tcp_size={self.cp_size},\n"
92
+ f"\tcp_backend={self.cp_backend},\n"
93
+ f"\tsp_size={self.sp_size},\n"
94
+ f"\tsp_backend={self.sp_backend},\n"
95
+ f"\ttotal_size={self.total_size}\n"
96
+ f"\ttp_handler={self.tp_handler},\n"
97
+ f"\tcp_handler={self.cp_handler})\n"
98
+ )
99
+
100
+ def to_json(self):
101
+ import copy
102
+
103
+ _non_serializable_fields = ["device_mesh"]
104
+
105
+ copy.deepcopy(
106
+ {
107
+ k: copy.deepcopy(v.__dict__) if hasattr(v, "__dict__") else v
108
+ for k, v in self.__dict__.items()
109
+ if k not in _non_serializable_fields
110
+ }
111
+ )
112
+
113
+ @property
114
+ def dp_dim_names(self):
115
+ """Names of enabled dimensions across which data parallelism is applied."""
116
+ dims = []
117
+ if self.dp_replicate_enabled:
118
+ dims += ["dp_replicate"]
119
+ if self.dp_shard_enabled:
120
+ dims += ["dp_shard"]
121
+ return dims
122
+
123
+ @property
124
+ def non_dp_dim_names(self):
125
+ """Names of enabled dimensions which will receive the same batch (non-data parallel dimensions)."""
126
+ dims = []
127
+ if self.tp_enabled:
128
+ dims += ["tp"]
129
+ if self.cp_enabled:
130
+ dims += ["cp"]
131
+ if self.sp_enabled:
132
+ dims += ["sp"]
133
+ return dims
134
+
135
+ @property
136
+ def dp_shard_cp_dim_names(self):
137
+ """Names of enabled dimensions which will be flattened into a joint mesh across which is model sharded in FSDP."""
138
+ dims = []
139
+ if self.dp_shard_enabled:
140
+ dims += ["dp_shard"]
141
+ if self.cp_enabled:
142
+ dims += ["cp"]
143
+ return dims
144
+
145
+ @property
146
+ def dp_cp_dim_names(self):
147
+ """Names of enabled dimensions across which loss should be averaged"""
148
+ dims = []
149
+ if self.dp_replicate_enabled:
150
+ dims += ["dp_replicate"]
151
+ if self.dp_shard_enabled:
152
+ dims += ["dp_shard"]
153
+ if self.cp_enabled:
154
+ dims += ["cp"]
155
+ return dims
156
+
157
+ @property
158
+ def fsdp_dim_names(self):
159
+ """Names of enabled dimensions across which FSDP is applied, including data parallel replication."""
160
+ dims = []
161
+ if self.dp_replicate_enabled:
162
+ dims += ["dp_replicate"]
163
+ dims += ["dp_shard_cp"]
164
+ return dims
165
+
166
+ @property
167
+ def total_size(self):
168
+ """The total size of the parallelism configuration, which is the product of all sizes."""
169
+ return self.dp_replicate_size * self.dp_shard_size * self.tp_size * self.cp_size * self.sp_size
170
+
171
+ @property
172
+ def non_data_parallel_size(self):
173
+ """The size of the non-data parallel dimensions, which is the product of tensor and context parallel sizes."""
174
+ return self.tp_size * self.cp_size * self.sp_size
175
+
176
+ @property
177
+ def data_parallel_size(self):
178
+ """The size of the data parallel dimensions, which is the product of data parallel replication and"""
179
+ return self.dp_replicate_size * self.dp_shard_size
180
+
181
+ @property
182
+ def dp_replicate_enabled(self):
183
+ """True if data parallel replication is enabled, i.e. `dp_replicate_size > 1`."""
184
+ return self.dp_replicate_size > 1
185
+
186
+ @property
187
+ def dp_shard_enabled(self):
188
+ """True if data parallel sharding is enabled, i.e. `dp_shard_size > 1`."""
189
+ return self.dp_shard_size > 1
190
+
191
+ @property
192
+ def tp_enabled(self):
193
+ """True if tensor parallelism is enabled, i.e. `tp_size > 1`."""
194
+ return self.tp_size > 1
195
+
196
+ @property
197
+ def cp_enabled(self):
198
+ """True if context parallelism is enabled, i.e. `cp_size > 1`."""
199
+ return self.cp_size > 1
200
+
201
+ @property
202
+ def sp_enabled(self):
203
+ """True if context parallelism is enabled, i.e. `sp_size > 1`."""
204
+ return self.sp_size > 1
205
+
206
+ @property
207
+ def active_mesh_dims(self):
208
+ """Names of all active mesh dimensions."""
209
+ return self.dp_dim_names + self.non_dp_dim_names
210
+
211
+ def build_device_mesh(self, device_type: str):
212
+ """Builds a device mesh for the given device type based on the parallelism configuration.
213
+ This method will also create required joint meshes (e.g. `dp_shard_cp`, `dp_cp`, `dp`).
214
+
215
+ Args:
216
+ device_type (`str`): The type of device for which to build the mesh, e
217
+ """
218
+ if is_torch_version(">=", "2.2.0"):
219
+ from torch.distributed.device_mesh import init_device_mesh
220
+ else:
221
+ raise RuntimeError("Building a device_mesh requires to have torch>=2.2.0")
222
+
223
+ mesh = self._get_mesh()
224
+ if len(mesh) == 0:
225
+ return None
226
+ mesh_dim_names, mesh_shape = mesh
227
+ device_mesh = init_device_mesh(
228
+ device_type,
229
+ mesh_shape,
230
+ mesh_dim_names=mesh_dim_names,
231
+ )
232
+ if self.dp_dim_names:
233
+ device_mesh[self.dp_dim_names]._flatten("dp")
234
+ if self.dp_shard_cp_dim_names:
235
+ device_mesh[self.dp_shard_cp_dim_names]._flatten("dp_shard_cp")
236
+ if self.dp_cp_dim_names:
237
+ device_mesh[self.dp_cp_dim_names]._flatten("dp_cp")
238
+
239
+ return device_mesh
240
+
241
+ def get_device_mesh(self, device_type: Optional[str] = None):
242
+ if self.device_mesh is None:
243
+ if device_type is not None:
244
+ self.device_mesh = self.build_device_mesh(device_type)
245
+ else:
246
+ raise ("You need to pass a device_type e.g cuda to build the device mesh")
247
+ else:
248
+ if device_type is not None:
249
+ if self.device_mesh.device_type != device_type:
250
+ raise ValueError(
251
+ f"The device_mesh is already created with device type {self.device_mesh.device_type}. However, you are trying to get a device mesh with device_type {device_type}. Please check if you correctly initialized your device_mesh"
252
+ )
253
+ return self.device_mesh
254
+
255
+ def _get_mesh(self) -> tuple[tuple[int, ...], tuple[str, ...]]:
256
+ """Generate mesh shape and dimension names for torch.distributed.init_device_mesh()."""
257
+
258
+ # Build mesh dimensions dictionary
259
+ mesh_dims = {parallelism: self._sizes[parallelism] for parallelism in self.active_mesh_dims}
260
+
261
+ # Apply canonical ordering
262
+ mesh_order = ["dp_replicate", "dp_shard", "cp", "sp", "tp"]
263
+ sorted_items = sorted(
264
+ mesh_dims.items(),
265
+ key=lambda x: (mesh_order.index(x[0])),
266
+ )
267
+ return tuple(zip(*sorted_items))
268
+
269
+ def __post_init__(self):
270
+ # Basic size validation
271
+ if self.dp_replicate_size is None:
272
+ self.dp_replicate_size = int(os.environ.get("PARALLELISM_CONFIG_DP_REPLICATE_SIZE", "1"))
273
+ if self.dp_shard_size is None:
274
+ self.dp_shard_size = int(os.environ.get("PARALLELISM_CONFIG_DP_SHARD_SIZE", "1"))
275
+ if self.tp_size is None:
276
+ self.tp_size = int(os.environ.get("PARALLELISM_CONFIG_TP_SIZE", "1"))
277
+ if self.cp_size is None:
278
+ self.cp_size = int(os.environ.get("PARALLELISM_CONFIG_CP_SIZE", "1"))
279
+ if self.cp_backend is None:
280
+ self.cp_backend = os.environ.get("PARALLELISM_CONFIG_CP_BACKEND", "torch")
281
+ if self.sp_size is None:
282
+ self.sp_size = int(os.environ.get("PARALLELISM_CONFIG_SP_SIZE", "1"))
283
+ if self.sp_backend is None:
284
+ self.sp_backend = os.environ.get("PARALLELISM_CONFIG_SP_BACKEND", "deepspeed")
285
+
286
+ if self.tp_size > 1:
287
+ if self.tp_handler is None:
288
+ self.tp_handler = TorchTensorParallelConfig()
289
+
290
+ if self.cp_size > 1:
291
+ if self.cp_handler is None:
292
+ self.cp_handler = TorchContextParallelConfig()
293
+ else:
294
+ cp_backends_config_map = dict(
295
+ torch=TorchContextParallelConfig,
296
+ )
297
+ if not isinstance(self.cp_handler, cp_backends_config_map[self.cp_backend]):
298
+ raise ValueError(
299
+ f"ParallelismConfig's cp_backend={self.cp_backend} requires {cp_backends_config_map[self.cp_backend]}, but cp_handler was set to {type(self.cp_handler)}"
300
+ )
301
+
302
+ if self.sp_size > 1:
303
+ if self.sp_handler is None:
304
+ self.sp_handler = DeepSpeedSequenceParallelConfig()
305
+ if self.dp_replicate_size < 1:
306
+ raise ValueError(f"dp_replicate_size must be at least 1, but got {self.dp_replicate_size}")
307
+ if self.dp_shard_size < 1:
308
+ raise ValueError(f"dp_shard_size must be at least 1, but got {self.dp_shard_size}")
309
+ if self.tp_size < 1:
310
+ raise ValueError(f"tp_size must be at least 1, but got {self.tp_size}")
311
+ if self.cp_size < 1:
312
+ raise ValueError(f"cp_size must be at least 1, but got {self.cp_size}")
313
+ valid_cp_backends = ["torch"]
314
+ if self.cp_backend not in valid_cp_backends:
315
+ raise ValueError(f"cp_backend must be one of {valid_cp_backends}, but got {self.cp_backend}")
316
+
317
+ if self.sp_size < 1:
318
+ raise ValueError(f"sp_size must be at least 1, but got {self.sp_size}")
319
+ valid_sp_backends = ["deepspeed"]
320
+ if self.sp_backend not in valid_sp_backends:
321
+ raise ValueError(f"sp_backend must be one of {valid_sp_backends}, but got {self.sp_backend}")
322
+
323
+ if (self.tp_size > 1 or self.cp_size > 1) and self.dp_replicate_size > 1 and self.dp_shard_size == 1:
324
+ raise ValueError(
325
+ "Tensor/Context parallelism (tp/cp_size > 1) cannot be used with pure data parallelism (dp_replicate_size > 1 and dp_shard_size == 1). "
326
+ "Please set dp_shard_size > 1 and dp_replicate_size == 1 to compose FSDP + TP/CP for 2D parallel, "
327
+ "or set dp_replicate_size == 1 and dp_shard_size > 1 to compose HSDP + TP/CP for 3D parallel."
328
+ )
329
+ self._sizes = {
330
+ "dp_replicate": self.dp_replicate_size,
331
+ "dp_shard": self.dp_shard_size,
332
+ "tp": self.tp_size,
333
+ "cp": self.cp_size,
334
+ "sp": self.sp_size,
335
+ }
336
+
337
+ def _set_size(self, parallelism: str, size: int):
338
+ assert parallelism in self._sizes.keys(), f"Parallelism must be one of {self._sizes.keys()}"
339
+ self._sizes[parallelism] = size
340
+ setattr(self, f"{parallelism}_size", size)
341
+
342
+ def _validate_accelerator(self, accelerator: "Accelerator"):
343
+ _warnings = set()
344
+ if not accelerator.multi_device and self.total_size == 1:
345
+ # No distributed setup, valid parallelism config
346
+ return
347
+
348
+ # We need this to ensure DDP works
349
+ if self.total_size == 1:
350
+ self._set_size("dp_replicate", accelerator.num_processes)
351
+
352
+ if self.total_size != accelerator.num_processes:
353
+ raise ValueError(
354
+ f"ParallelismConfig total_size ({self.total_size}) does not match "
355
+ f"num_processes ({accelerator.num_processes}). Please adjust dp_replicate_size/ "
356
+ f"dp_shard_size/tp_size/cp_size/sp_size."
357
+ )
358
+
359
+ if self.total_size > 1 and not (
360
+ accelerator.is_fsdp2
361
+ or accelerator.multi_device
362
+ or accelerator.distributed_type == DistributedType.DEEPSPEED
363
+ ):
364
+ raise ValueError(
365
+ f"ParallelismConfig is only compatible DistributedType.FSDP (version 2) or DistributedType.Multi{{Device}} or DistributedType.DEEPSPEED, but got {accelerator.distributed_type}."
366
+ )
367
+
368
+ for parallelism, size in self._sizes.items():
369
+ if size == 1 and getattr(self, f"{parallelism}_handler", None) is not None:
370
+ _warnings.add(
371
+ f"ParallelismConfig.{parallelism}_handler is set, but {parallelism}_size is set to 1. This handler will be ignored."
372
+ )
373
+
374
+ if _warnings and accelerator.is_main_process:
375
+ warnings.warn(
376
+ "ParallelismConfig has the following warnings:\n" + "\n".join(_warnings),
377
+ UserWarning,
378
+ )
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/scheduler.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2022 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # We ignore warnings about stepping the scheduler since we step it ourselves during gradient accumulation
16
+
17
+ import warnings
18
+
19
+ from .state import AcceleratorState, GradientState
20
+
21
+
22
+ warnings.filterwarnings("ignore", category=UserWarning, module="torch.optim.lr_scheduler")
23
+
24
+
25
+ class AcceleratedScheduler:
26
+ """
27
+ A wrapper around a learning rate scheduler that will only step when the optimizer(s) have a training step. Useful
28
+ to avoid making a scheduler step too fast when gradients went overflow and there was no training step (in mixed
29
+ precision training)
30
+
31
+ When performing gradient accumulation scheduler lengths should not be changed accordingly, Accelerate will always
32
+ step the scheduler to account for it.
33
+
34
+ Args:
35
+ scheduler (`torch.optim.lr_scheduler._LRScheduler`):
36
+ The scheduler to wrap.
37
+ optimizers (one or a list of `torch.optim.Optimizer`):
38
+ The optimizers used.
39
+ step_with_optimizer (`bool`, *optional*, defaults to `True`):
40
+ Whether or not the scheduler should be stepped at each optimizer step.
41
+ split_batches (`bool`, *optional*, defaults to `False`):
42
+ Whether or not the dataloaders split one batch across the different processes (so batch size is the same
43
+ regardless of the number of processes) or create batches on each process (so batch size is the original
44
+ batch size multiplied by the number of processes).
45
+ """
46
+
47
+ def __init__(self, scheduler, optimizers, step_with_optimizer: bool = True, split_batches: bool = False):
48
+ self.scheduler = scheduler
49
+ self.optimizers = optimizers if isinstance(optimizers, (list, tuple)) else [optimizers]
50
+ self.split_batches = split_batches
51
+ self.step_with_optimizer = step_with_optimizer
52
+ self.gradient_state = GradientState()
53
+
54
+ def step(self, *args, **kwargs):
55
+ if not self.step_with_optimizer:
56
+ # No link between scheduler and optimizer -> just step
57
+ self.scheduler.step(*args, **kwargs)
58
+ return
59
+
60
+ # Otherwise, first make sure the optimizer was stepped.
61
+ if not self.gradient_state.sync_gradients:
62
+ if self.gradient_state.adjust_scheduler:
63
+ self.scheduler._step_count += 1
64
+ return
65
+
66
+ for opt in self.optimizers:
67
+ if opt.step_was_skipped:
68
+ return
69
+ if self.split_batches:
70
+ # Split batches -> the training dataloader batch size is not changed so one step per training step
71
+ self.scheduler.step(*args, **kwargs)
72
+ else:
73
+ # Otherwise the training dataloader batch size was multiplied by `num_processes`, so we need to do
74
+ # num_processes steps per training step
75
+ num_processes = AcceleratorState().num_processes
76
+ for _ in range(num_processes):
77
+ # Special case when using OneCycle and `drop_last` was not used
78
+ if hasattr(self.scheduler, "total_steps"):
79
+ if self.scheduler._step_count <= self.scheduler.total_steps:
80
+ self.scheduler.step(*args, **kwargs)
81
+ else:
82
+ self.scheduler.step(*args, **kwargs)
83
+
84
+ # Passthroughs
85
+ def get_last_lr(self):
86
+ return self.scheduler.get_last_lr()
87
+
88
+ def state_dict(self):
89
+ return self.scheduler.state_dict()
90
+
91
+ def load_state_dict(self, state_dict):
92
+ self.scheduler.load_state_dict(state_dict)
93
+
94
+ def get_lr(self):
95
+ return self.scheduler.get_lr()
96
+
97
+ def print_lr(self, *args, **kwargs):
98
+ return self.scheduler.print_lr(*args, **kwargs)
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/state.py ADDED
@@ -0,0 +1,1373 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2021 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ import os
19
+ import threading
20
+ import warnings
21
+ import weakref
22
+ from contextlib import contextmanager
23
+ from functools import partial
24
+ from typing import Any, Callable
25
+
26
+ import torch
27
+
28
+ from .utils import (
29
+ DistributedType,
30
+ DynamoBackend,
31
+ GradientAccumulationPlugin,
32
+ check_cuda_fp8_capability,
33
+ check_cuda_p2p_ib_support,
34
+ deepspeed_required,
35
+ get_cpu_distributed_information,
36
+ get_int_from_env,
37
+ is_ccl_available,
38
+ is_datasets_available,
39
+ is_deepspeed_available,
40
+ is_fp8_available,
41
+ is_habana_gaudi1,
42
+ is_hpu_available,
43
+ is_ipex_available,
44
+ is_mlu_available,
45
+ is_mps_available,
46
+ is_musa_available,
47
+ is_npu_available,
48
+ is_sdaa_available,
49
+ is_torch_xla_available,
50
+ is_xccl_available,
51
+ is_xpu_available,
52
+ parse_choice_from_env,
53
+ parse_flag_from_env,
54
+ set_numa_affinity,
55
+ )
56
+ from .utils.dataclasses import SageMakerDistributedType
57
+
58
+
59
+ if is_torch_xla_available():
60
+ import torch_xla.core.xla_model as xm
61
+ import torch_xla.runtime as xr
62
+
63
+ if is_mlu_available(check_device=False):
64
+ import torch_mlu # noqa: F401
65
+
66
+ if is_sdaa_available(check_device=False):
67
+ import torch_sdaa # noqa: F401
68
+
69
+ if is_musa_available(check_device=False):
70
+ import torch_musa # noqa: F401
71
+
72
+ if is_npu_available(check_device=False):
73
+ import torch_npu # noqa: F401
74
+
75
+
76
+ logger = logging.getLogger(__name__)
77
+
78
+
79
+ def is_initialized() -> bool:
80
+ """
81
+ Checks if the `AcceleratorState` has been initialized from `Accelerator`. Same as `AcceleratorState.initialized`,
82
+ but works as a module method.
83
+ """
84
+ return AcceleratorState._shared_state != {}
85
+
86
+
87
+ # Lambda function that does nothing
88
+ def do_nothing(*args, **kwargs):
89
+ return None
90
+
91
+
92
+ class ThreadLocalSharedDict(threading.local):
93
+ """
94
+ Descriptor that holds a dict shared between instances of a class in the same thread.
95
+
96
+ Note: Descriptors have slightly different semantics than just a dict field on its own.
97
+ `PartialState(...)._shared_state` and `PartialState._shared_state` (instance vs class) give the same value: the
98
+ underlying _storage dict. Likewise, `PartialState(...)._shared_state = {...}` overrides the _storage dict inside
99
+ the descriptor as you would expect. However, `PartialState._shared_state = {}` actually replaces the descriptor
100
+ object with a dict instead Thus, you should modify the _storage dict in-place (e.g. `_shared_state.clear()`).
101
+
102
+ See Python documentation for an explanation of descriptors: https://docs.python.org/3/howto/descriptor.html
103
+
104
+ This is required for using PyTorch/XLA with PJRT in multithreaded mode (required for TPU v2 and v3).
105
+
106
+ See https://github.com/pytorch/xla/blob/r2.0/docs/pjrt.md#multithreading-on-tpu-v2v3
107
+ """
108
+
109
+ def __init__(self, thread_local: bool = False):
110
+ self._storage = {}
111
+
112
+ def __get__(self, obj, objtype=None):
113
+ return self._storage
114
+
115
+ def __set__(self, obj, value):
116
+ self._storage = value
117
+
118
+
119
+ # Prefer global shared dictionary, except when using TPU.
120
+ SharedDict = dict if not is_torch_xla_available() else ThreadLocalSharedDict
121
+
122
+
123
+ # Inspired by Alex Martelli's 'Borg'.
124
+ class PartialState:
125
+ """
126
+ Singleton class that has information about the current training environment and functions to help with process
127
+ control. Designed to be used when only process control and device execution states are needed. Does *not* need to
128
+ be initialized from `Accelerator`.
129
+
130
+ Args:
131
+ cpu (`bool`, *optional*):
132
+ Whether or not to force the script to execute on CPU. Will ignore any accelerators available if set to
133
+ `True` and force the execution on the CPU.
134
+ kwargs (additional keyword arguments, *optional*):
135
+ Additional keyword arguments to pass to the relevant `init_process_group` function. Valid `kwargs` can be
136
+ found in [`utils.InitProcessGroupKwargs`]. See the example section for detailed usage.
137
+
138
+ **Available attributes:**
139
+
140
+ - **device** (`torch.device`) -- The device to use.
141
+ - **distributed_type** ([`~accelerate.state.DistributedType`]) -- The type of distributed environment currently
142
+ in use.
143
+ - **local_process_index** (`int`) -- The index of the current process on the current server.
144
+ - **mixed_precision** (`str`) -- Whether or not the current script will use mixed precision, and if so the type
145
+ of mixed precision being performed. (Choose from 'no','fp16','bf16 or 'fp8').
146
+ - **num_processes** (`int`) -- The number of processes currently launched in parallel.
147
+ - **process_index** (`int`) -- The index of the current process.
148
+ - **is_last_process** (`bool`) -- Whether or not the current process is the last one.
149
+ - **is_main_process** (`bool`) -- Whether or not the current process is the main one.
150
+ - **is_local_main_process** (`bool`) -- Whether or not the current process is the main one on the local node.
151
+ - **debug** (`bool`) -- Whether or not the current script is being run in debug mode.
152
+
153
+ Example:
154
+ ```python
155
+ from accelerate.utils import InitProcessGroupKwargs
156
+
157
+ # To include `InitProcessGroupKwargs`, init then call `.to_kwargs()`
158
+ kwargs = InitProcessGroupKwargs(...).to_kwargs()
159
+ state = PartialState(**kwargs)
160
+ ```
161
+ """
162
+
163
+ _shared_state = SharedDict()
164
+ _known_attrs = [
165
+ "_cpu",
166
+ "_mixed_precision",
167
+ "_shared_state",
168
+ "backend",
169
+ "debug",
170
+ "device",
171
+ "distributed_type",
172
+ "fork_launched",
173
+ "local_process_index",
174
+ "num_processes",
175
+ "process_index",
176
+ ]
177
+
178
+ def __init__(self, cpu: bool = False, **kwargs):
179
+ self.__dict__ = self._shared_state
180
+ if not self.initialized:
181
+ self._cpu = cpu
182
+ self.backend = None
183
+ env_device = os.environ.get("ACCELERATE_TORCH_DEVICE", None)
184
+ self.device = torch.device(env_device) if env_device is not None else None
185
+ self.debug = parse_flag_from_env("ACCELERATE_DEBUG_MODE")
186
+ use_sagemaker_dp = kwargs.pop("_use_sagemaker_dp", None)
187
+ dist_information = None
188
+ if use_sagemaker_dp is None:
189
+ use_sagemaker_dp = (
190
+ os.environ.get("ACCELERATE_USE_SAGEMAKER", "false").lower() == "true"
191
+ and os.environ.get("ACCELERATE_SAGEMAKER_DISTRIBUTED_TYPE") != SageMakerDistributedType.NO
192
+ )
193
+
194
+ # Sets up self.backend + imports
195
+ original_backend = kwargs.pop("backend", None)
196
+ backend, distributed_type = self._prepare_backend(cpu, use_sagemaker_dp, original_backend)
197
+ if original_backend is not None and backend != original_backend:
198
+ raise ValueError(f"Your assigned backend {original_backend} is not available, please use {backend}")
199
+ self.backend = backend
200
+ self.distributed_type = distributed_type
201
+ use_deepspeed = False
202
+ if not cpu and self.backend != "xla":
203
+ if int(os.environ.get("LOCAL_RANK", -1)) != -1:
204
+ # Deal with spawning deepspeed
205
+ if os.environ.get("ACCELERATE_USE_DEEPSPEED", "false").lower() == "true":
206
+ if not is_deepspeed_available():
207
+ raise ImportError(
208
+ "DeepSpeed is not available => install it using `pip3 install deepspeed` or build it from source"
209
+ )
210
+ from deepspeed import comm as dist
211
+
212
+ if not dist.is_initialized():
213
+ if self.backend == "tccl":
214
+ local_rank = os.environ.get("LOCAL_RANK", -1)
215
+ torch.sdaa.set_device(f"sdaa:{local_rank}")
216
+ dist.init_distributed(dist_backend=self.backend, auto_mpi_discovery=False, **kwargs)
217
+ # We need to flag to `use_deepspeed` to be True to override `distributed_type` later
218
+ use_deepspeed = True
219
+ # Deal with all other backends but XPU and CPU, that gets handled special later
220
+ elif (
221
+ self.distributed_type not in (DistributedType.MULTI_XPU, DistributedType.MULTI_CPU)
222
+ and not torch.distributed.is_initialized()
223
+ ):
224
+ if self.backend == "tccl":
225
+ local_rank = os.environ.get("LOCAL_RANK", -1)
226
+ torch.sdaa.set_device(f"sdaa:{local_rank}")
227
+ if (
228
+ self.backend == "nccl"
229
+ and os.environ.get("ACCELERATE_USE_FSDP", "false").lower() == "true"
230
+ and (
231
+ os.environ.get("FSDP_OFFLOAD_PARAMS", "false").lower() == "true"
232
+ or os.environ.get("FSDP_STATE_DICT_TYPE", "SHARDED_STATE_DICT") == "FULL_STATE_DICT"
233
+ )
234
+ ):
235
+ self.backend = "cuda:nccl,cpu:gloo"
236
+ torch.distributed.init_process_group(backend=self.backend, **kwargs)
237
+
238
+ # XPU and CPU require special env configs to be set
239
+ if self.distributed_type in (DistributedType.MULTI_XPU, DistributedType.MULTI_CPU):
240
+ dist_information = get_cpu_distributed_information()
241
+ os.environ["RANK"] = str(dist_information.rank)
242
+ os.environ["WORLD_SIZE"] = str(dist_information.world_size)
243
+ os.environ["LOCAL_RANK"] = str(dist_information.local_rank)
244
+ os.environ["LOCAL_WORLD_SIZE"] = str(dist_information.local_world_size)
245
+ if not os.environ.get("MASTER_PORT", None):
246
+ os.environ["MASTER_PORT"] = "29500"
247
+ if (
248
+ not os.environ.get("MASTER_ADDR", None)
249
+ and dist_information.local_world_size != dist_information.world_size
250
+ and self.backend != "mpi"
251
+ ):
252
+ raise ValueError(
253
+ "Tried to launch on distributed with multinode, but `MASTER_ADDR` env was not set, "
254
+ "please try exporting rank 0's hostname as `MASTER_ADDR`"
255
+ )
256
+ kwargs["rank"] = dist_information.rank
257
+ kwargs["world_size"] = dist_information.world_size
258
+
259
+ if (
260
+ self.distributed_type == DistributedType.MULTI_CPU
261
+ and get_int_from_env(["OMP_NUM_THREADS"], 0) == 0
262
+ ):
263
+ import psutil
264
+
265
+ num_cpu_threads_per_process = int(
266
+ psutil.cpu_count(logical=False) / dist_information.local_world_size
267
+ )
268
+ if num_cpu_threads_per_process == 0:
269
+ num_cpu_threads_per_process = 1
270
+ torch.set_num_threads(num_cpu_threads_per_process)
271
+ warnings.warn(
272
+ f"OMP_NUM_THREADS/MKL_NUM_THREADS unset, we set it at {num_cpu_threads_per_process} to improve oob"
273
+ " performance."
274
+ )
275
+
276
+ if not torch.distributed.is_initialized():
277
+ torch.distributed.init_process_group(backend=self.backend, **kwargs)
278
+
279
+ # No backend == no distributed training
280
+ if self.backend is None:
281
+ self.distributed_type = DistributedType.NO
282
+ self.num_processes = 1
283
+ self.process_index = 0
284
+ self.local_process_index = 0
285
+ elif self.backend == "xla":
286
+ # XLA needs device setting first for `set_replication`
287
+ self.set_device()
288
+ xm.set_replication(self.device, xm.get_xla_supported_devices())
289
+ self.num_processes = xr.world_size()
290
+ self.process_index = xr.global_ordinal()
291
+ if is_torch_xla_available(check_is_tpu=True):
292
+ self.local_process_index = xm.get_local_ordinal()
293
+ else:
294
+ self.local_process_index = int(os.environ.get("LOCAL_RANK", -1))
295
+ else:
296
+ self.num_processes = torch.distributed.get_world_size()
297
+ self.process_index = torch.distributed.get_rank()
298
+ self.local_process_index = (
299
+ int(os.environ.get("LOCAL_RANK", -1)) if dist_information is None else dist_information.local_rank
300
+ )
301
+ self.set_device()
302
+ # Now we can change to deepseed
303
+ if use_deepspeed:
304
+ self.distributed_type = DistributedType.DEEPSPEED
305
+
306
+ # Set CPU affinity if enabled
307
+ if parse_flag_from_env("ACCELERATE_CPU_AFFINITY", False):
308
+ set_numa_affinity(self.local_process_index)
309
+
310
+ # Check for old RTX 4000's that can't use P2P or IB and are on old drivers
311
+ if self.device.type == "cuda" and not check_cuda_p2p_ib_support():
312
+ if "NCCL_P2P_DISABLE" not in os.environ or "NCCL_IB_DISABLE" not in os.environ:
313
+ raise NotImplementedError(
314
+ "Using RTX 4000 series doesn't support faster communication broadband via P2P or IB. "
315
+ 'Please set `NCCL_P2P_DISABLE="1"` and `NCCL_IB_DISABLE="1" or use `accelerate launch` which '
316
+ "will do this automatically."
317
+ )
318
+
319
+ # Important: This should be the *only* code outside of `self.initialized!`
320
+ self.fork_launched = parse_flag_from_env("FORK_LAUNCHED", 0)
321
+
322
+ def __repr__(self) -> str:
323
+ return (
324
+ f"Distributed environment: {self.distributed_type}{(' Backend: ' + self.backend) if self.backend else ''}\n"
325
+ f"Num processes: {self.num_processes}\n"
326
+ f"Process index: {self.process_index}\n"
327
+ f"Local process index: {self.local_process_index}\n"
328
+ f"Device: {self.device}\n"
329
+ )
330
+
331
+ @staticmethod
332
+ def _reset_state():
333
+ "Resets `_shared_state`, is used internally and should not be called"
334
+ PartialState._shared_state.clear()
335
+
336
+ @property
337
+ def initialized(self) -> bool:
338
+ "Returns whether the `PartialState` has been initialized"
339
+ return self._shared_state != {}
340
+
341
+ @property
342
+ def use_distributed(self):
343
+ """
344
+ Whether the Accelerator is configured for distributed training
345
+ """
346
+ return self.distributed_type != DistributedType.NO and self.num_processes > 1
347
+
348
+ @property
349
+ def is_last_process(self) -> bool:
350
+ "Returns whether the current process is the last one"
351
+ return self.process_index == self.num_processes - 1
352
+
353
+ @property
354
+ def is_main_process(self) -> bool:
355
+ "Returns whether the current process is the main process"
356
+ return (
357
+ self.process_index == 0 if self.distributed_type != DistributedType.MEGATRON_LM else self.is_last_process
358
+ )
359
+
360
+ @property
361
+ def is_local_main_process(self) -> bool:
362
+ "Returns whether the current process is the main process on the local node"
363
+ return (
364
+ self.local_process_index == 0
365
+ if self.distributed_type != DistributedType.MEGATRON_LM
366
+ else self.is_last_process
367
+ )
368
+
369
+ def wait_for_everyone(self):
370
+ """
371
+ Will stop the execution of the current process until every other process has reached that point (so this does
372
+ nothing when the script is only run in one process). Useful to do before saving a model.
373
+
374
+ Example:
375
+
376
+ ```python
377
+ >>> # Assuming two GPU processes
378
+ >>> import time
379
+ >>> from accelerate.state import PartialState
380
+
381
+ >>> state = PartialState()
382
+ >>> if state.is_main_process:
383
+ ... time.sleep(2)
384
+ >>> else:
385
+ ... print("I'm waiting for the main process to finish its sleep...")
386
+ >>> state.wait_for_everyone()
387
+ >>> # Should print on every process at the same time
388
+ >>> print("Everyone is here")
389
+ ```
390
+ """
391
+ if self.distributed_type in (
392
+ DistributedType.MULTI_GPU,
393
+ DistributedType.MULTI_MLU,
394
+ DistributedType.MULTI_SDAA,
395
+ DistributedType.MULTI_MUSA,
396
+ DistributedType.MULTI_NPU,
397
+ DistributedType.MULTI_XPU,
398
+ DistributedType.MULTI_CPU,
399
+ DistributedType.MULTI_HPU,
400
+ DistributedType.DEEPSPEED,
401
+ DistributedType.FSDP,
402
+ ):
403
+ torch.distributed.barrier(device_ids=[self.local_process_index])
404
+ elif self.distributed_type == DistributedType.XLA:
405
+ xm.rendezvous("accelerate.utils.wait_for_everyone")
406
+
407
+ def _goes_first(self, is_main: bool):
408
+ if not is_main:
409
+ self.wait_for_everyone()
410
+
411
+ yield
412
+
413
+ if is_main:
414
+ self.wait_for_everyone()
415
+
416
+ @contextmanager
417
+ def split_between_processes(self, inputs: list | tuple | dict | torch.Tensor, apply_padding: bool = False):
418
+ """
419
+ Splits `input` between `self.num_processes` quickly and can be then used on that process. Useful when doing
420
+ distributed inference, such as with different prompts.
421
+
422
+ Note that when using a `dict`, all keys need to have the same number of elements.
423
+
424
+ Args:
425
+ inputs (`list`, `tuple`, `torch.Tensor`, `dict` of `list`/`tuple`/`torch.Tensor`, or `datasets.Dataset`):
426
+ The input to split between processes.
427
+ apply_padding (`bool`, `optional`, defaults to `False`):
428
+ Whether to apply padding by repeating the last element of the input so that all processes have the same
429
+ number of elements. Useful when trying to perform actions such as `gather()` on the outputs or passing
430
+ in less inputs than there are processes. If so, just remember to drop the padded elements afterwards.
431
+
432
+
433
+ Example:
434
+
435
+ ```python
436
+ # Assume there are two processes
437
+ from accelerate import PartialState
438
+
439
+ state = PartialState()
440
+ with state.split_between_processes(["A", "B", "C"]) as inputs:
441
+ print(inputs)
442
+ # Process 0
443
+ ["A", "B"]
444
+ # Process 1
445
+ ["C"]
446
+
447
+ with state.split_between_processes(["A", "B", "C"], apply_padding=True) as inputs:
448
+ print(inputs)
449
+ # Process 0
450
+ ["A", "B"]
451
+ # Process 1
452
+ ["C", "C"]
453
+ ```
454
+ """
455
+ if self.num_processes == 1:
456
+ yield inputs
457
+ return
458
+ length = len(inputs)
459
+ # Nested dictionary of any types
460
+ if isinstance(inputs, dict):
461
+ length = len(inputs[list(inputs.keys())[0]])
462
+ if not all(len(v) == length for v in inputs.values()):
463
+ raise ValueError("All values in the dictionary must have the same length")
464
+ num_samples_per_process, num_extras = divmod(length, self.num_processes)
465
+ start_index = self.process_index * num_samples_per_process + min(self.process_index, num_extras)
466
+ end_index = start_index + num_samples_per_process + (1 if self.process_index < num_extras else 0)
467
+
468
+ def _split_values(inputs, start_index, end_index):
469
+ if isinstance(inputs, (list, tuple, torch.Tensor)):
470
+ if start_index >= len(inputs):
471
+ result = inputs[-1:]
472
+ else:
473
+ result = inputs[start_index:end_index]
474
+ if apply_padding:
475
+ if isinstance(result, torch.Tensor):
476
+ from accelerate.utils import pad_across_processes, send_to_device
477
+
478
+ # The tensor needs to be on the device before we can pad it
479
+ tensorized_result = send_to_device(result, self.device)
480
+ result = pad_across_processes(tensorized_result, pad_index=inputs[-1])
481
+ else:
482
+ result += [result[-1]] * (num_samples_per_process + (1 if num_extras > 0 else 0) - len(result))
483
+ return result
484
+ elif isinstance(inputs, dict):
485
+ for key in inputs.keys():
486
+ inputs[key] = _split_values(inputs[key], start_index, end_index)
487
+ return inputs
488
+ else:
489
+ if is_datasets_available():
490
+ from datasets import Dataset
491
+
492
+ if isinstance(inputs, Dataset):
493
+ if start_index >= len(inputs):
494
+ start_index = len(inputs) - 1
495
+ if end_index > len(inputs):
496
+ end_index = len(inputs)
497
+ result_idcs = list(range(start_index, end_index))
498
+ if apply_padding:
499
+ result_idcs += [end_index - 1] * (
500
+ num_samples_per_process + (1 if num_extras > 0 else 0) - len(result_idcs)
501
+ )
502
+ return inputs.select(result_idcs)
503
+ return inputs
504
+
505
+ yield _split_values(inputs, start_index, end_index)
506
+
507
+ @contextmanager
508
+ def main_process_first(self):
509
+ """
510
+ Lets the main process go first inside a with block.
511
+
512
+ The other processes will enter the with block after the main process exits.
513
+
514
+ Example:
515
+
516
+ ```python
517
+ >>> from accelerate import Accelerator
518
+
519
+ >>> accelerator = Accelerator()
520
+ >>> with accelerator.main_process_first():
521
+ ... # This will be printed first by process 0 then in a seemingly
522
+ ... # random order by the other processes.
523
+ ... print(f"This will be printed by process {accelerator.process_index}")
524
+ ```
525
+ """
526
+ yield from self._goes_first(self.is_main_process)
527
+
528
+ @contextmanager
529
+ def local_main_process_first(self):
530
+ """
531
+ Lets the local main process go inside a with block.
532
+
533
+ The other processes will enter the with block after the main process exits.
534
+
535
+ Example:
536
+
537
+ ```python
538
+ >>> from accelerate.state import PartialState
539
+
540
+ >>> state = PartialState()
541
+ >>> with state.local_main_process_first():
542
+ ... # This will be printed first by local process 0 then in a seemingly
543
+ ... # random order by the other processes.
544
+ ... print(f"This will be printed by process {state.local_process_index}")
545
+ ```
546
+ """
547
+ yield from self._goes_first(self.is_local_main_process)
548
+
549
+ def on_main_process(self, function: Callable[..., Any] | None = None):
550
+ """
551
+ Decorator that only runs the decorated function on the main process.
552
+
553
+ Args:
554
+ function (`Callable`): The function to decorate.
555
+
556
+ Example:
557
+
558
+ ```python
559
+ >>> from accelerate.state import PartialState
560
+
561
+ >>> state = PartialState()
562
+
563
+
564
+ >>> @state.on_main_process
565
+ ... def print_something():
566
+ ... print("This will be printed by process 0 only.")
567
+
568
+
569
+ >>> print_something()
570
+ "This will be printed by process 0 only"
571
+ ```
572
+ """
573
+ if not self.initialized:
574
+ raise ValueError("The `PartialState` or `Accelerator` must be initialized before calling this function.")
575
+ if self.is_main_process or not self.use_distributed:
576
+ return function
577
+ return do_nothing
578
+
579
+ def on_local_main_process(self, function: Callable[..., Any] | None = None):
580
+ """
581
+ Decorator that only runs the decorated function on the local main process.
582
+
583
+ Args:
584
+ function (`Callable`): The function to decorate.
585
+
586
+ Example:
587
+ ```python
588
+ # Assume we have 2 servers with 4 processes each.
589
+ from accelerate.state import PartialState
590
+
591
+ state = PartialState()
592
+
593
+
594
+ @state.on_local_main_process
595
+ def print_something():
596
+ print("This will be printed by process 0 only on each server.")
597
+
598
+
599
+ print_something()
600
+ # On server 1:
601
+ "This will be printed by process 0 only"
602
+ # On server 2:
603
+ "This will be printed by process 0 only"
604
+ ```
605
+ """
606
+ if self.is_local_main_process or not self.use_distributed:
607
+ return function
608
+ return do_nothing
609
+
610
+ def on_last_process(self, function: Callable[..., Any]):
611
+ """
612
+ Decorator that only runs the decorated function on the last process.
613
+
614
+ Args:
615
+ function (`Callable`): The function to decorate.
616
+
617
+ Example:
618
+ ```python
619
+ # Assume we have 4 processes.
620
+ from accelerate.state import PartialState
621
+
622
+ state = PartialState()
623
+
624
+
625
+ @state.on_last_process
626
+ def print_something():
627
+ print(f"Printed on process {state.process_index}")
628
+
629
+
630
+ print_something()
631
+ "Printed on process 3"
632
+ ```
633
+ """
634
+ if self.is_last_process or not self.use_distributed:
635
+ return function
636
+ return do_nothing
637
+
638
+ def on_process(self, function: Callable[..., Any] | None = None, process_index: int | None = None):
639
+ """
640
+ Decorator that only runs the decorated function on the process with the given index.
641
+
642
+ Args:
643
+ function (`Callable`, `optional`):
644
+ The function to decorate.
645
+ process_index (`int`, `optional`):
646
+ The index of the process on which to run the function.
647
+
648
+ Example:
649
+ ```python
650
+ # Assume we have 4 processes.
651
+ from accelerate.state import PartialState
652
+
653
+ state = PartialState()
654
+
655
+
656
+ @state.on_process(process_index=2)
657
+ def print_something():
658
+ print(f"Printed on process {state.process_index}")
659
+
660
+
661
+ print_something()
662
+ "Printed on process 2"
663
+ ```
664
+ """
665
+ if function is None:
666
+ return partial(self.on_process, process_index=process_index)
667
+ if (self.process_index == process_index) or (not self.use_distributed):
668
+ return function
669
+ return do_nothing
670
+
671
+ def on_local_process(self, function: Callable[..., Any] | None = None, local_process_index: int | None = None):
672
+ """
673
+ Decorator that only runs the decorated function on the process with the given index on the current node.
674
+
675
+ Args:
676
+ function (`Callable`, *optional*):
677
+ The function to decorate.
678
+ local_process_index (`int`, *optional*):
679
+ The index of the local process on which to run the function.
680
+
681
+ Example:
682
+ ```python
683
+ # Assume we have 2 servers with 4 processes each.
684
+ from accelerate import Accelerator
685
+
686
+ accelerator = Accelerator()
687
+
688
+
689
+ @accelerator.on_local_process(local_process_index=2)
690
+ def print_something():
691
+ print(f"Printed on process {accelerator.local_process_index}")
692
+
693
+
694
+ print_something()
695
+ # On server 1:
696
+ "Printed on process 2"
697
+ # On server 2:
698
+ "Printed on process 2"
699
+ ```
700
+ """
701
+ if function is None:
702
+ return partial(self.on_local_process, local_process_index=local_process_index)
703
+ if (self.local_process_index == local_process_index) or (not self.use_distributed):
704
+ return function
705
+ return do_nothing
706
+
707
+ def print(self, *args, **kwargs):
708
+ if self.is_local_main_process:
709
+ print(*args, **kwargs)
710
+
711
+ @property
712
+ def default_device(self) -> torch.device:
713
+ """
714
+ Returns the default device which is:
715
+ - MPS if `torch.backends.mps.is_available()` and `torch.backends.mps.is_built()` both return True.
716
+ - CUDA if `torch.cuda.is_available()`
717
+ - MLU if `is_mlu_available()`
718
+ - SDAA if `is_sdaa_available()`
719
+ - MUSA if `is_musa_available()`
720
+ - NPU if `is_npu_available()`
721
+ - HPU if `is_hpu_available()`
722
+ - CPU otherwise
723
+ """
724
+ if is_mps_available():
725
+ os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
726
+ return torch.device("mps")
727
+ elif is_mlu_available():
728
+ return torch.device("mlu")
729
+ elif is_sdaa_available():
730
+ return torch.device("sdaa")
731
+ elif is_musa_available():
732
+ return torch.device("musa")
733
+ # NPU should be checked before CUDA when using `transfer_to_npu`
734
+ # See issue #3020: https://github.com/huggingface/accelerate/issues/3020
735
+ elif is_npu_available():
736
+ return torch.device("npu")
737
+ elif is_hpu_available():
738
+ return torch.device("hpu")
739
+ elif torch.cuda.is_available():
740
+ return torch.device("cuda")
741
+ elif is_xpu_available():
742
+ return torch.device("xpu")
743
+ else:
744
+ return torch.device("cpu")
745
+
746
+ def _prepare_backend(
747
+ self, cpu: bool = False, sagemaker_dp=False, backend: str | None = None
748
+ ) -> tuple[str, DistributedType]:
749
+ "Prepares any imports needed before initializing the distributed backend and sets `self.backend` properly"
750
+ distributed_type = None
751
+ if sagemaker_dp:
752
+ import smdistributed.dataparallel.torch.torch_smddp # noqa
753
+
754
+ backend = "smddp"
755
+ distributed_type = DistributedType.MULTI_GPU
756
+ elif is_torch_xla_available():
757
+ backend = "xla"
758
+ distributed_type = DistributedType.XLA
759
+
760
+ elif int(os.environ.get("LOCAL_RANK", -1)) != -1 and not cpu:
761
+ if is_mlu_available():
762
+ backend = "cncl"
763
+ distributed_type = DistributedType.MULTI_MLU
764
+ if is_sdaa_available():
765
+ backend = "tccl"
766
+ distributed_type = DistributedType.MULTI_SDAA
767
+ elif is_musa_available():
768
+ backend = "mccl"
769
+ distributed_type = DistributedType.MULTI_MUSA
770
+ # NPU should be checked before CUDA when using `transfer_to_npu`
771
+ # See issue #3020: https://github.com/huggingface/accelerate/issues/3020
772
+ elif is_npu_available():
773
+ backend = "hccl"
774
+ distributed_type = DistributedType.MULTI_NPU
775
+ elif is_hpu_available(init_hccl=True):
776
+ if backend is None:
777
+ backend = "hccl"
778
+ distributed_type = DistributedType.MULTI_HPU
779
+ elif torch.cuda.is_available():
780
+ if backend is None:
781
+ backend = "nccl"
782
+ distributed_type = DistributedType.MULTI_GPU
783
+ elif is_xpu_available() and is_xccl_available():
784
+ if backend is None:
785
+ backend = "xccl"
786
+ distributed_type = DistributedType.MULTI_XPU
787
+
788
+ if distributed_type is None and (
789
+ int(os.environ.get("LOCAL_RANK", -1)) != -1
790
+ or get_int_from_env(["PMI_SIZE", "OMPI_COMM_WORLD_SIZE", "MV2_COMM_WORLD_SIZE", "WORLD_SIZE"], 1) > 1
791
+ ):
792
+ if not cpu and is_xpu_available():
793
+ distributed_type = DistributedType.MULTI_XPU
794
+ else:
795
+ distributed_type = DistributedType.MULTI_CPU
796
+
797
+ if (
798
+ backend in (None, "ccl")
799
+ and is_ccl_available()
800
+ and (get_int_from_env(["CCL_WORKER_COUNT"], 0) > 0 or distributed_type == DistributedType.MULTI_XPU)
801
+ ):
802
+ import oneccl_bindings_for_pytorch # noqa: F401
803
+
804
+ backend = "ccl"
805
+ elif backend in (None, "mpi") and torch.distributed.is_mpi_available():
806
+ backend = "mpi"
807
+ else:
808
+ backend = "gloo"
809
+ if distributed_type is None:
810
+ distributed_type = DistributedType.NO
811
+
812
+ return backend, distributed_type
813
+
814
+ def set_device(self):
815
+ """
816
+ Sets the device in `self.device` to the current distributed environment.
817
+ """
818
+ if self.device is not None:
819
+ return
820
+ if self.distributed_type == DistributedType.NO:
821
+ self.device = torch.device("cpu") if self._cpu else self.default_device
822
+ return
823
+ device = str(self.distributed_type).split(".")[-1].replace("MULTI_", "").lower()
824
+ if device not in ("cpu", "gpu", "mlu", "musa", "npu", "xpu", "xla", "hpu", "sdaa"):
825
+ raise ValueError(
826
+ f"Can't set device for {self.distributed_type} ({device}), verify we should be calling `_set_device()` for it!"
827
+ )
828
+ if device == "xla":
829
+ self.device = xm.xla_device()
830
+ elif device == "hpu":
831
+ self.device = torch.device("hpu", torch.hpu.current_device())
832
+ else:
833
+ if device == "gpu":
834
+ device = "cuda"
835
+ device_module = getattr(torch, device)
836
+ device_index = self.local_process_index % device_module.device_count()
837
+ self.device = torch.device(device, device_index)
838
+ device_module.set_device(self.device)
839
+
840
+ def destroy_process_group(self, group=None):
841
+ """
842
+ Destroys the process group. If one is not specified, the default process group is destroyed.
843
+ """
844
+ if self.fork_launched and group is None:
845
+ return
846
+ # needed when using torch.distributed.init_process_group
847
+ if torch.distributed.is_initialized():
848
+ torch.distributed.destroy_process_group(group)
849
+
850
+ def __getattr__(self, name: str):
851
+ # By this point we know that no attributes of `self` contain `name`,
852
+ # so we just modify the error message
853
+ if name in self._known_attrs:
854
+ raise AttributeError(
855
+ f"`PartialState` object has no attribute `{name}`. "
856
+ "This happens if `PartialState._reset_state()` was called and "
857
+ "an `Accelerator` or `PartialState` was not reinitialized."
858
+ )
859
+ # Raise a typical AttributeError
860
+ raise AttributeError(f"'PartialState' object has no attribute '{name}'")
861
+
862
+
863
+ class AcceleratorState:
864
+ """
865
+ Singleton class that has information about the current training environment.
866
+
867
+ **Available attributes:**
868
+
869
+ - **device** (`torch.device`) -- The device to use.
870
+ - **distributed_type** ([`~accelerate.state.DistributedType`]) -- The type of distributed environment currently
871
+ in use.
872
+ - **parallelism_config** ([`~accelerate.utils.ParallelismConfig`]) -- The parallelism configuration for the
873
+ current training environment. This is used to configure the distributed training environment.
874
+ - **initialized** (`bool`) -- Whether or not the `AcceleratorState` has been initialized from `Accelerator`.
875
+ - **local_process_index** (`int`) -- The index of the current process on the current server.
876
+ - **mixed_precision** (`str`) -- Whether or not the current script will use mixed precision, and if so the type
877
+ of mixed precision being performed. (Choose from 'no','fp16','bf16 or 'fp8').
878
+ - **num_processes** (`int`) -- The number of processes currently launched in parallel.
879
+ - **process_index** (`int`) -- The index of the current process.
880
+ - **is_last_process** (`bool`) -- Whether or not the current process is the last one.
881
+ - **is_main_process** (`bool`) -- Whether or not the current process is the main one.
882
+ - **is_local_main_process** (`bool`) -- Whether or not the current process is the main one on the local node.
883
+ - **debug** (`bool`) -- Whether or not the current script is being run in debug mode.
884
+ """
885
+
886
+ _shared_state = SharedDict()
887
+ _known_attrs = PartialState._known_attrs + [
888
+ "deepspeed_plugin",
889
+ "use_ipex",
890
+ "fsdp_plugin",
891
+ "megatron_lm_plugin",
892
+ "dynamo_plugin",
893
+ ]
894
+
895
+ def __init__(
896
+ self,
897
+ mixed_precision: str | None = None,
898
+ cpu: bool = False,
899
+ dynamo_plugin=None,
900
+ deepspeed_plugin=None,
901
+ fsdp_plugin=None,
902
+ torch_tp_plugin=None,
903
+ megatron_lm_plugin=None,
904
+ parallelism_config=None,
905
+ _from_accelerator: bool = False,
906
+ **kwargs,
907
+ ):
908
+ self.__dict__ = self._shared_state
909
+ if parse_flag_from_env("ACCELERATE_USE_CPU"):
910
+ cpu = True
911
+ if PartialState._shared_state == {}:
912
+ PartialState(cpu, **kwargs)
913
+ self.__dict__.update(PartialState._shared_state)
914
+ self._check_initialized(mixed_precision, cpu)
915
+ if not self.initialized:
916
+ self.deepspeed_plugins = None
917
+ self.use_ipex = None
918
+ self.torch_tp_plugin = torch_tp_plugin
919
+ self.parallelism_config = parallelism_config
920
+ self.device_mesh = None
921
+ mixed_precision = (
922
+ parse_choice_from_env("ACCELERATE_MIXED_PRECISION", "no")
923
+ if mixed_precision is None
924
+ else mixed_precision.lower()
925
+ )
926
+ if mixed_precision == "fp8":
927
+ # this is confusing, why is is_fp8_available only checks for library availability ?
928
+ if not is_fp8_available():
929
+ raise ValueError(
930
+ "Using `fp8` precision requires `transformer_engine` or `MS-AMP` to be installed."
931
+ )
932
+ elif torch.cuda.is_available() and not check_cuda_fp8_capability():
933
+ logger.warning(
934
+ f"The current device has compute capability of {torch.cuda.get_device_capability()} which is "
935
+ "insufficient for FP8 mixed precision training (requires a GPU Hopper/Ada Lovelace "
936
+ "or higher, compute capability of 8.9 or higher). Will use FP16 instead."
937
+ )
938
+ mixed_precision = "fp16"
939
+ elif is_habana_gaudi1():
940
+ logger.warning(
941
+ "The current HPU device is Gaudi1 which does not support FP8 mixed precision training (requires "
942
+ "Gaudi2 or higher). Will use BF16 instead."
943
+ )
944
+ mixed_precision = "bf16"
945
+
946
+ self.dynamo_plugin = dynamo_plugin
947
+ if not _from_accelerator:
948
+ raise ValueError(
949
+ "Please make sure to properly initialize your accelerator via `accelerator = Accelerator()` "
950
+ "before using any functionality from the `accelerate` library."
951
+ )
952
+ # deepspeed handles mixed_precision using deepspeed_config. But we need to set it to fp8
953
+ # if we're using fp8.
954
+ if self.distributed_type == DistributedType.DEEPSPEED and mixed_precision != "fp8":
955
+ self._mixed_precision = "no"
956
+ else:
957
+ self._mixed_precision = mixed_precision
958
+
959
+ if self.distributed_type == DistributedType.XLA and is_torch_xla_available(check_is_tpu=True):
960
+ if mixed_precision == "bf16":
961
+ if os.environ.get("ACCELERATE_DOWNCAST_BF16"):
962
+ os.environ["XLA_USE_BF16"] = str(0)
963
+ os.environ["XLA_DOWNCAST_BF16"] = str(1)
964
+ self.downcast_bfloat = True
965
+ else:
966
+ os.environ["XLA_USE_BF16"] = str(1)
967
+ os.environ["XLA_DOWNCAST_BF16"] = str(0)
968
+ self.downcast_bfloat = False
969
+ elif os.environ.get("ACCELERATE_USE_DEEPSPEED", "false").lower() == "true" and not cpu:
970
+ self.distributed_type = DistributedType.DEEPSPEED
971
+ if not isinstance(deepspeed_plugin, dict):
972
+ deepspeed_plugin.set_mixed_precision(mixed_precision)
973
+ deepspeed_plugin.select(_from_accelerator_state=True)
974
+ else:
975
+ for plugin in deepspeed_plugin.values():
976
+ plugin.set_mixed_precision(mixed_precision)
977
+ # The first plugin passed in is always the active one
978
+ first_plugin = next(iter(deepspeed_plugin.values()))
979
+ first_plugin.select(_from_accelerator_state=True)
980
+ self.deepspeed_plugins = deepspeed_plugin
981
+ elif self.distributed_type in [
982
+ DistributedType.MULTI_GPU,
983
+ DistributedType.MULTI_MLU,
984
+ DistributedType.MULTI_SDAA,
985
+ DistributedType.MULTI_MUSA,
986
+ DistributedType.MULTI_NPU,
987
+ DistributedType.MULTI_XPU,
988
+ DistributedType.MULTI_HPU,
989
+ ]:
990
+ # TODO: Siro - remove when axolotl fixes their side
991
+ if not os.environ.get("ACCELERATE_ALLOW_CP_STANDALONE", "false").lower() == "true":
992
+ if self.parallelism_config and self.parallelism_config.cp_enabled and fsdp_plugin is None:
993
+ raise ValueError(
994
+ "`cp_size > 1` specified in the `parallelism_config`, but no `fsdp_plugin` was provided. We need a `fsdp_plugin` to use context parallelism with `cp_backend=torch`, as we also shard the model across the device mesh to save more memory"
995
+ )
996
+ if (
997
+ self.parallelism_config is not None
998
+ and self.parallelism_config.cp_enabled
999
+ and fsdp_plugin.fsdp_version == 1
1000
+ ):
1001
+ raise ValueError(
1002
+ "Using `cp_size>1` requires FSDP2, but the provided `fsdp_plugin` is using FSDP1. "
1003
+ )
1004
+ if (os.environ.get("ACCELERATE_USE_FSDP", "false").lower() == "true" or fsdp_plugin is not None) or (
1005
+ self.parallelism_config is not None and self.parallelism_config.cp_enabled
1006
+ ):
1007
+ self.distributed_type = DistributedType.FSDP
1008
+ if self._mixed_precision != "no" and fsdp_plugin is not None:
1009
+ fsdp_plugin.set_mixed_precision(self._mixed_precision)
1010
+ self.fsdp_plugin = fsdp_plugin
1011
+ if os.environ.get(
1012
+ "ACCELERATE_USE_MEGATRON_LM", "false"
1013
+ ).lower() == "true" and self.distributed_type not in [
1014
+ DistributedType.MULTI_XPU,
1015
+ ]:
1016
+ self.distributed_type = DistributedType.MEGATRON_LM
1017
+ megatron_lm_plugin.set_mixed_precision(self._mixed_precision)
1018
+ self.megatron_lm_plugin = megatron_lm_plugin
1019
+ elif self.distributed_type in [DistributedType.MULTI_CPU, DistributedType.MULTI_XPU, DistributedType.NO]:
1020
+ if is_ipex_available():
1021
+ # check if user disables it explicitly
1022
+ self.use_ipex = parse_flag_from_env("ACCELERATE_USE_IPEX", default=True)
1023
+ else:
1024
+ self.use_ipex = False
1025
+ if (
1026
+ self.dynamo_plugin.backend != DynamoBackend.NO
1027
+ and self._mixed_precision == "no"
1028
+ and self.device.type == "cuda"
1029
+ ):
1030
+ torch.backends.cuda.matmul.allow_tf32 = True
1031
+ if (
1032
+ self.dynamo_plugin.backend != DynamoBackend.NO
1033
+ and self._mixed_precision == "no"
1034
+ and self.device.type == "musa"
1035
+ ):
1036
+ torch.backends.musa.matmul.allow_tf32 = True
1037
+ PartialState._shared_state["distributed_type"] = self.distributed_type
1038
+
1039
+ @property
1040
+ def initialized(self) -> bool:
1041
+ return self._shared_state != PartialState._shared_state
1042
+
1043
+ def __repr__(self):
1044
+ repr = PartialState().__repr__() + f"\nMixed precision type: {self.mixed_precision}\n"
1045
+ if self.distributed_type == DistributedType.DEEPSPEED:
1046
+ repr += f"ds_config: {self.deepspeed_plugin.deepspeed_config}\n"
1047
+ return repr
1048
+
1049
+ def _check_initialized(self, mixed_precision=None, cpu=None):
1050
+ "Checks if a modification is trying to be made and the `AcceleratorState` has already been initialized"
1051
+ if self.initialized:
1052
+ err = "AcceleratorState has already been initialized and cannot be changed, restart your runtime completely and pass `{flag}` to `Accelerator()`."
1053
+ if cpu and self.device.type != "cpu":
1054
+ raise ValueError(err.format(flag="cpu=True"))
1055
+ if (
1056
+ mixed_precision is not None
1057
+ and mixed_precision != self._mixed_precision
1058
+ and self.distributed_type != DistributedType.DEEPSPEED
1059
+ ):
1060
+ raise ValueError(err.format(flag=f"mixed_precision='{mixed_precision}'"))
1061
+
1062
+ @property
1063
+ def mixed_precision(self):
1064
+ if self.distributed_type == DistributedType.DEEPSPEED and self._mixed_precision != "fp8":
1065
+ config = self.deepspeed_plugin.deepspeed_config
1066
+ if config.get("fp16", {}).get("enabled", False):
1067
+ mixed_precision = "fp16"
1068
+ elif config.get("bf16", {}).get("enabled", False):
1069
+ mixed_precision = "bf16"
1070
+ else:
1071
+ mixed_precision = "no"
1072
+ else:
1073
+ mixed_precision = self._mixed_precision
1074
+ return mixed_precision
1075
+
1076
+ @staticmethod
1077
+ def _reset_state(reset_partial_state: bool = False):
1078
+ "Resets `_shared_state`, is used internally and should not be called"
1079
+ AcceleratorState._shared_state.clear()
1080
+ if reset_partial_state:
1081
+ PartialState._reset_state()
1082
+
1083
+ def destroy_process_group(self, group=None):
1084
+ """
1085
+ Destroys the process group. If one is not specified, the default process group is destroyed.
1086
+
1087
+ If `self.fork_launched` is `True` and `group` is `None`, nothing happens.
1088
+ """
1089
+ PartialState().destroy_process_group(group)
1090
+
1091
+ @property
1092
+ def fork_launched(self):
1093
+ return PartialState().fork_launched
1094
+
1095
+ @property
1096
+ def use_distributed(self):
1097
+ """
1098
+ Whether the Accelerator is configured for distributed training
1099
+ """
1100
+ return PartialState().use_distributed
1101
+
1102
+ @property
1103
+ def is_fsdp2(self) -> bool:
1104
+ return self.distributed_type == DistributedType.FSDP and self.fsdp_plugin.fsdp_version == 2
1105
+
1106
+ @property
1107
+ def is_last_process(self) -> bool:
1108
+ "Returns whether the current process is the last one"
1109
+ return PartialState().is_last_process
1110
+
1111
+ @property
1112
+ def is_main_process(self) -> bool:
1113
+ "Returns whether the current process is the main process"
1114
+ return PartialState().is_main_process
1115
+
1116
+ @property
1117
+ def is_local_main_process(self) -> bool:
1118
+ "Returns whether the current process is the main process on the local node"
1119
+ return PartialState().is_local_main_process
1120
+
1121
+ def wait_for_everyone(self):
1122
+ PartialState().wait_for_everyone()
1123
+
1124
+ @contextmanager
1125
+ def split_between_processes(self, inputs: list | tuple | dict | torch.Tensor, apply_padding: bool = False):
1126
+ """
1127
+ Splits `input` between `self.num_processes` quickly and can be then used on that process. Useful when doing
1128
+ distributed inference, such as with different prompts.
1129
+
1130
+ Note that when using a `dict`, all keys need to have the same number of elements.
1131
+
1132
+ Args:
1133
+ inputs (`list`, `tuple`, `torch.Tensor`, or `dict` of `list`/`tuple`/`torch.Tensor`):
1134
+ The input to split between processes.
1135
+ apply_padding (`bool`, `optional`, defaults to `False`):
1136
+ Whether to apply padding by repeating the last element of the input so that all processes have the same
1137
+ number of elements. Useful when trying to perform actions such as `gather()` on the outputs or passing
1138
+ in less inputs than there are processes. If so, just remember to drop the padded elements afterwards.
1139
+
1140
+
1141
+ Example:
1142
+
1143
+ ```python
1144
+ # Assume there are two processes
1145
+ from accelerate.state import AcceleratorState
1146
+
1147
+ state = AcceleratorState()
1148
+ with state.split_between_processes(["A", "B", "C"]) as inputs:
1149
+ print(inputs)
1150
+ # Process 0
1151
+ ["A", "B"]
1152
+ # Process 1
1153
+ ["C"]
1154
+
1155
+ with state.split_between_processes(["A", "B", "C"], apply_padding=True) as inputs:
1156
+ print(inputs)
1157
+ # Process 0
1158
+ ["A", "B"]
1159
+ # Process 1
1160
+ ["C", "C"]
1161
+ ```
1162
+ """
1163
+ with PartialState().split_between_processes(inputs, apply_padding=apply_padding) as inputs:
1164
+ yield inputs
1165
+
1166
+ @contextmanager
1167
+ def main_process_first(self):
1168
+ """
1169
+ Lets the main process go first inside a with block.
1170
+
1171
+ The other processes will enter the with block after the main process exits.
1172
+ """
1173
+ with PartialState().main_process_first():
1174
+ yield
1175
+
1176
+ @contextmanager
1177
+ def local_main_process_first(self):
1178
+ """
1179
+ Lets the local main process go inside a with block.
1180
+
1181
+ The other processes will enter the with block after the main process exits.
1182
+ """
1183
+ with PartialState().local_main_process_first():
1184
+ yield
1185
+
1186
+ @property
1187
+ def deepspeed_plugin(self):
1188
+ """
1189
+ Returns the currently active DeepSpeedPlugin.
1190
+
1191
+ If not using deepspeed, returns `None`.
1192
+ """
1193
+ # To maintain original behavior, return None if not using deepspeed.
1194
+ if self.distributed_type != DistributedType.DEEPSPEED:
1195
+ return None
1196
+ from accelerate.utils.deepspeed import get_active_deepspeed_plugin
1197
+
1198
+ return get_active_deepspeed_plugin(self)
1199
+
1200
+ @deepspeed_required
1201
+ def get_deepspeed_plugin(self, name: str):
1202
+ """
1203
+ Returns the DeepSpeedPlugin with the given plugin_key.
1204
+ """
1205
+ return self.deepspeed_plugins[name]
1206
+
1207
+ @deepspeed_required
1208
+ def select_deepspeed_plugin(self, name: str | None = None):
1209
+ """
1210
+ Activates the DeepSpeedPlugin with the given `name`, and will disable all other plugins.
1211
+ """
1212
+ for key, plugin in self.deepspeed_plugins.items():
1213
+ if key != name:
1214
+ plugin._unselect()
1215
+ self.deepspeed_plugins[name].select(_from_accelerator_state=True)
1216
+
1217
+ def print(self, *args, **kwargs):
1218
+ PartialState().print(*args, **kwargs)
1219
+
1220
+ def __getattr__(self, name: str):
1221
+ # By this point we know that no attributes of `self` contain `name`,
1222
+ # so we just modify the error message
1223
+ if name in self._known_attrs:
1224
+ raise AttributeError(
1225
+ f"`AcceleratorState` object has no attribute `{name}`. "
1226
+ "This happens if `AcceleratorState._reset_state()` was called and "
1227
+ "an `Accelerator` or `PartialState` was not reinitialized."
1228
+ )
1229
+ # Raise a typical AttributeError
1230
+ raise AttributeError(f"'AcceleratorState' object has no attribute '{name}'")
1231
+
1232
+
1233
+ class GradientState:
1234
+ """
1235
+ Singleton class that has information related to gradient synchronization for gradient accumulation
1236
+
1237
+ **Available attributes:**
1238
+
1239
+ - **end_of_dataloader** (`bool`) -- Whether we have reached the end the current dataloader
1240
+ - **remainder** (`int`) -- The number of extra samples that were added from padding the dataloader
1241
+ - **sync_gradients** (`bool`) -- Whether the gradients should be synced across all devices
1242
+ - **active_dataloader** (`Optional[DataLoader]`) -- The dataloader that is currently being iterated over
1243
+ - **dataloader_references** (`List[Optional[DataLoader]]`) -- A list of references to the dataloaders that are
1244
+ being iterated over
1245
+ - **num_steps** (`int`) -- The number of steps to accumulate over
1246
+ - **adjust_scheduler** (`bool`) -- Whether the scheduler should be adjusted to account for the gradient
1247
+ accumulation
1248
+ - **sync_with_dataloader** (`bool`) -- Whether the gradients should be synced at the end of the dataloader
1249
+ iteration and the number of total steps reset
1250
+ - **is_xla_gradients_synced** (`bool`) -- Whether the XLA gradients have been synchronized. It is initialized
1251
+ as false. Once gradients have been reduced before the optimizer step, this flag is set to true. Subsequently,
1252
+ after each step, the flag is reset to false. FSDP will always synchronize the gradients, hence
1253
+ is_xla_gradients_synced is always true.
1254
+ """
1255
+
1256
+ _shared_state = SharedDict()
1257
+
1258
+ def __init__(self, gradient_accumulation_plugin: GradientAccumulationPlugin | None = None):
1259
+ self.__dict__ = self._shared_state
1260
+ if not self.initialized:
1261
+ self.sync_gradients = True
1262
+ self._dataloader_references_ref = [None]
1263
+ self.plugin_kwargs = (
1264
+ gradient_accumulation_plugin.to_kwargs() if gradient_accumulation_plugin is not None else {}
1265
+ )
1266
+ self._is_xla_gradients_synced = False
1267
+
1268
+ # Plugin args are different and can be updated
1269
+ if gradient_accumulation_plugin is not None and self.plugin_kwargs != gradient_accumulation_plugin.to_kwargs():
1270
+ self.plugin_kwargs = gradient_accumulation_plugin.to_kwargs()
1271
+
1272
+ @property
1273
+ def num_steps(self) -> int:
1274
+ "Returns the number of steps to accumulate over"
1275
+ return self.plugin_kwargs.get("num_steps", 1)
1276
+
1277
+ @property
1278
+ def adjust_scheduler(self) -> bool:
1279
+ "Returns whether the scheduler should be adjusted"
1280
+ return self.plugin_kwargs.get("adjust_scheduler", False)
1281
+
1282
+ @property
1283
+ def sync_with_dataloader(self) -> bool:
1284
+ "Returns whether the gradients should be synced at the end of the dataloader iteration and the number of total steps reset"
1285
+ return self.plugin_kwargs.get("sync_with_dataloader", True)
1286
+
1287
+ @property
1288
+ def initialized(self) -> bool:
1289
+ "Returns whether the `GradientState` has been initialized"
1290
+ return GradientState._shared_state != {}
1291
+
1292
+ @property
1293
+ def end_of_dataloader(self) -> bool:
1294
+ "Returns whether we have reached the end of the current dataloader"
1295
+ if not self.in_dataloader:
1296
+ return False
1297
+ return self.active_dataloader.end_of_dataloader
1298
+
1299
+ @property
1300
+ def remainder(self) -> int:
1301
+ "Returns the number of extra samples that were added from padding the dataloader"
1302
+ if not self.in_dataloader:
1303
+ return -1
1304
+ return self.active_dataloader.remainder
1305
+
1306
+ def __repr__(self):
1307
+ return (
1308
+ f"Sync Gradients: {self.sync_gradients}\n"
1309
+ f"At end of current dataloader: {self.end_of_dataloader}\n"
1310
+ f"Extra samples added: {self.remainder}\n"
1311
+ f"Gradient accumulation plugin: {self.plugin_kwargs}\n"
1312
+ )
1313
+
1314
+ @property
1315
+ def is_xla_gradients_synced(self):
1316
+ "Returns the value of is_xla_gradients_synced. FSDP will always synchronize the gradients, hence is_xla_gradients_synced is always true."
1317
+ if parse_flag_from_env("ACCELERATE_USE_FSDP", default=False):
1318
+ return True
1319
+ return self._is_xla_gradients_synced
1320
+
1321
+ @is_xla_gradients_synced.setter
1322
+ def is_xla_gradients_synced(self, is_synced):
1323
+ "Set the _is_xla_gradients_synced attribute."
1324
+ self._is_xla_gradients_synced = is_synced
1325
+
1326
+ def _set_sync_gradients(self, sync_gradients):
1327
+ "Private function that sets whether gradients should be synchronized. Users should not have to call this."
1328
+ self.sync_gradients = sync_gradients
1329
+ # Allow grad-sync to automatically work on TPUs
1330
+ if (
1331
+ self.sync_gradients
1332
+ and is_torch_xla_available(check_is_tpu=True)
1333
+ and PartialState().distributed_type == DistributedType.XLA
1334
+ ):
1335
+ xm.mark_step()
1336
+
1337
+ def _add_dataloader(self, dataloader):
1338
+ "Private function that adds a dataloader to `self.dataloader_references` and sets `in_dataloader` to `True`. Users should not have to call this."
1339
+ # We explicitly use assignment to ensure that the property setter is triggered, which is required for garbage collection.
1340
+ # Avoid using self.dataloader_references.append as it will not trigger the setter.
1341
+ self.dataloader_references += [dataloader]
1342
+
1343
+ def _remove_dataloader(self, dataloader):
1344
+ "Private function that removes a dataloader from `self.dataloader_references` and sets `in_dataloader` to `False` if there are no more dataloaders. Users should not have to call this."
1345
+ # We explicitly use assignment to ensure that the property setter is triggered.
1346
+ self.dataloader_references = [
1347
+ dataloader_ref for dataloader_ref in self.dataloader_references if dataloader_ref != dataloader
1348
+ ]
1349
+
1350
+ @property
1351
+ def active_dataloader(self):
1352
+ return self.dataloader_references[-1]
1353
+
1354
+ @property
1355
+ def dataloader_references(self):
1356
+ # We use a property getter and setter with weakrefs to avoid circular references that prevent garbage collection
1357
+ return [reference() if reference is not None else reference for reference in self._dataloader_references_ref]
1358
+
1359
+ @dataloader_references.setter
1360
+ def dataloader_references(self, references):
1361
+ self._dataloader_references_ref = [
1362
+ weakref.ref(dataloader) if dataloader is not None else dataloader for dataloader in references
1363
+ ]
1364
+
1365
+ @property
1366
+ def in_dataloader(self) -> bool:
1367
+ "Returns whether the current process is in a dataloader"
1368
+ return self.active_dataloader is not None
1369
+
1370
+ @staticmethod
1371
+ def _reset_state():
1372
+ "Resets `_shared_state`, is used internally and should not be called"
1373
+ GradientState._shared_state.clear()
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/accelerate/tracking.py ADDED
@@ -0,0 +1,1326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2022 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # Expectation:
16
+ # Provide a project dir name, then each type of logger gets stored in project/{`logging_dir`}
17
+
18
+ import json
19
+ import os
20
+ import time
21
+ from functools import wraps
22
+ from typing import Any, Optional, Union
23
+
24
+ import yaml
25
+ from packaging import version
26
+
27
+ from .logging import get_logger
28
+ from .state import PartialState
29
+ from .utils import (
30
+ LoggerType,
31
+ compare_versions,
32
+ is_aim_available,
33
+ is_clearml_available,
34
+ is_comet_ml_available,
35
+ is_dvclive_available,
36
+ is_mlflow_available,
37
+ is_swanlab_available,
38
+ is_tensorboard_available,
39
+ is_trackio_available,
40
+ is_wandb_available,
41
+ listify,
42
+ )
43
+
44
+
45
+ _available_trackers = []
46
+
47
+ if is_tensorboard_available():
48
+ _available_trackers.append(LoggerType.TENSORBOARD)
49
+
50
+ if is_wandb_available():
51
+ _available_trackers.append(LoggerType.WANDB)
52
+
53
+ if is_comet_ml_available():
54
+ _available_trackers.append(LoggerType.COMETML)
55
+
56
+ if is_aim_available():
57
+ _available_trackers.append(LoggerType.AIM)
58
+
59
+ if is_mlflow_available():
60
+ _available_trackers.append(LoggerType.MLFLOW)
61
+
62
+ if is_clearml_available():
63
+ _available_trackers.append(LoggerType.CLEARML)
64
+
65
+ if is_dvclive_available():
66
+ _available_trackers.append(LoggerType.DVCLIVE)
67
+
68
+ if is_swanlab_available():
69
+ _available_trackers.append(LoggerType.SWANLAB)
70
+
71
+ if is_trackio_available():
72
+ _available_trackers.append(LoggerType.TRACKIO)
73
+
74
+ logger = get_logger(__name__)
75
+
76
+
77
+ def on_main_process(function):
78
+ """
79
+ Decorator to selectively run the decorated function on the main process only based on the `main_process_only`
80
+ attribute in a class.
81
+
82
+ Checks at function execution rather than initialization time, not triggering the initialization of the
83
+ `PartialState`.
84
+ """
85
+
86
+ @wraps(function)
87
+ def execute_on_main_process(self, *args, **kwargs):
88
+ if getattr(self, "main_process_only", False):
89
+ return PartialState().on_main_process(function)(self, *args, **kwargs)
90
+ else:
91
+ return function(self, *args, **kwargs)
92
+
93
+ return execute_on_main_process
94
+
95
+
96
+ def get_available_trackers():
97
+ "Returns a list of all supported available trackers in the system"
98
+ return _available_trackers
99
+
100
+
101
+ class GeneralTracker:
102
+ """
103
+ A base Tracker class to be used for all logging integration implementations.
104
+
105
+ Each function should take in `**kwargs` that will automatically be passed in from a base dictionary provided to
106
+ [`Accelerator`].
107
+
108
+ Should implement `name`, `requires_logging_directory`, and `tracker` properties such that:
109
+
110
+ `name` (`str`): String representation of the tracker class name, such as "TensorBoard" `requires_logging_directory`
111
+ (`bool`): Whether the logger requires a directory to store their logs. `tracker` (`object`): Should return internal
112
+ tracking mechanism used by a tracker class (such as the `run` for wandb)
113
+
114
+ Implementations can also include a `main_process_only` (`bool`) attribute to toggle if relevant logging, init, and
115
+ other functions should occur on the main process or across all processes (by default will use `True`)
116
+ """
117
+
118
+ main_process_only = True
119
+
120
+ def __init__(self, _blank=False):
121
+ if not _blank:
122
+ err = ""
123
+ if not hasattr(self, "name"):
124
+ err += "`name`"
125
+ if not hasattr(self, "requires_logging_directory"):
126
+ if len(err) > 0:
127
+ err += ", "
128
+ err += "`requires_logging_directory`"
129
+
130
+ # as tracker is a @property that relies on post-init
131
+ if "tracker" not in dir(self):
132
+ if len(err) > 0:
133
+ err += ", "
134
+ err += "`tracker`"
135
+ if len(err) > 0:
136
+ raise NotImplementedError(
137
+ f"The implementation for this tracker class is missing the following "
138
+ f"required attributes. Please define them in the class definition: "
139
+ f"{err}"
140
+ )
141
+
142
+ def start(self):
143
+ """
144
+ Lazy initialization of the tracker inside Accelerator to avoid initializing PartialState before
145
+ InitProcessGroupKwargs.
146
+ """
147
+ pass
148
+
149
+ def store_init_configuration(self, values: dict):
150
+ """
151
+ Logs `values` as hyperparameters for the run. Implementations should use the experiment configuration
152
+ functionality of a tracking API.
153
+
154
+ Args:
155
+ values (Dictionary `str` to `bool`, `str`, `float` or `int`):
156
+ Values to be stored as initial hyperparameters as key-value pairs. The values need to have type `bool`,
157
+ `str`, `float`, `int`, or `None`.
158
+ """
159
+ pass
160
+
161
+ def log(self, values: dict, step: Optional[int], **kwargs):
162
+ """
163
+ Logs `values` to the current run. Base `log` implementations of a tracking API should go in here, along with
164
+ special behavior for the `step parameter.
165
+
166
+ Args:
167
+ values (Dictionary `str` to `str`, `float`, or `int`):
168
+ Values to be logged as key-value pairs. The values need to have type `str`, `float`, or `int`.
169
+ step (`int`, *optional*):
170
+ The run step. If included, the log will be affiliated with this step.
171
+ """
172
+ pass
173
+
174
+ def finish(self):
175
+ """
176
+ Should run any finalizing functions within the tracking API. If the API should not have one, just don't
177
+ overwrite that method.
178
+ """
179
+ pass
180
+
181
+
182
+ class TensorBoardTracker(GeneralTracker):
183
+ """
184
+ A `Tracker` class that supports `tensorboard`. Should be initialized at the start of your script.
185
+
186
+ Args:
187
+ run_name (`str`):
188
+ The name of the experiment run
189
+ logging_dir (`str`, `os.PathLike`):
190
+ Location for TensorBoard logs to be stored.
191
+ **kwargs (additional keyword arguments, *optional*):
192
+ Additional key word arguments passed along to the `tensorboard.SummaryWriter.__init__` method.
193
+ """
194
+
195
+ name = "tensorboard"
196
+ requires_logging_directory = True
197
+
198
+ def __init__(self, run_name: str, logging_dir: Union[str, os.PathLike], **kwargs):
199
+ super().__init__()
200
+ self.run_name = run_name
201
+ self.logging_dir_param = logging_dir
202
+ self.init_kwargs = kwargs
203
+
204
+ @on_main_process
205
+ def start(self):
206
+ try:
207
+ from torch.utils import tensorboard
208
+ except ModuleNotFoundError:
209
+ import tensorboardX as tensorboard
210
+ self.logging_dir = os.path.join(self.logging_dir_param, self.run_name)
211
+ self.writer = tensorboard.SummaryWriter(self.logging_dir, **self.init_kwargs)
212
+ logger.debug(f"Initialized TensorBoard project {self.run_name} logging to {self.logging_dir}")
213
+ logger.debug(
214
+ "Make sure to log any initial configurations with `self.store_init_configuration` before training!"
215
+ )
216
+
217
+ @property
218
+ def tracker(self):
219
+ return self.writer
220
+
221
+ @on_main_process
222
+ def store_init_configuration(self, values: dict):
223
+ """
224
+ Logs `values` as hyperparameters for the run. Should be run at the beginning of your experiment. Stores the
225
+ hyperparameters in a yaml file for future use.
226
+
227
+ Args:
228
+ values (Dictionary `str` to `bool`, `str`, `float` or `int`):
229
+ Values to be stored as initial hyperparameters as key-value pairs. The values need to have type `bool`,
230
+ `str`, `float`, `int`, or `None`.
231
+ """
232
+ self.writer.add_hparams(values, metric_dict={})
233
+ self.writer.flush()
234
+ project_run_name = time.time()
235
+ dir_name = os.path.join(self.logging_dir, str(project_run_name))
236
+ os.makedirs(dir_name, exist_ok=True)
237
+ with open(os.path.join(dir_name, "hparams.yml"), "w") as outfile:
238
+ try:
239
+ yaml.dump(values, outfile)
240
+ except yaml.representer.RepresenterError:
241
+ logger.error("Serialization to store hyperparameters failed")
242
+ raise
243
+ logger.debug("Stored initial configuration hyperparameters to TensorBoard and hparams yaml file")
244
+
245
+ @on_main_process
246
+ def log(self, values: dict, step: Optional[int] = None, **kwargs):
247
+ """
248
+ Logs `values` to the current run.
249
+
250
+ Args:
251
+ values (Dictionary `str` to `str`, `float`, `int` or `dict` of `str` to `float`/`int`):
252
+ Values to be logged as key-value pairs. The values need to have type `str`, `float`, `int` or `dict` of
253
+ `str` to `float`/`int`.
254
+ step (`int`, *optional*):
255
+ The run step. If included, the log will be affiliated with this step.
256
+ kwargs:
257
+ Additional key word arguments passed along to either `SummaryWriter.add_scaler`,
258
+ `SummaryWriter.add_text`, or `SummaryWriter.add_scalers` method based on the contents of `values`.
259
+ """
260
+ values = listify(values)
261
+ for k, v in values.items():
262
+ if isinstance(v, (int, float)):
263
+ self.writer.add_scalar(k, v, global_step=step, **kwargs)
264
+ elif isinstance(v, str):
265
+ self.writer.add_text(k, v, global_step=step, **kwargs)
266
+ elif isinstance(v, dict):
267
+ self.writer.add_scalars(k, v, global_step=step, **kwargs)
268
+ self.writer.flush()
269
+ logger.debug("Successfully logged to TensorBoard")
270
+
271
+ @on_main_process
272
+ def log_images(self, values: dict, step: Optional[int], **kwargs):
273
+ """
274
+ Logs `images` to the current run.
275
+
276
+ Args:
277
+ values (Dictionary `str` to `List` of `np.ndarray` or `PIL.Image`):
278
+ Values to be logged as key-value pairs. The values need to have type `List` of `np.ndarray` or
279
+ step (`int`, *optional*):
280
+ The run step. If included, the log will be affiliated with this step.
281
+ kwargs:
282
+ Additional key word arguments passed along to the `SummaryWriter.add_image` method.
283
+ """
284
+ for k, v in values.items():
285
+ self.writer.add_images(k, v, global_step=step, **kwargs)
286
+ logger.debug("Successfully logged images to TensorBoard")
287
+
288
+ @on_main_process
289
+ def finish(self):
290
+ """
291
+ Closes `TensorBoard` writer
292
+ """
293
+ self.writer.close()
294
+ logger.debug("TensorBoard writer closed")
295
+
296
+
297
+ class WandBTracker(GeneralTracker):
298
+ """
299
+ A `Tracker` class that supports `wandb`. Should be initialized at the start of your script.
300
+
301
+ Args:
302
+ run_name (`str`):
303
+ The name of the experiment run.
304
+ **kwargs (additional keyword arguments, *optional*):
305
+ Additional key word arguments passed along to the `wandb.init` method.
306
+ """
307
+
308
+ name = "wandb"
309
+ requires_logging_directory = False
310
+ main_process_only = False
311
+
312
+ def __init__(self, run_name: str, **kwargs):
313
+ super().__init__()
314
+ self.run_name = run_name
315
+ self.init_kwargs = kwargs
316
+
317
+ @on_main_process
318
+ def start(self):
319
+ import wandb
320
+
321
+ self.run = wandb.init(project=self.run_name, **self.init_kwargs)
322
+ logger.debug(f"Initialized WandB project {self.run_name}")
323
+ logger.debug(
324
+ "Make sure to log any initial configurations with `self.store_init_configuration` before training!"
325
+ )
326
+
327
+ @property
328
+ def tracker(self):
329
+ return self.run
330
+
331
+ @on_main_process
332
+ def store_init_configuration(self, values: dict):
333
+ """
334
+ Logs `values` as hyperparameters for the run. Should be run at the beginning of your experiment.
335
+
336
+ Args:
337
+ values (Dictionary `str` to `bool`, `str`, `float` or `int`):
338
+ Values to be stored as initial hyperparameters as key-value pairs. The values need to have type `bool`,
339
+ `str`, `float`, `int`, or `None`.
340
+ """
341
+ import wandb
342
+
343
+ if os.environ.get("WANDB_MODE") == "offline":
344
+ # In offline mode, restart wandb with config included
345
+ if hasattr(self, "run") and self.run:
346
+ self.run.finish()
347
+
348
+ init_kwargs = self.init_kwargs.copy()
349
+ init_kwargs["config"] = values
350
+ self.run = wandb.init(project=self.run_name, **init_kwargs)
351
+ else:
352
+ wandb.config.update(values, allow_val_change=True)
353
+ logger.debug("Stored initial configuration hyperparameters to WandB")
354
+
355
+ @on_main_process
356
+ def log(self, values: dict, step: Optional[int] = None, **kwargs):
357
+ """
358
+ Logs `values` to the current run.
359
+
360
+ Args:
361
+ values (Dictionary `str` to `str`, `float`, `int` or `dict` of `str` to `float`/`int`):
362
+ Values to be logged as key-value pairs. The values need to have type `str`, `float`, `int` or `dict` of
363
+ `str` to `float`/`int`.
364
+ step (`int`, *optional*):
365
+ The run step. If included, the log will be affiliated with this step.
366
+ kwargs:
367
+ Additional key word arguments passed along to the `wandb.log` method.
368
+ """
369
+ self.run.log(values, step=step, **kwargs)
370
+ logger.debug("Successfully logged to WandB")
371
+
372
+ @on_main_process
373
+ def log_images(self, values: dict, step: Optional[int] = None, **kwargs):
374
+ """
375
+ Logs `images` to the current run.
376
+
377
+ Args:
378
+ values (Dictionary `str` to `List` of `np.ndarray` or `PIL.Image`):
379
+ Values to be logged as key-value pairs. The values need to have type `List` of `np.ndarray` or
380
+ step (`int`, *optional*):
381
+ The run step. If included, the log will be affiliated with this step.
382
+ kwargs:
383
+ Additional key word arguments passed along to the `wandb.log` method.
384
+ """
385
+ import wandb
386
+
387
+ for k, v in values.items():
388
+ self.log({k: [wandb.Image(image) for image in v]}, step=step, **kwargs)
389
+ logger.debug("Successfully logged images to WandB")
390
+
391
+ @on_main_process
392
+ def log_table(
393
+ self,
394
+ table_name: str,
395
+ columns: Optional[list[str]] = None,
396
+ data: Optional[list[list[Any]]] = None,
397
+ dataframe: Any = None,
398
+ step: Optional[int] = None,
399
+ **kwargs,
400
+ ):
401
+ """
402
+ Log a Table containing any object type (text, image, audio, video, molecule, html, etc). Can be defined either
403
+ with `columns` and `data` or with `dataframe`.
404
+
405
+ Args:
406
+ table_name (`str`):
407
+ The name to give to the logged table on the wandb workspace
408
+ columns (list of `str`, *optional*):
409
+ The name of the columns on the table
410
+ data (List of List of Any data type, *optional*):
411
+ The data to be logged in the table
412
+ dataframe (Any data type, *optional*):
413
+ The data to be logged in the table
414
+ step (`int`, *optional*):
415
+ The run step. If included, the log will be affiliated with this step.
416
+ """
417
+ import wandb
418
+
419
+ values = {table_name: wandb.Table(columns=columns, data=data, dataframe=dataframe)}
420
+ self.log(values, step=step, **kwargs)
421
+
422
+ @on_main_process
423
+ def finish(self):
424
+ """
425
+ Closes `wandb` writer
426
+ """
427
+ self.run.finish()
428
+ logger.debug("WandB run closed")
429
+
430
+
431
+ class TrackioTracker(GeneralTracker):
432
+ """
433
+ A `Tracker` class that supports `trackio`. Should be initialized at the start of your script.
434
+
435
+ Args:
436
+ run_name (`str`):
437
+ The name of the experiment run. Will be used as the `project` name when instantiating trackio.
438
+ **kwargs (additional keyword arguments, *optional*):
439
+ Additional key word arguments passed along to the `trackio.init` method. Refer to this
440
+ [init](https://github.com/gradio-app/trackio/blob/814809552310468b13f84f33764f1369b4e5136c/trackio/__init__.py#L22)
441
+ to see all supported key word arguments.
442
+ """
443
+
444
+ name = "trackio"
445
+ requires_logging_directory = False
446
+ main_process_only = False
447
+
448
+ def __init__(self, run_name: str, **kwargs):
449
+ super().__init__()
450
+ self.run_name = run_name
451
+ self.init_kwargs = kwargs
452
+
453
+ @on_main_process
454
+ def start(self):
455
+ import trackio
456
+
457
+ self.run = trackio.init(project=self.run_name, **self.init_kwargs)
458
+ logger.debug(f"Initialized trackio project {self.run_name}")
459
+ logger.debug(
460
+ "Make sure to log any initial configurations with `self.store_init_configuration` before training!"
461
+ )
462
+
463
+ @property
464
+ def tracker(self):
465
+ return self.run
466
+
467
+ @on_main_process
468
+ def store_init_configuration(self, values: dict):
469
+ """
470
+ Logs `values` as hyperparameters for the run. Should be run at the beginning of your experiment.
471
+
472
+ Args:
473
+ values (Dictionary `str` to `bool`, `str`, `float` or `int`):
474
+ Values to be stored as initial hyperparameters as key-value pairs. The values need to have type `bool`,
475
+ `str`, `float`, `int`, or `None`.
476
+ """
477
+ import trackio
478
+
479
+ trackio.config.update(values, allow_val_change=True)
480
+ logger.debug("Stored initial configuration hyperparameters to trackio")
481
+
482
+ @on_main_process
483
+ def log(self, values: dict, step: Optional[int] = None, **kwargs):
484
+ """
485
+ Logs `values` to the current run.
486
+
487
+ Args:
488
+ values (Dictionary `str` to `str`, `float`, `int` or `dict` of `str` to `float`/`int`):
489
+ Values to be logged as key-value pairs. The values need to have type `str`, `float`, `int` or `dict` of
490
+ `str` to `float`/`int`.
491
+ step (`int`, *optional*):
492
+ The run step. If included, the log will be affiliated with this step.
493
+ kwargs:
494
+ Additional key word arguments passed along to the `trackio.log` method.
495
+ """
496
+ self.run.log(values, **kwargs)
497
+ logger.debug("Successfully logged to trackio")
498
+
499
+ @on_main_process
500
+ def finish(self):
501
+ """
502
+ Closes `trackio` run
503
+ """
504
+ self.run.finish()
505
+ logger.debug("trackio run closed")
506
+
507
+
508
+ class CometMLTracker(GeneralTracker):
509
+ """
510
+ A `Tracker` class that supports `comet_ml`. Should be initialized at the start of your script.
511
+
512
+ API keys must be stored in a Comet config file.
513
+
514
+ Note:
515
+ For `comet_ml` versions < 3.41.0, additional keyword arguments are passed to `comet_ml.Experiment` instead:
516
+ https://www.comet.com/docs/v2/api-and-sdk/python-sdk/reference/Experiment/#comet_ml.Experiment.__init__
517
+
518
+ Args:
519
+ run_name (`str`):
520
+ The name of the experiment run.
521
+ **kwargs (additional keyword arguments, *optional*):
522
+ Additional key word arguments passed along to the `comet_ml.start` method:
523
+ https://www.comet.com/docs/v2/api-and-sdk/python-sdk/reference/start/
524
+ """
525
+
526
+ name = "comet_ml"
527
+ requires_logging_directory = False
528
+
529
+ def __init__(self, run_name: str, **kwargs):
530
+ super().__init__()
531
+ self.run_name = run_name
532
+ self.init_kwargs = kwargs
533
+
534
+ @on_main_process
535
+ def start(self):
536
+ import comet_ml
537
+
538
+ comet_version = version.parse(comet_ml.__version__)
539
+ if compare_versions(comet_version, ">=", "3.41.0"):
540
+ self.writer = comet_ml.start(project_name=self.run_name, **self.init_kwargs)
541
+ else:
542
+ logger.info("Update `comet_ml` (>=3.41.0) for experiment reuse and offline support.")
543
+ self.writer = comet_ml.Experiment(project_name=self.run_name, **self.init_kwargs)
544
+
545
+ logger.debug(f"Initialized CometML project {self.run_name}")
546
+ logger.debug(
547
+ "Make sure to log any initial configurations with `self.store_init_configuration` before training!"
548
+ )
549
+
550
+ @property
551
+ def tracker(self):
552
+ return self.writer
553
+
554
+ @on_main_process
555
+ def store_init_configuration(self, values: dict):
556
+ """
557
+ Logs `values` as hyperparameters for the run. Should be run at the beginning of your experiment.
558
+
559
+ Args:
560
+ values (Dictionary `str` to `bool`, `str`, `float` or `int`):
561
+ Values to be stored as initial hyperparameters as key-value pairs. The values need to have type `bool`,
562
+ `str`, `float`, `int`, or `None`.
563
+ """
564
+ self.writer.log_parameters(values)
565
+ logger.debug("Stored initial configuration hyperparameters to Comet")
566
+
567
+ @on_main_process
568
+ def log(self, values: dict, step: Optional[int] = None, **kwargs):
569
+ """
570
+ Logs `values` to the current run.
571
+
572
+ Args:
573
+ values (Dictionary `str` to `str`, `float`, `int` or `dict` of `str` to `float`/`int`):
574
+ Values to be logged as key-value pairs. The values need to have type `str`, `float`, `int` or `dict` of
575
+ `str` to `float`/`int`.
576
+ step (`int`, *optional*):
577
+ The run step. If included, the log will be affiliated with this step.
578
+ kwargs:
579
+ Additional key word arguments passed along to either `Experiment.log_metric`, `Experiment.log_other`,
580
+ or `Experiment.log_metrics` method based on the contents of `values`.
581
+ """
582
+ if step is not None:
583
+ self.writer.set_step(step)
584
+ for k, v in values.items():
585
+ if isinstance(v, (int, float)):
586
+ self.writer.log_metric(k, v, step=step, **kwargs)
587
+ elif isinstance(v, str):
588
+ self.writer.log_other(k, v, **kwargs)
589
+ elif isinstance(v, dict):
590
+ self.writer.log_metrics(v, step=step, **kwargs)
591
+ logger.debug("Successfully logged to Comet")
592
+
593
+ @on_main_process
594
+ def finish(self):
595
+ """
596
+ Flush `comet-ml` writer
597
+ """
598
+ self.writer.end()
599
+ logger.debug("Comet run flushed")
600
+
601
+
602
+ class AimTracker(GeneralTracker):
603
+ """
604
+ A `Tracker` class that supports `aim`. Should be initialized at the start of your script.
605
+
606
+ Args:
607
+ run_name (`str`):
608
+ The name of the experiment run.
609
+ **kwargs (additional keyword arguments, *optional*):
610
+ Additional key word arguments passed along to the `Run.__init__` method.
611
+ """
612
+
613
+ name = "aim"
614
+ requires_logging_directory = True
615
+
616
+ def __init__(self, run_name: str, logging_dir: Optional[Union[str, os.PathLike]] = ".", **kwargs):
617
+ super().__init__()
618
+ self.run_name = run_name
619
+ self.aim_repo_path = logging_dir
620
+ self.init_kwargs = kwargs
621
+
622
+ @on_main_process
623
+ def start(self):
624
+ from aim import Run
625
+
626
+ self.writer = Run(repo=self.aim_repo_path, **self.init_kwargs)
627
+ self.writer.name = self.run_name
628
+ logger.debug(f"Initialized Aim project {self.run_name}")
629
+ logger.debug(
630
+ "Make sure to log any initial configurations with `self.store_init_configuration` before training!"
631
+ )
632
+
633
+ @property
634
+ def tracker(self):
635
+ return self.writer
636
+
637
+ @on_main_process
638
+ def store_init_configuration(self, values: dict):
639
+ """
640
+ Logs `values` as hyperparameters for the run. Should be run at the beginning of your experiment.
641
+
642
+ Args:
643
+ values (`dict`):
644
+ Values to be stored as initial hyperparameters as key-value pairs.
645
+ """
646
+ self.writer["hparams"] = values
647
+
648
+ @on_main_process
649
+ def log(self, values: dict, step: Optional[int], **kwargs):
650
+ """
651
+ Logs `values` to the current run.
652
+
653
+ Args:
654
+ values (`dict`):
655
+ Values to be logged as key-value pairs.
656
+ step (`int`, *optional*):
657
+ The run step. If included, the log will be affiliated with this step.
658
+ kwargs:
659
+ Additional key word arguments passed along to the `Run.track` method.
660
+ """
661
+ # Note: replace this with the dictionary support when merged
662
+ for key, value in values.items():
663
+ self.writer.track(value, name=key, step=step, **kwargs)
664
+
665
+ @on_main_process
666
+ def log_images(self, values: dict, step: Optional[int] = None, kwargs: Optional[dict[str, dict]] = None):
667
+ """
668
+ Logs `images` to the current run.
669
+
670
+ Args:
671
+ values (`Dict[str, Union[np.ndarray, PIL.Image, Tuple[np.ndarray, str], Tuple[PIL.Image, str]]]`):
672
+ Values to be logged as key-value pairs. The values need to have type `np.ndarray` or PIL.Image. If a
673
+ tuple is provided, the first element should be the image and the second element should be the caption.
674
+ step (`int`, *optional*):
675
+ The run step. If included, the log will be affiliated with this step.
676
+ kwargs (`Dict[str, dict]`):
677
+ Additional key word arguments passed along to the `Run.Image` and `Run.track` method specified by the
678
+ keys `aim_image` and `track`, respectively.
679
+ """
680
+ import aim
681
+
682
+ aim_image_kw = {}
683
+ track_kw = {}
684
+
685
+ if kwargs is not None:
686
+ aim_image_kw = kwargs.get("aim_image", {})
687
+ track_kw = kwargs.get("track", {})
688
+
689
+ for key, value in values.items():
690
+ if isinstance(value, tuple):
691
+ img, caption = value
692
+ else:
693
+ img, caption = value, ""
694
+ aim_image = aim.Image(img, caption=caption, **aim_image_kw)
695
+ self.writer.track(aim_image, name=key, step=step, **track_kw)
696
+
697
+ @on_main_process
698
+ def finish(self):
699
+ """
700
+ Closes `aim` writer
701
+ """
702
+ self.writer.close()
703
+
704
+
705
+ class MLflowTracker(GeneralTracker):
706
+ """
707
+ A `Tracker` class that supports `mlflow`. Should be initialized at the start of your script.
708
+
709
+ Args:
710
+ experiment_name (`str`, *optional*):
711
+ Name of the experiment. Environment variable MLFLOW_EXPERIMENT_NAME has priority over this argument.
712
+ logging_dir (`str` or `os.PathLike`, defaults to `"."`):
713
+ Location for mlflow logs to be stored.
714
+ run_id (`str`, *optional*):
715
+ If specified, get the run with the specified UUID and log parameters and metrics under that run. The run’s
716
+ end time is unset and its status is set to running, but the run’s other attributes (source_version,
717
+ source_type, etc.) are not changed. Environment variable MLFLOW_RUN_ID has priority over this argument.
718
+ tags (`Dict[str, str]`, *optional*):
719
+ An optional `dict` of `str` keys and values, or a `str` dump from a `dict`, to set as tags on the run. If a
720
+ run is being resumed, these tags are set on the resumed run. If a new run is being created, these tags are
721
+ set on the new run. Environment variable MLFLOW_TAGS has priority over this argument.
722
+ nested_run (`bool`, *optional*, defaults to `False`):
723
+ Controls whether run is nested in parent run. True creates a nested run. Environment variable
724
+ MLFLOW_NESTED_RUN has priority over this argument.
725
+ run_name (`str`, *optional*):
726
+ Name of new run (stored as a mlflow.runName tag). Used only when `run_id` is unspecified.
727
+ description (`str`, *optional*):
728
+ An optional string that populates the description box of the run. If a run is being resumed, the
729
+ description is set on the resumed run. If a new run is being created, the description is set on the new
730
+ run.
731
+ """
732
+
733
+ name = "mlflow"
734
+ requires_logging_directory = False
735
+
736
+ def __init__(
737
+ self,
738
+ experiment_name: Optional[str] = None,
739
+ logging_dir: Optional[Union[str, os.PathLike]] = None,
740
+ run_id: Optional[str] = None,
741
+ tags: Optional[Union[dict[str, Any], str]] = None,
742
+ nested_run: Optional[bool] = False,
743
+ run_name: Optional[str] = None,
744
+ description: Optional[str] = None,
745
+ ):
746
+ experiment_name = os.environ.get("MLFLOW_EXPERIMENT_NAME", experiment_name)
747
+ run_id = os.environ.get("MLFLOW_RUN_ID", run_id)
748
+ tags = os.environ.get("MLFLOW_TAGS", tags)
749
+ if isinstance(tags, str):
750
+ tags = json.loads(tags)
751
+
752
+ nested_run = os.environ.get("MLFLOW_NESTED_RUN", nested_run)
753
+
754
+ self.experiment_name = experiment_name
755
+ self.logging_dir = logging_dir
756
+ self.run_id = run_id
757
+ self.tags = tags
758
+ self.nested_run = nested_run
759
+ self.run_name = run_name
760
+ self.description = description
761
+
762
+ @on_main_process
763
+ def start(self):
764
+ import mlflow
765
+
766
+ exps = mlflow.search_experiments(filter_string=f"name = '{self.experiment_name}'")
767
+ if len(exps) > 0:
768
+ if len(exps) > 1:
769
+ logger.warning("Multiple experiments with the same name found. Using first one.")
770
+ experiment_id = exps[0].experiment_id
771
+ else:
772
+ experiment_id = mlflow.create_experiment(
773
+ name=self.experiment_name,
774
+ artifact_location=self.logging_dir,
775
+ tags=self.tags,
776
+ )
777
+
778
+ self.active_run = mlflow.start_run(
779
+ run_id=self.run_id,
780
+ experiment_id=experiment_id,
781
+ run_name=self.run_name,
782
+ nested=self.nested_run,
783
+ tags=self.tags,
784
+ description=self.description,
785
+ )
786
+
787
+ logger.debug(f"Initialized mlflow experiment {self.experiment_name}")
788
+ logger.debug(
789
+ "Make sure to log any initial configurations with `self.store_init_configuration` before training!"
790
+ )
791
+
792
+ @property
793
+ def tracker(self):
794
+ return self.active_run
795
+
796
+ @on_main_process
797
+ def store_init_configuration(self, values: dict):
798
+ """
799
+ Logs `values` as hyperparameters for the run. Should be run at the beginning of your experiment.
800
+
801
+ Args:
802
+ values (`dict`):
803
+ Values to be stored as initial hyperparameters as key-value pairs.
804
+ """
805
+ import mlflow
806
+
807
+ for name, value in list(values.items()):
808
+ # internally, all values are converted to str in MLflow
809
+ if len(str(value)) > mlflow.utils.validation.MAX_PARAM_VAL_LENGTH:
810
+ logger.warning_once(
811
+ f'Accelerate is attempting to log a value of "{value}" for key "{name}" as a parameter. MLflow\'s'
812
+ f" log_param() only accepts values no longer than {mlflow.utils.validation.MAX_PARAM_VAL_LENGTH} characters so we dropped this attribute."
813
+ )
814
+ del values[name]
815
+
816
+ values_list = list(values.items())
817
+
818
+ # MLflow cannot log more than 100 values in one go, so we have to split it
819
+ for i in range(0, len(values_list), mlflow.utils.validation.MAX_PARAMS_TAGS_PER_BATCH):
820
+ mlflow.log_params(dict(values_list[i : i + mlflow.utils.validation.MAX_PARAMS_TAGS_PER_BATCH]))
821
+
822
+ logger.debug("Stored initial configuration hyperparameters to MLflow")
823
+
824
+ @on_main_process
825
+ def log(self, values: dict, step: Optional[int]):
826
+ """
827
+ Logs `values` to the current run.
828
+
829
+ Args:
830
+ values (`dict`):
831
+ Values to be logged as key-value pairs.
832
+ step (`int`, *optional*):
833
+ The run step. If included, the log will be affiliated with this step.
834
+ """
835
+ metrics = {}
836
+ for k, v in values.items():
837
+ if isinstance(v, (int, float)):
838
+ metrics[k] = v
839
+ else:
840
+ logger.warning_once(
841
+ f'MLflowTracker is attempting to log a value of "{v}" of type {type(v)} for key "{k}" as a metric. '
842
+ "MLflow's log_metric() only accepts float and int types so we dropped this attribute."
843
+ )
844
+ import mlflow
845
+
846
+ mlflow.log_metrics(metrics, step=step)
847
+ logger.debug("Successfully logged to mlflow")
848
+
849
+ @on_main_process
850
+ def log_figure(self, figure: Any, artifact_file: str, **save_kwargs):
851
+ """
852
+ Logs an figure to the current run.
853
+
854
+ Args:
855
+ figure (Any):
856
+ The figure to be logged.
857
+ artifact_file (`str`, *optional*):
858
+ The run-relative artifact file path in posixpath format to which the image is saved.
859
+ If not provided, the image is saved to a default location.
860
+ **kwargs:
861
+ Additional keyword arguments passed to the underlying mlflow.log_image function.
862
+ """
863
+ import mlflow
864
+
865
+ mlflow.log_figure(figure=figure, artifact_file=artifact_file, **save_kwargs)
866
+ logger.debug("Successfully logged image to mlflow")
867
+
868
+ @on_main_process
869
+ def log_artifacts(self, local_dir: str, artifact_path: Optional[str] = None):
870
+ """
871
+ Logs an artifacts (all content of a dir) to the current run.
872
+
873
+ local_dir (`str`):
874
+ Path to the directory to be logged as an artifact.
875
+ artifact_path (`str`, *optional*):
876
+ Directory within the run's artifact directory where the artifact will be logged. If omitted, the
877
+ artifact will be logged to the root of the run's artifact directory. The run step. If included, the
878
+ artifact will be affiliated with this step.
879
+ """
880
+ import mlflow
881
+
882
+ mlflow.log_artifacts(local_dir=local_dir, artifact_path=artifact_path)
883
+ logger.debug("Successfully logged artofact to mlflow")
884
+
885
+ @on_main_process
886
+ def log_artifact(self, local_path: str, artifact_path: Optional[str] = None):
887
+ """
888
+ Logs an artifact (file) to the current run.
889
+
890
+ local_path (`str`):
891
+ Path to the file to be logged as an artifact.
892
+ artifact_path (`str`, *optional*):
893
+ Directory within the run's artifact directory where the artifact will be logged. If omitted, the
894
+ artifact will be logged to the root of the run's artifact directory. The run step. If included, the
895
+ artifact will be affiliated with this step.
896
+ """
897
+ import mlflow
898
+
899
+ mlflow.log_artifact(local_path=local_path, artifact_path=artifact_path)
900
+ logger.debug("Successfully logged artofact to mlflow")
901
+
902
+ @on_main_process
903
+ def finish(self):
904
+ """
905
+ End the active MLflow run.
906
+ """
907
+ import mlflow
908
+
909
+ mlflow.end_run()
910
+
911
+
912
+ class ClearMLTracker(GeneralTracker):
913
+ """
914
+ A `Tracker` class that supports `clearml`. Should be initialized at the start of your script.
915
+
916
+ Args:
917
+ run_name (`str`, *optional*):
918
+ Name of the experiment. Environment variables `CLEARML_PROJECT` and `CLEARML_TASK` have priority over this
919
+ argument.
920
+ **kwargs (additional keyword arguments, *optional*):
921
+ Kwargs passed along to the `Task.__init__` method.
922
+ """
923
+
924
+ name = "clearml"
925
+ requires_logging_directory = False
926
+
927
+ def __init__(self, run_name: Optional[str] = None, **kwargs):
928
+ super().__init__()
929
+ self.user_provided_run_name = run_name
930
+ self._initialized_externally = False
931
+ self.init_kwargs = kwargs
932
+
933
+ @on_main_process
934
+ def start(self):
935
+ from clearml import Task
936
+
937
+ current_task = Task.current_task()
938
+ if current_task:
939
+ self._initialized_externally = True
940
+ self.task = current_task
941
+ return
942
+
943
+ task_init_args = {**self.init_kwargs}
944
+ task_init_args.setdefault("project_name", os.environ.get("CLEARML_PROJECT", self.user_provided_run_name))
945
+ task_init_args.setdefault("task_name", os.environ.get("CLEARML_TASK", self.user_provided_run_name))
946
+ self.task = Task.init(**task_init_args)
947
+
948
+ @property
949
+ def tracker(self):
950
+ return self.task
951
+
952
+ @on_main_process
953
+ def store_init_configuration(self, values: dict):
954
+ """
955
+ Connect configuration dictionary to the Task object. Should be run at the beginning of your experiment.
956
+
957
+ Args:
958
+ values (`dict`):
959
+ Values to be stored as initial hyperparameters as key-value pairs.
960
+ """
961
+ return self.task.connect_configuration(values)
962
+
963
+ @on_main_process
964
+ def log(self, values: dict[str, Union[int, float]], step: Optional[int] = None, **kwargs):
965
+ """
966
+ Logs `values` dictionary to the current run. The dictionary keys must be strings. The dictionary values must be
967
+ ints or floats
968
+
969
+ Args:
970
+ values (`Dict[str, Union[int, float]]`):
971
+ Values to be logged as key-value pairs. If the key starts with 'eval_'/'test_'/'train_', the value will
972
+ be reported under the 'eval'/'test'/'train' series and the respective prefix will be removed.
973
+ Otherwise, the value will be reported under the 'train' series, and no prefix will be removed.
974
+ step (`int`, *optional*):
975
+ If specified, the values will be reported as scalars, with the iteration number equal to `step`.
976
+ Otherwise they will be reported as single values.
977
+ kwargs:
978
+ Additional key word arguments passed along to the `clearml.Logger.report_single_value` or
979
+ `clearml.Logger.report_scalar` methods.
980
+ """
981
+ clearml_logger = self.task.get_logger()
982
+ for k, v in values.items():
983
+ if not isinstance(v, (int, float)):
984
+ logger.warning_once(
985
+ "Accelerator is attempting to log a value of "
986
+ f'"{v}" of type {type(v)} for key "{k}" as a scalar. '
987
+ "This invocation of ClearML logger's report_scalar() "
988
+ "is incorrect so we dropped this attribute."
989
+ )
990
+ continue
991
+ if step is None:
992
+ clearml_logger.report_single_value(name=k, value=v, **kwargs)
993
+ continue
994
+ title, series = ClearMLTracker._get_title_series(k)
995
+ clearml_logger.report_scalar(title=title, series=series, value=v, iteration=step, **kwargs)
996
+
997
+ @on_main_process
998
+ def log_images(self, values: dict, step: Optional[int] = None, **kwargs):
999
+ """
1000
+ Logs `images` to the current run.
1001
+
1002
+ Args:
1003
+ values (`Dict[str, List[Union[np.ndarray, PIL.Image]]`):
1004
+ Values to be logged as key-value pairs. The values need to have type `List` of `np.ndarray` or
1005
+ step (`int`, *optional*):
1006
+ The run step. If included, the log will be affiliated with this step.
1007
+ kwargs:
1008
+ Additional key word arguments passed along to the `clearml.Logger.report_image` method.
1009
+ """
1010
+ clearml_logger = self.task.get_logger()
1011
+ for k, v in values.items():
1012
+ title, series = ClearMLTracker._get_title_series(k)
1013
+ clearml_logger.report_image(title=title, series=series, iteration=step, image=v, **kwargs)
1014
+
1015
+ @on_main_process
1016
+ def log_table(
1017
+ self,
1018
+ table_name: str,
1019
+ columns: Optional[list[str]] = None,
1020
+ data: Optional[list[list[Any]]] = None,
1021
+ dataframe: Any = None,
1022
+ step: Optional[int] = None,
1023
+ **kwargs,
1024
+ ):
1025
+ """
1026
+ Log a Table to the task. Can be defined eitherwith `columns` and `data` or with `dataframe`.
1027
+
1028
+ Args:
1029
+ table_name (`str`):
1030
+ The name of the table
1031
+ columns (list of `str`, *optional*):
1032
+ The name of the columns on the table
1033
+ data (List of List of Any data type, *optional*):
1034
+ The data to be logged in the table. If `columns` is not specified, then the first entry in data will be
1035
+ the name of the columns of the table
1036
+ dataframe (Any data type, *optional*):
1037
+ The data to be logged in the table
1038
+ step (`int`, *optional*):
1039
+ The run step. If included, the log will be affiliated with this step.
1040
+ kwargs:
1041
+ Additional key word arguments passed along to the `clearml.Logger.report_table` method.
1042
+ """
1043
+ to_report = dataframe
1044
+ if dataframe is None:
1045
+ if data is None:
1046
+ raise ValueError(
1047
+ "`ClearMLTracker.log_table` requires that `data` to be supplied if `dataframe` is `None`"
1048
+ )
1049
+ to_report = [columns] + data if columns else data
1050
+ title, series = ClearMLTracker._get_title_series(table_name)
1051
+ self.task.get_logger().report_table(title=title, series=series, table_plot=to_report, iteration=step, **kwargs)
1052
+
1053
+ @on_main_process
1054
+ def finish(self):
1055
+ """
1056
+ Close the ClearML task. If the task was initialized externally (e.g. by manually calling `Task.init`), this
1057
+ function is a noop
1058
+ """
1059
+ if self.task and not self._initialized_externally:
1060
+ self.task.close()
1061
+
1062
+ @staticmethod
1063
+ def _get_title_series(name):
1064
+ for prefix in ["eval", "test", "train"]:
1065
+ if name.startswith(prefix + "_"):
1066
+ return name[len(prefix) + 1 :], prefix
1067
+ return name, "train"
1068
+
1069
+
1070
+ class DVCLiveTracker(GeneralTracker):
1071
+ """
1072
+ A `Tracker` class that supports `dvclive`. Should be initialized at the start of your script.
1073
+
1074
+ Args:
1075
+ run_name (`str`, *optional*):
1076
+ Ignored for dvclive. See `kwargs` instead.
1077
+ kwargs:
1078
+ Additional key word arguments passed along to [`dvclive.Live()`](https://dvc.org/doc/dvclive/live).
1079
+
1080
+ Example:
1081
+
1082
+ ```py
1083
+ from accelerate import Accelerator
1084
+
1085
+ accelerator = Accelerator(log_with="dvclive")
1086
+ accelerator.init_trackers(project_name="my_project", init_kwargs={"dvclive": {"dir": "my_directory"}})
1087
+ ```
1088
+ """
1089
+
1090
+ name = "dvclive"
1091
+ requires_logging_directory = False
1092
+
1093
+ def __init__(self, run_name: Optional[str] = None, live: Optional[Any] = None, **kwargs):
1094
+ super().__init__()
1095
+ self.live = live
1096
+ self.init_kwargs = kwargs
1097
+
1098
+ @on_main_process
1099
+ def start(self):
1100
+ from dvclive import Live
1101
+
1102
+ self.live = self.live if self.live is not None else Live(**self.init_kwargs)
1103
+
1104
+ @property
1105
+ def tracker(self):
1106
+ return self.live
1107
+
1108
+ @on_main_process
1109
+ def store_init_configuration(self, values: dict):
1110
+ """
1111
+ Logs `values` as hyperparameters for the run. Should be run at the beginning of your experiment. Stores the
1112
+ hyperparameters in a yaml file for future use.
1113
+
1114
+ Args:
1115
+ values (Dictionary `str` to `bool`, `str`, `float`, `int`, or a List or Dict of those types):
1116
+ Values to be stored as initial hyperparameters as key-value pairs. The values need to have type `bool`,
1117
+ `str`, `float`, or `int`.
1118
+ """
1119
+ self.live.log_params(values)
1120
+
1121
+ @on_main_process
1122
+ def log(self, values: dict, step: Optional[int] = None, **kwargs):
1123
+ """
1124
+ Logs `values` to the current run.
1125
+
1126
+ Args:
1127
+ values (Dictionary `str` to `str`, `float`, or `int`):
1128
+ Values to be logged as key-value pairs. The values need to have type `str`, `float`, or `int`.
1129
+ step (`int`, *optional*):
1130
+ The run step. If included, the log will be affiliated with this step.
1131
+ kwargs:
1132
+ Additional key word arguments passed along to `dvclive.Live.log_metric()`.
1133
+ """
1134
+ from dvclive.plots import Metric
1135
+
1136
+ if step is not None:
1137
+ self.live.step = step
1138
+ for k, v in values.items():
1139
+ if Metric.could_log(v):
1140
+ self.live.log_metric(k, v, **kwargs)
1141
+ else:
1142
+ logger.warning_once(
1143
+ "Accelerator attempted to log a value of "
1144
+ f'"{v}" of type {type(v)} for key "{k}" as a scalar. '
1145
+ "This invocation of DVCLive's Live.log_metric() "
1146
+ "is incorrect so we dropped this attribute."
1147
+ )
1148
+ self.live.next_step()
1149
+
1150
+ @on_main_process
1151
+ def finish(self):
1152
+ """
1153
+ Closes `dvclive.Live()`.
1154
+ """
1155
+ self.live.end()
1156
+
1157
+
1158
+ class SwanLabTracker(GeneralTracker):
1159
+ """
1160
+ A `Tracker` class that supports `swanlab`. Should be initialized at the start of your script.
1161
+
1162
+ Args:
1163
+ run_name (`str`):
1164
+ The name of the experiment run.
1165
+ **kwargs (additional keyword arguments, *optional*):
1166
+ Additional key word arguments passed along to the `swanlab.init` method.
1167
+ """
1168
+
1169
+ name = "swanlab"
1170
+ requires_logging_directory = False
1171
+ main_process_only = False
1172
+
1173
+ def __init__(self, run_name: str, **kwargs):
1174
+ super().__init__()
1175
+ self.run_name = run_name
1176
+ self.init_kwargs = kwargs
1177
+
1178
+ @on_main_process
1179
+ def start(self):
1180
+ import swanlab
1181
+
1182
+ self.run = swanlab.init(project=self.run_name, **self.init_kwargs)
1183
+ swanlab.config["FRAMEWORK"] = "🤗Accelerate" # add accelerate logo in config
1184
+ logger.debug(f"Initialized SwanLab project {self.run_name}")
1185
+ logger.debug(
1186
+ "Make sure to log any initial configurations with `self.store_init_configuration` before training!"
1187
+ )
1188
+
1189
+ @property
1190
+ def tracker(self):
1191
+ return self.run
1192
+
1193
+ @on_main_process
1194
+ def store_init_configuration(self, values: dict):
1195
+ """
1196
+ Logs `values` as hyperparameters for the run. Should be run at the beginning of your experiment.
1197
+
1198
+ Args:
1199
+ values (Dictionary `str` to `bool`, `str`, `float` or `int`):
1200
+ Values to be stored as initial hyperparameters as key-value pairs. The values need to have type `bool`,
1201
+ `str`, `float`, `int`, or `None`.
1202
+ """
1203
+ import swanlab
1204
+
1205
+ swanlab.config.update(values, allow_val_change=True)
1206
+ logger.debug("Stored initial configuration hyperparameters to SwanLab")
1207
+
1208
+ @on_main_process
1209
+ def log(self, values: dict, step: Optional[int] = None, **kwargs):
1210
+ """
1211
+ Logs `values` to the current run.
1212
+
1213
+ Args:
1214
+ data : Dict[str, DataType]
1215
+ Data must be a dict. The key must be a string with 0-9, a-z, A-Z, " ", "_", "-", "/". The value must be a
1216
+ `float`, `float convertible object`, `int` or `swanlab.data.BaseType`.
1217
+ step : int, optional
1218
+ The step number of the current data, if not provided, it will be automatically incremented.
1219
+ If step is duplicated, the data will be ignored.
1220
+ kwargs:
1221
+ Additional key word arguments passed along to the `swanlab.log` method. Likes:
1222
+ print_to_console : bool, optional
1223
+ Whether to print the data to the console, the default is False.
1224
+ """
1225
+ self.run.log(values, step=step, **kwargs)
1226
+ logger.debug("Successfully logged to SwanLab")
1227
+
1228
+ @on_main_process
1229
+ def log_images(self, values: dict, step: Optional[int] = None, **kwargs):
1230
+ """
1231
+ Logs `images` to the current run.
1232
+
1233
+ Args:
1234
+ values (Dictionary `str` to `List` of `np.ndarray` or `PIL.Image`):
1235
+ Values to be logged as key-value pairs. The values need to have type `List` of `np.ndarray` or
1236
+ step (`int`, *optional*):
1237
+ The run step. If included, the log will be affiliated with this step.
1238
+ kwargs:
1239
+ Additional key word arguments passed along to the `swanlab.log` method. Likes:
1240
+ print_to_console : bool, optional
1241
+ Whether to print the data to the console, the default is False.
1242
+ """
1243
+ import swanlab
1244
+
1245
+ for k, v in values.items():
1246
+ self.log({k: [swanlab.Image(image) for image in v]}, step=step, **kwargs)
1247
+ logger.debug("Successfully logged images to SwanLab")
1248
+
1249
+ @on_main_process
1250
+ def finish(self):
1251
+ """
1252
+ Closes `swanlab` writer
1253
+ """
1254
+ self.run.finish()
1255
+ logger.debug("SwanLab run closed")
1256
+
1257
+
1258
+ LOGGER_TYPE_TO_CLASS = {
1259
+ "aim": AimTracker,
1260
+ "comet_ml": CometMLTracker,
1261
+ "mlflow": MLflowTracker,
1262
+ "tensorboard": TensorBoardTracker,
1263
+ "wandb": WandBTracker,
1264
+ "clearml": ClearMLTracker,
1265
+ "dvclive": DVCLiveTracker,
1266
+ "swanlab": SwanLabTracker,
1267
+ "trackio": TrackioTracker,
1268
+ }
1269
+
1270
+
1271
+ def filter_trackers(
1272
+ log_with: list[Union[str, LoggerType, GeneralTracker]],
1273
+ logging_dir: Optional[Union[str, os.PathLike]] = None,
1274
+ ):
1275
+ """
1276
+ Takes in a list of potential tracker types and checks that:
1277
+ - The tracker wanted is available in that environment
1278
+ - Filters out repeats of tracker types
1279
+ - If `all` is in `log_with`, will return all trackers in the environment
1280
+ - If a tracker requires a `logging_dir`, ensures that `logging_dir` is not `None`
1281
+
1282
+ Args:
1283
+ log_with (list of `str`, [`~utils.LoggerType`] or [`~tracking.GeneralTracker`], *optional*):
1284
+ A list of loggers to be setup for experiment tracking. Should be one or several of:
1285
+
1286
+ - `"all"`
1287
+ - `"tensorboard"`
1288
+ - `"wandb"`
1289
+ - `"trackio"`
1290
+ - `"aim"`
1291
+ - `"comet_ml"`
1292
+ - `"mlflow"`
1293
+ - `"dvclive"`
1294
+ - `"swanlab"`
1295
+ If `"all"` is selected, will pick up all available trackers in the environment and initialize them. Can
1296
+ also accept implementations of `GeneralTracker` for custom trackers, and can be combined with `"all"`.
1297
+ logging_dir (`str`, `os.PathLike`, *optional*):
1298
+ A path to a directory for storing logs of locally-compatible loggers.
1299
+ """
1300
+ loggers = []
1301
+ if log_with is not None:
1302
+ if not isinstance(log_with, (list, tuple)):
1303
+ log_with = [log_with]
1304
+ if "all" in log_with or LoggerType.ALL in log_with:
1305
+ loggers = [o for o in log_with if issubclass(type(o), GeneralTracker)] + get_available_trackers()
1306
+ else:
1307
+ for log_type in log_with:
1308
+ if log_type not in LoggerType and not issubclass(type(log_type), GeneralTracker):
1309
+ raise ValueError(f"Unsupported logging capability: {log_type}. Choose between {LoggerType.list()}")
1310
+ if issubclass(type(log_type), GeneralTracker):
1311
+ loggers.append(log_type)
1312
+ else:
1313
+ log_type = LoggerType(log_type)
1314
+ if log_type not in loggers:
1315
+ if log_type in get_available_trackers():
1316
+ tracker_init = LOGGER_TYPE_TO_CLASS[str(log_type)]
1317
+ if tracker_init.requires_logging_directory:
1318
+ if logging_dir is None:
1319
+ raise ValueError(
1320
+ f"Logging with `{log_type}` requires a `logging_dir` to be passed in."
1321
+ )
1322
+ loggers.append(log_type)
1323
+ else:
1324
+ logger.debug(f"Tried adding logger {log_type}, but package is unavailable in the system.")
1325
+
1326
+ return loggers
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/cuda_pathfinder-1.4.0.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/cuda_pathfinder-1.4.0.dist-info/METADATA ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: cuda-pathfinder
3
+ Version: 1.4.0
4
+ Summary: Pathfinder for CUDA components
5
+ Author-email: NVIDIA Corporation <cuda-python-conduct@nvidia.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Repository, https://github.com/NVIDIA/cuda-python
8
+ Project-URL: Documentation, https://nvidia.github.io/cuda-python/
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/x-rst
11
+ License-File: LICENSE
12
+ Dynamic: license-file
13
+
14
+ .. SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
15
+ .. SPDX-License-Identifier: Apache-2.0
16
+
17
+ *******************************************************
18
+ cuda-pathfinder: Utilities for locating CUDA components
19
+ *******************************************************
20
+
21
+ .. image:: https://img.shields.io/badge/NVIDIA-black?logo=nvidia
22
+ :target: https://www.nvidia.com/
23
+ :alt: NVIDIA
24
+
25
+ `cuda.pathfinder <https://nvidia.github.io/cuda-python/cuda-pathfinder/>`_
26
+ aims to be a one-stop solution for locating CUDA components. Currently
27
+ it supports locating and loading dynamic libraries (``.so``, ``.dll``), and
28
+ locating CTK header directories. Support for other artifacts is in progress.
29
+
30
+ * `Documentation <https://nvidia.github.io/cuda-python/cuda-pathfinder/>`_
31
+ * `Releases <https://nvidia.github.io/cuda-python/cuda-pathfinder/latest/release.html>`_
32
+ * `Repository <https://github.com/NVIDIA/cuda-python/tree/main/cuda_pathfinder/>`_
33
+ * `Issue tracker <https://github.com/NVIDIA/cuda-python/issues/>`_ (select component ``cuda.pathfinder``)
34
+
35
+ ``cuda.pathfinder`` is under active development. Feedback and suggestions are welcome.
36
+
37
+
38
+ Installation
39
+ ============
40
+
41
+ .. code-block:: bash
42
+
43
+ pip install cuda-pathfinder
44
+
45
+ ``cuda-pathfinder`` is `CUDA Toolkit (CTK) <https://developer.nvidia.com/cuda-toolkit>`_
46
+ version-agnostic. It follows the general CUDA Toolkit support policy: the
47
+ two most recent major versions are supported simultaneously.
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/cuda_pathfinder-1.4.0.dist-info/RECORD ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ cuda/pathfinder/README.md,sha256=JPbUfq3xYFMGa1ZlnpHkKsGXjJi4Zr1nNU3SzWJIviw,123
2
+ cuda/pathfinder/__init__.py,sha256=rHJIVMBSxsxzk46Z7cwZpjTyJ_fsQy7TOn4VlF8bgqs,3238
3
+ cuda/pathfinder/__pycache__/__init__.cpython-312.pyc,,
4
+ cuda/pathfinder/__pycache__/_version.cpython-312.pyc,,
5
+ cuda/pathfinder/_binaries/__pycache__/find_nvidia_binary_utility.cpython-312.pyc,,
6
+ cuda/pathfinder/_binaries/__pycache__/supported_nvidia_binaries.cpython-312.pyc,,
7
+ cuda/pathfinder/_binaries/find_nvidia_binary_utility.py,sha256=8wrArz138JmogO9_ZA968OqQAf0rPkLYo9tndVUt4ug,4296
8
+ cuda/pathfinder/_binaries/supported_nvidia_binaries.py,sha256=dSdytgVLC8vZ2aF-iTQJ0DnbV2CrzKePLe4X9yWUu40,1285
9
+ cuda/pathfinder/_dynamic_libs/__pycache__/canary_probe_subprocess.cpython-312.pyc,,
10
+ cuda/pathfinder/_dynamic_libs/__pycache__/find_nvidia_dynamic_lib.cpython-312.pyc,,
11
+ cuda/pathfinder/_dynamic_libs/__pycache__/load_dl_common.cpython-312.pyc,,
12
+ cuda/pathfinder/_dynamic_libs/__pycache__/load_dl_linux.cpython-312.pyc,,
13
+ cuda/pathfinder/_dynamic_libs/__pycache__/load_dl_windows.cpython-312.pyc,,
14
+ cuda/pathfinder/_dynamic_libs/__pycache__/load_nvidia_dynamic_lib.cpython-312.pyc,,
15
+ cuda/pathfinder/_dynamic_libs/__pycache__/supported_nvidia_libs.cpython-312.pyc,,
16
+ cuda/pathfinder/_dynamic_libs/canary_probe_subprocess.py,sha256=GdzJwYzbtdsOrUB9LsPhUeImapPG0YE4M9o4USBdQxc,994
17
+ cuda/pathfinder/_dynamic_libs/find_nvidia_dynamic_lib.py,sha256=kNVNzDU3mwvXc9O444vdJRbGD09KlS6qIo7dmHSYkhA,10197
18
+ cuda/pathfinder/_dynamic_libs/load_dl_common.py,sha256=vOuh20EKkNKwbRUZFFP621RcjCvgSoKTzaXVmRyW8-k,831
19
+ cuda/pathfinder/_dynamic_libs/load_dl_linux.py,sha256=aSKBgYOm0CCjT-0TixLOgNOtD4kvHHe8gjCBH2aXhjk,8010
20
+ cuda/pathfinder/_dynamic_libs/load_dl_windows.py,sha256=hznwPky7mh5GRYUw12VWcuqrrV-tDLQHCrR6uevbLyQ,6066
21
+ cuda/pathfinder/_dynamic_libs/load_nvidia_dynamic_lib.py,sha256=Y8ZBeOw3_LfQKcPqTvd3yfE3OO8cIZXPfcg9aFLZwnU,11857
22
+ cuda/pathfinder/_dynamic_libs/supported_nvidia_libs.py,sha256=G_Y4C4OdVu768Y8D018PiqhzC7i4Yhi5if3A_aH7oJ0,14330
23
+ cuda/pathfinder/_headers/__pycache__/find_nvidia_headers.cpython-312.pyc,,
24
+ cuda/pathfinder/_headers/__pycache__/supported_nvidia_headers.cpython-312.pyc,,
25
+ cuda/pathfinder/_headers/find_nvidia_headers.py,sha256=FUxWFuHe2yZ91bAvt32vJOat0RSyUwNtAstofhCEOrs,7315
26
+ cuda/pathfinder/_headers/supported_nvidia_headers.py,sha256=GIC9Bkh2ufPbOSXxa5vBpsdVnkXCI67F-UCvtdLcYkE,3374
27
+ cuda/pathfinder/_static_libs/__pycache__/find_bitcode_lib.cpython-312.pyc,,
28
+ cuda/pathfinder/_static_libs/find_bitcode_lib.py,sha256=W_AyfNCZ9zx8-2W4ZdYjH6aiH6ic2u9ZlDaY-PsvwiU,5460
29
+ cuda/pathfinder/_utils/__pycache__/env_vars.cpython-312.pyc,,
30
+ cuda/pathfinder/_utils/__pycache__/find_site_packages_dll.cpython-312.pyc,,
31
+ cuda/pathfinder/_utils/__pycache__/find_site_packages_so.cpython-312.pyc,,
32
+ cuda/pathfinder/_utils/__pycache__/find_sub_dirs.cpython-312.pyc,,
33
+ cuda/pathfinder/_utils/__pycache__/platform_aware.cpython-312.pyc,,
34
+ cuda/pathfinder/_utils/__pycache__/spawned_process_runner.cpython-312.pyc,,
35
+ cuda/pathfinder/_utils/env_vars.py,sha256=wRpf6XnohhKIpiGz_HbvnGg9C9u0uY5uU3t9OyYCl1A,1756
36
+ cuda/pathfinder/_utils/find_site_packages_dll.py,sha256=HixAy-Ubd5Gu9DZwCLAu2jvlEr1nfolOSZ_7GX3ZgbM,1143
37
+ cuda/pathfinder/_utils/find_site_packages_so.py,sha256=_udOAI8RtTXRJWz-X6TUy-g9W_nW8PeAcG61k1AVSLw,1638
38
+ cuda/pathfinder/_utils/find_sub_dirs.py,sha256=L5SIXikH8U-6BCIvTotR4xwAelWUvJohgZ1RAuxg2eA,2032
39
+ cuda/pathfinder/_utils/platform_aware.py,sha256=oOCqy_VyNdEdISVM2ilde8Kl6rOeElItvsAnw0WD9b8,463
40
+ cuda/pathfinder/_utils/spawned_process_runner.py,sha256=De1idhZq6v3JF2Ar2d5e08XBsoqXyJiuPZlXilYi7Cc,4355
41
+ cuda/pathfinder/_version.py,sha256=cWq_fKDGb85xfXQfl5uEbSwbhongIFne__ApDDkK9B4,712
42
+ cuda_pathfinder-1.4.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
43
+ cuda_pathfinder-1.4.0.dist-info/METADATA,sha256=XrzGu-7KW6TGCSXmdhPRWx9t_nxpF4xfnS32vcuqG60,1906
44
+ cuda_pathfinder-1.4.0.dist-info/RECORD,,
45
+ cuda_pathfinder-1.4.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
46
+ cuda_pathfinder-1.4.0.dist-info/licenses/LICENSE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
47
+ cuda_pathfinder-1.4.0.dist-info/top_level.txt,sha256=U5vpnvwNpaJF8bl4KnoUluMDTRt0J972FipwXjgNQ3A,5
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/cuda_pathfinder-1.4.0.dist-info/WHEEL ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/cuda_pathfinder-1.4.0.dist-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ cuda
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/fsspec/caching.py ADDED
@@ -0,0 +1,950 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import collections
4
+ import functools
5
+ import logging
6
+ import math
7
+ import os
8
+ import threading
9
+ import warnings
10
+ from concurrent.futures import Future, ThreadPoolExecutor
11
+ from typing import (
12
+ TYPE_CHECKING,
13
+ Any,
14
+ Callable,
15
+ ClassVar,
16
+ Generic,
17
+ NamedTuple,
18
+ OrderedDict,
19
+ TypeVar,
20
+ )
21
+
22
+ if TYPE_CHECKING:
23
+ import mmap
24
+
25
+ from typing_extensions import ParamSpec
26
+
27
+ P = ParamSpec("P")
28
+ else:
29
+ P = TypeVar("P")
30
+
31
+ T = TypeVar("T")
32
+
33
+
34
+ logger = logging.getLogger("fsspec")
35
+
36
+ Fetcher = Callable[[int, int], bytes] # Maps (start, end) to bytes
37
+
38
+
39
+ class BaseCache:
40
+ """Pass-though cache: doesn't keep anything, calls every time
41
+
42
+ Acts as base class for other cachers
43
+
44
+ Parameters
45
+ ----------
46
+ blocksize: int
47
+ How far to read ahead in numbers of bytes
48
+ fetcher: func
49
+ Function of the form f(start, end) which gets bytes from remote as
50
+ specified
51
+ size: int
52
+ How big this file is
53
+ """
54
+
55
+ name: ClassVar[str] = "none"
56
+
57
+ def __init__(self, blocksize: int, fetcher: Fetcher, size: int) -> None:
58
+ self.blocksize = blocksize
59
+ self.nblocks = 0
60
+ self.fetcher = fetcher
61
+ self.size = size
62
+ self.hit_count = 0
63
+ self.miss_count = 0
64
+ # the bytes that we actually requested
65
+ self.total_requested_bytes = 0
66
+
67
+ def _fetch(self, start: int | None, stop: int | None) -> bytes:
68
+ if start is None:
69
+ start = 0
70
+ if stop is None:
71
+ stop = self.size
72
+ if start >= self.size or start >= stop:
73
+ return b""
74
+ return self.fetcher(start, stop)
75
+
76
+ def _reset_stats(self) -> None:
77
+ """Reset hit and miss counts for a more ganular report e.g. by file."""
78
+ self.hit_count = 0
79
+ self.miss_count = 0
80
+ self.total_requested_bytes = 0
81
+
82
+ def _log_stats(self) -> str:
83
+ """Return a formatted string of the cache statistics."""
84
+ if self.hit_count == 0 and self.miss_count == 0:
85
+ # a cache that does nothing, this is for logs only
86
+ return ""
87
+ return " , %s: %d hits, %d misses, %d total requested bytes" % (
88
+ self.name,
89
+ self.hit_count,
90
+ self.miss_count,
91
+ self.total_requested_bytes,
92
+ )
93
+
94
+ def __repr__(self) -> str:
95
+ # TODO: use rich for better formatting
96
+ return f"""
97
+ <{self.__class__.__name__}:
98
+ block size : {self.blocksize}
99
+ block count : {self.nblocks}
100
+ file size : {self.size}
101
+ cache hits : {self.hit_count}
102
+ cache misses: {self.miss_count}
103
+ total requested bytes: {self.total_requested_bytes}>
104
+ """
105
+
106
+
107
+ class MMapCache(BaseCache):
108
+ """memory-mapped sparse file cache
109
+
110
+ Opens temporary file, which is filled blocks-wise when data is requested.
111
+ Ensure there is enough disc space in the temporary location.
112
+
113
+ This cache method might only work on posix
114
+ """
115
+
116
+ name = "mmap"
117
+
118
+ def __init__(
119
+ self,
120
+ blocksize: int,
121
+ fetcher: Fetcher,
122
+ size: int,
123
+ location: str | None = None,
124
+ blocks: set[int] | None = None,
125
+ ) -> None:
126
+ super().__init__(blocksize, fetcher, size)
127
+ self.blocks = set() if blocks is None else blocks
128
+ self.location = location
129
+ self.cache = self._makefile()
130
+
131
+ def _makefile(self) -> mmap.mmap | bytearray:
132
+ import mmap
133
+ import tempfile
134
+
135
+ if self.size == 0:
136
+ return bytearray()
137
+
138
+ # posix version
139
+ if self.location is None or not os.path.exists(self.location):
140
+ if self.location is None:
141
+ fd = tempfile.TemporaryFile()
142
+ self.blocks = set()
143
+ else:
144
+ fd = open(self.location, "wb+")
145
+ fd.seek(self.size - 1)
146
+ fd.write(b"1")
147
+ fd.flush()
148
+ else:
149
+ fd = open(self.location, "r+b")
150
+
151
+ return mmap.mmap(fd.fileno(), self.size)
152
+
153
+ def _fetch(self, start: int | None, end: int | None) -> bytes:
154
+ logger.debug(f"MMap cache fetching {start}-{end}")
155
+ if start is None:
156
+ start = 0
157
+ if end is None:
158
+ end = self.size
159
+ if start >= self.size or start >= end:
160
+ return b""
161
+ start_block = start // self.blocksize
162
+ end_block = end // self.blocksize
163
+ need = [i for i in range(start_block, end_block + 1) if i not in self.blocks]
164
+ hits = [i for i in range(start_block, end_block + 1) if i in self.blocks]
165
+ self.miss_count += len(need)
166
+ self.hit_count += len(hits)
167
+ while need:
168
+ # TODO: not a for loop so we can consolidate blocks later to
169
+ # make fewer fetch calls; this could be parallel
170
+ i = need.pop(0)
171
+
172
+ sstart = i * self.blocksize
173
+ send = min(sstart + self.blocksize, self.size)
174
+ self.total_requested_bytes += send - sstart
175
+ logger.debug(f"MMap get block #{i} ({sstart}-{send})")
176
+ self.cache[sstart:send] = self.fetcher(sstart, send)
177
+ self.blocks.add(i)
178
+
179
+ return self.cache[start:end]
180
+
181
+ def __getstate__(self) -> dict[str, Any]:
182
+ state = self.__dict__.copy()
183
+ # Remove the unpicklable entries.
184
+ del state["cache"]
185
+ return state
186
+
187
+ def __setstate__(self, state: dict[str, Any]) -> None:
188
+ # Restore instance attributes
189
+ self.__dict__.update(state)
190
+ self.cache = self._makefile()
191
+
192
+
193
+ class ReadAheadCache(BaseCache):
194
+ """Cache which reads only when we get beyond a block of data
195
+
196
+ This is a much simpler version of BytesCache, and does not attempt to
197
+ fill holes in the cache or keep fragments alive. It is best suited to
198
+ many small reads in a sequential order (e.g., reading lines from a file).
199
+ """
200
+
201
+ name = "readahead"
202
+
203
+ def __init__(self, blocksize: int, fetcher: Fetcher, size: int) -> None:
204
+ super().__init__(blocksize, fetcher, size)
205
+ self.cache = b""
206
+ self.start = 0
207
+ self.end = 0
208
+
209
+ def _fetch(self, start: int | None, end: int | None) -> bytes:
210
+ if start is None:
211
+ start = 0
212
+ if end is None or end > self.size:
213
+ end = self.size
214
+ if start >= self.size or start >= end:
215
+ return b""
216
+ l = end - start
217
+ if start >= self.start and end <= self.end:
218
+ # cache hit
219
+ self.hit_count += 1
220
+ return self.cache[start - self.start : end - self.start]
221
+ elif self.start <= start < self.end:
222
+ # partial hit
223
+ self.miss_count += 1
224
+ part = self.cache[start - self.start :]
225
+ l -= len(part)
226
+ start = self.end
227
+ else:
228
+ # miss
229
+ self.miss_count += 1
230
+ part = b""
231
+ end = min(self.size, end + self.blocksize)
232
+ self.total_requested_bytes += end - start
233
+ self.cache = self.fetcher(start, end) # new block replaces old
234
+ self.start = start
235
+ self.end = self.start + len(self.cache)
236
+ return part + self.cache[:l]
237
+
238
+
239
+ class FirstChunkCache(BaseCache):
240
+ """Caches the first block of a file only
241
+
242
+ This may be useful for file types where the metadata is stored in the header,
243
+ but is randomly accessed.
244
+ """
245
+
246
+ name = "first"
247
+
248
+ def __init__(self, blocksize: int, fetcher: Fetcher, size: int) -> None:
249
+ if blocksize > size:
250
+ # this will buffer the whole thing
251
+ blocksize = size
252
+ super().__init__(blocksize, fetcher, size)
253
+ self.cache: bytes | None = None
254
+
255
+ def _fetch(self, start: int | None, end: int | None) -> bytes:
256
+ start = start or 0
257
+ if start > self.size:
258
+ logger.debug("FirstChunkCache: requested start > file size")
259
+ return b""
260
+
261
+ end = min(end, self.size)
262
+
263
+ if start < self.blocksize:
264
+ if self.cache is None:
265
+ self.miss_count += 1
266
+ if end > self.blocksize:
267
+ self.total_requested_bytes += end
268
+ data = self.fetcher(0, end)
269
+ self.cache = data[: self.blocksize]
270
+ return data[start:]
271
+ self.cache = self.fetcher(0, self.blocksize)
272
+ self.total_requested_bytes += self.blocksize
273
+ part = self.cache[start:end]
274
+ if end > self.blocksize:
275
+ self.total_requested_bytes += end - self.blocksize
276
+ part += self.fetcher(self.blocksize, end)
277
+ self.hit_count += 1
278
+ return part
279
+ else:
280
+ self.miss_count += 1
281
+ self.total_requested_bytes += end - start
282
+ return self.fetcher(start, end)
283
+
284
+
285
+ class BlockCache(BaseCache):
286
+ """
287
+ Cache holding memory as a set of blocks.
288
+
289
+ Requests are only ever made ``blocksize`` at a time, and are
290
+ stored in an LRU cache. The least recently accessed block is
291
+ discarded when more than ``maxblocks`` are stored.
292
+
293
+ Parameters
294
+ ----------
295
+ blocksize : int
296
+ The number of bytes to store in each block.
297
+ Requests are only ever made for ``blocksize``, so this
298
+ should balance the overhead of making a request against
299
+ the granularity of the blocks.
300
+ fetcher : Callable
301
+ size : int
302
+ The total size of the file being cached.
303
+ maxblocks : int
304
+ The maximum number of blocks to cache for. The maximum memory
305
+ use for this cache is then ``blocksize * maxblocks``.
306
+ """
307
+
308
+ name = "blockcache"
309
+
310
+ def __init__(
311
+ self, blocksize: int, fetcher: Fetcher, size: int, maxblocks: int = 32
312
+ ) -> None:
313
+ super().__init__(blocksize, fetcher, size)
314
+ self.nblocks = math.ceil(size / blocksize)
315
+ self.maxblocks = maxblocks
316
+ self._fetch_block_cached = functools.lru_cache(maxblocks)(self._fetch_block)
317
+
318
+ def cache_info(self):
319
+ """
320
+ The statistics on the block cache.
321
+
322
+ Returns
323
+ -------
324
+ NamedTuple
325
+ Returned directly from the LRU Cache used internally.
326
+ """
327
+ return self._fetch_block_cached.cache_info()
328
+
329
+ def __getstate__(self) -> dict[str, Any]:
330
+ state = self.__dict__
331
+ del state["_fetch_block_cached"]
332
+ return state
333
+
334
+ def __setstate__(self, state: dict[str, Any]) -> None:
335
+ self.__dict__.update(state)
336
+ self._fetch_block_cached = functools.lru_cache(state["maxblocks"])(
337
+ self._fetch_block
338
+ )
339
+
340
+ def _fetch(self, start: int | None, end: int | None) -> bytes:
341
+ if start is None:
342
+ start = 0
343
+ if end is None:
344
+ end = self.size
345
+ if start >= self.size or start >= end:
346
+ return b""
347
+
348
+ # byte position -> block numbers
349
+ start_block_number = start // self.blocksize
350
+ end_block_number = end // self.blocksize
351
+
352
+ # these are cached, so safe to do multiple calls for the same start and end.
353
+ for block_number in range(start_block_number, end_block_number + 1):
354
+ self._fetch_block_cached(block_number)
355
+
356
+ return self._read_cache(
357
+ start,
358
+ end,
359
+ start_block_number=start_block_number,
360
+ end_block_number=end_block_number,
361
+ )
362
+
363
+ def _fetch_block(self, block_number: int) -> bytes:
364
+ """
365
+ Fetch the block of data for `block_number`.
366
+ """
367
+ if block_number > self.nblocks:
368
+ raise ValueError(
369
+ f"'block_number={block_number}' is greater than "
370
+ f"the number of blocks ({self.nblocks})"
371
+ )
372
+
373
+ start = block_number * self.blocksize
374
+ end = start + self.blocksize
375
+ self.total_requested_bytes += end - start
376
+ self.miss_count += 1
377
+ logger.info("BlockCache fetching block %d", block_number)
378
+ block_contents = super()._fetch(start, end)
379
+ return block_contents
380
+
381
+ def _read_cache(
382
+ self, start: int, end: int, start_block_number: int, end_block_number: int
383
+ ) -> bytes:
384
+ """
385
+ Read from our block cache.
386
+
387
+ Parameters
388
+ ----------
389
+ start, end : int
390
+ The start and end byte positions.
391
+ start_block_number, end_block_number : int
392
+ The start and end block numbers.
393
+ """
394
+ start_pos = start % self.blocksize
395
+ end_pos = end % self.blocksize
396
+
397
+ self.hit_count += 1
398
+ if start_block_number == end_block_number:
399
+ block: bytes = self._fetch_block_cached(start_block_number)
400
+ return block[start_pos:end_pos]
401
+
402
+ else:
403
+ # read from the initial
404
+ out = [self._fetch_block_cached(start_block_number)[start_pos:]]
405
+
406
+ # intermediate blocks
407
+ # Note: it'd be nice to combine these into one big request. However
408
+ # that doesn't play nicely with our LRU cache.
409
+ out.extend(
410
+ map(
411
+ self._fetch_block_cached,
412
+ range(start_block_number + 1, end_block_number),
413
+ )
414
+ )
415
+
416
+ # final block
417
+ out.append(self._fetch_block_cached(end_block_number)[:end_pos])
418
+
419
+ return b"".join(out)
420
+
421
+
422
+ class BytesCache(BaseCache):
423
+ """Cache which holds data in a in-memory bytes object
424
+
425
+ Implements read-ahead by the block size, for semi-random reads progressing
426
+ through the file.
427
+
428
+ Parameters
429
+ ----------
430
+ trim: bool
431
+ As we read more data, whether to discard the start of the buffer when
432
+ we are more than a blocksize ahead of it.
433
+ """
434
+
435
+ name: ClassVar[str] = "bytes"
436
+
437
+ def __init__(
438
+ self, blocksize: int, fetcher: Fetcher, size: int, trim: bool = True
439
+ ) -> None:
440
+ super().__init__(blocksize, fetcher, size)
441
+ self.cache = b""
442
+ self.start: int | None = None
443
+ self.end: int | None = None
444
+ self.trim = trim
445
+
446
+ def _fetch(self, start: int | None, end: int | None) -> bytes:
447
+ # TODO: only set start/end after fetch, in case it fails?
448
+ # is this where retry logic might go?
449
+ if start is None:
450
+ start = 0
451
+ if end is None:
452
+ end = self.size
453
+ if start >= self.size or start >= end:
454
+ return b""
455
+ if (
456
+ self.start is not None
457
+ and start >= self.start
458
+ and self.end is not None
459
+ and end < self.end
460
+ ):
461
+ # cache hit: we have all the required data
462
+ offset = start - self.start
463
+ self.hit_count += 1
464
+ return self.cache[offset : offset + end - start]
465
+
466
+ if self.blocksize:
467
+ bend = min(self.size, end + self.blocksize)
468
+ else:
469
+ bend = end
470
+
471
+ if bend == start or start > self.size:
472
+ return b""
473
+
474
+ if (self.start is None or start < self.start) and (
475
+ self.end is None or end > self.end
476
+ ):
477
+ # First read, or extending both before and after
478
+ self.total_requested_bytes += bend - start
479
+ self.miss_count += 1
480
+ self.cache = self.fetcher(start, bend)
481
+ self.start = start
482
+ else:
483
+ assert self.start is not None
484
+ assert self.end is not None
485
+ self.miss_count += 1
486
+
487
+ if start < self.start:
488
+ if self.end is None or self.end - end > self.blocksize:
489
+ self.total_requested_bytes += bend - start
490
+ self.cache = self.fetcher(start, bend)
491
+ self.start = start
492
+ else:
493
+ self.total_requested_bytes += self.start - start
494
+ new = self.fetcher(start, self.start)
495
+ self.start = start
496
+ self.cache = new + self.cache
497
+ elif self.end is not None and bend > self.end:
498
+ if self.end > self.size:
499
+ pass
500
+ elif end - self.end > self.blocksize:
501
+ self.total_requested_bytes += bend - start
502
+ self.cache = self.fetcher(start, bend)
503
+ self.start = start
504
+ else:
505
+ self.total_requested_bytes += bend - self.end
506
+ new = self.fetcher(self.end, bend)
507
+ self.cache = self.cache + new
508
+
509
+ self.end = self.start + len(self.cache)
510
+ offset = start - self.start
511
+ out = self.cache[offset : offset + end - start]
512
+ if self.trim:
513
+ num = (self.end - self.start) // (self.blocksize + 1)
514
+ if num > 1:
515
+ self.start += self.blocksize * num
516
+ self.cache = self.cache[self.blocksize * num :]
517
+ return out
518
+
519
+ def __len__(self) -> int:
520
+ return len(self.cache)
521
+
522
+
523
+ class AllBytes(BaseCache):
524
+ """Cache entire contents of the file"""
525
+
526
+ name: ClassVar[str] = "all"
527
+
528
+ def __init__(
529
+ self,
530
+ blocksize: int | None = None,
531
+ fetcher: Fetcher | None = None,
532
+ size: int | None = None,
533
+ data: bytes | None = None,
534
+ ) -> None:
535
+ super().__init__(blocksize, fetcher, size) # type: ignore[arg-type]
536
+ if data is None:
537
+ self.miss_count += 1
538
+ self.total_requested_bytes += self.size
539
+ data = self.fetcher(0, self.size)
540
+ self.data = data
541
+
542
+ def _fetch(self, start: int | None, stop: int | None) -> bytes:
543
+ self.hit_count += 1
544
+ return self.data[start:stop]
545
+
546
+
547
+ class KnownPartsOfAFile(BaseCache):
548
+ """
549
+ Cache holding known file parts.
550
+
551
+ Parameters
552
+ ----------
553
+ blocksize: int
554
+ How far to read ahead in numbers of bytes
555
+ fetcher: func
556
+ Function of the form f(start, end) which gets bytes from remote as
557
+ specified
558
+ size: int
559
+ How big this file is
560
+ data: dict
561
+ A dictionary mapping explicit `(start, stop)` file-offset tuples
562
+ with known bytes.
563
+ strict: bool, default True
564
+ Whether to fetch reads that go beyond a known byte-range boundary.
565
+ If `False`, any read that ends outside a known part will be zero
566
+ padded. Note that zero padding will not be used for reads that
567
+ begin outside a known byte-range.
568
+ """
569
+
570
+ name: ClassVar[str] = "parts"
571
+
572
+ def __init__(
573
+ self,
574
+ blocksize: int,
575
+ fetcher: Fetcher,
576
+ size: int,
577
+ data: dict[tuple[int, int], bytes] = {},
578
+ strict: bool = True,
579
+ **_: Any,
580
+ ):
581
+ super().__init__(blocksize, fetcher, size)
582
+ self.strict = strict
583
+
584
+ # simple consolidation of contiguous blocks
585
+ if data:
586
+ old_offsets = sorted(data.keys())
587
+ offsets = [old_offsets[0]]
588
+ blocks = [data.pop(old_offsets[0])]
589
+ for start, stop in old_offsets[1:]:
590
+ start0, stop0 = offsets[-1]
591
+ if start == stop0:
592
+ offsets[-1] = (start0, stop)
593
+ blocks[-1] += data.pop((start, stop))
594
+ else:
595
+ offsets.append((start, stop))
596
+ blocks.append(data.pop((start, stop)))
597
+
598
+ self.data = dict(zip(offsets, blocks))
599
+ else:
600
+ self.data = data
601
+
602
+ def _fetch(self, start: int | None, stop: int | None) -> bytes:
603
+ if start is None:
604
+ start = 0
605
+ if stop is None:
606
+ stop = self.size
607
+
608
+ out = b""
609
+ for (loc0, loc1), data in self.data.items():
610
+ # If self.strict=False, use zero-padded data
611
+ # for reads beyond the end of a "known" buffer
612
+ if loc0 <= start < loc1:
613
+ off = start - loc0
614
+ out = data[off : off + stop - start]
615
+ if not self.strict or loc0 <= stop <= loc1:
616
+ # The request is within a known range, or
617
+ # it begins within a known range, and we
618
+ # are allowed to pad reads beyond the
619
+ # buffer with zero
620
+ out += b"\x00" * (stop - start - len(out))
621
+ self.hit_count += 1
622
+ return out
623
+ else:
624
+ # The request ends outside a known range,
625
+ # and we are being "strict" about reads
626
+ # beyond the buffer
627
+ start = loc1
628
+ break
629
+
630
+ # We only get here if there is a request outside the
631
+ # known parts of the file. In an ideal world, this
632
+ # should never happen
633
+ if self.fetcher is None:
634
+ # We cannot fetch the data, so raise an error
635
+ raise ValueError(f"Read is outside the known file parts: {(start, stop)}. ")
636
+ # We can fetch the data, but should warn the user
637
+ # that this may be slow
638
+ warnings.warn(
639
+ f"Read is outside the known file parts: {(start, stop)}. "
640
+ f"IO/caching performance may be poor!"
641
+ )
642
+ logger.debug(f"KnownPartsOfAFile cache fetching {start}-{stop}")
643
+ self.total_requested_bytes += stop - start
644
+ self.miss_count += 1
645
+ return out + super()._fetch(start, stop)
646
+
647
+
648
+ class UpdatableLRU(Generic[P, T]):
649
+ """
650
+ Custom implementation of LRU cache that allows updating keys
651
+
652
+ Used by BackgroudBlockCache
653
+ """
654
+
655
+ class CacheInfo(NamedTuple):
656
+ hits: int
657
+ misses: int
658
+ maxsize: int
659
+ currsize: int
660
+
661
+ def __init__(self, func: Callable[P, T], max_size: int = 128) -> None:
662
+ self._cache: OrderedDict[Any, T] = collections.OrderedDict()
663
+ self._func = func
664
+ self._max_size = max_size
665
+ self._hits = 0
666
+ self._misses = 0
667
+ self._lock = threading.Lock()
668
+
669
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
670
+ if kwargs:
671
+ raise TypeError(f"Got unexpected keyword argument {kwargs.keys()}")
672
+ with self._lock:
673
+ if args in self._cache:
674
+ self._cache.move_to_end(args)
675
+ self._hits += 1
676
+ return self._cache[args]
677
+
678
+ result = self._func(*args, **kwargs)
679
+
680
+ with self._lock:
681
+ self._cache[args] = result
682
+ self._misses += 1
683
+ if len(self._cache) > self._max_size:
684
+ self._cache.popitem(last=False)
685
+
686
+ return result
687
+
688
+ def is_key_cached(self, *args: Any) -> bool:
689
+ with self._lock:
690
+ return args in self._cache
691
+
692
+ def add_key(self, result: T, *args: Any) -> None:
693
+ with self._lock:
694
+ self._cache[args] = result
695
+ if len(self._cache) > self._max_size:
696
+ self._cache.popitem(last=False)
697
+
698
+ def cache_info(self) -> UpdatableLRU.CacheInfo:
699
+ with self._lock:
700
+ return self.CacheInfo(
701
+ maxsize=self._max_size,
702
+ currsize=len(self._cache),
703
+ hits=self._hits,
704
+ misses=self._misses,
705
+ )
706
+
707
+
708
+ class BackgroundBlockCache(BaseCache):
709
+ """
710
+ Cache holding memory as a set of blocks with pre-loading of
711
+ the next block in the background.
712
+
713
+ Requests are only ever made ``blocksize`` at a time, and are
714
+ stored in an LRU cache. The least recently accessed block is
715
+ discarded when more than ``maxblocks`` are stored. If the
716
+ next block is not in cache, it is loaded in a separate thread
717
+ in non-blocking way.
718
+
719
+ Parameters
720
+ ----------
721
+ blocksize : int
722
+ The number of bytes to store in each block.
723
+ Requests are only ever made for ``blocksize``, so this
724
+ should balance the overhead of making a request against
725
+ the granularity of the blocks.
726
+ fetcher : Callable
727
+ size : int
728
+ The total size of the file being cached.
729
+ maxblocks : int
730
+ The maximum number of blocks to cache for. The maximum memory
731
+ use for this cache is then ``blocksize * maxblocks``.
732
+ """
733
+
734
+ name: ClassVar[str] = "background"
735
+
736
+ def __init__(
737
+ self, blocksize: int, fetcher: Fetcher, size: int, maxblocks: int = 32
738
+ ) -> None:
739
+ super().__init__(blocksize, fetcher, size)
740
+ self.nblocks = math.ceil(size / blocksize)
741
+ self.maxblocks = maxblocks
742
+ self._fetch_block_cached = UpdatableLRU(self._fetch_block, maxblocks)
743
+
744
+ self._thread_executor = ThreadPoolExecutor(max_workers=1)
745
+ self._fetch_future_block_number: int | None = None
746
+ self._fetch_future: Future[bytes] | None = None
747
+ self._fetch_future_lock = threading.Lock()
748
+
749
+ def cache_info(self) -> UpdatableLRU.CacheInfo:
750
+ """
751
+ The statistics on the block cache.
752
+
753
+ Returns
754
+ -------
755
+ NamedTuple
756
+ Returned directly from the LRU Cache used internally.
757
+ """
758
+ return self._fetch_block_cached.cache_info()
759
+
760
+ def __getstate__(self) -> dict[str, Any]:
761
+ state = self.__dict__
762
+ del state["_fetch_block_cached"]
763
+ del state["_thread_executor"]
764
+ del state["_fetch_future_block_number"]
765
+ del state["_fetch_future"]
766
+ del state["_fetch_future_lock"]
767
+ return state
768
+
769
+ def __setstate__(self, state) -> None:
770
+ self.__dict__.update(state)
771
+ self._fetch_block_cached = UpdatableLRU(self._fetch_block, state["maxblocks"])
772
+ self._thread_executor = ThreadPoolExecutor(max_workers=1)
773
+ self._fetch_future_block_number = None
774
+ self._fetch_future = None
775
+ self._fetch_future_lock = threading.Lock()
776
+
777
+ def _fetch(self, start: int | None, end: int | None) -> bytes:
778
+ if start is None:
779
+ start = 0
780
+ if end is None:
781
+ end = self.size
782
+ if start >= self.size or start >= end:
783
+ return b""
784
+
785
+ # byte position -> block numbers
786
+ start_block_number = start // self.blocksize
787
+ end_block_number = end // self.blocksize
788
+
789
+ fetch_future_block_number = None
790
+ fetch_future = None
791
+ with self._fetch_future_lock:
792
+ # Background thread is running. Check we we can or must join it.
793
+ if self._fetch_future is not None:
794
+ assert self._fetch_future_block_number is not None
795
+ if self._fetch_future.done():
796
+ logger.info("BlockCache joined background fetch without waiting.")
797
+ self._fetch_block_cached.add_key(
798
+ self._fetch_future.result(), self._fetch_future_block_number
799
+ )
800
+ # Cleanup the fetch variables. Done with fetching the block.
801
+ self._fetch_future_block_number = None
802
+ self._fetch_future = None
803
+ else:
804
+ # Must join if we need the block for the current fetch
805
+ must_join = bool(
806
+ start_block_number
807
+ <= self._fetch_future_block_number
808
+ <= end_block_number
809
+ )
810
+ if must_join:
811
+ # Copy to the local variables to release lock
812
+ # before waiting for result
813
+ fetch_future_block_number = self._fetch_future_block_number
814
+ fetch_future = self._fetch_future
815
+
816
+ # Cleanup the fetch variables. Have a local copy.
817
+ self._fetch_future_block_number = None
818
+ self._fetch_future = None
819
+
820
+ # Need to wait for the future for the current read
821
+ if fetch_future is not None:
822
+ logger.info("BlockCache waiting for background fetch.")
823
+ # Wait until result and put it in cache
824
+ self._fetch_block_cached.add_key(
825
+ fetch_future.result(), fetch_future_block_number
826
+ )
827
+
828
+ # these are cached, so safe to do multiple calls for the same start and end.
829
+ for block_number in range(start_block_number, end_block_number + 1):
830
+ self._fetch_block_cached(block_number)
831
+
832
+ # fetch next block in the background if nothing is running in the background,
833
+ # the block is within file and it is not already cached
834
+ end_block_plus_1 = end_block_number + 1
835
+ with self._fetch_future_lock:
836
+ if (
837
+ self._fetch_future is None
838
+ and end_block_plus_1 <= self.nblocks
839
+ and not self._fetch_block_cached.is_key_cached(end_block_plus_1)
840
+ ):
841
+ self._fetch_future_block_number = end_block_plus_1
842
+ self._fetch_future = self._thread_executor.submit(
843
+ self._fetch_block, end_block_plus_1, "async"
844
+ )
845
+
846
+ return self._read_cache(
847
+ start,
848
+ end,
849
+ start_block_number=start_block_number,
850
+ end_block_number=end_block_number,
851
+ )
852
+
853
+ def _fetch_block(self, block_number: int, log_info: str = "sync") -> bytes:
854
+ """
855
+ Fetch the block of data for `block_number`.
856
+ """
857
+ if block_number > self.nblocks:
858
+ raise ValueError(
859
+ f"'block_number={block_number}' is greater than "
860
+ f"the number of blocks ({self.nblocks})"
861
+ )
862
+
863
+ start = block_number * self.blocksize
864
+ end = start + self.blocksize
865
+ logger.info("BlockCache fetching block (%s) %d", log_info, block_number)
866
+ self.total_requested_bytes += end - start
867
+ self.miss_count += 1
868
+ block_contents = super()._fetch(start, end)
869
+ return block_contents
870
+
871
+ def _read_cache(
872
+ self, start: int, end: int, start_block_number: int, end_block_number: int
873
+ ) -> bytes:
874
+ """
875
+ Read from our block cache.
876
+
877
+ Parameters
878
+ ----------
879
+ start, end : int
880
+ The start and end byte positions.
881
+ start_block_number, end_block_number : int
882
+ The start and end block numbers.
883
+ """
884
+ start_pos = start % self.blocksize
885
+ end_pos = end % self.blocksize
886
+
887
+ # kind of pointless to count this as a hit, but it is
888
+ self.hit_count += 1
889
+
890
+ if start_block_number == end_block_number:
891
+ block = self._fetch_block_cached(start_block_number)
892
+ return block[start_pos:end_pos]
893
+
894
+ else:
895
+ # read from the initial
896
+ out = [self._fetch_block_cached(start_block_number)[start_pos:]]
897
+
898
+ # intermediate blocks
899
+ # Note: it'd be nice to combine these into one big request. However
900
+ # that doesn't play nicely with our LRU cache.
901
+ out.extend(
902
+ map(
903
+ self._fetch_block_cached,
904
+ range(start_block_number + 1, end_block_number),
905
+ )
906
+ )
907
+
908
+ # final block
909
+ out.append(self._fetch_block_cached(end_block_number)[:end_pos])
910
+
911
+ return b"".join(out)
912
+
913
+
914
+ caches: dict[str | None, type[BaseCache]] = {
915
+ # one custom case
916
+ None: BaseCache,
917
+ }
918
+
919
+
920
+ def register_cache(cls: type[BaseCache], clobber: bool = False) -> None:
921
+ """'Register' cache implementation.
922
+
923
+ Parameters
924
+ ----------
925
+ clobber: bool, optional
926
+ If set to True (default is False) - allow to overwrite existing
927
+ entry.
928
+
929
+ Raises
930
+ ------
931
+ ValueError
932
+ """
933
+ name = cls.name
934
+ if not clobber and name in caches:
935
+ raise ValueError(f"Cache with name {name!r} is already known: {caches[name]}")
936
+ caches[name] = cls
937
+
938
+
939
+ for c in (
940
+ BaseCache,
941
+ MMapCache,
942
+ BytesCache,
943
+ ReadAheadCache,
944
+ BlockCache,
945
+ FirstChunkCache,
946
+ AllBytes,
947
+ KnownPartsOfAFile,
948
+ BackgroundBlockCache,
949
+ ):
950
+ register_cache(c)
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/fsspec/dircache.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ from collections.abc import MutableMapping
3
+ from functools import lru_cache
4
+
5
+
6
+ class DirCache(MutableMapping):
7
+ """
8
+ Caching of directory listings, in a structure like::
9
+
10
+ {"path0": [
11
+ {"name": "path0/file0",
12
+ "size": 123,
13
+ "type": "file",
14
+ ...
15
+ },
16
+ {"name": "path0/file1",
17
+ },
18
+ ...
19
+ ],
20
+ "path1": [...]
21
+ }
22
+
23
+ Parameters to this class control listing expiry or indeed turn
24
+ caching off
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ use_listings_cache=True,
30
+ listings_expiry_time=None,
31
+ max_paths=None,
32
+ **kwargs,
33
+ ):
34
+ """
35
+
36
+ Parameters
37
+ ----------
38
+ use_listings_cache: bool
39
+ If False, this cache never returns items, but always reports KeyError,
40
+ and setting items has no effect
41
+ listings_expiry_time: int or float (optional)
42
+ Time in seconds that a listing is considered valid. If None,
43
+ listings do not expire.
44
+ max_paths: int (optional)
45
+ The number of most recent listings that are considered valid; 'recent'
46
+ refers to when the entry was set.
47
+ """
48
+ self._cache = {}
49
+ self._times = {}
50
+ if max_paths:
51
+ self._q = lru_cache(max_paths + 1)(lambda key: self._cache.pop(key, None))
52
+ self.use_listings_cache = use_listings_cache
53
+ self.listings_expiry_time = listings_expiry_time
54
+ self.max_paths = max_paths
55
+
56
+ def __getitem__(self, item):
57
+ if self.listings_expiry_time is not None:
58
+ if self._times.get(item, 0) - time.time() < -self.listings_expiry_time:
59
+ del self._cache[item]
60
+ if self.max_paths:
61
+ self._q(item)
62
+ return self._cache[item] # maybe raises KeyError
63
+
64
+ def clear(self):
65
+ self._cache.clear()
66
+
67
+ def __len__(self):
68
+ return len(self._cache)
69
+
70
+ def __contains__(self, item):
71
+ try:
72
+ self[item]
73
+ return True
74
+ except KeyError:
75
+ return False
76
+
77
+ def __setitem__(self, key, value):
78
+ if not self.use_listings_cache:
79
+ return
80
+ if self.max_paths:
81
+ self._q(key)
82
+ self._cache[key] = value
83
+ if self.listings_expiry_time is not None:
84
+ self._times[key] = time.time()
85
+
86
+ def __delitem__(self, key):
87
+ del self._cache[key]
88
+
89
+ def __iter__(self):
90
+ entries = list(self._cache)
91
+
92
+ return (k for k in entries if k in self)
93
+
94
+ def __reduce__(self):
95
+ return (
96
+ DirCache,
97
+ (self.use_listings_cache, self.listings_expiry_time, self.max_paths),
98
+ )
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/fsspec/gui.py ADDED
@@ -0,0 +1,414 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ast
2
+ import contextlib
3
+ import logging
4
+ import os
5
+ import re
6
+ from typing import ClassVar, Sequence
7
+
8
+ import panel as pn
9
+
10
+ from .core import OpenFile, get_filesystem_class, split_protocol
11
+ from .registry import known_implementations
12
+
13
+ pn.extension()
14
+ logger = logging.getLogger("fsspec.gui")
15
+
16
+
17
+ class SigSlot:
18
+ """Signal-slot mixin, for Panel event passing
19
+
20
+ Include this class in a widget manager's superclasses to be able to
21
+ register events and callbacks on Panel widgets managed by that class.
22
+
23
+ The method ``_register`` should be called as widgets are added, and external
24
+ code should call ``connect`` to associate callbacks.
25
+
26
+ By default, all signals emit a DEBUG logging statement.
27
+ """
28
+
29
+ # names of signals that this class may emit each of which must be
30
+ # set by _register for any new instance
31
+ signals: ClassVar[Sequence[str]] = []
32
+ # names of actions that this class may respond to
33
+ slots: ClassVar[Sequence[str]] = []
34
+
35
+ # each of which must be a method name
36
+
37
+ def __init__(self):
38
+ self._ignoring_events = False
39
+ self._sigs = {}
40
+ self._map = {}
41
+ self._setup()
42
+
43
+ def _setup(self):
44
+ """Create GUI elements and register signals"""
45
+ self.panel = pn.pane.PaneBase()
46
+ # no signals to set up in the base class
47
+
48
+ def _register(
49
+ self, widget, name, thing="value", log_level=logging.DEBUG, auto=False
50
+ ):
51
+ """Watch the given attribute of a widget and assign it a named event
52
+
53
+ This is normally called at the time a widget is instantiated, in the
54
+ class which owns it.
55
+
56
+ Parameters
57
+ ----------
58
+ widget : pn.layout.Panel or None
59
+ Widget to watch. If None, an anonymous signal not associated with
60
+ any widget.
61
+ name : str
62
+ Name of this event
63
+ thing : str
64
+ Attribute of the given widget to watch
65
+ log_level : int
66
+ When the signal is triggered, a logging event of the given level
67
+ will be fired in the dfviz logger.
68
+ auto : bool
69
+ If True, automatically connects with a method in this class of the
70
+ same name.
71
+ """
72
+ if name not in self.signals:
73
+ raise ValueError(f"Attempt to assign an undeclared signal: {name}")
74
+ self._sigs[name] = {
75
+ "widget": widget,
76
+ "callbacks": [],
77
+ "thing": thing,
78
+ "log": log_level,
79
+ }
80
+ wn = "-".join(
81
+ [
82
+ getattr(widget, "name", str(widget)) if widget is not None else "none",
83
+ thing,
84
+ ]
85
+ )
86
+ self._map[wn] = name
87
+ if widget is not None:
88
+ widget.param.watch(self._signal, thing, onlychanged=True)
89
+ if auto and hasattr(self, name):
90
+ self.connect(name, getattr(self, name))
91
+
92
+ def _repr_mimebundle_(self, *args, **kwargs):
93
+ """Display in a notebook or a server"""
94
+ try:
95
+ return self.panel._repr_mimebundle_(*args, **kwargs)
96
+ except (ValueError, AttributeError):
97
+ raise NotImplementedError("Panel does not seem to be set up properly")
98
+
99
+ def connect(self, signal, slot):
100
+ """Associate call back with given event
101
+
102
+ The callback must be a function which takes the "new" value of the
103
+ watched attribute as the only parameter. If the callback return False,
104
+ this cancels any further processing of the given event.
105
+
106
+ Alternatively, the callback can be a string, in which case it means
107
+ emitting the correspondingly-named event (i.e., connect to self)
108
+ """
109
+ self._sigs[signal]["callbacks"].append(slot)
110
+
111
+ def _signal(self, event):
112
+ """This is called by a an action on a widget
113
+
114
+ Within an self.ignore_events context, nothing happens.
115
+
116
+ Tests can execute this method by directly changing the values of
117
+ widget components.
118
+ """
119
+ if not self._ignoring_events:
120
+ wn = "-".join([event.obj.name, event.name])
121
+ if wn in self._map and self._map[wn] in self._sigs:
122
+ self._emit(self._map[wn], event.new)
123
+
124
+ @contextlib.contextmanager
125
+ def ignore_events(self):
126
+ """Temporarily turn off events processing in this instance
127
+
128
+ (does not propagate to children)
129
+ """
130
+ self._ignoring_events = True
131
+ try:
132
+ yield
133
+ finally:
134
+ self._ignoring_events = False
135
+
136
+ def _emit(self, sig, value=None):
137
+ """An event happened, call its callbacks
138
+
139
+ This method can be used in tests to simulate message passing without
140
+ directly changing visual elements.
141
+
142
+ Calling of callbacks will halt whenever one returns False.
143
+ """
144
+ logger.log(self._sigs[sig]["log"], f"{sig}: {value}")
145
+ for callback in self._sigs[sig]["callbacks"]:
146
+ if isinstance(callback, str):
147
+ self._emit(callback)
148
+ else:
149
+ try:
150
+ # running callbacks should not break the interface
151
+ ret = callback(value)
152
+ if ret is False:
153
+ break
154
+ except Exception as e:
155
+ logger.exception(
156
+ "Exception (%s) while executing callback for signal: %s",
157
+ e,
158
+ sig,
159
+ )
160
+
161
+ def show(self, threads=False):
162
+ """Open a new browser tab and display this instance's interface"""
163
+ self.panel.show(threads=threads, verbose=False)
164
+ return self
165
+
166
+
167
+ class SingleSelect(SigSlot):
168
+ """A multiselect which only allows you to select one item for an event"""
169
+
170
+ signals = ["_selected", "selected"] # the first is internal
171
+ slots = ["set_options", "set_selection", "add", "clear", "select"]
172
+
173
+ def __init__(self, **kwargs):
174
+ self.kwargs = kwargs
175
+ super().__init__()
176
+
177
+ def _setup(self):
178
+ self.panel = pn.widgets.MultiSelect(**self.kwargs)
179
+ self._register(self.panel, "_selected", "value")
180
+ self._register(None, "selected")
181
+ self.connect("_selected", self.select_one)
182
+
183
+ def _signal(self, *args, **kwargs):
184
+ super()._signal(*args, **kwargs)
185
+
186
+ def select_one(self, *_):
187
+ with self.ignore_events():
188
+ val = [self.panel.value[-1]] if self.panel.value else []
189
+ self.panel.value = val
190
+ self._emit("selected", self.panel.value)
191
+
192
+ def set_options(self, options):
193
+ self.panel.options = options
194
+
195
+ def clear(self):
196
+ self.panel.options = []
197
+
198
+ @property
199
+ def value(self):
200
+ return self.panel.value
201
+
202
+ def set_selection(self, selection):
203
+ self.panel.value = [selection]
204
+
205
+
206
+ class FileSelector(SigSlot):
207
+ """Panel-based graphical file selector widget
208
+
209
+ Instances of this widget are interactive and can be displayed in jupyter by having
210
+ them as the output of a cell, or in a separate browser tab using ``.show()``.
211
+ """
212
+
213
+ signals = [
214
+ "protocol_changed",
215
+ "selection_changed",
216
+ "directory_entered",
217
+ "home_clicked",
218
+ "up_clicked",
219
+ "go_clicked",
220
+ "filters_changed",
221
+ ]
222
+ slots = ["set_filters", "go_home"]
223
+
224
+ def __init__(self, url=None, filters=None, ignore=None, kwargs=None):
225
+ """
226
+
227
+ Parameters
228
+ ----------
229
+ url : str (optional)
230
+ Initial value of the URL to populate the dialog; should include protocol
231
+ filters : list(str) (optional)
232
+ File endings to include in the listings. If not included, all files are
233
+ allowed. Does not affect directories.
234
+ If given, the endings will appear as checkboxes in the interface
235
+ ignore : list(str) (optional)
236
+ Regex(s) of file basename patterns to ignore, e.g., "\\." for typical
237
+ hidden files on posix
238
+ kwargs : dict (optional)
239
+ To pass to file system instance
240
+ """
241
+ if url:
242
+ self.init_protocol, url = split_protocol(url)
243
+ else:
244
+ self.init_protocol, url = "file", os.getcwd()
245
+ self.init_url = url
246
+ self.init_kwargs = (kwargs if isinstance(kwargs, str) else str(kwargs)) or "{}"
247
+ self.filters = filters
248
+ self.ignore = [re.compile(i) for i in ignore or []]
249
+ self._fs = None
250
+ super().__init__()
251
+
252
+ def _setup(self):
253
+ self.url = pn.widgets.TextInput(
254
+ name="url",
255
+ value=self.init_url,
256
+ align="end",
257
+ sizing_mode="stretch_width",
258
+ width_policy="max",
259
+ )
260
+ self.protocol = pn.widgets.Select(
261
+ options=sorted(known_implementations),
262
+ value=self.init_protocol,
263
+ name="protocol",
264
+ align="center",
265
+ )
266
+ self.kwargs = pn.widgets.TextInput(
267
+ name="kwargs", value=self.init_kwargs, align="center"
268
+ )
269
+ self.go = pn.widgets.Button(name="⇨", align="end", width=45)
270
+ self.main = SingleSelect(size=10)
271
+ self.home = pn.widgets.Button(name="🏠", width=40, height=30, align="end")
272
+ self.up = pn.widgets.Button(name="‹", width=30, height=30, align="end")
273
+
274
+ self._register(self.protocol, "protocol_changed", auto=True)
275
+ self._register(self.go, "go_clicked", "clicks", auto=True)
276
+ self._register(self.up, "up_clicked", "clicks", auto=True)
277
+ self._register(self.home, "home_clicked", "clicks", auto=True)
278
+ self._register(None, "selection_changed")
279
+ self.main.connect("selected", self.selection_changed)
280
+ self._register(None, "directory_entered")
281
+ self.prev_protocol = self.protocol.value
282
+ self.prev_kwargs = self.storage_options
283
+
284
+ self.filter_sel = pn.widgets.CheckBoxGroup(
285
+ value=[], options=[], inline=False, align="end", width_policy="min"
286
+ )
287
+ self._register(self.filter_sel, "filters_changed", auto=True)
288
+
289
+ self.panel = pn.Column(
290
+ pn.Row(self.protocol, self.kwargs),
291
+ pn.Row(self.home, self.up, self.url, self.go, self.filter_sel),
292
+ self.main.panel,
293
+ )
294
+ self.set_filters(self.filters)
295
+ self.go_clicked()
296
+
297
+ def set_filters(self, filters=None):
298
+ self.filters = filters
299
+ if filters:
300
+ self.filter_sel.options = filters
301
+ self.filter_sel.value = filters
302
+ else:
303
+ self.filter_sel.options = []
304
+ self.filter_sel.value = []
305
+
306
+ @property
307
+ def storage_options(self):
308
+ """Value of the kwargs box as a dictionary"""
309
+ return ast.literal_eval(self.kwargs.value) or {}
310
+
311
+ @property
312
+ def fs(self):
313
+ """Current filesystem instance"""
314
+ if self._fs is None:
315
+ cls = get_filesystem_class(self.protocol.value)
316
+ self._fs = cls(**self.storage_options)
317
+ return self._fs
318
+
319
+ @property
320
+ def urlpath(self):
321
+ """URL of currently selected item"""
322
+ return (
323
+ (f"{self.protocol.value}://{self.main.value[0]}")
324
+ if self.main.value
325
+ else None
326
+ )
327
+
328
+ def open_file(self, mode="rb", compression=None, encoding=None):
329
+ """Create OpenFile instance for the currently selected item
330
+
331
+ For example, in a notebook you might do something like
332
+
333
+ .. code-block::
334
+
335
+ [ ]: sel = FileSelector(); sel
336
+
337
+ # user selects their file
338
+
339
+ [ ]: with sel.open_file('rb') as f:
340
+ ... out = f.read()
341
+
342
+ Parameters
343
+ ----------
344
+ mode: str (optional)
345
+ Open mode for the file.
346
+ compression: str (optional)
347
+ The interact with the file as compressed. Set to 'infer' to guess
348
+ compression from the file ending
349
+ encoding: str (optional)
350
+ If using text mode, use this encoding; defaults to UTF8.
351
+ """
352
+ if self.urlpath is None:
353
+ raise ValueError("No file selected")
354
+ return OpenFile(self.fs, self.urlpath, mode, compression, encoding)
355
+
356
+ def filters_changed(self, values):
357
+ self.filters = values
358
+ self.go_clicked()
359
+
360
+ def selection_changed(self, *_):
361
+ if self.urlpath is None:
362
+ return
363
+ if self.fs.isdir(self.urlpath):
364
+ self.url.value = self.fs._strip_protocol(self.urlpath)
365
+ self.go_clicked()
366
+
367
+ def go_clicked(self, *_):
368
+ if (
369
+ self.prev_protocol != self.protocol.value
370
+ or self.prev_kwargs != self.storage_options
371
+ ):
372
+ self._fs = None # causes fs to be recreated
373
+ self.prev_protocol = self.protocol.value
374
+ self.prev_kwargs = self.storage_options
375
+ listing = sorted(
376
+ self.fs.ls(self.url.value, detail=True), key=lambda x: x["name"]
377
+ )
378
+ listing = [
379
+ l
380
+ for l in listing
381
+ if not any(i.match(l["name"].rsplit("/", 1)[-1]) for i in self.ignore)
382
+ ]
383
+ folders = {
384
+ "📁 " + o["name"].rsplit("/", 1)[-1]: o["name"]
385
+ for o in listing
386
+ if o["type"] == "directory"
387
+ }
388
+ files = {
389
+ "📄 " + o["name"].rsplit("/", 1)[-1]: o["name"]
390
+ for o in listing
391
+ if o["type"] == "file"
392
+ }
393
+ if self.filters:
394
+ files = {
395
+ k: v
396
+ for k, v in files.items()
397
+ if any(v.endswith(ext) for ext in self.filters)
398
+ }
399
+ self.main.set_options(dict(**folders, **files))
400
+
401
+ def protocol_changed(self, *_):
402
+ self._fs = None
403
+ self.main.options = []
404
+ self.url.value = ""
405
+
406
+ def home_clicked(self, *_):
407
+ self.protocol.value = self.init_protocol
408
+ self.kwargs.value = self.init_kwargs
409
+ self.url.value = self.init_url
410
+ self.go_clicked()
411
+
412
+ def up_clicked(self, *_):
413
+ self.url.value = self.fs._parent(self.url.value)
414
+ self.go_clicked()
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/fsspec/spec.py ADDED
@@ -0,0 +1,1982 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import io
4
+ import logging
5
+ import os
6
+ import threading
7
+ import warnings
8
+ import weakref
9
+ from errno import ESPIPE
10
+ from glob import has_magic
11
+ from hashlib import sha256
12
+ from typing import ClassVar
13
+
14
+ from .callbacks import DEFAULT_CALLBACK
15
+ from .config import apply_config, conf
16
+ from .dircache import DirCache
17
+ from .transaction import Transaction
18
+ from .utils import (
19
+ _unstrip_protocol,
20
+ glob_translate,
21
+ isfilelike,
22
+ other_paths,
23
+ read_block,
24
+ stringify_path,
25
+ tokenize,
26
+ )
27
+
28
+ logger = logging.getLogger("fsspec")
29
+
30
+
31
+ def make_instance(cls, args, kwargs):
32
+ return cls(*args, **kwargs)
33
+
34
+
35
+ class _Cached(type):
36
+ """
37
+ Metaclass for caching file system instances.
38
+
39
+ Notes
40
+ -----
41
+ Instances are cached according to
42
+
43
+ * The values of the class attributes listed in `_extra_tokenize_attributes`
44
+ * The arguments passed to ``__init__``.
45
+
46
+ This creates an additional reference to the filesystem, which prevents the
47
+ filesystem from being garbage collected when all *user* references go away.
48
+ A call to the :meth:`AbstractFileSystem.clear_instance_cache` must *also*
49
+ be made for a filesystem instance to be garbage collected.
50
+ """
51
+
52
+ def __init__(cls, *args, **kwargs):
53
+ super().__init__(*args, **kwargs)
54
+ # Note: we intentionally create a reference here, to avoid garbage
55
+ # collecting instances when all other references are gone. To really
56
+ # delete a FileSystem, the cache must be cleared.
57
+ if conf.get("weakref_instance_cache"): # pragma: no cover
58
+ # debug option for analysing fork/spawn conditions
59
+ cls._cache = weakref.WeakValueDictionary()
60
+ else:
61
+ cls._cache = {}
62
+ cls._pid = os.getpid()
63
+
64
+ def __call__(cls, *args, **kwargs):
65
+ kwargs = apply_config(cls, kwargs)
66
+ extra_tokens = tuple(
67
+ getattr(cls, attr, None) for attr in cls._extra_tokenize_attributes
68
+ )
69
+ token = tokenize(
70
+ cls, cls._pid, threading.get_ident(), *args, *extra_tokens, **kwargs
71
+ )
72
+ skip = kwargs.pop("skip_instance_cache", False)
73
+ if os.getpid() != cls._pid:
74
+ cls._cache.clear()
75
+ cls._pid = os.getpid()
76
+ if not skip and cls.cachable and token in cls._cache:
77
+ cls._latest = token
78
+ return cls._cache[token]
79
+ else:
80
+ obj = super().__call__(*args, **kwargs)
81
+ # Setting _fs_token here causes some static linters to complain.
82
+ obj._fs_token_ = token
83
+ obj.storage_args = args
84
+ obj.storage_options = kwargs
85
+ if obj.async_impl and obj.mirror_sync_methods:
86
+ from .asyn import mirror_sync_methods
87
+
88
+ mirror_sync_methods(obj)
89
+
90
+ if cls.cachable and not skip:
91
+ cls._latest = token
92
+ cls._cache[token] = obj
93
+ return obj
94
+
95
+
96
+ class AbstractFileSystem(metaclass=_Cached):
97
+ """
98
+ An abstract super-class for pythonic file-systems
99
+
100
+ Implementations are expected to be compatible with or, better, subclass
101
+ from here.
102
+ """
103
+
104
+ cachable = True # this class can be cached, instances reused
105
+ _cached = False
106
+ blocksize = 2**22
107
+ sep = "/"
108
+ protocol: ClassVar[str | tuple[str, ...]] = "abstract"
109
+ _latest = None
110
+ async_impl = False
111
+ mirror_sync_methods = False
112
+ root_marker = "" # For some FSs, may require leading '/' or other character
113
+ transaction_type = Transaction
114
+
115
+ #: Extra *class attributes* that should be considered when hashing.
116
+ _extra_tokenize_attributes = ()
117
+
118
+ def __init__(self, *args, **storage_options):
119
+ """Create and configure file-system instance
120
+
121
+ Instances may be cachable, so if similar enough arguments are seen
122
+ a new instance is not required. The token attribute exists to allow
123
+ implementations to cache instances if they wish.
124
+
125
+ A reasonable default should be provided if there are no arguments.
126
+
127
+ Subclasses should call this method.
128
+
129
+ Parameters
130
+ ----------
131
+ use_listings_cache, listings_expiry_time, max_paths:
132
+ passed to ``DirCache``, if the implementation supports
133
+ directory listing caching. Pass use_listings_cache=False
134
+ to disable such caching.
135
+ skip_instance_cache: bool
136
+ If this is a cachable implementation, pass True here to force
137
+ creating a new instance even if a matching instance exists, and prevent
138
+ storing this instance.
139
+ asynchronous: bool
140
+ loop: asyncio-compatible IOLoop or None
141
+ """
142
+ if self._cached:
143
+ # reusing instance, don't change
144
+ return
145
+ self._cached = True
146
+ self._intrans = False
147
+ self._transaction = None
148
+ self._invalidated_caches_in_transaction = []
149
+ self.dircache = DirCache(**storage_options)
150
+
151
+ if storage_options.pop("add_docs", None):
152
+ warnings.warn("add_docs is no longer supported.", FutureWarning)
153
+
154
+ if storage_options.pop("add_aliases", None):
155
+ warnings.warn("add_aliases has been removed.", FutureWarning)
156
+ # This is set in _Cached
157
+ self._fs_token_ = None
158
+
159
+ @property
160
+ def fsid(self):
161
+ """Persistent filesystem id that can be used to compare filesystems
162
+ across sessions.
163
+ """
164
+ raise NotImplementedError
165
+
166
+ @property
167
+ def _fs_token(self):
168
+ return self._fs_token_
169
+
170
+ def __dask_tokenize__(self):
171
+ return self._fs_token
172
+
173
+ def __hash__(self):
174
+ return int(self._fs_token, 16)
175
+
176
+ def __eq__(self, other):
177
+ return isinstance(other, type(self)) and self._fs_token == other._fs_token
178
+
179
+ def __reduce__(self):
180
+ return make_instance, (type(self), self.storage_args, self.storage_options)
181
+
182
+ @classmethod
183
+ def _strip_protocol(cls, path):
184
+ """Turn path from fully-qualified to file-system-specific
185
+
186
+ May require FS-specific handling, e.g., for relative paths or links.
187
+ """
188
+ if isinstance(path, list):
189
+ return [cls._strip_protocol(p) for p in path]
190
+ path = stringify_path(path)
191
+ protos = (cls.protocol,) if isinstance(cls.protocol, str) else cls.protocol
192
+ for protocol in protos:
193
+ if path.startswith(protocol + "://"):
194
+ path = path[len(protocol) + 3 :]
195
+ elif path.startswith(protocol + "::"):
196
+ path = path[len(protocol) + 2 :]
197
+ path = path.rstrip("/")
198
+ # use of root_marker to make minimum required path, e.g., "/"
199
+ return path or cls.root_marker
200
+
201
+ def unstrip_protocol(self, name: str) -> str:
202
+ """Format FS-specific path to generic, including protocol"""
203
+ protos = (self.protocol,) if isinstance(self.protocol, str) else self.protocol
204
+ for protocol in protos:
205
+ if name.startswith(f"{protocol}://"):
206
+ return name
207
+ return f"{protos[0]}://{name}"
208
+
209
+ @staticmethod
210
+ def _get_kwargs_from_urls(path):
211
+ """If kwargs can be encoded in the paths, extract them here
212
+
213
+ This should happen before instantiation of the class; incoming paths
214
+ then should be amended to strip the options in methods.
215
+
216
+ Examples may look like an sftp path "sftp://user@host:/my/path", where
217
+ the user and host should become kwargs and later get stripped.
218
+ """
219
+ # by default, nothing happens
220
+ return {}
221
+
222
+ @classmethod
223
+ def current(cls):
224
+ """Return the most recently instantiated FileSystem
225
+
226
+ If no instance has been created, then create one with defaults
227
+ """
228
+ if cls._latest in cls._cache:
229
+ return cls._cache[cls._latest]
230
+ return cls()
231
+
232
+ @property
233
+ def transaction(self):
234
+ """A context within which files are committed together upon exit
235
+
236
+ Requires the file class to implement `.commit()` and `.discard()`
237
+ for the normal and exception cases.
238
+ """
239
+ if self._transaction is None:
240
+ self._transaction = self.transaction_type(self)
241
+ return self._transaction
242
+
243
+ def start_transaction(self):
244
+ """Begin write transaction for deferring files, non-context version"""
245
+ self._intrans = True
246
+ self._transaction = self.transaction_type(self)
247
+ return self.transaction
248
+
249
+ def end_transaction(self):
250
+ """Finish write transaction, non-context version"""
251
+ self.transaction.complete()
252
+ self._transaction = None
253
+ # The invalid cache must be cleared after the transaction is completed.
254
+ for path in self._invalidated_caches_in_transaction:
255
+ self.invalidate_cache(path)
256
+ self._invalidated_caches_in_transaction.clear()
257
+
258
+ def invalidate_cache(self, path=None):
259
+ """
260
+ Discard any cached directory information
261
+
262
+ Parameters
263
+ ----------
264
+ path: string or None
265
+ If None, clear all listings cached else listings at or under given
266
+ path.
267
+ """
268
+ # Not necessary to implement invalidation mechanism, may have no cache.
269
+ # But if have, you should call this method of parent class from your
270
+ # subclass to ensure expiring caches after transacations correctly.
271
+ # See the implementation of FTPFileSystem in ftp.py
272
+ if self._intrans:
273
+ self._invalidated_caches_in_transaction.append(path)
274
+
275
+ def mkdir(self, path, create_parents=True, **kwargs):
276
+ """
277
+ Create directory entry at path
278
+
279
+ For systems that don't have true directories, may create an for
280
+ this instance only and not touch the real filesystem
281
+
282
+ Parameters
283
+ ----------
284
+ path: str
285
+ location
286
+ create_parents: bool
287
+ if True, this is equivalent to ``makedirs``
288
+ kwargs:
289
+ may be permissions, etc.
290
+ """
291
+ pass # not necessary to implement, may not have directories
292
+
293
+ def makedirs(self, path, exist_ok=False):
294
+ """Recursively make directories
295
+
296
+ Creates directory at path and any intervening required directories.
297
+ Raises exception if, for instance, the path already exists but is a
298
+ file.
299
+
300
+ Parameters
301
+ ----------
302
+ path: str
303
+ leaf directory name
304
+ exist_ok: bool (False)
305
+ If False, will error if the target already exists
306
+ """
307
+ pass # not necessary to implement, may not have directories
308
+
309
+ def rmdir(self, path):
310
+ """Remove a directory, if empty"""
311
+ pass # not necessary to implement, may not have directories
312
+
313
+ def ls(self, path, detail=True, **kwargs):
314
+ """List objects at path.
315
+
316
+ This should include subdirectories and files at that location. The
317
+ difference between a file and a directory must be clear when details
318
+ are requested.
319
+
320
+ The specific keys, or perhaps a FileInfo class, or similar, is TBD,
321
+ but must be consistent across implementations.
322
+ Must include:
323
+
324
+ - full path to the entry (without protocol)
325
+ - size of the entry, in bytes. If the value cannot be determined, will
326
+ be ``None``.
327
+ - type of entry, "file", "directory" or other
328
+
329
+ Additional information
330
+ may be present, appropriate to the file-system, e.g., generation,
331
+ checksum, etc.
332
+
333
+ May use refresh=True|False to allow use of self._ls_from_cache to
334
+ check for a saved listing and avoid calling the backend. This would be
335
+ common where listing may be expensive.
336
+
337
+ Parameters
338
+ ----------
339
+ path: str
340
+ detail: bool
341
+ if True, gives a list of dictionaries, where each is the same as
342
+ the result of ``info(path)``. If False, gives a list of paths
343
+ (str).
344
+ kwargs: may have additional backend-specific options, such as version
345
+ information
346
+
347
+ Returns
348
+ -------
349
+ List of strings if detail is False, or list of directory information
350
+ dicts if detail is True.
351
+ """
352
+ raise NotImplementedError
353
+
354
+ def _ls_from_cache(self, path):
355
+ """Check cache for listing
356
+
357
+ Returns listing, if found (may be empty list for a directly that exists
358
+ but contains nothing), None if not in cache.
359
+ """
360
+ parent = self._parent(path)
361
+ try:
362
+ return self.dircache[path.rstrip("/")]
363
+ except KeyError:
364
+ pass
365
+ try:
366
+ files = [
367
+ f
368
+ for f in self.dircache[parent]
369
+ if f["name"] == path
370
+ or (f["name"] == path.rstrip("/") and f["type"] == "directory")
371
+ ]
372
+ if len(files) == 0:
373
+ # parent dir was listed but did not contain this file
374
+ raise FileNotFoundError(path)
375
+ return files
376
+ except KeyError:
377
+ pass
378
+
379
+ def walk(self, path, maxdepth=None, topdown=True, on_error="omit", **kwargs):
380
+ """Return all files belows path
381
+
382
+ List all files, recursing into subdirectories; output is iterator-style,
383
+ like ``os.walk()``. For a simple list of files, ``find()`` is available.
384
+
385
+ When topdown is True, the caller can modify the dirnames list in-place (perhaps
386
+ using del or slice assignment), and walk() will
387
+ only recurse into the subdirectories whose names remain in dirnames;
388
+ this can be used to prune the search, impose a specific order of visiting,
389
+ or even to inform walk() about directories the caller creates or renames before
390
+ it resumes walk() again.
391
+ Modifying dirnames when topdown is False has no effect. (see os.walk)
392
+
393
+ Note that the "files" outputted will include anything that is not
394
+ a directory, such as links.
395
+
396
+ Parameters
397
+ ----------
398
+ path: str
399
+ Root to recurse into
400
+ maxdepth: int
401
+ Maximum recursion depth. None means limitless, but not recommended
402
+ on link-based file-systems.
403
+ topdown: bool (True)
404
+ Whether to walk the directory tree from the top downwards or from
405
+ the bottom upwards.
406
+ on_error: "omit", "raise", a collable
407
+ if omit (default), path with exception will simply be empty;
408
+ If raise, an underlying exception will be raised;
409
+ if callable, it will be called with a single OSError instance as argument
410
+ kwargs: passed to ``ls``
411
+ """
412
+ if maxdepth is not None and maxdepth < 1:
413
+ raise ValueError("maxdepth must be at least 1")
414
+
415
+ path = self._strip_protocol(path)
416
+ full_dirs = {}
417
+ dirs = {}
418
+ files = {}
419
+
420
+ detail = kwargs.pop("detail", False)
421
+ try:
422
+ listing = self.ls(path, detail=True, **kwargs)
423
+ except (FileNotFoundError, OSError) as e:
424
+ if on_error == "raise":
425
+ raise
426
+ elif callable(on_error):
427
+ on_error(e)
428
+ if detail:
429
+ return path, {}, {}
430
+ return path, [], []
431
+
432
+ for info in listing:
433
+ # each info name must be at least [path]/part , but here
434
+ # we check also for names like [path]/part/
435
+ pathname = info["name"].rstrip("/")
436
+ name = pathname.rsplit("/", 1)[-1]
437
+ if info["type"] == "directory" and pathname != path:
438
+ # do not include "self" path
439
+ full_dirs[name] = pathname
440
+ dirs[name] = info
441
+ elif pathname == path:
442
+ # file-like with same name as give path
443
+ files[""] = info
444
+ else:
445
+ files[name] = info
446
+
447
+ if not detail:
448
+ dirs = list(dirs)
449
+ files = list(files)
450
+
451
+ if topdown:
452
+ # Yield before recursion if walking top down
453
+ yield path, dirs, files
454
+
455
+ if maxdepth is not None:
456
+ maxdepth -= 1
457
+ if maxdepth < 1:
458
+ if not topdown:
459
+ yield path, dirs, files
460
+ return
461
+
462
+ for d in dirs:
463
+ yield from self.walk(
464
+ full_dirs[d],
465
+ maxdepth=maxdepth,
466
+ detail=detail,
467
+ topdown=topdown,
468
+ **kwargs,
469
+ )
470
+
471
+ if not topdown:
472
+ # Yield after recursion if walking bottom up
473
+ yield path, dirs, files
474
+
475
+ def find(self, path, maxdepth=None, withdirs=False, detail=False, **kwargs):
476
+ """List all files below path.
477
+
478
+ Like posix ``find`` command without conditions
479
+
480
+ Parameters
481
+ ----------
482
+ path : str
483
+ maxdepth: int or None
484
+ If not None, the maximum number of levels to descend
485
+ withdirs: bool
486
+ Whether to include directory paths in the output. This is True
487
+ when used by glob, but users usually only want files.
488
+ kwargs are passed to ``ls``.
489
+ """
490
+ # TODO: allow equivalent of -name parameter
491
+ path = self._strip_protocol(path)
492
+ out = {}
493
+
494
+ # Add the root directory if withdirs is requested
495
+ # This is needed for posix glob compliance
496
+ if withdirs and path != "" and self.isdir(path):
497
+ out[path] = self.info(path)
498
+
499
+ for _, dirs, files in self.walk(path, maxdepth, detail=True, **kwargs):
500
+ if withdirs:
501
+ files.update(dirs)
502
+ out.update({info["name"]: info for name, info in files.items()})
503
+ if not out and self.isfile(path):
504
+ # walk works on directories, but find should also return [path]
505
+ # when path happens to be a file
506
+ out[path] = {}
507
+ names = sorted(out)
508
+ if not detail:
509
+ return names
510
+ else:
511
+ return {name: out[name] for name in names}
512
+
513
+ def du(self, path, total=True, maxdepth=None, withdirs=False, **kwargs):
514
+ """Space used by files and optionally directories within a path
515
+
516
+ Directory size does not include the size of its contents.
517
+
518
+ Parameters
519
+ ----------
520
+ path: str
521
+ total: bool
522
+ Whether to sum all the file sizes
523
+ maxdepth: int or None
524
+ Maximum number of directory levels to descend, None for unlimited.
525
+ withdirs: bool
526
+ Whether to include directory paths in the output.
527
+ kwargs: passed to ``find``
528
+
529
+ Returns
530
+ -------
531
+ Dict of {path: size} if total=False, or int otherwise, where numbers
532
+ refer to bytes used.
533
+ """
534
+ sizes = {}
535
+ if withdirs and self.isdir(path):
536
+ # Include top-level directory in output
537
+ info = self.info(path)
538
+ sizes[info["name"]] = info["size"]
539
+ for f in self.find(path, maxdepth=maxdepth, withdirs=withdirs, **kwargs):
540
+ info = self.info(f)
541
+ sizes[info["name"]] = info["size"]
542
+ if total:
543
+ return sum(sizes.values())
544
+ else:
545
+ return sizes
546
+
547
+ def glob(self, path, maxdepth=None, **kwargs):
548
+ """
549
+ Find files by glob-matching.
550
+
551
+ If the path ends with '/', only folders are returned.
552
+
553
+ We support ``"**"``,
554
+ ``"?"`` and ``"[..]"``. We do not support ^ for pattern negation.
555
+
556
+ The `maxdepth` option is applied on the first `**` found in the path.
557
+
558
+ kwargs are passed to ``ls``.
559
+ """
560
+ if maxdepth is not None and maxdepth < 1:
561
+ raise ValueError("maxdepth must be at least 1")
562
+
563
+ import re
564
+
565
+ seps = (os.path.sep, os.path.altsep) if os.path.altsep else (os.path.sep,)
566
+ ends_with_sep = path.endswith(seps) # _strip_protocol strips trailing slash
567
+ path = self._strip_protocol(path)
568
+ append_slash_to_dirname = ends_with_sep or path.endswith(
569
+ tuple(sep + "**" for sep in seps)
570
+ )
571
+ idx_star = path.find("*") if path.find("*") >= 0 else len(path)
572
+ idx_qmark = path.find("?") if path.find("?") >= 0 else len(path)
573
+ idx_brace = path.find("[") if path.find("[") >= 0 else len(path)
574
+
575
+ min_idx = min(idx_star, idx_qmark, idx_brace)
576
+
577
+ detail = kwargs.pop("detail", False)
578
+
579
+ if not has_magic(path):
580
+ if self.exists(path, **kwargs):
581
+ if not detail:
582
+ return [path]
583
+ else:
584
+ return {path: self.info(path, **kwargs)}
585
+ else:
586
+ if not detail:
587
+ return [] # glob of non-existent returns empty
588
+ else:
589
+ return {}
590
+ elif "/" in path[:min_idx]:
591
+ min_idx = path[:min_idx].rindex("/")
592
+ root = path[: min_idx + 1]
593
+ depth = path[min_idx + 1 :].count("/") + 1
594
+ else:
595
+ root = ""
596
+ depth = path[min_idx + 1 :].count("/") + 1
597
+
598
+ if "**" in path:
599
+ if maxdepth is not None:
600
+ idx_double_stars = path.find("**")
601
+ depth_double_stars = path[idx_double_stars:].count("/") + 1
602
+ depth = depth - depth_double_stars + maxdepth
603
+ else:
604
+ depth = None
605
+
606
+ allpaths = self.find(root, maxdepth=depth, withdirs=True, detail=True, **kwargs)
607
+
608
+ pattern = glob_translate(path + ("/" if ends_with_sep else ""))
609
+ pattern = re.compile(pattern)
610
+
611
+ out = {
612
+ p: info
613
+ for p, info in sorted(allpaths.items())
614
+ if pattern.match(
615
+ (
616
+ p + "/"
617
+ if append_slash_to_dirname and info["type"] == "directory"
618
+ else p
619
+ )
620
+ )
621
+ }
622
+
623
+ if detail:
624
+ return out
625
+ else:
626
+ return list(out)
627
+
628
+ def exists(self, path, **kwargs):
629
+ """Is there a file at the given path"""
630
+ try:
631
+ self.info(path, **kwargs)
632
+ return True
633
+ except: # noqa: E722
634
+ # any exception allowed bar FileNotFoundError?
635
+ return False
636
+
637
+ def lexists(self, path, **kwargs):
638
+ """If there is a file at the given path (including
639
+ broken links)"""
640
+ return self.exists(path)
641
+
642
+ def info(self, path, **kwargs):
643
+ """Give details of entry at path
644
+
645
+ Returns a single dictionary, with exactly the same information as ``ls``
646
+ would with ``detail=True``.
647
+
648
+ The default implementation should calls ls and could be overridden by a
649
+ shortcut. kwargs are passed on to ```ls()``.
650
+
651
+ Some file systems might not be able to measure the file's size, in
652
+ which case, the returned dict will include ``'size': None``.
653
+
654
+ Returns
655
+ -------
656
+ dict with keys: name (full path in the FS), size (in bytes), type (file,
657
+ directory, or something else) and other FS-specific keys.
658
+ """
659
+ path = self._strip_protocol(path)
660
+ out = self.ls(self._parent(path), detail=True, **kwargs)
661
+ out = [o for o in out if o["name"].rstrip("/") == path]
662
+ if out:
663
+ return out[0]
664
+ out = self.ls(path, detail=True, **kwargs)
665
+ path = path.rstrip("/")
666
+ out1 = [o for o in out if o["name"].rstrip("/") == path]
667
+ if len(out1) == 1:
668
+ if "size" not in out1[0]:
669
+ out1[0]["size"] = None
670
+ return out1[0]
671
+ elif len(out1) > 1 or out:
672
+ return {"name": path, "size": 0, "type": "directory"}
673
+ else:
674
+ raise FileNotFoundError(path)
675
+
676
+ def checksum(self, path):
677
+ """Unique value for current version of file
678
+
679
+ If the checksum is the same from one moment to another, the contents
680
+ are guaranteed to be the same. If the checksum changes, the contents
681
+ *might* have changed.
682
+
683
+ This should normally be overridden; default will probably capture
684
+ creation/modification timestamp (which would be good) or maybe
685
+ access timestamp (which would be bad)
686
+ """
687
+ return int(tokenize(self.info(path)), 16)
688
+
689
+ def size(self, path):
690
+ """Size in bytes of file"""
691
+ return self.info(path).get("size", None)
692
+
693
+ def sizes(self, paths):
694
+ """Size in bytes of each file in a list of paths"""
695
+ return [self.size(p) for p in paths]
696
+
697
+ def isdir(self, path):
698
+ """Is this entry directory-like?"""
699
+ try:
700
+ return self.info(path)["type"] == "directory"
701
+ except OSError:
702
+ return False
703
+
704
+ def isfile(self, path):
705
+ """Is this entry file-like?"""
706
+ try:
707
+ return self.info(path)["type"] == "file"
708
+ except: # noqa: E722
709
+ return False
710
+
711
+ def read_text(self, path, encoding=None, errors=None, newline=None, **kwargs):
712
+ """Get the contents of the file as a string.
713
+
714
+ Parameters
715
+ ----------
716
+ path: str
717
+ URL of file on this filesystems
718
+ encoding, errors, newline: same as `open`.
719
+ """
720
+ with self.open(
721
+ path,
722
+ mode="r",
723
+ encoding=encoding,
724
+ errors=errors,
725
+ newline=newline,
726
+ **kwargs,
727
+ ) as f:
728
+ return f.read()
729
+
730
+ def write_text(
731
+ self, path, value, encoding=None, errors=None, newline=None, **kwargs
732
+ ):
733
+ """Write the text to the given file.
734
+
735
+ An existing file will be overwritten.
736
+
737
+ Parameters
738
+ ----------
739
+ path: str
740
+ URL of file on this filesystems
741
+ value: str
742
+ Text to write.
743
+ encoding, errors, newline: same as `open`.
744
+ """
745
+ with self.open(
746
+ path,
747
+ mode="w",
748
+ encoding=encoding,
749
+ errors=errors,
750
+ newline=newline,
751
+ **kwargs,
752
+ ) as f:
753
+ return f.write(value)
754
+
755
+ def cat_file(self, path, start=None, end=None, **kwargs):
756
+ """Get the content of a file
757
+
758
+ Parameters
759
+ ----------
760
+ path: URL of file on this filesystems
761
+ start, end: int
762
+ Bytes limits of the read. If negative, backwards from end,
763
+ like usual python slices. Either can be None for start or
764
+ end of file, respectively
765
+ kwargs: passed to ``open()``.
766
+ """
767
+ # explicitly set buffering off?
768
+ with self.open(path, "rb", **kwargs) as f:
769
+ if start is not None:
770
+ if start >= 0:
771
+ f.seek(start)
772
+ else:
773
+ f.seek(max(0, f.size + start))
774
+ if end is not None:
775
+ if end < 0:
776
+ end = f.size + end
777
+ return f.read(end - f.tell())
778
+ return f.read()
779
+
780
+ def pipe_file(self, path, value, **kwargs):
781
+ """Set the bytes of given file"""
782
+ with self.open(path, "wb", **kwargs) as f:
783
+ f.write(value)
784
+
785
+ def pipe(self, path, value=None, **kwargs):
786
+ """Put value into path
787
+
788
+ (counterpart to ``cat``)
789
+
790
+ Parameters
791
+ ----------
792
+ path: string or dict(str, bytes)
793
+ If a string, a single remote location to put ``value`` bytes; if a dict,
794
+ a mapping of {path: bytesvalue}.
795
+ value: bytes, optional
796
+ If using a single path, these are the bytes to put there. Ignored if
797
+ ``path`` is a dict
798
+ """
799
+ if isinstance(path, str):
800
+ self.pipe_file(self._strip_protocol(path), value, **kwargs)
801
+ elif isinstance(path, dict):
802
+ for k, v in path.items():
803
+ self.pipe_file(self._strip_protocol(k), v, **kwargs)
804
+ else:
805
+ raise ValueError("path must be str or dict")
806
+
807
+ def cat_ranges(
808
+ self, paths, starts, ends, max_gap=None, on_error="return", **kwargs
809
+ ):
810
+ """Get the contents of byte ranges from one or more files
811
+
812
+ Parameters
813
+ ----------
814
+ paths: list
815
+ A list of of filepaths on this filesystems
816
+ starts, ends: int or list
817
+ Bytes limits of the read. If using a single int, the same value will be
818
+ used to read all the specified files.
819
+ """
820
+ if max_gap is not None:
821
+ raise NotImplementedError
822
+ if not isinstance(paths, list):
823
+ raise TypeError
824
+ if not isinstance(starts, list):
825
+ starts = [starts] * len(paths)
826
+ if not isinstance(ends, list):
827
+ ends = [ends] * len(paths)
828
+ if len(starts) != len(paths) or len(ends) != len(paths):
829
+ raise ValueError
830
+ out = []
831
+ for p, s, e in zip(paths, starts, ends):
832
+ try:
833
+ out.append(self.cat_file(p, s, e))
834
+ except Exception as e:
835
+ if on_error == "return":
836
+ out.append(e)
837
+ else:
838
+ raise
839
+ return out
840
+
841
+ def cat(self, path, recursive=False, on_error="raise", **kwargs):
842
+ """Fetch (potentially multiple) paths' contents
843
+
844
+ Parameters
845
+ ----------
846
+ recursive: bool
847
+ If True, assume the path(s) are directories, and get all the
848
+ contained files
849
+ on_error : "raise", "omit", "return"
850
+ If raise, an underlying exception will be raised (converted to KeyError
851
+ if the type is in self.missing_exceptions); if omit, keys with exception
852
+ will simply not be included in the output; if "return", all keys are
853
+ included in the output, but the value will be bytes or an exception
854
+ instance.
855
+ kwargs: passed to cat_file
856
+
857
+ Returns
858
+ -------
859
+ dict of {path: contents} if there are multiple paths
860
+ or the path has been otherwise expanded
861
+ """
862
+ paths = self.expand_path(path, recursive=recursive)
863
+ if (
864
+ len(paths) > 1
865
+ or isinstance(path, list)
866
+ or paths[0] != self._strip_protocol(path)
867
+ ):
868
+ out = {}
869
+ for path in paths:
870
+ try:
871
+ out[path] = self.cat_file(path, **kwargs)
872
+ except Exception as e:
873
+ if on_error == "raise":
874
+ raise
875
+ if on_error == "return":
876
+ out[path] = e
877
+ return out
878
+ else:
879
+ return self.cat_file(paths[0], **kwargs)
880
+
881
+ def get_file(self, rpath, lpath, callback=DEFAULT_CALLBACK, outfile=None, **kwargs):
882
+ """Copy single remote file to local"""
883
+ from .implementations.local import LocalFileSystem
884
+
885
+ if isfilelike(lpath):
886
+ outfile = lpath
887
+ elif self.isdir(rpath):
888
+ os.makedirs(lpath, exist_ok=True)
889
+ return None
890
+
891
+ fs = LocalFileSystem(auto_mkdir=True)
892
+ fs.makedirs(fs._parent(lpath), exist_ok=True)
893
+
894
+ with self.open(rpath, "rb", **kwargs) as f1:
895
+ if outfile is None:
896
+ outfile = open(lpath, "wb")
897
+
898
+ try:
899
+ callback.set_size(getattr(f1, "size", None))
900
+ data = True
901
+ while data:
902
+ data = f1.read(self.blocksize)
903
+ segment_len = outfile.write(data)
904
+ if segment_len is None:
905
+ segment_len = len(data)
906
+ callback.relative_update(segment_len)
907
+ finally:
908
+ if not isfilelike(lpath):
909
+ outfile.close()
910
+
911
+ def get(
912
+ self,
913
+ rpath,
914
+ lpath,
915
+ recursive=False,
916
+ callback=DEFAULT_CALLBACK,
917
+ maxdepth=None,
918
+ **kwargs,
919
+ ):
920
+ """Copy file(s) to local.
921
+
922
+ Copies a specific file or tree of files (if recursive=True). If lpath
923
+ ends with a "/", it will be assumed to be a directory, and target files
924
+ will go within. Can submit a list of paths, which may be glob-patterns
925
+ and will be expanded.
926
+
927
+ Calls get_file for each source.
928
+ """
929
+ if isinstance(lpath, list) and isinstance(rpath, list):
930
+ # No need to expand paths when both source and destination
931
+ # are provided as lists
932
+ rpaths = rpath
933
+ lpaths = lpath
934
+ else:
935
+ from .implementations.local import (
936
+ LocalFileSystem,
937
+ make_path_posix,
938
+ trailing_sep,
939
+ )
940
+
941
+ source_is_str = isinstance(rpath, str)
942
+ rpaths = self.expand_path(rpath, recursive=recursive, maxdepth=maxdepth)
943
+ if source_is_str and (not recursive or maxdepth is not None):
944
+ # Non-recursive glob does not copy directories
945
+ rpaths = [p for p in rpaths if not (trailing_sep(p) or self.isdir(p))]
946
+ if not rpaths:
947
+ return
948
+
949
+ if isinstance(lpath, str):
950
+ lpath = make_path_posix(lpath)
951
+
952
+ source_is_file = len(rpaths) == 1
953
+ dest_is_dir = isinstance(lpath, str) and (
954
+ trailing_sep(lpath) or LocalFileSystem().isdir(lpath)
955
+ )
956
+
957
+ exists = source_is_str and (
958
+ (has_magic(rpath) and source_is_file)
959
+ or (not has_magic(rpath) and dest_is_dir and not trailing_sep(rpath))
960
+ )
961
+ lpaths = other_paths(
962
+ rpaths,
963
+ lpath,
964
+ exists=exists,
965
+ flatten=not source_is_str,
966
+ )
967
+
968
+ callback.set_size(len(lpaths))
969
+ for lpath, rpath in callback.wrap(zip(lpaths, rpaths)):
970
+ with callback.branched(rpath, lpath) as child:
971
+ self.get_file(rpath, lpath, callback=child, **kwargs)
972
+
973
+ def put_file(self, lpath, rpath, callback=DEFAULT_CALLBACK, **kwargs):
974
+ """Copy single file to remote"""
975
+ if os.path.isdir(lpath):
976
+ self.makedirs(rpath, exist_ok=True)
977
+ return None
978
+
979
+ with open(lpath, "rb") as f1:
980
+ size = f1.seek(0, 2)
981
+ callback.set_size(size)
982
+ f1.seek(0)
983
+
984
+ self.mkdirs(self._parent(os.fspath(rpath)), exist_ok=True)
985
+ with self.open(rpath, "wb", **kwargs) as f2:
986
+ while f1.tell() < size:
987
+ data = f1.read(self.blocksize)
988
+ segment_len = f2.write(data)
989
+ if segment_len is None:
990
+ segment_len = len(data)
991
+ callback.relative_update(segment_len)
992
+
993
+ def put(
994
+ self,
995
+ lpath,
996
+ rpath,
997
+ recursive=False,
998
+ callback=DEFAULT_CALLBACK,
999
+ maxdepth=None,
1000
+ **kwargs,
1001
+ ):
1002
+ """Copy file(s) from local.
1003
+
1004
+ Copies a specific file or tree of files (if recursive=True). If rpath
1005
+ ends with a "/", it will be assumed to be a directory, and target files
1006
+ will go within.
1007
+
1008
+ Calls put_file for each source.
1009
+ """
1010
+ if isinstance(lpath, list) and isinstance(rpath, list):
1011
+ # No need to expand paths when both source and destination
1012
+ # are provided as lists
1013
+ rpaths = rpath
1014
+ lpaths = lpath
1015
+ else:
1016
+ from .implementations.local import (
1017
+ LocalFileSystem,
1018
+ make_path_posix,
1019
+ trailing_sep,
1020
+ )
1021
+
1022
+ source_is_str = isinstance(lpath, str)
1023
+ if source_is_str:
1024
+ lpath = make_path_posix(lpath)
1025
+ fs = LocalFileSystem()
1026
+ lpaths = fs.expand_path(lpath, recursive=recursive, maxdepth=maxdepth)
1027
+ if source_is_str and (not recursive or maxdepth is not None):
1028
+ # Non-recursive glob does not copy directories
1029
+ lpaths = [p for p in lpaths if not (trailing_sep(p) or fs.isdir(p))]
1030
+ if not lpaths:
1031
+ return
1032
+
1033
+ source_is_file = len(lpaths) == 1
1034
+ dest_is_dir = isinstance(rpath, str) and (
1035
+ trailing_sep(rpath) or self.isdir(rpath)
1036
+ )
1037
+
1038
+ rpath = (
1039
+ self._strip_protocol(rpath)
1040
+ if isinstance(rpath, str)
1041
+ else [self._strip_protocol(p) for p in rpath]
1042
+ )
1043
+ exists = source_is_str and (
1044
+ (has_magic(lpath) and source_is_file)
1045
+ or (not has_magic(lpath) and dest_is_dir and not trailing_sep(lpath))
1046
+ )
1047
+ rpaths = other_paths(
1048
+ lpaths,
1049
+ rpath,
1050
+ exists=exists,
1051
+ flatten=not source_is_str,
1052
+ )
1053
+
1054
+ callback.set_size(len(rpaths))
1055
+ for lpath, rpath in callback.wrap(zip(lpaths, rpaths)):
1056
+ with callback.branched(lpath, rpath) as child:
1057
+ self.put_file(lpath, rpath, callback=child, **kwargs)
1058
+
1059
+ def head(self, path, size=1024):
1060
+ """Get the first ``size`` bytes from file"""
1061
+ with self.open(path, "rb") as f:
1062
+ return f.read(size)
1063
+
1064
+ def tail(self, path, size=1024):
1065
+ """Get the last ``size`` bytes from file"""
1066
+ with self.open(path, "rb") as f:
1067
+ f.seek(max(-size, -f.size), 2)
1068
+ return f.read()
1069
+
1070
+ def cp_file(self, path1, path2, **kwargs):
1071
+ raise NotImplementedError
1072
+
1073
+ def copy(
1074
+ self, path1, path2, recursive=False, maxdepth=None, on_error=None, **kwargs
1075
+ ):
1076
+ """Copy within two locations in the filesystem
1077
+
1078
+ on_error : "raise", "ignore"
1079
+ If raise, any not-found exceptions will be raised; if ignore any
1080
+ not-found exceptions will cause the path to be skipped; defaults to
1081
+ raise unless recursive is true, where the default is ignore
1082
+ """
1083
+ if on_error is None and recursive:
1084
+ on_error = "ignore"
1085
+ elif on_error is None:
1086
+ on_error = "raise"
1087
+
1088
+ if isinstance(path1, list) and isinstance(path2, list):
1089
+ # No need to expand paths when both source and destination
1090
+ # are provided as lists
1091
+ paths1 = path1
1092
+ paths2 = path2
1093
+ else:
1094
+ from .implementations.local import trailing_sep
1095
+
1096
+ source_is_str = isinstance(path1, str)
1097
+ paths1 = self.expand_path(path1, recursive=recursive, maxdepth=maxdepth)
1098
+ if source_is_str and (not recursive or maxdepth is not None):
1099
+ # Non-recursive glob does not copy directories
1100
+ paths1 = [p for p in paths1 if not (trailing_sep(p) or self.isdir(p))]
1101
+ if not paths1:
1102
+ return
1103
+
1104
+ source_is_file = len(paths1) == 1
1105
+ dest_is_dir = isinstance(path2, str) and (
1106
+ trailing_sep(path2) or self.isdir(path2)
1107
+ )
1108
+
1109
+ exists = source_is_str and (
1110
+ (has_magic(path1) and source_is_file)
1111
+ or (not has_magic(path1) and dest_is_dir and not trailing_sep(path1))
1112
+ )
1113
+ paths2 = other_paths(
1114
+ paths1,
1115
+ path2,
1116
+ exists=exists,
1117
+ flatten=not source_is_str,
1118
+ )
1119
+
1120
+ for p1, p2 in zip(paths1, paths2):
1121
+ try:
1122
+ self.cp_file(p1, p2, **kwargs)
1123
+ except FileNotFoundError:
1124
+ if on_error == "raise":
1125
+ raise
1126
+
1127
+ def expand_path(self, path, recursive=False, maxdepth=None, **kwargs):
1128
+ """Turn one or more globs or directories into a list of all matching paths
1129
+ to files or directories.
1130
+
1131
+ kwargs are passed to ``glob`` or ``find``, which may in turn call ``ls``
1132
+ """
1133
+
1134
+ if maxdepth is not None and maxdepth < 1:
1135
+ raise ValueError("maxdepth must be at least 1")
1136
+
1137
+ if isinstance(path, (str, os.PathLike)):
1138
+ out = self.expand_path([path], recursive, maxdepth)
1139
+ else:
1140
+ out = set()
1141
+ path = [self._strip_protocol(p) for p in path]
1142
+ for p in path:
1143
+ if has_magic(p):
1144
+ bit = set(self.glob(p, maxdepth=maxdepth, **kwargs))
1145
+ out |= bit
1146
+ if recursive:
1147
+ # glob call above expanded one depth so if maxdepth is defined
1148
+ # then decrement it in expand_path call below. If it is zero
1149
+ # after decrementing then avoid expand_path call.
1150
+ if maxdepth is not None and maxdepth <= 1:
1151
+ continue
1152
+ out |= set(
1153
+ self.expand_path(
1154
+ list(bit),
1155
+ recursive=recursive,
1156
+ maxdepth=maxdepth - 1 if maxdepth is not None else None,
1157
+ **kwargs,
1158
+ )
1159
+ )
1160
+ continue
1161
+ elif recursive:
1162
+ rec = set(
1163
+ self.find(
1164
+ p, maxdepth=maxdepth, withdirs=True, detail=False, **kwargs
1165
+ )
1166
+ )
1167
+ out |= rec
1168
+ if p not in out and (recursive is False or self.exists(p)):
1169
+ # should only check once, for the root
1170
+ out.add(p)
1171
+ if not out:
1172
+ raise FileNotFoundError(path)
1173
+ return sorted(out)
1174
+
1175
+ def mv(self, path1, path2, recursive=False, maxdepth=None, **kwargs):
1176
+ """Move file(s) from one location to another"""
1177
+ if path1 == path2:
1178
+ logger.debug("%s mv: The paths are the same, so no files were moved.", self)
1179
+ else:
1180
+ # explicitly raise exception to prevent data corruption
1181
+ self.copy(
1182
+ path1, path2, recursive=recursive, maxdepth=maxdepth, onerror="raise"
1183
+ )
1184
+ self.rm(path1, recursive=recursive)
1185
+
1186
+ def rm_file(self, path):
1187
+ """Delete a file"""
1188
+ self._rm(path)
1189
+
1190
+ def _rm(self, path):
1191
+ """Delete one file"""
1192
+ # this is the old name for the method, prefer rm_file
1193
+ raise NotImplementedError
1194
+
1195
+ def rm(self, path, recursive=False, maxdepth=None):
1196
+ """Delete files.
1197
+
1198
+ Parameters
1199
+ ----------
1200
+ path: str or list of str
1201
+ File(s) to delete.
1202
+ recursive: bool
1203
+ If file(s) are directories, recursively delete contents and then
1204
+ also remove the directory
1205
+ maxdepth: int or None
1206
+ Depth to pass to walk for finding files to delete, if recursive.
1207
+ If None, there will be no limit and infinite recursion may be
1208
+ possible.
1209
+ """
1210
+ path = self.expand_path(path, recursive=recursive, maxdepth=maxdepth)
1211
+ for p in reversed(path):
1212
+ self.rm_file(p)
1213
+
1214
+ @classmethod
1215
+ def _parent(cls, path):
1216
+ path = cls._strip_protocol(path)
1217
+ if "/" in path:
1218
+ parent = path.rsplit("/", 1)[0].lstrip(cls.root_marker)
1219
+ return cls.root_marker + parent
1220
+ else:
1221
+ return cls.root_marker
1222
+
1223
+ def _open(
1224
+ self,
1225
+ path,
1226
+ mode="rb",
1227
+ block_size=None,
1228
+ autocommit=True,
1229
+ cache_options=None,
1230
+ **kwargs,
1231
+ ):
1232
+ """Return raw bytes-mode file-like from the file-system"""
1233
+ return AbstractBufferedFile(
1234
+ self,
1235
+ path,
1236
+ mode,
1237
+ block_size,
1238
+ autocommit,
1239
+ cache_options=cache_options,
1240
+ **kwargs,
1241
+ )
1242
+
1243
+ def open(
1244
+ self,
1245
+ path,
1246
+ mode="rb",
1247
+ block_size=None,
1248
+ cache_options=None,
1249
+ compression=None,
1250
+ **kwargs,
1251
+ ):
1252
+ """
1253
+ Return a file-like object from the filesystem
1254
+
1255
+ The resultant instance must function correctly in a context ``with``
1256
+ block.
1257
+
1258
+ Parameters
1259
+ ----------
1260
+ path: str
1261
+ Target file
1262
+ mode: str like 'rb', 'w'
1263
+ See builtin ``open()``
1264
+ block_size: int
1265
+ Some indication of buffering - this is a value in bytes
1266
+ cache_options : dict, optional
1267
+ Extra arguments to pass through to the cache.
1268
+ compression: string or None
1269
+ If given, open file using compression codec. Can either be a compression
1270
+ name (a key in ``fsspec.compression.compr``) or "infer" to guess the
1271
+ compression from the filename suffix.
1272
+ encoding, errors, newline: passed on to TextIOWrapper for text mode
1273
+ """
1274
+ import io
1275
+
1276
+ path = self._strip_protocol(path)
1277
+ if "b" not in mode:
1278
+ mode = mode.replace("t", "") + "b"
1279
+
1280
+ text_kwargs = {
1281
+ k: kwargs.pop(k)
1282
+ for k in ["encoding", "errors", "newline"]
1283
+ if k in kwargs
1284
+ }
1285
+ return io.TextIOWrapper(
1286
+ self.open(
1287
+ path,
1288
+ mode,
1289
+ block_size=block_size,
1290
+ cache_options=cache_options,
1291
+ compression=compression,
1292
+ **kwargs,
1293
+ ),
1294
+ **text_kwargs,
1295
+ )
1296
+ else:
1297
+ ac = kwargs.pop("autocommit", not self._intrans)
1298
+ f = self._open(
1299
+ path,
1300
+ mode=mode,
1301
+ block_size=block_size,
1302
+ autocommit=ac,
1303
+ cache_options=cache_options,
1304
+ **kwargs,
1305
+ )
1306
+ if compression is not None:
1307
+ from fsspec.compression import compr
1308
+ from fsspec.core import get_compression
1309
+
1310
+ compression = get_compression(path, compression)
1311
+ compress = compr[compression]
1312
+ f = compress(f, mode=mode[0])
1313
+
1314
+ if not ac and "r" not in mode:
1315
+ self.transaction.files.append(f)
1316
+ return f
1317
+
1318
+ def touch(self, path, truncate=True, **kwargs):
1319
+ """Create empty file, or update timestamp
1320
+
1321
+ Parameters
1322
+ ----------
1323
+ path: str
1324
+ file location
1325
+ truncate: bool
1326
+ If True, always set file size to 0; if False, update timestamp and
1327
+ leave file unchanged, if backend allows this
1328
+ """
1329
+ if truncate or not self.exists(path):
1330
+ with self.open(path, "wb", **kwargs):
1331
+ pass
1332
+ else:
1333
+ raise NotImplementedError # update timestamp, if possible
1334
+
1335
+ def ukey(self, path):
1336
+ """Hash of file properties, to tell if it has changed"""
1337
+ return sha256(str(self.info(path)).encode()).hexdigest()
1338
+
1339
+ def read_block(self, fn, offset, length, delimiter=None):
1340
+ """Read a block of bytes from
1341
+
1342
+ Starting at ``offset`` of the file, read ``length`` bytes. If
1343
+ ``delimiter`` is set then we ensure that the read starts and stops at
1344
+ delimiter boundaries that follow the locations ``offset`` and ``offset
1345
+ + length``. If ``offset`` is zero then we start at zero. The
1346
+ bytestring returned WILL include the end delimiter string.
1347
+
1348
+ If offset+length is beyond the eof, reads to eof.
1349
+
1350
+ Parameters
1351
+ ----------
1352
+ fn: string
1353
+ Path to filename
1354
+ offset: int
1355
+ Byte offset to start read
1356
+ length: int
1357
+ Number of bytes to read. If None, read to end.
1358
+ delimiter: bytes (optional)
1359
+ Ensure reading starts and stops at delimiter bytestring
1360
+
1361
+ Examples
1362
+ --------
1363
+ >>> fs.read_block('data/file.csv', 0, 13) # doctest: +SKIP
1364
+ b'Alice, 100\\nBo'
1365
+ >>> fs.read_block('data/file.csv', 0, 13, delimiter=b'\\n') # doctest: +SKIP
1366
+ b'Alice, 100\\nBob, 200\\n'
1367
+
1368
+ Use ``length=None`` to read to the end of the file.
1369
+ >>> fs.read_block('data/file.csv', 0, None, delimiter=b'\\n') # doctest: +SKIP
1370
+ b'Alice, 100\\nBob, 200\\nCharlie, 300'
1371
+
1372
+ See Also
1373
+ --------
1374
+ :func:`fsspec.utils.read_block`
1375
+ """
1376
+ with self.open(fn, "rb") as f:
1377
+ size = f.size
1378
+ if length is None:
1379
+ length = size
1380
+ if size is not None and offset + length > size:
1381
+ length = size - offset
1382
+ return read_block(f, offset, length, delimiter)
1383
+
1384
+ def to_json(self):
1385
+ """
1386
+ JSON representation of this filesystem instance
1387
+
1388
+ Returns
1389
+ -------
1390
+ str: JSON structure with keys cls (the python location of this class),
1391
+ protocol (text name of this class's protocol, first one in case of
1392
+ multiple), args (positional args, usually empty), and all other
1393
+ kwargs as their own keys.
1394
+ """
1395
+ import json
1396
+
1397
+ cls = type(self)
1398
+ cls = ".".join((cls.__module__, cls.__name__))
1399
+ proto = (
1400
+ self.protocol[0]
1401
+ if isinstance(self.protocol, (tuple, list))
1402
+ else self.protocol
1403
+ )
1404
+ return json.dumps(
1405
+ dict(
1406
+ cls=cls,
1407
+ protocol=proto,
1408
+ args=self.storage_args,
1409
+ **self.storage_options,
1410
+ )
1411
+ )
1412
+
1413
+ @staticmethod
1414
+ def from_json(blob):
1415
+ """
1416
+ Recreate a filesystem instance from JSON representation
1417
+
1418
+ See ``.to_json()`` for the expected structure of the input
1419
+
1420
+ Parameters
1421
+ ----------
1422
+ blob: str
1423
+
1424
+ Returns
1425
+ -------
1426
+ file system instance, not necessarily of this particular class.
1427
+ """
1428
+ import json
1429
+
1430
+ from .registry import _import_class, get_filesystem_class
1431
+
1432
+ dic = json.loads(blob)
1433
+ protocol = dic.pop("protocol")
1434
+ try:
1435
+ cls = _import_class(dic.pop("cls"))
1436
+ except (ImportError, ValueError, RuntimeError, KeyError):
1437
+ cls = get_filesystem_class(protocol)
1438
+ return cls(*dic.pop("args", ()), **dic)
1439
+
1440
+ def _get_pyarrow_filesystem(self):
1441
+ """
1442
+ Make a version of the FS instance which will be acceptable to pyarrow
1443
+ """
1444
+ # all instances already also derive from pyarrow
1445
+ return self
1446
+
1447
+ def get_mapper(self, root="", check=False, create=False, missing_exceptions=None):
1448
+ """Create key/value store based on this file-system
1449
+
1450
+ Makes a MutableMapping interface to the FS at the given root path.
1451
+ See ``fsspec.mapping.FSMap`` for further details.
1452
+ """
1453
+ from .mapping import FSMap
1454
+
1455
+ return FSMap(
1456
+ root,
1457
+ self,
1458
+ check=check,
1459
+ create=create,
1460
+ missing_exceptions=missing_exceptions,
1461
+ )
1462
+
1463
+ @classmethod
1464
+ def clear_instance_cache(cls):
1465
+ """
1466
+ Clear the cache of filesystem instances.
1467
+
1468
+ Notes
1469
+ -----
1470
+ Unless overridden by setting the ``cachable`` class attribute to False,
1471
+ the filesystem class stores a reference to newly created instances. This
1472
+ prevents Python's normal rules around garbage collection from working,
1473
+ since the instances refcount will not drop to zero until
1474
+ ``clear_instance_cache`` is called.
1475
+ """
1476
+ cls._cache.clear()
1477
+
1478
+ def created(self, path):
1479
+ """Return the created timestamp of a file as a datetime.datetime"""
1480
+ raise NotImplementedError
1481
+
1482
+ def modified(self, path):
1483
+ """Return the modified timestamp of a file as a datetime.datetime"""
1484
+ raise NotImplementedError
1485
+
1486
+ # ------------------------------------------------------------------------
1487
+ # Aliases
1488
+
1489
+ def read_bytes(self, path, start=None, end=None, **kwargs):
1490
+ """Alias of `AbstractFileSystem.cat_file`."""
1491
+ return self.cat_file(path, start=start, end=end, **kwargs)
1492
+
1493
+ def write_bytes(self, path, value, **kwargs):
1494
+ """Alias of `AbstractFileSystem.pipe_file`."""
1495
+ self.pipe_file(path, value, **kwargs)
1496
+
1497
+ def makedir(self, path, create_parents=True, **kwargs):
1498
+ """Alias of `AbstractFileSystem.mkdir`."""
1499
+ return self.mkdir(path, create_parents=create_parents, **kwargs)
1500
+
1501
+ def mkdirs(self, path, exist_ok=False):
1502
+ """Alias of `AbstractFileSystem.makedirs`."""
1503
+ return self.makedirs(path, exist_ok=exist_ok)
1504
+
1505
+ def listdir(self, path, detail=True, **kwargs):
1506
+ """Alias of `AbstractFileSystem.ls`."""
1507
+ return self.ls(path, detail=detail, **kwargs)
1508
+
1509
+ def cp(self, path1, path2, **kwargs):
1510
+ """Alias of `AbstractFileSystem.copy`."""
1511
+ return self.copy(path1, path2, **kwargs)
1512
+
1513
+ def move(self, path1, path2, **kwargs):
1514
+ """Alias of `AbstractFileSystem.mv`."""
1515
+ return self.mv(path1, path2, **kwargs)
1516
+
1517
+ def stat(self, path, **kwargs):
1518
+ """Alias of `AbstractFileSystem.info`."""
1519
+ return self.info(path, **kwargs)
1520
+
1521
+ def disk_usage(self, path, total=True, maxdepth=None, **kwargs):
1522
+ """Alias of `AbstractFileSystem.du`."""
1523
+ return self.du(path, total=total, maxdepth=maxdepth, **kwargs)
1524
+
1525
+ def rename(self, path1, path2, **kwargs):
1526
+ """Alias of `AbstractFileSystem.mv`."""
1527
+ return self.mv(path1, path2, **kwargs)
1528
+
1529
+ def delete(self, path, recursive=False, maxdepth=None):
1530
+ """Alias of `AbstractFileSystem.rm`."""
1531
+ return self.rm(path, recursive=recursive, maxdepth=maxdepth)
1532
+
1533
+ def upload(self, lpath, rpath, recursive=False, **kwargs):
1534
+ """Alias of `AbstractFileSystem.put`."""
1535
+ return self.put(lpath, rpath, recursive=recursive, **kwargs)
1536
+
1537
+ def download(self, rpath, lpath, recursive=False, **kwargs):
1538
+ """Alias of `AbstractFileSystem.get`."""
1539
+ return self.get(rpath, lpath, recursive=recursive, **kwargs)
1540
+
1541
+ def sign(self, path, expiration=100, **kwargs):
1542
+ """Create a signed URL representing the given path
1543
+
1544
+ Some implementations allow temporary URLs to be generated, as a
1545
+ way of delegating credentials.
1546
+
1547
+ Parameters
1548
+ ----------
1549
+ path : str
1550
+ The path on the filesystem
1551
+ expiration : int
1552
+ Number of seconds to enable the URL for (if supported)
1553
+
1554
+ Returns
1555
+ -------
1556
+ URL : str
1557
+ The signed URL
1558
+
1559
+ Raises
1560
+ ------
1561
+ NotImplementedError : if method is not implemented for a filesystem
1562
+ """
1563
+ raise NotImplementedError("Sign is not implemented for this filesystem")
1564
+
1565
+ def _isfilestore(self):
1566
+ # Originally inherited from pyarrow DaskFileSystem. Keeping this
1567
+ # here for backwards compatibility as long as pyarrow uses its
1568
+ # legacy fsspec-compatible filesystems and thus accepts fsspec
1569
+ # filesystems as well
1570
+ return False
1571
+
1572
+
1573
+ class AbstractBufferedFile(io.IOBase):
1574
+ """Convenient class to derive from to provide buffering
1575
+
1576
+ In the case that the backend does not provide a pythonic file-like object
1577
+ already, this class contains much of the logic to build one. The only
1578
+ methods that need to be overridden are ``_upload_chunk``,
1579
+ ``_initiate_upload`` and ``_fetch_range``.
1580
+ """
1581
+
1582
+ DEFAULT_BLOCK_SIZE = 5 * 2**20
1583
+ _details = None
1584
+
1585
+ def __init__(
1586
+ self,
1587
+ fs,
1588
+ path,
1589
+ mode="rb",
1590
+ block_size="default",
1591
+ autocommit=True,
1592
+ cache_type="readahead",
1593
+ cache_options=None,
1594
+ size=None,
1595
+ **kwargs,
1596
+ ):
1597
+ """
1598
+ Template for files with buffered reading and writing
1599
+
1600
+ Parameters
1601
+ ----------
1602
+ fs: instance of FileSystem
1603
+ path: str
1604
+ location in file-system
1605
+ mode: str
1606
+ Normal file modes. Currently only 'wb', 'ab' or 'rb'. Some file
1607
+ systems may be read-only, and some may not support append.
1608
+ block_size: int
1609
+ Buffer size for reading or writing, 'default' for class default
1610
+ autocommit: bool
1611
+ Whether to write to final destination; may only impact what
1612
+ happens when file is being closed.
1613
+ cache_type: {"readahead", "none", "mmap", "bytes"}, default "readahead"
1614
+ Caching policy in read mode. See the definitions in ``core``.
1615
+ cache_options : dict
1616
+ Additional options passed to the constructor for the cache specified
1617
+ by `cache_type`.
1618
+ size: int
1619
+ If given and in read mode, suppressed having to look up the file size
1620
+ kwargs:
1621
+ Gets stored as self.kwargs
1622
+ """
1623
+ from .core import caches
1624
+
1625
+ self.path = path
1626
+ self.fs = fs
1627
+ self.mode = mode
1628
+ self.blocksize = (
1629
+ self.DEFAULT_BLOCK_SIZE if block_size in ["default", None] else block_size
1630
+ )
1631
+ self.loc = 0
1632
+ self.autocommit = autocommit
1633
+ self.end = None
1634
+ self.start = None
1635
+ self.closed = False
1636
+
1637
+ if cache_options is None:
1638
+ cache_options = {}
1639
+
1640
+ if "trim" in kwargs:
1641
+ warnings.warn(
1642
+ "Passing 'trim' to control the cache behavior has been deprecated. "
1643
+ "Specify it within the 'cache_options' argument instead.",
1644
+ FutureWarning,
1645
+ )
1646
+ cache_options["trim"] = kwargs.pop("trim")
1647
+
1648
+ self.kwargs = kwargs
1649
+
1650
+ if mode not in {"ab", "rb", "wb"}:
1651
+ raise NotImplementedError("File mode not supported")
1652
+ if mode == "rb":
1653
+ if size is not None:
1654
+ self.size = size
1655
+ else:
1656
+ self.size = self.details["size"]
1657
+ self.cache = caches[cache_type](
1658
+ self.blocksize, self._fetch_range, self.size, **cache_options
1659
+ )
1660
+ else:
1661
+ self.buffer = io.BytesIO()
1662
+ self.offset = None
1663
+ self.forced = False
1664
+ self.location = None
1665
+
1666
+ @property
1667
+ def details(self):
1668
+ if self._details is None:
1669
+ self._details = self.fs.info(self.path)
1670
+ return self._details
1671
+
1672
+ @details.setter
1673
+ def details(self, value):
1674
+ self._details = value
1675
+ self.size = value["size"]
1676
+
1677
+ @property
1678
+ def full_name(self):
1679
+ return _unstrip_protocol(self.path, self.fs)
1680
+
1681
+ @property
1682
+ def closed(self):
1683
+ # get around this attr being read-only in IOBase
1684
+ # use getattr here, since this can be called during del
1685
+ return getattr(self, "_closed", True)
1686
+
1687
+ @closed.setter
1688
+ def closed(self, c):
1689
+ self._closed = c
1690
+
1691
+ def __hash__(self):
1692
+ if "w" in self.mode:
1693
+ return id(self)
1694
+ else:
1695
+ return int(tokenize(self.details), 16)
1696
+
1697
+ def __eq__(self, other):
1698
+ """Files are equal if they have the same checksum, only in read mode"""
1699
+ if self is other:
1700
+ return True
1701
+ return (
1702
+ isinstance(other, type(self))
1703
+ and self.mode == "rb"
1704
+ and other.mode == "rb"
1705
+ and hash(self) == hash(other)
1706
+ )
1707
+
1708
+ def commit(self):
1709
+ """Move from temp to final destination"""
1710
+
1711
+ def discard(self):
1712
+ """Throw away temporary file"""
1713
+
1714
+ def info(self):
1715
+ """File information about this path"""
1716
+ if "r" in self.mode:
1717
+ return self.details
1718
+ else:
1719
+ raise ValueError("Info not available while writing")
1720
+
1721
+ def tell(self):
1722
+ """Current file location"""
1723
+ return self.loc
1724
+
1725
+ def seek(self, loc, whence=0):
1726
+ """Set current file location
1727
+
1728
+ Parameters
1729
+ ----------
1730
+ loc: int
1731
+ byte location
1732
+ whence: {0, 1, 2}
1733
+ from start of file, current location or end of file, resp.
1734
+ """
1735
+ loc = int(loc)
1736
+ if not self.mode == "rb":
1737
+ raise OSError(ESPIPE, "Seek only available in read mode")
1738
+ if whence == 0:
1739
+ nloc = loc
1740
+ elif whence == 1:
1741
+ nloc = self.loc + loc
1742
+ elif whence == 2:
1743
+ nloc = self.size + loc
1744
+ else:
1745
+ raise ValueError(f"invalid whence ({whence}, should be 0, 1 or 2)")
1746
+ if nloc < 0:
1747
+ raise ValueError("Seek before start of file")
1748
+ self.loc = nloc
1749
+ return self.loc
1750
+
1751
+ def write(self, data):
1752
+ """
1753
+ Write data to buffer.
1754
+
1755
+ Buffer only sent on flush() or if buffer is greater than
1756
+ or equal to blocksize.
1757
+
1758
+ Parameters
1759
+ ----------
1760
+ data: bytes
1761
+ Set of bytes to be written.
1762
+ """
1763
+ if self.mode not in {"wb", "ab"}:
1764
+ raise ValueError("File not in write mode")
1765
+ if self.closed:
1766
+ raise ValueError("I/O operation on closed file.")
1767
+ if self.forced:
1768
+ raise ValueError("This file has been force-flushed, can only close")
1769
+ out = self.buffer.write(data)
1770
+ self.loc += out
1771
+ if self.buffer.tell() >= self.blocksize:
1772
+ self.flush()
1773
+ return out
1774
+
1775
+ def flush(self, force=False):
1776
+ """
1777
+ Write buffered data to backend store.
1778
+
1779
+ Writes the current buffer, if it is larger than the block-size, or if
1780
+ the file is being closed.
1781
+
1782
+ Parameters
1783
+ ----------
1784
+ force: bool
1785
+ When closing, write the last block even if it is smaller than
1786
+ blocks are allowed to be. Disallows further writing to this file.
1787
+ """
1788
+
1789
+ if self.closed:
1790
+ raise ValueError("Flush on closed file")
1791
+ if force and self.forced:
1792
+ raise ValueError("Force flush cannot be called more than once")
1793
+ if force:
1794
+ self.forced = True
1795
+
1796
+ if self.mode not in {"wb", "ab"}:
1797
+ # no-op to flush on read-mode
1798
+ return
1799
+
1800
+ if not force and self.buffer.tell() < self.blocksize:
1801
+ # Defer write on small block
1802
+ return
1803
+
1804
+ if self.offset is None:
1805
+ # Initialize a multipart upload
1806
+ self.offset = 0
1807
+ try:
1808
+ self._initiate_upload()
1809
+ except: # noqa: E722
1810
+ self.closed = True
1811
+ raise
1812
+
1813
+ if self._upload_chunk(final=force) is not False:
1814
+ self.offset += self.buffer.seek(0, 2)
1815
+ self.buffer = io.BytesIO()
1816
+
1817
+ def _upload_chunk(self, final=False):
1818
+ """Write one part of a multi-block file upload
1819
+
1820
+ Parameters
1821
+ ==========
1822
+ final: bool
1823
+ This is the last block, so should complete file, if
1824
+ self.autocommit is True.
1825
+ """
1826
+ # may not yet have been initialized, may need to call _initialize_upload
1827
+
1828
+ def _initiate_upload(self):
1829
+ """Create remote file/upload"""
1830
+ pass
1831
+
1832
+ def _fetch_range(self, start, end):
1833
+ """Get the specified set of bytes from remote"""
1834
+ raise NotImplementedError
1835
+
1836
+ def read(self, length=-1):
1837
+ """
1838
+ Return data from cache, or fetch pieces as necessary
1839
+
1840
+ Parameters
1841
+ ----------
1842
+ length: int (-1)
1843
+ Number of bytes to read; if <0, all remaining bytes.
1844
+ """
1845
+ length = -1 if length is None else int(length)
1846
+ if self.mode != "rb":
1847
+ raise ValueError("File not in read mode")
1848
+ if length < 0:
1849
+ length = self.size - self.loc
1850
+ if self.closed:
1851
+ raise ValueError("I/O operation on closed file.")
1852
+ if length == 0:
1853
+ # don't even bother calling fetch
1854
+ return b""
1855
+ out = self.cache._fetch(self.loc, self.loc + length)
1856
+
1857
+ logger.debug(
1858
+ "%s read: %i - %i %s",
1859
+ self,
1860
+ self.loc,
1861
+ self.loc + length,
1862
+ self.cache._log_stats(),
1863
+ )
1864
+ self.loc += len(out)
1865
+ return out
1866
+
1867
+ def readinto(self, b):
1868
+ """mirrors builtin file's readinto method
1869
+
1870
+ https://docs.python.org/3/library/io.html#io.RawIOBase.readinto
1871
+ """
1872
+ out = memoryview(b).cast("B")
1873
+ data = self.read(out.nbytes)
1874
+ out[: len(data)] = data
1875
+ return len(data)
1876
+
1877
+ def readuntil(self, char=b"\n", blocks=None):
1878
+ """Return data between current position and first occurrence of char
1879
+
1880
+ char is included in the output, except if the end of the tile is
1881
+ encountered first.
1882
+
1883
+ Parameters
1884
+ ----------
1885
+ char: bytes
1886
+ Thing to find
1887
+ blocks: None or int
1888
+ How much to read in each go. Defaults to file blocksize - which may
1889
+ mean a new read on every call.
1890
+ """
1891
+ out = []
1892
+ while True:
1893
+ start = self.tell()
1894
+ part = self.read(blocks or self.blocksize)
1895
+ if len(part) == 0:
1896
+ break
1897
+ found = part.find(char)
1898
+ if found > -1:
1899
+ out.append(part[: found + len(char)])
1900
+ self.seek(start + found + len(char))
1901
+ break
1902
+ out.append(part)
1903
+ return b"".join(out)
1904
+
1905
+ def readline(self):
1906
+ """Read until first occurrence of newline character
1907
+
1908
+ Note that, because of character encoding, this is not necessarily a
1909
+ true line ending.
1910
+ """
1911
+ return self.readuntil(b"\n")
1912
+
1913
+ def __next__(self):
1914
+ out = self.readline()
1915
+ if out:
1916
+ return out
1917
+ raise StopIteration
1918
+
1919
+ def __iter__(self):
1920
+ return self
1921
+
1922
+ def readlines(self):
1923
+ """Return all data, split by the newline character"""
1924
+ data = self.read()
1925
+ lines = data.split(b"\n")
1926
+ out = [l + b"\n" for l in lines[:-1]]
1927
+ if data.endswith(b"\n"):
1928
+ return out
1929
+ else:
1930
+ return out + [lines[-1]]
1931
+ # return list(self) ???
1932
+
1933
+ def readinto1(self, b):
1934
+ return self.readinto(b)
1935
+
1936
+ def close(self):
1937
+ """Close file
1938
+
1939
+ Finalizes writes, discards cache
1940
+ """
1941
+ if getattr(self, "_unclosable", False):
1942
+ return
1943
+ if self.closed:
1944
+ return
1945
+ if self.mode == "rb":
1946
+ self.cache = None
1947
+ else:
1948
+ if not self.forced:
1949
+ self.flush(force=True)
1950
+
1951
+ if self.fs is not None:
1952
+ self.fs.invalidate_cache(self.path)
1953
+ self.fs.invalidate_cache(self.fs._parent(self.path))
1954
+
1955
+ self.closed = True
1956
+
1957
+ def readable(self):
1958
+ """Whether opened for reading"""
1959
+ return self.mode == "rb" and not self.closed
1960
+
1961
+ def seekable(self):
1962
+ """Whether is seekable (only in read mode)"""
1963
+ return self.readable()
1964
+
1965
+ def writable(self):
1966
+ """Whether opened for writing"""
1967
+ return self.mode in {"wb", "ab"} and not self.closed
1968
+
1969
+ def __del__(self):
1970
+ if not self.closed:
1971
+ self.close()
1972
+
1973
+ def __str__(self):
1974
+ return f"<File-like object {type(self.fs).__name__}, {self.path}>"
1975
+
1976
+ __repr__ = __str__
1977
+
1978
+ def __enter__(self):
1979
+ return self
1980
+
1981
+ def __exit__(self, *args):
1982
+ self.close()
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/hf_xet-1.3.2.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/hf_xet-1.3.2.dist-info/METADATA ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: hf-xet
3
+ Version: 1.3.2
4
+ Classifier: Development Status :: 5 - Production/Stable
5
+ Classifier: License :: OSI Approved :: Apache Software License
6
+ Classifier: Programming Language :: Rust
7
+ Classifier: Programming Language :: Python :: Implementation :: CPython
8
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Programming Language :: Python :: 3.8
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Programming Language :: Python :: Free Threading
19
+ Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Requires-Dist: pytest ; extra == 'tests'
22
+ Provides-Extra: tests
23
+ License-File: LICENSE
24
+ Summary: Fast transfer of large files with the Hugging Face Hub.
25
+ Maintainer-email: Rajat Arya <rajat@rajatarya.com>, Jared Sulzdorf <j.sulzdorf@gmail.com>, Di Xiao <di@huggingface.co>, Assaf Vayner <assaf@huggingface.co>, Hoyt Koepke <hoytak@gmail.com>
26
+ License-Expression: Apache-2.0
27
+ Requires-Python: >=3.8
28
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
29
+ Project-URL: Documentation, https://huggingface.co/docs/hub/xet/index
30
+ Project-URL: Homepage, https://github.com/huggingface/xet-core
31
+ Project-URL: Issues, https://github.com/huggingface/xet-core/issues
32
+ Project-URL: Repository, https://github.com/huggingface/xet-core.git
33
+
34
+ <!---
35
+ Copyright 2024 The HuggingFace Team. All rights reserved.
36
+
37
+ Licensed under the Apache License, Version 2.0 (the "License");
38
+ you may not use this file except in compliance with the License.
39
+ You may obtain a copy of the License at
40
+
41
+ http://www.apache.org/licenses/LICENSE-2.0
42
+
43
+ Unless required by applicable law or agreed to in writing, software
44
+ distributed under the License is distributed on an "AS IS" BASIS,
45
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
46
+ See the License for the specific language governing permissions and
47
+ limitations under the License.
48
+ -->
49
+ <p align="center">
50
+ <a href="https://github.com/huggingface/xet-core/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/github/license/huggingface/xet-core.svg?color=blue"></a>
51
+ <a href="https://github.com/huggingface/xet-core/releases"><img alt="GitHub release" src="https://img.shields.io/github/release/huggingface/xet-core.svg"></a>
52
+ <a href="https://github.com/huggingface/xet-core/blob/main/CODE_OF_CONDUCT.md"><img alt="Contributor Covenant" src="https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg"></a>
53
+ </p>
54
+
55
+ <h3 align="center">
56
+ <p>🤗 hf-xet - xet client tech, used in <a target="_blank" href="https://github.com/huggingface/huggingface_hub/">huggingface_hub</a></p>
57
+ </h3>
58
+
59
+ ## Welcome
60
+
61
+ `hf-xet` enables `huggingface_hub` to utilize xet storage for uploading and downloading to HF Hub. Xet storage provides chunk-based deduplication, efficient storage/retrieval with local disk caching, and backwards compatibility with Git LFS. This library is not meant to be used directly, and is instead intended to be used from [huggingface_hub](https://pypi.org/project/huggingface-hub).
62
+
63
+ ## Key features
64
+
65
+ ♻ **chunk-based deduplication implementation**: avoid transferring and storing chunks that are shared across binary files (models, datasets, etc).
66
+
67
+ 🤗 **Python bindings**: bindings for [huggingface_hub](https://github.com/huggingface/huggingface_hub/) package.
68
+
69
+ ↔ **network communications**: concurrent communication to HF Hub Xet backend services (CAS).
70
+
71
+ 🔖 **local disk caching**: chunk-based cache that sits alongside the existing [huggingface_hub disk cache](https://huggingface.co/docs/huggingface_hub/guides/manage-cache).
72
+
73
+ ## Installation
74
+
75
+ Install the `hf_xet` package with [pip](https://pypi.org/project/hf-xet/):
76
+
77
+ ```bash
78
+ pip install hf_xet
79
+ ```
80
+
81
+ ## Quick Start
82
+
83
+ `hf_xet` is not intended to be run independently as it is expected to be used from `huggingface_hub`, so to get started with `huggingface_hub` check out the documentation [here]("https://hf.co/docs/huggingface_hub").
84
+
85
+ ## Contributions (feature requests, bugs, etc.) are encouraged & appreciated 💙💚💛💜🧡❤️
86
+
87
+ Please join us in making hf-xet better. We value everyone's contributions. Code is not the only way to help. Answering questions, helping each other, improving documentation, filing issues all help immensely. If you are interested in contributing (please do!), check out the [contribution guide](https://github.com/huggingface/xet-core/blob/main/CONTRIBUTING.md) for this repository.
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/hf_xet-1.3.2.dist-info/RECORD ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ hf_xet-1.3.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ hf_xet-1.3.2.dist-info/METADATA,sha256=VdBkvkyMsWS6C51XipEai-iZGPIt9ydUqE6iReFQdVI,4882
3
+ hf_xet-1.3.2.dist-info/RECORD,,
4
+ hf_xet-1.3.2.dist-info/WHEEL,sha256=ycZRBBQNh-_vBSCR_X0ck7NlL8NDEI7G-tWH-Cjb8uw,143
5
+ hf_xet-1.3.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
6
+ hf_xet-1.3.2.dist-info/sboms/hf_xet.cyclonedx.json,sha256=Z8lgnfjLuQ1OS7SIhJmZNn9qdETBotCqRMS1kgQOnxE,394826
7
+ hf_xet/__init__.py,sha256=E8UDdyQ8glZ_nve9hHEf22bPang8-RKx4VuApXYeQUo,107
8
+ hf_xet/__pycache__/__init__.cpython-312.pyc,,
9
+ hf_xet/hf_xet.abi3.so,sha256=35NZSsDbLEwZbmB-1aRzdjHhZD_MwMLDqP-tmCeN2gE,10594520
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/hf_xet-1.3.2.dist-info/WHEEL ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.12.4)
3
+ Root-Is-Purelib: false
4
+ Tag: cp37-abi3-manylinux_2_17_x86_64
5
+ Tag: cp37-abi3-manylinux2014_x86_64
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/idna-3.10.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/idna-3.10.dist-info/LICENSE.md ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2013-2024, Kim Davies and contributors.
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are
8
+ met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright
11
+ notice, this list of conditions and the following disclaimer.
12
+
13
+ 2. Redistributions in binary form must reproduce the above copyright
14
+ notice, this list of conditions and the following disclaimer in the
15
+ documentation and/or other materials provided with the distribution.
16
+
17
+ 3. Neither the name of the copyright holder nor the names of its
18
+ contributors may be used to endorse or promote products derived from
19
+ this software without specific prior written permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
27
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/idna-3.10.dist-info/METADATA ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.1
2
+ Name: idna
3
+ Version: 3.10
4
+ Summary: Internationalized Domain Names in Applications (IDNA)
5
+ Author-email: Kim Davies <kim+pypi@gumleaf.org>
6
+ Requires-Python: >=3.6
7
+ Description-Content-Type: text/x-rst
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Intended Audience :: System Administrators
11
+ Classifier: License :: OSI Approved :: BSD License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.6
17
+ Classifier: Programming Language :: Python :: 3.7
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: Implementation :: CPython
25
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
26
+ Classifier: Topic :: Internet :: Name Service (DNS)
27
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
+ Classifier: Topic :: Utilities
29
+ Requires-Dist: ruff >= 0.6.2 ; extra == "all"
30
+ Requires-Dist: mypy >= 1.11.2 ; extra == "all"
31
+ Requires-Dist: pytest >= 8.3.2 ; extra == "all"
32
+ Requires-Dist: flake8 >= 7.1.1 ; extra == "all"
33
+ Project-URL: Changelog, https://github.com/kjd/idna/blob/master/HISTORY.rst
34
+ Project-URL: Issue tracker, https://github.com/kjd/idna/issues
35
+ Project-URL: Source, https://github.com/kjd/idna
36
+ Provides-Extra: all
37
+
38
+ Internationalized Domain Names in Applications (IDNA)
39
+ =====================================================
40
+
41
+ Support for the Internationalized Domain Names in
42
+ Applications (IDNA) protocol as specified in `RFC 5891
43
+ <https://tools.ietf.org/html/rfc5891>`_. This is the latest version of
44
+ the protocol and is sometimes referred to as “IDNA 2008”.
45
+
46
+ This library also provides support for Unicode Technical
47
+ Standard 46, `Unicode IDNA Compatibility Processing
48
+ <https://unicode.org/reports/tr46/>`_.
49
+
50
+ This acts as a suitable replacement for the “encodings.idna”
51
+ module that comes with the Python standard library, but which
52
+ only supports the older superseded IDNA specification (`RFC 3490
53
+ <https://tools.ietf.org/html/rfc3490>`_).
54
+
55
+ Basic functions are simply executed:
56
+
57
+ .. code-block:: pycon
58
+
59
+ >>> import idna
60
+ >>> idna.encode('ドメイン.テスト')
61
+ b'xn--eckwd4c7c.xn--zckzah'
62
+ >>> print(idna.decode('xn--eckwd4c7c.xn--zckzah'))
63
+ ドメイン.テスト
64
+
65
+
66
+ Installation
67
+ ------------
68
+
69
+ This package is available for installation from PyPI:
70
+
71
+ .. code-block:: bash
72
+
73
+ $ python3 -m pip install idna
74
+
75
+
76
+ Usage
77
+ -----
78
+
79
+ For typical usage, the ``encode`` and ``decode`` functions will take a
80
+ domain name argument and perform a conversion to A-labels or U-labels
81
+ respectively.
82
+
83
+ .. code-block:: pycon
84
+
85
+ >>> import idna
86
+ >>> idna.encode('ドメイン.テスト')
87
+ b'xn--eckwd4c7c.xn--zckzah'
88
+ >>> print(idna.decode('xn--eckwd4c7c.xn--zckzah'))
89
+ ドメイン.テスト
90
+
91
+ You may use the codec encoding and decoding methods using the
92
+ ``idna.codec`` module:
93
+
94
+ .. code-block:: pycon
95
+
96
+ >>> import idna.codec
97
+ >>> print('домен.испытание'.encode('idna2008'))
98
+ b'xn--d1acufc.xn--80akhbyknj4f'
99
+ >>> print(b'xn--d1acufc.xn--80akhbyknj4f'.decode('idna2008'))
100
+ домен.испытание
101
+
102
+ Conversions can be applied at a per-label basis using the ``ulabel`` or
103
+ ``alabel`` functions if necessary:
104
+
105
+ .. code-block:: pycon
106
+
107
+ >>> idna.alabel('测试')
108
+ b'xn--0zwm56d'
109
+
110
+ Compatibility Mapping (UTS #46)
111
+ +++++++++++++++++++++++++++++++
112
+
113
+ As described in `RFC 5895 <https://tools.ietf.org/html/rfc5895>`_, the
114
+ IDNA specification does not normalize input from different potential
115
+ ways a user may input a domain name. This functionality, known as
116
+ a “mapping”, is considered by the specification to be a local
117
+ user-interface issue distinct from IDNA conversion functionality.
118
+
119
+ This library provides one such mapping that was developed by the
120
+ Unicode Consortium. Known as `Unicode IDNA Compatibility Processing
121
+ <https://unicode.org/reports/tr46/>`_, it provides for both a regular
122
+ mapping for typical applications, as well as a transitional mapping to
123
+ help migrate from older IDNA 2003 applications. Strings are
124
+ preprocessed according to Section 4.4 “Preprocessing for IDNA2008”
125
+ prior to the IDNA operations.
126
+
127
+ For example, “Königsgäßchen” is not a permissible label as *LATIN
128
+ CAPITAL LETTER K* is not allowed (nor are capital letters in general).
129
+ UTS 46 will convert this into lower case prior to applying the IDNA
130
+ conversion.
131
+
132
+ .. code-block:: pycon
133
+
134
+ >>> import idna
135
+ >>> idna.encode('Königsgäßchen')
136
+ ...
137
+ idna.core.InvalidCodepoint: Codepoint U+004B at position 1 of 'Königsgäßchen' not allowed
138
+ >>> idna.encode('Königsgäßchen', uts46=True)
139
+ b'xn--knigsgchen-b4a3dun'
140
+ >>> print(idna.decode('xn--knigsgchen-b4a3dun'))
141
+ königsgäßchen
142
+
143
+ Transitional processing provides conversions to help transition from
144
+ the older 2003 standard to the current standard. For example, in the
145
+ original IDNA specification, the *LATIN SMALL LETTER SHARP S* (ß) was
146
+ converted into two *LATIN SMALL LETTER S* (ss), whereas in the current
147
+ IDNA specification this conversion is not performed.
148
+
149
+ .. code-block:: pycon
150
+
151
+ >>> idna.encode('Königsgäßchen', uts46=True, transitional=True)
152
+ 'xn--knigsgsschen-lcb0w'
153
+
154
+ Implementers should use transitional processing with caution, only in
155
+ rare cases where conversion from legacy labels to current labels must be
156
+ performed (i.e. IDNA implementations that pre-date 2008). For typical
157
+ applications that just need to convert labels, transitional processing
158
+ is unlikely to be beneficial and could produce unexpected incompatible
159
+ results.
160
+
161
+ ``encodings.idna`` Compatibility
162
+ ++++++++++++++++++++++++++++++++
163
+
164
+ Function calls from the Python built-in ``encodings.idna`` module are
165
+ mapped to their IDNA 2008 equivalents using the ``idna.compat`` module.
166
+ Simply substitute the ``import`` clause in your code to refer to the new
167
+ module name.
168
+
169
+ Exceptions
170
+ ----------
171
+
172
+ All errors raised during the conversion following the specification
173
+ should raise an exception derived from the ``idna.IDNAError`` base
174
+ class.
175
+
176
+ More specific exceptions that may be generated as ``idna.IDNABidiError``
177
+ when the error reflects an illegal combination of left-to-right and
178
+ right-to-left characters in a label; ``idna.InvalidCodepoint`` when
179
+ a specific codepoint is an illegal character in an IDN label (i.e.
180
+ INVALID); and ``idna.InvalidCodepointContext`` when the codepoint is
181
+ illegal based on its positional context (i.e. it is CONTEXTO or CONTEXTJ
182
+ but the contextual requirements are not satisfied.)
183
+
184
+ Building and Diagnostics
185
+ ------------------------
186
+
187
+ The IDNA and UTS 46 functionality relies upon pre-calculated lookup
188
+ tables for performance. These tables are derived from computing against
189
+ eligibility criteria in the respective standards. These tables are
190
+ computed using the command-line script ``tools/idna-data``.
191
+
192
+ This tool will fetch relevant codepoint data from the Unicode repository
193
+ and perform the required calculations to identify eligibility. There are
194
+ three main modes:
195
+
196
+ * ``idna-data make-libdata``. Generates ``idnadata.py`` and
197
+ ``uts46data.py``, the pre-calculated lookup tables used for IDNA and
198
+ UTS 46 conversions. Implementers who wish to track this library against
199
+ a different Unicode version may use this tool to manually generate a
200
+ different version of the ``idnadata.py`` and ``uts46data.py`` files.
201
+
202
+ * ``idna-data make-table``. Generate a table of the IDNA disposition
203
+ (e.g. PVALID, CONTEXTJ, CONTEXTO) in the format found in Appendix
204
+ B.1 of RFC 5892 and the pre-computed tables published by `IANA
205
+ <https://www.iana.org/>`_.
206
+
207
+ * ``idna-data U+0061``. Prints debugging output on the various
208
+ properties associated with an individual Unicode codepoint (in this
209
+ case, U+0061), that are used to assess the IDNA and UTS 46 status of a
210
+ codepoint. This is helpful in debugging or analysis.
211
+
212
+ The tool accepts a number of arguments, described using ``idna-data
213
+ -h``. Most notably, the ``--version`` argument allows the specification
214
+ of the version of Unicode to be used in computing the table data. For
215
+ example, ``idna-data --version 9.0.0 make-libdata`` will generate
216
+ library data against Unicode 9.0.0.
217
+
218
+
219
+ Additional Notes
220
+ ----------------
221
+
222
+ * **Packages**. The latest tagged release version is published in the
223
+ `Python Package Index <https://pypi.org/project/idna/>`_.
224
+
225
+ * **Version support**. This library supports Python 3.6 and higher.
226
+ As this library serves as a low-level toolkit for a variety of
227
+ applications, many of which strive for broad compatibility with older
228
+ Python versions, there is no rush to remove older interpreter support.
229
+ Removing support for older versions should be well justified in that the
230
+ maintenance burden has become too high.
231
+
232
+ * **Python 2**. Python 2 is supported by version 2.x of this library.
233
+ Use "idna<3" in your requirements file if you need this library for
234
+ a Python 2 application. Be advised that these versions are no longer
235
+ actively developed.
236
+
237
+ * **Testing**. The library has a test suite based on each rule of the
238
+ IDNA specification, as well as tests that are provided as part of the
239
+ Unicode Technical Standard 46, `Unicode IDNA Compatibility Processing
240
+ <https://unicode.org/reports/tr46/>`_.
241
+
242
+ * **Emoji**. It is an occasional request to support emoji domains in
243
+ this library. Encoding of symbols like emoji is expressly prohibited by
244
+ the technical standard IDNA 2008 and emoji domains are broadly phased
245
+ out across the domain industry due to associated security risks. For
246
+ now, applications that need to support these non-compliant labels
247
+ may wish to consider trying the encode/decode operation in this library
248
+ first, and then falling back to using `encodings.idna`. See `the Github
249
+ project <https://github.com/kjd/idna/issues/18>`_ for more discussion.
250
+
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/idna-3.10.dist-info/RECORD ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ idna-3.10.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ idna-3.10.dist-info/LICENSE.md,sha256=pZ8LDvNjWHQQmkRhykT_enDVBpboFHZ7-vch1Mmw2w8,1541
3
+ idna-3.10.dist-info/METADATA,sha256=URR5ZyDfQ1PCEGhkYoojqfi2Ra0tau2--lhwG4XSfjI,10158
4
+ idna-3.10.dist-info/RECORD,,
5
+ idna-3.10.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
6
+ idna/__init__.py,sha256=MPqNDLZbXqGaNdXxAFhiqFPKEQXju2jNQhCey6-5eJM,868
7
+ idna/__pycache__/__init__.cpython-312.pyc,,
8
+ idna/__pycache__/codec.cpython-312.pyc,,
9
+ idna/__pycache__/compat.cpython-312.pyc,,
10
+ idna/__pycache__/core.cpython-312.pyc,,
11
+ idna/__pycache__/idnadata.cpython-312.pyc,,
12
+ idna/__pycache__/intranges.cpython-312.pyc,,
13
+ idna/__pycache__/package_data.cpython-312.pyc,,
14
+ idna/__pycache__/uts46data.cpython-312.pyc,,
15
+ idna/codec.py,sha256=PEew3ItwzjW4hymbasnty2N2OXvNcgHB-JjrBuxHPYY,3422
16
+ idna/compat.py,sha256=RzLy6QQCdl9784aFhb2EX9EKGCJjg0P3PilGdeXXcx8,316
17
+ idna/core.py,sha256=YJYyAMnwiQEPjVC4-Fqu_p4CJ6yKKuDGmppBNQNQpFs,13239
18
+ idna/idnadata.py,sha256=W30GcIGvtOWYwAjZj4ZjuouUutC6ffgNuyjJy7fZ-lo,78306
19
+ idna/intranges.py,sha256=amUtkdhYcQG8Zr-CoMM_kVRacxkivC1WgxN1b63KKdU,1898
20
+ idna/package_data.py,sha256=q59S3OXsc5VI8j6vSD0sGBMyk6zZ4vWFREE88yCJYKs,21
21
+ idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ idna/uts46data.py,sha256=rt90K9J40gUSwppDPCrhjgi5AA6pWM65dEGRSf6rIhM,239289
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/idna-3.10.dist-info/WHEEL ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.9.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/nvidia_nccl_cu12-2.27.5.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/nvidia_nccl_cu12-2.27.5.dist-info/METADATA ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: nvidia-nccl-cu12
3
+ Version: 2.27.5
4
+ Summary: NVIDIA Collective Communication Library (NCCL) Runtime
5
+ Home-page: https://developer.nvidia.com/cuda-zone
6
+ Author: Nvidia CUDA Installer Team
7
+ Author-email: compute_installer@nvidia.com
8
+ License: BSD-3-Clause
9
+ Keywords: cuda,nvidia,runtime,machine learning,deep learning
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Education
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: Other/Proprietary License
15
+ Classifier: Natural Language :: English
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.5
18
+ Classifier: Programming Language :: Python :: 3.6
19
+ Classifier: Programming Language :: Python :: 3.7
20
+ Classifier: Programming Language :: Python :: 3.8
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3 :: Only
25
+ Classifier: Topic :: Scientific/Engineering
26
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
27
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
28
+ Classifier: Topic :: Software Development
29
+ Classifier: Topic :: Software Development :: Libraries
30
+ Classifier: Operating System :: Microsoft :: Windows
31
+ Classifier: Operating System :: POSIX :: Linux
32
+ Requires-Python: >=3
33
+ License-File: License.txt
34
+ Dynamic: author
35
+ Dynamic: author-email
36
+ Dynamic: classifier
37
+ Dynamic: description
38
+ Dynamic: home-page
39
+ Dynamic: keywords
40
+ Dynamic: license
41
+ Dynamic: license-file
42
+ Dynamic: requires-python
43
+ Dynamic: summary
44
+
45
+ NCCL (pronounced "Nickel") is a stand-alone library of standard collective communication routines for GPUs, implementing all-reduce, all-gather, reduce, broadcast, and reduce-scatter. It has been optimized to achieve high bandwidth on any platform using PCIe, NVLink, NVswitch, as well as networking using InfiniBand Verbs or TCP/IP sockets.
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/nvidia_nccl_cu12-2.27.5.dist-info/RECORD ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ nvidia/nccl/include/nccl.h,sha256=SyQKH6eYWMqM3_z1wvxArqyos-rsYXKCE3FfCQKro6g,22566
2
+ nvidia/nccl/lib/libnccl.so.2,sha256=HZwQMi76zeaVz2QNsfKfwVhskp8Qh_DhIC0tXnD7cWg,429628328
3
+ nvidia_nccl_cu12-2.27.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
4
+ nvidia_nccl_cu12-2.27.5.dist-info/METADATA,sha256=8QYX3uAOzTd6A-kQPrzyDDMwXQ30cZGlQEmKtF-Kxn0,2019
5
+ nvidia_nccl_cu12-2.27.5.dist-info/RECORD,,
6
+ nvidia_nccl_cu12-2.27.5.dist-info/WHEEL,sha256=gy6FWQgpujK_dnYc155G2NL32NQjpi5ebTEXjh8SGZQ,144
7
+ nvidia_nccl_cu12-2.27.5.dist-info/licenses/License.txt,sha256=DwF0prTgszrCY3W_cpUzB1sy9MUaW2gCo9dC19zcmnY,1895
8
+ nvidia_nccl_cu12-2.27.5.dist-info/top_level.txt,sha256=fTkAtiFuL16nUrB9ytDDtpytz2t0B4NvYTnRzwAhO14,7
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/nvidia_nccl_cu12-2.27.5.dist-info/WHEEL ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-manylinux2014_x86_64
5
+ Tag: py3-none-manylinux_2_17_x86_64
6
+
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/nvidia_nccl_cu12-2.27.5.dist-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ nvidia
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/__init__.py ADDED
@@ -0,0 +1,2485 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
2
+ # Use of this source code is governed by a BSD-style license that can be
3
+ # found in the LICENSE file.
4
+
5
+ """psutil is a cross-platform library for retrieving information on
6
+ running processes and system utilization (CPU, memory, disks, network,
7
+ sensors) in Python. Supported platforms:
8
+
9
+ - Linux
10
+ - Windows
11
+ - macOS
12
+ - FreeBSD
13
+ - OpenBSD
14
+ - NetBSD
15
+ - Sun Solaris
16
+ - AIX
17
+
18
+ Supported Python versions are cPython 3.6+ and PyPy.
19
+ """
20
+
21
+ import collections
22
+ import contextlib
23
+ import datetime
24
+ import functools
25
+ import os
26
+ import signal
27
+ import socket
28
+ import subprocess
29
+ import sys
30
+ import threading
31
+ import time
32
+
33
+ try:
34
+ import pwd
35
+ except ImportError:
36
+ pwd = None
37
+
38
+ from . import _common
39
+ from . import _ntuples as _ntp
40
+ from ._common import AIX
41
+ from ._common import BSD
42
+ from ._common import CONN_CLOSE
43
+ from ._common import CONN_CLOSE_WAIT
44
+ from ._common import CONN_CLOSING
45
+ from ._common import CONN_ESTABLISHED
46
+ from ._common import CONN_FIN_WAIT1
47
+ from ._common import CONN_FIN_WAIT2
48
+ from ._common import CONN_LAST_ACK
49
+ from ._common import CONN_LISTEN
50
+ from ._common import CONN_NONE
51
+ from ._common import CONN_SYN_RECV
52
+ from ._common import CONN_SYN_SENT
53
+ from ._common import CONN_TIME_WAIT
54
+ from ._common import FREEBSD
55
+ from ._common import LINUX
56
+ from ._common import MACOS
57
+ from ._common import NETBSD
58
+ from ._common import NIC_DUPLEX_FULL
59
+ from ._common import NIC_DUPLEX_HALF
60
+ from ._common import NIC_DUPLEX_UNKNOWN
61
+ from ._common import OPENBSD
62
+ from ._common import OSX # deprecated alias
63
+ from ._common import POSIX
64
+ from ._common import POWER_TIME_UNKNOWN
65
+ from ._common import POWER_TIME_UNLIMITED
66
+ from ._common import STATUS_DEAD
67
+ from ._common import STATUS_DISK_SLEEP
68
+ from ._common import STATUS_IDLE
69
+ from ._common import STATUS_LOCKED
70
+ from ._common import STATUS_PARKED
71
+ from ._common import STATUS_RUNNING
72
+ from ._common import STATUS_SLEEPING
73
+ from ._common import STATUS_STOPPED
74
+ from ._common import STATUS_TRACING_STOP
75
+ from ._common import STATUS_WAITING
76
+ from ._common import STATUS_WAKING
77
+ from ._common import STATUS_ZOMBIE
78
+ from ._common import SUNOS
79
+ from ._common import WINDOWS
80
+ from ._common import AccessDenied
81
+ from ._common import Error
82
+ from ._common import NoSuchProcess
83
+ from ._common import TimeoutExpired
84
+ from ._common import ZombieProcess
85
+ from ._common import debug
86
+ from ._common import memoize_when_activated
87
+ from ._common import wrap_numbers as _wrap_numbers
88
+
89
+ if LINUX:
90
+ # This is public API and it will be retrieved from _pslinux.py
91
+ # via sys.modules.
92
+ PROCFS_PATH = "/proc"
93
+
94
+ from . import _pslinux as _psplatform
95
+ from ._pslinux import IOPRIO_CLASS_BE # noqa: F401
96
+ from ._pslinux import IOPRIO_CLASS_IDLE # noqa: F401
97
+ from ._pslinux import IOPRIO_CLASS_NONE # noqa: F401
98
+ from ._pslinux import IOPRIO_CLASS_RT # noqa: F401
99
+
100
+ elif WINDOWS:
101
+ from . import _pswindows as _psplatform
102
+ from ._psutil_windows import ABOVE_NORMAL_PRIORITY_CLASS # noqa: F401
103
+ from ._psutil_windows import BELOW_NORMAL_PRIORITY_CLASS # noqa: F401
104
+ from ._psutil_windows import HIGH_PRIORITY_CLASS # noqa: F401
105
+ from ._psutil_windows import IDLE_PRIORITY_CLASS # noqa: F401
106
+ from ._psutil_windows import NORMAL_PRIORITY_CLASS # noqa: F401
107
+ from ._psutil_windows import REALTIME_PRIORITY_CLASS # noqa: F401
108
+ from ._pswindows import CONN_DELETE_TCB # noqa: F401
109
+ from ._pswindows import IOPRIO_HIGH # noqa: F401
110
+ from ._pswindows import IOPRIO_LOW # noqa: F401
111
+ from ._pswindows import IOPRIO_NORMAL # noqa: F401
112
+ from ._pswindows import IOPRIO_VERYLOW # noqa: F401
113
+
114
+ elif MACOS:
115
+ from . import _psosx as _psplatform
116
+
117
+ elif BSD:
118
+ from . import _psbsd as _psplatform
119
+
120
+ elif SUNOS:
121
+ from . import _pssunos as _psplatform
122
+ from ._pssunos import CONN_BOUND # noqa: F401
123
+ from ._pssunos import CONN_IDLE # noqa: F401
124
+
125
+ # This is public writable API which is read from _pslinux.py and
126
+ # _pssunos.py via sys.modules.
127
+ PROCFS_PATH = "/proc"
128
+
129
+ elif AIX:
130
+ from . import _psaix as _psplatform
131
+
132
+ # This is public API and it will be retrieved from _pslinux.py
133
+ # via sys.modules.
134
+ PROCFS_PATH = "/proc"
135
+
136
+ else: # pragma: no cover
137
+ msg = f"platform {sys.platform} is not supported"
138
+ raise NotImplementedError(msg)
139
+
140
+
141
+ # fmt: off
142
+ __all__ = [
143
+ # exceptions
144
+ "Error", "NoSuchProcess", "ZombieProcess", "AccessDenied",
145
+ "TimeoutExpired",
146
+
147
+ # constants
148
+ "version_info", "__version__",
149
+
150
+ "STATUS_RUNNING", "STATUS_IDLE", "STATUS_SLEEPING", "STATUS_DISK_SLEEP",
151
+ "STATUS_STOPPED", "STATUS_TRACING_STOP", "STATUS_ZOMBIE", "STATUS_DEAD",
152
+ "STATUS_WAKING", "STATUS_LOCKED", "STATUS_WAITING", "STATUS_LOCKED",
153
+ "STATUS_PARKED",
154
+
155
+ "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1",
156
+ "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT",
157
+ "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", "CONN_NONE",
158
+ # "CONN_IDLE", "CONN_BOUND",
159
+
160
+ "AF_LINK",
161
+
162
+ "NIC_DUPLEX_FULL", "NIC_DUPLEX_HALF", "NIC_DUPLEX_UNKNOWN",
163
+
164
+ "POWER_TIME_UNKNOWN", "POWER_TIME_UNLIMITED",
165
+
166
+ "BSD", "FREEBSD", "LINUX", "NETBSD", "OPENBSD", "MACOS", "OSX", "POSIX",
167
+ "SUNOS", "WINDOWS", "AIX",
168
+
169
+ # "RLIM_INFINITY", "RLIMIT_AS", "RLIMIT_CORE", "RLIMIT_CPU", "RLIMIT_DATA",
170
+ # "RLIMIT_FSIZE", "RLIMIT_LOCKS", "RLIMIT_MEMLOCK", "RLIMIT_NOFILE",
171
+ # "RLIMIT_NPROC", "RLIMIT_RSS", "RLIMIT_STACK", "RLIMIT_MSGQUEUE",
172
+ # "RLIMIT_NICE", "RLIMIT_RTPRIO", "RLIMIT_RTTIME", "RLIMIT_SIGPENDING",
173
+
174
+ # classes
175
+ "Process", "Popen",
176
+
177
+ # functions
178
+ "pid_exists", "pids", "process_iter", "wait_procs", # proc
179
+ "virtual_memory", "swap_memory", # memory
180
+ "cpu_times", "cpu_percent", "cpu_times_percent", "cpu_count", # cpu
181
+ "cpu_stats", # "cpu_freq", "getloadavg"
182
+ "net_io_counters", "net_connections", "net_if_addrs", # network
183
+ "net_if_stats",
184
+ "disk_io_counters", "disk_partitions", "disk_usage", # disk
185
+ # "sensors_temperatures", "sensors_battery", "sensors_fans" # sensors
186
+ "users", "boot_time", # others
187
+ ]
188
+ # fmt: on
189
+
190
+
191
+ __all__.extend(_psplatform.__extra__all__)
192
+
193
+ # Linux, FreeBSD
194
+ if hasattr(_psplatform.Process, "rlimit"):
195
+ # Populate global namespace with RLIM* constants.
196
+ _globals = globals()
197
+ _name = None
198
+ for _name in dir(_psplatform.cext):
199
+ if _name.startswith('RLIM') and _name.isupper():
200
+ _globals[_name] = getattr(_psplatform.cext, _name)
201
+ __all__.append(_name)
202
+ del _globals, _name
203
+
204
+ AF_LINK = _psplatform.AF_LINK
205
+
206
+ __author__ = "Giampaolo Rodola'"
207
+ __version__ = "7.2.2"
208
+ version_info = tuple(int(num) for num in __version__.split('.'))
209
+
210
+ _timer = getattr(time, 'monotonic', time.time)
211
+ _TOTAL_PHYMEM = None
212
+ _LOWEST_PID = None
213
+ _SENTINEL = object()
214
+
215
+ # Sanity check in case the user messed up with psutil installation
216
+ # or did something weird with sys.path. In this case we might end
217
+ # up importing a python module using a C extension module which
218
+ # was compiled for a different version of psutil.
219
+ # We want to prevent that by failing sooner rather than later.
220
+ # See: https://github.com/giampaolo/psutil/issues/564
221
+ if int(__version__.replace('.', '')) != getattr(
222
+ _psplatform.cext, 'version', None
223
+ ):
224
+ msg = f"version conflict: {_psplatform.cext.__file__!r} C extension "
225
+ msg += "module was built for another version of psutil"
226
+ if hasattr(_psplatform.cext, 'version'):
227
+ v = ".".join(list(str(_psplatform.cext.version)))
228
+ msg += f" ({v} instead of {__version__})"
229
+ else:
230
+ msg += f" (different than {__version__})"
231
+ what = getattr(
232
+ _psplatform.cext,
233
+ "__file__",
234
+ "the existing psutil install directory",
235
+ )
236
+ msg += f"; you may try to 'pip uninstall psutil', manually remove {what}"
237
+ msg += " or clean the virtual env somehow, then reinstall"
238
+ raise ImportError(msg)
239
+
240
+
241
+ # =====================================================================
242
+ # --- Utils
243
+ # =====================================================================
244
+
245
+
246
+ if hasattr(_psplatform, 'ppid_map'):
247
+ # Faster version (Windows and Linux).
248
+ _ppid_map = _psplatform.ppid_map
249
+ else: # pragma: no cover
250
+
251
+ def _ppid_map():
252
+ """Return a {pid: ppid, ...} dict for all running processes in
253
+ one shot. Used to speed up Process.children().
254
+ """
255
+ ret = {}
256
+ for pid in pids():
257
+ try:
258
+ ret[pid] = _psplatform.Process(pid).ppid()
259
+ except (NoSuchProcess, ZombieProcess):
260
+ pass
261
+ return ret
262
+
263
+
264
+ def _pprint_secs(secs):
265
+ """Format seconds in a human readable form."""
266
+ now = time.time()
267
+ secs_ago = int(now - secs)
268
+ fmt = "%H:%M:%S" if secs_ago < 60 * 60 * 24 else "%Y-%m-%d %H:%M:%S"
269
+ return datetime.datetime.fromtimestamp(secs).strftime(fmt)
270
+
271
+
272
+ def _check_conn_kind(kind):
273
+ """Check net_connections()'s `kind` parameter."""
274
+ kinds = tuple(_common.conn_tmap)
275
+ if kind not in kinds:
276
+ msg = f"invalid kind argument {kind!r}; valid ones are: {kinds}"
277
+ raise ValueError(msg)
278
+
279
+
280
+ # =====================================================================
281
+ # --- Process class
282
+ # =====================================================================
283
+
284
+
285
+ class Process:
286
+ """Represents an OS process with the given PID.
287
+ If PID is omitted current process PID (os.getpid()) is used.
288
+ Raise NoSuchProcess if PID does not exist.
289
+
290
+ Note that most of the methods of this class do not make sure that
291
+ the PID of the process being queried has been reused. That means
292
+ that you may end up retrieving information for another process.
293
+
294
+ The only exceptions for which process identity is pre-emptively
295
+ checked and guaranteed are:
296
+
297
+ - parent()
298
+ - children()
299
+ - nice() (set)
300
+ - ionice() (set)
301
+ - rlimit() (set)
302
+ - cpu_affinity (set)
303
+ - suspend()
304
+ - resume()
305
+ - send_signal()
306
+ - terminate()
307
+ - kill()
308
+
309
+ To prevent this problem for all other methods you can use
310
+ is_running() before querying the process.
311
+ """
312
+
313
+ def __init__(self, pid=None):
314
+ self._init(pid)
315
+
316
+ def _init(self, pid, _ignore_nsp=False):
317
+ if pid is None:
318
+ pid = os.getpid()
319
+ else:
320
+ if pid < 0:
321
+ msg = f"pid must be a positive integer (got {pid})"
322
+ raise ValueError(msg)
323
+ try:
324
+ _psplatform.cext.check_pid_range(pid)
325
+ except OverflowError as err:
326
+ msg = "process PID out of range"
327
+ raise NoSuchProcess(pid, msg=msg) from err
328
+
329
+ self._pid = pid
330
+ self._name = None
331
+ self._exe = None
332
+ self._create_time = None
333
+ self._gone = False
334
+ self._pid_reused = False
335
+ self._hash = None
336
+ self._lock = threading.RLock()
337
+ # used for caching on Windows only (on POSIX ppid may change)
338
+ self._ppid = None
339
+ # platform-specific modules define an _psplatform.Process
340
+ # implementation class
341
+ self._proc = _psplatform.Process(pid)
342
+ self._last_sys_cpu_times = None
343
+ self._last_proc_cpu_times = None
344
+ self._exitcode = _SENTINEL
345
+ self._ident = (self.pid, None)
346
+ try:
347
+ self._ident = self._get_ident()
348
+ except AccessDenied:
349
+ # This should happen on Windows only, since we use the fast
350
+ # create time method. AFAIK, on all other platforms we are
351
+ # able to get create time for all PIDs.
352
+ pass
353
+ except ZombieProcess:
354
+ # Zombies can still be queried by this class (although
355
+ # not always) and pids() return them so just go on.
356
+ pass
357
+ except NoSuchProcess:
358
+ if not _ignore_nsp:
359
+ msg = "process PID not found"
360
+ raise NoSuchProcess(pid, msg=msg) from None
361
+ self._gone = True
362
+
363
+ def _get_ident(self):
364
+ """Return a (pid, uid) tuple which is supposed to identify a
365
+ Process instance univocally over time. The PID alone is not
366
+ enough, as it can be assigned to a new process after this one
367
+ terminates, so we add process creation time to the mix. We need
368
+ this in order to prevent killing the wrong process later on.
369
+ This is also known as PID reuse or PID recycling problem.
370
+
371
+ The reliability of this strategy mostly depends on
372
+ create_time() precision, which is 0.01 secs on Linux. The
373
+ assumption is that, after a process terminates, the kernel
374
+ won't reuse the same PID after such a short period of time
375
+ (0.01 secs). Technically this is inherently racy, but
376
+ practically it should be good enough.
377
+
378
+ NOTE: unreliable on FreeBSD and OpenBSD as ctime is subject to
379
+ system clock updates.
380
+ """
381
+
382
+ if WINDOWS:
383
+ # Use create_time() fast method in order to speedup
384
+ # `process_iter()`. This means we'll get AccessDenied for
385
+ # most ADMIN processes, but that's fine since it means
386
+ # we'll also get AccessDenied on kill().
387
+ # https://github.com/giampaolo/psutil/issues/2366#issuecomment-2381646555
388
+ self._create_time = self._proc.create_time(fast_only=True)
389
+ return (self.pid, self._create_time)
390
+ elif LINUX or NETBSD or OSX:
391
+ # Use 'monotonic' process starttime since boot to form unique
392
+ # process identity, since it is stable over changes to system
393
+ # time.
394
+ return (self.pid, self._proc.create_time(monotonic=True))
395
+ else:
396
+ return (self.pid, self.create_time())
397
+
398
+ def __str__(self):
399
+ info = collections.OrderedDict()
400
+ info["pid"] = self.pid
401
+ if self._name:
402
+ info['name'] = self._name
403
+ with self.oneshot():
404
+ if self._pid_reused:
405
+ info["status"] = "terminated + PID reused"
406
+ else:
407
+ try:
408
+ info["name"] = self.name()
409
+ info["status"] = self.status()
410
+ except ZombieProcess:
411
+ info["status"] = "zombie"
412
+ except NoSuchProcess:
413
+ info["status"] = "terminated"
414
+ except AccessDenied:
415
+ pass
416
+
417
+ if self._exitcode not in {_SENTINEL, None}:
418
+ info["exitcode"] = self._exitcode
419
+ if self._create_time is not None:
420
+ info['started'] = _pprint_secs(self._create_time)
421
+
422
+ return "{}.{}({})".format(
423
+ self.__class__.__module__,
424
+ self.__class__.__name__,
425
+ ", ".join([f"{k}={v!r}" for k, v in info.items()]),
426
+ )
427
+
428
+ __repr__ = __str__
429
+
430
+ def __eq__(self, other):
431
+ # Test for equality with another Process object based
432
+ # on PID and creation time.
433
+ if not isinstance(other, Process):
434
+ return NotImplemented
435
+ if OPENBSD or NETBSD or SUNOS: # pragma: no cover
436
+ # Zombie processes on Open/NetBSD/illumos/Solaris have a
437
+ # creation time of 0.0. This covers the case when a process
438
+ # started normally (so it has a ctime), then it turned into a
439
+ # zombie. It's important to do this because is_running()
440
+ # depends on __eq__.
441
+ pid1, ident1 = self._ident
442
+ pid2, ident2 = other._ident
443
+ if pid1 == pid2:
444
+ if ident1 and not ident2:
445
+ try:
446
+ return self.status() == STATUS_ZOMBIE
447
+ except Error:
448
+ pass
449
+ return self._ident == other._ident
450
+
451
+ def __ne__(self, other):
452
+ return not self == other
453
+
454
+ def __hash__(self):
455
+ if self._hash is None:
456
+ self._hash = hash(self._ident)
457
+ return self._hash
458
+
459
+ def _raise_if_pid_reused(self):
460
+ """Raises NoSuchProcess in case process PID has been reused."""
461
+ if self._pid_reused or (not self.is_running() and self._pid_reused):
462
+ # We may directly raise NSP in here already if PID is just
463
+ # not running, but I prefer NSP to be raised naturally by
464
+ # the actual Process API call. This way unit tests will tell
465
+ # us if the API is broken (aka don't raise NSP when it
466
+ # should). We also remain consistent with all other "get"
467
+ # APIs which don't use _raise_if_pid_reused().
468
+ msg = "process no longer exists and its PID has been reused"
469
+ raise NoSuchProcess(self.pid, self._name, msg=msg)
470
+
471
+ @property
472
+ def pid(self):
473
+ """The process PID."""
474
+ return self._pid
475
+
476
+ # --- utility methods
477
+
478
+ @contextlib.contextmanager
479
+ def oneshot(self):
480
+ """Utility context manager which considerably speeds up the
481
+ retrieval of multiple process information at the same time.
482
+
483
+ Internally different process info (e.g. name, ppid, uids,
484
+ gids, ...) may be fetched by using the same routine, but
485
+ only one information is returned and the others are discarded.
486
+ When using this context manager the internal routine is
487
+ executed once (in the example below on name()) and the
488
+ other info are cached.
489
+
490
+ The cache is cleared when exiting the context manager block.
491
+ The advice is to use this every time you retrieve more than
492
+ one information about the process. If you're lucky, you'll
493
+ get a hell of a speedup.
494
+
495
+ >>> import psutil
496
+ >>> p = psutil.Process()
497
+ >>> with p.oneshot():
498
+ ... p.name() # collect multiple info
499
+ ... p.cpu_times() # return cached value
500
+ ... p.cpu_percent() # return cached value
501
+ ... p.create_time() # return cached value
502
+ ...
503
+ >>>
504
+ """
505
+ with self._lock:
506
+ if hasattr(self, "_cache"):
507
+ # NOOP: this covers the use case where the user enters the
508
+ # context twice:
509
+ #
510
+ # >>> with p.oneshot():
511
+ # ... with p.oneshot():
512
+ # ...
513
+ #
514
+ # Also, since as_dict() internally uses oneshot()
515
+ # I expect that the code below will be a pretty common
516
+ # "mistake" that the user will make, so let's guard
517
+ # against that:
518
+ #
519
+ # >>> with p.oneshot():
520
+ # ... p.as_dict()
521
+ # ...
522
+ yield
523
+ else:
524
+ try:
525
+ # cached in case cpu_percent() is used
526
+ self.cpu_times.cache_activate(self)
527
+ # cached in case memory_percent() is used
528
+ self.memory_info.cache_activate(self)
529
+ # cached in case parent() is used
530
+ self.ppid.cache_activate(self)
531
+ # cached in case username() is used
532
+ if POSIX:
533
+ self.uids.cache_activate(self)
534
+ # specific implementation cache
535
+ self._proc.oneshot_enter()
536
+ yield
537
+ finally:
538
+ self.cpu_times.cache_deactivate(self)
539
+ self.memory_info.cache_deactivate(self)
540
+ self.ppid.cache_deactivate(self)
541
+ if POSIX:
542
+ self.uids.cache_deactivate(self)
543
+ self._proc.oneshot_exit()
544
+
545
+ def as_dict(self, attrs=None, ad_value=None):
546
+ """Utility method returning process information as a
547
+ hashable dictionary.
548
+ If *attrs* is specified it must be a list of strings
549
+ reflecting available Process class' attribute names
550
+ (e.g. ['cpu_times', 'name']) else all public (read
551
+ only) attributes are assumed.
552
+ *ad_value* is the value which gets assigned in case
553
+ AccessDenied or ZombieProcess exception is raised when
554
+ retrieving that particular process information.
555
+ """
556
+ valid_names = _as_dict_attrnames
557
+ if attrs is not None:
558
+ if not isinstance(attrs, (list, tuple, set, frozenset)):
559
+ msg = f"invalid attrs type {type(attrs)}"
560
+ raise TypeError(msg)
561
+ attrs = set(attrs)
562
+ invalid_names = attrs - valid_names
563
+ if invalid_names:
564
+ msg = "invalid attr name{} {}".format(
565
+ "s" if len(invalid_names) > 1 else "",
566
+ ", ".join(map(repr, invalid_names)),
567
+ )
568
+ raise ValueError(msg)
569
+
570
+ retdict = {}
571
+ ls = attrs or valid_names
572
+ with self.oneshot():
573
+ for name in ls:
574
+ try:
575
+ if name == 'pid':
576
+ ret = self.pid
577
+ else:
578
+ meth = getattr(self, name)
579
+ ret = meth()
580
+ except (AccessDenied, ZombieProcess):
581
+ ret = ad_value
582
+ except NotImplementedError:
583
+ # in case of not implemented functionality (may happen
584
+ # on old or exotic systems) we want to crash only if
585
+ # the user explicitly asked for that particular attr
586
+ if attrs:
587
+ raise
588
+ continue
589
+ retdict[name] = ret
590
+ return retdict
591
+
592
+ def parent(self):
593
+ """Return the parent process as a Process object pre-emptively
594
+ checking whether PID has been reused.
595
+ If no parent is known return None.
596
+ """
597
+ lowest_pid = _LOWEST_PID if _LOWEST_PID is not None else pids()[0]
598
+ if self.pid == lowest_pid:
599
+ return None
600
+ ppid = self.ppid()
601
+ if ppid is not None:
602
+ # Get a fresh (non-cached) ctime in case the system clock
603
+ # was updated. TODO: use a monotonic ctime on platforms
604
+ # where it's supported.
605
+ proc_ctime = Process(self.pid).create_time()
606
+ try:
607
+ parent = Process(ppid)
608
+ if parent.create_time() <= proc_ctime:
609
+ return parent
610
+ # ...else ppid has been reused by another process
611
+ except NoSuchProcess:
612
+ pass
613
+
614
+ def parents(self):
615
+ """Return the parents of this process as a list of Process
616
+ instances. If no parents are known return an empty list.
617
+ """
618
+ parents = []
619
+ proc = self.parent()
620
+ while proc is not None:
621
+ parents.append(proc)
622
+ proc = proc.parent()
623
+ return parents
624
+
625
+ def is_running(self):
626
+ """Return whether this process is running.
627
+
628
+ It also checks if PID has been reused by another process, in
629
+ which case it will remove the process from `process_iter()`
630
+ internal cache and return False.
631
+ """
632
+ if self._gone or self._pid_reused:
633
+ return False
634
+ try:
635
+ # Checking if PID is alive is not enough as the PID might
636
+ # have been reused by another process. Process identity /
637
+ # uniqueness over time is guaranteed by (PID + creation
638
+ # time) and that is verified in __eq__.
639
+ self._pid_reused = self != Process(self.pid)
640
+ if self._pid_reused:
641
+ _pids_reused.add(self.pid)
642
+ raise NoSuchProcess(self.pid)
643
+ return True
644
+ except ZombieProcess:
645
+ # We should never get here as it's already handled in
646
+ # Process.__init__; here just for extra safety.
647
+ return True
648
+ except NoSuchProcess:
649
+ self._gone = True
650
+ return False
651
+
652
+ # --- actual API
653
+
654
+ @memoize_when_activated
655
+ def ppid(self):
656
+ """The process parent PID.
657
+ On Windows the return value is cached after first call.
658
+ """
659
+ # On POSIX we don't want to cache the ppid as it may unexpectedly
660
+ # change to 1 (init) in case this process turns into a zombie:
661
+ # https://github.com/giampaolo/psutil/issues/321
662
+ # http://stackoverflow.com/questions/356722/
663
+
664
+ # XXX should we check creation time here rather than in
665
+ # Process.parent()?
666
+ self._raise_if_pid_reused()
667
+ if POSIX:
668
+ return self._proc.ppid()
669
+ else: # pragma: no cover
670
+ self._ppid = self._ppid or self._proc.ppid()
671
+ return self._ppid
672
+
673
+ def name(self):
674
+ """The process name. The return value is cached after first call."""
675
+ # Process name is only cached on Windows as on POSIX it may
676
+ # change, see:
677
+ # https://github.com/giampaolo/psutil/issues/692
678
+ if WINDOWS and self._name is not None:
679
+ return self._name
680
+ name = self._proc.name()
681
+ if POSIX and len(name) >= 15:
682
+ # On UNIX the name gets truncated to the first 15 characters.
683
+ # If it matches the first part of the cmdline we return that
684
+ # one instead because it's usually more explicative.
685
+ # Examples are "gnome-keyring-d" vs. "gnome-keyring-daemon".
686
+ try:
687
+ cmdline = self.cmdline()
688
+ except (AccessDenied, ZombieProcess):
689
+ # Just pass and return the truncated name: it's better
690
+ # than nothing. Note: there are actual cases where a
691
+ # zombie process can return a name() but not a
692
+ # cmdline(), see:
693
+ # https://github.com/giampaolo/psutil/issues/2239
694
+ pass
695
+ else:
696
+ if cmdline:
697
+ extended_name = os.path.basename(cmdline[0])
698
+ if extended_name.startswith(name):
699
+ name = extended_name
700
+ self._name = name
701
+ self._proc._name = name
702
+ return name
703
+
704
+ def exe(self):
705
+ """The process executable as an absolute path.
706
+ May also be an empty string.
707
+ The return value is cached after first call.
708
+ """
709
+
710
+ def guess_it(fallback):
711
+ # try to guess exe from cmdline[0] in absence of a native
712
+ # exe representation
713
+ cmdline = self.cmdline()
714
+ if cmdline and hasattr(os, 'access') and hasattr(os, 'X_OK'):
715
+ exe = cmdline[0] # the possible exe
716
+ # Attempt to guess only in case of an absolute path.
717
+ # It is not safe otherwise as the process might have
718
+ # changed cwd.
719
+ if (
720
+ os.path.isabs(exe)
721
+ and os.path.isfile(exe)
722
+ and os.access(exe, os.X_OK)
723
+ ):
724
+ return exe
725
+ if isinstance(fallback, AccessDenied):
726
+ raise fallback
727
+ return fallback
728
+
729
+ if self._exe is None:
730
+ try:
731
+ exe = self._proc.exe()
732
+ except AccessDenied as err:
733
+ return guess_it(fallback=err)
734
+ else:
735
+ if not exe:
736
+ # underlying implementation can legitimately return an
737
+ # empty string; if that's the case we don't want to
738
+ # raise AD while guessing from the cmdline
739
+ try:
740
+ exe = guess_it(fallback=exe)
741
+ except AccessDenied:
742
+ pass
743
+ self._exe = exe
744
+ return self._exe
745
+
746
+ def cmdline(self):
747
+ """The command line this process has been called with."""
748
+ return self._proc.cmdline()
749
+
750
+ def status(self):
751
+ """The process current status as a STATUS_* constant."""
752
+ try:
753
+ return self._proc.status()
754
+ except ZombieProcess:
755
+ return STATUS_ZOMBIE
756
+
757
+ def username(self):
758
+ """The name of the user that owns the process.
759
+ On UNIX this is calculated by using *real* process uid.
760
+ """
761
+ if POSIX:
762
+ if pwd is None:
763
+ # might happen if python was installed from sources
764
+ msg = "requires pwd module shipped with standard python"
765
+ raise ImportError(msg)
766
+ real_uid = self.uids().real
767
+ try:
768
+ return pwd.getpwuid(real_uid).pw_name
769
+ except KeyError:
770
+ # the uid can't be resolved by the system
771
+ return str(real_uid)
772
+ else:
773
+ return self._proc.username()
774
+
775
+ def create_time(self):
776
+ """The process creation time as a floating point number
777
+ expressed in seconds since the epoch (seconds since January 1,
778
+ 1970, at midnight UTC). The return value, which is cached after
779
+ first call, is based on the system clock, which means it may be
780
+ affected by changes such as manual adjustments or time
781
+ synchronization (e.g. NTP).
782
+ """
783
+ if self._create_time is None:
784
+ self._create_time = self._proc.create_time()
785
+ return self._create_time
786
+
787
+ def cwd(self):
788
+ """Process current working directory as an absolute path."""
789
+ return self._proc.cwd()
790
+
791
+ def nice(self, value=None):
792
+ """Get or set process niceness (priority)."""
793
+ if value is None:
794
+ return self._proc.nice_get()
795
+ else:
796
+ self._raise_if_pid_reused()
797
+ self._proc.nice_set(value)
798
+
799
+ if POSIX:
800
+
801
+ @memoize_when_activated
802
+ def uids(self):
803
+ """Return process UIDs as a (real, effective, saved)
804
+ namedtuple.
805
+ """
806
+ return self._proc.uids()
807
+
808
+ def gids(self):
809
+ """Return process GIDs as a (real, effective, saved)
810
+ namedtuple.
811
+ """
812
+ return self._proc.gids()
813
+
814
+ def terminal(self):
815
+ """The terminal associated with this process, if any,
816
+ else None.
817
+ """
818
+ return self._proc.terminal()
819
+
820
+ def num_fds(self):
821
+ """Return the number of file descriptors opened by this
822
+ process (POSIX only).
823
+ """
824
+ return self._proc.num_fds()
825
+
826
+ # Linux, BSD, AIX and Windows only
827
+ if hasattr(_psplatform.Process, "io_counters"):
828
+
829
+ def io_counters(self):
830
+ """Return process I/O statistics as a
831
+ (read_count, write_count, read_bytes, write_bytes)
832
+ namedtuple.
833
+ Those are the number of read/write calls performed and the
834
+ amount of bytes read and written by the process.
835
+ """
836
+ return self._proc.io_counters()
837
+
838
+ # Linux and Windows
839
+ if hasattr(_psplatform.Process, "ionice_get"):
840
+
841
+ def ionice(self, ioclass=None, value=None):
842
+ """Get or set process I/O niceness (priority).
843
+
844
+ On Linux *ioclass* is one of the IOPRIO_CLASS_* constants.
845
+ *value* is a number which goes from 0 to 7. The higher the
846
+ value, the lower the I/O priority of the process.
847
+
848
+ On Windows only *ioclass* is used and it can be set to 2
849
+ (normal), 1 (low) or 0 (very low).
850
+
851
+ Available on Linux and Windows > Vista only.
852
+ """
853
+ if ioclass is None:
854
+ if value is not None:
855
+ msg = "'ioclass' argument must be specified"
856
+ raise ValueError(msg)
857
+ return self._proc.ionice_get()
858
+ else:
859
+ self._raise_if_pid_reused()
860
+ return self._proc.ionice_set(ioclass, value)
861
+
862
+ # Linux / FreeBSD only
863
+ if hasattr(_psplatform.Process, "rlimit"):
864
+
865
+ def rlimit(self, resource, limits=None):
866
+ """Get or set process resource limits as a (soft, hard)
867
+ tuple.
868
+
869
+ *resource* is one of the RLIMIT_* constants.
870
+ *limits* is supposed to be a (soft, hard) tuple.
871
+
872
+ See "man prlimit" for further info.
873
+ Available on Linux and FreeBSD only.
874
+ """
875
+ if limits is not None:
876
+ self._raise_if_pid_reused()
877
+ return self._proc.rlimit(resource, limits)
878
+
879
+ # Windows, Linux and FreeBSD only
880
+ if hasattr(_psplatform.Process, "cpu_affinity_get"):
881
+
882
+ def cpu_affinity(self, cpus=None):
883
+ """Get or set process CPU affinity.
884
+ If specified, *cpus* must be a list of CPUs for which you
885
+ want to set the affinity (e.g. [0, 1]).
886
+ If an empty list is passed, all egible CPUs are assumed
887
+ (and set).
888
+ (Windows, Linux and BSD only).
889
+ """
890
+ if cpus is None:
891
+ return sorted(set(self._proc.cpu_affinity_get()))
892
+ else:
893
+ self._raise_if_pid_reused()
894
+ if not cpus:
895
+ if hasattr(self._proc, "_get_eligible_cpus"):
896
+ cpus = self._proc._get_eligible_cpus()
897
+ else:
898
+ cpus = tuple(range(len(cpu_times(percpu=True))))
899
+ self._proc.cpu_affinity_set(list(set(cpus)))
900
+
901
+ # Linux, FreeBSD, SunOS
902
+ if hasattr(_psplatform.Process, "cpu_num"):
903
+
904
+ def cpu_num(self):
905
+ """Return what CPU this process is currently running on.
906
+ The returned number should be <= psutil.cpu_count()
907
+ and <= len(psutil.cpu_percent(percpu=True)).
908
+ It may be used in conjunction with
909
+ psutil.cpu_percent(percpu=True) to observe the system
910
+ workload distributed across CPUs.
911
+ """
912
+ return self._proc.cpu_num()
913
+
914
+ # All platforms has it, but maybe not in the future.
915
+ if hasattr(_psplatform.Process, "environ"):
916
+
917
+ def environ(self):
918
+ """The environment variables of the process as a dict. Note: this
919
+ might not reflect changes made after the process started.
920
+ """
921
+ return self._proc.environ()
922
+
923
+ if WINDOWS:
924
+
925
+ def num_handles(self):
926
+ """Return the number of handles opened by this process
927
+ (Windows only).
928
+ """
929
+ return self._proc.num_handles()
930
+
931
+ def num_ctx_switches(self):
932
+ """Return the number of voluntary and involuntary context
933
+ switches performed by this process.
934
+ """
935
+ return self._proc.num_ctx_switches()
936
+
937
+ def num_threads(self):
938
+ """Return the number of threads used by this process."""
939
+ return self._proc.num_threads()
940
+
941
+ if hasattr(_psplatform.Process, "threads"):
942
+
943
+ def threads(self):
944
+ """Return threads opened by process as a list of
945
+ (id, user_time, system_time) namedtuples representing
946
+ thread id and thread CPU times (user/system).
947
+ On OpenBSD this method requires root access.
948
+ """
949
+ return self._proc.threads()
950
+
951
+ def children(self, recursive=False):
952
+ """Return the children of this process as a list of Process
953
+ instances, pre-emptively checking whether PID has been reused.
954
+ If *recursive* is True return all the parent descendants.
955
+
956
+ Example (A == this process):
957
+
958
+ A ─┐
959
+ │
960
+ ├─ B (child) ─┐
961
+ │ └─ X (grandchild) ─┐
962
+ │ └─ Y (great grandchild)
963
+ ├─ C (child)
964
+ └─ D (child)
965
+
966
+ >>> import psutil
967
+ >>> p = psutil.Process()
968
+ >>> p.children()
969
+ B, C, D
970
+ >>> p.children(recursive=True)
971
+ B, X, Y, C, D
972
+
973
+ Note that in the example above if process X disappears
974
+ process Y won't be listed as the reference to process A
975
+ is lost.
976
+ """
977
+ self._raise_if_pid_reused()
978
+ ppid_map = _ppid_map()
979
+ # Get a fresh (non-cached) ctime in case the system clock was
980
+ # updated. TODO: use a monotonic ctime on platforms where it's
981
+ # supported.
982
+ proc_ctime = Process(self.pid).create_time()
983
+ ret = []
984
+ if not recursive:
985
+ for pid, ppid in ppid_map.items():
986
+ if ppid == self.pid:
987
+ try:
988
+ child = Process(pid)
989
+ # if child happens to be older than its parent
990
+ # (self) it means child's PID has been reused
991
+ if proc_ctime <= child.create_time():
992
+ ret.append(child)
993
+ except (NoSuchProcess, ZombieProcess):
994
+ pass
995
+ else:
996
+ # Construct a {pid: [child pids]} dict
997
+ reverse_ppid_map = collections.defaultdict(list)
998
+ for pid, ppid in ppid_map.items():
999
+ reverse_ppid_map[ppid].append(pid)
1000
+ # Recursively traverse that dict, starting from self.pid,
1001
+ # such that we only call Process() on actual children
1002
+ seen = set()
1003
+ stack = [self.pid]
1004
+ while stack:
1005
+ pid = stack.pop()
1006
+ if pid in seen:
1007
+ # Since pids can be reused while the ppid_map is
1008
+ # constructed, there may be rare instances where
1009
+ # there's a cycle in the recorded process "tree".
1010
+ continue
1011
+ seen.add(pid)
1012
+ for child_pid in reverse_ppid_map[pid]:
1013
+ try:
1014
+ child = Process(child_pid)
1015
+ # if child happens to be older than its parent
1016
+ # (self) it means child's PID has been reused
1017
+ intime = proc_ctime <= child.create_time()
1018
+ if intime:
1019
+ ret.append(child)
1020
+ stack.append(child_pid)
1021
+ except (NoSuchProcess, ZombieProcess):
1022
+ pass
1023
+ return ret
1024
+
1025
+ def cpu_percent(self, interval=None):
1026
+ """Return a float representing the current process CPU
1027
+ utilization as a percentage.
1028
+
1029
+ When *interval* is 0.0 or None (default) compares process times
1030
+ to system CPU times elapsed since last call, returning
1031
+ immediately (non-blocking). That means that the first time
1032
+ this is called it will return a meaningful 0.0 value.
1033
+
1034
+ When *interval* is > 0.0 compares process times to system CPU
1035
+ times elapsed before and after the interval (blocking).
1036
+
1037
+ In this case is recommended for accuracy that this function
1038
+ be called with at least 0.1 seconds between calls.
1039
+
1040
+ A value > 100.0 can be returned in case of processes running
1041
+ multiple threads on different CPU cores.
1042
+
1043
+ The returned value is explicitly NOT split evenly between
1044
+ all available logical CPUs. This means that a busy loop process
1045
+ running on a system with 2 logical CPUs will be reported as
1046
+ having 100% CPU utilization instead of 50%.
1047
+
1048
+ Examples:
1049
+
1050
+ >>> import psutil
1051
+ >>> p = psutil.Process(os.getpid())
1052
+ >>> # blocking
1053
+ >>> p.cpu_percent(interval=1)
1054
+ 2.0
1055
+ >>> # non-blocking (percentage since last call)
1056
+ >>> p.cpu_percent(interval=None)
1057
+ 2.9
1058
+ >>>
1059
+ """
1060
+ blocking = interval is not None and interval > 0.0
1061
+ if interval is not None and interval < 0:
1062
+ msg = f"interval is not positive (got {interval!r})"
1063
+ raise ValueError(msg)
1064
+ num_cpus = cpu_count() or 1
1065
+
1066
+ def timer():
1067
+ return _timer() * num_cpus
1068
+
1069
+ if blocking:
1070
+ st1 = timer()
1071
+ pt1 = self._proc.cpu_times()
1072
+ time.sleep(interval)
1073
+ st2 = timer()
1074
+ pt2 = self._proc.cpu_times()
1075
+ else:
1076
+ st1 = self._last_sys_cpu_times
1077
+ pt1 = self._last_proc_cpu_times
1078
+ st2 = timer()
1079
+ pt2 = self._proc.cpu_times()
1080
+ if st1 is None or pt1 is None:
1081
+ self._last_sys_cpu_times = st2
1082
+ self._last_proc_cpu_times = pt2
1083
+ return 0.0
1084
+
1085
+ delta_proc = (pt2.user - pt1.user) + (pt2.system - pt1.system)
1086
+ delta_time = st2 - st1
1087
+ # reset values for next call in case of interval == None
1088
+ self._last_sys_cpu_times = st2
1089
+ self._last_proc_cpu_times = pt2
1090
+
1091
+ try:
1092
+ # This is the utilization split evenly between all CPUs.
1093
+ # E.g. a busy loop process on a 2-CPU-cores system at this
1094
+ # point is reported as 50% instead of 100%.
1095
+ overall_cpus_percent = (delta_proc / delta_time) * 100
1096
+ except ZeroDivisionError:
1097
+ # interval was too low
1098
+ return 0.0
1099
+ else:
1100
+ # Note 1:
1101
+ # in order to emulate "top" we multiply the value for the num
1102
+ # of CPU cores. This way the busy process will be reported as
1103
+ # having 100% (or more) usage.
1104
+ #
1105
+ # Note 2:
1106
+ # taskmgr.exe on Windows differs in that it will show 50%
1107
+ # instead.
1108
+ #
1109
+ # Note 3:
1110
+ # a percentage > 100 is legitimate as it can result from a
1111
+ # process with multiple threads running on different CPU
1112
+ # cores (top does the same), see:
1113
+ # http://stackoverflow.com/questions/1032357
1114
+ # https://github.com/giampaolo/psutil/issues/474
1115
+ single_cpu_percent = overall_cpus_percent * num_cpus
1116
+ return round(single_cpu_percent, 1)
1117
+
1118
+ @memoize_when_activated
1119
+ def cpu_times(self):
1120
+ """Return a (user, system, children_user, children_system)
1121
+ namedtuple representing the accumulated process time, in
1122
+ seconds.
1123
+ This is similar to os.times() but per-process.
1124
+ On macOS and Windows children_user and children_system are
1125
+ always set to 0.
1126
+ """
1127
+ return self._proc.cpu_times()
1128
+
1129
+ @memoize_when_activated
1130
+ def memory_info(self):
1131
+ """Return a namedtuple with variable fields depending on the
1132
+ platform, representing memory information about the process.
1133
+
1134
+ The "portable" fields available on all platforms are `rss` and `vms`.
1135
+
1136
+ All numbers are expressed in bytes.
1137
+ """
1138
+ return self._proc.memory_info()
1139
+
1140
+ def memory_full_info(self):
1141
+ """This method returns the same information as memory_info(),
1142
+ plus, on some platform (Linux, macOS, Windows), also provides
1143
+ additional metrics (USS, PSS and swap).
1144
+ The additional metrics provide a better representation of actual
1145
+ process memory usage.
1146
+
1147
+ Namely USS is the memory which is unique to a process and which
1148
+ would be freed if the process was terminated right now.
1149
+
1150
+ It does so by passing through the whole process address.
1151
+ As such it usually requires higher user privileges than
1152
+ memory_info() and is considerably slower.
1153
+ """
1154
+ return self._proc.memory_full_info()
1155
+
1156
+ def memory_percent(self, memtype="rss"):
1157
+ """Compare process memory to total physical system memory and
1158
+ calculate process memory utilization as a percentage.
1159
+ *memtype* argument is a string that dictates what type of
1160
+ process memory you want to compare against (defaults to "rss").
1161
+ The list of available strings can be obtained like this:
1162
+
1163
+ >>> psutil.Process().memory_info()._fields
1164
+ ('rss', 'vms', 'shared', 'text', 'lib', 'data', 'dirty', 'uss', 'pss')
1165
+ """
1166
+ valid_types = list(_ntp.pfullmem._fields)
1167
+ if memtype not in valid_types:
1168
+ msg = (
1169
+ f"invalid memtype {memtype!r}; valid types are"
1170
+ f" {tuple(valid_types)!r}"
1171
+ )
1172
+ raise ValueError(msg)
1173
+ fun = (
1174
+ self.memory_info
1175
+ if memtype in _ntp.pmem._fields
1176
+ else self.memory_full_info
1177
+ )
1178
+ metrics = fun()
1179
+ value = getattr(metrics, memtype)
1180
+
1181
+ # use cached value if available
1182
+ total_phymem = _TOTAL_PHYMEM or virtual_memory().total
1183
+ if not total_phymem > 0:
1184
+ # we should never get here
1185
+ msg = (
1186
+ "can't calculate process memory percent because total physical"
1187
+ f" system memory is not positive ({total_phymem!r})"
1188
+ )
1189
+ raise ValueError(msg)
1190
+ return (value / float(total_phymem)) * 100
1191
+
1192
+ if hasattr(_psplatform.Process, "memory_maps"):
1193
+
1194
+ def memory_maps(self, grouped=True):
1195
+ """Return process' mapped memory regions as a list of namedtuples
1196
+ whose fields are variable depending on the platform.
1197
+
1198
+ If *grouped* is True the mapped regions with the same 'path'
1199
+ are grouped together and the different memory fields are summed.
1200
+
1201
+ If *grouped* is False every mapped region is shown as a single
1202
+ entity and the namedtuple will also include the mapped region's
1203
+ address space ('addr') and permission set ('perms').
1204
+ """
1205
+ it = self._proc.memory_maps()
1206
+ if grouped:
1207
+ d = {}
1208
+ for tupl in it:
1209
+ path = tupl[2]
1210
+ nums = tupl[3:]
1211
+ try:
1212
+ d[path] = list(map(lambda x, y: x + y, d[path], nums))
1213
+ except KeyError:
1214
+ d[path] = nums
1215
+ return [_ntp.pmmap_grouped(path, *d[path]) for path in d]
1216
+ else:
1217
+ return [_ntp.pmmap_ext(*x) for x in it]
1218
+
1219
+ def open_files(self):
1220
+ """Return files opened by process as a list of
1221
+ (path, fd) namedtuples including the absolute file name
1222
+ and file descriptor number.
1223
+ """
1224
+ return self._proc.open_files()
1225
+
1226
+ def net_connections(self, kind='inet'):
1227
+ """Return socket connections opened by process as a list of
1228
+ (fd, family, type, laddr, raddr, status) namedtuples.
1229
+ The *kind* parameter filters for connections that match the
1230
+ following criteria:
1231
+
1232
+ +------------+----------------------------------------------------+
1233
+ | Kind Value | Connections using |
1234
+ +------------+----------------------------------------------------+
1235
+ | inet | IPv4 and IPv6 |
1236
+ | inet4 | IPv4 |
1237
+ | inet6 | IPv6 |
1238
+ | tcp | TCP |
1239
+ | tcp4 | TCP over IPv4 |
1240
+ | tcp6 | TCP over IPv6 |
1241
+ | udp | UDP |
1242
+ | udp4 | UDP over IPv4 |
1243
+ | udp6 | UDP over IPv6 |
1244
+ | unix | UNIX socket (both UDP and TCP protocols) |
1245
+ | all | the sum of all the possible families and protocols |
1246
+ +------------+----------------------------------------------------+
1247
+ """
1248
+ _check_conn_kind(kind)
1249
+ return self._proc.net_connections(kind)
1250
+
1251
+ @_common.deprecated_method(replacement="net_connections")
1252
+ def connections(self, kind="inet"):
1253
+ return self.net_connections(kind=kind)
1254
+
1255
+ # --- signals
1256
+
1257
+ if POSIX:
1258
+
1259
+ def _send_signal(self, sig):
1260
+ assert not self.pid < 0, self.pid
1261
+ self._raise_if_pid_reused()
1262
+
1263
+ pid, ppid, name = self.pid, self._ppid, self._name
1264
+ if pid == 0:
1265
+ # see "man 2 kill"
1266
+ msg = (
1267
+ "preventing sending signal to process with PID 0 as it "
1268
+ "would affect every process in the process group of the "
1269
+ "calling process (os.getpid()) instead of PID 0"
1270
+ )
1271
+ raise ValueError(msg)
1272
+ try:
1273
+ os.kill(pid, sig)
1274
+ except ProcessLookupError as err:
1275
+ if OPENBSD and pid_exists(pid):
1276
+ # We do this because os.kill() lies in case of
1277
+ # zombie processes.
1278
+ raise ZombieProcess(pid, name, ppid) from err
1279
+ self._gone = True
1280
+ raise NoSuchProcess(pid, name) from err
1281
+ except PermissionError as err:
1282
+ raise AccessDenied(pid, name) from err
1283
+
1284
+ def send_signal(self, sig):
1285
+ """Send a signal *sig* to process pre-emptively checking
1286
+ whether PID has been reused (see signal module constants) .
1287
+ On Windows only SIGTERM is valid and is treated as an alias
1288
+ for kill().
1289
+ """
1290
+ if POSIX:
1291
+ self._send_signal(sig)
1292
+ else: # pragma: no cover
1293
+ self._raise_if_pid_reused()
1294
+ if sig != signal.SIGTERM and not self.is_running():
1295
+ msg = "process no longer exists"
1296
+ raise NoSuchProcess(self.pid, self._name, msg=msg)
1297
+ self._proc.send_signal(sig)
1298
+
1299
+ def suspend(self):
1300
+ """Suspend process execution with SIGSTOP pre-emptively checking
1301
+ whether PID has been reused.
1302
+ On Windows this has the effect of suspending all process threads.
1303
+ """
1304
+ if POSIX:
1305
+ self._send_signal(signal.SIGSTOP)
1306
+ else: # pragma: no cover
1307
+ self._raise_if_pid_reused()
1308
+ self._proc.suspend()
1309
+
1310
+ def resume(self):
1311
+ """Resume process execution with SIGCONT pre-emptively checking
1312
+ whether PID has been reused.
1313
+ On Windows this has the effect of resuming all process threads.
1314
+ """
1315
+ if POSIX:
1316
+ self._send_signal(signal.SIGCONT)
1317
+ else: # pragma: no cover
1318
+ self._raise_if_pid_reused()
1319
+ self._proc.resume()
1320
+
1321
+ def terminate(self):
1322
+ """Terminate the process with SIGTERM pre-emptively checking
1323
+ whether PID has been reused.
1324
+ On Windows this is an alias for kill().
1325
+ """
1326
+ if POSIX:
1327
+ self._send_signal(signal.SIGTERM)
1328
+ else: # pragma: no cover
1329
+ self._raise_if_pid_reused()
1330
+ self._proc.kill()
1331
+
1332
+ def kill(self):
1333
+ """Kill the current process with SIGKILL pre-emptively checking
1334
+ whether PID has been reused.
1335
+ """
1336
+ if POSIX:
1337
+ self._send_signal(signal.SIGKILL)
1338
+ else: # pragma: no cover
1339
+ self._raise_if_pid_reused()
1340
+ self._proc.kill()
1341
+
1342
+ def wait(self, timeout=None):
1343
+ """Wait for process to terminate, and if process is a children
1344
+ of os.getpid(), also return its exit code, else None.
1345
+ On Windows there's no such limitation (exit code is always
1346
+ returned).
1347
+
1348
+ If the process is already terminated, immediately return None
1349
+ instead of raising NoSuchProcess.
1350
+
1351
+ If *timeout* (in seconds) is specified and process is still
1352
+ alive, raise TimeoutExpired.
1353
+
1354
+ If *timeout=0* either return immediately or raise
1355
+ TimeoutExpired (non-blocking).
1356
+
1357
+ To wait for multiple Process objects use psutil.wait_procs().
1358
+ """
1359
+ if self.pid == 0:
1360
+ msg = "can't wait for PID 0"
1361
+ raise ValueError(msg)
1362
+ if timeout is not None:
1363
+ if not isinstance(timeout, (int, float)):
1364
+ msg = f"timeout must be an int or float (got {type(timeout)})"
1365
+ raise TypeError(msg)
1366
+ if timeout < 0:
1367
+ msg = f"timeout must be positive or zero (got {timeout})"
1368
+ raise ValueError(msg)
1369
+
1370
+ if self._exitcode is not _SENTINEL:
1371
+ return self._exitcode
1372
+
1373
+ try:
1374
+ self._exitcode = self._proc.wait(timeout)
1375
+ except TimeoutExpired as err:
1376
+ exc = TimeoutExpired(timeout, pid=self.pid, name=self._name)
1377
+ raise exc from err
1378
+
1379
+ return self._exitcode
1380
+
1381
+
1382
+ # The valid attr names which can be processed by Process.as_dict().
1383
+ # fmt: off
1384
+ _as_dict_attrnames = {
1385
+ x for x in dir(Process) if not x.startswith("_") and x not in
1386
+ {'send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait',
1387
+ 'is_running', 'as_dict', 'parent', 'parents', 'children', 'rlimit',
1388
+ 'connections', 'oneshot'}
1389
+ }
1390
+ # fmt: on
1391
+
1392
+
1393
+ # =====================================================================
1394
+ # --- Popen class
1395
+ # =====================================================================
1396
+
1397
+
1398
+ class Popen(Process):
1399
+ """Same as subprocess.Popen, but in addition it provides all
1400
+ psutil.Process methods in a single class.
1401
+ For the following methods which are common to both classes, psutil
1402
+ implementation takes precedence:
1403
+
1404
+ * send_signal()
1405
+ * terminate()
1406
+ * kill()
1407
+
1408
+ This is done in order to avoid killing another process in case its
1409
+ PID has been reused, fixing BPO-6973.
1410
+
1411
+ >>> import psutil
1412
+ >>> from subprocess import PIPE
1413
+ >>> p = psutil.Popen(["python", "-c", "print 'hi'"], stdout=PIPE)
1414
+ >>> p.name()
1415
+ 'python'
1416
+ >>> p.uids()
1417
+ user(real=1000, effective=1000, saved=1000)
1418
+ >>> p.username()
1419
+ 'giampaolo'
1420
+ >>> p.communicate()
1421
+ ('hi', None)
1422
+ >>> p.terminate()
1423
+ >>> p.wait(timeout=2)
1424
+ 0
1425
+ >>>
1426
+ """
1427
+
1428
+ def __init__(self, *args, **kwargs):
1429
+ # Explicitly avoid to raise NoSuchProcess in case the process
1430
+ # spawned by subprocess.Popen terminates too quickly, see:
1431
+ # https://github.com/giampaolo/psutil/issues/193
1432
+ self.__subproc = subprocess.Popen(*args, **kwargs)
1433
+ self._init(self.__subproc.pid, _ignore_nsp=True)
1434
+
1435
+ def __dir__(self):
1436
+ return sorted(set(dir(Popen) + dir(subprocess.Popen)))
1437
+
1438
+ def __enter__(self):
1439
+ if hasattr(self.__subproc, '__enter__'):
1440
+ self.__subproc.__enter__()
1441
+ return self
1442
+
1443
+ def __exit__(self, *args, **kwargs):
1444
+ if hasattr(self.__subproc, '__exit__'):
1445
+ return self.__subproc.__exit__(*args, **kwargs)
1446
+ else:
1447
+ if self.stdout:
1448
+ self.stdout.close()
1449
+ if self.stderr:
1450
+ self.stderr.close()
1451
+ try:
1452
+ # Flushing a BufferedWriter may raise an error.
1453
+ if self.stdin:
1454
+ self.stdin.close()
1455
+ finally:
1456
+ # Wait for the process to terminate, to avoid zombies.
1457
+ self.wait()
1458
+
1459
+ def __getattribute__(self, name):
1460
+ try:
1461
+ return object.__getattribute__(self, name)
1462
+ except AttributeError:
1463
+ try:
1464
+ return object.__getattribute__(self.__subproc, name)
1465
+ except AttributeError:
1466
+ msg = f"{self.__class__!r} has no attribute {name!r}"
1467
+ raise AttributeError(msg) from None
1468
+
1469
+ def wait(self, timeout=None):
1470
+ if self.__subproc.returncode is not None:
1471
+ return self.__subproc.returncode
1472
+ ret = super().wait(timeout)
1473
+ self.__subproc.returncode = ret
1474
+ return ret
1475
+
1476
+
1477
+ # =====================================================================
1478
+ # --- system processes related functions
1479
+ # =====================================================================
1480
+
1481
+
1482
+ def pids():
1483
+ """Return a list of current running PIDs."""
1484
+ global _LOWEST_PID
1485
+ ret = sorted(_psplatform.pids())
1486
+ _LOWEST_PID = ret[0]
1487
+ return ret
1488
+
1489
+
1490
+ def pid_exists(pid):
1491
+ """Return True if given PID exists in the current process list.
1492
+ This is faster than doing "pid in psutil.pids()" and
1493
+ should be preferred.
1494
+ """
1495
+ if pid < 0:
1496
+ return False
1497
+ elif pid == 0 and POSIX:
1498
+ # On POSIX we use os.kill() to determine PID existence.
1499
+ # According to "man 2 kill" PID 0 has a special meaning
1500
+ # though: it refers to <<every process in the process
1501
+ # group of the calling process>> and that is not we want
1502
+ # to do here.
1503
+ return pid in pids()
1504
+ else:
1505
+ return _psplatform.pid_exists(pid)
1506
+
1507
+
1508
+ _pmap = {}
1509
+ _pids_reused = set()
1510
+
1511
+
1512
+ def process_iter(attrs=None, ad_value=None):
1513
+ """Return a generator yielding a Process instance for all
1514
+ running processes.
1515
+
1516
+ Every new Process instance is only created once and then cached
1517
+ into an internal table which is updated every time this is used.
1518
+ Cache can optionally be cleared via `process_iter.cache_clear()`.
1519
+
1520
+ The sorting order in which processes are yielded is based on
1521
+ their PIDs.
1522
+
1523
+ *attrs* and *ad_value* have the same meaning as in
1524
+ Process.as_dict(). If *attrs* is specified as_dict() is called
1525
+ and the resulting dict is stored as a 'info' attribute attached
1526
+ to returned Process instance.
1527
+ If *attrs* is an empty list it will retrieve all process info
1528
+ (slow).
1529
+ """
1530
+ global _pmap
1531
+
1532
+ def add(pid):
1533
+ proc = Process(pid)
1534
+ pmap[proc.pid] = proc
1535
+ return proc
1536
+
1537
+ def remove(pid):
1538
+ pmap.pop(pid, None)
1539
+
1540
+ pmap = _pmap.copy()
1541
+ a = set(pids())
1542
+ b = set(pmap.keys())
1543
+ new_pids = a - b
1544
+ gone_pids = b - a
1545
+ for pid in gone_pids:
1546
+ remove(pid)
1547
+ while _pids_reused:
1548
+ pid = _pids_reused.pop()
1549
+ debug(f"refreshing Process instance for reused PID {pid}")
1550
+ remove(pid)
1551
+ try:
1552
+ ls = sorted(list(pmap.items()) + list(dict.fromkeys(new_pids).items()))
1553
+ for pid, proc in ls:
1554
+ try:
1555
+ if proc is None: # new process
1556
+ proc = add(pid)
1557
+ if attrs is not None:
1558
+ proc.info = proc.as_dict(attrs=attrs, ad_value=ad_value)
1559
+ yield proc
1560
+ except NoSuchProcess:
1561
+ remove(pid)
1562
+ finally:
1563
+ _pmap = pmap
1564
+
1565
+
1566
+ process_iter.cache_clear = lambda: _pmap.clear() # noqa: PLW0108
1567
+ process_iter.cache_clear.__doc__ = "Clear process_iter() internal cache."
1568
+
1569
+
1570
+ def wait_procs(procs, timeout=None, callback=None):
1571
+ """Convenience function which waits for a list of processes to
1572
+ terminate.
1573
+
1574
+ Return a (gone, alive) tuple indicating which processes
1575
+ are gone and which ones are still alive.
1576
+
1577
+ The gone ones will have a new *returncode* attribute indicating
1578
+ process exit status (may be None).
1579
+
1580
+ *callback* is a function which gets called every time a process
1581
+ terminates (a Process instance is passed as callback argument).
1582
+
1583
+ Function will return as soon as all processes terminate or when
1584
+ *timeout* occurs.
1585
+ Differently from Process.wait() it will not raise TimeoutExpired if
1586
+ *timeout* occurs.
1587
+
1588
+ Typical use case is:
1589
+
1590
+ - send SIGTERM to a list of processes
1591
+ - give them some time to terminate
1592
+ - send SIGKILL to those ones which are still alive
1593
+
1594
+ Example:
1595
+
1596
+ >>> def on_terminate(proc):
1597
+ ... print("process {} terminated".format(proc))
1598
+ ...
1599
+ >>> for p in procs:
1600
+ ... p.terminate()
1601
+ ...
1602
+ >>> gone, alive = wait_procs(procs, timeout=3, callback=on_terminate)
1603
+ >>> for p in alive:
1604
+ ... p.kill()
1605
+ """
1606
+
1607
+ def check_gone(proc, timeout):
1608
+ try:
1609
+ returncode = proc.wait(timeout=timeout)
1610
+ except (TimeoutExpired, subprocess.TimeoutExpired):
1611
+ pass
1612
+ else:
1613
+ if returncode is not None or not proc.is_running():
1614
+ # Set new Process instance attribute.
1615
+ proc.returncode = returncode
1616
+ gone.add(proc)
1617
+ if callback is not None:
1618
+ callback(proc)
1619
+
1620
+ if timeout is not None and not timeout >= 0:
1621
+ msg = f"timeout must be a positive integer, got {timeout}"
1622
+ raise ValueError(msg)
1623
+ if callback is not None and not callable(callback):
1624
+ msg = f"callback {callback!r} is not a callable"
1625
+ raise TypeError(msg)
1626
+
1627
+ gone = set()
1628
+ alive = set(procs)
1629
+ if timeout is not None:
1630
+ deadline = _timer() + timeout
1631
+
1632
+ while alive:
1633
+ if timeout is not None and timeout <= 0:
1634
+ break
1635
+ for proc in alive:
1636
+ # Make sure that every complete iteration (all processes)
1637
+ # will last max 1 sec.
1638
+ # We do this because we don't want to wait too long on a
1639
+ # single process: in case it terminates too late other
1640
+ # processes may disappear in the meantime and their PID
1641
+ # reused.
1642
+ max_timeout = 1.0 / len(alive)
1643
+ if timeout is not None:
1644
+ timeout = min((deadline - _timer()), max_timeout)
1645
+ if timeout <= 0:
1646
+ break
1647
+ check_gone(proc, timeout)
1648
+ else:
1649
+ check_gone(proc, max_timeout)
1650
+ alive = alive - gone # noqa: PLR6104
1651
+
1652
+ if alive:
1653
+ # Last attempt over processes survived so far.
1654
+ # timeout == 0 won't make this function wait any further.
1655
+ for proc in alive:
1656
+ check_gone(proc, 0)
1657
+ alive = alive - gone # noqa: PLR6104
1658
+
1659
+ return (list(gone), list(alive))
1660
+
1661
+
1662
+ # =====================================================================
1663
+ # --- CPU related functions
1664
+ # =====================================================================
1665
+
1666
+
1667
+ def cpu_count(logical=True):
1668
+ """Return the number of logical CPUs in the system (same as
1669
+ os.cpu_count()).
1670
+
1671
+ If *logical* is False return the number of physical cores only
1672
+ (e.g. hyper thread CPUs are excluded).
1673
+
1674
+ Return None if undetermined.
1675
+
1676
+ The return value is cached after first call.
1677
+ If desired cache can be cleared like this:
1678
+
1679
+ >>> psutil.cpu_count.cache_clear()
1680
+ """
1681
+ if logical:
1682
+ ret = _psplatform.cpu_count_logical()
1683
+ else:
1684
+ ret = _psplatform.cpu_count_cores()
1685
+ if ret is not None and ret < 1:
1686
+ ret = None
1687
+ return ret
1688
+
1689
+
1690
+ def cpu_times(percpu=False):
1691
+ """Return system-wide CPU times as a namedtuple.
1692
+ Every CPU time represents the seconds the CPU has spent in the
1693
+ given mode. The namedtuple's fields availability varies depending on the
1694
+ platform:
1695
+
1696
+ - user
1697
+ - system
1698
+ - idle
1699
+ - nice (UNIX)
1700
+ - iowait (Linux)
1701
+ - irq (Linux, FreeBSD)
1702
+ - softirq (Linux)
1703
+ - steal (Linux >= 2.6.11)
1704
+ - guest (Linux >= 2.6.24)
1705
+ - guest_nice (Linux >= 3.2.0)
1706
+
1707
+ When *percpu* is True return a list of namedtuples for each CPU.
1708
+ First element of the list refers to first CPU, second element
1709
+ to second CPU and so on.
1710
+ The order of the list is consistent across calls.
1711
+ """
1712
+ if not percpu:
1713
+ return _psplatform.cpu_times()
1714
+ else:
1715
+ return _psplatform.per_cpu_times()
1716
+
1717
+
1718
+ try:
1719
+ _last_cpu_times = {threading.current_thread().ident: cpu_times()}
1720
+ except Exception: # noqa: BLE001
1721
+ # Don't want to crash at import time.
1722
+ _last_cpu_times = {}
1723
+
1724
+ try:
1725
+ _last_per_cpu_times = {
1726
+ threading.current_thread().ident: cpu_times(percpu=True)
1727
+ }
1728
+ except Exception: # noqa: BLE001
1729
+ # Don't want to crash at import time.
1730
+ _last_per_cpu_times = {}
1731
+
1732
+
1733
+ def _cpu_tot_time(times):
1734
+ """Given a cpu_time() ntuple calculates the total CPU time
1735
+ (including idle time).
1736
+ """
1737
+ tot = sum(times)
1738
+ if LINUX:
1739
+ # On Linux guest times are already accounted in "user" or
1740
+ # "nice" times, so we subtract them from total.
1741
+ # Htop does the same. References:
1742
+ # https://github.com/giampaolo/psutil/pull/940
1743
+ # http://unix.stackexchange.com/questions/178045
1744
+ # https://github.com/torvalds/linux/blob/
1745
+ # 447976ef4fd09b1be88b316d1a81553f1aa7cd07/kernel/sched/
1746
+ # cputime.c#L158
1747
+ tot -= getattr(times, "guest", 0) # Linux 2.6.24+
1748
+ tot -= getattr(times, "guest_nice", 0) # Linux 3.2.0+
1749
+ return tot
1750
+
1751
+
1752
+ def _cpu_busy_time(times):
1753
+ """Given a cpu_time() ntuple calculates the busy CPU time.
1754
+ We do so by subtracting all idle CPU times.
1755
+ """
1756
+ busy = _cpu_tot_time(times)
1757
+ busy -= times.idle
1758
+ # Linux: "iowait" is time during which the CPU does not do anything
1759
+ # (waits for IO to complete). On Linux IO wait is *not* accounted
1760
+ # in "idle" time so we subtract it. Htop does the same.
1761
+ # References:
1762
+ # https://github.com/torvalds/linux/blob/
1763
+ # 447976ef4fd09b1be88b316d1a81553f1aa7cd07/kernel/sched/cputime.c#L244
1764
+ busy -= getattr(times, "iowait", 0)
1765
+ return busy
1766
+
1767
+
1768
+ def _cpu_times_deltas(t1, t2):
1769
+ assert t1._fields == t2._fields, (t1, t2)
1770
+ field_deltas = []
1771
+ for field in _ntp.scputimes._fields:
1772
+ field_delta = getattr(t2, field) - getattr(t1, field)
1773
+ # CPU times are always supposed to increase over time
1774
+ # or at least remain the same and that's because time
1775
+ # cannot go backwards.
1776
+ # Surprisingly sometimes this might not be the case (at
1777
+ # least on Windows and Linux), see:
1778
+ # https://github.com/giampaolo/psutil/issues/392
1779
+ # https://github.com/giampaolo/psutil/issues/645
1780
+ # https://github.com/giampaolo/psutil/issues/1210
1781
+ # Trim negative deltas to zero to ignore decreasing fields.
1782
+ # top does the same. Reference:
1783
+ # https://gitlab.com/procps-ng/procps/blob/v3.3.12/top/top.c#L5063
1784
+ field_delta = max(0, field_delta)
1785
+ field_deltas.append(field_delta)
1786
+ return _ntp.scputimes(*field_deltas)
1787
+
1788
+
1789
+ def cpu_percent(interval=None, percpu=False):
1790
+ """Return a float representing the current system-wide CPU
1791
+ utilization as a percentage.
1792
+
1793
+ When *interval* is > 0.0 compares system CPU times elapsed before
1794
+ and after the interval (blocking).
1795
+
1796
+ When *interval* is 0.0 or None compares system CPU times elapsed
1797
+ since last call or module import, returning immediately (non
1798
+ blocking). That means the first time this is called it will
1799
+ return a meaningless 0.0 value which you should ignore.
1800
+ In this case is recommended for accuracy that this function be
1801
+ called with at least 0.1 seconds between calls.
1802
+
1803
+ When *percpu* is True returns a list of floats representing the
1804
+ utilization as a percentage for each CPU.
1805
+ First element of the list refers to first CPU, second element
1806
+ to second CPU and so on.
1807
+ The order of the list is consistent across calls.
1808
+
1809
+ Examples:
1810
+
1811
+ >>> # blocking, system-wide
1812
+ >>> psutil.cpu_percent(interval=1)
1813
+ 2.0
1814
+ >>>
1815
+ >>> # blocking, per-cpu
1816
+ >>> psutil.cpu_percent(interval=1, percpu=True)
1817
+ [2.0, 1.0]
1818
+ >>>
1819
+ >>> # non-blocking (percentage since last call)
1820
+ >>> psutil.cpu_percent(interval=None)
1821
+ 2.9
1822
+ >>>
1823
+ """
1824
+ tid = threading.current_thread().ident
1825
+ blocking = interval is not None and interval > 0.0
1826
+ if interval is not None and interval < 0:
1827
+ msg = f"interval is not positive (got {interval})"
1828
+ raise ValueError(msg)
1829
+
1830
+ def calculate(t1, t2):
1831
+ times_delta = _cpu_times_deltas(t1, t2)
1832
+ all_delta = _cpu_tot_time(times_delta)
1833
+ busy_delta = _cpu_busy_time(times_delta)
1834
+
1835
+ try:
1836
+ busy_perc = (busy_delta / all_delta) * 100
1837
+ except ZeroDivisionError:
1838
+ return 0.0
1839
+ else:
1840
+ return round(busy_perc, 1)
1841
+
1842
+ # system-wide usage
1843
+ if not percpu:
1844
+ if blocking:
1845
+ t1 = cpu_times()
1846
+ time.sleep(interval)
1847
+ else:
1848
+ t1 = _last_cpu_times.get(tid) or cpu_times()
1849
+ _last_cpu_times[tid] = cpu_times()
1850
+ return calculate(t1, _last_cpu_times[tid])
1851
+ # per-cpu usage
1852
+ else:
1853
+ ret = []
1854
+ if blocking:
1855
+ tot1 = cpu_times(percpu=True)
1856
+ time.sleep(interval)
1857
+ else:
1858
+ tot1 = _last_per_cpu_times.get(tid) or cpu_times(percpu=True)
1859
+ _last_per_cpu_times[tid] = cpu_times(percpu=True)
1860
+ for t1, t2 in zip(tot1, _last_per_cpu_times[tid]):
1861
+ ret.append(calculate(t1, t2))
1862
+ return ret
1863
+
1864
+
1865
+ # Use a separate dict for cpu_times_percent(), so it's independent from
1866
+ # cpu_percent() and they can both be used within the same program.
1867
+ _last_cpu_times_2 = _last_cpu_times.copy()
1868
+ _last_per_cpu_times_2 = _last_per_cpu_times.copy()
1869
+
1870
+
1871
+ def cpu_times_percent(interval=None, percpu=False):
1872
+ """Same as cpu_percent() but provides utilization percentages
1873
+ for each specific CPU time as is returned by cpu_times().
1874
+ For instance, on Linux we'll get:
1875
+
1876
+ >>> cpu_times_percent()
1877
+ cpupercent(user=4.8, nice=0.0, system=4.8, idle=90.5, iowait=0.0,
1878
+ irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0)
1879
+ >>>
1880
+
1881
+ *interval* and *percpu* arguments have the same meaning as in
1882
+ cpu_percent().
1883
+ """
1884
+ tid = threading.current_thread().ident
1885
+ blocking = interval is not None and interval > 0.0
1886
+ if interval is not None and interval < 0:
1887
+ msg = f"interval is not positive (got {interval!r})"
1888
+ raise ValueError(msg)
1889
+
1890
+ def calculate(t1, t2):
1891
+ nums = []
1892
+ times_delta = _cpu_times_deltas(t1, t2)
1893
+ all_delta = _cpu_tot_time(times_delta)
1894
+ # "scale" is the value to multiply each delta with to get percentages.
1895
+ # We use "max" to avoid division by zero (if all_delta is 0, then all
1896
+ # fields are 0 so percentages will be 0 too. all_delta cannot be a
1897
+ # fraction because cpu times are integers)
1898
+ scale = 100.0 / max(1, all_delta)
1899
+ for field_delta in times_delta:
1900
+ field_perc = field_delta * scale
1901
+ field_perc = round(field_perc, 1)
1902
+ # make sure we don't return negative values or values over 100%
1903
+ field_perc = min(max(0.0, field_perc), 100.0)
1904
+ nums.append(field_perc)
1905
+ return _ntp.scputimes(*nums)
1906
+
1907
+ # system-wide usage
1908
+ if not percpu:
1909
+ if blocking:
1910
+ t1 = cpu_times()
1911
+ time.sleep(interval)
1912
+ else:
1913
+ t1 = _last_cpu_times_2.get(tid) or cpu_times()
1914
+ _last_cpu_times_2[tid] = cpu_times()
1915
+ return calculate(t1, _last_cpu_times_2[tid])
1916
+ # per-cpu usage
1917
+ else:
1918
+ ret = []
1919
+ if blocking:
1920
+ tot1 = cpu_times(percpu=True)
1921
+ time.sleep(interval)
1922
+ else:
1923
+ tot1 = _last_per_cpu_times_2.get(tid) or cpu_times(percpu=True)
1924
+ _last_per_cpu_times_2[tid] = cpu_times(percpu=True)
1925
+ for t1, t2 in zip(tot1, _last_per_cpu_times_2[tid]):
1926
+ ret.append(calculate(t1, t2))
1927
+ return ret
1928
+
1929
+
1930
+ def cpu_stats():
1931
+ """Return CPU statistics."""
1932
+ return _psplatform.cpu_stats()
1933
+
1934
+
1935
+ if hasattr(_psplatform, "cpu_freq"):
1936
+
1937
+ def cpu_freq(percpu=False):
1938
+ """Return CPU frequency as a namedtuple including current,
1939
+ min and max frequency expressed in Mhz.
1940
+
1941
+ If *percpu* is True and the system supports per-cpu frequency
1942
+ retrieval (Linux only) a list of frequencies is returned for
1943
+ each CPU. If not a list with one element is returned.
1944
+ """
1945
+ ret = _psplatform.cpu_freq()
1946
+ if percpu:
1947
+ return ret
1948
+ else:
1949
+ num_cpus = float(len(ret))
1950
+ if num_cpus == 0:
1951
+ return None
1952
+ elif num_cpus == 1:
1953
+ return ret[0]
1954
+ else:
1955
+ currs, mins, maxs = 0.0, 0.0, 0.0
1956
+ set_none = False
1957
+ for cpu in ret:
1958
+ currs += cpu.current
1959
+ # On Linux if /proc/cpuinfo is used min/max are set
1960
+ # to None.
1961
+ if LINUX and cpu.min is None:
1962
+ set_none = True
1963
+ continue
1964
+ mins += cpu.min
1965
+ maxs += cpu.max
1966
+
1967
+ current = currs / num_cpus
1968
+
1969
+ if set_none:
1970
+ min_ = max_ = None
1971
+ else:
1972
+ min_ = mins / num_cpus
1973
+ max_ = maxs / num_cpus
1974
+
1975
+ return _ntp.scpufreq(current, min_, max_)
1976
+
1977
+ __all__.append("cpu_freq")
1978
+
1979
+
1980
+ if hasattr(os, "getloadavg") or hasattr(_psplatform, "getloadavg"):
1981
+ # Perform this hasattr check once on import time to either use the
1982
+ # platform based code or proxy straight from the os module.
1983
+ if hasattr(os, "getloadavg"):
1984
+ getloadavg = os.getloadavg
1985
+ else:
1986
+ getloadavg = _psplatform.getloadavg
1987
+
1988
+ __all__.append("getloadavg")
1989
+
1990
+
1991
+ # =====================================================================
1992
+ # --- system memory related functions
1993
+ # =====================================================================
1994
+
1995
+
1996
+ def virtual_memory():
1997
+ """Return statistics about system memory usage as a namedtuple
1998
+ including the following fields, expressed in bytes:
1999
+
2000
+ - total:
2001
+ total physical memory available.
2002
+
2003
+ - available:
2004
+ the memory that can be given instantly to processes without the
2005
+ system going into swap.
2006
+ This is calculated by summing different memory values depending
2007
+ on the platform and it is supposed to be used to monitor actual
2008
+ memory usage in a cross platform fashion.
2009
+
2010
+ - percent:
2011
+ the percentage usage calculated as (total - available) / total * 100
2012
+
2013
+ - used:
2014
+ memory used, calculated differently depending on the platform and
2015
+ designed for informational purposes only:
2016
+ macOS: active + wired
2017
+ BSD: active + wired + cached
2018
+ Linux: total - free
2019
+
2020
+ - free:
2021
+ memory not being used at all (zeroed) that is readily available;
2022
+ note that this doesn't reflect the actual memory available
2023
+ (use 'available' instead)
2024
+
2025
+ Platform-specific fields:
2026
+
2027
+ - active (UNIX):
2028
+ memory currently in use or very recently used, and so it is in RAM.
2029
+
2030
+ - inactive (UNIX):
2031
+ memory that is marked as not used.
2032
+
2033
+ - buffers (BSD, Linux):
2034
+ cache for things like file system metadata.
2035
+
2036
+ - cached (BSD, macOS):
2037
+ cache for various things.
2038
+
2039
+ - wired (macOS, BSD):
2040
+ memory that is marked to always stay in RAM. It is never moved to disk.
2041
+
2042
+ - shared (BSD):
2043
+ memory that may be simultaneously accessed by multiple processes.
2044
+
2045
+ The sum of 'used' and 'available' does not necessarily equal total.
2046
+ On Windows 'available' and 'free' are the same.
2047
+ """
2048
+ global _TOTAL_PHYMEM
2049
+ ret = _psplatform.virtual_memory()
2050
+ # cached for later use in Process.memory_percent()
2051
+ _TOTAL_PHYMEM = ret.total
2052
+ return ret
2053
+
2054
+
2055
+ def swap_memory():
2056
+ """Return system swap memory statistics as a namedtuple including
2057
+ the following fields:
2058
+
2059
+ - total: total swap memory in bytes
2060
+ - used: used swap memory in bytes
2061
+ - free: free swap memory in bytes
2062
+ - percent: the percentage usage
2063
+ - sin: no. of bytes the system has swapped in from disk (cumulative)
2064
+ - sout: no. of bytes the system has swapped out from disk (cumulative)
2065
+
2066
+ 'sin' and 'sout' on Windows are meaningless and always set to 0.
2067
+ """
2068
+ return _psplatform.swap_memory()
2069
+
2070
+
2071
+ # =====================================================================
2072
+ # --- disks/partitions related functions
2073
+ # =====================================================================
2074
+
2075
+
2076
+ def disk_usage(path):
2077
+ """Return disk usage statistics about the given *path* as a
2078
+ namedtuple including total, used and free space expressed in bytes
2079
+ plus the percentage usage.
2080
+ """
2081
+ return _psplatform.disk_usage(path)
2082
+
2083
+
2084
+ def disk_partitions(all=False):
2085
+ """Return mounted partitions as a list of
2086
+ (device, mountpoint, fstype, opts) namedtuple.
2087
+ 'opts' field is a raw string separated by commas indicating mount
2088
+ options which may vary depending on the platform.
2089
+
2090
+ If *all* parameter is False return physical devices only and ignore
2091
+ all others.
2092
+ """
2093
+ return _psplatform.disk_partitions(all)
2094
+
2095
+
2096
+ def disk_io_counters(perdisk=False, nowrap=True):
2097
+ """Return system disk I/O statistics as a namedtuple including
2098
+ the following fields:
2099
+
2100
+ - read_count: number of reads
2101
+ - write_count: number of writes
2102
+ - read_bytes: number of bytes read
2103
+ - write_bytes: number of bytes written
2104
+ - read_time: time spent reading from disk (in ms)
2105
+ - write_time: time spent writing to disk (in ms)
2106
+
2107
+ Platform specific:
2108
+
2109
+ - busy_time: (Linux, FreeBSD) time spent doing actual I/Os (in ms)
2110
+ - read_merged_count (Linux): number of merged reads
2111
+ - write_merged_count (Linux): number of merged writes
2112
+
2113
+ If *perdisk* is True return the same information for every
2114
+ physical disk installed on the system as a dictionary
2115
+ with partition names as the keys and the namedtuple
2116
+ described above as the values.
2117
+
2118
+ If *nowrap* is True it detects and adjust the numbers which overflow
2119
+ and wrap (restart from 0) and add "old value" to "new value" so that
2120
+ the returned numbers will always be increasing or remain the same,
2121
+ but never decrease.
2122
+ "disk_io_counters.cache_clear()" can be used to invalidate the
2123
+ cache.
2124
+
2125
+ On recent Windows versions 'diskperf -y' command may need to be
2126
+ executed first otherwise this function won't find any disk.
2127
+ """
2128
+ kwargs = dict(perdisk=perdisk) if LINUX else {}
2129
+ rawdict = _psplatform.disk_io_counters(**kwargs)
2130
+ if not rawdict:
2131
+ return {} if perdisk else None
2132
+ if nowrap:
2133
+ rawdict = _wrap_numbers(rawdict, 'psutil.disk_io_counters')
2134
+ if perdisk:
2135
+ for disk, fields in rawdict.items():
2136
+ rawdict[disk] = _ntp.sdiskio(*fields)
2137
+ return rawdict
2138
+ else:
2139
+ return _ntp.sdiskio(*(sum(x) for x in zip(*rawdict.values())))
2140
+
2141
+
2142
+ disk_io_counters.cache_clear = functools.partial(
2143
+ _wrap_numbers.cache_clear, 'psutil.disk_io_counters'
2144
+ )
2145
+ disk_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache"
2146
+
2147
+
2148
+ # =====================================================================
2149
+ # --- network related functions
2150
+ # =====================================================================
2151
+
2152
+
2153
+ def net_io_counters(pernic=False, nowrap=True):
2154
+ """Return network I/O statistics as a namedtuple including
2155
+ the following fields:
2156
+
2157
+ - bytes_sent: number of bytes sent
2158
+ - bytes_recv: number of bytes received
2159
+ - packets_sent: number of packets sent
2160
+ - packets_recv: number of packets received
2161
+ - errin: total number of errors while receiving
2162
+ - errout: total number of errors while sending
2163
+ - dropin: total number of incoming packets which were dropped
2164
+ - dropout: total number of outgoing packets which were dropped
2165
+ (always 0 on macOS and BSD)
2166
+
2167
+ If *pernic* is True return the same information for every
2168
+ network interface installed on the system as a dictionary
2169
+ with network interface names as the keys and the namedtuple
2170
+ described above as the values.
2171
+
2172
+ If *nowrap* is True it detects and adjust the numbers which overflow
2173
+ and wrap (restart from 0) and add "old value" to "new value" so that
2174
+ the returned numbers will always be increasing or remain the same,
2175
+ but never decrease.
2176
+ "net_io_counters.cache_clear()" can be used to invalidate the
2177
+ cache.
2178
+ """
2179
+ rawdict = _psplatform.net_io_counters()
2180
+ if not rawdict:
2181
+ return {} if pernic else None
2182
+ if nowrap:
2183
+ rawdict = _wrap_numbers(rawdict, 'psutil.net_io_counters')
2184
+ if pernic:
2185
+ for nic, fields in rawdict.items():
2186
+ rawdict[nic] = _ntp.snetio(*fields)
2187
+ return rawdict
2188
+ else:
2189
+ return _ntp.snetio(*[sum(x) for x in zip(*rawdict.values())])
2190
+
2191
+
2192
+ net_io_counters.cache_clear = functools.partial(
2193
+ _wrap_numbers.cache_clear, 'psutil.net_io_counters'
2194
+ )
2195
+ net_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache"
2196
+
2197
+
2198
+ def net_connections(kind='inet'):
2199
+ """Return system-wide socket connections as a list of
2200
+ (fd, family, type, laddr, raddr, status, pid) namedtuples.
2201
+ In case of limited privileges 'fd' and 'pid' may be set to -1
2202
+ and None respectively.
2203
+ The *kind* parameter filters for connections that fit the
2204
+ following criteria:
2205
+
2206
+ +------------+----------------------------------------------------+
2207
+ | Kind Value | Connections using |
2208
+ +------------+----------------------------------------------------+
2209
+ | inet | IPv4 and IPv6 |
2210
+ | inet4 | IPv4 |
2211
+ | inet6 | IPv6 |
2212
+ | tcp | TCP |
2213
+ | tcp4 | TCP over IPv4 |
2214
+ | tcp6 | TCP over IPv6 |
2215
+ | udp | UDP |
2216
+ | udp4 | UDP over IPv4 |
2217
+ | udp6 | UDP over IPv6 |
2218
+ | unix | UNIX socket (both UDP and TCP protocols) |
2219
+ | all | the sum of all the possible families and protocols |
2220
+ +------------+----------------------------------------------------+
2221
+
2222
+ On macOS this function requires root privileges.
2223
+ """
2224
+ _check_conn_kind(kind)
2225
+ return _psplatform.net_connections(kind)
2226
+
2227
+
2228
+ def net_if_addrs():
2229
+ """Return the addresses associated to each NIC (network interface
2230
+ card) installed on the system as a dictionary whose keys are the
2231
+ NIC names and value is a list of namedtuples for each address
2232
+ assigned to the NIC. Each namedtuple includes 5 fields:
2233
+
2234
+ - family: can be either socket.AF_INET, socket.AF_INET6 or
2235
+ psutil.AF_LINK, which refers to a MAC address.
2236
+ - address: is the primary address and it is always set.
2237
+ - netmask: and 'broadcast' and 'ptp' may be None.
2238
+ - ptp: stands for "point to point" and references the
2239
+ destination address on a point to point interface
2240
+ (typically a VPN).
2241
+ - broadcast: and *ptp* are mutually exclusive.
2242
+
2243
+ Note: you can have more than one address of the same family
2244
+ associated with each interface.
2245
+ """
2246
+ rawlist = _psplatform.net_if_addrs()
2247
+ rawlist.sort(key=lambda x: x[1]) # sort by family
2248
+ ret = collections.defaultdict(list)
2249
+ for name, fam, addr, mask, broadcast, ptp in rawlist:
2250
+ try:
2251
+ fam = socket.AddressFamily(fam)
2252
+ except ValueError:
2253
+ if WINDOWS and fam == -1:
2254
+ fam = _psplatform.AF_LINK
2255
+ elif (
2256
+ hasattr(_psplatform, "AF_LINK") and fam == _psplatform.AF_LINK
2257
+ ):
2258
+ # Linux defines AF_LINK as an alias for AF_PACKET.
2259
+ # We re-set the family here so that repr(family)
2260
+ # will show AF_LINK rather than AF_PACKET
2261
+ fam = _psplatform.AF_LINK
2262
+
2263
+ if fam == _psplatform.AF_LINK:
2264
+ # The underlying C function may return an incomplete MAC
2265
+ # address in which case we fill it with null bytes, see:
2266
+ # https://github.com/giampaolo/psutil/issues/786
2267
+ separator = ":" if POSIX else "-"
2268
+ while addr.count(separator) < 5:
2269
+ addr += f"{separator}00"
2270
+
2271
+ nt = _ntp.snicaddr(fam, addr, mask, broadcast, ptp)
2272
+
2273
+ # On Windows broadcast is None, so we determine it via
2274
+ # ipaddress module.
2275
+ if WINDOWS and fam in {socket.AF_INET, socket.AF_INET6}:
2276
+ try:
2277
+ broadcast = _common.broadcast_addr(nt)
2278
+ except Exception as err: # noqa: BLE001
2279
+ debug(err)
2280
+ else:
2281
+ if broadcast is not None:
2282
+ nt._replace(broadcast=broadcast)
2283
+
2284
+ ret[name].append(nt)
2285
+
2286
+ return dict(ret)
2287
+
2288
+
2289
+ def net_if_stats():
2290
+ """Return information about each NIC (network interface card)
2291
+ installed on the system as a dictionary whose keys are the
2292
+ NIC names and value is a namedtuple with the following fields:
2293
+
2294
+ - isup: whether the interface is up (bool)
2295
+ - duplex: can be either NIC_DUPLEX_FULL, NIC_DUPLEX_HALF or
2296
+ NIC_DUPLEX_UNKNOWN
2297
+ - speed: the NIC speed expressed in mega bits (MB); if it can't
2298
+ be determined (e.g. 'localhost') it will be set to 0.
2299
+ - mtu: the maximum transmission unit expressed in bytes.
2300
+ """
2301
+ return _psplatform.net_if_stats()
2302
+
2303
+
2304
+ # =====================================================================
2305
+ # --- sensors
2306
+ # =====================================================================
2307
+
2308
+
2309
+ # Linux, macOS
2310
+ if hasattr(_psplatform, "sensors_temperatures"):
2311
+
2312
+ def sensors_temperatures(fahrenheit=False):
2313
+ """Return hardware temperatures. Each entry is a namedtuple
2314
+ representing a certain hardware sensor (it may be a CPU, an
2315
+ hard disk or something else, depending on the OS and its
2316
+ configuration).
2317
+ All temperatures are expressed in celsius unless *fahrenheit*
2318
+ is set to True.
2319
+ """
2320
+
2321
+ def convert(n):
2322
+ if n is not None:
2323
+ return (float(n) * 9 / 5) + 32 if fahrenheit else n
2324
+
2325
+ ret = collections.defaultdict(list)
2326
+ rawdict = _psplatform.sensors_temperatures()
2327
+
2328
+ for name, values in rawdict.items():
2329
+ while values:
2330
+ label, current, high, critical = values.pop(0)
2331
+ current = convert(current)
2332
+ high = convert(high)
2333
+ critical = convert(critical)
2334
+
2335
+ if high and not critical:
2336
+ critical = high
2337
+ elif critical and not high:
2338
+ high = critical
2339
+
2340
+ ret[name].append(_ntp.shwtemp(label, current, high, critical))
2341
+
2342
+ return dict(ret)
2343
+
2344
+ __all__.append("sensors_temperatures")
2345
+
2346
+
2347
+ # Linux
2348
+ if hasattr(_psplatform, "sensors_fans"):
2349
+
2350
+ def sensors_fans():
2351
+ """Return fans speed. Each entry is a namedtuple
2352
+ representing a certain hardware sensor.
2353
+ All speed are expressed in RPM (rounds per minute).
2354
+ """
2355
+ return _psplatform.sensors_fans()
2356
+
2357
+ __all__.append("sensors_fans")
2358
+
2359
+
2360
+ # Linux, Windows, FreeBSD, macOS
2361
+ if hasattr(_psplatform, "sensors_battery"):
2362
+
2363
+ def sensors_battery():
2364
+ """Return battery information. If no battery is installed
2365
+ returns None.
2366
+
2367
+ - percent: battery power left as a percentage.
2368
+ - secsleft: a rough approximation of how many seconds are left
2369
+ before the battery runs out of power. May be
2370
+ POWER_TIME_UNLIMITED or POWER_TIME_UNLIMITED.
2371
+ - power_plugged: True if the AC power cable is connected.
2372
+ """
2373
+ return _psplatform.sensors_battery()
2374
+
2375
+ __all__.append("sensors_battery")
2376
+
2377
+
2378
+ # =====================================================================
2379
+ # --- other system related functions
2380
+ # =====================================================================
2381
+
2382
+
2383
+ def boot_time():
2384
+ """Return the system boot time expressed in seconds since the epoch
2385
+ (seconds since January 1, 1970, at midnight UTC). The returned
2386
+ value is based on the system clock, which means it may be affected
2387
+ by changes such as manual adjustments or time synchronization (e.g.
2388
+ NTP).
2389
+ """
2390
+ return _psplatform.boot_time()
2391
+
2392
+
2393
+ def users():
2394
+ """Return users currently connected on the system as a list of
2395
+ namedtuples including the following fields.
2396
+
2397
+ - user: the name of the user
2398
+ - terminal: the tty or pseudo-tty associated with the user, if any.
2399
+ - host: the host name associated with the entry, if any.
2400
+ - started: the creation time as a floating point number expressed in
2401
+ seconds since the epoch.
2402
+ """
2403
+ return _psplatform.users()
2404
+
2405
+
2406
+ # =====================================================================
2407
+ # --- Windows services
2408
+ # =====================================================================
2409
+
2410
+
2411
+ if WINDOWS:
2412
+
2413
+ def win_service_iter():
2414
+ """Return a generator yielding a WindowsService instance for all
2415
+ Windows services installed.
2416
+ """
2417
+ return _psplatform.win_service_iter()
2418
+
2419
+ def win_service_get(name):
2420
+ """Get a Windows service by *name*.
2421
+ Raise NoSuchProcess if no service with such name exists.
2422
+ """
2423
+ return _psplatform.win_service_get(name)
2424
+
2425
+
2426
+ # =====================================================================
2427
+ # --- malloc / heap
2428
+ # =====================================================================
2429
+
2430
+
2431
+ # Linux + glibc, Windows, macOS, FreeBSD, NetBSD
2432
+ if hasattr(_psplatform, "heap_info"):
2433
+
2434
+ def heap_info():
2435
+ """Return low-level heap statistics from the C heap allocator
2436
+ (glibc).
2437
+
2438
+ - `heap_used`: the total number of bytes allocated via
2439
+ malloc/free. These are typically allocations smaller than
2440
+ MMAP_THRESHOLD.
2441
+
2442
+ - `mmap_used`: the total number of bytes allocated via `mmap()`
2443
+ or via large ``malloc()`` allocations.
2444
+
2445
+ - `heap_count` (Windows only): number of private heaps created
2446
+ via `HeapCreate()`.
2447
+ """
2448
+ return _ntp.pheap(*_psplatform.heap_info())
2449
+
2450
+ def heap_trim():
2451
+ """Request that the underlying allocator free any unused memory
2452
+ it's holding in the heap (typically small `malloc()`
2453
+ allocations).
2454
+
2455
+ In practice, modern allocators rarely comply, so this is not a
2456
+ general-purpose memory-reduction tool and won't meaningfully
2457
+ shrink RSS in real programs. Its primary value is in **leak
2458
+ detection tools**.
2459
+
2460
+ Calling `heap_trim()` before taking measurements helps reduce
2461
+ allocator noise, giving you a cleaner baseline so that changes
2462
+ in `heap_used` come from the code you're testing, not from
2463
+ internal allocator caching or fragmentation. Its effectiveness
2464
+ depends on allocator behavior and fragmentation patterns.
2465
+ """
2466
+ _psplatform.heap_trim()
2467
+
2468
+ __all__.append("heap_info")
2469
+ __all__.append("heap_trim")
2470
+
2471
+
2472
+ # =====================================================================
2473
+
2474
+
2475
+ def _set_debug(value):
2476
+ """Enable or disable PSUTIL_DEBUG option, which prints debugging
2477
+ messages to stderr.
2478
+ """
2479
+ import psutil._common
2480
+
2481
+ psutil._common.PSUTIL_DEBUG = bool(value)
2482
+ _psplatform.cext.set_debug(bool(value))
2483
+
2484
+
2485
+ del memoize_when_activated
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_common.py ADDED
@@ -0,0 +1,861 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
2
+ # Use of this source code is governed by a BSD-style license that can be
3
+ # found in the LICENSE file.
4
+
5
+ """Common objects shared by __init__.py and _ps*.py modules.
6
+
7
+ Note: this module is imported by setup.py, so it should not import
8
+ psutil or third-party modules.
9
+ """
10
+
11
+ import collections
12
+ import enum
13
+ import functools
14
+ import os
15
+ import socket
16
+ import stat
17
+ import sys
18
+ import threading
19
+ import warnings
20
+ from socket import AF_INET
21
+ from socket import SOCK_DGRAM
22
+ from socket import SOCK_STREAM
23
+
24
+ try:
25
+ from socket import AF_INET6
26
+ except ImportError:
27
+ AF_INET6 = None
28
+ try:
29
+ from socket import AF_UNIX
30
+ except ImportError:
31
+ AF_UNIX = None
32
+
33
+
34
+ PSUTIL_DEBUG = bool(os.getenv('PSUTIL_DEBUG'))
35
+ _DEFAULT = object()
36
+
37
+ # fmt: off
38
+ __all__ = [
39
+ # OS constants
40
+ 'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'MACOS', 'OSX', 'POSIX',
41
+ 'SUNOS', 'WINDOWS',
42
+ # connection constants
43
+ 'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED',
44
+ 'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN',
45
+ 'CONN_NONE', 'CONN_SYN_RECV', 'CONN_SYN_SENT', 'CONN_TIME_WAIT',
46
+ # net constants
47
+ 'NIC_DUPLEX_FULL', 'NIC_DUPLEX_HALF', 'NIC_DUPLEX_UNKNOWN', # noqa: F822
48
+ # process status constants
49
+ 'STATUS_DEAD', 'STATUS_DISK_SLEEP', 'STATUS_IDLE', 'STATUS_LOCKED',
50
+ 'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED',
51
+ 'STATUS_TRACING_STOP', 'STATUS_WAITING', 'STATUS_WAKE_KILL',
52
+ 'STATUS_WAKING', 'STATUS_ZOMBIE', 'STATUS_PARKED',
53
+ # other constants
54
+ 'ENCODING', 'ENCODING_ERRS', 'AF_INET6',
55
+ # utility functions
56
+ 'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize',
57
+ 'parse_environ_block', 'path_exists_strict', 'usage_percent',
58
+ 'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers",
59
+ 'open_text', 'open_binary', 'cat', 'bcat',
60
+ 'bytes2human', 'conn_to_ntuple', 'debug',
61
+ # shell utils
62
+ 'hilite', 'term_supports_colors', 'print_color',
63
+ ]
64
+ # fmt: on
65
+
66
+
67
+ # ===================================================================
68
+ # --- OS constants
69
+ # ===================================================================
70
+
71
+
72
+ POSIX = os.name == "posix"
73
+ WINDOWS = os.name == "nt"
74
+ LINUX = sys.platform.startswith("linux")
75
+ MACOS = sys.platform.startswith("darwin")
76
+ OSX = MACOS # deprecated alias
77
+ FREEBSD = sys.platform.startswith(("freebsd", "midnightbsd"))
78
+ OPENBSD = sys.platform.startswith("openbsd")
79
+ NETBSD = sys.platform.startswith("netbsd")
80
+ BSD = FREEBSD or OPENBSD or NETBSD
81
+ SUNOS = sys.platform.startswith(("sunos", "solaris"))
82
+ AIX = sys.platform.startswith("aix")
83
+
84
+
85
+ # ===================================================================
86
+ # --- API constants
87
+ # ===================================================================
88
+
89
+
90
+ # Process.status()
91
+ STATUS_RUNNING = "running"
92
+ STATUS_SLEEPING = "sleeping"
93
+ STATUS_DISK_SLEEP = "disk-sleep"
94
+ STATUS_STOPPED = "stopped"
95
+ STATUS_TRACING_STOP = "tracing-stop"
96
+ STATUS_ZOMBIE = "zombie"
97
+ STATUS_DEAD = "dead"
98
+ STATUS_WAKE_KILL = "wake-kill"
99
+ STATUS_WAKING = "waking"
100
+ STATUS_IDLE = "idle" # Linux, macOS, FreeBSD
101
+ STATUS_LOCKED = "locked" # FreeBSD
102
+ STATUS_WAITING = "waiting" # FreeBSD
103
+ STATUS_SUSPENDED = "suspended" # NetBSD
104
+ STATUS_PARKED = "parked" # Linux
105
+
106
+ # Process.net_connections() and psutil.net_connections()
107
+ CONN_ESTABLISHED = "ESTABLISHED"
108
+ CONN_SYN_SENT = "SYN_SENT"
109
+ CONN_SYN_RECV = "SYN_RECV"
110
+ CONN_FIN_WAIT1 = "FIN_WAIT1"
111
+ CONN_FIN_WAIT2 = "FIN_WAIT2"
112
+ CONN_TIME_WAIT = "TIME_WAIT"
113
+ CONN_CLOSE = "CLOSE"
114
+ CONN_CLOSE_WAIT = "CLOSE_WAIT"
115
+ CONN_LAST_ACK = "LAST_ACK"
116
+ CONN_LISTEN = "LISTEN"
117
+ CONN_CLOSING = "CLOSING"
118
+ CONN_NONE = "NONE"
119
+
120
+
121
+ # net_if_stats()
122
+ class NicDuplex(enum.IntEnum):
123
+ NIC_DUPLEX_FULL = 2
124
+ NIC_DUPLEX_HALF = 1
125
+ NIC_DUPLEX_UNKNOWN = 0
126
+
127
+
128
+ globals().update(NicDuplex.__members__)
129
+
130
+
131
+ # sensors_battery()
132
+ class BatteryTime(enum.IntEnum):
133
+ POWER_TIME_UNKNOWN = -1
134
+ POWER_TIME_UNLIMITED = -2
135
+
136
+
137
+ globals().update(BatteryTime.__members__)
138
+
139
+ # --- others
140
+
141
+ ENCODING = sys.getfilesystemencoding()
142
+ ENCODING_ERRS = sys.getfilesystemencodeerrors()
143
+
144
+
145
+ # ===================================================================
146
+ # --- Process.net_connections() 'kind' parameter mapping
147
+ # ===================================================================
148
+
149
+
150
+ conn_tmap = {
151
+ "all": ([AF_INET, AF_INET6, AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
152
+ "tcp": ([AF_INET, AF_INET6], [SOCK_STREAM]),
153
+ "tcp4": ([AF_INET], [SOCK_STREAM]),
154
+ "udp": ([AF_INET, AF_INET6], [SOCK_DGRAM]),
155
+ "udp4": ([AF_INET], [SOCK_DGRAM]),
156
+ "inet": ([AF_INET, AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
157
+ "inet4": ([AF_INET], [SOCK_STREAM, SOCK_DGRAM]),
158
+ "inet6": ([AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
159
+ }
160
+
161
+ if AF_INET6 is not None:
162
+ conn_tmap.update({
163
+ "tcp6": ([AF_INET6], [SOCK_STREAM]),
164
+ "udp6": ([AF_INET6], [SOCK_DGRAM]),
165
+ })
166
+
167
+ if AF_UNIX is not None and not SUNOS:
168
+ conn_tmap.update({"unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM])})
169
+
170
+
171
+ # =====================================================================
172
+ # --- Exceptions
173
+ # =====================================================================
174
+
175
+
176
+ class Error(Exception):
177
+ """Base exception class. All other psutil exceptions inherit
178
+ from this one.
179
+ """
180
+
181
+ __module__ = 'psutil'
182
+
183
+ def _infodict(self, attrs):
184
+ info = collections.OrderedDict()
185
+ for name in attrs:
186
+ value = getattr(self, name, None)
187
+ if value or (name == "pid" and value == 0):
188
+ info[name] = value
189
+ return info
190
+
191
+ def __str__(self):
192
+ # invoked on `raise Error`
193
+ info = self._infodict(("pid", "ppid", "name"))
194
+ if info:
195
+ details = "({})".format(
196
+ ", ".join([f"{k}={v!r}" for k, v in info.items()])
197
+ )
198
+ else:
199
+ details = None
200
+ return " ".join([x for x in (getattr(self, "msg", ""), details) if x])
201
+
202
+ def __repr__(self):
203
+ # invoked on `repr(Error)`
204
+ info = self._infodict(("pid", "ppid", "name", "seconds", "msg"))
205
+ details = ", ".join([f"{k}={v!r}" for k, v in info.items()])
206
+ return f"psutil.{self.__class__.__name__}({details})"
207
+
208
+
209
+ class NoSuchProcess(Error):
210
+ """Exception raised when a process with a certain PID doesn't
211
+ or no longer exists.
212
+ """
213
+
214
+ __module__ = 'psutil'
215
+
216
+ def __init__(self, pid, name=None, msg=None):
217
+ Error.__init__(self)
218
+ self.pid = pid
219
+ self.name = name
220
+ self.msg = msg or "process no longer exists"
221
+
222
+ def __reduce__(self):
223
+ return (self.__class__, (self.pid, self.name, self.msg))
224
+
225
+
226
+ class ZombieProcess(NoSuchProcess):
227
+ """Exception raised when querying a zombie process. This is
228
+ raised on macOS, BSD and Solaris only, and not always: depending
229
+ on the query the OS may be able to succeed anyway.
230
+ On Linux all zombie processes are querable (hence this is never
231
+ raised). Windows doesn't have zombie processes.
232
+ """
233
+
234
+ __module__ = 'psutil'
235
+
236
+ def __init__(self, pid, name=None, ppid=None, msg=None):
237
+ NoSuchProcess.__init__(self, pid, name, msg)
238
+ self.ppid = ppid
239
+ self.msg = msg or "PID still exists but it's a zombie"
240
+
241
+ def __reduce__(self):
242
+ return (self.__class__, (self.pid, self.name, self.ppid, self.msg))
243
+
244
+
245
+ class AccessDenied(Error):
246
+ """Exception raised when permission to perform an action is denied."""
247
+
248
+ __module__ = 'psutil'
249
+
250
+ def __init__(self, pid=None, name=None, msg=None):
251
+ Error.__init__(self)
252
+ self.pid = pid
253
+ self.name = name
254
+ self.msg = msg or ""
255
+
256
+ def __reduce__(self):
257
+ return (self.__class__, (self.pid, self.name, self.msg))
258
+
259
+
260
+ class TimeoutExpired(Error):
261
+ """Raised on Process.wait(timeout) if timeout expires and process
262
+ is still alive.
263
+ """
264
+
265
+ __module__ = 'psutil'
266
+
267
+ def __init__(self, seconds, pid=None, name=None):
268
+ Error.__init__(self)
269
+ self.seconds = seconds
270
+ self.pid = pid
271
+ self.name = name
272
+ self.msg = f"timeout after {seconds} seconds"
273
+
274
+ def __reduce__(self):
275
+ return (self.__class__, (self.seconds, self.pid, self.name))
276
+
277
+
278
+ # ===================================================================
279
+ # --- utils
280
+ # ===================================================================
281
+
282
+
283
+ def usage_percent(used, total, round_=None):
284
+ """Calculate percentage usage of 'used' against 'total'."""
285
+ try:
286
+ ret = (float(used) / total) * 100
287
+ except ZeroDivisionError:
288
+ return 0.0
289
+ else:
290
+ if round_ is not None:
291
+ ret = round(ret, round_)
292
+ return ret
293
+
294
+
295
+ def memoize(fun):
296
+ """A simple memoize decorator for functions supporting (hashable)
297
+ positional arguments.
298
+ It also provides a cache_clear() function for clearing the cache:
299
+
300
+ >>> @memoize
301
+ ... def foo()
302
+ ... return 1
303
+ ...
304
+ >>> foo()
305
+ 1
306
+ >>> foo.cache_clear()
307
+ >>>
308
+
309
+ It supports:
310
+ - functions
311
+ - classes (acts as a @singleton)
312
+ - staticmethods
313
+ - classmethods
314
+
315
+ It does NOT support:
316
+ - methods
317
+ """
318
+
319
+ @functools.wraps(fun)
320
+ def wrapper(*args, **kwargs):
321
+ key = (args, frozenset(sorted(kwargs.items())))
322
+ try:
323
+ return cache[key]
324
+ except KeyError:
325
+ try:
326
+ ret = cache[key] = fun(*args, **kwargs)
327
+ except Exception as err:
328
+ raise err from None
329
+ return ret
330
+
331
+ def cache_clear():
332
+ """Clear cache."""
333
+ cache.clear()
334
+
335
+ cache = {}
336
+ wrapper.cache_clear = cache_clear
337
+ return wrapper
338
+
339
+
340
+ def memoize_when_activated(fun):
341
+ """A memoize decorator which is disabled by default. It can be
342
+ activated and deactivated on request.
343
+ For efficiency reasons it can be used only against class methods
344
+ accepting no arguments.
345
+
346
+ >>> class Foo:
347
+ ... @memoize
348
+ ... def foo()
349
+ ... print(1)
350
+ ...
351
+ >>> f = Foo()
352
+ >>> # deactivated (default)
353
+ >>> foo()
354
+ 1
355
+ >>> foo()
356
+ 1
357
+ >>>
358
+ >>> # activated
359
+ >>> foo.cache_activate(self)
360
+ >>> foo()
361
+ 1
362
+ >>> foo()
363
+ >>> foo()
364
+ >>>
365
+ """
366
+
367
+ @functools.wraps(fun)
368
+ def wrapper(self):
369
+ try:
370
+ # case 1: we previously entered oneshot() ctx
371
+ ret = self._cache[fun]
372
+ except AttributeError:
373
+ # case 2: we never entered oneshot() ctx
374
+ try:
375
+ return fun(self)
376
+ except Exception as err:
377
+ raise err from None
378
+ except KeyError:
379
+ # case 3: we entered oneshot() ctx but there's no cache
380
+ # for this entry yet
381
+ try:
382
+ ret = fun(self)
383
+ except Exception as err:
384
+ raise err from None
385
+ try:
386
+ self._cache[fun] = ret
387
+ except AttributeError:
388
+ # multi-threading race condition, see:
389
+ # https://github.com/giampaolo/psutil/issues/1948
390
+ pass
391
+ return ret
392
+
393
+ def cache_activate(proc):
394
+ """Activate cache. Expects a Process instance. Cache will be
395
+ stored as a "_cache" instance attribute.
396
+ """
397
+ proc._cache = {}
398
+
399
+ def cache_deactivate(proc):
400
+ """Deactivate and clear cache."""
401
+ try:
402
+ del proc._cache
403
+ except AttributeError:
404
+ pass
405
+
406
+ wrapper.cache_activate = cache_activate
407
+ wrapper.cache_deactivate = cache_deactivate
408
+ return wrapper
409
+
410
+
411
+ def isfile_strict(path):
412
+ """Same as os.path.isfile() but does not swallow EACCES / EPERM
413
+ exceptions, see:
414
+ http://mail.python.org/pipermail/python-dev/2012-June/120787.html.
415
+ """
416
+ try:
417
+ st = os.stat(path)
418
+ except PermissionError:
419
+ raise
420
+ except OSError:
421
+ return False
422
+ else:
423
+ return stat.S_ISREG(st.st_mode)
424
+
425
+
426
+ def path_exists_strict(path):
427
+ """Same as os.path.exists() but does not swallow EACCES / EPERM
428
+ exceptions. See:
429
+ http://mail.python.org/pipermail/python-dev/2012-June/120787.html.
430
+ """
431
+ try:
432
+ os.stat(path)
433
+ except PermissionError:
434
+ raise
435
+ except OSError:
436
+ return False
437
+ else:
438
+ return True
439
+
440
+
441
+ def supports_ipv6():
442
+ """Return True if IPv6 is supported on this platform."""
443
+ if not socket.has_ipv6 or AF_INET6 is None:
444
+ return False
445
+ try:
446
+ with socket.socket(AF_INET6, socket.SOCK_STREAM) as sock:
447
+ sock.bind(("::1", 0))
448
+ return True
449
+ except OSError:
450
+ return False
451
+
452
+
453
+ def parse_environ_block(data):
454
+ """Parse a C environ block of environment variables into a dictionary."""
455
+ # The block is usually raw data from the target process. It might contain
456
+ # trailing garbage and lines that do not look like assignments.
457
+ ret = {}
458
+ pos = 0
459
+
460
+ # localize global variable to speed up access.
461
+ WINDOWS_ = WINDOWS
462
+ while True:
463
+ next_pos = data.find("\0", pos)
464
+ # nul byte at the beginning or double nul byte means finish
465
+ if next_pos <= pos:
466
+ break
467
+ # there might not be an equals sign
468
+ equal_pos = data.find("=", pos, next_pos)
469
+ if equal_pos > pos:
470
+ key = data[pos:equal_pos]
471
+ value = data[equal_pos + 1 : next_pos]
472
+ # Windows expects environment variables to be uppercase only
473
+ if WINDOWS_:
474
+ key = key.upper()
475
+ ret[key] = value
476
+ pos = next_pos + 1
477
+
478
+ return ret
479
+
480
+
481
+ def sockfam_to_enum(num):
482
+ """Convert a numeric socket family value to an IntEnum member.
483
+ If it's not a known member, return the numeric value itself.
484
+ """
485
+ try:
486
+ return socket.AddressFamily(num)
487
+ except ValueError:
488
+ return num
489
+
490
+
491
+ def socktype_to_enum(num):
492
+ """Convert a numeric socket type value to an IntEnum member.
493
+ If it's not a known member, return the numeric value itself.
494
+ """
495
+ try:
496
+ return socket.SocketKind(num)
497
+ except ValueError:
498
+ return num
499
+
500
+
501
+ def conn_to_ntuple(fd, fam, type_, laddr, raddr, status, status_map, pid=None):
502
+ """Convert a raw connection tuple to a proper ntuple."""
503
+ from . import _ntuples as ntp
504
+
505
+ if fam in {socket.AF_INET, AF_INET6}:
506
+ if laddr:
507
+ laddr = ntp.addr(*laddr)
508
+ if raddr:
509
+ raddr = ntp.addr(*raddr)
510
+ if type_ == socket.SOCK_STREAM and fam in {AF_INET, AF_INET6}:
511
+ status = status_map.get(status, CONN_NONE)
512
+ else:
513
+ status = CONN_NONE # ignore whatever C returned to us
514
+ fam = sockfam_to_enum(fam)
515
+ type_ = socktype_to_enum(type_)
516
+ if pid is None:
517
+ return ntp.pconn(fd, fam, type_, laddr, raddr, status)
518
+ else:
519
+ return ntp.sconn(fd, fam, type_, laddr, raddr, status, pid)
520
+
521
+
522
+ def broadcast_addr(addr):
523
+ """Given the address ntuple returned by ``net_if_addrs()``
524
+ calculates the broadcast address.
525
+ """
526
+ import ipaddress
527
+
528
+ if not addr.address or not addr.netmask:
529
+ return None
530
+ if addr.family == socket.AF_INET:
531
+ return str(
532
+ ipaddress.IPv4Network(
533
+ f"{addr.address}/{addr.netmask}", strict=False
534
+ ).broadcast_address
535
+ )
536
+ if addr.family == socket.AF_INET6:
537
+ return str(
538
+ ipaddress.IPv6Network(
539
+ f"{addr.address}/{addr.netmask}", strict=False
540
+ ).broadcast_address
541
+ )
542
+
543
+
544
+ def deprecated_method(replacement):
545
+ """A decorator which can be used to mark a method as deprecated
546
+ 'replcement' is the method name which will be called instead.
547
+ """
548
+
549
+ def outer(fun):
550
+ msg = (
551
+ f"{fun.__name__}() is deprecated and will be removed; use"
552
+ f" {replacement}() instead"
553
+ )
554
+ if fun.__doc__ is None:
555
+ fun.__doc__ = msg
556
+
557
+ @functools.wraps(fun)
558
+ def inner(self, *args, **kwargs):
559
+ warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
560
+ return getattr(self, replacement)(*args, **kwargs)
561
+
562
+ return inner
563
+
564
+ return outer
565
+
566
+
567
+ class _WrapNumbers:
568
+ """Watches numbers so that they don't overflow and wrap
569
+ (reset to zero).
570
+ """
571
+
572
+ def __init__(self):
573
+ self.lock = threading.Lock()
574
+ self.cache = {}
575
+ self.reminders = {}
576
+ self.reminder_keys = {}
577
+
578
+ def _add_dict(self, input_dict, name):
579
+ assert name not in self.cache
580
+ assert name not in self.reminders
581
+ assert name not in self.reminder_keys
582
+ self.cache[name] = input_dict
583
+ self.reminders[name] = collections.defaultdict(int)
584
+ self.reminder_keys[name] = collections.defaultdict(set)
585
+
586
+ def _remove_dead_reminders(self, input_dict, name):
587
+ """In case the number of keys changed between calls (e.g. a
588
+ disk disappears) this removes the entry from self.reminders.
589
+ """
590
+ old_dict = self.cache[name]
591
+ gone_keys = set(old_dict.keys()) - set(input_dict.keys())
592
+ for gone_key in gone_keys:
593
+ for remkey in self.reminder_keys[name][gone_key]:
594
+ del self.reminders[name][remkey]
595
+ del self.reminder_keys[name][gone_key]
596
+
597
+ def run(self, input_dict, name):
598
+ """Cache dict and sum numbers which overflow and wrap.
599
+ Return an updated copy of `input_dict`.
600
+ """
601
+ if name not in self.cache:
602
+ # This was the first call.
603
+ self._add_dict(input_dict, name)
604
+ return input_dict
605
+
606
+ self._remove_dead_reminders(input_dict, name)
607
+
608
+ old_dict = self.cache[name]
609
+ new_dict = {}
610
+ for key in input_dict:
611
+ input_tuple = input_dict[key]
612
+ try:
613
+ old_tuple = old_dict[key]
614
+ except KeyError:
615
+ # The input dict has a new key (e.g. a new disk or NIC)
616
+ # which didn't exist in the previous call.
617
+ new_dict[key] = input_tuple
618
+ continue
619
+
620
+ bits = []
621
+ for i in range(len(input_tuple)):
622
+ input_value = input_tuple[i]
623
+ old_value = old_tuple[i]
624
+ remkey = (key, i)
625
+ if input_value < old_value:
626
+ # it wrapped!
627
+ self.reminders[name][remkey] += old_value
628
+ self.reminder_keys[name][key].add(remkey)
629
+ bits.append(input_value + self.reminders[name][remkey])
630
+
631
+ new_dict[key] = tuple(bits)
632
+
633
+ self.cache[name] = input_dict
634
+ return new_dict
635
+
636
+ def cache_clear(self, name=None):
637
+ """Clear the internal cache, optionally only for function 'name'."""
638
+ with self.lock:
639
+ if name is None:
640
+ self.cache.clear()
641
+ self.reminders.clear()
642
+ self.reminder_keys.clear()
643
+ else:
644
+ self.cache.pop(name, None)
645
+ self.reminders.pop(name, None)
646
+ self.reminder_keys.pop(name, None)
647
+
648
+ def cache_info(self):
649
+ """Return internal cache dicts as a tuple of 3 elements."""
650
+ with self.lock:
651
+ return (self.cache, self.reminders, self.reminder_keys)
652
+
653
+
654
+ def wrap_numbers(input_dict, name):
655
+ """Given an `input_dict` and a function `name`, adjust the numbers
656
+ which "wrap" (restart from zero) across different calls by adding
657
+ "old value" to "new value" and return an updated dict.
658
+ """
659
+ with _wn.lock:
660
+ return _wn.run(input_dict, name)
661
+
662
+
663
+ _wn = _WrapNumbers()
664
+ wrap_numbers.cache_clear = _wn.cache_clear
665
+ wrap_numbers.cache_info = _wn.cache_info
666
+
667
+
668
+ # The read buffer size for open() builtin. This (also) dictates how
669
+ # much data we read(2) when iterating over file lines as in:
670
+ # >>> with open(file) as f:
671
+ # ... for line in f:
672
+ # ... ...
673
+ # Default per-line buffer size for binary files is 1K. For text files
674
+ # is 8K. We use a bigger buffer (32K) in order to have more consistent
675
+ # results when reading /proc pseudo files on Linux, see:
676
+ # https://github.com/giampaolo/psutil/issues/2050
677
+ # https://github.com/giampaolo/psutil/issues/708
678
+ FILE_READ_BUFFER_SIZE = 32 * 1024
679
+
680
+
681
+ def open_binary(fname):
682
+ return open(fname, "rb", buffering=FILE_READ_BUFFER_SIZE)
683
+
684
+
685
+ def open_text(fname):
686
+ """Open a file in text mode by using the proper FS encoding and
687
+ en/decoding error handlers.
688
+ """
689
+ # See:
690
+ # https://github.com/giampaolo/psutil/issues/675
691
+ # https://github.com/giampaolo/psutil/pull/733
692
+ fobj = open( # noqa: SIM115
693
+ fname,
694
+ buffering=FILE_READ_BUFFER_SIZE,
695
+ encoding=ENCODING,
696
+ errors=ENCODING_ERRS,
697
+ )
698
+ try:
699
+ # Dictates per-line read(2) buffer size. Defaults is 8k. See:
700
+ # https://github.com/giampaolo/psutil/issues/2050#issuecomment-1013387546
701
+ fobj._CHUNK_SIZE = FILE_READ_BUFFER_SIZE
702
+ except AttributeError:
703
+ pass
704
+ except Exception:
705
+ fobj.close()
706
+ raise
707
+
708
+ return fobj
709
+
710
+
711
+ def cat(fname, fallback=_DEFAULT, _open=open_text):
712
+ """Read entire file content and return it as a string. File is
713
+ opened in text mode. If specified, `fallback` is the value
714
+ returned in case of error, either if the file does not exist or
715
+ it can't be read().
716
+ """
717
+ if fallback is _DEFAULT:
718
+ with _open(fname) as f:
719
+ return f.read()
720
+ else:
721
+ try:
722
+ with _open(fname) as f:
723
+ return f.read()
724
+ except OSError:
725
+ return fallback
726
+
727
+
728
+ def bcat(fname, fallback=_DEFAULT):
729
+ """Same as above but opens file in binary mode."""
730
+ return cat(fname, fallback=fallback, _open=open_binary)
731
+
732
+
733
+ def bytes2human(n, format="%(value).1f%(symbol)s"):
734
+ """Used by various scripts. See: https://code.activestate.com/recipes/578019-bytes-to-human-human-to-bytes-converter/?in=user-4178764.
735
+
736
+ >>> bytes2human(10000)
737
+ '9.8K'
738
+ >>> bytes2human(100001221)
739
+ '95.4M'
740
+ """
741
+ symbols = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
742
+ prefix = {}
743
+ for i, s in enumerate(symbols[1:]):
744
+ prefix[s] = 1 << (i + 1) * 10
745
+ for symbol in reversed(symbols[1:]):
746
+ if abs(n) >= prefix[symbol]:
747
+ value = float(n) / prefix[symbol]
748
+ return format % locals()
749
+ return format % dict(symbol=symbols[0], value=n)
750
+
751
+
752
+ def get_procfs_path():
753
+ """Return updated psutil.PROCFS_PATH constant."""
754
+ return sys.modules['psutil'].PROCFS_PATH
755
+
756
+
757
+ def decode(s):
758
+ return s.decode(encoding=ENCODING, errors=ENCODING_ERRS)
759
+
760
+
761
+ # =====================================================================
762
+ # --- shell utils
763
+ # =====================================================================
764
+
765
+
766
+ @memoize
767
+ def term_supports_colors(file=sys.stdout): # pragma: no cover
768
+ if not hasattr(file, "isatty") or not file.isatty():
769
+ return False
770
+ try:
771
+ file.fileno()
772
+ except Exception: # noqa: BLE001
773
+ return False
774
+ return True
775
+
776
+
777
+ def hilite(s, color=None, bold=False): # pragma: no cover
778
+ """Return an highlighted version of 'string'."""
779
+ if not term_supports_colors():
780
+ return s
781
+ attr = []
782
+ colors = dict(
783
+ blue='34',
784
+ brown='33',
785
+ darkgrey='30',
786
+ green='32',
787
+ grey='37',
788
+ lightblue='36',
789
+ red='91',
790
+ violet='35',
791
+ yellow='93',
792
+ )
793
+ colors[None] = '29'
794
+ try:
795
+ color = colors[color]
796
+ except KeyError:
797
+ msg = f"invalid color {color!r}; choose amongst {list(colors.keys())}"
798
+ raise ValueError(msg) from None
799
+ attr.append(color)
800
+ if bold:
801
+ attr.append('1')
802
+ return f"\x1b[{';'.join(attr)}m{s}\x1b[0m"
803
+
804
+
805
+ def print_color(
806
+ s, color=None, bold=False, file=sys.stdout
807
+ ): # pragma: no cover
808
+ """Print a colorized version of string."""
809
+ if not term_supports_colors():
810
+ print(s, file=file)
811
+ elif POSIX:
812
+ print(hilite(s, color, bold), file=file)
813
+ else:
814
+ import ctypes
815
+
816
+ DEFAULT_COLOR = 7
817
+ GetStdHandle = ctypes.windll.Kernel32.GetStdHandle
818
+ SetConsoleTextAttribute = (
819
+ ctypes.windll.Kernel32.SetConsoleTextAttribute
820
+ )
821
+
822
+ colors = dict(green=2, red=4, brown=6, yellow=6)
823
+ colors[None] = DEFAULT_COLOR
824
+ try:
825
+ color = colors[color]
826
+ except KeyError:
827
+ msg = (
828
+ f"invalid color {color!r}; choose between"
829
+ f" {list(colors.keys())!r}"
830
+ )
831
+ raise ValueError(msg) from None
832
+ if bold and color <= 7:
833
+ color += 8
834
+
835
+ handle_id = -12 if file is sys.stderr else -11
836
+ GetStdHandle.restype = ctypes.c_ulong
837
+ handle = GetStdHandle(handle_id)
838
+ SetConsoleTextAttribute(handle, color)
839
+ try:
840
+ print(s, file=file)
841
+ finally:
842
+ SetConsoleTextAttribute(handle, DEFAULT_COLOR)
843
+
844
+
845
+ def debug(msg):
846
+ """If PSUTIL_DEBUG env var is set, print a debug message to stderr."""
847
+ if PSUTIL_DEBUG:
848
+ import inspect
849
+
850
+ fname, lineno, _, _lines, _index = inspect.getframeinfo(
851
+ inspect.currentframe().f_back
852
+ )
853
+ if isinstance(msg, Exception):
854
+ if isinstance(msg, OSError):
855
+ # ...because str(exc) may contain info about the file name
856
+ msg = f"ignoring {msg}"
857
+ else:
858
+ msg = f"ignoring {msg!r}"
859
+ print( # noqa: T201
860
+ f"psutil-debug [{fname}:{lineno}]> {msg}", file=sys.stderr
861
+ )
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_ntuples.py ADDED
@@ -0,0 +1,429 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2009, Giampaolo Rodola". All rights reserved.
2
+ # Use of this source code is governed by a BSD-style license that can be
3
+ # found in the LICENSE file.
4
+
5
+ from collections import namedtuple as nt
6
+
7
+ from ._common import AIX
8
+ from ._common import BSD
9
+ from ._common import FREEBSD
10
+ from ._common import LINUX
11
+ from ._common import MACOS
12
+ from ._common import SUNOS
13
+ from ._common import WINDOWS
14
+
15
+ # ===================================================================
16
+ # --- system functions
17
+ # ===================================================================
18
+
19
+ # psutil.swap_memory()
20
+ sswap = nt("sswap", ("total", "used", "free", "percent", "sin", "sout"))
21
+
22
+ # psutil.disk_usage()
23
+ sdiskusage = nt("sdiskusage", ("total", "used", "free", "percent"))
24
+
25
+ # psutil.disk_io_counters()
26
+ sdiskio = nt(
27
+ "sdiskio",
28
+ (
29
+ "read_count",
30
+ "write_count",
31
+ "read_bytes",
32
+ "write_bytes",
33
+ "read_time",
34
+ "write_time",
35
+ ),
36
+ )
37
+
38
+ # psutil.disk_partitions()
39
+ sdiskpart = nt("sdiskpart", ("device", "mountpoint", "fstype", "opts"))
40
+
41
+ # psutil.net_io_counters()
42
+ snetio = nt(
43
+ "snetio",
44
+ (
45
+ "bytes_sent",
46
+ "bytes_recv",
47
+ "packets_sent",
48
+ "packets_recv",
49
+ "errin",
50
+ "errout",
51
+ "dropin",
52
+ "dropout",
53
+ ),
54
+ )
55
+
56
+ # psutil.users()
57
+ suser = nt("suser", ("name", "terminal", "host", "started", "pid"))
58
+
59
+ # psutil.net_connections()
60
+ sconn = nt(
61
+ "sconn", ("fd", "family", "type", "laddr", "raddr", "status", "pid")
62
+ )
63
+
64
+ # psutil.net_if_addrs()
65
+ snicaddr = nt("snicaddr", ("family", "address", "netmask", "broadcast", "ptp"))
66
+
67
+ # psutil.net_if_stats()
68
+ snicstats = nt("snicstats", ("isup", "duplex", "speed", "mtu", "flags"))
69
+
70
+ # psutil.cpu_stats()
71
+ scpustats = nt(
72
+ "scpustats", ("ctx_switches", "interrupts", "soft_interrupts", "syscalls")
73
+ )
74
+
75
+ # psutil.cpu_freq()
76
+ scpufreq = nt("scpufreq", ("current", "min", "max"))
77
+
78
+ # psutil.sensors_temperatures()
79
+ shwtemp = nt("shwtemp", ("label", "current", "high", "critical"))
80
+
81
+ # psutil.sensors_battery()
82
+ sbattery = nt("sbattery", ("percent", "secsleft", "power_plugged"))
83
+
84
+ # psutil.sensors_fans()
85
+ sfan = nt("sfan", ("label", "current"))
86
+
87
+ # psutil.heap_info() (mallinfo2 Linux struct)
88
+ if LINUX or WINDOWS or MACOS or BSD:
89
+ pheap = nt(
90
+ "pheap",
91
+ [
92
+ "heap_used", # uordblks, memory allocated via malloc()
93
+ "mmap_used", # hblkhd, memory allocated via mmap() (large blocks)
94
+ ],
95
+ )
96
+ if WINDOWS:
97
+ pheap = nt("pheap", pheap._fields + ("heap_count",))
98
+
99
+ # ===================================================================
100
+ # --- Process class
101
+ # ===================================================================
102
+
103
+ # psutil.Process.cpu_times()
104
+ pcputimes = nt(
105
+ "pcputimes", ("user", "system", "children_user", "children_system")
106
+ )
107
+
108
+ # psutil.Process.open_files()
109
+ popenfile = nt("popenfile", ("path", "fd"))
110
+
111
+ # psutil.Process.threads()
112
+ pthread = nt("pthread", ("id", "user_time", "system_time"))
113
+
114
+ # psutil.Process.uids()
115
+ puids = nt("puids", ("real", "effective", "saved"))
116
+
117
+ # psutil.Process.gids()
118
+ pgids = nt("pgids", ("real", "effective", "saved"))
119
+
120
+ # psutil.Process.io_counters()
121
+ pio = nt("pio", ("read_count", "write_count", "read_bytes", "write_bytes"))
122
+
123
+ # psutil.Process.ionice()
124
+ pionice = nt("pionice", ("ioclass", "value"))
125
+
126
+ # psutil.Process.ctx_switches()
127
+ pctxsw = nt("pctxsw", ("voluntary", "involuntary"))
128
+
129
+ # psutil.Process.net_connections()
130
+ pconn = nt("pconn", ("fd", "family", "type", "laddr", "raddr", "status"))
131
+
132
+ # psutil.net_connections() and psutil.Process.net_connections()
133
+ addr = nt("addr", ("ip", "port"))
134
+
135
+ # ===================================================================
136
+ # --- Linux
137
+ # ===================================================================
138
+
139
+ if LINUX:
140
+
141
+ # This gets set from _pslinux.py
142
+ scputimes = None
143
+
144
+ # psutil.virtual_memory()
145
+ svmem = nt(
146
+ "svmem",
147
+ (
148
+ "total",
149
+ "available",
150
+ "percent",
151
+ "used",
152
+ "free",
153
+ "active",
154
+ "inactive",
155
+ "buffers",
156
+ "cached",
157
+ "shared",
158
+ "slab",
159
+ ),
160
+ )
161
+
162
+ # psutil.disk_io_counters()
163
+ sdiskio = nt(
164
+ "sdiskio",
165
+ (
166
+ "read_count",
167
+ "write_count",
168
+ "read_bytes",
169
+ "write_bytes",
170
+ "read_time",
171
+ "write_time",
172
+ "read_merged_count",
173
+ "write_merged_count",
174
+ "busy_time",
175
+ ),
176
+ )
177
+
178
+ # psutil.Process().open_files()
179
+ popenfile = nt("popenfile", ("path", "fd", "position", "mode", "flags"))
180
+
181
+ # psutil.Process().memory_info()
182
+ pmem = nt("pmem", ("rss", "vms", "shared", "text", "lib", "data", "dirty"))
183
+
184
+ # psutil.Process().memory_full_info()
185
+ pfullmem = nt("pfullmem", pmem._fields + ("uss", "pss", "swap"))
186
+
187
+ # psutil.Process().memory_maps(grouped=True)
188
+ pmmap_grouped = nt(
189
+ "pmmap_grouped",
190
+ (
191
+ "path",
192
+ "rss",
193
+ "size",
194
+ "pss",
195
+ "shared_clean",
196
+ "shared_dirty",
197
+ "private_clean",
198
+ "private_dirty",
199
+ "referenced",
200
+ "anonymous",
201
+ "swap",
202
+ ),
203
+ )
204
+
205
+ # psutil.Process().memory_maps(grouped=False)
206
+ pmmap_ext = nt(
207
+ "pmmap_ext", "addr perms " + " ".join(pmmap_grouped._fields)
208
+ )
209
+
210
+ # psutil.Process.io_counters()
211
+ pio = nt(
212
+ "pio",
213
+ (
214
+ "read_count",
215
+ "write_count",
216
+ "read_bytes",
217
+ "write_bytes",
218
+ "read_chars",
219
+ "write_chars",
220
+ ),
221
+ )
222
+
223
+ # psutil.Process.cpu_times()
224
+ pcputimes = nt(
225
+ "pcputimes",
226
+ ("user", "system", "children_user", "children_system", "iowait"),
227
+ )
228
+
229
+ # ===================================================================
230
+ # --- Windows
231
+ # ===================================================================
232
+
233
+ elif WINDOWS:
234
+
235
+ # psutil.cpu_times()
236
+ scputimes = nt("scputimes", ("user", "system", "idle", "interrupt", "dpc"))
237
+
238
+ # psutil.virtual_memory()
239
+ svmem = nt("svmem", ("total", "available", "percent", "used", "free"))
240
+
241
+ # psutil.Process.memory_info()
242
+ pmem = nt(
243
+ "pmem",
244
+ (
245
+ "rss",
246
+ "vms",
247
+ "num_page_faults",
248
+ "peak_wset",
249
+ "wset",
250
+ "peak_paged_pool",
251
+ "paged_pool",
252
+ "peak_nonpaged_pool",
253
+ "nonpaged_pool",
254
+ "pagefile",
255
+ "peak_pagefile",
256
+ "private",
257
+ ),
258
+ )
259
+
260
+ # psutil.Process.memory_full_info()
261
+ pfullmem = nt("pfullmem", pmem._fields + ("uss",))
262
+
263
+ # psutil.Process.memory_maps(grouped=True)
264
+ pmmap_grouped = nt("pmmap_grouped", ("path", "rss"))
265
+
266
+ # psutil.Process.memory_maps(grouped=False)
267
+ pmmap_ext = nt(
268
+ "pmmap_ext", "addr perms " + " ".join(pmmap_grouped._fields)
269
+ )
270
+
271
+ # psutil.Process.io_counters()
272
+ pio = nt(
273
+ "pio",
274
+ (
275
+ "read_count",
276
+ "write_count",
277
+ "read_bytes",
278
+ "write_bytes",
279
+ "other_count",
280
+ "other_bytes",
281
+ ),
282
+ )
283
+
284
+ # ===================================================================
285
+ # --- macOS
286
+ # ===================================================================
287
+
288
+ elif MACOS:
289
+
290
+ # psutil.cpu_times()
291
+ scputimes = nt("scputimes", ("user", "nice", "system", "idle"))
292
+
293
+ # psutil.virtual_memory()
294
+ svmem = nt(
295
+ "svmem",
296
+ (
297
+ "total",
298
+ "available",
299
+ "percent",
300
+ "used",
301
+ "free",
302
+ "active",
303
+ "inactive",
304
+ "wired",
305
+ ),
306
+ )
307
+
308
+ # psutil.Process.memory_info()
309
+ pmem = nt("pmem", ("rss", "vms", "pfaults", "pageins"))
310
+
311
+ # psutil.Process.memory_full_info()
312
+ pfullmem = nt("pfullmem", pmem._fields + ("uss",))
313
+
314
+ # ===================================================================
315
+ # --- BSD
316
+ # ===================================================================
317
+
318
+ elif BSD:
319
+
320
+ # psutil.virtual_memory()
321
+ svmem = nt(
322
+ "svmem",
323
+ (
324
+ "total",
325
+ "available",
326
+ "percent",
327
+ "used",
328
+ "free",
329
+ "active",
330
+ "inactive",
331
+ "buffers",
332
+ "cached",
333
+ "shared",
334
+ "wired",
335
+ ),
336
+ )
337
+
338
+ # psutil.cpu_times()
339
+ scputimes = nt("scputimes", ("user", "nice", "system", "idle", "irq"))
340
+
341
+ # psutil.Process.memory_info()
342
+ pmem = nt("pmem", ("rss", "vms", "text", "data", "stack"))
343
+
344
+ # psutil.Process.memory_full_info()
345
+ pfullmem = pmem
346
+
347
+ # psutil.Process.cpu_times()
348
+ pcputimes = nt(
349
+ "pcputimes", ("user", "system", "children_user", "children_system")
350
+ )
351
+
352
+ # psutil.Process.memory_maps(grouped=True)
353
+ pmmap_grouped = nt(
354
+ "pmmap_grouped", "path rss, private, ref_count, shadow_count"
355
+ )
356
+
357
+ # psutil.Process.memory_maps(grouped=False)
358
+ pmmap_ext = nt(
359
+ "pmmap_ext", "addr, perms path rss, private, ref_count, shadow_count"
360
+ )
361
+
362
+ # psutil.disk_io_counters()
363
+ if FREEBSD:
364
+ sdiskio = nt(
365
+ "sdiskio",
366
+ (
367
+ "read_count",
368
+ "write_count",
369
+ "read_bytes",
370
+ "write_bytes",
371
+ "read_time",
372
+ "write_time",
373
+ "busy_time",
374
+ ),
375
+ )
376
+ else:
377
+ sdiskio = nt(
378
+ "sdiskio",
379
+ ("read_count", "write_count", "read_bytes", "write_bytes"),
380
+ )
381
+
382
+ # ===================================================================
383
+ # --- SunOS
384
+ # ===================================================================
385
+
386
+ elif SUNOS:
387
+
388
+ # psutil.cpu_times()
389
+ scputimes = nt("scputimes", ("user", "system", "idle", "iowait"))
390
+
391
+ # psutil.cpu_times(percpu=True)
392
+ pcputimes = nt(
393
+ "pcputimes", ("user", "system", "children_user", "children_system")
394
+ )
395
+
396
+ # psutil.virtual_memory()
397
+ svmem = nt("svmem", ("total", "available", "percent", "used", "free"))
398
+
399
+ # psutil.Process.memory_info()
400
+ pmem = nt("pmem", ("rss", "vms"))
401
+
402
+ # psutil.Process.memory_full_info()
403
+ pfullmem = pmem
404
+
405
+ # psutil.Process.memory_maps(grouped=True)
406
+ pmmap_grouped = nt("pmmap_grouped", ("path", "rss", "anonymous", "locked"))
407
+
408
+ # psutil.Process.memory_maps(grouped=False)
409
+ pmmap_ext = nt(
410
+ "pmmap_ext", "addr perms " + " ".join(pmmap_grouped._fields)
411
+ )
412
+
413
+ # ===================================================================
414
+ # --- AIX
415
+ # ===================================================================
416
+
417
+ elif AIX:
418
+
419
+ # psutil.Process.memory_info()
420
+ pmem = nt("pmem", ("rss", "vms"))
421
+
422
+ # psutil.Process.memory_full_info()
423
+ pfullmem = pmem
424
+
425
+ # psutil.Process.cpu_times()
426
+ scputimes = nt("scputimes", ("user", "system", "idle", "iowait"))
427
+
428
+ # psutil.virtual_memory()
429
+ svmem = nt("svmem", ("total", "available", "percent", "used", "free"))
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_psaix.py ADDED
@@ -0,0 +1,546 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2009, Giampaolo Rodola'
2
+ # Copyright (c) 2017, Arnon Yaari
3
+ # All rights reserved.
4
+ # Use of this source code is governed by a BSD-style license that can be
5
+ # found in the LICENSE file.
6
+
7
+ """AIX platform implementation."""
8
+
9
+ import functools
10
+ import glob
11
+ import os
12
+ import re
13
+ import subprocess
14
+ import sys
15
+
16
+ from . import _common
17
+ from . import _ntuples as ntp
18
+ from . import _psposix
19
+ from . import _psutil_aix as cext
20
+ from ._common import NIC_DUPLEX_FULL
21
+ from ._common import NIC_DUPLEX_HALF
22
+ from ._common import NIC_DUPLEX_UNKNOWN
23
+ from ._common import AccessDenied
24
+ from ._common import NoSuchProcess
25
+ from ._common import ZombieProcess
26
+ from ._common import conn_to_ntuple
27
+ from ._common import get_procfs_path
28
+ from ._common import memoize_when_activated
29
+ from ._common import usage_percent
30
+
31
+ __extra__all__ = ["PROCFS_PATH"]
32
+
33
+
34
+ # =====================================================================
35
+ # --- globals
36
+ # =====================================================================
37
+
38
+
39
+ HAS_THREADS = hasattr(cext, "proc_threads")
40
+ HAS_NET_IO_COUNTERS = hasattr(cext, "net_io_counters")
41
+ HAS_PROC_IO_COUNTERS = hasattr(cext, "proc_io_counters")
42
+
43
+ PAGE_SIZE = cext.getpagesize()
44
+ AF_LINK = cext.AF_LINK
45
+
46
+ PROC_STATUSES = {
47
+ cext.SIDL: _common.STATUS_IDLE,
48
+ cext.SZOMB: _common.STATUS_ZOMBIE,
49
+ cext.SACTIVE: _common.STATUS_RUNNING,
50
+ cext.SSWAP: _common.STATUS_RUNNING, # TODO what status is this?
51
+ cext.SSTOP: _common.STATUS_STOPPED,
52
+ }
53
+
54
+ TCP_STATUSES = {
55
+ cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
56
+ cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
57
+ cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV,
58
+ cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
59
+ cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
60
+ cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
61
+ cext.TCPS_CLOSED: _common.CONN_CLOSE,
62
+ cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
63
+ cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
64
+ cext.TCPS_LISTEN: _common.CONN_LISTEN,
65
+ cext.TCPS_CLOSING: _common.CONN_CLOSING,
66
+ cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
67
+ }
68
+
69
+ proc_info_map = dict(
70
+ ppid=0,
71
+ rss=1,
72
+ vms=2,
73
+ create_time=3,
74
+ nice=4,
75
+ num_threads=5,
76
+ status=6,
77
+ ttynr=7,
78
+ )
79
+
80
+
81
+ # =====================================================================
82
+ # --- memory
83
+ # =====================================================================
84
+
85
+
86
+ def virtual_memory():
87
+ total, avail, free, _pinned, inuse = cext.virtual_mem()
88
+ percent = usage_percent((total - avail), total, round_=1)
89
+ return ntp.svmem(total, avail, percent, inuse, free)
90
+
91
+
92
+ def swap_memory():
93
+ """Swap system memory as a (total, used, free, sin, sout) tuple."""
94
+ total, free, sin, sout = cext.swap_mem()
95
+ used = total - free
96
+ percent = usage_percent(used, total, round_=1)
97
+ return ntp.sswap(total, used, free, percent, sin, sout)
98
+
99
+
100
+ # =====================================================================
101
+ # --- CPU
102
+ # =====================================================================
103
+
104
+
105
+ def cpu_times():
106
+ """Return system-wide CPU times as a named tuple."""
107
+ ret = cext.per_cpu_times()
108
+ return ntp.scputimes(*[sum(x) for x in zip(*ret)])
109
+
110
+
111
+ def per_cpu_times():
112
+ """Return system per-CPU times as a list of named tuples."""
113
+ ret = cext.per_cpu_times()
114
+ return [ntp.scputimes(*x) for x in ret]
115
+
116
+
117
+ def cpu_count_logical():
118
+ """Return the number of logical CPUs in the system."""
119
+ try:
120
+ return os.sysconf("SC_NPROCESSORS_ONLN")
121
+ except ValueError:
122
+ # mimic os.cpu_count() behavior
123
+ return None
124
+
125
+
126
+ def cpu_count_cores():
127
+ cmd = ["lsdev", "-Cc", "processor"]
128
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
129
+ stdout, stderr = p.communicate()
130
+ stdout, stderr = (x.decode(sys.stdout.encoding) for x in (stdout, stderr))
131
+ if p.returncode != 0:
132
+ msg = f"{cmd!r} command error\n{stderr}"
133
+ raise RuntimeError(msg)
134
+ processors = stdout.strip().splitlines()
135
+ return len(processors) or None
136
+
137
+
138
+ def cpu_stats():
139
+ """Return various CPU stats as a named tuple."""
140
+ ctx_switches, interrupts, soft_interrupts, syscalls = cext.cpu_stats()
141
+ return ntp.scpustats(ctx_switches, interrupts, soft_interrupts, syscalls)
142
+
143
+
144
+ # =====================================================================
145
+ # --- disks
146
+ # =====================================================================
147
+
148
+
149
+ disk_io_counters = cext.disk_io_counters
150
+ disk_usage = _psposix.disk_usage
151
+
152
+
153
+ def disk_partitions(all=False):
154
+ """Return system disk partitions."""
155
+ # TODO - the filtering logic should be better checked so that
156
+ # it tries to reflect 'df' as much as possible
157
+ retlist = []
158
+ partitions = cext.disk_partitions()
159
+ for partition in partitions:
160
+ device, mountpoint, fstype, opts = partition
161
+ if device == 'none':
162
+ device = ''
163
+ if not all:
164
+ # Differently from, say, Linux, we don't have a list of
165
+ # common fs types so the best we can do, AFAIK, is to
166
+ # filter by filesystem having a total size > 0.
167
+ if not disk_usage(mountpoint).total:
168
+ continue
169
+ ntuple = ntp.sdiskpart(device, mountpoint, fstype, opts)
170
+ retlist.append(ntuple)
171
+ return retlist
172
+
173
+
174
+ # =====================================================================
175
+ # --- network
176
+ # =====================================================================
177
+
178
+
179
+ net_if_addrs = cext.net_if_addrs
180
+
181
+ if HAS_NET_IO_COUNTERS:
182
+ net_io_counters = cext.net_io_counters
183
+
184
+
185
+ def net_connections(kind, _pid=-1):
186
+ """Return socket connections. If pid == -1 return system-wide
187
+ connections (as opposed to connections opened by one process only).
188
+ """
189
+ families, types = _common.conn_tmap[kind]
190
+ rawlist = cext.net_connections(_pid)
191
+ ret = []
192
+ for item in rawlist:
193
+ fd, fam, type_, laddr, raddr, status, pid = item
194
+ if fam not in families:
195
+ continue
196
+ if type_ not in types:
197
+ continue
198
+ nt = conn_to_ntuple(
199
+ fd,
200
+ fam,
201
+ type_,
202
+ laddr,
203
+ raddr,
204
+ status,
205
+ TCP_STATUSES,
206
+ pid=pid if _pid == -1 else None,
207
+ )
208
+ ret.append(nt)
209
+ return ret
210
+
211
+
212
+ def net_if_stats():
213
+ """Get NIC stats (isup, duplex, speed, mtu)."""
214
+ duplex_map = {"Full": NIC_DUPLEX_FULL, "Half": NIC_DUPLEX_HALF}
215
+ names = {x[0] for x in net_if_addrs()}
216
+ ret = {}
217
+ for name in names:
218
+ mtu = cext.net_if_mtu(name)
219
+ flags = cext.net_if_flags(name)
220
+
221
+ # try to get speed and duplex
222
+ # TODO: rewrite this in C (entstat forks, so use truss -f to follow.
223
+ # looks like it is using an undocumented ioctl?)
224
+ duplex = ""
225
+ speed = 0
226
+ p = subprocess.Popen(
227
+ ["/usr/bin/entstat", "-d", name],
228
+ stdout=subprocess.PIPE,
229
+ stderr=subprocess.PIPE,
230
+ )
231
+ stdout, stderr = p.communicate()
232
+ stdout, stderr = (
233
+ x.decode(sys.stdout.encoding) for x in (stdout, stderr)
234
+ )
235
+ if p.returncode == 0:
236
+ re_result = re.search(
237
+ r"Running: (\d+) Mbps.*?(\w+) Duplex", stdout
238
+ )
239
+ if re_result is not None:
240
+ speed = int(re_result.group(1))
241
+ duplex = re_result.group(2)
242
+
243
+ output_flags = ','.join(flags)
244
+ isup = 'running' in flags
245
+ duplex = duplex_map.get(duplex, NIC_DUPLEX_UNKNOWN)
246
+ ret[name] = ntp.snicstats(isup, duplex, speed, mtu, output_flags)
247
+ return ret
248
+
249
+
250
+ # =====================================================================
251
+ # --- other system functions
252
+ # =====================================================================
253
+
254
+
255
+ def boot_time():
256
+ """The system boot time expressed in seconds since the epoch."""
257
+ return cext.boot_time()
258
+
259
+
260
+ def users():
261
+ """Return currently connected users as a list of namedtuples."""
262
+ retlist = []
263
+ rawlist = cext.users()
264
+ localhost = (':0.0', ':0')
265
+ for item in rawlist:
266
+ user, tty, hostname, tstamp, user_process, pid = item
267
+ # note: the underlying C function includes entries about
268
+ # system boot, run level and others. We might want
269
+ # to use them in the future.
270
+ if not user_process:
271
+ continue
272
+ if hostname in localhost:
273
+ hostname = 'localhost'
274
+ nt = ntp.suser(user, tty, hostname, tstamp, pid)
275
+ retlist.append(nt)
276
+ return retlist
277
+
278
+
279
+ # =====================================================================
280
+ # --- processes
281
+ # =====================================================================
282
+
283
+
284
+ def pids():
285
+ """Returns a list of PIDs currently running on the system."""
286
+ return [int(x) for x in os.listdir(get_procfs_path()) if x.isdigit()]
287
+
288
+
289
+ def pid_exists(pid):
290
+ """Check for the existence of a unix pid."""
291
+ return os.path.exists(os.path.join(get_procfs_path(), str(pid), "psinfo"))
292
+
293
+
294
+ def wrap_exceptions(fun):
295
+ """Call callable into a try/except clause and translate ENOENT,
296
+ EACCES and EPERM in NoSuchProcess or AccessDenied exceptions.
297
+ """
298
+
299
+ @functools.wraps(fun)
300
+ def wrapper(self, *args, **kwargs):
301
+ pid, ppid, name = self.pid, self._ppid, self._name
302
+ try:
303
+ return fun(self, *args, **kwargs)
304
+ except (FileNotFoundError, ProcessLookupError) as err:
305
+ # ENOENT (no such file or directory) gets raised on open().
306
+ # ESRCH (no such process) can get raised on read() if
307
+ # process is gone in meantime.
308
+ if not pid_exists(pid):
309
+ raise NoSuchProcess(pid, name) from err
310
+ raise ZombieProcess(pid, name, ppid) from err
311
+ except PermissionError as err:
312
+ raise AccessDenied(pid, name) from err
313
+
314
+ return wrapper
315
+
316
+
317
+ class Process:
318
+ """Wrapper class around underlying C implementation."""
319
+
320
+ __slots__ = ["_cache", "_name", "_ppid", "_procfs_path", "pid"]
321
+
322
+ def __init__(self, pid):
323
+ self.pid = pid
324
+ self._name = None
325
+ self._ppid = None
326
+ self._procfs_path = get_procfs_path()
327
+
328
+ def oneshot_enter(self):
329
+ self._proc_basic_info.cache_activate(self)
330
+ self._proc_cred.cache_activate(self)
331
+
332
+ def oneshot_exit(self):
333
+ self._proc_basic_info.cache_deactivate(self)
334
+ self._proc_cred.cache_deactivate(self)
335
+
336
+ @wrap_exceptions
337
+ @memoize_when_activated
338
+ def _proc_basic_info(self):
339
+ return cext.proc_basic_info(self.pid, self._procfs_path)
340
+
341
+ @wrap_exceptions
342
+ @memoize_when_activated
343
+ def _proc_cred(self):
344
+ return cext.proc_cred(self.pid, self._procfs_path)
345
+
346
+ @wrap_exceptions
347
+ def name(self):
348
+ if self.pid == 0:
349
+ return "swapper"
350
+ # note: max 16 characters
351
+ return cext.proc_name(self.pid, self._procfs_path).rstrip("\x00")
352
+
353
+ @wrap_exceptions
354
+ def exe(self):
355
+ # there is no way to get executable path in AIX other than to guess,
356
+ # and guessing is more complex than what's in the wrapping class
357
+ cmdline = self.cmdline()
358
+ if not cmdline:
359
+ return ''
360
+ exe = cmdline[0]
361
+ if os.path.sep in exe:
362
+ # relative or absolute path
363
+ if not os.path.isabs(exe):
364
+ # if cwd has changed, we're out of luck - this may be wrong!
365
+ exe = os.path.abspath(os.path.join(self.cwd(), exe))
366
+ if (
367
+ os.path.isabs(exe)
368
+ and os.path.isfile(exe)
369
+ and os.access(exe, os.X_OK)
370
+ ):
371
+ return exe
372
+ # not found, move to search in PATH using basename only
373
+ exe = os.path.basename(exe)
374
+ # search for exe name PATH
375
+ for path in os.environ["PATH"].split(":"):
376
+ possible_exe = os.path.abspath(os.path.join(path, exe))
377
+ if os.path.isfile(possible_exe) and os.access(
378
+ possible_exe, os.X_OK
379
+ ):
380
+ return possible_exe
381
+ return ''
382
+
383
+ @wrap_exceptions
384
+ def cmdline(self):
385
+ return cext.proc_args(self.pid)
386
+
387
+ @wrap_exceptions
388
+ def environ(self):
389
+ return cext.proc_environ(self.pid)
390
+
391
+ @wrap_exceptions
392
+ def create_time(self):
393
+ return self._proc_basic_info()[proc_info_map['create_time']]
394
+
395
+ @wrap_exceptions
396
+ def num_threads(self):
397
+ return self._proc_basic_info()[proc_info_map['num_threads']]
398
+
399
+ if HAS_THREADS:
400
+
401
+ @wrap_exceptions
402
+ def threads(self):
403
+ rawlist = cext.proc_threads(self.pid)
404
+ retlist = []
405
+ for thread_id, utime, stime in rawlist:
406
+ ntuple = ntp.pthread(thread_id, utime, stime)
407
+ retlist.append(ntuple)
408
+ # The underlying C implementation retrieves all OS threads
409
+ # and filters them by PID. At this point we can't tell whether
410
+ # an empty list means there were no connections for process or
411
+ # process is no longer active so we force NSP in case the PID
412
+ # is no longer there.
413
+ if not retlist:
414
+ # will raise NSP if process is gone
415
+ os.stat(f"{self._procfs_path}/{self.pid}")
416
+ return retlist
417
+
418
+ @wrap_exceptions
419
+ def net_connections(self, kind='inet'):
420
+ ret = net_connections(kind, _pid=self.pid)
421
+ # The underlying C implementation retrieves all OS connections
422
+ # and filters them by PID. At this point we can't tell whether
423
+ # an empty list means there were no connections for process or
424
+ # process is no longer active so we force NSP in case the PID
425
+ # is no longer there.
426
+ if not ret:
427
+ # will raise NSP if process is gone
428
+ os.stat(f"{self._procfs_path}/{self.pid}")
429
+ return ret
430
+
431
+ @wrap_exceptions
432
+ def nice_get(self):
433
+ return cext.proc_priority_get(self.pid)
434
+
435
+ @wrap_exceptions
436
+ def nice_set(self, value):
437
+ return cext.proc_priority_set(self.pid, value)
438
+
439
+ @wrap_exceptions
440
+ def ppid(self):
441
+ self._ppid = self._proc_basic_info()[proc_info_map['ppid']]
442
+ return self._ppid
443
+
444
+ @wrap_exceptions
445
+ def uids(self):
446
+ real, effective, saved, _, _, _ = self._proc_cred()
447
+ return ntp.puids(real, effective, saved)
448
+
449
+ @wrap_exceptions
450
+ def gids(self):
451
+ _, _, _, real, effective, saved = self._proc_cred()
452
+ return ntp.puids(real, effective, saved)
453
+
454
+ @wrap_exceptions
455
+ def cpu_times(self):
456
+ t = cext.proc_cpu_times(self.pid, self._procfs_path)
457
+ return ntp.pcputimes(*t)
458
+
459
+ @wrap_exceptions
460
+ def terminal(self):
461
+ ttydev = self._proc_basic_info()[proc_info_map['ttynr']]
462
+ # convert from 64-bit dev_t to 32-bit dev_t and then map the device
463
+ ttydev = ((ttydev & 0x0000FFFF00000000) >> 16) | (ttydev & 0xFFFF)
464
+ # try to match rdev of /dev/pts/* files ttydev
465
+ for dev in glob.glob("/dev/**/*"):
466
+ if os.stat(dev).st_rdev == ttydev:
467
+ return dev
468
+ return None
469
+
470
+ @wrap_exceptions
471
+ def cwd(self):
472
+ procfs_path = self._procfs_path
473
+ try:
474
+ result = os.readlink(f"{procfs_path}/{self.pid}/cwd")
475
+ return result.rstrip('/')
476
+ except FileNotFoundError:
477
+ os.stat(f"{procfs_path}/{self.pid}") # raise NSP or AD
478
+ return ""
479
+
480
+ @wrap_exceptions
481
+ def memory_info(self):
482
+ ret = self._proc_basic_info()
483
+ rss = ret[proc_info_map['rss']] * 1024
484
+ vms = ret[proc_info_map['vms']] * 1024
485
+ return ntp.pmem(rss, vms)
486
+
487
+ memory_full_info = memory_info
488
+
489
+ @wrap_exceptions
490
+ def status(self):
491
+ code = self._proc_basic_info()[proc_info_map['status']]
492
+ # XXX is '?' legit? (we're not supposed to return it anyway)
493
+ return PROC_STATUSES.get(code, '?')
494
+
495
+ def open_files(self):
496
+ # TODO rewrite without using procfiles (stat /proc/pid/fd/* and then
497
+ # find matching name of the inode)
498
+ p = subprocess.Popen(
499
+ ["/usr/bin/procfiles", "-n", str(self.pid)],
500
+ stdout=subprocess.PIPE,
501
+ stderr=subprocess.PIPE,
502
+ )
503
+ stdout, stderr = p.communicate()
504
+ stdout, stderr = (
505
+ x.decode(sys.stdout.encoding) for x in (stdout, stderr)
506
+ )
507
+ if "no such process" in stderr.lower():
508
+ raise NoSuchProcess(self.pid, self._name)
509
+ procfiles = re.findall(r"(\d+): S_IFREG.*name:(.*)\n", stdout)
510
+ retlist = []
511
+ for fd, path in procfiles:
512
+ path = path.strip()
513
+ if path.startswith("//"):
514
+ path = path[1:]
515
+ if path.lower() == "cannot be retrieved":
516
+ continue
517
+ retlist.append(ntp.popenfile(path, int(fd)))
518
+ return retlist
519
+
520
+ @wrap_exceptions
521
+ def num_fds(self):
522
+ if self.pid == 0: # no /proc/0/fd
523
+ return 0
524
+ return len(os.listdir(f"{self._procfs_path}/{self.pid}/fd"))
525
+
526
+ @wrap_exceptions
527
+ def num_ctx_switches(self):
528
+ return ntp.pctxsw(*cext.proc_num_ctx_switches(self.pid))
529
+
530
+ @wrap_exceptions
531
+ def wait(self, timeout=None):
532
+ return _psposix.wait_pid(self.pid, timeout)
533
+
534
+ if HAS_PROC_IO_COUNTERS:
535
+
536
+ @wrap_exceptions
537
+ def io_counters(self):
538
+ try:
539
+ rc, wc, rb, wb = cext.proc_io_counters(self.pid)
540
+ except OSError as err:
541
+ # if process is terminated, proc_io_counters returns OSError
542
+ # instead of NSP
543
+ if not pid_exists(self.pid):
544
+ raise NoSuchProcess(self.pid, self._name) from err
545
+ raise
546
+ return ntp.pio(rc, wc, rb, wb)
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_psbsd.py ADDED
@@ -0,0 +1,904 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
2
+ # Use of this source code is governed by a BSD-style license that can be
3
+ # found in the LICENSE file.
4
+
5
+ """FreeBSD, OpenBSD and NetBSD platforms implementation."""
6
+
7
+ import contextlib
8
+ import errno
9
+ import functools
10
+ import os
11
+ from collections import defaultdict
12
+ from collections import namedtuple
13
+ from xml.etree import ElementTree # noqa: ICN001
14
+
15
+ from . import _common
16
+ from . import _ntuples as ntp
17
+ from . import _psposix
18
+ from . import _psutil_bsd as cext
19
+ from ._common import FREEBSD
20
+ from ._common import NETBSD
21
+ from ._common import OPENBSD
22
+ from ._common import AccessDenied
23
+ from ._common import NoSuchProcess
24
+ from ._common import ZombieProcess
25
+ from ._common import conn_tmap
26
+ from ._common import conn_to_ntuple
27
+ from ._common import debug
28
+ from ._common import memoize
29
+ from ._common import memoize_when_activated
30
+ from ._common import usage_percent
31
+
32
+ __extra__all__ = []
33
+
34
+
35
+ # =====================================================================
36
+ # --- globals
37
+ # =====================================================================
38
+
39
+
40
+ if FREEBSD:
41
+ PROC_STATUSES = {
42
+ cext.SIDL: _common.STATUS_IDLE,
43
+ cext.SRUN: _common.STATUS_RUNNING,
44
+ cext.SSLEEP: _common.STATUS_SLEEPING,
45
+ cext.SSTOP: _common.STATUS_STOPPED,
46
+ cext.SZOMB: _common.STATUS_ZOMBIE,
47
+ cext.SWAIT: _common.STATUS_WAITING,
48
+ cext.SLOCK: _common.STATUS_LOCKED,
49
+ }
50
+ elif OPENBSD:
51
+ PROC_STATUSES = {
52
+ cext.SIDL: _common.STATUS_IDLE,
53
+ cext.SSLEEP: _common.STATUS_SLEEPING,
54
+ cext.SSTOP: _common.STATUS_STOPPED,
55
+ # According to /usr/include/sys/proc.h SZOMB is unused.
56
+ # test_zombie_process() shows that SDEAD is the right
57
+ # equivalent. Also it appears there's no equivalent of
58
+ # psutil.STATUS_DEAD. SDEAD really means STATUS_ZOMBIE.
59
+ # cext.SZOMB: _common.STATUS_ZOMBIE,
60
+ cext.SDEAD: _common.STATUS_ZOMBIE,
61
+ cext.SZOMB: _common.STATUS_ZOMBIE,
62
+ # From http://www.eecs.harvard.edu/~margo/cs161/videos/proc.h.txt
63
+ # OpenBSD has SRUN and SONPROC: SRUN indicates that a process
64
+ # is runnable but *not* yet running, i.e. is on a run queue.
65
+ # SONPROC indicates that the process is actually executing on
66
+ # a CPU, i.e. it is no longer on a run queue.
67
+ # As such we'll map SRUN to STATUS_WAKING and SONPROC to
68
+ # STATUS_RUNNING
69
+ cext.SRUN: _common.STATUS_WAKING,
70
+ cext.SONPROC: _common.STATUS_RUNNING,
71
+ }
72
+ elif NETBSD:
73
+ PROC_STATUSES = {
74
+ cext.SIDL: _common.STATUS_IDLE,
75
+ cext.SSLEEP: _common.STATUS_SLEEPING,
76
+ cext.SSTOP: _common.STATUS_STOPPED,
77
+ cext.SZOMB: _common.STATUS_ZOMBIE,
78
+ cext.SRUN: _common.STATUS_WAKING,
79
+ cext.SONPROC: _common.STATUS_RUNNING,
80
+ }
81
+
82
+ TCP_STATUSES = {
83
+ cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
84
+ cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
85
+ cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV,
86
+ cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
87
+ cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
88
+ cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
89
+ cext.TCPS_CLOSED: _common.CONN_CLOSE,
90
+ cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
91
+ cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
92
+ cext.TCPS_LISTEN: _common.CONN_LISTEN,
93
+ cext.TCPS_CLOSING: _common.CONN_CLOSING,
94
+ cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
95
+ }
96
+
97
+ PAGESIZE = cext.getpagesize()
98
+ AF_LINK = cext.AF_LINK
99
+
100
+ HAS_PROC_NUM_THREADS = hasattr(cext, "proc_num_threads")
101
+
102
+ kinfo_proc_map = dict(
103
+ ppid=0,
104
+ status=1,
105
+ real_uid=2,
106
+ effective_uid=3,
107
+ saved_uid=4,
108
+ real_gid=5,
109
+ effective_gid=6,
110
+ saved_gid=7,
111
+ ttynr=8,
112
+ create_time=9,
113
+ ctx_switches_vol=10,
114
+ ctx_switches_unvol=11,
115
+ read_io_count=12,
116
+ write_io_count=13,
117
+ user_time=14,
118
+ sys_time=15,
119
+ ch_user_time=16,
120
+ ch_sys_time=17,
121
+ rss=18,
122
+ vms=19,
123
+ memtext=20,
124
+ memdata=21,
125
+ memstack=22,
126
+ cpunum=23,
127
+ name=24,
128
+ )
129
+
130
+
131
+ # =====================================================================
132
+ # --- memory
133
+ # =====================================================================
134
+
135
+
136
+ def virtual_memory():
137
+ mem = cext.virtual_mem()
138
+ if NETBSD:
139
+ total, free, active, inactive, wired, cached = mem
140
+ # On NetBSD buffers and shared mem is determined via /proc.
141
+ # The C ext set them to 0.
142
+ with open('/proc/meminfo', 'rb') as f:
143
+ for line in f:
144
+ if line.startswith(b'Buffers:'):
145
+ buffers = int(line.split()[1]) * 1024
146
+ elif line.startswith(b'MemShared:'):
147
+ shared = int(line.split()[1]) * 1024
148
+ # Before avail was calculated as (inactive + cached + free),
149
+ # same as zabbix, but it turned out it could exceed total (see
150
+ # #2233), so zabbix seems to be wrong. Htop calculates it
151
+ # differently, and the used value seem more realistic, so let's
152
+ # match htop.
153
+ # https://github.com/htop-dev/htop/blob/e7f447b/netbsd/NetBSDProcessList.c#L162
154
+ # https://github.com/zabbix/zabbix/blob/af5e0f8/src/libs/zbxsysinfo/netbsd/memory.c#L135
155
+ used = active + wired
156
+ avail = total - used
157
+ else:
158
+ total, free, active, inactive, wired, cached, buffers, shared = mem
159
+ # matches freebsd-memory CLI:
160
+ # * https://people.freebsd.org/~rse/dist/freebsd-memory
161
+ # * https://www.cyberciti.biz/files/scripts/freebsd-memory.pl.txt
162
+ # matches zabbix:
163
+ # * https://github.com/zabbix/zabbix/blob/af5e0f8/src/libs/zbxsysinfo/freebsd/memory.c#L143
164
+ avail = inactive + cached + free
165
+ used = active + wired + cached
166
+
167
+ percent = usage_percent((total - avail), total, round_=1)
168
+ return ntp.svmem(
169
+ total,
170
+ avail,
171
+ percent,
172
+ used,
173
+ free,
174
+ active,
175
+ inactive,
176
+ buffers,
177
+ cached,
178
+ shared,
179
+ wired,
180
+ )
181
+
182
+
183
+ def swap_memory():
184
+ """System swap memory as (total, used, free, sin, sout) namedtuple."""
185
+ total, used, free, sin, sout = cext.swap_mem()
186
+ percent = usage_percent(used, total, round_=1)
187
+ return ntp.sswap(total, used, free, percent, sin, sout)
188
+
189
+
190
+ # malloc / heap functions (FreeBSD / NetBSD)
191
+ if hasattr(cext, "heap_info"):
192
+ heap_info = cext.heap_info
193
+ heap_trim = cext.heap_trim
194
+
195
+
196
+ # =====================================================================
197
+ # --- CPU
198
+ # =====================================================================
199
+
200
+
201
+ def cpu_times():
202
+ """Return system per-CPU times as a namedtuple."""
203
+ user, nice, system, idle, irq = cext.cpu_times()
204
+ return ntp.scputimes(user, nice, system, idle, irq)
205
+
206
+
207
+ def per_cpu_times():
208
+ """Return system CPU times as a namedtuple."""
209
+ ret = []
210
+ for cpu_t in cext.per_cpu_times():
211
+ user, nice, system, idle, irq = cpu_t
212
+ item = ntp.scputimes(user, nice, system, idle, irq)
213
+ ret.append(item)
214
+ return ret
215
+
216
+
217
+ def cpu_count_logical():
218
+ """Return the number of logical CPUs in the system."""
219
+ return cext.cpu_count_logical()
220
+
221
+
222
+ if OPENBSD or NETBSD:
223
+
224
+ def cpu_count_cores():
225
+ # OpenBSD and NetBSD do not implement this.
226
+ return 1 if cpu_count_logical() == 1 else None
227
+
228
+ else:
229
+
230
+ def cpu_count_cores():
231
+ """Return the number of CPU cores in the system."""
232
+ # From the C module we'll get an XML string similar to this:
233
+ # http://manpages.ubuntu.com/manpages/precise/man4/smp.4freebsd.html
234
+ # We may get None in case "sysctl kern.sched.topology_spec"
235
+ # is not supported on this BSD version, in which case we'll mimic
236
+ # os.cpu_count() and return None.
237
+ ret = None
238
+ s = cext.cpu_topology()
239
+ if s is not None:
240
+ # get rid of padding chars appended at the end of the string
241
+ index = s.rfind("</groups>")
242
+ if index != -1:
243
+ s = s[: index + 9]
244
+ root = ElementTree.fromstring(s)
245
+ try:
246
+ ret = len(root.findall('group/children/group/cpu')) or None
247
+ finally:
248
+ # needed otherwise it will memleak
249
+ root.clear()
250
+ if not ret:
251
+ # If logical CPUs == 1 it's obvious we' have only 1 core.
252
+ if cpu_count_logical() == 1:
253
+ return 1
254
+ return ret
255
+
256
+
257
+ def cpu_stats():
258
+ """Return various CPU stats as a named tuple."""
259
+ if FREEBSD:
260
+ # Note: the C ext is returning some metrics we are not exposing:
261
+ # traps.
262
+ ctxsw, intrs, soft_intrs, syscalls, _traps = cext.cpu_stats()
263
+ elif NETBSD:
264
+ # XXX
265
+ # Note about intrs: the C extension returns 0. intrs
266
+ # can be determined via /proc/stat; it has the same value as
267
+ # soft_intrs thought so the kernel is faking it (?).
268
+ #
269
+ # Note about syscalls: the C extension always sets it to 0 (?).
270
+ #
271
+ # Note: the C ext is returning some metrics we are not exposing:
272
+ # traps, faults and forks.
273
+ ctxsw, intrs, soft_intrs, syscalls, _traps, _faults, _forks = (
274
+ cext.cpu_stats()
275
+ )
276
+ with open('/proc/stat', 'rb') as f:
277
+ for line in f:
278
+ if line.startswith(b'intr'):
279
+ intrs = int(line.split()[1])
280
+ elif OPENBSD:
281
+ # Note: the C ext is returning some metrics we are not exposing:
282
+ # traps, faults and forks.
283
+ ctxsw, intrs, soft_intrs, syscalls, _traps, _faults, _forks = (
284
+ cext.cpu_stats()
285
+ )
286
+ return ntp.scpustats(ctxsw, intrs, soft_intrs, syscalls)
287
+
288
+
289
+ if FREEBSD:
290
+
291
+ def cpu_freq():
292
+ """Return frequency metrics for CPUs. As of Dec 2018 only
293
+ CPU 0 appears to be supported by FreeBSD and all other cores
294
+ match the frequency of CPU 0.
295
+ """
296
+ ret = []
297
+ num_cpus = cpu_count_logical()
298
+ for cpu in range(num_cpus):
299
+ try:
300
+ current, available_freq = cext.cpu_freq(cpu)
301
+ except NotImplementedError:
302
+ continue
303
+ if available_freq:
304
+ try:
305
+ min_freq = int(available_freq.split(" ")[-1].split("/")[0])
306
+ except (IndexError, ValueError):
307
+ min_freq = None
308
+ try:
309
+ max_freq = int(available_freq.split(" ")[0].split("/")[0])
310
+ except (IndexError, ValueError):
311
+ max_freq = None
312
+ ret.append(ntp.scpufreq(current, min_freq, max_freq))
313
+ return ret
314
+
315
+ elif OPENBSD:
316
+
317
+ def cpu_freq():
318
+ curr = float(cext.cpu_freq())
319
+ return [ntp.scpufreq(curr, 0.0, 0.0)]
320
+
321
+
322
+ # =====================================================================
323
+ # --- disks
324
+ # =====================================================================
325
+
326
+
327
+ def disk_partitions(all=False):
328
+ """Return mounted disk partitions as a list of namedtuples.
329
+ 'all' argument is ignored, see:
330
+ https://github.com/giampaolo/psutil/issues/906.
331
+ """
332
+ retlist = []
333
+ partitions = cext.disk_partitions()
334
+ for partition in partitions:
335
+ device, mountpoint, fstype, opts = partition
336
+ ntuple = ntp.sdiskpart(device, mountpoint, fstype, opts)
337
+ retlist.append(ntuple)
338
+ return retlist
339
+
340
+
341
+ disk_usage = _psposix.disk_usage
342
+ disk_io_counters = cext.disk_io_counters
343
+
344
+
345
+ # =====================================================================
346
+ # --- network
347
+ # =====================================================================
348
+
349
+
350
+ net_io_counters = cext.net_io_counters
351
+ net_if_addrs = cext.net_if_addrs
352
+
353
+
354
+ def net_if_stats():
355
+ """Get NIC stats (isup, duplex, speed, mtu)."""
356
+ names = net_io_counters().keys()
357
+ ret = {}
358
+ for name in names:
359
+ try:
360
+ mtu = cext.net_if_mtu(name)
361
+ flags = cext.net_if_flags(name)
362
+ duplex, speed = cext.net_if_duplex_speed(name)
363
+ except OSError as err:
364
+ # https://github.com/giampaolo/psutil/issues/1279
365
+ if err.errno != errno.ENODEV:
366
+ raise
367
+ else:
368
+ if hasattr(_common, 'NicDuplex'):
369
+ duplex = _common.NicDuplex(duplex)
370
+ output_flags = ','.join(flags)
371
+ isup = 'running' in flags
372
+ ret[name] = ntp.snicstats(isup, duplex, speed, mtu, output_flags)
373
+ return ret
374
+
375
+
376
+ def net_connections(kind):
377
+ """System-wide network connections."""
378
+ families, types = conn_tmap[kind]
379
+ ret = set()
380
+ if OPENBSD:
381
+ rawlist = cext.net_connections(-1, families, types)
382
+ elif NETBSD:
383
+ rawlist = cext.net_connections(-1, kind)
384
+ else: # FreeBSD
385
+ rawlist = cext.net_connections(families, types)
386
+
387
+ for item in rawlist:
388
+ fd, fam, type, laddr, raddr, status, pid = item
389
+ nt = conn_to_ntuple(
390
+ fd, fam, type, laddr, raddr, status, TCP_STATUSES, pid
391
+ )
392
+ ret.add(nt)
393
+ return list(ret)
394
+
395
+
396
+ # =====================================================================
397
+ # --- sensors
398
+ # =====================================================================
399
+
400
+
401
+ if FREEBSD:
402
+
403
+ def sensors_battery():
404
+ """Return battery info."""
405
+ try:
406
+ percent, minsleft, power_plugged = cext.sensors_battery()
407
+ except NotImplementedError:
408
+ # See: https://github.com/giampaolo/psutil/issues/1074
409
+ return None
410
+ power_plugged = power_plugged == 1
411
+ if power_plugged:
412
+ secsleft = _common.POWER_TIME_UNLIMITED
413
+ elif minsleft == -1:
414
+ secsleft = _common.POWER_TIME_UNKNOWN
415
+ else:
416
+ secsleft = minsleft * 60
417
+ return ntp.sbattery(percent, secsleft, power_plugged)
418
+
419
+ def sensors_temperatures():
420
+ """Return CPU cores temperatures if available, else an empty dict."""
421
+ ret = defaultdict(list)
422
+ num_cpus = cpu_count_logical()
423
+ for cpu in range(num_cpus):
424
+ try:
425
+ current, high = cext.sensors_cpu_temperature(cpu)
426
+ if high <= 0:
427
+ high = None
428
+ name = f"Core {cpu}"
429
+ ret["coretemp"].append(ntp.shwtemp(name, current, high, high))
430
+ except NotImplementedError:
431
+ pass
432
+
433
+ return ret
434
+
435
+
436
+ # =====================================================================
437
+ # --- other system functions
438
+ # =====================================================================
439
+
440
+
441
+ def boot_time():
442
+ """The system boot time expressed in seconds since the epoch."""
443
+ return cext.boot_time()
444
+
445
+
446
+ if NETBSD:
447
+
448
+ try:
449
+ INIT_BOOT_TIME = boot_time()
450
+ except Exception as err: # noqa: BLE001
451
+ # Don't want to crash at import time.
452
+ debug(f"ignoring exception on import: {err!r}")
453
+ INIT_BOOT_TIME = 0
454
+
455
+ def adjust_proc_create_time(ctime):
456
+ """Account for system clock updates."""
457
+ if INIT_BOOT_TIME == 0:
458
+ return ctime
459
+
460
+ diff = INIT_BOOT_TIME - boot_time()
461
+ if diff == 0 or abs(diff) < 1:
462
+ return ctime
463
+
464
+ debug("system clock was updated; adjusting process create_time()")
465
+ if diff < 0:
466
+ return ctime - diff
467
+ return ctime + diff
468
+
469
+
470
+ def users():
471
+ """Return currently connected users as a list of namedtuples."""
472
+ retlist = []
473
+ rawlist = cext.users()
474
+ for item in rawlist:
475
+ user, tty, hostname, tstamp, pid = item
476
+ if tty == '~':
477
+ continue # reboot or shutdown
478
+ nt = ntp.suser(user, tty or None, hostname, tstamp, pid)
479
+ retlist.append(nt)
480
+ return retlist
481
+
482
+
483
+ # =====================================================================
484
+ # --- processes
485
+ # =====================================================================
486
+
487
+
488
+ @memoize
489
+ def _pid_0_exists():
490
+ try:
491
+ Process(0).name()
492
+ except NoSuchProcess:
493
+ return False
494
+ except AccessDenied:
495
+ return True
496
+ else:
497
+ return True
498
+
499
+
500
+ def pids():
501
+ """Returns a list of PIDs currently running on the system."""
502
+ ret = cext.pids()
503
+ if OPENBSD and (0 not in ret) and _pid_0_exists():
504
+ # On OpenBSD the kernel does not return PID 0 (neither does
505
+ # ps) but it's actually querable (Process(0) will succeed).
506
+ ret.insert(0, 0)
507
+ return ret
508
+
509
+
510
+ if NETBSD:
511
+
512
+ def pid_exists(pid):
513
+ exists = _psposix.pid_exists(pid)
514
+ if not exists:
515
+ # We do this because _psposix.pid_exists() lies in case of
516
+ # zombie processes.
517
+ return pid in pids()
518
+ else:
519
+ return True
520
+
521
+ elif OPENBSD:
522
+
523
+ def pid_exists(pid):
524
+ exists = _psposix.pid_exists(pid)
525
+ if not exists:
526
+ return False
527
+ else:
528
+ # OpenBSD seems to be the only BSD platform where
529
+ # _psposix.pid_exists() returns True for thread IDs (tids),
530
+ # so we can't use it.
531
+ return pid in pids()
532
+
533
+ else: # FreeBSD
534
+ pid_exists = _psposix.pid_exists
535
+
536
+
537
+ def wrap_exceptions(fun):
538
+ """Decorator which translates bare OSError exceptions into
539
+ NoSuchProcess and AccessDenied.
540
+ """
541
+
542
+ @functools.wraps(fun)
543
+ def wrapper(self, *args, **kwargs):
544
+ pid, ppid, name = self.pid, self._ppid, self._name
545
+ try:
546
+ return fun(self, *args, **kwargs)
547
+ except ProcessLookupError as err:
548
+ if cext.proc_is_zombie(pid):
549
+ raise ZombieProcess(pid, name, ppid) from err
550
+ raise NoSuchProcess(pid, name) from err
551
+ except PermissionError as err:
552
+ raise AccessDenied(pid, name) from err
553
+ except cext.ZombieProcessError as err:
554
+ raise ZombieProcess(pid, name, ppid) from err
555
+ except OSError as err:
556
+ if pid == 0 and 0 in pids():
557
+ raise AccessDenied(pid, name) from err
558
+ raise err from None
559
+
560
+ return wrapper
561
+
562
+
563
+ @contextlib.contextmanager
564
+ def wrap_exceptions_procfs(inst):
565
+ """Same as above, for routines relying on reading /proc fs."""
566
+ pid, name, ppid = inst.pid, inst._name, inst._ppid
567
+ try:
568
+ yield
569
+ except (ProcessLookupError, FileNotFoundError) as err:
570
+ # ENOENT (no such file or directory) gets raised on open().
571
+ # ESRCH (no such process) can get raised on read() if
572
+ # process is gone in meantime.
573
+ if cext.proc_is_zombie(inst.pid):
574
+ raise ZombieProcess(pid, name, ppid) from err
575
+ else:
576
+ raise NoSuchProcess(pid, name) from err
577
+ except PermissionError as err:
578
+ raise AccessDenied(pid, name) from err
579
+
580
+
581
+ class Process:
582
+ """Wrapper class around underlying C implementation."""
583
+
584
+ __slots__ = ["_cache", "_name", "_ppid", "pid"]
585
+
586
+ def __init__(self, pid):
587
+ self.pid = pid
588
+ self._name = None
589
+ self._ppid = None
590
+
591
+ def _assert_alive(self):
592
+ """Raise NSP if the process disappeared on us."""
593
+ # For those C function who do not raise NSP, possibly returning
594
+ # incorrect or incomplete result.
595
+ cext.proc_name(self.pid)
596
+
597
+ @wrap_exceptions
598
+ @memoize_when_activated
599
+ def oneshot(self):
600
+ """Retrieves multiple process info in one shot as a raw tuple."""
601
+ ret = cext.proc_oneshot_info(self.pid)
602
+ assert len(ret) == len(kinfo_proc_map)
603
+ return ret
604
+
605
+ def oneshot_enter(self):
606
+ self.oneshot.cache_activate(self)
607
+
608
+ def oneshot_exit(self):
609
+ self.oneshot.cache_deactivate(self)
610
+
611
+ @wrap_exceptions
612
+ def name(self):
613
+ name = self.oneshot()[kinfo_proc_map['name']]
614
+ return name if name is not None else cext.proc_name(self.pid)
615
+
616
+ @wrap_exceptions
617
+ def exe(self):
618
+ if FREEBSD:
619
+ if self.pid == 0:
620
+ return '' # else NSP
621
+ return cext.proc_exe(self.pid)
622
+ elif NETBSD:
623
+ if self.pid == 0:
624
+ # /proc/0 dir exists but /proc/0/exe doesn't
625
+ return ""
626
+ with wrap_exceptions_procfs(self):
627
+ return os.readlink(f"/proc/{self.pid}/exe")
628
+ else:
629
+ # OpenBSD: exe cannot be determined; references:
630
+ # https://chromium.googlesource.com/chromium/src/base/+/
631
+ # master/base_paths_posix.cc
632
+ # We try our best guess by using which against the first
633
+ # cmdline arg (may return None).
634
+ import shutil
635
+
636
+ cmdline = self.cmdline()
637
+ if cmdline:
638
+ return shutil.which(cmdline[0]) or ""
639
+ else:
640
+ return ""
641
+
642
+ @wrap_exceptions
643
+ def cmdline(self):
644
+ if OPENBSD and self.pid == 0:
645
+ return [] # ...else it crashes
646
+ elif NETBSD:
647
+ # XXX - most of the times the underlying sysctl() call on
648
+ # NetBSD and OpenBSD returns a truncated string. Also
649
+ # /proc/pid/cmdline behaves the same so it looks like this
650
+ # is a kernel bug.
651
+ try:
652
+ return cext.proc_cmdline(self.pid)
653
+ except OSError as err:
654
+ if err.errno == errno.EINVAL:
655
+ pid, name, ppid = self.pid, self._name, self._ppid
656
+ if cext.proc_is_zombie(self.pid):
657
+ raise ZombieProcess(pid, name, ppid) from err
658
+ if not pid_exists(self.pid):
659
+ raise NoSuchProcess(pid, name, ppid) from err
660
+ # XXX: this happens with unicode tests. It means the C
661
+ # routine is unable to decode invalid unicode chars.
662
+ debug(f"ignoring {err!r} and returning an empty list")
663
+ return []
664
+ else:
665
+ raise
666
+ else:
667
+ return cext.proc_cmdline(self.pid)
668
+
669
+ @wrap_exceptions
670
+ def environ(self):
671
+ return cext.proc_environ(self.pid)
672
+
673
+ @wrap_exceptions
674
+ def terminal(self):
675
+ tty_nr = self.oneshot()[kinfo_proc_map['ttynr']]
676
+ tmap = _psposix.get_terminal_map()
677
+ try:
678
+ return tmap[tty_nr]
679
+ except KeyError:
680
+ return None
681
+
682
+ @wrap_exceptions
683
+ def ppid(self):
684
+ self._ppid = self.oneshot()[kinfo_proc_map['ppid']]
685
+ return self._ppid
686
+
687
+ @wrap_exceptions
688
+ def uids(self):
689
+ rawtuple = self.oneshot()
690
+ return ntp.puids(
691
+ rawtuple[kinfo_proc_map['real_uid']],
692
+ rawtuple[kinfo_proc_map['effective_uid']],
693
+ rawtuple[kinfo_proc_map['saved_uid']],
694
+ )
695
+
696
+ @wrap_exceptions
697
+ def gids(self):
698
+ rawtuple = self.oneshot()
699
+ return ntp.pgids(
700
+ rawtuple[kinfo_proc_map['real_gid']],
701
+ rawtuple[kinfo_proc_map['effective_gid']],
702
+ rawtuple[kinfo_proc_map['saved_gid']],
703
+ )
704
+
705
+ @wrap_exceptions
706
+ def cpu_times(self):
707
+ rawtuple = self.oneshot()
708
+ return ntp.pcputimes(
709
+ rawtuple[kinfo_proc_map['user_time']],
710
+ rawtuple[kinfo_proc_map['sys_time']],
711
+ rawtuple[kinfo_proc_map['ch_user_time']],
712
+ rawtuple[kinfo_proc_map['ch_sys_time']],
713
+ )
714
+
715
+ if FREEBSD:
716
+
717
+ @wrap_exceptions
718
+ def cpu_num(self):
719
+ return self.oneshot()[kinfo_proc_map['cpunum']]
720
+
721
+ @wrap_exceptions
722
+ def memory_info(self):
723
+ rawtuple = self.oneshot()
724
+ return ntp.pmem(
725
+ rawtuple[kinfo_proc_map['rss']],
726
+ rawtuple[kinfo_proc_map['vms']],
727
+ rawtuple[kinfo_proc_map['memtext']],
728
+ rawtuple[kinfo_proc_map['memdata']],
729
+ rawtuple[kinfo_proc_map['memstack']],
730
+ )
731
+
732
+ memory_full_info = memory_info
733
+
734
+ @wrap_exceptions
735
+ def create_time(self, monotonic=False):
736
+ ctime = self.oneshot()[kinfo_proc_map['create_time']]
737
+ if NETBSD and not monotonic:
738
+ # NetBSD: ctime subject to system clock updates.
739
+ ctime = adjust_proc_create_time(ctime)
740
+ return ctime
741
+
742
+ @wrap_exceptions
743
+ def num_threads(self):
744
+ if HAS_PROC_NUM_THREADS:
745
+ # FreeBSD / NetBSD
746
+ return cext.proc_num_threads(self.pid)
747
+ else:
748
+ return len(self.threads())
749
+
750
+ @wrap_exceptions
751
+ def num_ctx_switches(self):
752
+ rawtuple = self.oneshot()
753
+ return ntp.pctxsw(
754
+ rawtuple[kinfo_proc_map['ctx_switches_vol']],
755
+ rawtuple[kinfo_proc_map['ctx_switches_unvol']],
756
+ )
757
+
758
+ @wrap_exceptions
759
+ def threads(self):
760
+ # Note: on OpenSBD this (/dev/mem) requires root access.
761
+ rawlist = cext.proc_threads(self.pid)
762
+ retlist = []
763
+ for thread_id, utime, stime in rawlist:
764
+ ntuple = ntp.pthread(thread_id, utime, stime)
765
+ retlist.append(ntuple)
766
+ if OPENBSD:
767
+ self._assert_alive()
768
+ return retlist
769
+
770
+ @wrap_exceptions
771
+ def net_connections(self, kind='inet'):
772
+ families, types = conn_tmap[kind]
773
+ ret = []
774
+
775
+ if NETBSD:
776
+ rawlist = cext.net_connections(self.pid, kind)
777
+ elif OPENBSD:
778
+ rawlist = cext.net_connections(self.pid, families, types)
779
+ else:
780
+ rawlist = cext.proc_net_connections(self.pid, families, types)
781
+
782
+ for item in rawlist:
783
+ fd, fam, type, laddr, raddr, status = item[:6]
784
+ if FREEBSD:
785
+ if (fam not in families) or (type not in types):
786
+ continue
787
+ nt = conn_to_ntuple(
788
+ fd, fam, type, laddr, raddr, status, TCP_STATUSES
789
+ )
790
+ ret.append(nt)
791
+
792
+ self._assert_alive()
793
+ return ret
794
+
795
+ @wrap_exceptions
796
+ def wait(self, timeout=None):
797
+ return _psposix.wait_pid(self.pid, timeout)
798
+
799
+ @wrap_exceptions
800
+ def nice_get(self):
801
+ return cext.proc_priority_get(self.pid)
802
+
803
+ @wrap_exceptions
804
+ def nice_set(self, value):
805
+ return cext.proc_priority_set(self.pid, value)
806
+
807
+ @wrap_exceptions
808
+ def status(self):
809
+ code = self.oneshot()[kinfo_proc_map['status']]
810
+ # XXX is '?' legit? (we're not supposed to return it anyway)
811
+ return PROC_STATUSES.get(code, '?')
812
+
813
+ @wrap_exceptions
814
+ def io_counters(self):
815
+ rawtuple = self.oneshot()
816
+ return ntp.pio(
817
+ rawtuple[kinfo_proc_map['read_io_count']],
818
+ rawtuple[kinfo_proc_map['write_io_count']],
819
+ -1,
820
+ -1,
821
+ )
822
+
823
+ @wrap_exceptions
824
+ def cwd(self):
825
+ """Return process current working directory."""
826
+ # sometimes we get an empty string, in which case we turn
827
+ # it into None
828
+ if OPENBSD and self.pid == 0:
829
+ return "" # ...else it would raise EINVAL
830
+ return cext.proc_cwd(self.pid)
831
+
832
+ nt_mmap_grouped = namedtuple(
833
+ 'mmap', 'path rss, private, ref_count, shadow_count'
834
+ )
835
+ nt_mmap_ext = namedtuple(
836
+ 'mmap', 'addr, perms path rss, private, ref_count, shadow_count'
837
+ )
838
+
839
+ @wrap_exceptions
840
+ def open_files(self):
841
+ """Return files opened by process as a list of namedtuples."""
842
+ rawlist = cext.proc_open_files(self.pid)
843
+ return [ntp.popenfile(path, fd) for path, fd in rawlist]
844
+
845
+ @wrap_exceptions
846
+ def num_fds(self):
847
+ """Return the number of file descriptors opened by this process."""
848
+ ret = cext.proc_num_fds(self.pid)
849
+ if NETBSD:
850
+ self._assert_alive()
851
+ return ret
852
+
853
+ # --- FreeBSD only APIs
854
+
855
+ if FREEBSD:
856
+
857
+ @wrap_exceptions
858
+ def cpu_affinity_get(self):
859
+ return cext.proc_cpu_affinity_get(self.pid)
860
+
861
+ @wrap_exceptions
862
+ def cpu_affinity_set(self, cpus):
863
+ # Pre-emptively check if CPUs are valid because the C
864
+ # function has a weird behavior in case of invalid CPUs,
865
+ # see: https://github.com/giampaolo/psutil/issues/586
866
+ allcpus = set(range(len(per_cpu_times())))
867
+ for cpu in cpus:
868
+ if cpu not in allcpus:
869
+ msg = f"invalid CPU {cpu!r} (choose between {allcpus})"
870
+ raise ValueError(msg)
871
+ try:
872
+ cext.proc_cpu_affinity_set(self.pid, cpus)
873
+ except OSError as err:
874
+ # 'man cpuset_setaffinity' about EDEADLK:
875
+ # <<the call would leave a thread without a valid CPU to run
876
+ # on because the set does not overlap with the thread's
877
+ # anonymous mask>>
878
+ if err.errno in {errno.EINVAL, errno.EDEADLK}:
879
+ for cpu in cpus:
880
+ if cpu not in allcpus:
881
+ msg = (
882
+ f"invalid CPU {cpu!r} (choose between"
883
+ f" {allcpus})"
884
+ )
885
+ raise ValueError(msg) from err
886
+ raise
887
+
888
+ @wrap_exceptions
889
+ def memory_maps(self):
890
+ return cext.proc_memory_maps(self.pid)
891
+
892
+ @wrap_exceptions
893
+ def rlimit(self, resource, limits=None):
894
+ if limits is None:
895
+ return cext.proc_getrlimit(self.pid, resource)
896
+ else:
897
+ if len(limits) != 2:
898
+ msg = (
899
+ "second argument must be a (soft, hard) tuple, got"
900
+ f" {limits!r}"
901
+ )
902
+ raise ValueError(msg)
903
+ soft, hard = limits
904
+ return cext.proc_setrlimit(self.pid, resource, soft, hard)
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_pslinux.py ADDED
@@ -0,0 +1,2269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
2
+ # Use of this source code is governed by a BSD-style license that can be
3
+ # found in the LICENSE file.
4
+
5
+ """Linux platform implementation."""
6
+
7
+ import base64
8
+ import collections
9
+ import enum
10
+ import errno
11
+ import functools
12
+ import glob
13
+ import os
14
+ import re
15
+ import resource
16
+ import socket
17
+ import struct
18
+ import sys
19
+ import warnings
20
+ from collections import defaultdict
21
+ from collections import namedtuple
22
+
23
+ from . import _common
24
+ from . import _ntuples as ntp
25
+ from . import _psposix
26
+ from . import _psutil_linux as cext
27
+ from ._common import ENCODING
28
+ from ._common import NIC_DUPLEX_FULL
29
+ from ._common import NIC_DUPLEX_HALF
30
+ from ._common import NIC_DUPLEX_UNKNOWN
31
+ from ._common import AccessDenied
32
+ from ._common import NoSuchProcess
33
+ from ._common import ZombieProcess
34
+ from ._common import bcat
35
+ from ._common import cat
36
+ from ._common import debug
37
+ from ._common import decode
38
+ from ._common import get_procfs_path
39
+ from ._common import isfile_strict
40
+ from ._common import memoize
41
+ from ._common import memoize_when_activated
42
+ from ._common import open_binary
43
+ from ._common import open_text
44
+ from ._common import parse_environ_block
45
+ from ._common import path_exists_strict
46
+ from ._common import supports_ipv6
47
+ from ._common import usage_percent
48
+
49
+ # fmt: off
50
+ __extra__all__ = [
51
+ 'PROCFS_PATH',
52
+ # io prio constants
53
+ "IOPRIO_CLASS_NONE", "IOPRIO_CLASS_RT", "IOPRIO_CLASS_BE",
54
+ "IOPRIO_CLASS_IDLE",
55
+ # connection status constants
56
+ "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1",
57
+ "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT",
58
+ "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING",
59
+ ]
60
+ # fmt: on
61
+
62
+
63
+ # =====================================================================
64
+ # --- globals
65
+ # =====================================================================
66
+
67
+
68
+ POWER_SUPPLY_PATH = "/sys/class/power_supply"
69
+ HAS_PROC_SMAPS = os.path.exists(f"/proc/{os.getpid()}/smaps")
70
+ HAS_PROC_SMAPS_ROLLUP = os.path.exists(f"/proc/{os.getpid()}/smaps_rollup")
71
+ HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_ioprio_get")
72
+ HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get")
73
+
74
+ # Number of clock ticks per second
75
+ CLOCK_TICKS = os.sysconf("SC_CLK_TCK")
76
+ PAGESIZE = cext.getpagesize()
77
+ LITTLE_ENDIAN = sys.byteorder == 'little'
78
+ UNSET = object()
79
+
80
+ # "man iostat" states that sectors are equivalent with blocks and have
81
+ # a size of 512 bytes. Despite this value can be queried at runtime
82
+ # via /sys/block/{DISK}/queue/hw_sector_size and results may vary
83
+ # between 1k, 2k, or 4k... 512 appears to be a magic constant used
84
+ # throughout Linux source code:
85
+ # * https://stackoverflow.com/a/38136179/376587
86
+ # * https://lists.gt.net/linux/kernel/2241060
87
+ # * https://github.com/giampaolo/psutil/issues/1305
88
+ # * https://github.com/torvalds/linux/blob/
89
+ # 4f671fe2f9523a1ea206f63fe60a7c7b3a56d5c7/include/linux/bio.h#L99
90
+ # * https://lkml.org/lkml/2015/8/17/234
91
+ DISK_SECTOR_SIZE = 512
92
+
93
+ AddressFamily = enum.IntEnum(
94
+ 'AddressFamily', {'AF_LINK': int(socket.AF_PACKET)}
95
+ )
96
+ AF_LINK = AddressFamily.AF_LINK
97
+
98
+
99
+ # ioprio_* constants http://linux.die.net/man/2/ioprio_get
100
+ class IOPriority(enum.IntEnum):
101
+ IOPRIO_CLASS_NONE = 0
102
+ IOPRIO_CLASS_RT = 1
103
+ IOPRIO_CLASS_BE = 2
104
+ IOPRIO_CLASS_IDLE = 3
105
+
106
+
107
+ globals().update(IOPriority.__members__)
108
+
109
+ # See:
110
+ # https://github.com/torvalds/linux/blame/master/fs/proc/array.c
111
+ # ...and (TASK_* constants):
112
+ # https://github.com/torvalds/linux/blob/master/include/linux/sched.h
113
+ PROC_STATUSES = {
114
+ "R": _common.STATUS_RUNNING,
115
+ "S": _common.STATUS_SLEEPING,
116
+ "D": _common.STATUS_DISK_SLEEP,
117
+ "T": _common.STATUS_STOPPED,
118
+ "t": _common.STATUS_TRACING_STOP,
119
+ "Z": _common.STATUS_ZOMBIE,
120
+ "X": _common.STATUS_DEAD,
121
+ "x": _common.STATUS_DEAD,
122
+ "K": _common.STATUS_WAKE_KILL,
123
+ "W": _common.STATUS_WAKING,
124
+ "I": _common.STATUS_IDLE,
125
+ "P": _common.STATUS_PARKED,
126
+ }
127
+
128
+ # https://github.com/torvalds/linux/blob/master/include/net/tcp_states.h
129
+ TCP_STATUSES = {
130
+ "01": _common.CONN_ESTABLISHED,
131
+ "02": _common.CONN_SYN_SENT,
132
+ "03": _common.CONN_SYN_RECV,
133
+ "04": _common.CONN_FIN_WAIT1,
134
+ "05": _common.CONN_FIN_WAIT2,
135
+ "06": _common.CONN_TIME_WAIT,
136
+ "07": _common.CONN_CLOSE,
137
+ "08": _common.CONN_CLOSE_WAIT,
138
+ "09": _common.CONN_LAST_ACK,
139
+ "0A": _common.CONN_LISTEN,
140
+ "0B": _common.CONN_CLOSING,
141
+ }
142
+
143
+
144
+ # =====================================================================
145
+ # --- utils
146
+ # =====================================================================
147
+
148
+
149
+ def readlink(path):
150
+ """Wrapper around os.readlink()."""
151
+ assert isinstance(path, str), path
152
+ path = os.readlink(path)
153
+ # readlink() might return paths containing null bytes ('\x00')
154
+ # resulting in "TypeError: must be encoded string without NULL
155
+ # bytes, not str" errors when the string is passed to other
156
+ # fs-related functions (os.*, open(), ...).
157
+ # Apparently everything after '\x00' is garbage (we can have
158
+ # ' (deleted)', 'new' and possibly others), see:
159
+ # https://github.com/giampaolo/psutil/issues/717
160
+ path = path.split('\x00')[0]
161
+ # Certain paths have ' (deleted)' appended. Usually this is
162
+ # bogus as the file actually exists. Even if it doesn't we
163
+ # don't care.
164
+ if path.endswith(' (deleted)') and not path_exists_strict(path):
165
+ path = path[:-10]
166
+ return path
167
+
168
+
169
+ def file_flags_to_mode(flags):
170
+ """Convert file's open() flags into a readable string.
171
+ Used by Process.open_files().
172
+ """
173
+ modes_map = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
174
+ mode = modes_map[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
175
+ if flags & os.O_APPEND:
176
+ mode = mode.replace('w', 'a', 1)
177
+ mode = mode.replace('w+', 'r+')
178
+ # possible values: r, w, a, r+, a+
179
+ return mode
180
+
181
+
182
+ def is_storage_device(name):
183
+ """Return True if the given name refers to a root device (e.g.
184
+ "sda", "nvme0n1") as opposed to a logical partition (e.g. "sda1",
185
+ "nvme0n1p1"). If name is a virtual device (e.g. "loop1", "ram")
186
+ return True.
187
+ """
188
+ # Re-adapted from iostat source code, see:
189
+ # https://github.com/sysstat/sysstat/blob/
190
+ # 97912938cd476645b267280069e83b1c8dc0e1c7/common.c#L208
191
+ # Some devices may have a slash in their name (e.g. cciss/c0d0...).
192
+ name = name.replace('/', '!')
193
+ including_virtual = True
194
+ if including_virtual:
195
+ path = f"/sys/block/{name}"
196
+ else:
197
+ path = f"/sys/block/{name}/device"
198
+ return os.access(path, os.F_OK)
199
+
200
+
201
+ @memoize
202
+ def _scputimes_ntuple(procfs_path):
203
+ """Return a namedtuple of variable fields depending on the CPU times
204
+ available on this Linux kernel version which may be:
205
+ (user, nice, system, idle, iowait, irq, softirq, [steal, [guest,
206
+ [guest_nice]]])
207
+ Used by cpu_times() function.
208
+ """
209
+ with open_binary(f"{procfs_path}/stat") as f:
210
+ values = f.readline().split()[1:]
211
+ fields = ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq']
212
+ vlen = len(values)
213
+ if vlen >= 8:
214
+ # Linux >= 2.6.11
215
+ fields.append('steal')
216
+ if vlen >= 9:
217
+ # Linux >= 2.6.24
218
+ fields.append('guest')
219
+ if vlen >= 10:
220
+ # Linux >= 3.2.0
221
+ fields.append('guest_nice')
222
+ return namedtuple('scputimes', fields)
223
+
224
+
225
+ # Set it into _ntuples.py namespace.
226
+ try:
227
+ ntp.scputimes = _scputimes_ntuple("/proc")
228
+ except Exception as err: # noqa: BLE001
229
+ # Don't want to crash at import time.
230
+ debug(f"ignoring exception on import: {err!r}")
231
+ ntp.scputimes = namedtuple('scputimes', 'user system idle')(0.0, 0.0, 0.0)
232
+
233
+ # XXX: must be available also at this module level in order to be
234
+ # serialized (tests/test_misc.py::TestMisc::test_serialization).
235
+ scputimes = ntp.scputimes
236
+
237
+
238
+ # =====================================================================
239
+ # --- system memory
240
+ # =====================================================================
241
+
242
+
243
+ def calculate_avail_vmem(mems):
244
+ """Fallback for kernels < 3.14 where /proc/meminfo does not provide
245
+ "MemAvailable", see:
246
+ https://blog.famzah.net/2014/09/24/.
247
+
248
+ This code reimplements the algorithm outlined here:
249
+ https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/
250
+ commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
251
+
252
+ We use this function also when "MemAvailable" returns 0 (possibly a
253
+ kernel bug, see: https://github.com/giampaolo/psutil/issues/1915).
254
+ In that case this routine matches "free" CLI tool result ("available"
255
+ column).
256
+
257
+ XXX: on recent kernels this calculation may differ by ~1.5% compared
258
+ to "MemAvailable:", as it's calculated slightly differently.
259
+ It is still way more realistic than doing (free + cached) though.
260
+ See:
261
+ * https://gitlab.com/procps-ng/procps/issues/42
262
+ * https://github.com/famzah/linux-memavailable-procfs/issues/2
263
+ """
264
+ # Note about "fallback" value. According to:
265
+ # https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/
266
+ # commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
267
+ # ...long ago "available" memory was calculated as (free + cached),
268
+ # We use fallback when one of these is missing from /proc/meminfo:
269
+ # "Active(file)": introduced in 2.6.28 / Dec 2008
270
+ # "Inactive(file)": introduced in 2.6.28 / Dec 2008
271
+ # "SReclaimable": introduced in 2.6.19 / Nov 2006
272
+ # /proc/zoneinfo: introduced in 2.6.13 / Aug 2005
273
+ free = mems[b'MemFree:']
274
+ fallback = free + mems.get(b"Cached:", 0)
275
+ try:
276
+ lru_active_file = mems[b'Active(file):']
277
+ lru_inactive_file = mems[b'Inactive(file):']
278
+ slab_reclaimable = mems[b'SReclaimable:']
279
+ except KeyError as err:
280
+ debug(
281
+ f"{err.args[0]} is missing from /proc/meminfo; using an"
282
+ " approximation for calculating available memory"
283
+ )
284
+ return fallback
285
+ try:
286
+ f = open_binary(f"{get_procfs_path()}/zoneinfo")
287
+ except OSError:
288
+ return fallback # kernel 2.6.13
289
+
290
+ watermark_low = 0
291
+ with f:
292
+ for line in f:
293
+ line = line.strip()
294
+ if line.startswith(b'low'):
295
+ watermark_low += int(line.split()[1])
296
+ watermark_low *= PAGESIZE
297
+
298
+ avail = free - watermark_low
299
+ pagecache = lru_active_file + lru_inactive_file
300
+ pagecache -= min(pagecache / 2, watermark_low)
301
+ avail += pagecache
302
+ avail += slab_reclaimable - min(slab_reclaimable / 2.0, watermark_low)
303
+ return int(avail)
304
+
305
+
306
+ def virtual_memory():
307
+ """Report virtual memory stats.
308
+ This implementation mimics procps-ng-3.3.12, aka "free" CLI tool:
309
+ https://gitlab.com/procps-ng/procps/blob/
310
+ 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L778-791
311
+ The returned values are supposed to match both "free" and "vmstat -s"
312
+ CLI tools.
313
+ """
314
+ missing_fields = []
315
+ mems = {}
316
+ with open_binary(f"{get_procfs_path()}/meminfo") as f:
317
+ for line in f:
318
+ fields = line.split()
319
+ mems[fields[0]] = int(fields[1]) * 1024
320
+
321
+ # /proc doc states that the available fields in /proc/meminfo vary
322
+ # by architecture and compile options, but these 3 values are also
323
+ # returned by sysinfo(2); as such we assume they are always there.
324
+ total = mems[b'MemTotal:']
325
+ free = mems[b'MemFree:']
326
+ try:
327
+ buffers = mems[b'Buffers:']
328
+ except KeyError:
329
+ # https://github.com/giampaolo/psutil/issues/1010
330
+ buffers = 0
331
+ missing_fields.append('buffers')
332
+ try:
333
+ cached = mems[b"Cached:"]
334
+ except KeyError:
335
+ cached = 0
336
+ missing_fields.append('cached')
337
+ else:
338
+ # "free" cmdline utility sums reclaimable to cached.
339
+ # Older versions of procps used to add slab memory instead.
340
+ # This got changed in:
341
+ # https://gitlab.com/procps-ng/procps/commit/
342
+ # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e
343
+ cached += mems.get(b"SReclaimable:", 0) # since kernel 2.6.19
344
+
345
+ try:
346
+ shared = mems[b'Shmem:'] # since kernel 2.6.32
347
+ except KeyError:
348
+ try:
349
+ shared = mems[b'MemShared:'] # kernels 2.4
350
+ except KeyError:
351
+ shared = 0
352
+ missing_fields.append('shared')
353
+
354
+ try:
355
+ active = mems[b"Active:"]
356
+ except KeyError:
357
+ active = 0
358
+ missing_fields.append('active')
359
+
360
+ try:
361
+ inactive = mems[b"Inactive:"]
362
+ except KeyError:
363
+ try:
364
+ inactive = (
365
+ mems[b"Inact_dirty:"]
366
+ + mems[b"Inact_clean:"]
367
+ + mems[b"Inact_laundry:"]
368
+ )
369
+ except KeyError:
370
+ inactive = 0
371
+ missing_fields.append('inactive')
372
+
373
+ try:
374
+ slab = mems[b"Slab:"]
375
+ except KeyError:
376
+ slab = 0
377
+
378
+ # - starting from 4.4.0 we match free's "available" column.
379
+ # Before 4.4.0 we calculated it as (free + buffers + cached)
380
+ # which matched htop.
381
+ # - free and htop available memory differs as per:
382
+ # http://askubuntu.com/a/369589
383
+ # http://unix.stackexchange.com/a/65852/168884
384
+ # - MemAvailable has been introduced in kernel 3.14
385
+ try:
386
+ avail = mems[b'MemAvailable:']
387
+ except KeyError:
388
+ avail = calculate_avail_vmem(mems)
389
+ else:
390
+ if avail == 0:
391
+ # Yes, it can happen (probably a kernel bug):
392
+ # https://github.com/giampaolo/psutil/issues/1915
393
+ # In this case "free" CLI tool makes an estimate. We do the same,
394
+ # and it matches "free" CLI tool.
395
+ avail = calculate_avail_vmem(mems)
396
+
397
+ if avail < 0:
398
+ avail = 0
399
+ missing_fields.append('available')
400
+ elif avail > total:
401
+ # If avail is greater than total or our calculation overflows,
402
+ # that's symptomatic of running within a LCX container where such
403
+ # values will be dramatically distorted over those of the host.
404
+ # https://gitlab.com/procps-ng/procps/blob/
405
+ # 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L764
406
+ avail = free
407
+
408
+ used = total - avail
409
+
410
+ percent = usage_percent((total - avail), total, round_=1)
411
+
412
+ # Warn about missing metrics which are set to 0.
413
+ if missing_fields:
414
+ msg = "{} memory stats couldn't be determined and {} set to 0".format(
415
+ ", ".join(missing_fields),
416
+ "was" if len(missing_fields) == 1 else "were",
417
+ )
418
+ warnings.warn(msg, RuntimeWarning, stacklevel=2)
419
+
420
+ return ntp.svmem(
421
+ total,
422
+ avail,
423
+ percent,
424
+ used,
425
+ free,
426
+ active,
427
+ inactive,
428
+ buffers,
429
+ cached,
430
+ shared,
431
+ slab,
432
+ )
433
+
434
+
435
+ def swap_memory():
436
+ """Return swap memory metrics."""
437
+ mems = {}
438
+ with open_binary(f"{get_procfs_path()}/meminfo") as f:
439
+ for line in f:
440
+ fields = line.split()
441
+ mems[fields[0]] = int(fields[1]) * 1024
442
+ # We prefer /proc/meminfo over sysinfo() syscall so that
443
+ # psutil.PROCFS_PATH can be used in order to allow retrieval
444
+ # for linux containers, see:
445
+ # https://github.com/giampaolo/psutil/issues/1015
446
+ try:
447
+ total = mems[b'SwapTotal:']
448
+ free = mems[b'SwapFree:']
449
+ except KeyError:
450
+ _, _, _, _, total, free, unit_multiplier = cext.linux_sysinfo()
451
+ total *= unit_multiplier
452
+ free *= unit_multiplier
453
+
454
+ used = total - free
455
+ percent = usage_percent(used, total, round_=1)
456
+ # get pgin/pgouts
457
+ try:
458
+ f = open_binary(f"{get_procfs_path()}/vmstat")
459
+ except OSError as err:
460
+ # see https://github.com/giampaolo/psutil/issues/722
461
+ msg = (
462
+ "'sin' and 'sout' swap memory stats couldn't "
463
+ f"be determined and were set to 0 ({err})"
464
+ )
465
+ warnings.warn(msg, RuntimeWarning, stacklevel=2)
466
+ sin = sout = 0
467
+ else:
468
+ with f:
469
+ sin = sout = None
470
+ for line in f:
471
+ # values are expressed in 4 kilo bytes, we want
472
+ # bytes instead
473
+ if line.startswith(b'pswpin'):
474
+ sin = int(line.split(b' ')[1]) * 4 * 1024
475
+ elif line.startswith(b'pswpout'):
476
+ sout = int(line.split(b' ')[1]) * 4 * 1024
477
+ if sin is not None and sout is not None:
478
+ break
479
+ else:
480
+ # we might get here when dealing with exotic Linux
481
+ # flavors, see:
482
+ # https://github.com/giampaolo/psutil/issues/313
483
+ msg = "'sin' and 'sout' swap memory stats couldn't "
484
+ msg += "be determined and were set to 0"
485
+ warnings.warn(msg, RuntimeWarning, stacklevel=2)
486
+ sin = sout = 0
487
+ return ntp.sswap(total, used, free, percent, sin, sout)
488
+
489
+
490
+ # malloc / heap functions; require glibc
491
+ if hasattr(cext, "heap_info"):
492
+ heap_info = cext.heap_info
493
+ heap_trim = cext.heap_trim
494
+
495
+
496
+ # =====================================================================
497
+ # --- CPU
498
+ # =====================================================================
499
+
500
+
501
+ def cpu_times():
502
+ """Return a named tuple representing the following system-wide
503
+ CPU times:
504
+ (user, nice, system, idle, iowait, irq, softirq [steal, [guest,
505
+ [guest_nice]]])
506
+ Last 3 fields may not be available on all Linux kernel versions.
507
+ """
508
+ procfs_path = get_procfs_path()
509
+ with open_binary(f"{procfs_path}/stat") as f:
510
+ values = f.readline().split()
511
+ fields = values[1 : len(ntp.scputimes._fields) + 1]
512
+ fields = [float(x) / CLOCK_TICKS for x in fields]
513
+ return ntp.scputimes(*fields)
514
+
515
+
516
+ def per_cpu_times():
517
+ """Return a list of namedtuple representing the CPU times
518
+ for every CPU available on the system.
519
+ """
520
+ procfs_path = get_procfs_path()
521
+ cpus = []
522
+ with open_binary(f"{procfs_path}/stat") as f:
523
+ # get rid of the first line which refers to system wide CPU stats
524
+ f.readline()
525
+ for line in f:
526
+ if line.startswith(b'cpu'):
527
+ values = line.split()
528
+ fields = values[1 : len(ntp.scputimes._fields) + 1]
529
+ fields = [float(x) / CLOCK_TICKS for x in fields]
530
+ entry = ntp.scputimes(*fields)
531
+ cpus.append(entry)
532
+ return cpus
533
+
534
+
535
+ def cpu_count_logical():
536
+ """Return the number of logical CPUs in the system."""
537
+ try:
538
+ return os.sysconf("SC_NPROCESSORS_ONLN")
539
+ except ValueError:
540
+ # as a second fallback we try to parse /proc/cpuinfo
541
+ num = 0
542
+ with open_binary(f"{get_procfs_path()}/cpuinfo") as f:
543
+ for line in f:
544
+ if line.lower().startswith(b'processor'):
545
+ num += 1
546
+
547
+ # unknown format (e.g. amrel/sparc architectures), see:
548
+ # https://github.com/giampaolo/psutil/issues/200
549
+ # try to parse /proc/stat as a last resort
550
+ if num == 0:
551
+ search = re.compile(r'cpu\d')
552
+ with open_text(f"{get_procfs_path()}/stat") as f:
553
+ for line in f:
554
+ line = line.split(' ')[0]
555
+ if search.match(line):
556
+ num += 1
557
+
558
+ if num == 0:
559
+ # mimic os.cpu_count()
560
+ return None
561
+ return num
562
+
563
+
564
+ def cpu_count_cores():
565
+ """Return the number of CPU cores in the system."""
566
+ # Method #1
567
+ ls = set()
568
+ # These 2 files are the same but */core_cpus_list is newer while
569
+ # */thread_siblings_list is deprecated and may disappear in the future.
570
+ # https://www.kernel.org/doc/Documentation/admin-guide/cputopology.rst
571
+ # https://github.com/giampaolo/psutil/pull/1727#issuecomment-707624964
572
+ # https://lkml.org/lkml/2019/2/26/41
573
+ p1 = "/sys/devices/system/cpu/cpu[0-9]*/topology/core_cpus_list"
574
+ p2 = "/sys/devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list"
575
+ for path in glob.glob(p1) or glob.glob(p2):
576
+ with open_binary(path) as f:
577
+ ls.add(f.read().strip())
578
+ result = len(ls)
579
+ if result != 0:
580
+ return result
581
+
582
+ # Method #2
583
+ mapping = {}
584
+ current_info = {}
585
+ with open_binary(f"{get_procfs_path()}/cpuinfo") as f:
586
+ for line in f:
587
+ line = line.strip().lower()
588
+ if not line:
589
+ # new section
590
+ try:
591
+ mapping[current_info[b'physical id']] = current_info[
592
+ b'cpu cores'
593
+ ]
594
+ except KeyError:
595
+ pass
596
+ current_info = {}
597
+ elif line.startswith((b'physical id', b'cpu cores')):
598
+ # ongoing section
599
+ key, value = line.split(b'\t:', 1)
600
+ current_info[key] = int(value)
601
+
602
+ result = sum(mapping.values())
603
+ return result or None # mimic os.cpu_count()
604
+
605
+
606
+ def cpu_stats():
607
+ """Return various CPU stats as a named tuple."""
608
+ with open_binary(f"{get_procfs_path()}/stat") as f:
609
+ ctx_switches = None
610
+ interrupts = None
611
+ soft_interrupts = None
612
+ for line in f:
613
+ if line.startswith(b'ctxt'):
614
+ ctx_switches = int(line.split()[1])
615
+ elif line.startswith(b'intr'):
616
+ interrupts = int(line.split()[1])
617
+ elif line.startswith(b'softirq'):
618
+ soft_interrupts = int(line.split()[1])
619
+ if (
620
+ ctx_switches is not None
621
+ and soft_interrupts is not None
622
+ and interrupts is not None
623
+ ):
624
+ break
625
+ syscalls = 0
626
+ return ntp.scpustats(ctx_switches, interrupts, soft_interrupts, syscalls)
627
+
628
+
629
+ def _cpu_get_cpuinfo_freq():
630
+ """Return current CPU frequency from cpuinfo if available."""
631
+ with open_binary(f"{get_procfs_path()}/cpuinfo") as f:
632
+ return [
633
+ float(line.split(b':', 1)[1])
634
+ for line in f
635
+ if line.lower().startswith(b'cpu mhz')
636
+ ]
637
+
638
+
639
+ if os.path.exists("/sys/devices/system/cpu/cpufreq/policy0") or os.path.exists(
640
+ "/sys/devices/system/cpu/cpu0/cpufreq"
641
+ ):
642
+
643
+ def cpu_freq():
644
+ """Return frequency metrics for all CPUs.
645
+ Contrarily to other OSes, Linux updates these values in
646
+ real-time.
647
+ """
648
+ cpuinfo_freqs = _cpu_get_cpuinfo_freq()
649
+ paths = glob.glob(
650
+ "/sys/devices/system/cpu/cpufreq/policy[0-9]*"
651
+ ) or glob.glob("/sys/devices/system/cpu/cpu[0-9]*/cpufreq")
652
+ paths.sort(key=lambda x: int(re.search(r"[0-9]+", x).group()))
653
+ ret = []
654
+ pjoin = os.path.join
655
+ for i, path in enumerate(paths):
656
+ if len(paths) == len(cpuinfo_freqs):
657
+ # take cached value from cpuinfo if available, see:
658
+ # https://github.com/giampaolo/psutil/issues/1851
659
+ curr = cpuinfo_freqs[i] * 1000
660
+ else:
661
+ curr = bcat(pjoin(path, "scaling_cur_freq"), fallback=None)
662
+ if curr is None:
663
+ # Likely an old RedHat, see:
664
+ # https://github.com/giampaolo/psutil/issues/1071
665
+ curr = bcat(pjoin(path, "cpuinfo_cur_freq"), fallback=None)
666
+ if curr is None:
667
+ online_path = f"/sys/devices/system/cpu/cpu{i}/online"
668
+ # if cpu core is offline, set to all zeroes
669
+ if cat(online_path, fallback=None) == "0\n":
670
+ ret.append(ntp.scpufreq(0.0, 0.0, 0.0))
671
+ continue
672
+ msg = "can't find current frequency file"
673
+ raise NotImplementedError(msg)
674
+ curr = int(curr) / 1000
675
+ max_ = int(bcat(pjoin(path, "scaling_max_freq"))) / 1000
676
+ min_ = int(bcat(pjoin(path, "scaling_min_freq"))) / 1000
677
+ ret.append(ntp.scpufreq(curr, min_, max_))
678
+ return ret
679
+
680
+ else:
681
+
682
+ def cpu_freq():
683
+ """Alternate implementation using /proc/cpuinfo.
684
+ min and max frequencies are not available and are set to None.
685
+ """
686
+ return [ntp.scpufreq(x, 0.0, 0.0) for x in _cpu_get_cpuinfo_freq()]
687
+
688
+
689
+ # =====================================================================
690
+ # --- network
691
+ # =====================================================================
692
+
693
+
694
+ net_if_addrs = cext.net_if_addrs
695
+
696
+
697
+ class _Ipv6UnsupportedError(Exception):
698
+ pass
699
+
700
+
701
+ class NetConnections:
702
+ """A wrapper on top of /proc/net/* files, retrieving per-process
703
+ and system-wide open connections (TCP, UDP, UNIX) similarly to
704
+ "netstat -an".
705
+
706
+ Note: in case of UNIX sockets we're only able to determine the
707
+ local endpoint/path, not the one it's connected to.
708
+ According to [1] it would be possible but not easily.
709
+
710
+ [1] http://serverfault.com/a/417946
711
+ """
712
+
713
+ def __init__(self):
714
+ # The string represents the basename of the corresponding
715
+ # /proc/net/{proto_name} file.
716
+ tcp4 = ("tcp", socket.AF_INET, socket.SOCK_STREAM)
717
+ tcp6 = ("tcp6", socket.AF_INET6, socket.SOCK_STREAM)
718
+ udp4 = ("udp", socket.AF_INET, socket.SOCK_DGRAM)
719
+ udp6 = ("udp6", socket.AF_INET6, socket.SOCK_DGRAM)
720
+ unix = ("unix", socket.AF_UNIX, None)
721
+ self.tmap = {
722
+ "all": (tcp4, tcp6, udp4, udp6, unix),
723
+ "tcp": (tcp4, tcp6),
724
+ "tcp4": (tcp4,),
725
+ "tcp6": (tcp6,),
726
+ "udp": (udp4, udp6),
727
+ "udp4": (udp4,),
728
+ "udp6": (udp6,),
729
+ "unix": (unix,),
730
+ "inet": (tcp4, tcp6, udp4, udp6),
731
+ "inet4": (tcp4, udp4),
732
+ "inet6": (tcp6, udp6),
733
+ }
734
+ self._procfs_path = None
735
+
736
+ def get_proc_inodes(self, pid):
737
+ inodes = defaultdict(list)
738
+ for fd in os.listdir(f"{self._procfs_path}/{pid}/fd"):
739
+ try:
740
+ inode = readlink(f"{self._procfs_path}/{pid}/fd/{fd}")
741
+ except (FileNotFoundError, ProcessLookupError):
742
+ # ENOENT == file which is gone in the meantime;
743
+ # os.stat(f"/proc/{self.pid}") will be done later
744
+ # to force NSP (if it's the case)
745
+ continue
746
+ except OSError as err:
747
+ if err.errno == errno.EINVAL:
748
+ # not a link
749
+ continue
750
+ if err.errno == errno.ENAMETOOLONG:
751
+ # file name too long
752
+ debug(err)
753
+ continue
754
+ raise
755
+ else:
756
+ if inode.startswith('socket:['):
757
+ # the process is using a socket
758
+ inode = inode[8:][:-1]
759
+ inodes[inode].append((pid, int(fd)))
760
+ return inodes
761
+
762
+ def get_all_inodes(self):
763
+ inodes = {}
764
+ for pid in pids():
765
+ try:
766
+ inodes.update(self.get_proc_inodes(pid))
767
+ except (FileNotFoundError, ProcessLookupError, PermissionError):
768
+ # os.listdir() is gonna raise a lot of access denied
769
+ # exceptions in case of unprivileged user; that's fine
770
+ # as we'll just end up returning a connection with PID
771
+ # and fd set to None anyway.
772
+ # Both netstat -an and lsof does the same so it's
773
+ # unlikely we can do any better.
774
+ # ENOENT just means a PID disappeared on us.
775
+ continue
776
+ return inodes
777
+
778
+ @staticmethod
779
+ def decode_address(addr, family):
780
+ """Accept an "ip:port" address as displayed in /proc/net/*
781
+ and convert it into a human readable form, like:
782
+
783
+ "0500000A:0016" -> ("10.0.0.5", 22)
784
+ "0000000000000000FFFF00000100007F:9E49" -> ("::ffff:127.0.0.1", 40521)
785
+
786
+ The IP address portion is a little or big endian four-byte
787
+ hexadecimal number; that is, the least significant byte is listed
788
+ first, so we need to reverse the order of the bytes to convert it
789
+ to an IP address.
790
+ The port is represented as a two-byte hexadecimal number.
791
+
792
+ Reference:
793
+ http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html
794
+ """
795
+ ip, port = addr.split(':')
796
+ port = int(port, 16)
797
+ # this usually refers to a local socket in listen mode with
798
+ # no end-points connected
799
+ if not port:
800
+ return ()
801
+ ip = ip.encode('ascii')
802
+ if family == socket.AF_INET:
803
+ # see: https://github.com/giampaolo/psutil/issues/201
804
+ if LITTLE_ENDIAN:
805
+ ip = socket.inet_ntop(family, base64.b16decode(ip)[::-1])
806
+ else:
807
+ ip = socket.inet_ntop(family, base64.b16decode(ip))
808
+ else: # IPv6
809
+ ip = base64.b16decode(ip)
810
+ try:
811
+ # see: https://github.com/giampaolo/psutil/issues/201
812
+ if LITTLE_ENDIAN:
813
+ ip = socket.inet_ntop(
814
+ socket.AF_INET6,
815
+ struct.pack('>4I', *struct.unpack('<4I', ip)),
816
+ )
817
+ else:
818
+ ip = socket.inet_ntop(
819
+ socket.AF_INET6,
820
+ struct.pack('<4I', *struct.unpack('<4I', ip)),
821
+ )
822
+ except ValueError:
823
+ # see: https://github.com/giampaolo/psutil/issues/623
824
+ if not supports_ipv6():
825
+ raise _Ipv6UnsupportedError from None
826
+ raise
827
+ return ntp.addr(ip, port)
828
+
829
+ @staticmethod
830
+ def process_inet(file, family, type_, inodes, filter_pid=None):
831
+ """Parse /proc/net/tcp* and /proc/net/udp* files."""
832
+ if file.endswith('6') and not os.path.exists(file):
833
+ # IPv6 not supported
834
+ return
835
+ with open_text(file) as f:
836
+ f.readline() # skip the first line
837
+ for lineno, line in enumerate(f, 1):
838
+ try:
839
+ _, laddr, raddr, status, _, _, _, _, _, inode = (
840
+ line.split()[:10]
841
+ )
842
+ except ValueError:
843
+ msg = (
844
+ f"error while parsing {file}; malformed line"
845
+ f" {lineno} {line!r}"
846
+ )
847
+ raise RuntimeError(msg) from None
848
+ if inode in inodes:
849
+ # # We assume inet sockets are unique, so we error
850
+ # # out if there are multiple references to the
851
+ # # same inode. We won't do this for UNIX sockets.
852
+ # if len(inodes[inode]) > 1 and family != socket.AF_UNIX:
853
+ # raise ValueError("ambiguous inode with multiple "
854
+ # "PIDs references")
855
+ pid, fd = inodes[inode][0]
856
+ else:
857
+ pid, fd = None, -1
858
+ if filter_pid is not None and filter_pid != pid:
859
+ continue
860
+ else:
861
+ if type_ == socket.SOCK_STREAM:
862
+ status = TCP_STATUSES[status]
863
+ else:
864
+ status = _common.CONN_NONE
865
+ try:
866
+ laddr = NetConnections.decode_address(laddr, family)
867
+ raddr = NetConnections.decode_address(raddr, family)
868
+ except _Ipv6UnsupportedError:
869
+ continue
870
+ yield (fd, family, type_, laddr, raddr, status, pid)
871
+
872
+ @staticmethod
873
+ def process_unix(file, family, inodes, filter_pid=None):
874
+ """Parse /proc/net/unix files."""
875
+ with open_text(file) as f:
876
+ f.readline() # skip the first line
877
+ for line in f:
878
+ tokens = line.split()
879
+ try:
880
+ _, _, _, _, type_, _, inode = tokens[0:7]
881
+ except ValueError:
882
+ if ' ' not in line:
883
+ # see: https://github.com/giampaolo/psutil/issues/766
884
+ continue
885
+ msg = (
886
+ f"error while parsing {file}; malformed line {line!r}"
887
+ )
888
+ raise RuntimeError(msg) # noqa: B904
889
+ if inode in inodes: # noqa: SIM108
890
+ # With UNIX sockets we can have a single inode
891
+ # referencing many file descriptors.
892
+ pairs = inodes[inode]
893
+ else:
894
+ pairs = [(None, -1)]
895
+ for pid, fd in pairs:
896
+ if filter_pid is not None and filter_pid != pid:
897
+ continue
898
+ else:
899
+ path = tokens[-1] if len(tokens) == 8 else ''
900
+ type_ = _common.socktype_to_enum(int(type_))
901
+ # XXX: determining the remote endpoint of a
902
+ # UNIX socket on Linux is not possible, see:
903
+ # https://serverfault.com/questions/252723/
904
+ raddr = ""
905
+ status = _common.CONN_NONE
906
+ yield (fd, family, type_, path, raddr, status, pid)
907
+
908
+ def retrieve(self, kind, pid=None):
909
+ self._procfs_path = get_procfs_path()
910
+ if pid is not None:
911
+ inodes = self.get_proc_inodes(pid)
912
+ if not inodes:
913
+ # no connections for this process
914
+ return []
915
+ else:
916
+ inodes = self.get_all_inodes()
917
+ ret = set()
918
+ for proto_name, family, type_ in self.tmap[kind]:
919
+ path = f"{self._procfs_path}/net/{proto_name}"
920
+ if family in {socket.AF_INET, socket.AF_INET6}:
921
+ ls = self.process_inet(
922
+ path, family, type_, inodes, filter_pid=pid
923
+ )
924
+ else:
925
+ ls = self.process_unix(path, family, inodes, filter_pid=pid)
926
+ for fd, family, type_, laddr, raddr, status, bound_pid in ls:
927
+ if pid:
928
+ conn = ntp.pconn(fd, family, type_, laddr, raddr, status)
929
+ else:
930
+ conn = ntp.sconn(
931
+ fd, family, type_, laddr, raddr, status, bound_pid
932
+ )
933
+ ret.add(conn)
934
+ return list(ret)
935
+
936
+
937
+ _net_connections = NetConnections()
938
+
939
+
940
+ def net_connections(kind='inet'):
941
+ """Return system-wide open connections."""
942
+ return _net_connections.retrieve(kind)
943
+
944
+
945
+ def net_io_counters():
946
+ """Return network I/O statistics for every network interface
947
+ installed on the system as a dict of raw tuples.
948
+ """
949
+ with open_text(f"{get_procfs_path()}/net/dev") as f:
950
+ lines = f.readlines()
951
+ retdict = {}
952
+ for line in lines[2:]:
953
+ colon = line.rfind(':')
954
+ assert colon > 0, repr(line)
955
+ name = line[:colon].strip()
956
+ fields = line[colon + 1 :].strip().split()
957
+
958
+ (
959
+ # in
960
+ bytes_recv,
961
+ packets_recv,
962
+ errin,
963
+ dropin,
964
+ _fifoin, # unused
965
+ _framein, # unused
966
+ _compressedin, # unused
967
+ _multicastin, # unused
968
+ # out
969
+ bytes_sent,
970
+ packets_sent,
971
+ errout,
972
+ dropout,
973
+ _fifoout, # unused
974
+ _collisionsout, # unused
975
+ _carrierout, # unused
976
+ _compressedout, # unused
977
+ ) = map(int, fields)
978
+
979
+ retdict[name] = (
980
+ bytes_sent,
981
+ bytes_recv,
982
+ packets_sent,
983
+ packets_recv,
984
+ errin,
985
+ errout,
986
+ dropin,
987
+ dropout,
988
+ )
989
+ return retdict
990
+
991
+
992
+ def net_if_stats():
993
+ """Get NIC stats (isup, duplex, speed, mtu)."""
994
+ duplex_map = {
995
+ cext.DUPLEX_FULL: NIC_DUPLEX_FULL,
996
+ cext.DUPLEX_HALF: NIC_DUPLEX_HALF,
997
+ cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN,
998
+ }
999
+ names = net_io_counters().keys()
1000
+ ret = {}
1001
+ for name in names:
1002
+ try:
1003
+ mtu = cext.net_if_mtu(name)
1004
+ flags = cext.net_if_flags(name)
1005
+ duplex, speed = cext.net_if_duplex_speed(name)
1006
+ except OSError as err:
1007
+ # https://github.com/giampaolo/psutil/issues/1279
1008
+ if err.errno != errno.ENODEV:
1009
+ raise
1010
+ debug(err)
1011
+ else:
1012
+ output_flags = ','.join(flags)
1013
+ isup = 'running' in flags
1014
+ ret[name] = ntp.snicstats(
1015
+ isup, duplex_map[duplex], speed, mtu, output_flags
1016
+ )
1017
+ return ret
1018
+
1019
+
1020
+ # =====================================================================
1021
+ # --- disks
1022
+ # =====================================================================
1023
+
1024
+
1025
+ disk_usage = _psposix.disk_usage
1026
+
1027
+
1028
+ def disk_io_counters(perdisk=False):
1029
+ """Return disk I/O statistics for every disk installed on the
1030
+ system as a dict of raw tuples.
1031
+ """
1032
+
1033
+ def read_procfs():
1034
+ # OK, this is a bit confusing. The format of /proc/diskstats can
1035
+ # have 3 variations.
1036
+ # On Linux 2.4 each line has always 15 fields, e.g.:
1037
+ # "3 0 8 hda 8 8 8 8 8 8 8 8 8 8 8"
1038
+ # On Linux 2.6+ each line *usually* has 14 fields, and the disk
1039
+ # name is in another position, like this:
1040
+ # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8"
1041
+ # ...unless (Linux 2.6) the line refers to a partition instead
1042
+ # of a disk, in which case the line has less fields (7):
1043
+ # "3 1 hda1 8 8 8 8"
1044
+ # 4.18+ has 4 fields added:
1045
+ # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8 0 0 0 0"
1046
+ # 5.5 has 2 more fields.
1047
+ # See:
1048
+ # https://www.kernel.org/doc/Documentation/iostats.txt
1049
+ # https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
1050
+ with open_text(f"{get_procfs_path()}/diskstats") as f:
1051
+ lines = f.readlines()
1052
+ for line in lines:
1053
+ fields = line.split()
1054
+ flen = len(fields)
1055
+ # fmt: off
1056
+ if flen == 15:
1057
+ # Linux 2.4
1058
+ name = fields[3]
1059
+ reads = int(fields[2])
1060
+ (reads_merged, rbytes, rtime, writes, writes_merged,
1061
+ wbytes, wtime, _, busy_time, _) = map(int, fields[4:14])
1062
+ elif flen == 14 or flen >= 18:
1063
+ # Linux 2.6+, line referring to a disk
1064
+ name = fields[2]
1065
+ (reads, reads_merged, rbytes, rtime, writes, writes_merged,
1066
+ wbytes, wtime, _, busy_time, _) = map(int, fields[3:14])
1067
+ elif flen == 7:
1068
+ # Linux 2.6+, line referring to a partition
1069
+ name = fields[2]
1070
+ reads, rbytes, writes, wbytes = map(int, fields[3:])
1071
+ rtime = wtime = reads_merged = writes_merged = busy_time = 0
1072
+ else:
1073
+ msg = f"not sure how to interpret line {line!r}"
1074
+ raise ValueError(msg)
1075
+ yield (name, reads, writes, rbytes, wbytes, rtime, wtime,
1076
+ reads_merged, writes_merged, busy_time)
1077
+ # fmt: on
1078
+
1079
+ def read_sysfs():
1080
+ for block in os.listdir('/sys/block'):
1081
+ for root, _, files in os.walk(os.path.join('/sys/block', block)):
1082
+ if 'stat' not in files:
1083
+ continue
1084
+ with open_text(os.path.join(root, 'stat')) as f:
1085
+ fields = f.read().strip().split()
1086
+ name = os.path.basename(root)
1087
+ # fmt: off
1088
+ (reads, reads_merged, rbytes, rtime, writes, writes_merged,
1089
+ wbytes, wtime, _, busy_time) = map(int, fields[:10])
1090
+ yield (name, reads, writes, rbytes, wbytes, rtime,
1091
+ wtime, reads_merged, writes_merged, busy_time)
1092
+ # fmt: on
1093
+
1094
+ if os.path.exists(f"{get_procfs_path()}/diskstats"):
1095
+ gen = read_procfs()
1096
+ elif os.path.exists('/sys/block'):
1097
+ gen = read_sysfs()
1098
+ else:
1099
+ msg = (
1100
+ f"{get_procfs_path()}/diskstats nor /sys/block are available on"
1101
+ " this system"
1102
+ )
1103
+ raise NotImplementedError(msg)
1104
+
1105
+ retdict = {}
1106
+ for entry in gen:
1107
+ # fmt: off
1108
+ (name, reads, writes, rbytes, wbytes, rtime, wtime, reads_merged,
1109
+ writes_merged, busy_time) = entry
1110
+ if not perdisk and not is_storage_device(name):
1111
+ # perdisk=False means we want to calculate totals so we skip
1112
+ # partitions (e.g. 'sda1', 'nvme0n1p1') and only include
1113
+ # base disk devices (e.g. 'sda', 'nvme0n1'). Base disks
1114
+ # include a total of all their partitions + some extra size
1115
+ # of their own:
1116
+ # $ cat /proc/diskstats
1117
+ # 259 0 sda 10485760 ...
1118
+ # 259 1 sda1 5186039 ...
1119
+ # 259 1 sda2 5082039 ...
1120
+ # See:
1121
+ # https://github.com/giampaolo/psutil/pull/1313
1122
+ continue
1123
+
1124
+ rbytes *= DISK_SECTOR_SIZE
1125
+ wbytes *= DISK_SECTOR_SIZE
1126
+ retdict[name] = (reads, writes, rbytes, wbytes, rtime, wtime,
1127
+ reads_merged, writes_merged, busy_time)
1128
+ # fmt: on
1129
+
1130
+ return retdict
1131
+
1132
+
1133
+ class RootFsDeviceFinder:
1134
+ """disk_partitions() may return partitions with device == "/dev/root"
1135
+ or "rootfs". This container class uses different strategies to try to
1136
+ obtain the real device path. Resources:
1137
+ https://bootlin.com/blog/find-root-device/
1138
+ https://www.systutorials.com/how-to-find-the-disk-where-root-is-on-in-bash-on-linux/.
1139
+ """
1140
+
1141
+ __slots__ = ['major', 'minor']
1142
+
1143
+ def __init__(self):
1144
+ dev = os.stat("/").st_dev
1145
+ self.major = os.major(dev)
1146
+ self.minor = os.minor(dev)
1147
+
1148
+ def ask_proc_partitions(self):
1149
+ with open_text(f"{get_procfs_path()}/partitions") as f:
1150
+ for line in f.readlines()[2:]:
1151
+ fields = line.split()
1152
+ if len(fields) < 4: # just for extra safety
1153
+ continue
1154
+ major = int(fields[0]) if fields[0].isdigit() else None
1155
+ minor = int(fields[1]) if fields[1].isdigit() else None
1156
+ name = fields[3]
1157
+ if major == self.major and minor == self.minor:
1158
+ if name: # just for extra safety
1159
+ return f"/dev/{name}"
1160
+
1161
+ def ask_sys_dev_block(self):
1162
+ path = f"/sys/dev/block/{self.major}:{self.minor}/uevent"
1163
+ with open_text(path) as f:
1164
+ for line in f:
1165
+ if line.startswith("DEVNAME="):
1166
+ name = line.strip().rpartition("DEVNAME=")[2]
1167
+ if name: # just for extra safety
1168
+ return f"/dev/{name}"
1169
+
1170
+ def ask_sys_class_block(self):
1171
+ needle = f"{self.major}:{self.minor}"
1172
+ files = glob.iglob("/sys/class/block/*/dev")
1173
+ for file in files:
1174
+ try:
1175
+ f = open_text(file)
1176
+ except FileNotFoundError: # race condition
1177
+ continue
1178
+ else:
1179
+ with f:
1180
+ data = f.read().strip()
1181
+ if data == needle:
1182
+ name = os.path.basename(os.path.dirname(file))
1183
+ return f"/dev/{name}"
1184
+
1185
+ def find(self):
1186
+ path = None
1187
+ if path is None:
1188
+ try:
1189
+ path = self.ask_proc_partitions()
1190
+ except OSError as err:
1191
+ debug(err)
1192
+ if path is None:
1193
+ try:
1194
+ path = self.ask_sys_dev_block()
1195
+ except OSError as err:
1196
+ debug(err)
1197
+ if path is None:
1198
+ try:
1199
+ path = self.ask_sys_class_block()
1200
+ except OSError as err:
1201
+ debug(err)
1202
+ # We use exists() because the "/dev/*" part of the path is hard
1203
+ # coded, so we want to be sure.
1204
+ if path is not None and os.path.exists(path):
1205
+ return path
1206
+
1207
+
1208
+ def disk_partitions(all=False):
1209
+ """Return mounted disk partitions as a list of namedtuples."""
1210
+ fstypes = set()
1211
+ procfs_path = get_procfs_path()
1212
+ if not all:
1213
+ with open_text(f"{procfs_path}/filesystems") as f:
1214
+ for line in f:
1215
+ line = line.strip()
1216
+ if not line.startswith("nodev"):
1217
+ fstypes.add(line.strip())
1218
+ else:
1219
+ # ignore all lines starting with "nodev" except "nodev zfs"
1220
+ fstype = line.split("\t")[1]
1221
+ if fstype == "zfs":
1222
+ fstypes.add("zfs")
1223
+
1224
+ # See: https://github.com/giampaolo/psutil/issues/1307
1225
+ if procfs_path == "/proc" and os.path.isfile('/etc/mtab'):
1226
+ mounts_path = os.path.realpath("/etc/mtab")
1227
+ else:
1228
+ mounts_path = os.path.realpath(f"{procfs_path}/self/mounts")
1229
+
1230
+ retlist = []
1231
+ partitions = cext.disk_partitions(mounts_path)
1232
+ for partition in partitions:
1233
+ device, mountpoint, fstype, opts = partition
1234
+ if device == 'none':
1235
+ device = ''
1236
+ if device in {"/dev/root", "rootfs"}:
1237
+ device = RootFsDeviceFinder().find() or device
1238
+ if not all:
1239
+ if not device or fstype not in fstypes:
1240
+ continue
1241
+ ntuple = ntp.sdiskpart(device, mountpoint, fstype, opts)
1242
+ retlist.append(ntuple)
1243
+
1244
+ return retlist
1245
+
1246
+
1247
+ # =====================================================================
1248
+ # --- sensors
1249
+ # =====================================================================
1250
+
1251
+
1252
+ def sensors_temperatures():
1253
+ """Return hardware (CPU and others) temperatures as a dict
1254
+ including hardware name, label, current, max and critical
1255
+ temperatures.
1256
+
1257
+ Implementation notes:
1258
+ - /sys/class/hwmon looks like the most recent interface to
1259
+ retrieve this info, and this implementation relies on it
1260
+ only (old distros will probably use something else)
1261
+ - lm-sensors on Ubuntu 16.04 relies on /sys/class/hwmon
1262
+ - /sys/class/thermal/thermal_zone* is another one but it's more
1263
+ difficult to parse
1264
+ """
1265
+ ret = collections.defaultdict(list)
1266
+ basenames = glob.glob('/sys/class/hwmon/hwmon*/temp*_*')
1267
+ # CentOS has an intermediate /device directory:
1268
+ # https://github.com/giampaolo/psutil/issues/971
1269
+ # https://github.com/nicolargo/glances/issues/1060
1270
+ basenames.extend(glob.glob('/sys/class/hwmon/hwmon*/device/temp*_*'))
1271
+ basenames = sorted({x.split('_')[0] for x in basenames})
1272
+
1273
+ # Only add the coretemp hwmon entries if they're not already in
1274
+ # /sys/class/hwmon/
1275
+ # https://github.com/giampaolo/psutil/issues/1708
1276
+ # https://github.com/giampaolo/psutil/pull/1648
1277
+ basenames2 = glob.glob(
1278
+ '/sys/devices/platform/coretemp.*/hwmon/hwmon*/temp*_*'
1279
+ )
1280
+ repl = re.compile(r"/sys/devices/platform/coretemp.*/hwmon/")
1281
+ for name in basenames2:
1282
+ altname = repl.sub('/sys/class/hwmon/', name)
1283
+ if altname not in basenames:
1284
+ basenames.append(name)
1285
+
1286
+ for base in basenames:
1287
+ try:
1288
+ path = base + '_input'
1289
+ current = float(bcat(path)) / 1000.0
1290
+ path = os.path.join(os.path.dirname(base), 'name')
1291
+ unit_name = cat(path).strip()
1292
+ except (OSError, ValueError):
1293
+ # A lot of things can go wrong here, so let's just skip the
1294
+ # whole entry. Sure thing is Linux's /sys/class/hwmon really
1295
+ # is a stinky broken mess.
1296
+ # https://github.com/giampaolo/psutil/issues/1009
1297
+ # https://github.com/giampaolo/psutil/issues/1101
1298
+ # https://github.com/giampaolo/psutil/issues/1129
1299
+ # https://github.com/giampaolo/psutil/issues/1245
1300
+ # https://github.com/giampaolo/psutil/issues/1323
1301
+ continue
1302
+
1303
+ high = bcat(base + '_max', fallback=None)
1304
+ critical = bcat(base + '_crit', fallback=None)
1305
+ label = cat(base + '_label', fallback='').strip()
1306
+
1307
+ if high is not None:
1308
+ try:
1309
+ high = float(high) / 1000.0
1310
+ except ValueError:
1311
+ high = None
1312
+ if critical is not None:
1313
+ try:
1314
+ critical = float(critical) / 1000.0
1315
+ except ValueError:
1316
+ critical = None
1317
+
1318
+ ret[unit_name].append((label, current, high, critical))
1319
+
1320
+ # Indication that no sensors were detected in /sys/class/hwmon/
1321
+ if not basenames:
1322
+ basenames = glob.glob('/sys/class/thermal/thermal_zone*')
1323
+ basenames = sorted(set(basenames))
1324
+
1325
+ for base in basenames:
1326
+ try:
1327
+ path = os.path.join(base, 'temp')
1328
+ current = float(bcat(path)) / 1000.0
1329
+ path = os.path.join(base, 'type')
1330
+ unit_name = cat(path).strip()
1331
+ except (OSError, ValueError) as err:
1332
+ debug(err)
1333
+ continue
1334
+
1335
+ trip_paths = glob.glob(base + '/trip_point*')
1336
+ trip_points = {
1337
+ '_'.join(os.path.basename(p).split('_')[0:3])
1338
+ for p in trip_paths
1339
+ }
1340
+ critical = None
1341
+ high = None
1342
+ for trip_point in trip_points:
1343
+ path = os.path.join(base, trip_point + "_type")
1344
+ trip_type = cat(path, fallback='').strip()
1345
+ if trip_type == 'critical':
1346
+ critical = bcat(
1347
+ os.path.join(base, trip_point + "_temp"), fallback=None
1348
+ )
1349
+ elif trip_type == 'high':
1350
+ high = bcat(
1351
+ os.path.join(base, trip_point + "_temp"), fallback=None
1352
+ )
1353
+
1354
+ if high is not None:
1355
+ try:
1356
+ high = float(high) / 1000.0
1357
+ except ValueError:
1358
+ high = None
1359
+ if critical is not None:
1360
+ try:
1361
+ critical = float(critical) / 1000.0
1362
+ except ValueError:
1363
+ critical = None
1364
+
1365
+ ret[unit_name].append(('', current, high, critical))
1366
+
1367
+ return dict(ret)
1368
+
1369
+
1370
+ def sensors_fans():
1371
+ """Return hardware fans info (for CPU and other peripherals) as a
1372
+ dict including hardware label and current speed.
1373
+
1374
+ Implementation notes:
1375
+ - /sys/class/hwmon looks like the most recent interface to
1376
+ retrieve this info, and this implementation relies on it
1377
+ only (old distros will probably use something else)
1378
+ - lm-sensors on Ubuntu 16.04 relies on /sys/class/hwmon
1379
+ """
1380
+ ret = collections.defaultdict(list)
1381
+ basenames = glob.glob('/sys/class/hwmon/hwmon*/fan*_*')
1382
+ if not basenames:
1383
+ # CentOS has an intermediate /device directory:
1384
+ # https://github.com/giampaolo/psutil/issues/971
1385
+ basenames = glob.glob('/sys/class/hwmon/hwmon*/device/fan*_*')
1386
+
1387
+ basenames = sorted({x.split("_")[0] for x in basenames})
1388
+ for base in basenames:
1389
+ try:
1390
+ current = int(bcat(base + '_input'))
1391
+ except OSError as err:
1392
+ debug(err)
1393
+ continue
1394
+ unit_name = cat(os.path.join(os.path.dirname(base), 'name')).strip()
1395
+ label = cat(base + '_label', fallback='').strip()
1396
+ ret[unit_name].append(ntp.sfan(label, current))
1397
+
1398
+ return dict(ret)
1399
+
1400
+
1401
+ def sensors_battery():
1402
+ """Return battery information.
1403
+ Implementation note: it appears /sys/class/power_supply/BAT0/
1404
+ directory structure may vary and provide files with the same
1405
+ meaning but under different names, see:
1406
+ https://github.com/giampaolo/psutil/issues/966.
1407
+ """
1408
+ null = object()
1409
+
1410
+ def multi_bcat(*paths):
1411
+ """Attempt to read the content of multiple files which may
1412
+ not exist. If none of them exist return None.
1413
+ """
1414
+ for path in paths:
1415
+ ret = bcat(path, fallback=null)
1416
+ if ret != null:
1417
+ try:
1418
+ return int(ret)
1419
+ except ValueError:
1420
+ return ret.strip()
1421
+ return None
1422
+
1423
+ bats = [
1424
+ x
1425
+ for x in os.listdir(POWER_SUPPLY_PATH)
1426
+ if x.startswith('BAT') or 'battery' in x.lower()
1427
+ ]
1428
+ if not bats:
1429
+ return None
1430
+ # Get the first available battery. Usually this is "BAT0", except
1431
+ # some rare exceptions:
1432
+ # https://github.com/giampaolo/psutil/issues/1238
1433
+ root = os.path.join(POWER_SUPPLY_PATH, min(bats))
1434
+
1435
+ # Base metrics.
1436
+ energy_now = multi_bcat(root + "/energy_now", root + "/charge_now")
1437
+ power_now = multi_bcat(root + "/power_now", root + "/current_now")
1438
+ energy_full = multi_bcat(root + "/energy_full", root + "/charge_full")
1439
+ time_to_empty = multi_bcat(root + "/time_to_empty_now")
1440
+
1441
+ # Percent. If we have energy_full the percentage will be more
1442
+ # accurate compared to reading /capacity file (float vs. int).
1443
+ if energy_full is not None and energy_now is not None:
1444
+ try:
1445
+ percent = 100.0 * energy_now / energy_full
1446
+ except ZeroDivisionError:
1447
+ percent = 0.0
1448
+ else:
1449
+ percent = int(cat(root + "/capacity", fallback=-1))
1450
+ if percent == -1:
1451
+ return None
1452
+
1453
+ # Is AC power cable plugged in?
1454
+ # Note: AC0 is not always available and sometimes (e.g. CentOS7)
1455
+ # it's called "AC".
1456
+ power_plugged = None
1457
+ online = multi_bcat(
1458
+ os.path.join(POWER_SUPPLY_PATH, "AC0/online"),
1459
+ os.path.join(POWER_SUPPLY_PATH, "AC/online"),
1460
+ )
1461
+ if online is not None:
1462
+ power_plugged = online == 1
1463
+ else:
1464
+ status = cat(root + "/status", fallback="").strip().lower()
1465
+ if status == "discharging":
1466
+ power_plugged = False
1467
+ elif status in {"charging", "full"}:
1468
+ power_plugged = True
1469
+
1470
+ # Seconds left.
1471
+ # Note to self: we may also calculate the charging ETA as per:
1472
+ # https://github.com/thialfihar/dotfiles/blob/
1473
+ # 013937745fd9050c30146290e8f963d65c0179e6/bin/battery.py#L55
1474
+ if power_plugged:
1475
+ secsleft = _common.POWER_TIME_UNLIMITED
1476
+ elif energy_now is not None and power_now is not None:
1477
+ try:
1478
+ secsleft = int(energy_now / abs(power_now) * 3600)
1479
+ except ZeroDivisionError:
1480
+ secsleft = _common.POWER_TIME_UNKNOWN
1481
+ elif time_to_empty is not None:
1482
+ secsleft = int(time_to_empty * 60)
1483
+ if secsleft < 0:
1484
+ secsleft = _common.POWER_TIME_UNKNOWN
1485
+ else:
1486
+ secsleft = _common.POWER_TIME_UNKNOWN
1487
+
1488
+ return ntp.sbattery(percent, secsleft, power_plugged)
1489
+
1490
+
1491
+ # =====================================================================
1492
+ # --- other system functions
1493
+ # =====================================================================
1494
+
1495
+
1496
+ def users():
1497
+ """Return currently connected users as a list of namedtuples."""
1498
+ retlist = []
1499
+ rawlist = cext.users()
1500
+ for item in rawlist:
1501
+ user, tty, hostname, tstamp, pid = item
1502
+ nt = ntp.suser(user, tty or None, hostname, tstamp, pid)
1503
+ retlist.append(nt)
1504
+ return retlist
1505
+
1506
+
1507
+ def boot_time():
1508
+ """Return the system boot time expressed in seconds since the epoch."""
1509
+ path = f"{get_procfs_path()}/stat"
1510
+ with open_binary(path) as f:
1511
+ for line in f:
1512
+ if line.startswith(b'btime'):
1513
+ return float(line.strip().split()[1])
1514
+ msg = f"line 'btime' not found in {path}"
1515
+ raise RuntimeError(msg)
1516
+
1517
+
1518
+ # =====================================================================
1519
+ # --- processes
1520
+ # =====================================================================
1521
+
1522
+
1523
+ def pids():
1524
+ """Returns a list of PIDs currently running on the system."""
1525
+ path = get_procfs_path().encode(ENCODING)
1526
+ return [int(x) for x in os.listdir(path) if x.isdigit()]
1527
+
1528
+
1529
+ def pid_exists(pid):
1530
+ """Check for the existence of a unix PID. Linux TIDs are not
1531
+ supported (always return False).
1532
+ """
1533
+ if not _psposix.pid_exists(pid):
1534
+ return False
1535
+ else:
1536
+ # Linux's apparently does not distinguish between PIDs and TIDs
1537
+ # (thread IDs).
1538
+ # listdir("/proc") won't show any TID (only PIDs) but
1539
+ # os.stat("/proc/{tid}") will succeed if {tid} exists.
1540
+ # os.kill() can also be passed a TID. This is quite confusing.
1541
+ # In here we want to enforce this distinction and support PIDs
1542
+ # only, see:
1543
+ # https://github.com/giampaolo/psutil/issues/687
1544
+ try:
1545
+ # Note: already checked that this is faster than using a
1546
+ # regular expr. Also (a lot) faster than doing
1547
+ # 'return pid in pids()'
1548
+ path = f"{get_procfs_path()}/{pid}/status"
1549
+ with open_binary(path) as f:
1550
+ for line in f:
1551
+ if line.startswith(b"Tgid:"):
1552
+ tgid = int(line.split()[1])
1553
+ # If tgid and pid are the same then we're
1554
+ # dealing with a process PID.
1555
+ return tgid == pid
1556
+ msg = f"'Tgid' line not found in {path}"
1557
+ raise ValueError(msg)
1558
+ except (OSError, ValueError):
1559
+ return pid in pids()
1560
+
1561
+
1562
+ def ppid_map():
1563
+ """Obtain a {pid: ppid, ...} dict for all running processes in
1564
+ one shot. Used to speed up Process.children().
1565
+ """
1566
+ ret = {}
1567
+ procfs_path = get_procfs_path()
1568
+ for pid in pids():
1569
+ try:
1570
+ with open_binary(f"{procfs_path}/{pid}/stat") as f:
1571
+ data = f.read()
1572
+ except (FileNotFoundError, ProcessLookupError):
1573
+ pass
1574
+ except PermissionError as err:
1575
+ raise AccessDenied(pid) from err
1576
+ else:
1577
+ rpar = data.rfind(b')')
1578
+ dset = data[rpar + 2 :].split()
1579
+ ppid = int(dset[1])
1580
+ ret[pid] = ppid
1581
+ return ret
1582
+
1583
+
1584
+ def wrap_exceptions(fun):
1585
+ """Decorator which translates bare OSError exceptions into
1586
+ NoSuchProcess and AccessDenied.
1587
+ """
1588
+
1589
+ @functools.wraps(fun)
1590
+ def wrapper(self, *args, **kwargs):
1591
+ pid, name = self.pid, self._name
1592
+ try:
1593
+ return fun(self, *args, **kwargs)
1594
+ except PermissionError as err:
1595
+ raise AccessDenied(pid, name) from err
1596
+ except ProcessLookupError as err:
1597
+ self._raise_if_zombie()
1598
+ raise NoSuchProcess(pid, name) from err
1599
+ except FileNotFoundError as err:
1600
+ self._raise_if_zombie()
1601
+ # /proc/PID directory may still exist, but the files within
1602
+ # it may not, indicating the process is gone, see:
1603
+ # https://github.com/giampaolo/psutil/issues/2418
1604
+ if not os.path.exists(f"{self._procfs_path}/{pid}/stat"):
1605
+ raise NoSuchProcess(pid, name) from err
1606
+ raise
1607
+
1608
+ return wrapper
1609
+
1610
+
1611
+ class Process:
1612
+ """Linux process implementation."""
1613
+
1614
+ __slots__ = [
1615
+ "_cache",
1616
+ "_ctime",
1617
+ "_name",
1618
+ "_ppid",
1619
+ "_procfs_path",
1620
+ "pid",
1621
+ ]
1622
+
1623
+ def __init__(self, pid):
1624
+ self.pid = pid
1625
+ self._name = None
1626
+ self._ppid = None
1627
+ self._ctime = None
1628
+ self._procfs_path = get_procfs_path()
1629
+
1630
+ def _is_zombie(self):
1631
+ # Note: most of the times Linux is able to return info about the
1632
+ # process even if it's a zombie, and /proc/{pid} will exist.
1633
+ # There are some exceptions though, like exe(), cmdline() and
1634
+ # memory_maps(). In these cases /proc/{pid}/{file} exists but
1635
+ # it's empty. Instead of returning a "null" value we'll raise an
1636
+ # exception.
1637
+ try:
1638
+ data = bcat(f"{self._procfs_path}/{self.pid}/stat")
1639
+ except OSError:
1640
+ return False
1641
+ else:
1642
+ rpar = data.rfind(b')')
1643
+ status = data[rpar + 2 : rpar + 3]
1644
+ return status == b"Z"
1645
+
1646
+ def _raise_if_zombie(self):
1647
+ if self._is_zombie():
1648
+ raise ZombieProcess(self.pid, self._name, self._ppid)
1649
+
1650
+ def _raise_if_not_alive(self):
1651
+ """Raise NSP if the process disappeared on us."""
1652
+ # For those C function who do not raise NSP, possibly returning
1653
+ # incorrect or incomplete result.
1654
+ os.stat(f"{self._procfs_path}/{self.pid}")
1655
+
1656
+ def _readlink(self, path, fallback=UNSET):
1657
+ # * https://github.com/giampaolo/psutil/issues/503
1658
+ # os.readlink('/proc/pid/exe') may raise ESRCH (ProcessLookupError)
1659
+ # instead of ENOENT (FileNotFoundError) when it races.
1660
+ # * ENOENT may occur also if the path actually exists if PID is
1661
+ # a low PID (~0-20 range).
1662
+ # * https://github.com/giampaolo/psutil/issues/2514
1663
+ try:
1664
+ return readlink(path)
1665
+ except (FileNotFoundError, ProcessLookupError):
1666
+ if os.path.lexists(f"{self._procfs_path}/{self.pid}"):
1667
+ self._raise_if_zombie()
1668
+ if fallback is not UNSET:
1669
+ return fallback
1670
+ raise
1671
+
1672
+ @wrap_exceptions
1673
+ @memoize_when_activated
1674
+ def _parse_stat_file(self):
1675
+ """Parse /proc/{pid}/stat file and return a dict with various
1676
+ process info.
1677
+ Using "man proc" as a reference: where "man proc" refers to
1678
+ position N always subtract 3 (e.g ppid position 4 in
1679
+ 'man proc' == position 1 in here).
1680
+ The return value is cached in case oneshot() ctx manager is
1681
+ in use.
1682
+ """
1683
+ data = bcat(f"{self._procfs_path}/{self.pid}/stat")
1684
+ # Process name is between parentheses. It can contain spaces and
1685
+ # other parentheses. This is taken into account by looking for
1686
+ # the first occurrence of "(" and the last occurrence of ")".
1687
+ rpar = data.rfind(b')')
1688
+ name = data[data.find(b'(') + 1 : rpar]
1689
+ fields = data[rpar + 2 :].split()
1690
+
1691
+ ret = {}
1692
+ ret['name'] = name
1693
+ ret['status'] = fields[0]
1694
+ ret['ppid'] = fields[1]
1695
+ ret['ttynr'] = fields[4]
1696
+ ret['utime'] = fields[11]
1697
+ ret['stime'] = fields[12]
1698
+ ret['children_utime'] = fields[13]
1699
+ ret['children_stime'] = fields[14]
1700
+ ret['create_time'] = fields[19]
1701
+ ret['cpu_num'] = fields[36]
1702
+ try:
1703
+ ret['blkio_ticks'] = fields[39] # aka 'delayacct_blkio_ticks'
1704
+ except IndexError:
1705
+ # https://github.com/giampaolo/psutil/issues/2455
1706
+ debug("can't get blkio_ticks, set iowait to 0")
1707
+ ret['blkio_ticks'] = 0
1708
+
1709
+ return ret
1710
+
1711
+ @wrap_exceptions
1712
+ @memoize_when_activated
1713
+ def _read_status_file(self):
1714
+ """Read /proc/{pid}/stat file and return its content.
1715
+ The return value is cached in case oneshot() ctx manager is
1716
+ in use.
1717
+ """
1718
+ with open_binary(f"{self._procfs_path}/{self.pid}/status") as f:
1719
+ return f.read()
1720
+
1721
+ @wrap_exceptions
1722
+ @memoize_when_activated
1723
+ def _read_smaps_file(self):
1724
+ with open_binary(f"{self._procfs_path}/{self.pid}/smaps") as f:
1725
+ return f.read().strip()
1726
+
1727
+ def oneshot_enter(self):
1728
+ self._parse_stat_file.cache_activate(self)
1729
+ self._read_status_file.cache_activate(self)
1730
+ self._read_smaps_file.cache_activate(self)
1731
+
1732
+ def oneshot_exit(self):
1733
+ self._parse_stat_file.cache_deactivate(self)
1734
+ self._read_status_file.cache_deactivate(self)
1735
+ self._read_smaps_file.cache_deactivate(self)
1736
+
1737
+ @wrap_exceptions
1738
+ def name(self):
1739
+ # XXX - gets changed later and probably needs refactoring
1740
+ return decode(self._parse_stat_file()['name'])
1741
+
1742
+ @wrap_exceptions
1743
+ def exe(self):
1744
+ return self._readlink(
1745
+ f"{self._procfs_path}/{self.pid}/exe", fallback=""
1746
+ )
1747
+
1748
+ @wrap_exceptions
1749
+ def cmdline(self):
1750
+ with open_text(f"{self._procfs_path}/{self.pid}/cmdline") as f:
1751
+ data = f.read()
1752
+ if not data:
1753
+ # may happen in case of zombie process
1754
+ self._raise_if_zombie()
1755
+ return []
1756
+ # 'man proc' states that args are separated by null bytes '\0'
1757
+ # and last char is supposed to be a null byte. Nevertheless
1758
+ # some processes may change their cmdline after being started
1759
+ # (via setproctitle() or similar), they are usually not
1760
+ # compliant with this rule and use spaces instead. Google
1761
+ # Chrome process is an example. See:
1762
+ # https://github.com/giampaolo/psutil/issues/1179
1763
+ sep = '\x00' if data.endswith('\x00') else ' '
1764
+ if data.endswith(sep):
1765
+ data = data[:-1]
1766
+ cmdline = data.split(sep)
1767
+ # Sometimes last char is a null byte '\0' but the args are
1768
+ # separated by spaces, see: https://github.com/giampaolo/psutil/
1769
+ # issues/1179#issuecomment-552984549
1770
+ if sep == '\x00' and len(cmdline) == 1 and ' ' in data:
1771
+ cmdline = data.split(' ')
1772
+ return cmdline
1773
+
1774
+ @wrap_exceptions
1775
+ def environ(self):
1776
+ with open_text(f"{self._procfs_path}/{self.pid}/environ") as f:
1777
+ data = f.read()
1778
+ return parse_environ_block(data)
1779
+
1780
+ @wrap_exceptions
1781
+ def terminal(self):
1782
+ tty_nr = int(self._parse_stat_file()['ttynr'])
1783
+ tmap = _psposix.get_terminal_map()
1784
+ try:
1785
+ return tmap[tty_nr]
1786
+ except KeyError:
1787
+ return None
1788
+
1789
+ # May not be available on old kernels.
1790
+ if os.path.exists(f"/proc/{os.getpid()}/io"):
1791
+
1792
+ @wrap_exceptions
1793
+ def io_counters(self):
1794
+ fname = f"{self._procfs_path}/{self.pid}/io"
1795
+ fields = {}
1796
+ with open_binary(fname) as f:
1797
+ for line in f:
1798
+ # https://github.com/giampaolo/psutil/issues/1004
1799
+ line = line.strip()
1800
+ if line:
1801
+ try:
1802
+ name, value = line.split(b': ')
1803
+ except ValueError:
1804
+ # https://github.com/giampaolo/psutil/issues/1004
1805
+ continue
1806
+ else:
1807
+ fields[name] = int(value)
1808
+ if not fields:
1809
+ msg = f"{fname} file was empty"
1810
+ raise RuntimeError(msg)
1811
+ try:
1812
+ return ntp.pio(
1813
+ fields[b'syscr'], # read syscalls
1814
+ fields[b'syscw'], # write syscalls
1815
+ fields[b'read_bytes'], # read bytes
1816
+ fields[b'write_bytes'], # write bytes
1817
+ fields[b'rchar'], # read chars
1818
+ fields[b'wchar'], # write chars
1819
+ )
1820
+ except KeyError as err:
1821
+ msg = (
1822
+ f"{err.args[0]!r} field was not found in {fname}; found"
1823
+ f" fields are {fields!r}"
1824
+ )
1825
+ raise ValueError(msg) from None
1826
+
1827
+ @wrap_exceptions
1828
+ def cpu_times(self):
1829
+ values = self._parse_stat_file()
1830
+ utime = float(values['utime']) / CLOCK_TICKS
1831
+ stime = float(values['stime']) / CLOCK_TICKS
1832
+ children_utime = float(values['children_utime']) / CLOCK_TICKS
1833
+ children_stime = float(values['children_stime']) / CLOCK_TICKS
1834
+ iowait = float(values['blkio_ticks']) / CLOCK_TICKS
1835
+ return ntp.pcputimes(
1836
+ utime, stime, children_utime, children_stime, iowait
1837
+ )
1838
+
1839
+ @wrap_exceptions
1840
+ def cpu_num(self):
1841
+ """What CPU the process is on."""
1842
+ return int(self._parse_stat_file()['cpu_num'])
1843
+
1844
+ @wrap_exceptions
1845
+ def wait(self, timeout=None):
1846
+ return _psposix.wait_pid(self.pid, timeout)
1847
+
1848
+ @wrap_exceptions
1849
+ def create_time(self, monotonic=False):
1850
+ # The 'starttime' field in /proc/[pid]/stat is expressed in
1851
+ # jiffies (clock ticks per second), a relative value which
1852
+ # represents the number of clock ticks that have passed since
1853
+ # the system booted until the process was created. It never
1854
+ # changes and is unaffected by system clock updates.
1855
+ if self._ctime is None:
1856
+ self._ctime = (
1857
+ float(self._parse_stat_file()['create_time']) / CLOCK_TICKS
1858
+ )
1859
+ if monotonic:
1860
+ return self._ctime
1861
+ # Add the boot time, returning time expressed in seconds since
1862
+ # the epoch. This is subject to system clock updates.
1863
+ return self._ctime + boot_time()
1864
+
1865
+ @wrap_exceptions
1866
+ def memory_info(self):
1867
+ # ============================================================
1868
+ # | FIELD | DESCRIPTION | AKA | TOP |
1869
+ # ============================================================
1870
+ # | rss | resident set size | | RES |
1871
+ # | vms | total program size | size | VIRT |
1872
+ # | shared | shared pages (from shared mappings) | | SHR |
1873
+ # | text | text ('code') | trs | CODE |
1874
+ # | lib | library (unused in Linux 2.6) | lrs | |
1875
+ # | data | data + stack | drs | DATA |
1876
+ # | dirty | dirty pages (unused in Linux 2.6) | dt | |
1877
+ # ============================================================
1878
+ with open_binary(f"{self._procfs_path}/{self.pid}/statm") as f:
1879
+ vms, rss, shared, text, lib, data, dirty = (
1880
+ int(x) * PAGESIZE for x in f.readline().split()[:7]
1881
+ )
1882
+ return ntp.pmem(rss, vms, shared, text, lib, data, dirty)
1883
+
1884
+ if HAS_PROC_SMAPS_ROLLUP or HAS_PROC_SMAPS:
1885
+
1886
+ def _parse_smaps_rollup(self):
1887
+ # /proc/pid/smaps_rollup was added to Linux in 2017. Faster
1888
+ # than /proc/pid/smaps. It reports higher PSS than */smaps
1889
+ # (from 1k up to 200k higher; tested against all processes).
1890
+ # IMPORTANT: /proc/pid/smaps_rollup is weird, because it
1891
+ # raises ESRCH / ENOENT for many PIDs, even if they're alive
1892
+ # (also as root). In that case we'll use /proc/pid/smaps as
1893
+ # fallback, which is slower but has a +50% success rate
1894
+ # compared to /proc/pid/smaps_rollup.
1895
+ uss = pss = swap = 0
1896
+ with open_binary(
1897
+ f"{self._procfs_path}/{self.pid}/smaps_rollup"
1898
+ ) as f:
1899
+ for line in f:
1900
+ if line.startswith(b"Private_"):
1901
+ # Private_Clean, Private_Dirty, Private_Hugetlb
1902
+ uss += int(line.split()[1]) * 1024
1903
+ elif line.startswith(b"Pss:"):
1904
+ pss = int(line.split()[1]) * 1024
1905
+ elif line.startswith(b"Swap:"):
1906
+ swap = int(line.split()[1]) * 1024
1907
+ return (uss, pss, swap)
1908
+
1909
+ @wrap_exceptions
1910
+ def _parse_smaps(
1911
+ self,
1912
+ # Gets Private_Clean, Private_Dirty, Private_Hugetlb.
1913
+ _private_re=re.compile(br"\nPrivate.*:\s+(\d+)"),
1914
+ _pss_re=re.compile(br"\nPss\:\s+(\d+)"),
1915
+ _swap_re=re.compile(br"\nSwap\:\s+(\d+)"),
1916
+ ):
1917
+ # /proc/pid/smaps does not exist on kernels < 2.6.14 or if
1918
+ # CONFIG_MMU kernel configuration option is not enabled.
1919
+
1920
+ # Note: using 3 regexes is faster than reading the file
1921
+ # line by line.
1922
+ #
1923
+ # You might be tempted to calculate USS by subtracting
1924
+ # the "shared" value from the "resident" value in
1925
+ # /proc/<pid>/statm. But at least on Linux, statm's "shared"
1926
+ # value actually counts pages backed by files, which has
1927
+ # little to do with whether the pages are actually shared.
1928
+ # /proc/self/smaps on the other hand appears to give us the
1929
+ # correct information.
1930
+ smaps_data = self._read_smaps_file()
1931
+ # Note: smaps file can be empty for certain processes.
1932
+ # The code below will not crash though and will result to 0.
1933
+ uss = sum(map(int, _private_re.findall(smaps_data))) * 1024
1934
+ pss = sum(map(int, _pss_re.findall(smaps_data))) * 1024
1935
+ swap = sum(map(int, _swap_re.findall(smaps_data))) * 1024
1936
+ return (uss, pss, swap)
1937
+
1938
+ @wrap_exceptions
1939
+ def memory_full_info(self):
1940
+ if HAS_PROC_SMAPS_ROLLUP: # faster
1941
+ try:
1942
+ uss, pss, swap = self._parse_smaps_rollup()
1943
+ except (ProcessLookupError, FileNotFoundError):
1944
+ uss, pss, swap = self._parse_smaps()
1945
+ else:
1946
+ uss, pss, swap = self._parse_smaps()
1947
+ basic_mem = self.memory_info()
1948
+ return ntp.pfullmem(*basic_mem + (uss, pss, swap))
1949
+
1950
+ else:
1951
+ memory_full_info = memory_info
1952
+
1953
+ if HAS_PROC_SMAPS:
1954
+
1955
+ @wrap_exceptions
1956
+ def memory_maps(self):
1957
+ """Return process's mapped memory regions as a list of named
1958
+ tuples. Fields are explained in 'man proc'; here is an updated
1959
+ (Apr 2012) version: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/filesystems/proc.txt?id=b76437579d1344b612cf1851ae610c636cec7db0.
1960
+
1961
+ /proc/{PID}/smaps does not exist on kernels < 2.6.14 or if
1962
+ CONFIG_MMU kernel configuration option is not enabled.
1963
+ """
1964
+
1965
+ def get_blocks(lines, current_block):
1966
+ data = {}
1967
+ for line in lines:
1968
+ fields = line.split(None, 5)
1969
+ if not fields[0].endswith(b':'):
1970
+ # new block section
1971
+ yield (current_block.pop(), data)
1972
+ current_block.append(line)
1973
+ else:
1974
+ try:
1975
+ data[fields[0]] = int(fields[1]) * 1024
1976
+ except (ValueError, IndexError):
1977
+ if fields[0].startswith(b'VmFlags:'):
1978
+ # see issue #369
1979
+ continue
1980
+ msg = f"don't know how to interpret line {line!r}"
1981
+ raise ValueError(msg) from None
1982
+ yield (current_block.pop(), data)
1983
+
1984
+ data = self._read_smaps_file()
1985
+ # Note: smaps file can be empty for certain processes or for
1986
+ # zombies.
1987
+ if not data:
1988
+ self._raise_if_zombie()
1989
+ return []
1990
+ lines = data.split(b'\n')
1991
+ ls = []
1992
+ first_line = lines.pop(0)
1993
+ current_block = [first_line]
1994
+ for header, data in get_blocks(lines, current_block):
1995
+ hfields = header.split(None, 5)
1996
+ try:
1997
+ addr, perms, _offset, _dev, _inode, path = hfields
1998
+ except ValueError:
1999
+ addr, perms, _offset, _dev, _inode, path = hfields + ['']
2000
+ if not path:
2001
+ path = '[anon]'
2002
+ else:
2003
+ path = decode(path)
2004
+ path = path.strip()
2005
+ if path.endswith(' (deleted)') and not path_exists_strict(
2006
+ path
2007
+ ):
2008
+ path = path[:-10]
2009
+ item = (
2010
+ decode(addr),
2011
+ decode(perms),
2012
+ path,
2013
+ data.get(b'Rss:', 0),
2014
+ data.get(b'Size:', 0),
2015
+ data.get(b'Pss:', 0),
2016
+ data.get(b'Shared_Clean:', 0),
2017
+ data.get(b'Shared_Dirty:', 0),
2018
+ data.get(b'Private_Clean:', 0),
2019
+ data.get(b'Private_Dirty:', 0),
2020
+ data.get(b'Referenced:', 0),
2021
+ data.get(b'Anonymous:', 0),
2022
+ data.get(b'Swap:', 0),
2023
+ )
2024
+ ls.append(item)
2025
+ return ls
2026
+
2027
+ @wrap_exceptions
2028
+ def cwd(self):
2029
+ return self._readlink(
2030
+ f"{self._procfs_path}/{self.pid}/cwd", fallback=""
2031
+ )
2032
+
2033
+ @wrap_exceptions
2034
+ def num_ctx_switches(
2035
+ self, _ctxsw_re=re.compile(br'ctxt_switches:\t(\d+)')
2036
+ ):
2037
+ data = self._read_status_file()
2038
+ ctxsw = _ctxsw_re.findall(data)
2039
+ if not ctxsw:
2040
+ msg = (
2041
+ "'voluntary_ctxt_switches' and"
2042
+ " 'nonvoluntary_ctxt_switches'lines were not found in"
2043
+ f" {self._procfs_path}/{self.pid}/status; the kernel is"
2044
+ " probably older than 2.6.23"
2045
+ )
2046
+ raise NotImplementedError(msg)
2047
+ return ntp.pctxsw(int(ctxsw[0]), int(ctxsw[1]))
2048
+
2049
+ @wrap_exceptions
2050
+ def num_threads(self, _num_threads_re=re.compile(br'Threads:\t(\d+)')):
2051
+ # Using a re is faster than iterating over file line by line.
2052
+ data = self._read_status_file()
2053
+ return int(_num_threads_re.findall(data)[0])
2054
+
2055
+ @wrap_exceptions
2056
+ def threads(self):
2057
+ thread_ids = os.listdir(f"{self._procfs_path}/{self.pid}/task")
2058
+ thread_ids.sort()
2059
+ retlist = []
2060
+ hit_enoent = False
2061
+ for thread_id in thread_ids:
2062
+ fname = f"{self._procfs_path}/{self.pid}/task/{thread_id}/stat"
2063
+ try:
2064
+ with open_binary(fname) as f:
2065
+ st = f.read().strip()
2066
+ except (FileNotFoundError, ProcessLookupError):
2067
+ # no such file or directory or no such process;
2068
+ # it means thread disappeared on us
2069
+ hit_enoent = True
2070
+ continue
2071
+ # ignore the first two values ("pid (exe)")
2072
+ st = st[st.find(b')') + 2 :]
2073
+ values = st.split(b' ')
2074
+ utime = float(values[11]) / CLOCK_TICKS
2075
+ stime = float(values[12]) / CLOCK_TICKS
2076
+ ntuple = ntp.pthread(int(thread_id), utime, stime)
2077
+ retlist.append(ntuple)
2078
+ if hit_enoent:
2079
+ self._raise_if_not_alive()
2080
+ return retlist
2081
+
2082
+ @wrap_exceptions
2083
+ def nice_get(self):
2084
+ # with open_text(f"{self._procfs_path}/{self.pid}/stat") as f:
2085
+ # data = f.read()
2086
+ # return int(data.split()[18])
2087
+
2088
+ # Use C implementation
2089
+ return cext.proc_priority_get(self.pid)
2090
+
2091
+ @wrap_exceptions
2092
+ def nice_set(self, value):
2093
+ return cext.proc_priority_set(self.pid, value)
2094
+
2095
+ # starting from CentOS 6.
2096
+ if HAS_CPU_AFFINITY:
2097
+
2098
+ @wrap_exceptions
2099
+ def cpu_affinity_get(self):
2100
+ return cext.proc_cpu_affinity_get(self.pid)
2101
+
2102
+ def _get_eligible_cpus(
2103
+ self, _re=re.compile(br"Cpus_allowed_list:\t(\d+)-(\d+)")
2104
+ ):
2105
+ # See: https://github.com/giampaolo/psutil/issues/956
2106
+ data = self._read_status_file()
2107
+ match = _re.findall(data)
2108
+ if match:
2109
+ return list(range(int(match[0][0]), int(match[0][1]) + 1))
2110
+ else:
2111
+ return list(range(len(per_cpu_times())))
2112
+
2113
+ @wrap_exceptions
2114
+ def cpu_affinity_set(self, cpus):
2115
+ try:
2116
+ cext.proc_cpu_affinity_set(self.pid, cpus)
2117
+ except (OSError, ValueError) as err:
2118
+ if isinstance(err, ValueError) or err.errno == errno.EINVAL:
2119
+ eligible_cpus = self._get_eligible_cpus()
2120
+ all_cpus = tuple(range(len(per_cpu_times())))
2121
+ for cpu in cpus:
2122
+ if cpu not in all_cpus:
2123
+ msg = (
2124
+ f"invalid CPU {cpu!r}; choose between"
2125
+ f" {eligible_cpus!r}"
2126
+ )
2127
+ raise ValueError(msg) from None
2128
+ if cpu not in eligible_cpus:
2129
+ msg = (
2130
+ f"CPU number {cpu} is not eligible; choose"
2131
+ f" between {eligible_cpus}"
2132
+ )
2133
+ raise ValueError(msg) from err
2134
+ raise
2135
+
2136
+ # only starting from kernel 2.6.13
2137
+ if HAS_PROC_IO_PRIORITY:
2138
+
2139
+ @wrap_exceptions
2140
+ def ionice_get(self):
2141
+ ioclass, value = cext.proc_ioprio_get(self.pid)
2142
+ ioclass = IOPriority(ioclass)
2143
+ return ntp.pionice(ioclass, value)
2144
+
2145
+ @wrap_exceptions
2146
+ def ionice_set(self, ioclass, value):
2147
+ if value is None:
2148
+ value = 0
2149
+ if value and ioclass in {
2150
+ IOPriority.IOPRIO_CLASS_IDLE,
2151
+ IOPriority.IOPRIO_CLASS_NONE,
2152
+ }:
2153
+ msg = f"{ioclass!r} ioclass accepts no value"
2154
+ raise ValueError(msg)
2155
+ if value < 0 or value > 7:
2156
+ msg = "value not in 0-7 range"
2157
+ raise ValueError(msg)
2158
+ return cext.proc_ioprio_set(self.pid, ioclass, value)
2159
+
2160
+ if hasattr(resource, "prlimit"):
2161
+
2162
+ @wrap_exceptions
2163
+ def rlimit(self, resource_, limits=None):
2164
+ # If pid is 0 prlimit() applies to the calling process and
2165
+ # we don't want that. We should never get here though as
2166
+ # PID 0 is not supported on Linux.
2167
+ if self.pid == 0:
2168
+ msg = "can't use prlimit() against PID 0 process"
2169
+ raise ValueError(msg)
2170
+ try:
2171
+ if limits is None:
2172
+ # get
2173
+ return resource.prlimit(self.pid, resource_)
2174
+ else:
2175
+ # set
2176
+ if len(limits) != 2:
2177
+ msg = (
2178
+ "second argument must be a (soft, hard) "
2179
+ f"tuple, got {limits!r}"
2180
+ )
2181
+ raise ValueError(msg)
2182
+ resource.prlimit(self.pid, resource_, limits)
2183
+ except OSError as err:
2184
+ if err.errno == errno.ENOSYS:
2185
+ # I saw this happening on Travis:
2186
+ # https://travis-ci.org/giampaolo/psutil/jobs/51368273
2187
+ self._raise_if_zombie()
2188
+ raise
2189
+
2190
+ @wrap_exceptions
2191
+ def status(self):
2192
+ letter = self._parse_stat_file()['status']
2193
+ letter = letter.decode()
2194
+ # XXX is '?' legit? (we're not supposed to return it anyway)
2195
+ return PROC_STATUSES.get(letter, '?')
2196
+
2197
+ @wrap_exceptions
2198
+ def open_files(self):
2199
+ retlist = []
2200
+ files = os.listdir(f"{self._procfs_path}/{self.pid}/fd")
2201
+ hit_enoent = False
2202
+ for fd in files:
2203
+ file = f"{self._procfs_path}/{self.pid}/fd/{fd}"
2204
+ try:
2205
+ path = readlink(file)
2206
+ except (FileNotFoundError, ProcessLookupError):
2207
+ # ENOENT == file which is gone in the meantime
2208
+ hit_enoent = True
2209
+ continue
2210
+ except OSError as err:
2211
+ if err.errno == errno.EINVAL:
2212
+ # not a link
2213
+ continue
2214
+ if err.errno == errno.ENAMETOOLONG:
2215
+ # file name too long
2216
+ debug(err)
2217
+ continue
2218
+ raise
2219
+ else:
2220
+ # If path is not an absolute there's no way to tell
2221
+ # whether it's a regular file or not, so we skip it.
2222
+ # A regular file is always supposed to be have an
2223
+ # absolute path though.
2224
+ if path.startswith('/') and isfile_strict(path):
2225
+ # Get file position and flags.
2226
+ file = f"{self._procfs_path}/{self.pid}/fdinfo/{fd}"
2227
+ try:
2228
+ with open_binary(file) as f:
2229
+ pos = int(f.readline().split()[1])
2230
+ flags = int(f.readline().split()[1], 8)
2231
+ except (FileNotFoundError, ProcessLookupError):
2232
+ # fd gone in the meantime; process may
2233
+ # still be alive
2234
+ hit_enoent = True
2235
+ else:
2236
+ mode = file_flags_to_mode(flags)
2237
+ ntuple = ntp.popenfile(
2238
+ path, int(fd), int(pos), mode, flags
2239
+ )
2240
+ retlist.append(ntuple)
2241
+ if hit_enoent:
2242
+ self._raise_if_not_alive()
2243
+ return retlist
2244
+
2245
+ @wrap_exceptions
2246
+ def net_connections(self, kind='inet'):
2247
+ ret = _net_connections.retrieve(kind, self.pid)
2248
+ self._raise_if_not_alive()
2249
+ return ret
2250
+
2251
+ @wrap_exceptions
2252
+ def num_fds(self):
2253
+ return len(os.listdir(f"{self._procfs_path}/{self.pid}/fd"))
2254
+
2255
+ @wrap_exceptions
2256
+ def ppid(self):
2257
+ return int(self._parse_stat_file()['ppid'])
2258
+
2259
+ @wrap_exceptions
2260
+ def uids(self, _uids_re=re.compile(br'Uid:\t(\d+)\t(\d+)\t(\d+)')):
2261
+ data = self._read_status_file()
2262
+ real, effective, saved = _uids_re.findall(data)[0]
2263
+ return ntp.puids(int(real), int(effective), int(saved))
2264
+
2265
+ @wrap_exceptions
2266
+ def gids(self, _gids_re=re.compile(br'Gid:\t(\d+)\t(\d+)\t(\d+)')):
2267
+ data = self._read_status_file()
2268
+ real, effective, saved = _gids_re.findall(data)[0]
2269
+ return ntp.pgids(int(real), int(effective), int(saved))
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_psosx.py ADDED
@@ -0,0 +1,549 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
2
+ # Use of this source code is governed by a BSD-style license that can be
3
+ # found in the LICENSE file.
4
+
5
+ """macOS platform implementation."""
6
+
7
+ import errno
8
+ import functools
9
+ import os
10
+
11
+ from . import _common
12
+ from . import _ntuples as ntp
13
+ from . import _psposix
14
+ from . import _psutil_osx as cext
15
+ from ._common import AccessDenied
16
+ from ._common import NoSuchProcess
17
+ from ._common import ZombieProcess
18
+ from ._common import conn_tmap
19
+ from ._common import conn_to_ntuple
20
+ from ._common import debug
21
+ from ._common import isfile_strict
22
+ from ._common import memoize_when_activated
23
+ from ._common import parse_environ_block
24
+ from ._common import usage_percent
25
+
26
+ __extra__all__ = []
27
+
28
+
29
+ # =====================================================================
30
+ # --- globals
31
+ # =====================================================================
32
+
33
+
34
+ PAGESIZE = cext.getpagesize()
35
+ AF_LINK = cext.AF_LINK
36
+
37
+ TCP_STATUSES = {
38
+ cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
39
+ cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
40
+ cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV,
41
+ cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
42
+ cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
43
+ cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
44
+ cext.TCPS_CLOSED: _common.CONN_CLOSE,
45
+ cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
46
+ cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
47
+ cext.TCPS_LISTEN: _common.CONN_LISTEN,
48
+ cext.TCPS_CLOSING: _common.CONN_CLOSING,
49
+ cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
50
+ }
51
+
52
+ PROC_STATUSES = {
53
+ cext.SIDL: _common.STATUS_IDLE,
54
+ cext.SRUN: _common.STATUS_RUNNING,
55
+ cext.SSLEEP: _common.STATUS_SLEEPING,
56
+ cext.SSTOP: _common.STATUS_STOPPED,
57
+ cext.SZOMB: _common.STATUS_ZOMBIE,
58
+ }
59
+
60
+ kinfo_proc_map = dict(
61
+ ppid=0,
62
+ ruid=1,
63
+ euid=2,
64
+ suid=3,
65
+ rgid=4,
66
+ egid=5,
67
+ sgid=6,
68
+ ttynr=7,
69
+ ctime=8,
70
+ status=9,
71
+ name=10,
72
+ )
73
+
74
+ pidtaskinfo_map = dict(
75
+ cpuutime=0,
76
+ cpustime=1,
77
+ rss=2,
78
+ vms=3,
79
+ pfaults=4,
80
+ pageins=5,
81
+ numthreads=6,
82
+ volctxsw=7,
83
+ )
84
+
85
+
86
+ # =====================================================================
87
+ # --- memory
88
+ # =====================================================================
89
+
90
+
91
+ def virtual_memory():
92
+ """System virtual memory as a namedtuple."""
93
+ total, active, inactive, wired, free, speculative = cext.virtual_mem()
94
+ # This is how Zabbix calculate avail and used mem:
95
+ # https://github.com/zabbix/zabbix/blob/master/src/libs/zbxsysinfo/osx/memory.c
96
+ # Also see: https://github.com/giampaolo/psutil/issues/1277
97
+ avail = inactive + free
98
+ used = active + wired
99
+ # This is NOT how Zabbix calculates free mem but it matches "free"
100
+ # cmdline utility.
101
+ free -= speculative
102
+ percent = usage_percent((total - avail), total, round_=1)
103
+ return ntp.svmem(
104
+ total, avail, percent, used, free, active, inactive, wired
105
+ )
106
+
107
+
108
+ def swap_memory():
109
+ """Swap system memory as a (total, used, free, sin, sout) tuple."""
110
+ total, used, free, sin, sout = cext.swap_mem()
111
+ percent = usage_percent(used, total, round_=1)
112
+ return ntp.sswap(total, used, free, percent, sin, sout)
113
+
114
+
115
+ # malloc / heap functions
116
+ heap_info = cext.heap_info
117
+ heap_trim = cext.heap_trim
118
+
119
+
120
+ # =====================================================================
121
+ # --- CPU
122
+ # =====================================================================
123
+
124
+
125
+ def cpu_times():
126
+ """Return system CPU times as a namedtuple."""
127
+ user, nice, system, idle = cext.cpu_times()
128
+ return ntp.scputimes(user, nice, system, idle)
129
+
130
+
131
+ def per_cpu_times():
132
+ """Return system CPU times as a named tuple."""
133
+ ret = []
134
+ for cpu_t in cext.per_cpu_times():
135
+ user, nice, system, idle = cpu_t
136
+ item = ntp.scputimes(user, nice, system, idle)
137
+ ret.append(item)
138
+ return ret
139
+
140
+
141
+ def cpu_count_logical():
142
+ """Return the number of logical CPUs in the system."""
143
+ return cext.cpu_count_logical()
144
+
145
+
146
+ def cpu_count_cores():
147
+ """Return the number of CPU cores in the system."""
148
+ return cext.cpu_count_cores()
149
+
150
+
151
+ def cpu_stats():
152
+ ctx_switches, interrupts, soft_interrupts, syscalls, _traps = (
153
+ cext.cpu_stats()
154
+ )
155
+ return ntp.scpustats(ctx_switches, interrupts, soft_interrupts, syscalls)
156
+
157
+
158
+ if cext.has_cpu_freq(): # not always available on ARM64
159
+
160
+ def cpu_freq():
161
+ """Return CPU frequency.
162
+ On macOS per-cpu frequency is not supported.
163
+ Also, the returned frequency never changes, see:
164
+ https://arstechnica.com/civis/viewtopic.php?f=19&t=465002.
165
+ """
166
+ curr, min_, max_ = cext.cpu_freq()
167
+ return [ntp.scpufreq(curr, min_, max_)]
168
+
169
+
170
+ # =====================================================================
171
+ # --- disks
172
+ # =====================================================================
173
+
174
+
175
+ disk_usage = _psposix.disk_usage
176
+ disk_io_counters = cext.disk_io_counters
177
+
178
+
179
+ def disk_partitions(all=False):
180
+ """Return mounted disk partitions as a list of namedtuples."""
181
+ retlist = []
182
+ partitions = cext.disk_partitions()
183
+ for partition in partitions:
184
+ device, mountpoint, fstype, opts = partition
185
+ if device == 'none':
186
+ device = ''
187
+ if not all:
188
+ if not os.path.isabs(device) or not os.path.exists(device):
189
+ continue
190
+ ntuple = ntp.sdiskpart(device, mountpoint, fstype, opts)
191
+ retlist.append(ntuple)
192
+ return retlist
193
+
194
+
195
+ # =====================================================================
196
+ # --- sensors
197
+ # =====================================================================
198
+
199
+
200
+ def sensors_battery():
201
+ """Return battery information."""
202
+ try:
203
+ percent, minsleft, power_plugged = cext.sensors_battery()
204
+ except NotImplementedError:
205
+ # no power source - return None according to interface
206
+ return None
207
+ power_plugged = power_plugged == 1
208
+ if power_plugged:
209
+ secsleft = _common.POWER_TIME_UNLIMITED
210
+ elif minsleft == -1:
211
+ secsleft = _common.POWER_TIME_UNKNOWN
212
+ else:
213
+ secsleft = minsleft * 60
214
+ return ntp.sbattery(percent, secsleft, power_plugged)
215
+
216
+
217
+ # =====================================================================
218
+ # --- network
219
+ # =====================================================================
220
+
221
+
222
+ net_io_counters = cext.net_io_counters
223
+ net_if_addrs = cext.net_if_addrs
224
+
225
+
226
+ def net_connections(kind='inet'):
227
+ """System-wide network connections."""
228
+ # Note: on macOS this will fail with AccessDenied unless
229
+ # the process is owned by root.
230
+ ret = []
231
+ for pid in pids():
232
+ try:
233
+ cons = Process(pid).net_connections(kind)
234
+ except NoSuchProcess:
235
+ continue
236
+ else:
237
+ if cons:
238
+ for c in cons:
239
+ c = list(c) + [pid]
240
+ ret.append(ntp.sconn(*c))
241
+ return ret
242
+
243
+
244
+ def net_if_stats():
245
+ """Get NIC stats (isup, duplex, speed, mtu)."""
246
+ names = net_io_counters().keys()
247
+ ret = {}
248
+ for name in names:
249
+ try:
250
+ mtu = cext.net_if_mtu(name)
251
+ flags = cext.net_if_flags(name)
252
+ duplex, speed = cext.net_if_duplex_speed(name)
253
+ except OSError as err:
254
+ # https://github.com/giampaolo/psutil/issues/1279
255
+ if err.errno != errno.ENODEV:
256
+ raise
257
+ else:
258
+ if hasattr(_common, 'NicDuplex'):
259
+ duplex = _common.NicDuplex(duplex)
260
+ output_flags = ','.join(flags)
261
+ isup = 'running' in flags
262
+ ret[name] = ntp.snicstats(isup, duplex, speed, mtu, output_flags)
263
+ return ret
264
+
265
+
266
+ # =====================================================================
267
+ # --- other system functions
268
+ # =====================================================================
269
+
270
+
271
+ def boot_time():
272
+ """The system boot time expressed in seconds since the epoch."""
273
+ return cext.boot_time()
274
+
275
+
276
+ try:
277
+ INIT_BOOT_TIME = boot_time()
278
+ except Exception as err: # noqa: BLE001
279
+ # Don't want to crash at import time.
280
+ debug(f"ignoring exception on import: {err!r}")
281
+ INIT_BOOT_TIME = 0
282
+
283
+
284
+ def adjust_proc_create_time(ctime):
285
+ """Account for system clock updates."""
286
+ if INIT_BOOT_TIME == 0:
287
+ return ctime
288
+
289
+ diff = INIT_BOOT_TIME - boot_time()
290
+ if diff == 0 or abs(diff) < 1:
291
+ return ctime
292
+
293
+ debug("system clock was updated; adjusting process create_time()")
294
+ if diff < 0:
295
+ return ctime - diff
296
+ return ctime + diff
297
+
298
+
299
+ def users():
300
+ """Return currently connected users as a list of namedtuples."""
301
+ retlist = []
302
+ rawlist = cext.users()
303
+ for item in rawlist:
304
+ user, tty, hostname, tstamp, pid = item
305
+ if tty == '~':
306
+ continue # reboot or shutdown
307
+ if not tstamp:
308
+ continue
309
+ nt = ntp.suser(user, tty or None, hostname or None, tstamp, pid)
310
+ retlist.append(nt)
311
+ return retlist
312
+
313
+
314
+ # =====================================================================
315
+ # --- processes
316
+ # =====================================================================
317
+
318
+
319
+ def pids():
320
+ ls = cext.pids()
321
+ if 0 not in ls:
322
+ # On certain macOS versions pids() C doesn't return PID 0 but
323
+ # "ps" does and the process is querable via sysctl():
324
+ # https://travis-ci.org/giampaolo/psutil/jobs/309619941
325
+ try:
326
+ Process(0).create_time()
327
+ ls.insert(0, 0)
328
+ except NoSuchProcess:
329
+ pass
330
+ except AccessDenied:
331
+ ls.insert(0, 0)
332
+ return ls
333
+
334
+
335
+ pid_exists = _psposix.pid_exists
336
+
337
+
338
+ def wrap_exceptions(fun):
339
+ """Decorator which translates bare OSError exceptions into
340
+ NoSuchProcess and AccessDenied.
341
+ """
342
+
343
+ @functools.wraps(fun)
344
+ def wrapper(self, *args, **kwargs):
345
+ pid, ppid, name = self.pid, self._ppid, self._name
346
+ try:
347
+ return fun(self, *args, **kwargs)
348
+ except ProcessLookupError as err:
349
+ if cext.proc_is_zombie(pid):
350
+ raise ZombieProcess(pid, name, ppid) from err
351
+ raise NoSuchProcess(pid, name) from err
352
+ except PermissionError as err:
353
+ raise AccessDenied(pid, name) from err
354
+ except cext.ZombieProcessError as err:
355
+ raise ZombieProcess(pid, name, ppid) from err
356
+
357
+ return wrapper
358
+
359
+
360
+ class Process:
361
+ """Wrapper class around underlying C implementation."""
362
+
363
+ __slots__ = ["_cache", "_name", "_ppid", "pid"]
364
+
365
+ def __init__(self, pid):
366
+ self.pid = pid
367
+ self._name = None
368
+ self._ppid = None
369
+
370
+ @wrap_exceptions
371
+ @memoize_when_activated
372
+ def _get_kinfo_proc(self):
373
+ # Note: should work with all PIDs without permission issues.
374
+ ret = cext.proc_kinfo_oneshot(self.pid)
375
+ assert len(ret) == len(kinfo_proc_map)
376
+ return ret
377
+
378
+ @wrap_exceptions
379
+ @memoize_when_activated
380
+ def _get_pidtaskinfo(self):
381
+ # Note: should work for PIDs owned by user only.
382
+ ret = cext.proc_pidtaskinfo_oneshot(self.pid)
383
+ assert len(ret) == len(pidtaskinfo_map)
384
+ return ret
385
+
386
+ def oneshot_enter(self):
387
+ self._get_kinfo_proc.cache_activate(self)
388
+ self._get_pidtaskinfo.cache_activate(self)
389
+
390
+ def oneshot_exit(self):
391
+ self._get_kinfo_proc.cache_deactivate(self)
392
+ self._get_pidtaskinfo.cache_deactivate(self)
393
+
394
+ @wrap_exceptions
395
+ def name(self):
396
+ name = self._get_kinfo_proc()[kinfo_proc_map['name']]
397
+ return name if name is not None else cext.proc_name(self.pid)
398
+
399
+ @wrap_exceptions
400
+ def exe(self):
401
+ return cext.proc_exe(self.pid)
402
+
403
+ @wrap_exceptions
404
+ def cmdline(self):
405
+ return cext.proc_cmdline(self.pid)
406
+
407
+ @wrap_exceptions
408
+ def environ(self):
409
+ return parse_environ_block(cext.proc_environ(self.pid))
410
+
411
+ @wrap_exceptions
412
+ def ppid(self):
413
+ self._ppid = self._get_kinfo_proc()[kinfo_proc_map['ppid']]
414
+ return self._ppid
415
+
416
+ @wrap_exceptions
417
+ def cwd(self):
418
+ return cext.proc_cwd(self.pid)
419
+
420
+ @wrap_exceptions
421
+ def uids(self):
422
+ rawtuple = self._get_kinfo_proc()
423
+ return ntp.puids(
424
+ rawtuple[kinfo_proc_map['ruid']],
425
+ rawtuple[kinfo_proc_map['euid']],
426
+ rawtuple[kinfo_proc_map['suid']],
427
+ )
428
+
429
+ @wrap_exceptions
430
+ def gids(self):
431
+ rawtuple = self._get_kinfo_proc()
432
+ return ntp.puids(
433
+ rawtuple[kinfo_proc_map['rgid']],
434
+ rawtuple[kinfo_proc_map['egid']],
435
+ rawtuple[kinfo_proc_map['sgid']],
436
+ )
437
+
438
+ @wrap_exceptions
439
+ def terminal(self):
440
+ tty_nr = self._get_kinfo_proc()[kinfo_proc_map['ttynr']]
441
+ tmap = _psposix.get_terminal_map()
442
+ try:
443
+ return tmap[tty_nr]
444
+ except KeyError:
445
+ return None
446
+
447
+ @wrap_exceptions
448
+ def memory_info(self):
449
+ rawtuple = self._get_pidtaskinfo()
450
+ return ntp.pmem(
451
+ rawtuple[pidtaskinfo_map['rss']],
452
+ rawtuple[pidtaskinfo_map['vms']],
453
+ rawtuple[pidtaskinfo_map['pfaults']],
454
+ rawtuple[pidtaskinfo_map['pageins']],
455
+ )
456
+
457
+ @wrap_exceptions
458
+ def memory_full_info(self):
459
+ basic_mem = self.memory_info()
460
+ uss = cext.proc_memory_uss(self.pid)
461
+ return ntp.pfullmem(*basic_mem + (uss,))
462
+
463
+ @wrap_exceptions
464
+ def cpu_times(self):
465
+ rawtuple = self._get_pidtaskinfo()
466
+ return ntp.pcputimes(
467
+ rawtuple[pidtaskinfo_map['cpuutime']],
468
+ rawtuple[pidtaskinfo_map['cpustime']],
469
+ # children user / system times are not retrievable (set to 0)
470
+ 0.0,
471
+ 0.0,
472
+ )
473
+
474
+ @wrap_exceptions
475
+ def create_time(self, monotonic=False):
476
+ ctime = self._get_kinfo_proc()[kinfo_proc_map['ctime']]
477
+ if not monotonic:
478
+ ctime = adjust_proc_create_time(ctime)
479
+ return ctime
480
+
481
+ @wrap_exceptions
482
+ def num_ctx_switches(self):
483
+ # Unvoluntary value seems not to be available;
484
+ # getrusage() numbers seems to confirm this theory.
485
+ # We set it to 0.
486
+ vol = self._get_pidtaskinfo()[pidtaskinfo_map['volctxsw']]
487
+ return ntp.pctxsw(vol, 0)
488
+
489
+ @wrap_exceptions
490
+ def num_threads(self):
491
+ return self._get_pidtaskinfo()[pidtaskinfo_map['numthreads']]
492
+
493
+ @wrap_exceptions
494
+ def open_files(self):
495
+ if self.pid == 0:
496
+ return []
497
+ files = []
498
+ rawlist = cext.proc_open_files(self.pid)
499
+ for path, fd in rawlist:
500
+ if isfile_strict(path):
501
+ ntuple = ntp.popenfile(path, fd)
502
+ files.append(ntuple)
503
+ return files
504
+
505
+ @wrap_exceptions
506
+ def net_connections(self, kind='inet'):
507
+ families, types = conn_tmap[kind]
508
+ rawlist = cext.proc_net_connections(self.pid, families, types)
509
+ ret = []
510
+ for item in rawlist:
511
+ fd, fam, type, laddr, raddr, status = item
512
+ nt = conn_to_ntuple(
513
+ fd, fam, type, laddr, raddr, status, TCP_STATUSES
514
+ )
515
+ ret.append(nt)
516
+ return ret
517
+
518
+ @wrap_exceptions
519
+ def num_fds(self):
520
+ if self.pid == 0:
521
+ return 0
522
+ return cext.proc_num_fds(self.pid)
523
+
524
+ @wrap_exceptions
525
+ def wait(self, timeout=None):
526
+ return _psposix.wait_pid(self.pid, timeout)
527
+
528
+ @wrap_exceptions
529
+ def nice_get(self):
530
+ return cext.proc_priority_get(self.pid)
531
+
532
+ @wrap_exceptions
533
+ def nice_set(self, value):
534
+ return cext.proc_priority_set(self.pid, value)
535
+
536
+ @wrap_exceptions
537
+ def status(self):
538
+ code = self._get_kinfo_proc()[kinfo_proc_map['status']]
539
+ # XXX is '?' legit? (we're not supposed to return it anyway)
540
+ return PROC_STATUSES.get(code, '?')
541
+
542
+ @wrap_exceptions
543
+ def threads(self):
544
+ rawlist = cext.proc_threads(self.pid)
545
+ retlist = []
546
+ for thread_id, utime, stime in rawlist:
547
+ ntuple = ntp.pthread(thread_id, utime, stime)
548
+ retlist.append(ntuple)
549
+ return retlist
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_psposix.py ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
2
+ # Use of this source code is governed by a BSD-style license that can be
3
+ # found in the LICENSE file.
4
+
5
+ """Routines common to all posix systems."""
6
+
7
+ import enum
8
+ import errno
9
+ import glob
10
+ import os
11
+ import select
12
+ import signal
13
+ import time
14
+
15
+ from . import _ntuples as ntp
16
+ from ._common import MACOS
17
+ from ._common import TimeoutExpired
18
+ from ._common import debug
19
+ from ._common import memoize
20
+ from ._common import usage_percent
21
+
22
+ if MACOS:
23
+ from . import _psutil_osx
24
+
25
+
26
+ __all__ = ['pid_exists', 'wait_pid', 'disk_usage', 'get_terminal_map']
27
+
28
+
29
+ def pid_exists(pid):
30
+ """Check whether pid exists in the current process table."""
31
+ if pid == 0:
32
+ # According to "man 2 kill" PID 0 has a special meaning:
33
+ # it refers to <<every process in the process group of the
34
+ # calling process>> so we don't want to go any further.
35
+ # If we get here it means this UNIX platform *does* have
36
+ # a process with id 0.
37
+ return True
38
+ try:
39
+ os.kill(pid, 0)
40
+ except ProcessLookupError:
41
+ return False
42
+ except PermissionError:
43
+ # EPERM clearly means there's a process to deny access to
44
+ return True
45
+ # According to "man 2 kill" possible error values are
46
+ # (EINVAL, EPERM, ESRCH)
47
+ else:
48
+ return True
49
+
50
+
51
+ Negsignal = enum.IntEnum(
52
+ 'Negsignal', {x.name: -x.value for x in signal.Signals}
53
+ )
54
+
55
+
56
+ def negsig_to_enum(num):
57
+ """Convert a negative signal value to an enum."""
58
+ try:
59
+ return Negsignal(num)
60
+ except ValueError:
61
+ return num
62
+
63
+
64
+ def convert_exit_code(status):
65
+ """Convert a os.waitpid() status to an exit code."""
66
+ if os.WIFEXITED(status):
67
+ # Process terminated normally by calling exit(3) or _exit(2),
68
+ # or by returning from main(). The return value is the
69
+ # positive integer passed to *exit().
70
+ return os.WEXITSTATUS(status)
71
+ if os.WIFSIGNALED(status):
72
+ # Process exited due to a signal. Return the negative value
73
+ # of that signal.
74
+ return negsig_to_enum(-os.WTERMSIG(status))
75
+ # if os.WIFSTOPPED(status):
76
+ # # Process was stopped via SIGSTOP or is being traced, and
77
+ # # waitpid() was called with WUNTRACED flag. PID is still
78
+ # # alive. From now on waitpid() will keep returning (0, 0)
79
+ # # until the process state doesn't change.
80
+ # # It may make sense to catch/enable this since stopped PIDs
81
+ # # ignore SIGTERM.
82
+ # interval = sleep(interval)
83
+ # continue
84
+ # if os.WIFCONTINUED(status):
85
+ # # Process was resumed via SIGCONT and waitpid() was called
86
+ # # with WCONTINUED flag.
87
+ # interval = sleep(interval)
88
+ # continue
89
+
90
+ # Should never happen.
91
+ msg = f"unknown process exit status {status!r}"
92
+ raise ValueError(msg)
93
+
94
+
95
+ def wait_pid_posix(
96
+ pid,
97
+ timeout=None,
98
+ _waitpid=os.waitpid,
99
+ _timer=getattr(time, 'monotonic', time.time), # noqa: B008
100
+ _min=min,
101
+ _sleep=time.sleep,
102
+ _pid_exists=pid_exists,
103
+ ):
104
+ """Wait for a process PID to terminate.
105
+
106
+ If the process terminated normally by calling exit(3) or _exit(2),
107
+ or by returning from main(), the return value is the positive integer
108
+ passed to *exit().
109
+
110
+ If it was terminated by a signal it returns the negated value of the
111
+ signal which caused the termination (e.g. -SIGTERM).
112
+
113
+ If PID is not a children of os.getpid() (current process) just
114
+ wait until the process disappears and return None.
115
+
116
+ If PID does not exist at all return None immediately.
117
+
118
+ If timeout is specified and process is still alive raise
119
+ TimeoutExpired.
120
+
121
+ If timeout=0 either return immediately or raise TimeoutExpired
122
+ (non-blocking).
123
+ """
124
+ interval = 0.0001
125
+ max_interval = 0.04
126
+ flags = 0
127
+ stop_at = None
128
+
129
+ if timeout is not None:
130
+ flags |= os.WNOHANG
131
+ if timeout != 0:
132
+ stop_at = _timer() + timeout
133
+
134
+ def sleep_or_timeout(interval):
135
+ # Sleep for some time and return a new increased interval.
136
+ if timeout == 0 or (stop_at is not None and _timer() >= stop_at):
137
+ raise TimeoutExpired(timeout)
138
+ _sleep(interval)
139
+ return _min(interval * 2, max_interval)
140
+
141
+ # See: https://linux.die.net/man/2/waitpid
142
+ while True:
143
+ try:
144
+ retpid, status = os.waitpid(pid, flags)
145
+ except ChildProcessError:
146
+ # This has two meanings:
147
+ # - PID is not a child of os.getpid() in which case
148
+ # we keep polling until it's gone
149
+ # - PID never existed in the first place
150
+ # In both cases we'll eventually return None as we
151
+ # can't determine its exit status code.
152
+ while _pid_exists(pid):
153
+ interval = sleep_or_timeout(interval)
154
+ return None
155
+ else:
156
+ if retpid == 0:
157
+ # WNOHANG flag was used and PID is still running.
158
+ interval = sleep_or_timeout(interval)
159
+ else:
160
+ return convert_exit_code(status)
161
+
162
+
163
+ def _waitpid(pid, timeout):
164
+ """Wrapper around os.waitpid(). PID is supposed to be gone already,
165
+ it just returns the exit code.
166
+ """
167
+ try:
168
+ retpid, status = os.waitpid(pid, 0)
169
+ except ChildProcessError:
170
+ # PID is not a child of os.getpid().
171
+ return wait_pid_posix(pid, timeout)
172
+ else:
173
+ assert retpid != 0
174
+ return convert_exit_code(status)
175
+
176
+
177
+ def wait_pid_pidfd_open(pid, timeout=None):
178
+ """Wait for PID to terminate using pidfd_open() + poll(). Linux >=
179
+ 5.3 + Python >= 3.9 only.
180
+ """
181
+ try:
182
+ pidfd = os.pidfd_open(pid, 0)
183
+ except OSError as err:
184
+ if err.errno == errno.ESRCH:
185
+ # No such process. os.waitpid() may still be able to return
186
+ # the status code.
187
+ return wait_pid_posix(pid, timeout)
188
+ if err.errno in {errno.EMFILE, errno.ENFILE, errno.ENODEV}:
189
+ # EMFILE, ENFILE: too many open files
190
+ # ENODEV: anonymous inode filesystem not supported
191
+ debug(f"pidfd_open() failed ({err!r}); use fallback")
192
+ return wait_pid_posix(pid, timeout)
193
+ raise
194
+
195
+ try:
196
+ # poll() / select() have the advantage of not requiring any
197
+ # extra file descriptor, contrary to epoll() / kqueue().
198
+ # select() crashes if process opens > 1024 FDs, so we use
199
+ # poll().
200
+ poller = select.poll()
201
+ poller.register(pidfd, select.POLLIN)
202
+ timeout_ms = None if timeout is None else int(timeout * 1000)
203
+ events = poller.poll(timeout_ms) # wait
204
+
205
+ if not events:
206
+ raise TimeoutExpired(timeout)
207
+ return _waitpid(pid, timeout)
208
+ finally:
209
+ os.close(pidfd)
210
+
211
+
212
+ def wait_pid_kqueue(pid, timeout=None):
213
+ """Wait for PID to terminate using kqueue(). macOS and BSD only."""
214
+ try:
215
+ kq = select.kqueue()
216
+ except OSError as err:
217
+ if err.errno in {errno.EMFILE, errno.ENFILE}: # too many open files
218
+ debug(f"kqueue() failed ({err!r}); use fallback")
219
+ return wait_pid_posix(pid, timeout)
220
+ raise
221
+
222
+ try:
223
+ kev = select.kevent(
224
+ pid,
225
+ filter=select.KQ_FILTER_PROC,
226
+ flags=select.KQ_EV_ADD | select.KQ_EV_ONESHOT,
227
+ fflags=select.KQ_NOTE_EXIT,
228
+ )
229
+ try:
230
+ events = kq.control([kev], 1, timeout) # wait
231
+ except OSError as err:
232
+ if err.errno in {errno.EACCES, errno.EPERM, errno.ESRCH}:
233
+ debug(f"kqueue.control() failed ({err!r}); use fallback")
234
+ return wait_pid_posix(pid, timeout)
235
+ raise
236
+ else:
237
+ if not events:
238
+ raise TimeoutExpired(timeout)
239
+ return _waitpid(pid, timeout)
240
+ finally:
241
+ kq.close()
242
+
243
+
244
+ @memoize
245
+ def can_use_pidfd_open():
246
+ # Availability: Linux >= 5.3, Python >= 3.9
247
+ if not hasattr(os, "pidfd_open"):
248
+ return False
249
+ try:
250
+ pidfd = os.pidfd_open(os.getpid(), 0)
251
+ except OSError as err:
252
+ if err.errno in {errno.EMFILE, errno.ENFILE}: # noqa: SIM103
253
+ # transitory 'too many open files'
254
+ return True
255
+ # likely blocked by security policy like SECCOMP (EPERM,
256
+ # EACCES, ENOSYS)
257
+ return False
258
+ else:
259
+ os.close(pidfd)
260
+ return True
261
+
262
+
263
+ @memoize
264
+ def can_use_kqueue():
265
+ # Availability: macOS, BSD
266
+ names = (
267
+ "kqueue",
268
+ "KQ_EV_ADD",
269
+ "KQ_EV_ONESHOT",
270
+ "KQ_FILTER_PROC",
271
+ "KQ_NOTE_EXIT",
272
+ )
273
+ if not all(hasattr(select, x) for x in names):
274
+ return False
275
+ kq = None
276
+ try:
277
+ kq = select.kqueue()
278
+ kev = select.kevent(
279
+ os.getpid(),
280
+ filter=select.KQ_FILTER_PROC,
281
+ flags=select.KQ_EV_ADD | select.KQ_EV_ONESHOT,
282
+ fflags=select.KQ_NOTE_EXIT,
283
+ )
284
+ kq.control([kev], 1, 0)
285
+ return True
286
+ except OSError as err:
287
+ if err.errno in {errno.EMFILE, errno.ENFILE}: # noqa: SIM103
288
+ # transitory 'too many open files'
289
+ return True
290
+ return False
291
+ finally:
292
+ if kq is not None:
293
+ kq.close()
294
+
295
+
296
+ def wait_pid(pid, timeout=None):
297
+ # PID 0 passed to waitpid() waits for any child of the current
298
+ # process to change state.
299
+ assert pid > 0
300
+ if timeout is not None:
301
+ assert timeout >= 0
302
+
303
+ if can_use_pidfd_open():
304
+ return wait_pid_pidfd_open(pid, timeout)
305
+ elif can_use_kqueue():
306
+ return wait_pid_kqueue(pid, timeout)
307
+ else:
308
+ return wait_pid_posix(pid, timeout)
309
+
310
+
311
+ wait_pid.__doc__ = wait_pid_posix.__doc__
312
+
313
+
314
+ def disk_usage(path):
315
+ """Return disk usage associated with path.
316
+ Note: UNIX usually reserves 5% disk space which is not accessible
317
+ by user. In this function "total" and "used" values reflect the
318
+ total and used disk space whereas "free" and "percent" represent
319
+ the "free" and "used percent" user disk space.
320
+ """
321
+ st = os.statvfs(path)
322
+ # Total space which is only available to root (unless changed
323
+ # at system level).
324
+ total = st.f_blocks * st.f_frsize
325
+ # Remaining free space usable by root.
326
+ avail_to_root = st.f_bfree * st.f_frsize
327
+ # Remaining free space usable by user.
328
+ avail_to_user = st.f_bavail * st.f_frsize
329
+ # Total space being used in general.
330
+ used = total - avail_to_root
331
+ if MACOS:
332
+ # see: https://github.com/giampaolo/psutil/pull/2152
333
+ used = _psutil_osx.disk_usage_used(path, used)
334
+ # Total space which is available to user (same as 'total' but
335
+ # for the user).
336
+ total_user = used + avail_to_user
337
+ # User usage percent compared to the total amount of space
338
+ # the user can use. This number would be higher if compared
339
+ # to root's because the user has less space (usually -5%).
340
+ usage_percent_user = usage_percent(used, total_user, round_=1)
341
+
342
+ # NB: the percentage is -5% than what shown by df due to
343
+ # reserved blocks that we are currently not considering:
344
+ # https://github.com/giampaolo/psutil/issues/829#issuecomment-223750462
345
+ return ntp.sdiskusage(
346
+ total=total, used=used, free=avail_to_user, percent=usage_percent_user
347
+ )
348
+
349
+
350
+ @memoize
351
+ def get_terminal_map():
352
+ """Get a map of device-id -> path as a dict.
353
+ Used by Process.terminal().
354
+ """
355
+ ret = {}
356
+ ls = glob.glob('/dev/tty*') + glob.glob('/dev/pts/*')
357
+ for name in ls:
358
+ assert name not in ret, name
359
+ try:
360
+ ret[os.stat(name).st_rdev] = name
361
+ except FileNotFoundError:
362
+ pass
363
+ return ret
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_pssunos.py ADDED
@@ -0,0 +1,704 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
2
+ # Use of this source code is governed by a BSD-style license that can be
3
+ # found in the LICENSE file.
4
+
5
+ """Sun OS Solaris platform implementation."""
6
+
7
+ import errno
8
+ import functools
9
+ import os
10
+ import socket
11
+ import subprocess
12
+ import sys
13
+ from collections import namedtuple
14
+ from socket import AF_INET
15
+
16
+ from . import _common
17
+ from . import _ntuples as ntp
18
+ from . import _psposix
19
+ from . import _psutil_sunos as cext
20
+ from ._common import AF_INET6
21
+ from ._common import ENCODING
22
+ from ._common import AccessDenied
23
+ from ._common import NoSuchProcess
24
+ from ._common import ZombieProcess
25
+ from ._common import debug
26
+ from ._common import get_procfs_path
27
+ from ._common import isfile_strict
28
+ from ._common import memoize_when_activated
29
+ from ._common import sockfam_to_enum
30
+ from ._common import socktype_to_enum
31
+ from ._common import usage_percent
32
+
33
+ __extra__all__ = ["CONN_IDLE", "CONN_BOUND", "PROCFS_PATH"]
34
+
35
+
36
+ # =====================================================================
37
+ # --- globals
38
+ # =====================================================================
39
+
40
+
41
+ PAGE_SIZE = cext.getpagesize()
42
+ AF_LINK = cext.AF_LINK
43
+ IS_64_BIT = sys.maxsize > 2**32
44
+
45
+ CONN_IDLE = "IDLE"
46
+ CONN_BOUND = "BOUND"
47
+
48
+ PROC_STATUSES = {
49
+ cext.SSLEEP: _common.STATUS_SLEEPING,
50
+ cext.SRUN: _common.STATUS_RUNNING,
51
+ cext.SZOMB: _common.STATUS_ZOMBIE,
52
+ cext.SSTOP: _common.STATUS_STOPPED,
53
+ cext.SIDL: _common.STATUS_IDLE,
54
+ cext.SONPROC: _common.STATUS_RUNNING, # same as run
55
+ cext.SWAIT: _common.STATUS_WAITING,
56
+ }
57
+
58
+ TCP_STATUSES = {
59
+ cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
60
+ cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
61
+ cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV,
62
+ cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
63
+ cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
64
+ cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
65
+ cext.TCPS_CLOSED: _common.CONN_CLOSE,
66
+ cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
67
+ cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
68
+ cext.TCPS_LISTEN: _common.CONN_LISTEN,
69
+ cext.TCPS_CLOSING: _common.CONN_CLOSING,
70
+ cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
71
+ cext.TCPS_IDLE: CONN_IDLE, # sunos specific
72
+ cext.TCPS_BOUND: CONN_BOUND, # sunos specific
73
+ }
74
+
75
+ proc_info_map = dict(
76
+ ppid=0,
77
+ rss=1,
78
+ vms=2,
79
+ create_time=3,
80
+ nice=4,
81
+ num_threads=5,
82
+ status=6,
83
+ ttynr=7,
84
+ uid=8,
85
+ euid=9,
86
+ gid=10,
87
+ egid=11,
88
+ )
89
+
90
+
91
+ # =====================================================================
92
+ # --- memory
93
+ # =====================================================================
94
+
95
+
96
+ def virtual_memory():
97
+ """Report virtual memory metrics."""
98
+ # we could have done this with kstat, but IMHO this is good enough
99
+ total = os.sysconf('SC_PHYS_PAGES') * PAGE_SIZE
100
+ # note: there's no difference on Solaris
101
+ free = avail = os.sysconf('SC_AVPHYS_PAGES') * PAGE_SIZE
102
+ used = total - free
103
+ percent = usage_percent(used, total, round_=1)
104
+ return ntp.svmem(total, avail, percent, used, free)
105
+
106
+
107
+ def swap_memory():
108
+ """Report swap memory metrics."""
109
+ sin, sout = cext.swap_mem()
110
+ # XXX
111
+ # we are supposed to get total/free by doing so:
112
+ # http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/
113
+ # usr/src/cmd/swap/swap.c
114
+ # ...nevertheless I can't manage to obtain the same numbers as 'swap'
115
+ # cmdline utility, so let's parse its output (sigh!)
116
+ p = subprocess.Popen(
117
+ [
118
+ '/usr/bin/env',
119
+ f"PATH=/usr/sbin:/sbin:{os.environ['PATH']}",
120
+ 'swap',
121
+ '-l',
122
+ ],
123
+ stdout=subprocess.PIPE,
124
+ )
125
+ stdout, _ = p.communicate()
126
+ stdout = stdout.decode(sys.stdout.encoding)
127
+ if p.returncode != 0:
128
+ msg = f"'swap -l' failed (retcode={p.returncode})"
129
+ raise RuntimeError(msg)
130
+
131
+ lines = stdout.strip().split('\n')[1:]
132
+ if not lines:
133
+ msg = 'no swap device(s) configured'
134
+ raise RuntimeError(msg)
135
+ total = free = 0
136
+ for line in lines:
137
+ line = line.split()
138
+ t, f = line[3:5]
139
+ total += int(int(t) * 512)
140
+ free += int(int(f) * 512)
141
+ used = total - free
142
+ percent = usage_percent(used, total, round_=1)
143
+ return ntp.sswap(
144
+ total, used, free, percent, sin * PAGE_SIZE, sout * PAGE_SIZE
145
+ )
146
+
147
+
148
+ # =====================================================================
149
+ # --- CPU
150
+ # =====================================================================
151
+
152
+
153
+ def cpu_times():
154
+ """Return system-wide CPU times as a named tuple."""
155
+ ret = cext.per_cpu_times()
156
+ return ntp.scputimes(*[sum(x) for x in zip(*ret)])
157
+
158
+
159
+ def per_cpu_times():
160
+ """Return system per-CPU times as a list of named tuples."""
161
+ ret = cext.per_cpu_times()
162
+ return [ntp.scputimes(*x) for x in ret]
163
+
164
+
165
+ def cpu_count_logical():
166
+ """Return the number of logical CPUs in the system."""
167
+ try:
168
+ return os.sysconf("SC_NPROCESSORS_ONLN")
169
+ except ValueError:
170
+ # mimic os.cpu_count() behavior
171
+ return None
172
+
173
+
174
+ def cpu_count_cores():
175
+ """Return the number of CPU cores in the system."""
176
+ return cext.cpu_count_cores()
177
+
178
+
179
+ def cpu_stats():
180
+ """Return various CPU stats as a named tuple."""
181
+ ctx_switches, interrupts, syscalls, _traps = cext.cpu_stats()
182
+ soft_interrupts = 0
183
+ return ntp.scpustats(ctx_switches, interrupts, soft_interrupts, syscalls)
184
+
185
+
186
+ # =====================================================================
187
+ # --- disks
188
+ # =====================================================================
189
+
190
+
191
+ disk_io_counters = cext.disk_io_counters
192
+ disk_usage = _psposix.disk_usage
193
+
194
+
195
+ def disk_partitions(all=False):
196
+ """Return system disk partitions."""
197
+ # TODO - the filtering logic should be better checked so that
198
+ # it tries to reflect 'df' as much as possible
199
+ retlist = []
200
+ partitions = cext.disk_partitions()
201
+ for partition in partitions:
202
+ device, mountpoint, fstype, opts = partition
203
+ if device == 'none':
204
+ device = ''
205
+ if not all:
206
+ # Differently from, say, Linux, we don't have a list of
207
+ # common fs types so the best we can do, AFAIK, is to
208
+ # filter by filesystem having a total size > 0.
209
+ try:
210
+ if not disk_usage(mountpoint).total:
211
+ continue
212
+ except OSError as err:
213
+ # https://github.com/giampaolo/psutil/issues/1674
214
+ debug(f"skipping {mountpoint!r}: {err}")
215
+ continue
216
+ ntuple = ntp.sdiskpart(device, mountpoint, fstype, opts)
217
+ retlist.append(ntuple)
218
+ return retlist
219
+
220
+
221
+ # =====================================================================
222
+ # --- network
223
+ # =====================================================================
224
+
225
+
226
+ net_io_counters = cext.net_io_counters
227
+ net_if_addrs = cext.net_if_addrs
228
+
229
+
230
+ def net_connections(kind, _pid=-1):
231
+ """Return socket connections. If pid == -1 return system-wide
232
+ connections (as opposed to connections opened by one process only).
233
+ Only INET sockets are returned (UNIX are not).
234
+ """
235
+ families, types = _common.conn_tmap[kind]
236
+ rawlist = cext.net_connections(_pid)
237
+ ret = set()
238
+ for item in rawlist:
239
+ fd, fam, type_, laddr, raddr, status, pid = item
240
+ if fam not in families:
241
+ continue
242
+ if type_ not in types:
243
+ continue
244
+ # TODO: refactor and use _common.conn_to_ntuple.
245
+ if fam in {AF_INET, AF_INET6}:
246
+ if laddr:
247
+ laddr = ntp.addr(*laddr)
248
+ if raddr:
249
+ raddr = ntp.addr(*raddr)
250
+ status = TCP_STATUSES[status]
251
+ fam = sockfam_to_enum(fam)
252
+ type_ = socktype_to_enum(type_)
253
+ if _pid == -1:
254
+ nt = ntp.sconn(fd, fam, type_, laddr, raddr, status, pid)
255
+ else:
256
+ nt = ntp.pconn(fd, fam, type_, laddr, raddr, status)
257
+ ret.add(nt)
258
+ return list(ret)
259
+
260
+
261
+ def net_if_stats():
262
+ """Get NIC stats (isup, duplex, speed, mtu)."""
263
+ ret = cext.net_if_stats()
264
+ for name, items in ret.items():
265
+ isup, duplex, speed, mtu = items
266
+ if hasattr(_common, 'NicDuplex'):
267
+ duplex = _common.NicDuplex(duplex)
268
+ ret[name] = ntp.snicstats(isup, duplex, speed, mtu, '')
269
+ return ret
270
+
271
+
272
+ # =====================================================================
273
+ # --- other system functions
274
+ # =====================================================================
275
+
276
+
277
+ def boot_time():
278
+ """The system boot time expressed in seconds since the epoch."""
279
+ return cext.boot_time()
280
+
281
+
282
+ def users():
283
+ """Return currently connected users as a list of namedtuples."""
284
+ retlist = []
285
+ rawlist = cext.users()
286
+ localhost = (':0.0', ':0')
287
+ for item in rawlist:
288
+ user, tty, hostname, tstamp, user_process, pid = item
289
+ # note: the underlying C function includes entries about
290
+ # system boot, run level and others. We might want
291
+ # to use them in the future.
292
+ if not user_process:
293
+ continue
294
+ if hostname in localhost:
295
+ hostname = 'localhost'
296
+ nt = ntp.suser(user, tty, hostname, tstamp, pid)
297
+ retlist.append(nt)
298
+ return retlist
299
+
300
+
301
+ # =====================================================================
302
+ # --- processes
303
+ # =====================================================================
304
+
305
+
306
+ def pids():
307
+ """Returns a list of PIDs currently running on the system."""
308
+ path = get_procfs_path().encode(ENCODING)
309
+ return [int(x) for x in os.listdir(path) if x.isdigit()]
310
+
311
+
312
+ def pid_exists(pid):
313
+ """Check for the existence of a unix pid."""
314
+ return _psposix.pid_exists(pid)
315
+
316
+
317
+ def wrap_exceptions(fun):
318
+ """Call callable into a try/except clause and translate ENOENT,
319
+ EACCES and EPERM in NoSuchProcess or AccessDenied exceptions.
320
+ """
321
+
322
+ @functools.wraps(fun)
323
+ def wrapper(self, *args, **kwargs):
324
+ pid, ppid, name = self.pid, self._ppid, self._name
325
+ try:
326
+ return fun(self, *args, **kwargs)
327
+ except (FileNotFoundError, ProcessLookupError) as err:
328
+ # ENOENT (no such file or directory) gets raised on open().
329
+ # ESRCH (no such process) can get raised on read() if
330
+ # process is gone in meantime.
331
+ if not pid_exists(pid):
332
+ raise NoSuchProcess(pid, name) from err
333
+ raise ZombieProcess(pid, name, ppid) from err
334
+ except PermissionError as err:
335
+ raise AccessDenied(pid, name) from err
336
+ except OSError as err:
337
+ if pid == 0:
338
+ if 0 in pids():
339
+ raise AccessDenied(pid, name) from err
340
+ raise
341
+ raise
342
+
343
+ return wrapper
344
+
345
+
346
+ class Process:
347
+ """Wrapper class around underlying C implementation."""
348
+
349
+ __slots__ = ["_cache", "_name", "_ppid", "_procfs_path", "pid"]
350
+
351
+ def __init__(self, pid):
352
+ self.pid = pid
353
+ self._name = None
354
+ self._ppid = None
355
+ self._procfs_path = get_procfs_path()
356
+
357
+ def _assert_alive(self):
358
+ """Raise NSP if the process disappeared on us."""
359
+ # For those C function who do not raise NSP, possibly returning
360
+ # incorrect or incomplete result.
361
+ os.stat(f"{self._procfs_path}/{self.pid}")
362
+
363
+ def oneshot_enter(self):
364
+ self._proc_name_and_args.cache_activate(self)
365
+ self._proc_basic_info.cache_activate(self)
366
+ self._proc_cred.cache_activate(self)
367
+
368
+ def oneshot_exit(self):
369
+ self._proc_name_and_args.cache_deactivate(self)
370
+ self._proc_basic_info.cache_deactivate(self)
371
+ self._proc_cred.cache_deactivate(self)
372
+
373
+ @wrap_exceptions
374
+ @memoize_when_activated
375
+ def _proc_name_and_args(self):
376
+ return cext.proc_name_and_args(self.pid, self._procfs_path)
377
+
378
+ @wrap_exceptions
379
+ @memoize_when_activated
380
+ def _proc_basic_info(self):
381
+ if self.pid == 0 and not os.path.exists(
382
+ f"{self._procfs_path}/{self.pid}/psinfo"
383
+ ):
384
+ raise AccessDenied(self.pid)
385
+ ret = cext.proc_basic_info(self.pid, self._procfs_path)
386
+ assert len(ret) == len(proc_info_map)
387
+ return ret
388
+
389
+ @wrap_exceptions
390
+ @memoize_when_activated
391
+ def _proc_cred(self):
392
+ return cext.proc_cred(self.pid, self._procfs_path)
393
+
394
+ @wrap_exceptions
395
+ def name(self):
396
+ # note: max len == 15
397
+ return self._proc_name_and_args()[0]
398
+
399
+ @wrap_exceptions
400
+ def exe(self):
401
+ try:
402
+ return os.readlink(f"{self._procfs_path}/{self.pid}/path/a.out")
403
+ except OSError:
404
+ pass # continue and guess the exe name from the cmdline
405
+ # Will be guessed later from cmdline but we want to explicitly
406
+ # invoke cmdline here in order to get an AccessDenied
407
+ # exception if the user has not enough privileges.
408
+ self.cmdline()
409
+ return ""
410
+
411
+ @wrap_exceptions
412
+ def cmdline(self):
413
+ return self._proc_name_and_args()[1]
414
+
415
+ @wrap_exceptions
416
+ def environ(self):
417
+ return cext.proc_environ(self.pid, self._procfs_path)
418
+
419
+ @wrap_exceptions
420
+ def create_time(self):
421
+ return self._proc_basic_info()[proc_info_map['create_time']]
422
+
423
+ @wrap_exceptions
424
+ def num_threads(self):
425
+ return self._proc_basic_info()[proc_info_map['num_threads']]
426
+
427
+ @wrap_exceptions
428
+ def nice_get(self):
429
+ # Note #1: getpriority(3) doesn't work for realtime processes.
430
+ # Psinfo is what ps uses, see:
431
+ # https://github.com/giampaolo/psutil/issues/1194
432
+ return self._proc_basic_info()[proc_info_map['nice']]
433
+
434
+ @wrap_exceptions
435
+ def nice_set(self, value):
436
+ if self.pid in {2, 3}:
437
+ # Special case PIDs: internally setpriority(3) return ESRCH
438
+ # (no such process), no matter what.
439
+ # The process actually exists though, as it has a name,
440
+ # creation time, etc.
441
+ raise AccessDenied(self.pid, self._name)
442
+ return cext.proc_priority_set(self.pid, value)
443
+
444
+ @wrap_exceptions
445
+ def ppid(self):
446
+ self._ppid = self._proc_basic_info()[proc_info_map['ppid']]
447
+ return self._ppid
448
+
449
+ @wrap_exceptions
450
+ def uids(self):
451
+ try:
452
+ real, effective, saved, _, _, _ = self._proc_cred()
453
+ except AccessDenied:
454
+ real = self._proc_basic_info()[proc_info_map['uid']]
455
+ effective = self._proc_basic_info()[proc_info_map['euid']]
456
+ saved = None
457
+ return ntp.puids(real, effective, saved)
458
+
459
+ @wrap_exceptions
460
+ def gids(self):
461
+ try:
462
+ _, _, _, real, effective, saved = self._proc_cred()
463
+ except AccessDenied:
464
+ real = self._proc_basic_info()[proc_info_map['gid']]
465
+ effective = self._proc_basic_info()[proc_info_map['egid']]
466
+ saved = None
467
+ return ntp.puids(real, effective, saved)
468
+
469
+ @wrap_exceptions
470
+ def cpu_times(self):
471
+ try:
472
+ times = cext.proc_cpu_times(self.pid, self._procfs_path)
473
+ except OSError as err:
474
+ if err.errno == errno.EOVERFLOW and not IS_64_BIT:
475
+ # We may get here if we attempt to query a 64bit process
476
+ # with a 32bit python.
477
+ # Error originates from read() and also tools like "cat"
478
+ # fail in the same way (!).
479
+ # Since there simply is no way to determine CPU times we
480
+ # return 0.0 as a fallback. See:
481
+ # https://github.com/giampaolo/psutil/issues/857
482
+ times = (0.0, 0.0, 0.0, 0.0)
483
+ else:
484
+ raise
485
+ return ntp.pcputimes(*times)
486
+
487
+ @wrap_exceptions
488
+ def cpu_num(self):
489
+ return cext.proc_cpu_num(self.pid, self._procfs_path)
490
+
491
+ @wrap_exceptions
492
+ def terminal(self):
493
+ procfs_path = self._procfs_path
494
+ hit_enoent = False
495
+ tty = wrap_exceptions(self._proc_basic_info()[proc_info_map['ttynr']])
496
+ if tty != cext.PRNODEV:
497
+ for x in (0, 1, 2, 255):
498
+ try:
499
+ return os.readlink(f"{procfs_path}/{self.pid}/path/{x}")
500
+ except FileNotFoundError:
501
+ hit_enoent = True
502
+ continue
503
+ if hit_enoent:
504
+ self._assert_alive()
505
+
506
+ @wrap_exceptions
507
+ def cwd(self):
508
+ # /proc/PID/path/cwd may not be resolved by readlink() even if
509
+ # it exists (ls shows it). If that's the case and the process
510
+ # is still alive return None (we can return None also on BSD).
511
+ # Reference: https://groups.google.com/g/comp.unix.solaris/c/tcqvhTNFCAs
512
+ procfs_path = self._procfs_path
513
+ try:
514
+ return os.readlink(f"{procfs_path}/{self.pid}/path/cwd")
515
+ except FileNotFoundError:
516
+ os.stat(f"{procfs_path}/{self.pid}") # raise NSP or AD
517
+ return ""
518
+
519
+ @wrap_exceptions
520
+ def memory_info(self):
521
+ ret = self._proc_basic_info()
522
+ rss = ret[proc_info_map['rss']] * 1024
523
+ vms = ret[proc_info_map['vms']] * 1024
524
+ return ntp.pmem(rss, vms)
525
+
526
+ memory_full_info = memory_info
527
+
528
+ @wrap_exceptions
529
+ def status(self):
530
+ code = self._proc_basic_info()[proc_info_map['status']]
531
+ # XXX is '?' legit? (we're not supposed to return it anyway)
532
+ return PROC_STATUSES.get(code, '?')
533
+
534
+ @wrap_exceptions
535
+ def threads(self):
536
+ procfs_path = self._procfs_path
537
+ ret = []
538
+ tids = os.listdir(f"{procfs_path}/{self.pid}/lwp")
539
+ hit_enoent = False
540
+ for tid in tids:
541
+ tid = int(tid)
542
+ try:
543
+ utime, stime = cext.query_process_thread(
544
+ self.pid, tid, procfs_path
545
+ )
546
+ except OSError as err:
547
+ if err.errno == errno.EOVERFLOW and not IS_64_BIT:
548
+ # We may get here if we attempt to query a 64bit process
549
+ # with a 32bit python.
550
+ # Error originates from read() and also tools like "cat"
551
+ # fail in the same way (!).
552
+ # Since there simply is no way to determine CPU times we
553
+ # return 0.0 as a fallback. See:
554
+ # https://github.com/giampaolo/psutil/issues/857
555
+ continue
556
+ # ENOENT == thread gone in meantime
557
+ if err.errno == errno.ENOENT:
558
+ hit_enoent = True
559
+ continue
560
+ raise
561
+ else:
562
+ nt = ntp.pthread(tid, utime, stime)
563
+ ret.append(nt)
564
+ if hit_enoent:
565
+ self._assert_alive()
566
+ return ret
567
+
568
+ @wrap_exceptions
569
+ def open_files(self):
570
+ retlist = []
571
+ hit_enoent = False
572
+ procfs_path = self._procfs_path
573
+ pathdir = f"{procfs_path}/{self.pid}/path"
574
+ for fd in os.listdir(f"{procfs_path}/{self.pid}/fd"):
575
+ path = os.path.join(pathdir, fd)
576
+ if os.path.islink(path):
577
+ try:
578
+ file = os.readlink(path)
579
+ except FileNotFoundError:
580
+ hit_enoent = True
581
+ continue
582
+ else:
583
+ if isfile_strict(file):
584
+ retlist.append(ntp.popenfile(file, int(fd)))
585
+ if hit_enoent:
586
+ self._assert_alive()
587
+ return retlist
588
+
589
+ def _get_unix_sockets(self, pid):
590
+ """Get UNIX sockets used by process by parsing 'pfiles' output."""
591
+ # TODO: rewrite this in C (...but the damn netstat source code
592
+ # does not include this part! Argh!!)
593
+ cmd = ["pfiles", str(pid)]
594
+ p = subprocess.Popen(
595
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
596
+ )
597
+ stdout, stderr = p.communicate()
598
+ stdout, stderr = (
599
+ x.decode(sys.stdout.encoding) for x in (stdout, stderr)
600
+ )
601
+ if p.returncode != 0:
602
+ if 'permission denied' in stderr.lower():
603
+ raise AccessDenied(self.pid, self._name)
604
+ if 'no such process' in stderr.lower():
605
+ raise NoSuchProcess(self.pid, self._name)
606
+ msg = f"{cmd!r} command error\n{stderr}"
607
+ raise RuntimeError(msg)
608
+
609
+ lines = stdout.split('\n')[2:]
610
+ for i, line in enumerate(lines):
611
+ line = line.lstrip()
612
+ if line.startswith('sockname: AF_UNIX'):
613
+ path = line.split(' ', 2)[2]
614
+ type = lines[i - 2].strip()
615
+ if type == 'SOCK_STREAM':
616
+ type = socket.SOCK_STREAM
617
+ elif type == 'SOCK_DGRAM':
618
+ type = socket.SOCK_DGRAM
619
+ else:
620
+ type = -1
621
+ yield (-1, socket.AF_UNIX, type, path, "", _common.CONN_NONE)
622
+
623
+ @wrap_exceptions
624
+ def net_connections(self, kind='inet'):
625
+ ret = net_connections(kind, _pid=self.pid)
626
+ # The underlying C implementation retrieves all OS connections
627
+ # and filters them by PID. At this point we can't tell whether
628
+ # an empty list means there were no connections for process or
629
+ # process is no longer active so we force NSP in case the PID
630
+ # is no longer there.
631
+ if not ret:
632
+ # will raise NSP if process is gone
633
+ os.stat(f"{self._procfs_path}/{self.pid}")
634
+
635
+ # UNIX sockets
636
+ if kind in {'all', 'unix'}:
637
+ ret.extend(
638
+ [ntp.pconn(*conn) for conn in self._get_unix_sockets(self.pid)]
639
+ )
640
+ return ret
641
+
642
+ nt_mmap_grouped = namedtuple('mmap', 'path rss anon locked')
643
+ nt_mmap_ext = namedtuple('mmap', 'addr perms path rss anon locked')
644
+
645
+ @wrap_exceptions
646
+ def memory_maps(self):
647
+ def toaddr(start, end):
648
+ return "{}-{}".format(
649
+ hex(start)[2:].strip('L'), hex(end)[2:].strip('L')
650
+ )
651
+
652
+ procfs_path = self._procfs_path
653
+ retlist = []
654
+ try:
655
+ rawlist = cext.proc_memory_maps(self.pid, procfs_path)
656
+ except OSError as err:
657
+ if err.errno == errno.EOVERFLOW and not IS_64_BIT:
658
+ # We may get here if we attempt to query a 64bit process
659
+ # with a 32bit python.
660
+ # Error originates from read() and also tools like "cat"
661
+ # fail in the same way (!).
662
+ # Since there simply is no way to determine CPU times we
663
+ # return 0.0 as a fallback. See:
664
+ # https://github.com/giampaolo/psutil/issues/857
665
+ return []
666
+ else:
667
+ raise
668
+ hit_enoent = False
669
+ for item in rawlist:
670
+ addr, addrsize, perm, name, rss, anon, locked = item
671
+ addr = toaddr(addr, addrsize)
672
+ if not name.startswith('['):
673
+ try:
674
+ name = os.readlink(f"{procfs_path}/{self.pid}/path/{name}")
675
+ except OSError as err:
676
+ if err.errno == errno.ENOENT:
677
+ # sometimes the link may not be resolved by
678
+ # readlink() even if it exists (ls shows it).
679
+ # If that's the case we just return the
680
+ # unresolved link path.
681
+ # This seems an inconsistency with /proc similar
682
+ # to: http://goo.gl/55XgO
683
+ name = f"{procfs_path}/{self.pid}/path/{name}"
684
+ hit_enoent = True
685
+ else:
686
+ raise
687
+ retlist.append((addr, perm, name, rss, anon, locked))
688
+ if hit_enoent:
689
+ self._assert_alive()
690
+ return retlist
691
+
692
+ @wrap_exceptions
693
+ def num_fds(self):
694
+ return len(os.listdir(f"{self._procfs_path}/{self.pid}/fd"))
695
+
696
+ @wrap_exceptions
697
+ def num_ctx_switches(self):
698
+ return ntp.pctxsw(
699
+ *cext.proc_num_ctx_switches(self.pid, self._procfs_path)
700
+ )
701
+
702
+ @wrap_exceptions
703
+ def wait(self, timeout=None):
704
+ return _psposix.wait_pid(self.pid, timeout)
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/psutil/_pswindows.py ADDED
@@ -0,0 +1,1096 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
2
+ # Use of this source code is governed by a BSD-style license that can be
3
+ # found in the LICENSE file.
4
+
5
+ """Windows platform implementation."""
6
+
7
+ import contextlib
8
+ import enum
9
+ import functools
10
+ import os
11
+ import signal
12
+ import sys
13
+ import threading
14
+ import time
15
+
16
+ from . import _common
17
+ from . import _ntuples as ntp
18
+ from ._common import ENCODING
19
+ from ._common import AccessDenied
20
+ from ._common import NoSuchProcess
21
+ from ._common import TimeoutExpired
22
+ from ._common import conn_tmap
23
+ from ._common import conn_to_ntuple
24
+ from ._common import debug
25
+ from ._common import isfile_strict
26
+ from ._common import memoize
27
+ from ._common import memoize_when_activated
28
+ from ._common import parse_environ_block
29
+ from ._common import usage_percent
30
+ from ._psutil_windows import ABOVE_NORMAL_PRIORITY_CLASS
31
+ from ._psutil_windows import BELOW_NORMAL_PRIORITY_CLASS
32
+ from ._psutil_windows import HIGH_PRIORITY_CLASS
33
+ from ._psutil_windows import IDLE_PRIORITY_CLASS
34
+ from ._psutil_windows import NORMAL_PRIORITY_CLASS
35
+ from ._psutil_windows import REALTIME_PRIORITY_CLASS
36
+
37
+ try:
38
+ from . import _psutil_windows as cext
39
+ except ImportError as err:
40
+ if (
41
+ str(err).lower().startswith("dll load failed")
42
+ and sys.getwindowsversion()[0] < 6
43
+ ):
44
+ # We may get here if:
45
+ # 1) we are on an old Windows version
46
+ # 2) psutil was installed via pip + wheel
47
+ # See: https://github.com/giampaolo/psutil/issues/811
48
+ msg = "this Windows version is too old (< Windows Vista); "
49
+ msg += "psutil 3.4.2 is the latest version which supports Windows "
50
+ msg += "2000, XP and 2003 server"
51
+ raise RuntimeError(msg) from err
52
+ else:
53
+ raise
54
+
55
+
56
+ # process priority constants, import from __init__.py:
57
+ # http://msdn.microsoft.com/en-us/library/ms686219(v=vs.85).aspx
58
+ # fmt: off
59
+ __extra__all__ = [
60
+ "win_service_iter", "win_service_get",
61
+ # Process priority
62
+ "ABOVE_NORMAL_PRIORITY_CLASS", "BELOW_NORMAL_PRIORITY_CLASS",
63
+ "HIGH_PRIORITY_CLASS", "IDLE_PRIORITY_CLASS", "NORMAL_PRIORITY_CLASS",
64
+ "REALTIME_PRIORITY_CLASS",
65
+ # IO priority
66
+ "IOPRIO_VERYLOW", "IOPRIO_LOW", "IOPRIO_NORMAL", "IOPRIO_HIGH",
67
+ # others
68
+ "CONN_DELETE_TCB", "AF_LINK",
69
+ ]
70
+ # fmt: on
71
+
72
+
73
+ # =====================================================================
74
+ # --- globals
75
+ # =====================================================================
76
+
77
+ CONN_DELETE_TCB = "DELETE_TCB"
78
+ ERROR_PARTIAL_COPY = 299
79
+ PYPY = '__pypy__' in sys.builtin_module_names
80
+
81
+ AddressFamily = enum.IntEnum('AddressFamily', {'AF_LINK': -1})
82
+ AF_LINK = AddressFamily.AF_LINK
83
+
84
+ TCP_STATUSES = {
85
+ cext.MIB_TCP_STATE_ESTAB: _common.CONN_ESTABLISHED,
86
+ cext.MIB_TCP_STATE_SYN_SENT: _common.CONN_SYN_SENT,
87
+ cext.MIB_TCP_STATE_SYN_RCVD: _common.CONN_SYN_RECV,
88
+ cext.MIB_TCP_STATE_FIN_WAIT1: _common.CONN_FIN_WAIT1,
89
+ cext.MIB_TCP_STATE_FIN_WAIT2: _common.CONN_FIN_WAIT2,
90
+ cext.MIB_TCP_STATE_TIME_WAIT: _common.CONN_TIME_WAIT,
91
+ cext.MIB_TCP_STATE_CLOSED: _common.CONN_CLOSE,
92
+ cext.MIB_TCP_STATE_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
93
+ cext.MIB_TCP_STATE_LAST_ACK: _common.CONN_LAST_ACK,
94
+ cext.MIB_TCP_STATE_LISTEN: _common.CONN_LISTEN,
95
+ cext.MIB_TCP_STATE_CLOSING: _common.CONN_CLOSING,
96
+ cext.MIB_TCP_STATE_DELETE_TCB: CONN_DELETE_TCB,
97
+ cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
98
+ }
99
+
100
+
101
+ class Priority(enum.IntEnum):
102
+ ABOVE_NORMAL_PRIORITY_CLASS = ABOVE_NORMAL_PRIORITY_CLASS
103
+ BELOW_NORMAL_PRIORITY_CLASS = BELOW_NORMAL_PRIORITY_CLASS
104
+ HIGH_PRIORITY_CLASS = HIGH_PRIORITY_CLASS
105
+ IDLE_PRIORITY_CLASS = IDLE_PRIORITY_CLASS
106
+ NORMAL_PRIORITY_CLASS = NORMAL_PRIORITY_CLASS
107
+ REALTIME_PRIORITY_CLASS = REALTIME_PRIORITY_CLASS
108
+
109
+
110
+ globals().update(Priority.__members__)
111
+
112
+
113
+ class IOPriority(enum.IntEnum):
114
+ IOPRIO_VERYLOW = 0
115
+ IOPRIO_LOW = 1
116
+ IOPRIO_NORMAL = 2
117
+ IOPRIO_HIGH = 3
118
+
119
+
120
+ globals().update(IOPriority.__members__)
121
+
122
+ pinfo_map = dict(
123
+ num_handles=0,
124
+ ctx_switches=1,
125
+ user_time=2,
126
+ kernel_time=3,
127
+ create_time=4,
128
+ num_threads=5,
129
+ io_rcount=6,
130
+ io_wcount=7,
131
+ io_rbytes=8,
132
+ io_wbytes=9,
133
+ io_count_others=10,
134
+ io_bytes_others=11,
135
+ num_page_faults=12,
136
+ peak_wset=13,
137
+ wset=14,
138
+ peak_paged_pool=15,
139
+ paged_pool=16,
140
+ peak_non_paged_pool=17,
141
+ non_paged_pool=18,
142
+ pagefile=19,
143
+ peak_pagefile=20,
144
+ mem_private=21,
145
+ )
146
+
147
+
148
+ # =====================================================================
149
+ # --- utils
150
+ # =====================================================================
151
+
152
+
153
+ @functools.lru_cache(maxsize=512)
154
+ def convert_dos_path(s):
155
+ r"""Convert paths using native DOS format like:
156
+ "\Device\HarddiskVolume1\Windows\systemew\file.txt" or
157
+ "\??\C:\Windows\systemew\file.txt"
158
+ into:
159
+ "C:\Windows\systemew\file.txt".
160
+ """
161
+ if s.startswith('\\\\'):
162
+ return s
163
+ rawdrive = '\\'.join(s.split('\\')[:3])
164
+ if rawdrive in {"\\??\\UNC", "\\Device\\Mup"}:
165
+ rawdrive = '\\'.join(s.split('\\')[:5])
166
+ driveletter = '\\\\' + '\\'.join(s.split('\\')[3:5])
167
+ elif rawdrive.startswith('\\??\\'):
168
+ driveletter = s.split('\\')[2]
169
+ else:
170
+ driveletter = cext.QueryDosDevice(rawdrive)
171
+ remainder = s[len(rawdrive) :]
172
+ return os.path.join(driveletter, remainder)
173
+
174
+
175
+ @memoize
176
+ def getpagesize():
177
+ return cext.getpagesize()
178
+
179
+
180
+ # =====================================================================
181
+ # --- memory
182
+ # =====================================================================
183
+
184
+
185
+ def virtual_memory():
186
+ """System virtual memory as a namedtuple."""
187
+ mem = cext.virtual_mem()
188
+ totphys, availphys, _totsys, _availsys = mem
189
+ total = totphys
190
+ avail = availphys
191
+ free = availphys
192
+ used = total - avail
193
+ percent = usage_percent((total - avail), total, round_=1)
194
+ return ntp.svmem(total, avail, percent, used, free)
195
+
196
+
197
+ def swap_memory():
198
+ """Swap system memory as a (total, used, free, sin, sout) tuple."""
199
+ mem = cext.virtual_mem()
200
+
201
+ total_phys = mem[0]
202
+ total_system = mem[2]
203
+
204
+ # system memory (commit total/limit) is the sum of physical and swap
205
+ # thus physical memory values need to be subtracted to get swap values
206
+ total = total_system - total_phys
207
+ # commit total is incremented immediately (decrementing free_system)
208
+ # while the corresponding free physical value is not decremented until
209
+ # pages are accessed, so we can't use free system memory for swap.
210
+ # instead, we calculate page file usage based on performance counter
211
+ if total > 0:
212
+ percentswap = cext.swap_percent()
213
+ used = int(0.01 * percentswap * total)
214
+ else:
215
+ percentswap = 0.0
216
+ used = 0
217
+
218
+ free = total - used
219
+ percent = round(percentswap, 1)
220
+ return ntp.sswap(total, used, free, percent, 0, 0)
221
+
222
+
223
+ # malloc / heap functions
224
+ heap_info = cext.heap_info
225
+ heap_trim = cext.heap_trim
226
+
227
+
228
+ # =====================================================================
229
+ # --- disk
230
+ # =====================================================================
231
+
232
+
233
+ disk_io_counters = cext.disk_io_counters
234
+
235
+
236
+ def disk_usage(path):
237
+ """Return disk usage associated with path."""
238
+ if isinstance(path, bytes):
239
+ # XXX: do we want to use "strict"? Probably yes, in order
240
+ # to fail immediately. After all we are accepting input here...
241
+ path = path.decode(ENCODING, errors="strict")
242
+ total, used, free = cext.disk_usage(path)
243
+ percent = usage_percent(used, total, round_=1)
244
+ return ntp.sdiskusage(total, used, free, percent)
245
+
246
+
247
+ def disk_partitions(all):
248
+ """Return disk partitions."""
249
+ rawlist = cext.disk_partitions(all)
250
+ return [ntp.sdiskpart(*x) for x in rawlist]
251
+
252
+
253
+ # =====================================================================
254
+ # --- CPU
255
+ # =====================================================================
256
+
257
+
258
+ def cpu_times():
259
+ """Return system CPU times as a named tuple."""
260
+ user, system, idle = cext.cpu_times()
261
+ # Internally, GetSystemTimes() is used, and it doesn't return
262
+ # interrupt and dpc times. cext.per_cpu_times() does, so we
263
+ # rely on it to get those only.
264
+ percpu_summed = ntp.scputimes(
265
+ *[sum(n) for n in zip(*cext.per_cpu_times())]
266
+ )
267
+ return ntp.scputimes(
268
+ user, system, idle, percpu_summed.interrupt, percpu_summed.dpc
269
+ )
270
+
271
+
272
+ def per_cpu_times():
273
+ """Return system per-CPU times as a list of named tuples."""
274
+ ret = []
275
+ for user, system, idle, interrupt, dpc in cext.per_cpu_times():
276
+ item = ntp.scputimes(user, system, idle, interrupt, dpc)
277
+ ret.append(item)
278
+ return ret
279
+
280
+
281
+ def cpu_count_logical():
282
+ """Return the number of logical CPUs in the system."""
283
+ return cext.cpu_count_logical()
284
+
285
+
286
+ def cpu_count_cores():
287
+ """Return the number of CPU cores in the system."""
288
+ return cext.cpu_count_cores()
289
+
290
+
291
+ def cpu_stats():
292
+ """Return CPU statistics."""
293
+ ctx_switches, interrupts, _dpcs, syscalls = cext.cpu_stats()
294
+ soft_interrupts = 0
295
+ return ntp.scpustats(ctx_switches, interrupts, soft_interrupts, syscalls)
296
+
297
+
298
+ def cpu_freq():
299
+ """Return CPU frequency.
300
+ On Windows per-cpu frequency is not supported.
301
+ """
302
+ curr, max_ = cext.cpu_freq()
303
+ min_ = 0.0
304
+ return [ntp.scpufreq(float(curr), min_, float(max_))]
305
+
306
+
307
+ _loadavg_initialized = False
308
+ _lock = threading.Lock()
309
+
310
+
311
+ def _getloadavg_impl():
312
+ # Drop to 2 decimal points which is what Linux does
313
+ raw_loads = cext.getloadavg()
314
+ return tuple(round(load, 2) for load in raw_loads)
315
+
316
+
317
+ def getloadavg():
318
+ """Return the number of processes in the system run queue averaged
319
+ over the last 1, 5, and 15 minutes respectively as a tuple.
320
+ """
321
+ global _loadavg_initialized
322
+
323
+ if _loadavg_initialized:
324
+ return _getloadavg_impl()
325
+
326
+ with _lock:
327
+ if not _loadavg_initialized:
328
+ cext.init_loadavg_counter()
329
+ _loadavg_initialized = True
330
+
331
+ return _getloadavg_impl()
332
+
333
+
334
+ # =====================================================================
335
+ # --- network
336
+ # =====================================================================
337
+
338
+
339
+ def net_connections(kind, _pid=-1):
340
+ """Return socket connections. If pid == -1 return system-wide
341
+ connections (as opposed to connections opened by one process only).
342
+ """
343
+ families, types = conn_tmap[kind]
344
+ rawlist = cext.net_connections(_pid, families, types)
345
+ ret = set()
346
+ for item in rawlist:
347
+ fd, fam, type, laddr, raddr, status, pid = item
348
+ nt = conn_to_ntuple(
349
+ fd,
350
+ fam,
351
+ type,
352
+ laddr,
353
+ raddr,
354
+ status,
355
+ TCP_STATUSES,
356
+ pid=pid if _pid == -1 else None,
357
+ )
358
+ ret.add(nt)
359
+ return list(ret)
360
+
361
+
362
+ def net_if_stats():
363
+ """Get NIC stats (isup, duplex, speed, mtu)."""
364
+ ret = {}
365
+ rawdict = cext.net_if_stats()
366
+ for name, items in rawdict.items():
367
+ isup, duplex, speed, mtu = items
368
+ if hasattr(_common, 'NicDuplex'):
369
+ duplex = _common.NicDuplex(duplex)
370
+ ret[name] = ntp.snicstats(isup, duplex, speed, mtu, '')
371
+ return ret
372
+
373
+
374
+ def net_io_counters():
375
+ """Return network I/O statistics for every network interface
376
+ installed on the system as a dict of raw tuples.
377
+ """
378
+ return cext.net_io_counters()
379
+
380
+
381
+ def net_if_addrs():
382
+ """Return the addresses associated to each NIC."""
383
+ return cext.net_if_addrs()
384
+
385
+
386
+ # =====================================================================
387
+ # --- sensors
388
+ # =====================================================================
389
+
390
+
391
+ def sensors_battery():
392
+ """Return battery information."""
393
+ # For constants meaning see:
394
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/
395
+ # aa373232(v=vs.85).aspx
396
+ acline_status, flags, percent, secsleft = cext.sensors_battery()
397
+ power_plugged = acline_status == 1
398
+ no_battery = bool(flags & 128)
399
+ charging = bool(flags & 8)
400
+
401
+ if no_battery:
402
+ return None
403
+ if power_plugged or charging:
404
+ secsleft = _common.POWER_TIME_UNLIMITED
405
+ elif secsleft == -1:
406
+ secsleft = _common.POWER_TIME_UNKNOWN
407
+
408
+ return ntp.sbattery(percent, secsleft, power_plugged)
409
+
410
+
411
+ # =====================================================================
412
+ # --- other system functions
413
+ # =====================================================================
414
+
415
+
416
+ _last_btime = 0
417
+
418
+
419
+ def boot_time():
420
+ """The system boot time expressed in seconds since the epoch. This
421
+ also includes the time spent during hybernate / suspend.
422
+ """
423
+ # This dirty hack is to adjust the precision of the returned
424
+ # value which may have a 1 second fluctuation, see:
425
+ # https://github.com/giampaolo/psutil/issues/1007
426
+ global _last_btime
427
+ ret = time.time() - cext.uptime()
428
+ if abs(ret - _last_btime) <= 1:
429
+ return _last_btime
430
+ else:
431
+ _last_btime = ret
432
+ return ret
433
+
434
+
435
+ def users():
436
+ """Return currently connected users as a list of namedtuples."""
437
+ retlist = []
438
+ rawlist = cext.users()
439
+ for item in rawlist:
440
+ user, hostname, tstamp = item
441
+ nt = ntp.suser(user, None, hostname, tstamp, None)
442
+ retlist.append(nt)
443
+ return retlist
444
+
445
+
446
+ # =====================================================================
447
+ # --- Windows services
448
+ # =====================================================================
449
+
450
+
451
+ def win_service_iter():
452
+ """Yields a list of WindowsService instances."""
453
+ for name, display_name in cext.winservice_enumerate():
454
+ yield WindowsService(name, display_name)
455
+
456
+
457
+ def win_service_get(name):
458
+ """Open a Windows service and return it as a WindowsService instance."""
459
+ service = WindowsService(name, None)
460
+ service._display_name = service._query_config()['display_name']
461
+ return service
462
+
463
+
464
+ class WindowsService: # noqa: PLW1641
465
+ """Represents an installed Windows service."""
466
+
467
+ def __init__(self, name, display_name):
468
+ self._name = name
469
+ self._display_name = display_name
470
+
471
+ def __str__(self):
472
+ details = f"(name={self._name!r}, display_name={self._display_name!r})"
473
+ return f"{self.__class__.__name__}{details}"
474
+
475
+ def __repr__(self):
476
+ return f"<{self.__str__()} at {id(self)}>"
477
+
478
+ def __eq__(self, other):
479
+ # Test for equality with another WindosService object based
480
+ # on name.
481
+ if not isinstance(other, WindowsService):
482
+ return NotImplemented
483
+ return self._name == other._name
484
+
485
+ def __ne__(self, other):
486
+ return not self == other
487
+
488
+ def _query_config(self):
489
+ with self._wrap_exceptions():
490
+ display_name, binpath, username, start_type = (
491
+ cext.winservice_query_config(self._name)
492
+ )
493
+ # XXX - update _self.display_name?
494
+ return dict(
495
+ display_name=display_name,
496
+ binpath=binpath,
497
+ username=username,
498
+ start_type=start_type,
499
+ )
500
+
501
+ def _query_status(self):
502
+ with self._wrap_exceptions():
503
+ status, pid = cext.winservice_query_status(self._name)
504
+ if pid == 0:
505
+ pid = None
506
+ return dict(status=status, pid=pid)
507
+
508
+ @contextlib.contextmanager
509
+ def _wrap_exceptions(self):
510
+ """Ctx manager which translates bare OSError and WindowsError
511
+ exceptions into NoSuchProcess and AccessDenied.
512
+ """
513
+ try:
514
+ yield
515
+ except OSError as err:
516
+ name = self._name
517
+ if is_permission_err(err):
518
+ msg = (
519
+ f"service {name!r} is not querable (not enough privileges)"
520
+ )
521
+ raise AccessDenied(pid=None, name=name, msg=msg) from err
522
+ elif err.winerror in {
523
+ cext.ERROR_INVALID_NAME,
524
+ cext.ERROR_SERVICE_DOES_NOT_EXIST,
525
+ }:
526
+ msg = f"service {name!r} does not exist"
527
+ raise NoSuchProcess(pid=None, name=name, msg=msg) from err
528
+ else:
529
+ raise
530
+
531
+ # config query
532
+
533
+ def name(self):
534
+ """The service name. This string is how a service is referenced
535
+ and can be passed to win_service_get() to get a new
536
+ WindowsService instance.
537
+ """
538
+ return self._name
539
+
540
+ def display_name(self):
541
+ """The service display name. The value is cached when this class
542
+ is instantiated.
543
+ """
544
+ return self._display_name
545
+
546
+ def binpath(self):
547
+ """The fully qualified path to the service binary/exe file as
548
+ a string, including command line arguments.
549
+ """
550
+ return self._query_config()['binpath']
551
+
552
+ def username(self):
553
+ """The name of the user that owns this service."""
554
+ return self._query_config()['username']
555
+
556
+ def start_type(self):
557
+ """A string which can either be "automatic", "manual" or
558
+ "disabled".
559
+ """
560
+ return self._query_config()['start_type']
561
+
562
+ # status query
563
+
564
+ def pid(self):
565
+ """The process PID, if any, else None. This can be passed
566
+ to Process class to control the service's process.
567
+ """
568
+ return self._query_status()['pid']
569
+
570
+ def status(self):
571
+ """Service status as a string."""
572
+ return self._query_status()['status']
573
+
574
+ def description(self):
575
+ """Service long description."""
576
+ return cext.winservice_query_descr(self.name())
577
+
578
+ # utils
579
+
580
+ def as_dict(self):
581
+ """Utility method retrieving all the information above as a
582
+ dictionary.
583
+ """
584
+ d = self._query_config()
585
+ d.update(self._query_status())
586
+ d['name'] = self.name()
587
+ d['display_name'] = self.display_name()
588
+ d['description'] = self.description()
589
+ return d
590
+
591
+ # actions
592
+ # XXX: the necessary C bindings for start() and stop() are
593
+ # implemented but for now I prefer not to expose them.
594
+ # I may change my mind in the future. Reasons:
595
+ # - they require Administrator privileges
596
+ # - can't implement a timeout for stop() (unless by using a thread,
597
+ # which sucks)
598
+ # - would require adding ServiceAlreadyStarted and
599
+ # ServiceAlreadyStopped exceptions, adding two new APIs.
600
+ # - we might also want to have modify(), which would basically mean
601
+ # rewriting win32serviceutil.ChangeServiceConfig, which involves a
602
+ # lot of stuff (and API constants which would pollute the API), see:
603
+ # http://pyxr.sourceforge.net/PyXR/c/python24/lib/site-packages/
604
+ # win32/lib/win32serviceutil.py.html#0175
605
+ # - psutil is typically about "read only" monitoring stuff;
606
+ # win_service_* APIs should only be used to retrieve a service and
607
+ # check whether it's running
608
+
609
+ # def start(self, timeout=None):
610
+ # with self._wrap_exceptions():
611
+ # cext.winservice_start(self.name())
612
+ # if timeout:
613
+ # giveup_at = time.time() + timeout
614
+ # while True:
615
+ # if self.status() == "running":
616
+ # return
617
+ # else:
618
+ # if time.time() > giveup_at:
619
+ # raise TimeoutExpired(timeout)
620
+ # else:
621
+ # time.sleep(.1)
622
+
623
+ # def stop(self):
624
+ # # Note: timeout is not implemented because it's just not
625
+ # # possible, see:
626
+ # # http://stackoverflow.com/questions/11973228/
627
+ # with self._wrap_exceptions():
628
+ # return cext.winservice_stop(self.name())
629
+
630
+
631
+ # =====================================================================
632
+ # --- processes
633
+ # =====================================================================
634
+
635
+
636
+ pids = cext.pids
637
+ pid_exists = cext.pid_exists
638
+ ppid_map = cext.ppid_map # used internally by Process.children()
639
+
640
+
641
+ def is_permission_err(exc):
642
+ """Return True if this is a permission error."""
643
+ assert isinstance(exc, OSError), exc
644
+ return isinstance(exc, PermissionError) or exc.winerror in {
645
+ cext.ERROR_ACCESS_DENIED,
646
+ cext.ERROR_PRIVILEGE_NOT_HELD,
647
+ }
648
+
649
+
650
+ def convert_oserror(exc, pid=None, name=None):
651
+ """Convert OSError into NoSuchProcess or AccessDenied."""
652
+ assert isinstance(exc, OSError), exc
653
+ if is_permission_err(exc):
654
+ return AccessDenied(pid=pid, name=name)
655
+ if isinstance(exc, ProcessLookupError):
656
+ return NoSuchProcess(pid=pid, name=name)
657
+ raise exc
658
+
659
+
660
+ def wrap_exceptions(fun):
661
+ """Decorator which converts OSError into NoSuchProcess or AccessDenied."""
662
+
663
+ @functools.wraps(fun)
664
+ def wrapper(self, *args, **kwargs):
665
+ try:
666
+ return fun(self, *args, **kwargs)
667
+ except OSError as err:
668
+ raise convert_oserror(err, pid=self.pid, name=self._name) from err
669
+
670
+ return wrapper
671
+
672
+
673
+ def retry_error_partial_copy(fun):
674
+ """Workaround for https://github.com/giampaolo/psutil/issues/875.
675
+ See: https://stackoverflow.com/questions/4457745#4457745.
676
+ """
677
+
678
+ @functools.wraps(fun)
679
+ def wrapper(self, *args, **kwargs):
680
+ delay = 0.0001
681
+ times = 33
682
+ for _ in range(times): # retries for roughly 1 second
683
+ try:
684
+ return fun(self, *args, **kwargs)
685
+ except OSError as _:
686
+ err = _
687
+ if err.winerror == ERROR_PARTIAL_COPY:
688
+ time.sleep(delay)
689
+ delay = min(delay * 2, 0.04)
690
+ continue
691
+ raise
692
+ msg = (
693
+ f"{fun} retried {times} times, converted to AccessDenied as it's "
694
+ f"still returning {err}"
695
+ )
696
+ raise AccessDenied(pid=self.pid, name=self._name, msg=msg)
697
+
698
+ return wrapper
699
+
700
+
701
+ class Process:
702
+ """Wrapper class around underlying C implementation."""
703
+
704
+ __slots__ = ["_cache", "_name", "_ppid", "pid"]
705
+
706
+ def __init__(self, pid):
707
+ self.pid = pid
708
+ self._name = None
709
+ self._ppid = None
710
+
711
+ # --- oneshot() stuff
712
+
713
+ def oneshot_enter(self):
714
+ self._proc_info.cache_activate(self)
715
+ self.exe.cache_activate(self)
716
+
717
+ def oneshot_exit(self):
718
+ self._proc_info.cache_deactivate(self)
719
+ self.exe.cache_deactivate(self)
720
+
721
+ @memoize_when_activated
722
+ def _proc_info(self):
723
+ """Return multiple information about this process as a
724
+ raw tuple.
725
+ """
726
+ ret = cext.proc_info(self.pid)
727
+ assert len(ret) == len(pinfo_map)
728
+ return ret
729
+
730
+ def name(self):
731
+ """Return process name, which on Windows is always the final
732
+ part of the executable.
733
+ """
734
+ # This is how PIDs 0 and 4 are always represented in taskmgr
735
+ # and process-hacker.
736
+ if self.pid == 0:
737
+ return "System Idle Process"
738
+ if self.pid == 4:
739
+ return "System"
740
+ return os.path.basename(self.exe())
741
+
742
+ @wrap_exceptions
743
+ @memoize_when_activated
744
+ def exe(self):
745
+ if PYPY:
746
+ try:
747
+ exe = cext.proc_exe(self.pid)
748
+ except OSError as err:
749
+ # 24 = ERROR_TOO_MANY_OPEN_FILES. Not sure why this happens
750
+ # (perhaps PyPy's JIT delaying garbage collection of files?).
751
+ if err.errno == 24:
752
+ debug(f"{err!r} translated into AccessDenied")
753
+ raise AccessDenied(self.pid, self._name) from err
754
+ raise
755
+ else:
756
+ exe = cext.proc_exe(self.pid)
757
+ if exe.startswith('\\'):
758
+ return convert_dos_path(exe)
759
+ return exe # May be "Registry", "MemCompression", ...
760
+
761
+ @wrap_exceptions
762
+ @retry_error_partial_copy
763
+ def cmdline(self):
764
+ if cext.WINVER >= cext.WINDOWS_8_1:
765
+ # PEB method detects cmdline changes but requires more
766
+ # privileges: https://github.com/giampaolo/psutil/pull/1398
767
+ try:
768
+ return cext.proc_cmdline(self.pid, use_peb=True)
769
+ except OSError as err:
770
+ if is_permission_err(err):
771
+ return cext.proc_cmdline(self.pid, use_peb=False)
772
+ else:
773
+ raise
774
+ else:
775
+ return cext.proc_cmdline(self.pid, use_peb=True)
776
+
777
+ @wrap_exceptions
778
+ @retry_error_partial_copy
779
+ def environ(self):
780
+ s = cext.proc_environ(self.pid)
781
+ return parse_environ_block(s)
782
+
783
+ def ppid(self):
784
+ try:
785
+ return ppid_map()[self.pid]
786
+ except KeyError:
787
+ raise NoSuchProcess(self.pid, self._name) from None
788
+
789
+ def _get_raw_meminfo(self):
790
+ try:
791
+ return cext.proc_memory_info(self.pid)
792
+ except OSError as err:
793
+ if is_permission_err(err):
794
+ # TODO: the C ext can probably be refactored in order
795
+ # to get this from cext.proc_info()
796
+ debug("attempting memory_info() fallback (slower)")
797
+ info = self._proc_info()
798
+ return (
799
+ info[pinfo_map['num_page_faults']],
800
+ info[pinfo_map['peak_wset']],
801
+ info[pinfo_map['wset']],
802
+ info[pinfo_map['peak_paged_pool']],
803
+ info[pinfo_map['paged_pool']],
804
+ info[pinfo_map['peak_non_paged_pool']],
805
+ info[pinfo_map['non_paged_pool']],
806
+ info[pinfo_map['pagefile']],
807
+ info[pinfo_map['peak_pagefile']],
808
+ info[pinfo_map['mem_private']],
809
+ )
810
+ raise
811
+
812
+ @wrap_exceptions
813
+ def memory_info(self):
814
+ # on Windows RSS == WorkingSetSize and VSM == PagefileUsage.
815
+ # Underlying C function returns fields of PROCESS_MEMORY_COUNTERS
816
+ # struct.
817
+ t = self._get_raw_meminfo()
818
+ rss = t[2] # wset
819
+ vms = t[7] # pagefile
820
+ return ntp.pmem(*(rss, vms) + t)
821
+
822
+ @wrap_exceptions
823
+ def memory_full_info(self):
824
+ basic_mem = self.memory_info()
825
+ uss = cext.proc_memory_uss(self.pid)
826
+ uss *= getpagesize()
827
+ return ntp.pfullmem(*basic_mem + (uss,))
828
+
829
+ def memory_maps(self):
830
+ try:
831
+ raw = cext.proc_memory_maps(self.pid)
832
+ except OSError as err:
833
+ # XXX - can't use wrap_exceptions decorator as we're
834
+ # returning a generator; probably needs refactoring.
835
+ raise convert_oserror(err, self.pid, self._name) from err
836
+ else:
837
+ for addr, perm, path, rss in raw:
838
+ path = convert_dos_path(path)
839
+ addr = hex(addr)
840
+ yield (addr, perm, path, rss)
841
+
842
+ @wrap_exceptions
843
+ def kill(self):
844
+ return cext.proc_kill(self.pid)
845
+
846
+ @wrap_exceptions
847
+ def send_signal(self, sig):
848
+ if sig == signal.SIGTERM:
849
+ cext.proc_kill(self.pid)
850
+ elif sig in {signal.CTRL_C_EVENT, signal.CTRL_BREAK_EVENT}:
851
+ os.kill(self.pid, sig)
852
+ else:
853
+ msg = (
854
+ "only SIGTERM, CTRL_C_EVENT and CTRL_BREAK_EVENT signals "
855
+ "are supported on Windows"
856
+ )
857
+ raise ValueError(msg)
858
+
859
+ @wrap_exceptions
860
+ def wait(self, timeout=None):
861
+ if timeout is None:
862
+ cext_timeout = cext.INFINITE
863
+ else:
864
+ # WaitForSingleObject() expects time in milliseconds.
865
+ cext_timeout = int(timeout * 1000)
866
+
867
+ timer = getattr(time, 'monotonic', time.time)
868
+ stop_at = timer() + timeout if timeout is not None else None
869
+
870
+ try:
871
+ # Exit code is supposed to come from GetExitCodeProcess().
872
+ # May also be None if OpenProcess() failed with
873
+ # ERROR_INVALID_PARAMETER, meaning PID is already gone.
874
+ exit_code = cext.proc_wait(self.pid, cext_timeout)
875
+ except cext.TimeoutExpired as err:
876
+ # WaitForSingleObject() returned WAIT_TIMEOUT. Just raise.
877
+ raise TimeoutExpired(timeout, self.pid, self._name) from err
878
+ except cext.TimeoutAbandoned:
879
+ # WaitForSingleObject() returned WAIT_ABANDONED, see:
880
+ # https://github.com/giampaolo/psutil/issues/1224
881
+ # We'll just rely on the internal polling and return None
882
+ # when the PID disappears. Subprocess module does the same
883
+ # (return None):
884
+ # https://github.com/python/cpython/blob/
885
+ # be50a7b627d0aa37e08fa8e2d5568891f19903ce/
886
+ # Lib/subprocess.py#L1193-L1194
887
+ exit_code = None
888
+
889
+ # At this point WaitForSingleObject() returned WAIT_OBJECT_0,
890
+ # meaning the process is gone. Stupidly there are cases where
891
+ # its PID may still stick around so we do a further internal
892
+ # polling.
893
+ delay = 0.0001
894
+ while True:
895
+ if not pid_exists(self.pid):
896
+ return exit_code
897
+ if stop_at and timer() >= stop_at:
898
+ raise TimeoutExpired(timeout, pid=self.pid, name=self._name)
899
+ time.sleep(delay)
900
+ delay = min(delay * 2, 0.04) # incremental delay
901
+
902
+ @wrap_exceptions
903
+ def username(self):
904
+ if self.pid in {0, 4}:
905
+ return 'NT AUTHORITY\\SYSTEM'
906
+ domain, user = cext.proc_username(self.pid)
907
+ return f"{domain}\\{user}"
908
+
909
+ @wrap_exceptions
910
+ def create_time(self, fast_only=False):
911
+ # Note: proc_times() not put under oneshot() 'cause create_time()
912
+ # is already cached by the main Process class.
913
+ try:
914
+ _user, _system, created = cext.proc_times(self.pid)
915
+ return created
916
+ except OSError as err:
917
+ if is_permission_err(err):
918
+ if fast_only:
919
+ raise
920
+ debug("attempting create_time() fallback (slower)")
921
+ return self._proc_info()[pinfo_map['create_time']]
922
+ raise
923
+
924
+ @wrap_exceptions
925
+ def num_threads(self):
926
+ return self._proc_info()[pinfo_map['num_threads']]
927
+
928
+ @wrap_exceptions
929
+ def threads(self):
930
+ rawlist = cext.proc_threads(self.pid)
931
+ retlist = []
932
+ for thread_id, utime, stime in rawlist:
933
+ ntuple = ntp.pthread(thread_id, utime, stime)
934
+ retlist.append(ntuple)
935
+ return retlist
936
+
937
+ @wrap_exceptions
938
+ def cpu_times(self):
939
+ try:
940
+ user, system, _created = cext.proc_times(self.pid)
941
+ except OSError as err:
942
+ if not is_permission_err(err):
943
+ raise
944
+ debug("attempting cpu_times() fallback (slower)")
945
+ info = self._proc_info()
946
+ user = info[pinfo_map['user_time']]
947
+ system = info[pinfo_map['kernel_time']]
948
+ # Children user/system times are not retrievable (set to 0).
949
+ return ntp.pcputimes(user, system, 0.0, 0.0)
950
+
951
+ @wrap_exceptions
952
+ def suspend(self):
953
+ cext.proc_suspend_or_resume(self.pid, True)
954
+
955
+ @wrap_exceptions
956
+ def resume(self):
957
+ cext.proc_suspend_or_resume(self.pid, False)
958
+
959
+ @wrap_exceptions
960
+ @retry_error_partial_copy
961
+ def cwd(self):
962
+ if self.pid in {0, 4}:
963
+ raise AccessDenied(self.pid, self._name)
964
+ # return a normalized pathname since the native C function appends
965
+ # "\\" at the and of the path
966
+ path = cext.proc_cwd(self.pid)
967
+ return os.path.normpath(path)
968
+
969
+ @wrap_exceptions
970
+ def open_files(self):
971
+ if self.pid in {0, 4}:
972
+ return []
973
+ ret = set()
974
+ # Filenames come in in native format like:
975
+ # "\Device\HarddiskVolume1\Windows\systemew\file.txt"
976
+ # Convert the first part in the corresponding drive letter
977
+ # (e.g. "C:\") by using Windows's QueryDosDevice()
978
+ raw_file_names = cext.proc_open_files(self.pid)
979
+ for file in raw_file_names:
980
+ file = convert_dos_path(file)
981
+ if isfile_strict(file):
982
+ ntuple = ntp.popenfile(file, -1)
983
+ ret.add(ntuple)
984
+ return list(ret)
985
+
986
+ @wrap_exceptions
987
+ def net_connections(self, kind='inet'):
988
+ return net_connections(kind, _pid=self.pid)
989
+
990
+ @wrap_exceptions
991
+ def nice_get(self):
992
+ value = cext.proc_priority_get(self.pid)
993
+ value = Priority(value)
994
+ return value
995
+
996
+ @wrap_exceptions
997
+ def nice_set(self, value):
998
+ return cext.proc_priority_set(self.pid, value)
999
+
1000
+ @wrap_exceptions
1001
+ def ionice_get(self):
1002
+ ret = cext.proc_io_priority_get(self.pid)
1003
+ ret = IOPriority(ret)
1004
+ return ret
1005
+
1006
+ @wrap_exceptions
1007
+ def ionice_set(self, ioclass, value):
1008
+ if value:
1009
+ msg = "value argument not accepted on Windows"
1010
+ raise TypeError(msg)
1011
+ if ioclass not in {
1012
+ IOPriority.IOPRIO_VERYLOW,
1013
+ IOPriority.IOPRIO_LOW,
1014
+ IOPriority.IOPRIO_NORMAL,
1015
+ IOPriority.IOPRIO_HIGH,
1016
+ }:
1017
+ msg = f"{ioclass} is not a valid priority"
1018
+ raise ValueError(msg)
1019
+ cext.proc_io_priority_set(self.pid, ioclass)
1020
+
1021
+ @wrap_exceptions
1022
+ def io_counters(self):
1023
+ try:
1024
+ ret = cext.proc_io_counters(self.pid)
1025
+ except OSError as err:
1026
+ if not is_permission_err(err):
1027
+ raise
1028
+ debug("attempting io_counters() fallback (slower)")
1029
+ info = self._proc_info()
1030
+ ret = (
1031
+ info[pinfo_map['io_rcount']],
1032
+ info[pinfo_map['io_wcount']],
1033
+ info[pinfo_map['io_rbytes']],
1034
+ info[pinfo_map['io_wbytes']],
1035
+ info[pinfo_map['io_count_others']],
1036
+ info[pinfo_map['io_bytes_others']],
1037
+ )
1038
+ return ntp.pio(*ret)
1039
+
1040
+ @wrap_exceptions
1041
+ def status(self):
1042
+ suspended = cext.proc_is_suspended(self.pid)
1043
+ if suspended:
1044
+ return _common.STATUS_STOPPED
1045
+ else:
1046
+ return _common.STATUS_RUNNING
1047
+
1048
+ @wrap_exceptions
1049
+ def cpu_affinity_get(self):
1050
+ def from_bitmask(x):
1051
+ return [i for i in range(64) if (1 << i) & x]
1052
+
1053
+ bitmask = cext.proc_cpu_affinity_get(self.pid)
1054
+ return from_bitmask(bitmask)
1055
+
1056
+ @wrap_exceptions
1057
+ def cpu_affinity_set(self, value):
1058
+ def to_bitmask(ls):
1059
+ if not ls:
1060
+ msg = f"invalid argument {ls!r}"
1061
+ raise ValueError(msg)
1062
+ out = 0
1063
+ for b in ls:
1064
+ out |= 2**b
1065
+ return out
1066
+
1067
+ # SetProcessAffinityMask() states that ERROR_INVALID_PARAMETER
1068
+ # is returned for an invalid CPU but this seems not to be true,
1069
+ # therefore we check CPUs validy beforehand.
1070
+ allcpus = list(range(len(per_cpu_times())))
1071
+ for cpu in value:
1072
+ if cpu not in allcpus:
1073
+ if not isinstance(cpu, int):
1074
+ msg = f"invalid CPU {cpu!r}; an integer is required"
1075
+ raise TypeError(msg)
1076
+ msg = f"invalid CPU {cpu!r}"
1077
+ raise ValueError(msg)
1078
+
1079
+ bitmask = to_bitmask(value)
1080
+ cext.proc_cpu_affinity_set(self.pid, bitmask)
1081
+
1082
+ @wrap_exceptions
1083
+ def num_handles(self):
1084
+ try:
1085
+ return cext.proc_num_handles(self.pid)
1086
+ except OSError as err:
1087
+ if is_permission_err(err):
1088
+ debug("attempting num_handles() fallback (slower)")
1089
+ return self._proc_info()[pinfo_map['num_handles']]
1090
+ raise
1091
+
1092
+ @wrap_exceptions
1093
+ def num_ctx_switches(self):
1094
+ ctx_switches = self._proc_info()[pinfo_map['ctx_switches']]
1095
+ # only voluntary ctx switches are supported
1096
+ return ntp.pctxsw(ctx_switches, 0)
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/pytablewriter-1.2.1.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip