statblk 1.26__tar.gz → 1.29__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.
- {statblk-1.26 → statblk-1.29}/PKG-INFO +1 -1
- {statblk-1.26 → statblk-1.29}/statblk.egg-info/PKG-INFO +1 -1
- {statblk-1.26 → statblk-1.29}/statblk.py +100 -81
- {statblk-1.26 → statblk-1.29}/README.txt +0 -0
- {statblk-1.26 → statblk-1.29}/setup.cfg +0 -0
- {statblk-1.26 → statblk-1.29}/setup.py +0 -0
- {statblk-1.26 → statblk-1.29}/statblk.egg-info/SOURCES.txt +0 -0
- {statblk-1.26 → statblk-1.29}/statblk.egg-info/dependency_links.txt +0 -0
- {statblk-1.26 → statblk-1.29}/statblk.egg-info/entry_points.txt +0 -0
- {statblk-1.26 → statblk-1.29}/statblk.egg-info/requires.txt +0 -0
- {statblk-1.26 → statblk-1.29}/statblk.egg-info/top_level.txt +0 -0
@@ -1,9 +1,6 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
# requires-python = ">=3.6"
|
3
3
|
# -*- coding: utf-8 -*-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
4
|
import os
|
8
5
|
import re
|
9
6
|
import stat
|
@@ -13,11 +10,11 @@ import shutil
|
|
13
10
|
import subprocess
|
14
11
|
try:
|
15
12
|
import multiCMD
|
16
|
-
assert float(multiCMD.version) >= 1.
|
13
|
+
assert float(multiCMD.version) >= 1.37
|
17
14
|
except:
|
18
15
|
import time,threading,io,sys,subprocess,select,string,re,itertools,signal
|
19
16
|
class multiCMD:
|
20
|
-
version='1.
|
17
|
+
version='1.37_min_statblk'
|
21
18
|
__version__=version
|
22
19
|
COMMIT_DATE='2025-09-10'
|
23
20
|
__running_threads=set()
|
@@ -168,7 +165,7 @@ except:
|
|
168
165
|
E=A
|
169
166
|
def V(hdr,rows_):
|
170
167
|
B=hdr;C=[0]*len(B)
|
171
|
-
for A in range(len(B)):C[A]=max(J(B[A]),*(J(B[A])for B in rows_ if A<len(B)))
|
168
|
+
for A in range(len(B)):C[A]=max(0,J(B[A]),*(J(B[A])for B in rows_ if A<len(B)))
|
172
169
|
return C
|
173
170
|
P=[]
|
174
171
|
for F in E:
|
@@ -280,10 +277,10 @@ except :
|
|
280
277
|
def cache_decorator(func):
|
281
278
|
return func
|
282
279
|
|
283
|
-
version = '1.
|
280
|
+
version = '1.29'
|
284
281
|
VERSION = version
|
285
282
|
__version__ = version
|
286
|
-
COMMIT_DATE = '2025-09-
|
283
|
+
COMMIT_DATE = '2025-09-11'
|
287
284
|
|
288
285
|
SMARTCTL_PATH = shutil.which("smartctl")
|
289
286
|
|
@@ -503,19 +500,43 @@ def get_read_write_rate_throughput_iter(sysfs_block_path):
|
|
503
500
|
except Exception:
|
504
501
|
yield 0, 0
|
505
502
|
|
503
|
+
ALL_OUTPUT_FIELDS = ["NAME", "FSTYPE", "SIZE", "FSUSE%", "MOUNTPOINT", "SMART", "LABEL", "UUID", "MODEL", "SERIAL", "DISCARD", "READ", "WRITE"]
|
504
|
+
|
506
505
|
# DRIVE_INFO = namedtuple("DRIVE_INFO",
|
507
506
|
# ["NAME", "FSTYPE", "SIZE", "FSUSEPCT", "MOUNTPOINT", "SMART","RTPT",'WTPT', "LABEL", "UUID", "MODEL", "SERIAL", "DISCARD"])
|
508
507
|
def get_drives_info(print_bytes = False, use_1024 = False, mounted_only=False, best_only=False,
|
509
508
|
formated_only=False, show_zero_size_devices=False,pseudo=False,tptDict = {},
|
510
|
-
full=False,active_only=False):
|
511
|
-
|
509
|
+
full=False,active_only=False,output="all",exclude=""):
|
510
|
+
global SMARTCTL_PATH
|
511
|
+
global ALL_OUTPUT_FIELDS
|
512
|
+
if output == "all":
|
513
|
+
output_fields = ALL_OUTPUT_FIELDS
|
514
|
+
else:
|
515
|
+
output_fields = [x.strip().upper() for x in output.split(',')]
|
516
|
+
for field in output_fields:
|
517
|
+
if field not in ALL_OUTPUT_FIELDS:
|
518
|
+
print(f"Ignoring invalid output field: {field}.", file=sys.stderr)
|
519
|
+
output_fields.remove(field)
|
520
|
+
if exclude:
|
521
|
+
exclude_fields = [x.strip().upper() for x in exclude.split(',')]
|
522
|
+
for field in exclude_fields:
|
523
|
+
if field in output_fields:
|
524
|
+
output_fields.remove(field)
|
525
|
+
if not output_fields:
|
526
|
+
print("No valid output fields specified.", file=sys.stderr)
|
527
|
+
return []
|
528
|
+
output_list = [output_fields]
|
529
|
+
output_fields_set = set(output_fields)
|
530
|
+
if {'SIZE','FSTYPE','UUID','LABEL'}.intersection(output_fields_set):
|
531
|
+
lsblk_result = multiCMD.run_command(f'lsblk -brnp -o NAME,SIZE,FSTYPE,UUID,LABEL',timeout=2,quiet=True,wait_for_return=False,return_object=True)
|
512
532
|
block_devices = get_blocks()
|
513
533
|
smart_infos = {}
|
514
534
|
for block_device in block_devices:
|
515
|
-
|
516
|
-
|
517
|
-
if parent_name
|
518
|
-
|
535
|
+
if 'SMART' in output_fields_set:
|
536
|
+
parent_name = get_partition_parent_name(block_device)
|
537
|
+
if parent_name:
|
538
|
+
if parent_name not in smart_infos:
|
539
|
+
smart_infos[parent_name] = multiCMD.run_command(f'{SMARTCTL_PATH} -H {parent_name}',timeout=2,quiet=True,wait_for_return=False,return_object=True)
|
519
540
|
if block_device not in tptDict:
|
520
541
|
sysfs_block_path = os.path.join('/sys/class/block', os.path.basename(block_device))
|
521
542
|
tptDict[block_device] = get_read_write_rate_throughput_iter(sysfs_block_path)
|
@@ -524,56 +545,56 @@ def get_drives_info(print_bytes = False, use_1024 = False, mounted_only=False, b
|
|
524
545
|
if pseudo:
|
525
546
|
target_devices.update(mount_table.keys())
|
526
547
|
target_devices = sorted(target_devices)
|
527
|
-
uuid_dict =
|
528
|
-
|
548
|
+
uuid_dict = {}
|
549
|
+
if 'UUID' in output_fields_set:
|
550
|
+
uuid_dict = build_symlink_dict("/dev/disk/by-uuid")
|
551
|
+
label_dict = {}
|
552
|
+
if 'LABEL' in output_fields_set:
|
553
|
+
label_dict = build_symlink_dict("/dev/disk/by-label")
|
529
554
|
fstype_dict = {}
|
530
555
|
size_dict = {}
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
lsblk_name
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
556
|
+
if {'SIZE','FSTYPE','UUID','LABEL'}.intersection(output_fields_set):
|
557
|
+
lsblk_result.thread.join()
|
558
|
+
if lsblk_result.returncode == 0:
|
559
|
+
for line in lsblk_result.stdout:
|
560
|
+
lsblk_name, lsblk_size, lsblk_fstype, lsblk_uuid, lsblk_label = line.split(' ', 4)
|
561
|
+
if lsblk_name.startswith(os.path.sep):
|
562
|
+
lsblk_name = os.path.realpath(lsblk_name)
|
563
|
+
# the label can be \x escaped, we need to decode it
|
564
|
+
if 'UUID' in output_fields_set:
|
565
|
+
lsblk_uuid = bytes(lsblk_uuid, "utf-8").decode("unicode_escape")
|
566
|
+
if lsblk_uuid:
|
567
|
+
uuid_dict[lsblk_name] = lsblk_uuid
|
568
|
+
if 'FSTYPE' in output_fields_set:
|
569
|
+
lsblk_fstype = bytes(lsblk_fstype, "utf-8").decode("unicode_escape")
|
570
|
+
if lsblk_fstype:
|
571
|
+
fstype_dict[lsblk_name] = lsblk_fstype
|
572
|
+
if 'LABEL' in output_fields_set:
|
573
|
+
lsblk_label = bytes(lsblk_label, "utf-8").decode("unicode_escape")
|
574
|
+
if lsblk_label:
|
575
|
+
label_dict[lsblk_name] = lsblk_label
|
576
|
+
if 'SIZE' in output_fields_set:
|
577
|
+
try:
|
578
|
+
size_dict[lsblk_name] = int(lsblk_size)
|
579
|
+
except Exception:
|
580
|
+
pass
|
552
581
|
for device_name in target_devices:
|
553
582
|
if mounted_only and device_name not in mount_table:
|
554
583
|
continue
|
555
584
|
if active_only and device_name not in tptDict:
|
556
585
|
continue
|
557
|
-
|
558
|
-
|
559
|
-
fsusepct = ''
|
560
|
-
mountpoint = ''
|
561
|
-
smart = ''
|
562
|
-
label = ''
|
563
|
-
uuid = ''
|
564
|
-
model = ''
|
565
|
-
serial = ''
|
566
|
-
discard = ''
|
567
|
-
rtpt = ''
|
568
|
-
wtpt = ''
|
586
|
+
device_properties = defaultdict(str)
|
587
|
+
device_properties['NAME'] = device_name if full else device_name.replace('/dev/', '')
|
569
588
|
# fstype, size, fsuse%, mountpoint, rtpt, wtpt, lable, uuid are partition specific
|
570
589
|
# smart, model, serial, discard are device specific, and only for block devices
|
571
590
|
# fstype, size, fsuse%, mountpoint does not require block device and can have multiple values per device
|
572
591
|
if is_block_device(device_name):
|
573
592
|
parent_name = get_partition_parent_name(device_name)
|
574
593
|
parent_sysfs_path = os.path.realpath(os.path.join('/sys/class/block', os.path.basename(parent_name))) if parent_name else None
|
575
|
-
|
576
|
-
|
594
|
+
if 'MODEL' in output_fields_set or 'SERIAL' in output_fields_set:
|
595
|
+
device_properties['MODEL'], device_properties['SERIAL'] = read_model_and_serial(parent_sysfs_path)
|
596
|
+
if 'DISCARD' in output_fields_set:
|
597
|
+
device_properties['DISCARD'] = read_discard_support(parent_sysfs_path)
|
577
598
|
if parent_name in smart_infos and SMARTCTL_PATH:
|
578
599
|
smart_info_obj = smart_infos[parent_name]
|
579
600
|
smart_info_obj.thread.join()
|
@@ -581,68 +602,64 @@ def get_drives_info(print_bytes = False, use_1024 = False, mounted_only=False, b
|
|
581
602
|
line = line.lower()
|
582
603
|
if "health" in line:
|
583
604
|
smartinfo = line.rpartition(':')[2].strip().upper()
|
584
|
-
|
605
|
+
device_properties['SMART'] = smartinfo.replace('PASSED', 'OK')
|
585
606
|
break
|
586
607
|
elif "denied" in line:
|
587
|
-
|
608
|
+
device_properties['SMART'] = 'DENIED'
|
588
609
|
break
|
589
610
|
size_bytes = read_size(os.path.join('/sys/class/block', os.path.basename(device_name)))
|
590
611
|
if device_name in tptDict:
|
591
612
|
try:
|
592
|
-
|
593
|
-
if active_only and
|
613
|
+
device_properties['READ'], device_properties['WRITE'] = next(tptDict[device_name])
|
614
|
+
if active_only and device_properties['READ'] == 0 and device_properties['WRITE'] == 0:
|
594
615
|
continue
|
595
616
|
if print_bytes:
|
596
|
-
|
597
|
-
|
617
|
+
device_properties['READ'] = str(device_properties['READ'])
|
618
|
+
device_properties['WRITE'] = str(device_properties['WRITE'])
|
598
619
|
else:
|
599
|
-
|
600
|
-
|
620
|
+
device_properties['READ'] = multiCMD.format_bytes(device_properties['READ'], use_1024_bytes=use_1024, to_str=True,str_format='.0f') + 'B/s'
|
621
|
+
device_properties['WRITE'] = multiCMD.format_bytes(device_properties['WRITE'], use_1024_bytes=use_1024, to_str=True,str_format='.0f') + 'B/s'
|
601
622
|
except Exception:
|
602
|
-
|
603
|
-
|
623
|
+
device_properties['READ'] = ''
|
624
|
+
device_properties['WRITE'] = ''
|
604
625
|
if device_name in label_dict:
|
605
|
-
|
626
|
+
device_properties['LABEL'] = label_dict[device_name]
|
606
627
|
if device_name in uuid_dict:
|
607
|
-
|
628
|
+
device_properties['UUID'] = uuid_dict[device_name]
|
608
629
|
mount_points = mount_table.get(device_name, [])
|
609
630
|
if best_only:
|
610
631
|
if mount_points:
|
611
632
|
mount_points = [sorted(mount_points, key=lambda x: len(x.MOUNTPOINT))[0]]
|
612
633
|
if mount_points:
|
613
634
|
for mount_entry in mount_points:
|
614
|
-
|
615
|
-
if formated_only and not
|
635
|
+
device_properties['FSTYPE'] = mount_entry.FSTYPE
|
636
|
+
if formated_only and not device_properties['FSTYPE']:
|
616
637
|
continue
|
617
|
-
|
618
|
-
size_bytes, used_bytes = get_statvfs_use_size(
|
638
|
+
device_properties['MOUNTPOINT'] = mount_entry.MOUNTPOINT
|
639
|
+
size_bytes, used_bytes = get_statvfs_use_size(device_properties['MOUNTPOINT'])
|
619
640
|
if size_bytes == 0 and not show_zero_size_devices:
|
620
641
|
continue
|
621
|
-
|
642
|
+
device_properties['FSUSE%'] = f"{int(round(100.0 * used_bytes / size_bytes))}%" if size_bytes > 0 else "N/A"
|
622
643
|
if print_bytes:
|
623
|
-
|
644
|
+
device_properties['SIZE'] = str(size_bytes)
|
624
645
|
else:
|
625
|
-
|
626
|
-
|
627
|
-
device_name = device_name.replace('/dev/', '')
|
628
|
-
output.append([device_name, fstype, size, fsusepct, mountpoint, smart, label, uuid, model, serial, discard, rtpt, wtpt])
|
646
|
+
device_properties['SIZE'] = multiCMD.format_bytes(size_bytes, use_1024_bytes=use_1024, to_str=True) + 'B'
|
647
|
+
output_list.append([device_properties[output_field] for output_field in output_fields])
|
629
648
|
else:
|
630
649
|
if formated_only and device_name not in fstype_dict:
|
631
650
|
continue
|
632
|
-
|
651
|
+
device_properties['FSTYPE'] = fstype_dict.get(device_name, '')
|
633
652
|
if not size_bytes:
|
634
653
|
size_bytes = size_dict.get(device_name, 0)
|
635
654
|
if size_bytes == 0 and not show_zero_size_devices:
|
636
655
|
continue
|
637
656
|
if print_bytes:
|
638
|
-
|
657
|
+
device_properties['SIZE'] = str(size_bytes)
|
639
658
|
else:
|
640
|
-
|
641
|
-
|
642
|
-
device_name = device_name.replace('/dev/', '')
|
643
|
-
output.append([device_name, fstype, size, fsusepct, mountpoint, smart, label, uuid, model, serial, discard, rtpt, wtpt])
|
659
|
+
device_properties['SIZE'] = multiCMD.format_bytes(size_bytes, use_1024_bytes=use_1024, to_str=True) + 'B'
|
660
|
+
output_list.append([device_properties[output_field] for output_field in output_fields])
|
644
661
|
multiCMD.join_threads()
|
645
|
-
return
|
662
|
+
return output_list
|
646
663
|
|
647
664
|
|
648
665
|
def main():
|
@@ -656,6 +673,8 @@ def main():
|
|
656
673
|
parser.add_argument('-A','-ao','--active_only', help="Show only active devices (positive read/write activity)", action="store_true")
|
657
674
|
parser.add_argument('-R','--full', help="Show full device information, do not collapse drive info when length > console length", action="store_true")
|
658
675
|
parser.add_argument('-P','--pseudo', help="Include pseudo file systems as well (tmpfs / nfs / cifs etc.)", action="store_true")
|
676
|
+
parser.add_argument('-o','--output', help="Specify which output columns to print.Use comma to separate columns. default: all available", default="all", type=str)
|
677
|
+
parser.add_argument('-x','--exclude', help="Specify which output columns to exclude.Use comma to separate columns. default: none", default="", type=str)
|
659
678
|
parser.add_argument('--show_zero_size_devices', help="Show devices with zero size", action="store_true")
|
660
679
|
parser.add_argument('print_period', nargs='?', default=0, type=int, help="If specified as a number, repeat the output every N seconds")
|
661
680
|
parser.add_argument('-V', '--version', action='version', version=f"%(prog)s {version} @ {COMMIT_DATE} stat drives by pan@zopyr.us")
|
@@ -666,7 +685,7 @@ def main():
|
|
666
685
|
results = get_drives_info(print_bytes = args.bytes, use_1024 = not args.si,
|
667
686
|
mounted_only=args.mounted_only, best_only=args.best_only,
|
668
687
|
formated_only=args.formated_only, show_zero_size_devices=args.show_zero_size_devices,
|
669
|
-
pseudo=args.pseudo,tptDict=tptDict,full=args.full,active_only=args.active_only)
|
688
|
+
pseudo=args.pseudo,tptDict=tptDict,full=args.full,active_only=args.active_only,output=args.output,exclude=args.exclude)
|
670
689
|
if args.json:
|
671
690
|
import json
|
672
691
|
print(json.dumps(results, indent=1))
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|