diff --git a/makefile b/makefile index 9db3e15..f94c268 100644 --- a/makefile +++ b/makefile @@ -23,7 +23,7 @@ install: cp -rpf navigator $(DESTDIR)/usr/share/cockpit uninstall: - rm -rf $(DESTDIR)/usr/share/cockpit/samba-manager + rm -rf $(DESTDIR)/usr/share/cockpit/navigator install-local: mkdir -p $(HOME)/.local/share/cockpit diff --git a/navigator/navigator.js b/navigator/navigator.js index d9d1833..910bfa9 100644 --- a/navigator/navigator.js +++ b/navigator/navigator.js @@ -360,7 +360,7 @@ class NavDir extends NavEntry { async cephfs_dir_stats() { try { var proc = await cockpit.spawn( - ["cephfs-dir-stats", "-j", this.path_str()], + ["/usr/share/cockpit/navigator/scripts/cephfs-dir-stats.py", "-j", this.path_str()], {err: "ignore"} ); return JSON.parse(proc)[0]; diff --git a/navigator/scripts/cephfs-dir-stats.py b/navigator/scripts/cephfs-dir-stats.py new file mode 100755 index 0000000..2ad3dfe --- /dev/null +++ b/navigator/scripts/cephfs-dir-stats.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 + +import sys +import re +import subprocess +import math +import json +from optparse import OptionParser + +############################################################################### +# Name: dir_attributes +# Args: path to directory, command to run +# Desc: executes getfattr to retrive ceph dir attribute and returns as dictionary +############################################################################### + + +def dir_attributes(path, type, command): + attrs = {} + try: + child = subprocess.Popen( + ["getfattr", "-n", "ceph." + type + "." + command, path], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + output_string, err = child.communicate() + except OSError: + print("Error executing getfattr. Is xattr installed?") + sys.exit(1) + if child.wait() != 0: + if err.find("No such attribute") != -1: + return {} + else: + print("Error executing getfattr. Is xattr installed?") + sys.exit(1) + # return {} + fields = re.findall( + r"^ceph\.[dir|quota]+\.([^=]+)=\"([^\"]+)\"$", output_string, re.MULTILINE) + if len(fields) == 0: + print(f'No ceph xattrs, is {path} in a ceph filesystem?') + sys.exit(1) + attrs[fields[0][0]] = fields[0][1] + return attrs + + +############################################################################### +# Name: run_dir_commands +# Args: path to directory +# Desc: calls dir_attributes and quota_attrivutes multiple times each with a different command +# Retu: Returns the outputs of all commands in a dictionary +############################################################################### + + +def run_dir_commands(path): + outputs = {'path':path} + dirList = ["entries", "files", "rbytes", "rentries", + "rfiles", "rsubdirs", "subdirs", "layout.pool"] + quotaList = ["max_files", "max_bytes"] + for items in dirList: + outputs.update(dir_attributes(path, "dir", items)) + for items in quotaList: + outputs.update(dir_attributes(path, "quota", items)) + + if "rbytes" in outputs.keys(): + outputs["rbytes"] = format_bytes(int(outputs["rbytes"])) + if "max_bytes" in outputs.keys(): + outputs["max_bytes"] = format_bytes(int(outputs["max_bytes"])) + + return outputs + +############################################################################### +# Name: display_attributes +# Args: path to directory +# Desc: calls run_dir_commands and prints output +############################################################################### + + +def display_attributes(path): + attrs = run_dir_commands(path) + + print(path, ":") + max_width = len( + max(attrs.values(), key=lambda x: len(x.split(" ")[0])).split(" ")[0]) + print("Files: ", "{0:>{1}}".format( + attrs["files"], max_width) if "files" in attrs.keys() else "N/A") + print("Directories: ", "{0:>{1}}".format( + attrs["subdirs"], max_width) if "subdirs" in attrs.keys() else "N/A") + print("Recursive Files: ", "{0:>{1}}".format( + attrs["rfiles"], max_width) if "rfiles" in attrs.keys() else "N/A") + print("Recursive Directories: ", "{0:>{1}}".format( + attrs["rsubdirs"], max_width) if "rsubdirs" in attrs.keys() else "N/A") + print("Total Size: ", "{0:>{1}}".format(attrs["rbytes"].split(" ")[ + 0], max_width) + " " + attrs["rbytes"].split(" ")[1] if "rbytes" in attrs.keys() else "N/A") + print("Layout Pool: ", "{0:>{1}}".format( + attrs["layout.pool"], max_width) if "layout.pool" in attrs.keys() else "N/A") + print("Max Files: ", "{0:>{1}}".format( + attrs["max_files"], max_width) if "max_files" in attrs.keys() else " N/A") + print("Max Bytes: ", "{0:>{1}}".format( + attrs["max_bytes"], max_width) if "max_bytes" in attrs.keys() else " N/A") + print() + +################################################################################ +# Name: format_bytes +# Args: integer value in bytes +# Desc: formats size_bytes in SI base units and returns as string +################################################################################ + + +def format_bytes(size_bytes): + if size_bytes == 0: + return "0 B" + size_name = ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB") + i = int(math.floor(math.log(size_bytes, 1024))) + p = math.pow(1024, i) + s = round(size_bytes / p, 2) + return "%s %s" % (s, size_name[i]) + +############################################################################### +# Name: main (cephfs-dir-stats) +# Args: (see parser) +# Desc: lists recursive ceph stats of specified directory +############################################################################### + + +def main(): + parser = OptionParser() + parser.add_option("-j", "--json", help="output stats in JSON format.", action="store_true", dest="json", default=False) + (options, args) = parser.parse_args() + if len(args) == 0: + args = ["."] + if(options.json): + obj = [] + for arg in args: + obj.append(run_dir_commands(arg)) + obj = json.dumps(obj) + print(obj) + else: + for arg in args: + display_attributes(arg) + + +if __name__ == "__main__": + main()