statbtrfs 0.20__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.
@@ -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,18 @@
1
+ usage: statbtrfs [-h] [-s] [-i INTERVAL] [-m MAX_SCRUB_COUNT] [--scrub_command_lockout SCRUB_COMMAND_LOCKOUT] [-V]
2
+
3
+ Check Btrfs filesystem status.
4
+
5
+ optional arguments:
6
+ -h, --help show this help message and exit
7
+ -s, --scrub Issue scrub to all pools
8
+ -i INTERVAL, --interval INTERVAL
9
+ Interval for status check in seconds
10
+ -m MAX_SCRUB_COUNT, --max_scrub_count MAX_SCRUB_COUNT
11
+ Maximum number of scrubs to issue at the same time
12
+ --scrub_command_lockout SCRUB_COMMAND_LOCKOUT
13
+ Lockout for scrub command. Used to block two commands sent quickly.
14
+ -V, --version show program's version number and exit
15
+
16
+
17
+ Install:
18
+ sudo pip install statbtrfs
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,30 @@
1
+ from setuptools import setup
2
+ from statbtrfs import version
3
+
4
+ setup(
5
+ name='statbtrfs',
6
+ version=version,
7
+ description='Query Btrfs filesystem status',
8
+ long_description=open('README.txt').read(),
9
+ long_description_content_type='text/plain',
10
+ author='Yufei Pan',
11
+ author_email='pan@zopyr.us',
12
+ url='https://github.com/yufei-pan/statbtrfs',
13
+ py_modules=['statbtrfs'],
14
+ entry_points={
15
+ 'console_scripts': [
16
+ 'statbtrfs=statbtrfs:main',
17
+ ],
18
+ },
19
+ install_requires=[
20
+ 'prettytable',
21
+ 'multiCMD',
22
+ ],
23
+ classifiers=[
24
+ 'Programming Language :: Python :: 3',
25
+ 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
26
+ 'Operating System :: POSIX :: Linux',
27
+ ],
28
+ python_requires='>=3.6',
29
+ license='GPLv3+',
30
+ )
@@ -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,9 @@
1
+ README.txt
2
+ setup.py
3
+ statbtrfs.py
4
+ statbtrfs.egg-info/PKG-INFO
5
+ statbtrfs.egg-info/SOURCES.txt
6
+ statbtrfs.egg-info/dependency_links.txt
7
+ statbtrfs.egg-info/entry_points.txt
8
+ statbtrfs.egg-info/requires.txt
9
+ statbtrfs.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ statbtrfs = statbtrfs:main
@@ -0,0 +1,2 @@
1
+ prettytable
2
+ multiCMD
@@ -0,0 +1 @@
1
+ statbtrfs
@@ -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()