statblk 1.25__tar.gz → 1.28__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.25 → statblk-1.28}/PKG-INFO +1 -1
- {statblk-1.25 → statblk-1.28}/statblk.egg-info/PKG-INFO +1 -1
- {statblk-1.25 → statblk-1.28}/statblk.py +103 -79
- {statblk-1.25 → statblk-1.28}/README.txt +0 -0
- {statblk-1.25 → statblk-1.28}/setup.cfg +0 -0
- {statblk-1.25 → statblk-1.28}/setup.py +0 -0
- {statblk-1.25 → statblk-1.28}/statblk.egg-info/SOURCES.txt +0 -0
- {statblk-1.25 → statblk-1.28}/statblk.egg-info/dependency_links.txt +0 -0
- {statblk-1.25 → statblk-1.28}/statblk.egg-info/entry_points.txt +0 -0
- {statblk-1.25 → statblk-1.28}/statblk.egg-info/requires.txt +0 -0
- {statblk-1.25 → statblk-1.28}/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
|
@@ -139,6 +136,10 @@ except:
|
|
139
136
|
elif return_object:return A
|
140
137
|
elif with_stdErr:return[A.stdout+A.stderr for A in A]
|
141
138
|
else:return[A.stdout for A in A]
|
139
|
+
def join_threads(threads=...,timeout=None):
|
140
|
+
A=multiCMD.__running_threads if threads is ... else threads
|
141
|
+
for B in A:B.join(timeout=timeout)
|
142
|
+
if A is multiCMD.__running_threads:multiCMD.__running_threads={A for A in A if A.is_alive()}
|
142
143
|
def pretty_format_table(data,delimiter='\t',header=None,full=False):
|
143
144
|
O=delimiter;B=header;A=data;import re;S=1.12;Z=S
|
144
145
|
def J(s):return len(re.sub('\\x1b\\[[0-?]*[ -/]*[@-~]','',s))
|
@@ -276,10 +277,10 @@ except :
|
|
276
277
|
def cache_decorator(func):
|
277
278
|
return func
|
278
279
|
|
279
|
-
version = '1.
|
280
|
+
version = '1.28'
|
280
281
|
VERSION = version
|
281
282
|
__version__ = version
|
282
|
-
COMMIT_DATE = '2025-09-
|
283
|
+
COMMIT_DATE = '2025-09-11'
|
283
284
|
|
284
285
|
SMARTCTL_PATH = shutil.which("smartctl")
|
285
286
|
|
@@ -499,20 +500,44 @@ def get_read_write_rate_throughput_iter(sysfs_block_path):
|
|
499
500
|
except Exception:
|
500
501
|
yield 0, 0
|
501
502
|
|
503
|
+
ALL_OUTPUT_FIELDS = ["NAME", "FSTYPE", "SIZE", "FSUSE%", "MOUNTPOINT", "SMART", "LABEL", "UUID", "MODEL", "SERIAL", "DISCARD", "READ", "WRITE"]
|
504
|
+
|
502
505
|
# DRIVE_INFO = namedtuple("DRIVE_INFO",
|
503
506
|
# ["NAME", "FSTYPE", "SIZE", "FSUSEPCT", "MOUNTPOINT", "SMART","RTPT",'WTPT', "LABEL", "UUID", "MODEL", "SERIAL", "DISCARD"])
|
504
507
|
def get_drives_info(print_bytes = False, use_1024 = False, mounted_only=False, best_only=False,
|
505
508
|
formated_only=False, show_zero_size_devices=False,pseudo=False,tptDict = {},
|
506
|
-
full=False,active_only=False):
|
507
|
-
|
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)
|
508
532
|
block_devices = get_blocks()
|
509
533
|
smart_infos = {}
|
510
534
|
for block_device in block_devices:
|
511
|
-
|
512
|
-
|
513
|
-
if parent_name
|
514
|
-
|
515
|
-
|
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)
|
540
|
+
if ('READ' in output_fields_set or 'WRITE' in output_fields_set) and block_device not in tptDict:
|
516
541
|
sysfs_block_path = os.path.join('/sys/class/block', os.path.basename(block_device))
|
517
542
|
tptDict[block_device] = get_read_write_rate_throughput_iter(sysfs_block_path)
|
518
543
|
mount_table = parseMount()
|
@@ -520,56 +545,56 @@ def get_drives_info(print_bytes = False, use_1024 = False, mounted_only=False, b
|
|
520
545
|
if pseudo:
|
521
546
|
target_devices.update(mount_table.keys())
|
522
547
|
target_devices = sorted(target_devices)
|
523
|
-
uuid_dict =
|
524
|
-
|
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")
|
525
554
|
fstype_dict = {}
|
526
555
|
size_dict = {}
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
lsblk_name
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
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
|
548
581
|
for device_name in target_devices:
|
549
582
|
if mounted_only and device_name not in mount_table:
|
550
583
|
continue
|
551
584
|
if active_only and device_name not in tptDict:
|
552
585
|
continue
|
553
|
-
|
554
|
-
|
555
|
-
fsusepct = ''
|
556
|
-
mountpoint = ''
|
557
|
-
smart = ''
|
558
|
-
label = ''
|
559
|
-
uuid = ''
|
560
|
-
model = ''
|
561
|
-
serial = ''
|
562
|
-
discard = ''
|
563
|
-
rtpt = ''
|
564
|
-
wtpt = ''
|
586
|
+
device_properties = defaultdict(str)
|
587
|
+
device_properties['NAME'] = device_name if full else device_name.replace('/dev/', '')
|
565
588
|
# fstype, size, fsuse%, mountpoint, rtpt, wtpt, lable, uuid are partition specific
|
566
589
|
# smart, model, serial, discard are device specific, and only for block devices
|
567
590
|
# fstype, size, fsuse%, mountpoint does not require block device and can have multiple values per device
|
568
591
|
if is_block_device(device_name):
|
569
592
|
parent_name = get_partition_parent_name(device_name)
|
570
593
|
parent_sysfs_path = os.path.realpath(os.path.join('/sys/class/block', os.path.basename(parent_name))) if parent_name else None
|
571
|
-
|
572
|
-
|
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)
|
573
598
|
if parent_name in smart_infos and SMARTCTL_PATH:
|
574
599
|
smart_info_obj = smart_infos[parent_name]
|
575
600
|
smart_info_obj.thread.join()
|
@@ -577,67 +602,64 @@ def get_drives_info(print_bytes = False, use_1024 = False, mounted_only=False, b
|
|
577
602
|
line = line.lower()
|
578
603
|
if "health" in line:
|
579
604
|
smartinfo = line.rpartition(':')[2].strip().upper()
|
580
|
-
|
605
|
+
device_properties['SMART'] = smartinfo.replace('PASSED', 'OK')
|
581
606
|
break
|
582
607
|
elif "denied" in line:
|
583
|
-
|
608
|
+
device_properties['SMART'] = 'DENIED'
|
584
609
|
break
|
585
610
|
size_bytes = read_size(os.path.join('/sys/class/block', os.path.basename(device_name)))
|
586
611
|
if device_name in tptDict:
|
587
612
|
try:
|
588
|
-
|
589
|
-
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:
|
590
615
|
continue
|
591
616
|
if print_bytes:
|
592
|
-
|
593
|
-
|
617
|
+
device_properties['READ'] = str(device_properties['READ'])
|
618
|
+
device_properties['WRITE'] = str(device_properties['WRITE'])
|
594
619
|
else:
|
595
|
-
|
596
|
-
|
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'
|
597
622
|
except Exception:
|
598
|
-
|
599
|
-
|
623
|
+
device_properties['READ'] = ''
|
624
|
+
device_properties['WRITE'] = ''
|
600
625
|
if device_name in label_dict:
|
601
|
-
|
626
|
+
device_properties['LABEL'] = label_dict[device_name]
|
602
627
|
if device_name in uuid_dict:
|
603
|
-
|
628
|
+
device_properties['UUID'] = uuid_dict[device_name]
|
604
629
|
mount_points = mount_table.get(device_name, [])
|
605
630
|
if best_only:
|
606
631
|
if mount_points:
|
607
632
|
mount_points = [sorted(mount_points, key=lambda x: len(x.MOUNTPOINT))[0]]
|
608
633
|
if mount_points:
|
609
634
|
for mount_entry in mount_points:
|
610
|
-
|
611
|
-
if formated_only and not
|
635
|
+
device_properties['FSTYPE'] = mount_entry.FSTYPE
|
636
|
+
if formated_only and not device_properties['FSTYPE']:
|
612
637
|
continue
|
613
|
-
|
614
|
-
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'])
|
615
640
|
if size_bytes == 0 and not show_zero_size_devices:
|
616
641
|
continue
|
617
|
-
|
642
|
+
device_properties['FSUSE%'] = f"{int(round(100.0 * used_bytes / size_bytes))}%" if size_bytes > 0 else "N/A"
|
618
643
|
if print_bytes:
|
619
|
-
|
644
|
+
device_properties['SIZE'] = str(size_bytes)
|
620
645
|
else:
|
621
|
-
|
622
|
-
|
623
|
-
device_name = device_name.replace('/dev/', '')
|
624
|
-
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])
|
625
648
|
else:
|
626
649
|
if formated_only and device_name not in fstype_dict:
|
627
650
|
continue
|
628
|
-
|
651
|
+
device_properties['FSTYPE'] = fstype_dict.get(device_name, '')
|
629
652
|
if not size_bytes:
|
630
653
|
size_bytes = size_dict.get(device_name, 0)
|
631
654
|
if size_bytes == 0 and not show_zero_size_devices:
|
632
655
|
continue
|
633
656
|
if print_bytes:
|
634
|
-
|
657
|
+
device_properties['SIZE'] = str(size_bytes)
|
635
658
|
else:
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
return output
|
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])
|
661
|
+
multiCMD.join_threads()
|
662
|
+
return output_list
|
641
663
|
|
642
664
|
|
643
665
|
def main():
|
@@ -651,6 +673,8 @@ def main():
|
|
651
673
|
parser.add_argument('-A','-ao','--active_only', help="Show only active devices (positive read/write activity)", action="store_true")
|
652
674
|
parser.add_argument('-R','--full', help="Show full device information, do not collapse drive info when length > console length", action="store_true")
|
653
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)
|
654
678
|
parser.add_argument('--show_zero_size_devices', help="Show devices with zero size", action="store_true")
|
655
679
|
parser.add_argument('print_period', nargs='?', default=0, type=int, help="If specified as a number, repeat the output every N seconds")
|
656
680
|
parser.add_argument('-V', '--version', action='version', version=f"%(prog)s {version} @ {COMMIT_DATE} stat drives by pan@zopyr.us")
|
@@ -661,7 +685,7 @@ def main():
|
|
661
685
|
results = get_drives_info(print_bytes = args.bytes, use_1024 = not args.si,
|
662
686
|
mounted_only=args.mounted_only, best_only=args.best_only,
|
663
687
|
formated_only=args.formated_only, show_zero_size_devices=args.show_zero_size_devices,
|
664
|
-
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)
|
665
689
|
if args.json:
|
666
690
|
import json
|
667
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
|