litlogger 0.0.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
litlogger/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from litlogger.logger import LightningLogger
2
+
3
+ __all__ = ["LightningLogger"]
4
+
5
+ __version__ = "0.0.15"
litlogger/artifacts.py ADDED
@@ -0,0 +1,9 @@
1
+ # Copyright The Lightning AI team.
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # http://www.apache.org/licenses/LICENSE-2.0
4
+ #
5
+ from lightning_utilities import module_available
6
+
7
+ if module_available("litmodels"):
8
+ # for compatibility with past versions but could be dropped in the future
9
+ from litmodels import download_model, upload_model # noqa: F401
litlogger/client.py ADDED
@@ -0,0 +1,87 @@
1
+ # Copyright The Lightning AI team.
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # http://www.apache.org/licenses/LICENSE-2.0
4
+ #
5
+ import time
6
+ from functools import wraps
7
+ from logging import Logger
8
+ from typing import Any, Callable
9
+
10
+ import urllib3
11
+ from lightning_sdk.lightning_cloud.openapi.rest import ApiException
12
+ from lightning_sdk.lightning_cloud.rest_client import GridRestClient, _get_next_backoff_time, create_swagger_client
13
+
14
+ logger = Logger(__name__)
15
+
16
+
17
+ def _should_retry(ex: BaseException) -> bool:
18
+ if isinstance(ex, urllib3.exceptions.HTTPError):
19
+ return True
20
+
21
+ if "not found" in str(ex):
22
+ return False
23
+
24
+ if str(ex.status).startswith("4") and ex.status not in (400, 401, 404):
25
+ return True
26
+
27
+ return str(ex.status).startswith("5")
28
+
29
+
30
+ def _retry_wrapper(self: Any, func: Callable, max_retries: int = -1) -> Callable:
31
+ """Returns the function decorated by a wrapper that retries the call several times if a connection error occurs.
32
+
33
+ The retries follow an exponential backoff.
34
+
35
+ """
36
+
37
+ @wraps(func)
38
+ def wrapped(*args: Any, **kwargs: Any) -> Any:
39
+ consecutive_errors = 0
40
+
41
+ while True:
42
+ try:
43
+ return func(self, *args, **kwargs)
44
+ except (ApiException, urllib3.exceptions.HTTPError) as ex:
45
+ if not _should_retry(ex):
46
+ raise ex
47
+
48
+ msg = f"error: {ex!s}" if isinstance(ex, urllib3.exceptions.HTTPError) else f"response: {ex.status}"
49
+
50
+ if consecutive_errors == max_retries:
51
+ raise RuntimeError(f"The {func.__name__} request failed to reach the server, {msg}.") from ex
52
+
53
+ consecutive_errors += 1
54
+ backoff_time = _get_next_backoff_time(consecutive_errors)
55
+ logger.warning(
56
+ f"The {func.__name__} request failed to reach the server, {msg}."
57
+ f" Retrying after {backoff_time} seconds."
58
+ )
59
+
60
+ time.sleep(backoff_time)
61
+
62
+ return wrapped
63
+
64
+
65
+ class LitRestClient(GridRestClient):
66
+ """The LitRestClient is a wrapper around the GridRestClient.
67
+
68
+ It wraps all methods to monitor connection exceptions and employs a retry strategy.
69
+
70
+ Args:
71
+ max_retries: Maximum number of attempts where each delay between retries is exponential.
72
+ If set to -1, it will retry forever, in contrast if set 0, it runs it only once.
73
+
74
+ """
75
+
76
+ def __init__(self, max_retries: int = -1) -> None:
77
+ super().__init__(api_client=create_swagger_client())
78
+ if max_retries == 0:
79
+ return
80
+ for base_class in GridRestClient.__mro__:
81
+ for name, attribute in base_class.__dict__.items():
82
+ if callable(attribute) and attribute.__name__ != "__init__":
83
+ setattr(
84
+ self,
85
+ name,
86
+ _retry_wrapper(self, attribute, max_retries=max_retries),
87
+ )
litlogger/colors.py ADDED
@@ -0,0 +1,217 @@
1
+ # Copyright The Lightning AI team.
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # http://www.apache.org/licenses/LICENSE-2.0
4
+ #
5
+ import random
6
+ from typing import Tuple
7
+
8
+ _LIGHT_THEME_COLORS = [
9
+ "#792EE5",
10
+ "#3FC55F",
11
+ "#E02C2D",
12
+ "#1877F2",
13
+ "#F1AA03",
14
+ "#D22CE0",
15
+ "#FF6666",
16
+ "#5CDB95",
17
+ "#FFA500",
18
+ "#00CED1",
19
+ "#FFD700",
20
+ "#BA55D3",
21
+ "#1E90FF",
22
+ "#FF1493",
23
+ "#228B22",
24
+ "#800000",
25
+ "#FF00FF",
26
+ "#00FA9A",
27
+ "#8B008B",
28
+ "#808080",
29
+ "#7029e0",
30
+ "#e52534",
31
+ "#1172e8",
32
+ "#cf33dc",
33
+ "#188a23",
34
+ "#7d0007",
35
+ "#8d0894",
36
+ "#7732dc",
37
+ "#e43029",
38
+ "#1d6eea",
39
+ "#23911d",
40
+ "#870009",
41
+ "#8b0383",
42
+ "#7427e3",
43
+ "#e92633",
44
+ "#1d6ef7",
45
+ "#d02edc",
46
+ "#188421",
47
+ "#860100",
48
+ "#8a0086",
49
+ "#792fe7",
50
+ "#e22830",
51
+ "#1075f1",
52
+ "#d02cdc",
53
+ "#2c861b",
54
+ "#780505",
55
+ "#930283",
56
+ "#7536e3",
57
+ "#d6252a",
58
+ "#cb24e3",
59
+ "#1e9126",
60
+ "#760000",
61
+ "#8d0893",
62
+ "#7124dc",
63
+ "#df292f",
64
+ "#1c74f2",
65
+ "#ce24da",
66
+ "#b05aca",
67
+ "#1f9123",
68
+ "#780009",
69
+ "#8b0790",
70
+ "#7c7b7e",
71
+ "#7d30e0",
72
+ "#da2e2b",
73
+ "#258122",
74
+ "#8b0a87",
75
+ "#7529e1",
76
+ "#dd2532",
77
+ "#1276f4",
78
+ "#d822da",
79
+ "#188321",
80
+ "#770a03",
81
+ "#920091",
82
+ "#7d26e6",
83
+ "#e92d2d",
84
+ "#1979ec",
85
+ "#d526d7",
86
+ "#23901b",
87
+ "#790700",
88
+ "#8e0185",
89
+ "#798082",
90
+ "#7134e0",
91
+ "#da2824",
92
+ "#1b77ef",
93
+ "#20851a",
94
+ "#7b0700",
95
+ "#870185",
96
+ "#797c85",
97
+ "#732aee",
98
+ "#e22a26",
99
+ "#146efb",
100
+ "#b551cc",
101
+ "#258c19",
102
+ "#870001",
103
+ "#900081",
104
+ "#768184",
105
+ "#8335ed",
106
+ "#e82634",
107
+ "#1c78eb",
108
+ "#1a8d24",
109
+ ]
110
+
111
+ _DARK_THEME_COLORS = [
112
+ "#FA00F0",
113
+ "#00FA9A",
114
+ "#FA0000",
115
+ "#0091FA",
116
+ "#FFD700",
117
+ "#FF6666",
118
+ "#D22CE0",
119
+ "#5CDB95",
120
+ "#FFA500",
121
+ "#00CED1",
122
+ "#F1AA03",
123
+ "#BA55D3",
124
+ "#1E90FF",
125
+ "#FF1493",
126
+ "#228B22",
127
+ "#800000",
128
+ "#FF00FF",
129
+ "#3FC55F",
130
+ "#8B008B",
131
+ "#808080",
132
+ "#fe00ed",
133
+ "#06eea7",
134
+ "#0085f9",
135
+ "#f5cf00",
136
+ "#ff6b6c",
137
+ "#d224ee",
138
+ "#5fdfa1",
139
+ "#ffa700",
140
+ "#00cdd6",
141
+ "#ffa110",
142
+ "#b751d5",
143
+ "#128ef4",
144
+ "#ff1688",
145
+ "#ff08f6",
146
+ "#43c961",
147
+ "#7a8784",
148
+ "#ff00ef",
149
+ "#0bfaad",
150
+ "#0086f3",
151
+ "#ffd700",
152
+ "#ff646b",
153
+ "#5ce394",
154
+ "#ffab00",
155
+ "#00d0cf",
156
+ "#fbaa03",
157
+ "#219aff",
158
+ "#ff137b",
159
+ "#ff0cf1",
160
+ "#39d56b",
161
+ "#6f9176",
162
+ "#fb00ea",
163
+ "#00fbb8",
164
+ "#0a91fb",
165
+ "#f8d902",
166
+ "#fb7073",
167
+ "#54dca1",
168
+ "#fb9d02",
169
+ "#00c2db",
170
+ "#ffb30f",
171
+ "#268cf0",
172
+ "#f11170",
173
+ "#f211f2",
174
+ "#47ce7a",
175
+ "#719b73",
176
+ "#f305e3",
177
+ "#03ffbd",
178
+ "#048df6",
179
+ "#ffe200",
180
+ "#f56973",
181
+ "#4cdeaf",
182
+ "#ff9a0f",
183
+ "#00c3ce",
184
+ "#ffb417",
185
+ "#2898f6",
186
+ "#f10468",
187
+ "#ff03e5",
188
+ "#54d584",
189
+ "#6a9179",
190
+ "#fe0be3",
191
+ "#00fec0",
192
+ "#0097fb",
193
+ "#feef01",
194
+ "#f2756e",
195
+ "#4ee0a4",
196
+ "#f29902",
197
+ "#00cfce",
198
+ "#ffa91e",
199
+ "#2292f9",
200
+ "#fb0361",
201
+ "#f306e6",
202
+ "#4ed78e",
203
+ "#70897c",
204
+ "#fe05e8",
205
+ "#00f6bd",
206
+ "#00a6ff",
207
+ "#ffe900",
208
+ "#f67b78",
209
+ "#40d3a0",
210
+ "#e98e10",
211
+ "#00d0db",
212
+ ]
213
+
214
+
215
+ def _create_colors() -> Tuple[str, str]:
216
+ idx = random.randint(0, len(_DARK_THEME_COLORS) - 1)
217
+ return _LIGHT_THEME_COLORS[idx], _DARK_THEME_COLORS[idx]
@@ -0,0 +1,193 @@
1
+ # Copyright The Lightning AI team.
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # http://www.apache.org/licenses/LICENSE-2.0
4
+ #
5
+ import os
6
+ import platform
7
+ import re
8
+ import subprocess
9
+ import sys
10
+
11
+ import pkg_resources
12
+ import psutil
13
+
14
+ import litlogger
15
+
16
+
17
+ def get_os_info() -> str:
18
+ """Return a pretty name of operating system and its version: 'Ubuntu 20.04.6 LTS' or 'MacOS 15.0.1'.
19
+
20
+ If it's not possible, then the more verbose name is returned:
21
+ 'Linux-5.15.0-1070-aws-x86_64-with-glibc2.31' or 'macOS-15.0.1-arm64-arm-64bit'.
22
+ """
23
+ os_name = platform.system()
24
+ default_os_name = platform.platform()
25
+
26
+ if os_name == "Linux":
27
+ try:
28
+ with open("/etc/os-release", encoding="utf-8") as f:
29
+ for line in f:
30
+ if line.startswith("PRETTY_NAME"):
31
+ return line.strip().split("=")[1].strip('"')
32
+ return default_os_name
33
+ except FileNotFoundError:
34
+ return default_os_name
35
+
36
+ if os_name == "Darwin":
37
+ try:
38
+ version = platform.mac_ver()[0]
39
+ return f"MacOS {version}"
40
+ except Exception:
41
+ return default_os_name
42
+
43
+ return default_os_name
44
+
45
+
46
+ def get_cpu_name() -> str:
47
+ """Return the name of the CPU (e.g. Apple M3 Pro, Intel(R) Xeon(R) Platinum 8488C)."""
48
+ system = platform.system()
49
+ try:
50
+ if system == "Linux":
51
+ cpu_info = subprocess.check_output("grep 'model name' /proc/cpuinfo | head -n 1", shell=True, text=True)
52
+ return cpu_info.split(":")[1].strip()
53
+
54
+ if system == "Darwin":
55
+ cpu_info = subprocess.check_output(["sysctl", "-n", "machdep.cpu.brand_string"], text=True)
56
+ return cpu_info.strip()
57
+
58
+ return ""
59
+ except Exception:
60
+ return ""
61
+
62
+
63
+ def get_gpu_info() -> dict:
64
+ """Return Nvidia GPU info: name, count and memory size in GB.
65
+
66
+ Example output: {"name": "Nvidia T4, "count": 4, "memory_gb": 16}
67
+ """
68
+ default_output = {
69
+ "name": "",
70
+ "count": 0,
71
+ "memory_gb": 0,
72
+ }
73
+
74
+ try:
75
+ output = subprocess.check_output(
76
+ ["nvidia-smi", "--query-gpu=name,memory.total", "--format=csv,noheader,nounits"], text=True
77
+ ).strip()
78
+
79
+ if not output:
80
+ return default_output
81
+
82
+ gpu_list = output.split("\n")
83
+ gpu_name, gpu_memory_mib = re.split(r",\s+", gpu_list[0])
84
+ gpu_memory_gb = round((int(gpu_memory_mib) / 1e6) * 1024)
85
+
86
+ return {"name": gpu_name, "count": len(gpu_list), "memory_gb": gpu_memory_gb}
87
+
88
+ except Exception:
89
+ return default_output
90
+
91
+
92
+ def get_cuda_version() -> str:
93
+ """Return the CUDA version installed on the system."""
94
+ try:
95
+ output = subprocess.check_output(["nvcc", "--version"], text=True)
96
+ match = re.search(r"release (\d+\.\d+)", output)
97
+ return match.group(1) if match else ""
98
+ except Exception:
99
+ return ""
100
+
101
+
102
+ def get_cudnn_version() -> str:
103
+ """Return the cuDNN version installed on the system."""
104
+ cudnn_path = "/usr/include/cudnn_version.h"
105
+ if os.path.isfile(cudnn_path):
106
+ # Read the file content and find cuDNN version
107
+ with open(cudnn_path, encoding="utf-8") as fin:
108
+ content = fin.read()
109
+ # Find the major, minor, and patch versions of cuDNN
110
+ major = re.search(r"#define CUDNN_MAJOR (\d+)", content)
111
+ minor = re.search(r"#define CUDNN_MINOR (\d+)", content)
112
+ patch = re.search(r"#define CUDNN_PATCHLEVEL (\d+)", content)
113
+ if major and minor and patch:
114
+ return f"{major.group(1)}.{minor.group(1)}.{patch.group(1)}"
115
+ return ""
116
+
117
+
118
+ def _parse_cmd_arguments(cmd_args: str) -> list[str]:
119
+ """Parse command-line arguments into a list of strings.
120
+
121
+ >>> _parse_cmd_arguments("--option1 value1 --flag2 --option3 value3 --option4=value4")
122
+ ['--option1 value1', '--flag2', '--option3 value3', '--option4 value4']
123
+ """
124
+ return ["--" + arg.replace("=", " ").strip() for arg in cmd_args.strip().split("--") if arg]
125
+
126
+
127
+ def get_cli_args() -> str:
128
+ r"""Parse and format command-line arguments into a human-readable string.
129
+
130
+ Pairs flags with their values (e.g., `--option value`) and leaves standalone
131
+ flags (e.g., `--flag`). Returns one argument or pair per line.
132
+
133
+ Example:
134
+ For the input
135
+ `["--option1", "value1", "--flag2", "--option3", "value3", "--option4=value4", "--option5 value5 value6"]`,
136
+ the output would be:
137
+ `"--option1 value1\n--flag2\n--option3 value3\n--option4 value4\n--option5 value5 value6"`.
138
+ """
139
+ args_str = " ".join(sys.argv[1:])
140
+ return "\n".join(_parse_cmd_arguments(args_str))
141
+
142
+
143
+ def collect_system_info() -> dict:
144
+ """Collects git, system, hardware and CLI args information."""
145
+
146
+ # git
147
+ def get_git_info(command: list[str], default_message: str) -> str:
148
+ try:
149
+ return subprocess.check_output(
150
+ command,
151
+ text=True,
152
+ stderr=subprocess.DEVNULL,
153
+ ).strip()
154
+ except subprocess.CalledProcessError:
155
+ return default_message
156
+
157
+ git_repo_name = get_git_info(["git", "rev-parse", "--show-toplevel"], "Not a git repository").split("/")[-1]
158
+ git_branch = get_git_info(["git", "rev-parse", "--abbrev-ref", "HEAD"], "No branch found")
159
+ git_commit_hash = get_git_info(["git", "rev-parse", "HEAD"], "No commit hash found")
160
+
161
+ # software
162
+ litlogger_version = litlogger.__version__
163
+ installed_packages = "\n".join(sorted([f"{pkg.project_name}=={pkg.version}" for pkg in pkg_resources.working_set]))
164
+
165
+ # hardware
166
+ system_memory_gb = psutil.virtual_memory().total // 1024**3
167
+ gpu_info = get_gpu_info()
168
+
169
+ # args
170
+ execution_command = os.path.abspath(sys.argv[0])
171
+ cli_args = get_cli_args()
172
+
173
+ return {
174
+ "git_repo_name": git_repo_name,
175
+ "git_branch": git_branch,
176
+ "git_commit_hash": git_commit_hash,
177
+ "os_name": get_os_info(),
178
+ "python_version": platform.python_version(),
179
+ "litlogger_version": litlogger_version,
180
+ "installed_packages": installed_packages,
181
+ "cpu_name": get_cpu_name(),
182
+ "cpu_count_logical": psutil.cpu_count(logical=True),
183
+ "cpu_count_physical": psutil.cpu_count(logical=False),
184
+ "system_memory_gb": system_memory_gb,
185
+ "gpu_name": gpu_info["name"],
186
+ "gpu_count": gpu_info["count"],
187
+ "gpu_memory_gb": gpu_info["memory_gb"],
188
+ "cuda_version": get_cuda_version(),
189
+ "cudnn_version": get_cudnn_version(),
190
+ "execution_command": execution_command,
191
+ "cli_args": cli_args,
192
+ "hostname": platform.node(),
193
+ }