pmemstat 3.0.2__tar.gz → 3.0.4__tar.gz
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.
- {pmemstat-3.0.2 → pmemstat-3.0.4}/PKG-INFO +3 -4
- {pmemstat-3.0.2 → pmemstat-3.0.4}/pmemstat/main.py +216 -48
- {pmemstat-3.0.2 → pmemstat-3.0.4}/pyproject.toml +2 -2
- pmemstat-3.0.4/runner +17 -0
- pmemstat-3.0.2/run.sh +0 -9
- {pmemstat-3.0.2 → pmemstat-3.0.4}/.gitignore +0 -0
- {pmemstat-3.0.2 → pmemstat-3.0.4}/LICENSE.txt +0 -0
- {pmemstat-3.0.2 → pmemstat-3.0.4}/README.md +0 -0
- {pmemstat-3.0.2 → pmemstat-3.0.4}/TODO.md +0 -0
- {pmemstat-3.0.2 → pmemstat-3.0.4}/images/help-screen.png +0 -0
- {pmemstat-3.0.2 → pmemstat-3.0.4}/images/pmemstat.png +0 -0
- {pmemstat-3.0.2 → pmemstat-3.0.4}/images/scroll-pos.png +0 -0
- {pmemstat-3.0.2 → pmemstat-3.0.4}/memtest.c +0 -0
- {pmemstat-3.0.2 → pmemstat-3.0.4}/pmemstat/CpuSmooth.py +0 -0
- {pmemstat-3.0.2 → pmemstat-3.0.4}/pmemstat/KillThem.py +0 -0
- {pmemstat-3.0.2 → pmemstat-3.0.4}/pmemstat/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: pmemstat
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.4
|
|
4
4
|
Summary: Report Proportional Memory on Linux
|
|
5
5
|
Keywords: proportional memory,RAM,htop,top,monitor
|
|
6
6
|
Author-email: Joe Defen <joedef@duck.com>
|
|
@@ -9,9 +9,8 @@ Description-Content-Type: text/markdown
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
-
License-File: LICENSE.txt
|
|
13
12
|
Requires-Dist: psutil > 5.9
|
|
14
|
-
Requires-Dist: console-window >= 1.0.
|
|
13
|
+
Requires-Dist: console-window >= 1.0.5
|
|
15
14
|
Project-URL: Bug Tracker, https://github.com/joedefen/pmemstat/issues
|
|
16
15
|
Project-URL: Homepage, https://github.com/joedefen/pmemstat
|
|
17
16
|
|
|
@@ -94,6 +94,92 @@ def human(number):
|
|
|
94
94
|
return f'{number:.1f}{suffix}'
|
|
95
95
|
return '' # impossible, but make pylint happy
|
|
96
96
|
|
|
97
|
+
####################################################################################
|
|
98
|
+
# PORTABLE FUNCTION - Copy-pasted from zram-advisor
|
|
99
|
+
# When updating: copy entire function from zram_advisor/main.py (lines 86-166)
|
|
100
|
+
####################################################################################
|
|
101
|
+
def compute_zram_effective(meminfo_total, meminfo_used, meminfo_available,
|
|
102
|
+
zram_orig_data_size, zram_mem_used_total,
|
|
103
|
+
zram_disksize, zram_mem_limit=0, limit_pct=80):
|
|
104
|
+
"""
|
|
105
|
+
Compute effective memory values accounting for zRAM compression.
|
|
106
|
+
|
|
107
|
+
This is a PORTABLE function that can be copy-pasted between projects.
|
|
108
|
+
NO class dependencies - all inputs passed as parameters.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
meminfo_total: Total physical RAM in bytes
|
|
112
|
+
meminfo_used: Currently used RAM in bytes
|
|
113
|
+
meminfo_available: Available RAM in bytes
|
|
114
|
+
zram_orig_data_size: Uncompressed data size stored in zRAM (bytes)
|
|
115
|
+
zram_mem_used_total: Physical RAM consumed by zRAM including overhead (bytes)
|
|
116
|
+
zram_disksize: Max uncompressed data zRAM will accept (bytes)
|
|
117
|
+
zram_mem_limit: Max RAM zRAM can use (0=unlimited) (bytes)
|
|
118
|
+
limit_pct: Arbitrary limit on % of RAM zRAM should use (default 80%)
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
SimpleNamespace with:
|
|
122
|
+
- e_used: Effective memory used (uncompressed equivalent)
|
|
123
|
+
- e_avail: Effective memory available
|
|
124
|
+
- e_max_used: Maximum effective memory at full zRAM capacity
|
|
125
|
+
- ratio_current: Current effective compression ratio
|
|
126
|
+
- ratio_projected: Projected ratio accounting for degradation
|
|
127
|
+
- projection_confidence: 'low', 'medium', or 'high'
|
|
128
|
+
- usage_fraction: Fraction of disksize currently used
|
|
129
|
+
"""
|
|
130
|
+
# Calculate effective used memory (what it would be if uncompressed)
|
|
131
|
+
e_used = meminfo_used - zram_mem_used_total + zram_orig_data_size
|
|
132
|
+
|
|
133
|
+
# Determine compression ratio
|
|
134
|
+
ratio = None
|
|
135
|
+
if e_used <= meminfo_used: # zRAM not helping yet
|
|
136
|
+
e_used = meminfo_used
|
|
137
|
+
ratio = 2.75 # Conservative guess for projection when no data yet
|
|
138
|
+
|
|
139
|
+
if ratio is None: # Calculate actual ratio
|
|
140
|
+
ratio = zram_orig_data_size / zram_mem_used_total if zram_mem_used_total > 0 else 2.75
|
|
141
|
+
|
|
142
|
+
# Apply memory limit if configured
|
|
143
|
+
if zram_mem_limit > 0:
|
|
144
|
+
stats_limit_pct = (zram_mem_limit / meminfo_total) * 100
|
|
145
|
+
limit_pct = min(100.0, limit_pct, stats_limit_pct)
|
|
146
|
+
|
|
147
|
+
# Calculate usage fraction and apply degradation
|
|
148
|
+
usage_fraction = zram_orig_data_size / zram_disksize if zram_disksize > 0 else 0
|
|
149
|
+
|
|
150
|
+
# Degradation logic based on current usage
|
|
151
|
+
if usage_fraction < 0.10:
|
|
152
|
+
degradation_factor = 0.75 # Aggressive: little real data
|
|
153
|
+
confidence = "low"
|
|
154
|
+
elif usage_fraction < 0.50:
|
|
155
|
+
degradation_factor = 0.85 # Moderate: some real data
|
|
156
|
+
confidence = "medium"
|
|
157
|
+
else:
|
|
158
|
+
degradation_factor = 0.95 # Conservative: lots of real data
|
|
159
|
+
confidence = "high"
|
|
160
|
+
|
|
161
|
+
projected_ratio = ratio * degradation_factor
|
|
162
|
+
|
|
163
|
+
# Project maximum effective memory when zRAM fills to limit
|
|
164
|
+
e_max_used = projected_ratio * meminfo_total * limit_pct / 100
|
|
165
|
+
# Cap by disksize limit
|
|
166
|
+
e_max_used = min(e_max_used, zram_disksize)
|
|
167
|
+
# Add uncompressed memory not in zRAM
|
|
168
|
+
e_max_used += meminfo_total - e_max_used / projected_ratio
|
|
169
|
+
|
|
170
|
+
# Calculate effective available
|
|
171
|
+
e_avail = e_max_used - e_used
|
|
172
|
+
|
|
173
|
+
return SimpleNamespace(
|
|
174
|
+
e_used=e_used,
|
|
175
|
+
e_avail=e_avail,
|
|
176
|
+
e_max_used=e_max_used,
|
|
177
|
+
ratio_current=ratio,
|
|
178
|
+
ratio_projected=projected_ratio,
|
|
179
|
+
projection_confidence=confidence,
|
|
180
|
+
usage_fraction=usage_fraction
|
|
181
|
+
)
|
|
182
|
+
|
|
97
183
|
####################################################################################
|
|
98
184
|
###### ZramProjector class
|
|
99
185
|
####################################################################################
|
|
@@ -156,7 +242,7 @@ class ZramProjector:
|
|
|
156
242
|
return infos
|
|
157
243
|
|
|
158
244
|
def compute_effective(self, meminfoKB):
|
|
159
|
-
""" Compute the effective values """
|
|
245
|
+
""" Compute the effective values - now uses portable function """
|
|
160
246
|
def seed(meminfoKB):
|
|
161
247
|
nonlocal self
|
|
162
248
|
meminfo = self.meminfo = SimpleNamespace()
|
|
@@ -175,6 +261,8 @@ class ZramProjector:
|
|
|
175
261
|
devs = self._get_zram_stats()
|
|
176
262
|
if not devs:
|
|
177
263
|
return
|
|
264
|
+
|
|
265
|
+
# Aggregate stats from all zRAM devices
|
|
178
266
|
stats = None
|
|
179
267
|
for stat in devs.values():
|
|
180
268
|
# NOTE: generalization for more than one weak unless all
|
|
@@ -187,29 +275,25 @@ class ZramProjector:
|
|
|
187
275
|
stats = SimpleNamespace(**stats)
|
|
188
276
|
self.meminfo.MemZram = stats.mem_used_total
|
|
189
277
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
e_max_used =
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
e_max_used += meminfo.MemTotal - e_max_used/ratio
|
|
210
|
-
self.e_max_used = e_max_used
|
|
211
|
-
# self.e_max_used = max(e_max_total, e_max_used)
|
|
212
|
-
self.e_avail = e_max_used - e_used
|
|
278
|
+
# Call the portable computation function
|
|
279
|
+
result = compute_zram_effective(
|
|
280
|
+
meminfo_total=meminfo.MemTotal,
|
|
281
|
+
meminfo_used=meminfo.MemUsed,
|
|
282
|
+
meminfo_available=meminfo.MemAvailable,
|
|
283
|
+
zram_orig_data_size=stats.orig_data_size,
|
|
284
|
+
zram_mem_used_total=stats.mem_used_total,
|
|
285
|
+
zram_disksize=stats.disksize,
|
|
286
|
+
zram_mem_limit=stats.mem_limit,
|
|
287
|
+
limit_pct=self.limit_pct
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Update instance variables from result
|
|
291
|
+
self.e_used = result.e_used
|
|
292
|
+
self.e_avail = result.e_avail
|
|
293
|
+
self.e_max_used = result.e_max_used
|
|
294
|
+
self.ratio = result.ratio_current
|
|
295
|
+
self.ratio_projected = result.ratio_projected
|
|
296
|
+
self.projection_confidence = result.projection_confidence
|
|
213
297
|
|
|
214
298
|
|
|
215
299
|
####################################################################################
|
|
@@ -246,7 +330,7 @@ class ProcMem:
|
|
|
246
330
|
# units = '' # determined by arguments
|
|
247
331
|
# fwidth = 11
|
|
248
332
|
pmemstat = None # the main program object
|
|
249
|
-
max_cmd_len =
|
|
333
|
+
max_cmd_len = 96 # command line maximum length
|
|
250
334
|
chunk_dict = {
|
|
251
335
|
'cat': None,
|
|
252
336
|
'beg': 0,
|
|
@@ -286,58 +370,137 @@ class ProcMem:
|
|
|
286
370
|
self.cpu = CpuSmooth(self.pid, avg_secs= ProcMem.opts.cpu_avg_secs)
|
|
287
371
|
return self.cpu.refresh_cpu() # sets self.cpu.percent
|
|
288
372
|
|
|
373
|
+
def _get_exebasename(self, exepath, wds):
|
|
374
|
+
"""
|
|
375
|
+
Final, definitive, robust executable base name resolver.
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
# --- Phase 1: Determine the Initial Best Name Candidate ---
|
|
379
|
+
# ... (same initial logic as before, using os.readlink)
|
|
380
|
+
basename = os.path.basename(exepath)
|
|
381
|
+
is_bad_name = not basename or basename == 'exe' or len(basename) > 16 or not re.search(r'[a-zA-Z]', basename)
|
|
382
|
+
|
|
383
|
+
if is_bad_name:
|
|
384
|
+
try:
|
|
385
|
+
link_target = os.readlink(f'/proc/{self.pid}/exe')
|
|
386
|
+
if ' (deleted)' not in link_target and 'a.out' not in link_target:
|
|
387
|
+
basename = os.path.basename(link_target)
|
|
388
|
+
except (FileNotFoundError, OSError):
|
|
389
|
+
pass
|
|
390
|
+
|
|
391
|
+
# --- Phase 2: Multi-Step Aggressive Cleanup (The FINAL Final Fix) ---
|
|
392
|
+
|
|
393
|
+
# 1. Remove VQ... style hashes and generic suffixes
|
|
394
|
+
hash_and_bin_pattern = r'(VQ[A-Z0-9]{10,}|-bin|\.bin)'
|
|
395
|
+
basename = re.sub(hash_and_bin_pattern, '', basename).strip()
|
|
396
|
+
|
|
397
|
+
# 2. **CRITICAL FIX:** Remove all tab/PID/browser-related arguments from the tail.
|
|
398
|
+
# This addresses both 'browser 14 tab' AND any simple trailing numbers (like 'browser 4')
|
|
399
|
+
tab_and_num_pattern = r'(?:\s+\d+)?\s+(tab.*|socket.*|rdd.*|process.*|\d+)$'
|
|
400
|
+
basename = re.sub(tab_and_num_pattern, '', basename, flags=re.IGNORECASE).strip()
|
|
401
|
+
|
|
402
|
+
# 3. Remove common internal browser-role suffixes (renderer, rdd, content, etc.)
|
|
403
|
+
roles_pattern = r'-(renderer|gpu|utility|plugin|content|chrm|bsp|extension)$'
|
|
404
|
+
basename = re.sub(roles_pattern, '', basename, flags=re.IGNORECASE).strip()
|
|
405
|
+
|
|
406
|
+
# Final safety cleanup for any leading/trailing punctuation/spaces leftover
|
|
407
|
+
basename = re.sub(r'^\W+|\W+$', '', basename).strip()
|
|
408
|
+
|
|
409
|
+
# --- Phase 3: Last Resort Fallback ---
|
|
410
|
+
|
|
411
|
+
if not basename or basename in ('exe', 'a.out'):
|
|
412
|
+
try:
|
|
413
|
+
basename = os.path.basename(wds[0])
|
|
414
|
+
except IndexError:
|
|
415
|
+
basename = 'exe'
|
|
416
|
+
|
|
417
|
+
# Special Case Protection: Restore I3 if it was reduced
|
|
418
|
+
if basename == 'i':
|
|
419
|
+
basename = 'i3'
|
|
420
|
+
|
|
421
|
+
return basename
|
|
422
|
+
|
|
289
423
|
def get_cmdline(self):
|
|
290
424
|
"""Get the command line of the PID."""
|
|
291
|
-
try:
|
|
292
425
|
|
|
426
|
+
try:
|
|
293
427
|
cmdline_file = f'/proc/{self.pid}/cmdline'
|
|
294
428
|
try:
|
|
295
429
|
# pylint: disable=consider-using-with
|
|
296
430
|
line = open(cmdline_file, encoding='utf-8').read()[:-1]
|
|
297
431
|
except FileNotFoundError as exc:
|
|
298
|
-
# this seems to be a race which ignore; either the process is just
|
|
299
|
-
# started or just quickly ended before even identified
|
|
300
432
|
if DebugLevel:
|
|
301
433
|
DB(1, f'skip pid={self.pid} no-rollup-lines exc={type(exc).__name__}')
|
|
302
434
|
return
|
|
435
|
+
|
|
303
436
|
arguments = line.split('\0')
|
|
304
437
|
if not arguments or not arguments[0]: # kernel process
|
|
305
438
|
self.wanted, self.kernel = False, True
|
|
306
|
-
# print(f'{self.pid}: kernel thread')
|
|
307
439
|
return
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
440
|
+
|
|
441
|
+
exepath = arguments[0]
|
|
442
|
+
|
|
443
|
+
# Prepare wds (words/arguments) for use in the helper and cmdline rebuild
|
|
444
|
+
base_name_list = os.path.basename(exepath).split()
|
|
445
|
+
if base_name_list:
|
|
446
|
+
initial_basename = base_name_list.pop(0)
|
|
447
|
+
wds = base_name_list + arguments[1:]
|
|
448
|
+
else:
|
|
449
|
+
initial_basename = ''
|
|
450
|
+
wds = arguments[1:]
|
|
451
|
+
|
|
452
|
+
# Use the robust helper to determine the final, clean basename
|
|
453
|
+
self.exebasename = self._get_exebasename(exepath, wds)
|
|
454
|
+
|
|
455
|
+
# --- START ELABORATION LOGIC ---
|
|
456
|
+
|
|
457
|
+
# 1. Elaboration: The Sudo Wrapper
|
|
458
|
+
if self.exebasename == 'sudo' and wds:
|
|
459
|
+
actual_cmd_name = os.path.basename(wds[0])
|
|
460
|
+
if actual_cmd_name:
|
|
461
|
+
self.exebasename = actual_cmd_name
|
|
462
|
+
del wds[0]
|
|
463
|
+
|
|
464
|
+
# 2. Elaboration: The Interpreter (Python, Perl, etc.)
|
|
465
|
+
if self.exebasename in ('python', 'python2', 'python3', 'perl', 'bash', 'ruby',
|
|
315
466
|
'sh', 'ksh', 'zsh') and wds:
|
|
316
|
-
|
|
317
|
-
#
|
|
318
|
-
if
|
|
319
|
-
|
|
467
|
+
|
|
468
|
+
# 2.a. Catch the 'python -m <module>' pattern (The New Fix)
|
|
469
|
+
if len(wds) >= 2 and wds[0] == '-m':
|
|
470
|
+
module_name = wds[1]
|
|
471
|
+
self.exebasename = f'{self.exebasename}->{module_name}'
|
|
472
|
+
# Remove '-m' and the module name from arguments
|
|
473
|
+
del wds[0]
|
|
320
474
|
del wds[0]
|
|
321
|
-
|
|
322
|
-
|
|
475
|
+
|
|
476
|
+
# 2.b. Catch the standard '<script.py>' pattern (Original Logic)
|
|
477
|
+
elif wds[0] and os.path.exists(wds[0]) and os.path.isfile(wds[0]):
|
|
478
|
+
script = os.path.basename(wds[0])
|
|
479
|
+
if script != wds[0]:
|
|
480
|
+
# Only elaborate if it's a clear script name, not a generic main
|
|
481
|
+
if not re.search(r'^__main__\.', script):
|
|
482
|
+
self.exebasename = f'{self.exebasename}->{script}'
|
|
483
|
+
del wds[0] # Remove the script path from arguments
|
|
484
|
+
|
|
485
|
+
# --- END ELABORATION LOGIC ---
|
|
486
|
+
|
|
487
|
+
self.cmdline = ' '.join([self.exebasename] + wds)
|
|
323
488
|
self.cmdline_trunc = self.cmdline[0:ProcMem.max_cmd_len]
|
|
324
|
-
|
|
489
|
+
|
|
325
490
|
except Exception as exc:
|
|
326
|
-
#
|
|
491
|
+
# ... (rest of exception handling is the same)
|
|
327
492
|
print(f' WARNING: skip pid={self.pid} no-basename exc={exc}')
|
|
328
493
|
print(traceback.format_exc())
|
|
329
494
|
self.wanted = self.kernel = False
|
|
330
495
|
self.why_not = 'CannotGetCmdline'
|
|
331
496
|
return
|
|
332
497
|
|
|
333
|
-
## print(f'DBDB: {self.pid} {ProcMem.opts.pids}')
|
|
334
498
|
# filter unwanted before too much work
|
|
335
499
|
if (ProcMem.opts.pids and str(self.pid) not in ProcMem.opts.pids
|
|
336
500
|
and self.exebasename not in ProcMem.opts.pids):
|
|
337
501
|
self.wanted = False
|
|
338
502
|
self.why_not = 'FilteredByArgs'
|
|
339
|
-
|
|
340
|
-
## print(f'DBDB: {self.pid} wanted={self.wanted} why_not={self.why_not}')
|
|
503
|
+
|
|
341
504
|
self.set_key()
|
|
342
505
|
|
|
343
506
|
def set_key(self):
|
|
@@ -1235,7 +1398,12 @@ def rerun_module_as_root(module_name):
|
|
|
1235
1398
|
""" rerun using the module name """
|
|
1236
1399
|
if os.geteuid() != 0: # Re-run the script with sudo
|
|
1237
1400
|
os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
1238
|
-
|
|
1401
|
+
# Preserve PYTHONPATH to allow sudo to find user-installed packages
|
|
1402
|
+
pythonpath = os.pathsep.join(sys.path)
|
|
1403
|
+
env_vars = os.environ.copy()
|
|
1404
|
+
env_vars['PYTHONPATH'] = pythonpath
|
|
1405
|
+
# Use env to pass PYTHONPATH through sudo
|
|
1406
|
+
vp = ['sudo', 'env', f'PYTHONPATH={pythonpath}', sys.executable, '-m', module_name] + sys.argv[1:]
|
|
1239
1407
|
os.execvp('sudo', vp)
|
|
1240
1408
|
|
|
1241
1409
|
def main():
|
|
@@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pmemstat"
|
|
7
|
-
version = "3.0.
|
|
7
|
+
version = "3.0.4"
|
|
8
8
|
description = "Report Proportional Memory on Linux"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Joe Defen", email = "joedef@duck.com"},
|
|
@@ -20,7 +20,7 @@ classifiers = [
|
|
|
20
20
|
]
|
|
21
21
|
dependencies = [
|
|
22
22
|
'psutil > 5.9',
|
|
23
|
-
'console-window >= 1.0.
|
|
23
|
+
'console-window >= 1.0.5',
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
[project.urls]
|
pmemstat-3.0.4/runner
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# runner.py
|
|
3
|
+
# Located in the top-level <svr pmemstat> directory
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
# Add the project directory to PYTHONPATH for editable install support
|
|
9
|
+
project_dir = os.path.dirname(os.path.abspath(__file__))
|
|
10
|
+
if project_dir not in sys.path:
|
|
11
|
+
sys.path.insert(0, project_dir)
|
|
12
|
+
|
|
13
|
+
# Import and run the main module directly
|
|
14
|
+
if __name__ == '__main__':
|
|
15
|
+
sys.argv[0] = 'pmemstat'
|
|
16
|
+
from pmemstat import main
|
|
17
|
+
main.run()
|
pmemstat-3.0.2/run.sh
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|