Shirochi commited on
Commit
cb72edf
·
verified ·
1 Parent(s): 619539e

Delete memory_usage_reporter.py

Browse files
Files changed (1) hide show
  1. memory_usage_reporter.py +0 -225
memory_usage_reporter.py DELETED
@@ -1,225 +0,0 @@
1
- # memory_usage_reporter.py
2
- """
3
- Background memory usage reporter.
4
- - Logs process RSS, VMS, peak (if available), GC counts, and optional tracemalloc stats
5
- - Writes to logs/memory.log and also propagates to root logger (run.log) via a child logger
6
- - Designed to be lightweight and safe in GUI apps
7
- """
8
- import os
9
- import sys
10
- import time
11
- import threading
12
- import logging
13
- import gc
14
- from logging.handlers import RotatingFileHandler
15
-
16
- try:
17
- import psutil
18
- except Exception:
19
- psutil = None
20
-
21
- # Global singletons
22
- _GLOBAL_THREAD = None
23
- _GLOBAL_STOP = threading.Event()
24
-
25
-
26
- def _ensure_logs_dir() -> str:
27
- # Prefer explicit override from main app
28
- try:
29
- env_dir = os.environ.get("GLOSSARION_LOG_DIR")
30
- if env_dir:
31
- dir_path = os.path.expanduser(env_dir)
32
- os.makedirs(dir_path, exist_ok=True)
33
- return dir_path
34
- except Exception:
35
- pass
36
-
37
- def _can_write(p: str) -> bool:
38
- try:
39
- os.makedirs(p, exist_ok=True)
40
- test_file = os.path.join(p, ".write_test")
41
- with open(test_file, "w", encoding="utf-8") as f:
42
- f.write("ok")
43
- os.remove(test_file)
44
- return True
45
- except Exception:
46
- return False
47
-
48
- # Frozen exe: try next to the executable first
49
- try:
50
- if getattr(sys, 'frozen', False) and hasattr(sys, 'executable'):
51
- exe_dir = os.path.dirname(sys.executable)
52
- candidate = os.path.join(exe_dir, "logs")
53
- if _can_write(candidate):
54
- return candidate
55
- except Exception:
56
- pass
57
-
58
- # User-local app data (persistent and writable)
59
- try:
60
- base = os.environ.get('LOCALAPPDATA') or os.environ.get('APPDATA') or os.path.expanduser('~')
61
- candidate = os.path.join(base, 'Glossarion', 'logs')
62
- if _can_write(candidate):
63
- return candidate
64
- except Exception:
65
- pass
66
-
67
- # Development fallback: next to this file
68
- try:
69
- base_dir = os.path.abspath(os.path.dirname(__file__))
70
- candidate = os.path.join(base_dir, "logs")
71
- if _can_write(candidate):
72
- return candidate
73
- except Exception:
74
- pass
75
-
76
- # Final fallback: CWD
77
- fallback = os.path.join(os.getcwd(), "logs")
78
- os.makedirs(fallback, exist_ok=True)
79
- return fallback
80
-
81
-
82
- def _make_logger() -> logging.Logger:
83
- logger = logging.getLogger("memory")
84
- logger.setLevel(logging.INFO)
85
-
86
- # Avoid duplicate handlers if called more than once
87
- if not any(isinstance(h, RotatingFileHandler) for h in logger.handlers):
88
- logs_dir = _ensure_logs_dir()
89
- file_path = os.path.join(logs_dir, "memory.log")
90
- fh = RotatingFileHandler(file_path, maxBytes=2 * 1024 * 1024, backupCount=3, encoding="utf-8")
91
- fmt = logging.Formatter(
92
- fmt="%(asctime)s %(levelname)s [%(process)d:%(threadName)s] %(name)s: %(message)s",
93
- datefmt="%Y-%m-%d %H:%M:%S",
94
- )
95
- fh.setFormatter(fmt)
96
- logger.addHandler(fh)
97
-
98
- # Do NOT propagate to root; keep memory logs out of console and only in memory.log
99
- logger.propagate = False
100
- return logger
101
-
102
-
103
- def _get_process() -> "psutil.Process | None":
104
- if psutil is None:
105
- return None
106
- try:
107
- return psutil.Process()
108
- except Exception:
109
- return None
110
-
111
-
112
- def _format_bytes(num: int) -> str:
113
- try:
114
- for unit in ["B", "KB", "MB", "GB", "TB"]:
115
- if num < 1024.0:
116
- return f"{num:,.1f}{unit}"
117
- num /= 1024.0
118
- return f"{num:,.1f}PB"
119
- except Exception:
120
- return str(num)
121
-
122
-
123
- def _collect_stats(proc) -> dict:
124
- stats = {}
125
- try:
126
- if proc is not None:
127
- mi = proc.memory_info()
128
- stats["rss"] = mi.rss
129
- stats["vms"] = getattr(mi, "vms", 0)
130
- # Peak RSS on Windows via psutil.Process.memory_info() may expose peak_wset in private API; skip for portability
131
- else:
132
- stats["rss"] = 0
133
- stats["vms"] = 0
134
- except Exception:
135
- stats["rss"] = stats.get("rss", 0)
136
- stats["vms"] = stats.get("vms", 0)
137
-
138
- # GC stats
139
- try:
140
- counts = gc.get_count()
141
- stats["gc"] = counts
142
- except Exception:
143
- stats["gc"] = (0, 0, 0)
144
-
145
- return stats
146
-
147
-
148
- def _worker(interval_sec: float, include_tracemalloc: bool):
149
- log = _make_logger()
150
- proc = _get_process()
151
-
152
- # Optional tracemalloc
153
- if include_tracemalloc:
154
- try:
155
- import tracemalloc
156
- if not tracemalloc.is_tracing():
157
- tracemalloc.start()
158
- tm_enabled = True
159
- except Exception:
160
- tm_enabled = False
161
- else:
162
- tm_enabled = False
163
-
164
- while not _GLOBAL_STOP.is_set():
165
- try:
166
- st = _collect_stats(proc)
167
- rss = st.get("rss", 0)
168
- vms = st.get("vms", 0)
169
- gc0, gc1, gc2 = st.get("gc", (0, 0, 0))
170
-
171
- msg = (
172
- f"RSS={_format_bytes(rss)} VMS={_format_bytes(vms)} "
173
- f"GC={gc0}/{gc1}/{gc2}"
174
- )
175
-
176
- if tm_enabled:
177
- try:
178
- import tracemalloc
179
- cur, peak = tracemalloc.get_traced_memory()
180
- msg += f" TM_CUR={_format_bytes(cur)} TM_PEAK={_format_bytes(peak)}"
181
- except Exception:
182
- pass
183
-
184
- log.info(msg)
185
- except Exception as e:
186
- try:
187
- log.warning("memory reporter error: %s", e)
188
- except Exception:
189
- pass
190
- finally:
191
- # Sleep in small chunks to react faster to stop
192
- for _ in range(int(max(1, interval_sec * 10))):
193
- if _GLOBAL_STOP.is_set():
194
- break
195
- time.sleep(0.1)
196
-
197
-
198
- def start_global_memory_logger(interval_sec: float = 3.0, include_tracemalloc: bool = False) -> None:
199
- """Start the background memory logger once per process.
200
-
201
- interval_sec: how often to log
202
- include_tracemalloc: if True, also log tracemalloc current/peak
203
- """
204
- global _GLOBAL_THREAD
205
- if _GLOBAL_THREAD and _GLOBAL_THREAD.is_alive():
206
- return
207
-
208
- _GLOBAL_STOP.clear()
209
- t = threading.Thread(target=_worker, args=(interval_sec, include_tracemalloc), name="mem-logger", daemon=True)
210
- _GLOBAL_THREAD = t
211
- try:
212
- t.start()
213
- except Exception:
214
- # Do not raise to avoid breaking GUI startup
215
- pass
216
-
217
-
218
- def stop_global_memory_logger() -> None:
219
- try:
220
- _GLOBAL_STOP.set()
221
- if _GLOBAL_THREAD and _GLOBAL_THREAD.is_alive():
222
- # Give it a moment to exit
223
- _GLOBAL_THREAD.join(timeout=2.0)
224
- except Exception:
225
- pass