statblk 1.26__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: statblk
3
- Version: 1.26
3
+ Version: 1.28
4
4
  Summary: Gather essential disk and partition info for block devices and print it in a nice table
5
5
  Home-page: https://github.com/yufei-pan/statblk
6
6
  Author: Yufei Pan
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: statblk
3
- Version: 1.26
3
+ Version: 1.28
4
4
  Summary: Gather essential disk and partition info for block devices and print it in a nice table
5
5
  Home-page: https://github.com/yufei-pan/statblk
6
6
  Author: Yufei Pan
@@ -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
@@ -280,10 +277,10 @@ except :
280
277
  def cache_decorator(func):
281
278
  return func
282
279
 
283
- version = '1.26'
280
+ version = '1.28'
284
281
  VERSION = version
285
282
  __version__ = version
286
- COMMIT_DATE = '2025-09-10'
283
+ COMMIT_DATE = '2025-09-11'
287
284
 
288
285
  SMARTCTL_PATH = shutil.which("smartctl")
289
286
 
@@ -503,20 +500,44 @@ 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
- 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)
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
- parent_name = get_partition_parent_name(block_device)
516
- if parent_name:
517
- if parent_name not in smart_infos:
518
- 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
- if block_device not in tptDict:
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:
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)
522
543
  mount_table = parseMount()
@@ -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 = build_symlink_dict("/dev/disk/by-uuid")
528
- label_dict = build_symlink_dict("/dev/disk/by-label")
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
- lsblk_result.thread.join()
532
- if lsblk_result.returncode == 0:
533
- for line in lsblk_result.stdout:
534
- lsblk_name, lsblk_size, lsblk_fstype, lsblk_uuid, lsblk_label = line.split(' ', 4)
535
- if lsblk_name.startswith(os.path.sep):
536
- lsblk_name = os.path.realpath(lsblk_name)
537
- # the label can be \x escaped, we need to decode it
538
- lsblk_uuid = bytes(lsblk_uuid, "utf-8").decode("unicode_escape")
539
- lsblk_fstype = bytes(lsblk_fstype, "utf-8").decode("unicode_escape")
540
- lsblk_label = bytes(lsblk_label, "utf-8").decode("unicode_escape")
541
- if lsblk_uuid:
542
- uuid_dict[lsblk_name] = lsblk_uuid
543
- if lsblk_fstype:
544
- fstype_dict[lsblk_name] = lsblk_fstype
545
- if lsblk_label:
546
- label_dict[lsblk_name] = lsblk_label
547
- try:
548
- size_dict[lsblk_name] = int(lsblk_size)
549
- except Exception:
550
- pass
551
- output = [["NAME", "FSTYPE", "SIZE", "FSUSE%", "MOUNTPOINT", "SMART", "LABEL", "UUID", "MODEL", "SERIAL", "DISCARD","READ",'WRITE']]
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
- fstype = ''
558
- size = ''
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
- model, serial = read_model_and_serial(parent_sysfs_path)
576
- discard = read_discard_support(parent_sysfs_path)
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
- smart = smartinfo.replace('PASSED', 'OK')
605
+ device_properties['SMART'] = smartinfo.replace('PASSED', 'OK')
585
606
  break
586
607
  elif "denied" in line:
587
- smart = 'DENIED'
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
- rtpt, wtpt = next(tptDict[device_name])
593
- if active_only and rtpt == 0 and wtpt == 0:
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
- rtpt = str(rtpt)
597
- wtpt = str(wtpt)
617
+ device_properties['READ'] = str(device_properties['READ'])
618
+ device_properties['WRITE'] = str(device_properties['WRITE'])
598
619
  else:
599
- rtpt = multiCMD.format_bytes(rtpt, use_1024_bytes=use_1024, to_str=True,str_format='.0f') + 'B/s'
600
- wtpt = multiCMD.format_bytes(wtpt, use_1024_bytes=use_1024, to_str=True,str_format='.0f') + 'B/s'
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
- rtpt = ''
603
- wtpt = ''
623
+ device_properties['READ'] = ''
624
+ device_properties['WRITE'] = ''
604
625
  if device_name in label_dict:
605
- label = label_dict[device_name]
626
+ device_properties['LABEL'] = label_dict[device_name]
606
627
  if device_name in uuid_dict:
607
- uuid = uuid_dict[device_name]
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
- fstype = mount_entry.FSTYPE
615
- if formated_only and not fstype:
635
+ device_properties['FSTYPE'] = mount_entry.FSTYPE
636
+ if formated_only and not device_properties['FSTYPE']:
616
637
  continue
617
- mountpoint = mount_entry.MOUNTPOINT
618
- size_bytes, used_bytes = get_statvfs_use_size(mountpoint)
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
- fsusepct = f"{int(round(100.0 * used_bytes / size_bytes))}%" if size_bytes > 0 else "N/A"
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
- size = str(size_bytes)
644
+ device_properties['SIZE'] = str(size_bytes)
624
645
  else:
625
- size = multiCMD.format_bytes(size_bytes, use_1024_bytes=use_1024, to_str=True) + 'B'
626
- if not full:
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
- fstype = fstype_dict.get(device_name, '')
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
- size = str(size_bytes)
657
+ device_properties['SIZE'] = str(size_bytes)
639
658
  else:
640
- size = multiCMD.format_bytes(size_bytes, use_1024_bytes=use_1024, to_str=True) + 'B'
641
- if not full:
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 output
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