statbtrfs 0.20__py3-none-any.whl
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.
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: statbtrfs
|
|
3
|
+
Version: 0.20
|
|
4
|
+
Summary: Query Btrfs filesystem status
|
|
5
|
+
Home-page: https://github.com/yufei-pan/statbtrfs
|
|
6
|
+
Author: Yufei Pan
|
|
7
|
+
Author-email: pan@zopyr.us
|
|
8
|
+
License: GPLv3+
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Requires-Python: >=3.6
|
|
13
|
+
Description-Content-Type: text/plain
|
|
14
|
+
Requires-Dist: prettytable
|
|
15
|
+
Requires-Dist: multiCMD
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: author-email
|
|
18
|
+
Dynamic: classifier
|
|
19
|
+
Dynamic: description
|
|
20
|
+
Dynamic: description-content-type
|
|
21
|
+
Dynamic: home-page
|
|
22
|
+
Dynamic: license
|
|
23
|
+
Dynamic: requires-dist
|
|
24
|
+
Dynamic: requires-python
|
|
25
|
+
Dynamic: summary
|
|
26
|
+
|
|
27
|
+
usage: statbtrfs [-h] [-s] [-i INTERVAL] [-m MAX_SCRUB_COUNT] [--scrub_command_lockout SCRUB_COMMAND_LOCKOUT] [-V]
|
|
28
|
+
|
|
29
|
+
Check Btrfs filesystem status.
|
|
30
|
+
|
|
31
|
+
optional arguments:
|
|
32
|
+
-h, --help show this help message and exit
|
|
33
|
+
-s, --scrub Issue scrub to all pools
|
|
34
|
+
-i INTERVAL, --interval INTERVAL
|
|
35
|
+
Interval for status check in seconds
|
|
36
|
+
-m MAX_SCRUB_COUNT, --max_scrub_count MAX_SCRUB_COUNT
|
|
37
|
+
Maximum number of scrubs to issue at the same time
|
|
38
|
+
--scrub_command_lockout SCRUB_COMMAND_LOCKOUT
|
|
39
|
+
Lockout for scrub command. Used to block two commands sent quickly.
|
|
40
|
+
-V, --version show program's version number and exit
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
Install:
|
|
44
|
+
sudo pip install statbtrfs
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
statbtrfs.py,sha256=oRLzmAzIFKQxCI5MvjXjOgRsSnqrhSuF4lEN9y1UKAg,8985
|
|
2
|
+
statbtrfs-0.20.dist-info/METADATA,sha256=oEPpduTfma9HlmIXdQndi3-4VxpM3DOFPg_-euSd5zY,1451
|
|
3
|
+
statbtrfs-0.20.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
4
|
+
statbtrfs-0.20.dist-info/entry_points.txt,sha256=vYrh-WMvmuKpaknUzxQAIn2d9r0fGqhJw1HBk3JqKxg,45
|
|
5
|
+
statbtrfs-0.20.dist-info/top_level.txt,sha256=5aCdt8cDgGV1-phlf6AZdHrUb1xV5OxgXwdiyE_qn4o,10
|
|
6
|
+
statbtrfs-0.20.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
statbtrfs
|
statbtrfs.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from prettytable import PrettyTable
|
|
3
|
+
import argparse
|
|
4
|
+
import time
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import fnmatch
|
|
8
|
+
import multiCMD
|
|
9
|
+
|
|
10
|
+
version='0.20'
|
|
11
|
+
|
|
12
|
+
# Colors
|
|
13
|
+
RED = "\033[91m"
|
|
14
|
+
GREEN = "\033[92m"
|
|
15
|
+
YELLOW = "\033[93m"
|
|
16
|
+
RESET = "\033[0m"
|
|
17
|
+
|
|
18
|
+
USE_SUDO = True
|
|
19
|
+
if os.geteuid() == 0:
|
|
20
|
+
USE_SUDO = False
|
|
21
|
+
|
|
22
|
+
if USE_SUDO:
|
|
23
|
+
#check if sudo is available
|
|
24
|
+
if multiCMD.run_command(['which','sudo'],quiet=True,return_code_only=True):
|
|
25
|
+
print(f"{RED}sudo is required to run this script.{RESET}")
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
def execute_command(command,wait=True):
|
|
29
|
+
if USE_SUDO:
|
|
30
|
+
command = ['sudo'] + command
|
|
31
|
+
return multiCMD.run_command(command,quiet=True,wait_for_return=wait)
|
|
32
|
+
|
|
33
|
+
def find_btrfs_mounts(filters):
|
|
34
|
+
mounts = execute_command(["mount", "-t", "btrfs"])
|
|
35
|
+
#mounts = multiCMD.run_command(["mount", "-t", "btrfs"],quiet=True)
|
|
36
|
+
btrfs_mounts = set()
|
|
37
|
+
if mounts:
|
|
38
|
+
if filters:
|
|
39
|
+
filters = [f'*{filter}*' for filter in filters]
|
|
40
|
+
mount_list = [line.split()[2] for line in mounts]
|
|
41
|
+
for filter in filters:
|
|
42
|
+
btrfs_mounts.update(fnmatch.filter(mount_list,filter))
|
|
43
|
+
else:
|
|
44
|
+
btrfs_mounts.update([line.split()[2] for line in mounts])
|
|
45
|
+
return btrfs_mounts
|
|
46
|
+
|
|
47
|
+
def parse_show_info(info):
|
|
48
|
+
fs_used = None
|
|
49
|
+
device_path = None
|
|
50
|
+
uuid = None
|
|
51
|
+
for line in info:
|
|
52
|
+
if "uuid:" in line:
|
|
53
|
+
uuid = line.split("uuid:")[1].split()[0].strip()
|
|
54
|
+
if "Total devices" in line:
|
|
55
|
+
fs_used = line.split("FS bytes used")[1].strip()
|
|
56
|
+
if "path" in line:
|
|
57
|
+
device_path = line.split("path")[1].strip()
|
|
58
|
+
if fs_used is None or device_path is None or uuid is None:
|
|
59
|
+
print(f"{RED}Could not parse btrfs show info.{RESET}")
|
|
60
|
+
print(f"Output from btrfs filesystem show was:\n{info}")
|
|
61
|
+
return uuid, fs_used, device_path
|
|
62
|
+
|
|
63
|
+
def parse_device_stats(stats):
|
|
64
|
+
err_dict = {}
|
|
65
|
+
for line in stats:
|
|
66
|
+
key, val = line.split()
|
|
67
|
+
err_dict[key.split(".")[-1]] = val
|
|
68
|
+
return err_dict
|
|
69
|
+
|
|
70
|
+
def color_error_count(error_count):
|
|
71
|
+
if int(error_count) == 0:
|
|
72
|
+
return f"{GREEN}{error_count}{RESET}"
|
|
73
|
+
elif int(error_count) < 10:
|
|
74
|
+
return f"{YELLOW}{error_count}{RESET}"
|
|
75
|
+
else:
|
|
76
|
+
return f"{RED}{error_count}{RESET}"
|
|
77
|
+
|
|
78
|
+
def parse_scrub_status(output):
|
|
79
|
+
scrub_data = {}
|
|
80
|
+
lines = output
|
|
81
|
+
# if "no stats available" in output:
|
|
82
|
+
# scrub_data = {
|
|
83
|
+
# "scrub_status": "Never",
|
|
84
|
+
# "scrub_time": "N/A",
|
|
85
|
+
# "scrub_rate": "N/A",
|
|
86
|
+
# "scrubbed_data": "N/A",
|
|
87
|
+
# "error_summary": "N/A"
|
|
88
|
+
# }
|
|
89
|
+
# else:
|
|
90
|
+
scrub_data = {
|
|
91
|
+
"scrub_status": "Never",
|
|
92
|
+
"scrub_time": "N/A",
|
|
93
|
+
"scrub_rate": "N/A",
|
|
94
|
+
"scrubbed_data": "N/A",
|
|
95
|
+
"error_summary": "N/A"
|
|
96
|
+
}
|
|
97
|
+
for line in lines:
|
|
98
|
+
if "Status:" in line:
|
|
99
|
+
scrub_data["scrub_status"] = line.split(":")[1].strip()
|
|
100
|
+
elif "Scrub started:" in line:
|
|
101
|
+
scrub_data["scrub_time"] = line.split(":")[1].strip()
|
|
102
|
+
elif "Rate:" in line:
|
|
103
|
+
scrub_data["scrub_rate"] = line.split(":")[1].strip()
|
|
104
|
+
elif "Total to scrub:" in line and scrub_data.get("scrub_status") != "running":
|
|
105
|
+
scrub_data["scrubbed_data"] = line.split(":")[1].strip()
|
|
106
|
+
elif "Bytes scrubbed:" in line and scrub_data.get("scrub_status") == "running":
|
|
107
|
+
scrub_data["scrubbed_data"] = line.split(":")[1].strip()
|
|
108
|
+
elif "Error summary:" in line:
|
|
109
|
+
err_idx = lines.index(line)
|
|
110
|
+
error_text = "\n".join(lines[err_idx:])
|
|
111
|
+
if "no errors found" in error_text.lower():
|
|
112
|
+
scrub_data["error_summary"] = f"{GREEN}OK{RESET}" # Green-colored OK
|
|
113
|
+
else:
|
|
114
|
+
scrub_data["error_summary"] = F"{RED}{error_text}{RESET}"
|
|
115
|
+
|
|
116
|
+
return scrub_data
|
|
117
|
+
|
|
118
|
+
def issue_scrub_command(mounts):
|
|
119
|
+
# print(f"{YELLOW}Issuing scrub on {mount}{RESET}")
|
|
120
|
+
# execute_command(['bash','-c',f"btrfs scrub start {mount}"],wait=False)
|
|
121
|
+
# limit the number of mounts to scrub to max_scrub_count
|
|
122
|
+
scrubed_mounts = []
|
|
123
|
+
for mount in mounts:
|
|
124
|
+
print(f"{YELLOW}Issuing scrub on {mount}{RESET}")
|
|
125
|
+
execute_command(['btrfs','scrub','start',mount],wait=False)
|
|
126
|
+
#multiCMD.run_command(['btrfs','scrub','start',mount],quiet=True)
|
|
127
|
+
scrubed_mounts.append(mount)
|
|
128
|
+
yield scrubed_mounts
|
|
129
|
+
return scrubed_mounts
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main():
|
|
133
|
+
parser = argparse.ArgumentParser(description="Check Btrfs filesystem status.")
|
|
134
|
+
parser.add_argument("-s", "--scrub", help="Issue scrub to all pools", action="store_true")
|
|
135
|
+
parser.add_argument("-i", "--interval", help="Interval for status check in seconds", default=2, type=int)
|
|
136
|
+
parser.add_argument("-m", "--max_scrub_count", help="Maximum number of scrubs to issue at the same time", default=32, type=int)
|
|
137
|
+
parser.add_argument("--scrub_command_lockout", help="Lockout for scrub command. Used to block two commands sent quickly.", default=10, type=int)
|
|
138
|
+
parser.add_argument('pattern',nargs='*',help='Patterns to filter btrfs moutns. Default="*"')
|
|
139
|
+
parser.add_argument('-V', '--version', action='version', version=f"%(prog)s {version} stat btrfs by pan@zopyr.us")
|
|
140
|
+
args = parser.parse_args()
|
|
141
|
+
|
|
142
|
+
btrfs_mounts = set(find_btrfs_mounts(args.pattern))
|
|
143
|
+
|
|
144
|
+
# If the scrub flag is enabled, issue the scrub command to all mounts
|
|
145
|
+
if args.scrub:
|
|
146
|
+
if args.max_scrub_count < 1:
|
|
147
|
+
scrub_count = os.cpu_count()
|
|
148
|
+
else:
|
|
149
|
+
scrub_count = args.max_scrub_count
|
|
150
|
+
if scrub_count > len(btrfs_mounts):
|
|
151
|
+
scrub_count = len(btrfs_mounts)
|
|
152
|
+
scrubber = issue_scrub_command(btrfs_mounts)
|
|
153
|
+
for i in range(scrub_count):
|
|
154
|
+
scrubed_mounts = next(scrubber)
|
|
155
|
+
print(f'Issued scrub to {GREEN}{scrubed_mounts}{RESET} mounts')
|
|
156
|
+
scrub_start_time = time.time()
|
|
157
|
+
#print(f'{GREEN}Scrub all issued!{RESET}')
|
|
158
|
+
time.sleep(args.interval)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
while True:
|
|
162
|
+
table = PrettyTable()
|
|
163
|
+
|
|
164
|
+
table.field_names = ["Mount", "Path", "FS Used", "w_err", "r_err", "bit_rot", "gen_err",
|
|
165
|
+
"scrub_status", "scrub_time", "scrub_rate", "scrubbed_data", "error_summary"]
|
|
166
|
+
|
|
167
|
+
if not btrfs_mounts:
|
|
168
|
+
print(f"{RED}No Btrfs mounts found.{RESET}")
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
for mount in btrfs_mounts:
|
|
172
|
+
mount_dir_name = mount.split("/")[-1]
|
|
173
|
+
if not mount_dir_name:
|
|
174
|
+
mount_dir_name = "/"
|
|
175
|
+
|
|
176
|
+
filesystem_show = execute_command(["btrfs", "filesystem", "show", mount])
|
|
177
|
+
#filesystem_show = multiCMD.run_command(["btrfs", "filesystem", "show", mount],quiet=True)
|
|
178
|
+
uuid, fs_used, device_path = parse_show_info(filesystem_show)
|
|
179
|
+
|
|
180
|
+
if uuid is None or fs_used is None or device_path is None:
|
|
181
|
+
print(f"{RED}Could not fetch complete info for {mount}. Skipping...{RESET}")
|
|
182
|
+
continue
|
|
183
|
+
|
|
184
|
+
device_stats = execute_command(["btrfs", "device", "stats", mount])
|
|
185
|
+
#device_stats = multiCMD.run_command(["btrfs", "device", "stats", mount],quiet=True)
|
|
186
|
+
err_dict = parse_device_stats(device_stats)
|
|
187
|
+
|
|
188
|
+
scrub_output = execute_command(["btrfs", "scrub", "status", device_path])
|
|
189
|
+
#scrub_output = multiCMD.run_command(["btrfs", "scrub", "status", device_path],quiet=True)
|
|
190
|
+
scrub_data = parse_scrub_status(scrub_output)
|
|
191
|
+
|
|
192
|
+
table.add_row([
|
|
193
|
+
mount_dir_name,
|
|
194
|
+
device_path,
|
|
195
|
+
fs_used,
|
|
196
|
+
color_error_count(err_dict['write_io_errs']),
|
|
197
|
+
color_error_count(err_dict['read_io_errs']),
|
|
198
|
+
color_error_count(err_dict['corruption_errs']),
|
|
199
|
+
color_error_count(err_dict['generation_errs']),
|
|
200
|
+
scrub_data["scrub_status"],
|
|
201
|
+
scrub_data["scrub_time"],
|
|
202
|
+
scrub_data["scrub_rate"],
|
|
203
|
+
scrub_data["scrubbed_data"],
|
|
204
|
+
scrub_data["error_summary"]
|
|
205
|
+
])
|
|
206
|
+
|
|
207
|
+
print(table)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
if not args.scrub:
|
|
211
|
+
break
|
|
212
|
+
|
|
213
|
+
# Check if all scrubs are finished.
|
|
214
|
+
scrub_status_idx = table.field_names.index("scrub_status")
|
|
215
|
+
running_scrubs = [row[scrub_status_idx] == "running" for row in table._rows]
|
|
216
|
+
# issue scrub command to next mount if there are less than max_scrub_count scrubs running
|
|
217
|
+
if sum(running_scrubs) < scrub_count and time.time() - scrub_start_time > args.scrub_command_lockout and len(scrubed_mounts) < len(btrfs_mounts):
|
|
218
|
+
scrubed_mounts = next(scrubber)
|
|
219
|
+
# print(f'Issued scrub to {GREEN}{scrubed_mounts}{RESET} mounts')
|
|
220
|
+
print(f"{GREEN}Scrubed issued to {scrubed_mounts}.{RESET}")
|
|
221
|
+
scrub_start_time = time.time()
|
|
222
|
+
|
|
223
|
+
if sum(running_scrubs) == 0 and time.time() - scrub_start_time > args.scrub_command_lockout:
|
|
224
|
+
print(f"{GREEN}All Scrubs finished.{RESET}")
|
|
225
|
+
break
|
|
226
|
+
|
|
227
|
+
time.sleep(args.interval)
|
|
228
|
+
|
|
229
|
+
if __name__ == "__main__":
|
|
230
|
+
main()
|