diff options
Diffstat (limited to 'glustolibs-io')
-rw-r--r-- | glustolibs-io/glustolibs/io/memory_and_cpu_utils.py | 924 | ||||
-rwxr-xr-x | glustolibs-io/glustolibs/io/utils.py | 389 | ||||
-rw-r--r-- | glustolibs-io/setup.py | 38 | ||||
-rwxr-xr-x | glustolibs-io/shared_files/scripts/fd_writes.py | 68 | ||||
-rwxr-xr-x | glustolibs-io/shared_files/scripts/file_dir_ops.py | 473 | ||||
-rw-r--r-- | glustolibs-io/shared_files/scripts/file_lock.py | 51 | ||||
-rwxr-xr-x | glustolibs-io/shared_files/scripts/generate_io.py | 246 | ||||
-rw-r--r-- | glustolibs-io/shared_files/scripts/memory_and_cpu_logger.py | 108 | ||||
-rw-r--r-- | glustolibs-io/shared_files/tools/fio/run_fio.py | 18 |
9 files changed, 1912 insertions, 403 deletions
diff --git a/glustolibs-io/glustolibs/io/memory_and_cpu_utils.py b/glustolibs-io/glustolibs/io/memory_and_cpu_utils.py new file mode 100644 index 000000000..4e1dadbd7 --- /dev/null +++ b/glustolibs-io/glustolibs/io/memory_and_cpu_utils.py @@ -0,0 +1,924 @@ +# Copyright (C) 2020 Red Hat, Inc. <http://www.redhat.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from glusto.core import Glusto as g + +from glustolibs.gluster.volume_ops import get_volume_status +from glustolibs.gluster.glusterfile import file_exists +from glustolibs.misc.misc_libs import upload_scripts, kill_process + +import numpy as np +import pandas as pd +from statistics import mean, median + + +def check_upload_memory_and_cpu_logger_script(servers): + """Check and upload memory_and_cpu_logger.py to servers if not present + + Args: + servers(list): List of all servers where script has to be uploaded + + Returns: + bool: True if script is uploaded successfully else false + """ + script = "/usr/share/glustolibs/io/scripts/memory_and_cpu_logger.py" + is_present = [] + for server in servers: + if not file_exists(server, script): + if not upload_scripts(server, script): + g.log.error("Unable to upload memory_and_cpu_logger.py on %s", + server) + is_present.append(False) + else: + is_present.append(True) + return all(is_present) + + +def _start_logging_processes(process, servers, test_name, interval, count): + """Start logging processes on all nodes for a given process + + Args: + servers(list): Servers on which CPU and memory usage has to be logged + test_name(str): Name of testcase for which logs are to be collected + interval(int): Time interval after which logs are to be collected + count(int): Number of samples to be captured + + Returns: + list: A list of logging processes + """ + cmd = ("/usr/bin/env python " + "/usr/share/glustolibs/io/scripts/memory_and_cpu_logger.py" + " -p %s -t %s -i %d -c %d" % (process, test_name, + interval, count)) + logging_process = [] + for server in servers: + proc = g.run_async(server, cmd) + logging_process.append(proc) + return logging_process + + +def log_memory_and_cpu_usage_on_servers(servers, test_name, interval=60, + count=100): + """Log memory and CPU usage of gluster server processes + + Args: + servers(list): Servers on which CPU and memory usage has to be logged + test_name(str): Name of the testcase for which logs are to be collected + + Kwargs: + interval(int): Time interval after which logs are to be collected + (Default:60) + count(int): Number of samples to be captured (Default:100) + + Returns: + dict: Logging processes dict for all gluster server processes + """ + logging_process_dict = {} + for proc_name in ('glusterd', 'glusterfs', 'glusterfsd'): + logging_procs = _start_logging_processes( + proc_name, servers, test_name, interval, count) + logging_process_dict[proc_name] = logging_procs + return logging_process_dict + + +def log_memory_and_cpu_usage_on_clients(servers, test_name, interval=60, + count=100): + """Log memory and CPU usage of gluster client processes + + Args: + servers(list): Clients on which CPU and memory usage has to be logged + test_name(str): Name of testcase for which logs are to be collected + + Kwargs: + interval(int): Time interval after which logs are to be collected + (Defaults:60) + count(int): Number of samples to be captured (Default:100) + + Returns: + dict: Logging processes dict for all gluster client processes + """ + logging_process_dict = {} + logging_procs = _start_logging_processes( + 'glusterfs', servers, test_name, interval, count) + logging_process_dict['glusterfs'] = logging_procs + return logging_process_dict + + +def log_memory_and_cpu_usage_on_cluster(servers, clients, test_name, + interval=60, count=100): + """Log memory and CPU usage on gluster cluster + + Args: + servers(list): Servers on which memory and CPU usage is to be logged + clients(list): Clients on which memory and CPU usage is to be logged + test_name(str): Name of testcase for which logs are to be collected + + Kwargs: + interval(int): Time interval after which logs are to be collected + (Default:60) + count(int): Number of samples to be captured (Default:100) + + Returns: + dict: Logging processes dict for all servers and clients + """ + # Start logging on all servers + server_logging_processes = log_memory_and_cpu_usage_on_servers( + servers, test_name, interval, count) + if not server_logging_processes: + return {} + + # Starting logging on all clients + client_logging_processes = log_memory_and_cpu_usage_on_clients( + clients, test_name, interval, count) + if not client_logging_processes: + return {} + + # Combining dicts + logging_process_dict = {} + for node_type, proc_dict in (('server', server_logging_processes), + ('client', client_logging_processes)): + logging_process_dict[node_type] = {} + for proc in proc_dict: + logging_process_dict[node_type][proc] = ( + proc_dict[proc]) + return logging_process_dict + + +def _process_wait_flag_append(proc, flag): + """Run async communicate and adds true to flag list""" + # If the process is already completed async_communicate() + # throws a ValueError + try: + proc.async_communicate() + flag.append(True) + except ValueError: + flag.append(True) + + +def wait_for_logging_processes_to_stop(proc_dict, cluster=False): + """Wait for all given logging processes to stop + + Args: + proc_dict(dict): Dictionary of all the active logging processes + + Kwargs: + cluster(bool): True if proc_dict is for the entire cluster else False + (Default:False) + + Retruns: + bool: True if processes are completed else False + """ + flag = [] + if cluster: + for sub_dict in proc_dict: + for proc_name in proc_dict[sub_dict]: + for proc in proc_dict[sub_dict][proc_name]: + _process_wait_flag_append(proc, flag) + else: + for proc_name in proc_dict: + for proc in proc_dict[proc_name]: + _process_wait_flag_append(proc, flag) + return all(flag) + + +def kill_all_logging_processes(proc_dict, nodes, cluster=False): + """Kill logging processes on all given nodes + + Args: + proc_dict(dict): Dictonary of all active logging processes + nodes(list): List of nodes where logging has to be stopped + + Kwargs: + cluster(bool): True if proc_dict is for a full cluster else False + (Default:False) + + Retruns: + bool: True if processes are completed else False + """ + # Kill all logging processes + for server in nodes: + if not kill_process(server, process_names='memory_and_cpu_logger.py'): + g.log.error("Unable to kill some of the processes at %s.", server) + + # This will stop the async threads created by run_aysnc() as the proc is + # already killed. + ret = wait_for_logging_processes_to_stop(proc_dict, cluster) + if ret: + return True + return False + + +def create_dataframe_from_csv(node, proc_name, test_name): + """Creates a dataframe from a given process. + + Args: + node(str): Node from which csv is to be picked + proc_name(str): Name of process for which csv is to picked + test_name(str): Name of the testcase for which CSV + + Returns: + dataframe: Pandas dataframe if CSV file exits else None + """ + # Read the csv file generated by memory_and_cpu_logger.py + ret, raw_data, _ = g.run(node, "cat /root/{}.csv" + .format(proc_name)) + if ret: + return None + + # Split the complete dump to individual lines + data = raw_data.split("\r\n") + rows, flag = [], False + for line in data: + values = line.split(',') + if test_name == values[0]: + # Reset rows if it's the second instance + if flag: + rows = [] + flag = True + continue + + # Pick and append values which have complete entry + if flag and len(values) == 4: + rows.append(values) + + # Create a panda dataframe and set the type for columns + dataframe = pd.DataFrame(rows[1:], columns=rows[0]) + conversion_dict = {'Process ID': int, + 'CPU Usage': float, + 'Memory Usage': float} + dataframe = dataframe.astype(conversion_dict) + return dataframe + + +def _get_min_max_mean_median(entrylist): + """Get the mix, max. mean and median of a list + + Args: + entrylist(list): List of values to be used + + Returns: + dict:Result dict generate from list + """ + result = {} + result['Min'] = min(entrylist) + result['Max'] = max(entrylist) + result['Mean'] = mean(entrylist) + result['Median'] = median(entrylist) + return result + + +def _compute_min_max_mean_median(dataframe, data_dict, process, node, + volume=None, brick=None): + """Compute min, max, mean and median for a given process + + Args: + dataframe(panda dataframe): Panda data frame of the csv file + data_dict(dict): data dict to which info is to be added + process(str): Name of process for which data is to be computed + node(str): Node for which min, max, mean and median has to be computed + + Kwargs: + volume(str): Volume name of the volume for which data is to be computed + brick(str): Brick path of the brick for which data is to be computed + """ + if volume and process == 'glusterfs': + # Create subdict inside dict + data_dict[node][process][volume] = {} + for usage in ('CPU Usage', 'Memory Usage'): + # Create usage subdict + data_dict[node][process][volume][usage] = {} + + # Clean data and compute values + cleaned_usage = list(dataframe[usage].dropna()) + out = _get_min_max_mean_median(cleaned_usage) + + # Add values to data_dict + for key in ('Min', 'Max', 'Mean', 'Median'): + data_dict[node][process][volume][usage][key] = out[key] + + if volume and brick and process == 'glusterfsd': + # Create subdict inside dict + data_dict[node][process][volume] = {} + data_dict[node][process][volume][brick] = {} + for usage in ('CPU Usage', 'Memory Usage'): + # Create usage subdict + data_dict[node][process][volume][brick][usage] = {} + + # Clean data and compute values + cleaned_usage = list(dataframe[usage].dropna()) + out = _get_min_max_mean_median(cleaned_usage) + + # Add values to data_dict + for key in ('Min', 'Max', 'Mean', 'Median'): + data_dict[node][process][volume][brick][usage][key] = out[key] + + # Compute CPU Uage and Memory Usage for glusterd + else: + for usage in ('CPU Usage', 'Memory Usage'): + # Create uage subdict + data_dict[node][process][usage] = {} + + # Clean data and compute value + cleaned_usage = list(dataframe[usage].dropna()) + out = _get_min_max_mean_median(cleaned_usage) + + # Add values to data_dict + for key in ('Min', 'Max', 'Mean', 'Median'): + data_dict[node][process][usage][key] = out[key] + + +def compute_data_usage_stats_on_servers(nodes, test_name): + """Compute min, max, mean and median for servers + + Args: + nodes(list): Servers from which data is to be used to compute min, max + , mean, mode and median + test_name(str): Name of testcase for which data has to be processed + + Returns: + dict: dict of min, max, mean and median for a given process + + NOTE: + This function has to be always run before cleanup. + """ + data_dict = {} + for node in nodes: + # Get the volume status on the node + volume_status = get_volume_status(node) + data_dict[node] = {} + for process in ('glusterd', 'glusterfs', 'glusterfsd'): + + # Generate a dataframe from the csv file + dataframe = create_dataframe_from_csv(node, process, test_name) + if dataframe.empty: + return {} + + data_dict[node][process] = {} + if process == 'glusterd': + # Checking if glusterd is restarted. + if len(set(dataframe['Process ID'])) > 1: + data_dict[node][process]['is_restarted'] = True + else: + data_dict[node][process]['is_restarted'] = False + + # Call function to compute min, max, mean and median + _compute_min_max_mean_median(dataframe, data_dict, process, + node) + continue + + # Map volumes to volume process + for volume in volume_status.keys(): + for proc in volume_status[volume][node].keys(): + if (proc == 'Self-heal Daemon' and process == 'glusterfs'): + # Fetching pid from volume status output and create a + # dataframe with the entries of only that pid + pid = volume_status[volume][node][proc]['pid'] + proc_dataframe = dataframe[ + dataframe['Process ID'] == pid] + + # Call function to compute min, max, mean + # and median + _compute_min_max_mean_median( + proc_dataframe, data_dict, process, node, volume) + + if (proc.count('/') >= 2 and process == 'glusterfsd'): + # Fetching pid from volume status output and create a + # dataframe with the entries of only that pid + pid = volume_status[volume][node][proc]['pid'] + proc_dataframe = dataframe[ + dataframe['Process ID'] == pid] + + # Call function to compute min, max, mean and median + _compute_min_max_mean_median( + proc_dataframe, data_dict, process, node, volume, + proc) + + return data_dict + + +def compute_data_usage_stats_on_clients(nodes, test_name): + """Compute min, max, mean and median for clients + + Args: + nodes(list): Clients from which data is to be used to compute min, max + , mean, mode and median + test_name(str): Name of the testcase for which data has to be processed + + Returns: + dict: dict of min, max, mean and median for a given process + """ + data_dict = {} + for node in nodes: + data_dict[node] = {} + dataframe = create_dataframe_from_csv(node, 'glusterfs', test_name) + if dataframe.empty: + return {} + + data_dict[node]['glusterfs'] = {} + # Call function to compute min, max, mean and median + _compute_min_max_mean_median(dataframe, data_dict, 'glusterfs', node) + + return data_dict + + +def _perform_three_point_check_for_memory_leak(dataframe, node, process, gain, + volume_status=None, + volume=None, + vol_name=None): + """Perform three point check + + Args: + dataframe(panda dataframe): Panda dataframe of a given process + node(str): Node on which memory leak has to be checked + process(str): Name of process for which check has to be done + gain(float): Accepted amount of leak for a given testcase in MB + + kwargs: + volume_status(dict): Volume status output on the give name + volumne(str):Name of volume for which 3 point check has to be done + vol_name(str): Name of volume process according to volume status + + Returns: + bool: True if memory leak instances are observed else False + """ + # Filter dataframe to be process wise if it's volume specific process + if process in ('glusterfs', 'glusterfsd'): + if process == 'glusterfs' and vol_name: + pid = int(volume_status[volume][node][vol_name]['pid']) + dataframe = dataframe[dataframe['Process ID'] == pid] + + # Compute usage gain throught the data frame + memory_increments = list(dataframe['Memory Usage'].diff().dropna()) + + # Check if usage is more than accepted amount of leak + memory_leak_decision_array = np.where( + dataframe['Memory Usage'].diff().dropna() > gain, True, False) + instances_of_leak = np.where(memory_leak_decision_array)[0] + + # If memory leak instances are present check if it's reduced + count_of_leak_instances = len(instances_of_leak) + if count_of_leak_instances > 0: + g.log.error('There are %s instances of memory leaks on node %s', + count_of_leak_instances, node) + for instance in instances_of_leak: + # In cases of last log file entry the below op could throw + # IndexError which is handled as below. + try: + # Check if memory gain had decrease in the consecutive + # entries, after 2 entry and betwen current and last entry + if all([memory_increments[instance+1] > + memory_increments[instance], + memory_increments[instance+2] > + memory_increments[instance], + (memory_increments[len(memory_increments)-1] > + memory_increments[instance])]): + return True + + except IndexError: + # In case of last log file entry rerun the command + # and check for difference + g.log.info('Instance at last log entry.') + if process in ('glusterfs', 'glusterfsd'): + cmd = ("ps u -p %s | awk 'NR>1 && $11~/%s$/{print " + " $6/1024}'" % (pid, process)) + else: + cmd = ("ps u -p `pgrep glusterd` | awk 'NR>1 && $11~/" + "glusterd$/{print $6/1024}'") + ret, out, _ = g.run(node, cmd) + if ret: + g.log.error('Unable to run the command to fetch current ' + 'memory utilization.') + continue + usage_now = float(out.replace('\n', '')[2]) + last_entry = dataframe['Memory Usage'].iloc[-1] + + # Check if current memory usage is higher than last entry + fresh_diff = last_entry - usage_now + if fresh_diff > gain and last_entry > fresh_diff: + return True + return False + + +def check_for_memory_leaks_in_glusterd(nodes, test_name, gain=30.0): + """Check for memory leaks in glusterd + + Args: + nodes(list): Servers on which memory leaks have to be checked + test_name(str): Name of testcase for which memory leaks has to be checked + + Kwargs: + gain(float): Accepted amount of leak for a given testcase in MB + (Default:30) + + Returns: + bool: True if memory leak was obsevred else False + """ + is_there_a_leak = [] + for node in nodes: + dataframe = create_dataframe_from_csv(node, 'glusterd', test_name) + if dataframe.empty: + return False + + # Call 3 point check function + three_point_check = _perform_three_point_check_for_memory_leak( + dataframe, node, 'glusterd', gain) + if three_point_check: + g.log.error("Memory leak observed on node %s in glusterd", + node) + is_there_a_leak.append(three_point_check) + + return any(is_there_a_leak) + + +def check_for_memory_leaks_in_glusterfs(nodes, test_name, gain=30.0): + """Check for memory leaks in glusterfs + + Args: + nodes(list): Servers on which memory leaks have to be checked + test_name(str): Name of testcase for which memory leaks has to be checked + + Kwargs: + gain(float): Accepted amount of leak for a given testcase in MB + (Default:30) + + Returns: + bool: True if memory leak was obsevred else False + + NOTE: + This function should be executed with the volumes present on the cluster + """ + is_there_a_leak = [] + for node in nodes: + # Get the volume status on the node + volume_status = get_volume_status(node) + dataframe = create_dataframe_from_csv(node, 'glusterfs', test_name) + if dataframe.empty: + return False + + for volume in volume_status.keys(): + for process in volume_status[volume][node].keys(): + # Skiping if process isn't Self-heal Deamon + if process != 'Self-heal Daemon': + continue + + # Call 3 point check function + three_point_check = _perform_three_point_check_for_memory_leak( + dataframe, node, 'glusterfs', gain, volume_status, volume, + 'Self-heal Daemon') + if three_point_check: + g.log.error("Memory leak observed on node %s in shd " + "on volume %s", node, volume) + is_there_a_leak.append(three_point_check) + + return any(is_there_a_leak) + + +def check_for_memory_leaks_in_glusterfsd(nodes, test_name, gain=30.0): + """Check for memory leaks in glusterfsd + + Args: + nodes(list): Servers on which memory leaks have to be checked + test_name(str): Name of testcase for which memory leaks has to be checked + + Kwargs: + gain(float): Accepted amount of leak for a given testcase in MB + (Default:30) + + Returns: + bool: True if memory leak was obsevred else False + + NOTE: + This function should be executed with the volumes present on the cluster. + """ + is_there_a_leak = [] + for node in nodes: + # Get the volume status on the node + volume_status = get_volume_status(node) + dataframe = create_dataframe_from_csv(node, 'glusterfsd', test_name) + if dataframe.empty: + return False + + for volume in volume_status.keys(): + for process in volume_status[volume][node].keys(): + # Skiping if process isn't brick process + if not process.count('/'): + continue + + # Call 3 point check function + three_point_check = _perform_three_point_check_for_memory_leak( + dataframe, node, 'glusterfsd', gain, volume_status, volume, + process) + if three_point_check: + g.log.error("Memory leak observed on node %s in brick " + " process for brick %s on volume %s", node, + process, volume) + is_there_a_leak.append(three_point_check) + + return any(is_there_a_leak) + + +def check_for_memory_leaks_in_glusterfs_fuse(nodes, test_name, gain=30.0): + """Check for memory leaks in glusterfs fuse + + Args: + nodes(list): Servers on which memory leaks have to be checked + test_name(str): Name of testcase for which memory leaks has to be checked + + Kwargs: + gain(float): Accepted amount of leak for a given testcase in MB + (Default:30) + + Returns: + bool: True if memory leak was observed else False + + NOTE: + This function should be executed when the volume is still mounted. + """ + is_there_a_leak = [] + for node in nodes: + # Get the volume status on the node + dataframe = create_dataframe_from_csv(node, 'glusterfs', test_name) + if dataframe.empty: + return False + + # Call 3 point check function + three_point_check = _perform_three_point_check_for_memory_leak( + dataframe, node, 'glusterfs', gain) + if three_point_check: + g.log.error("Memory leak observed on node %s for client", + node) + + # If I/O is constantly running on Clients the memory + # usage spikes up and stays at a point for long. + last_entry = dataframe['Memory Usage'].iloc[-1] + cmd = ("ps u -p `pidof glusterfs` | " + "awk 'NR>1 && $11~/glusterfs$/{print" + " $6/1024}'") + ret, out, _ = g.run(node, cmd) + if ret: + g.log.error('Unable to run the command to fetch current ' + 'memory utilization.') + continue + + if float(out) != last_entry: + if float(out) > last_entry: + is_there_a_leak.append(True) + continue + + is_there_a_leak.append(False) + + return any(is_there_a_leak) + + +def _check_for_oom_killers(nodes, process, oom_killer_list): + """Checks for OOM killers for a specific process + + Args: + nodes(list): Nodes on which OOM killers have to be checked + process(str): Process for which OOM killers have to be checked + oom_killer_list(list): A list in which the presence of + OOM killer has to be noted + """ + cmd = ("grep -i 'killed process' /var/log/messages* " + "| grep -w '{}'".format(process)) + ret_codes = g.run_parallel(nodes, cmd) + for key in ret_codes.keys(): + ret, out, _ = ret_codes[key] + if not ret: + g.log.error('OOM killer observed on %s for %s', key, process) + g.log.error(out) + oom_killer_list.append(True) + else: + oom_killer_list.append(False) + + +def check_for_oom_killers_on_servers(nodes): + """Check for OOM killers on servers + + Args: + nodes(list): Servers on which OOM kills have to be checked + + Returns: + bool: True if OOM killers are present on any server else False + """ + oom_killer_list = [] + for process in ('glusterfs', 'glusterfsd', 'glusterd'): + _check_for_oom_killers(nodes, process, oom_killer_list) + return any(oom_killer_list) + + +def check_for_oom_killers_on_clients(nodes): + """Check for OOM killers on clients + + Args: + nodes(list): Clients on which OOM kills have to be checked + + Returns: + bool: True if OOM killers are present on any client else false + """ + oom_killer_list = [] + _check_for_oom_killers(nodes, 'glusterfs', oom_killer_list) + return any(oom_killer_list) + + +def _check_for_cpu_usage_spikes(dataframe, node, process, threshold, + volume_status=None, volume=None, + vol_name=None): + """Check for cpu spikes for a given process + + Args: + dataframe(panda dataframe): Panda dataframe of a given process + node(str): Node on which cpu spikes has to be checked + process(str): Name of process for which check has to be done + threshold(int): Accepted amount of 100% CPU usage instances + + kwargs: + volume_status(dict): Volume status output on the give name + volume(str):Name of volume for which check has to be done + vol_name(str): Name of volume process according to volume status + + Returns: + bool: True if number of instances more than threshold else False + """ + # Filter dataframe to be process wise if it's volume specific process + if process in ('glusterfs', 'glusterfsd'): + pid = int(volume_status[volume][node][vol_name]['pid']) + dataframe = dataframe[dataframe['Process ID'] == pid] + + # Check if usage is more than accepted amount of leak + cpu_spike_decision_array = np.where( + dataframe['CPU Usage'].dropna() == 100.0, True, False) + instances_of_spikes = np.where(cpu_spike_decision_array)[0] + + return bool(len(instances_of_spikes) > threshold) + + +def check_for_cpu_usage_spikes_on_glusterd(nodes, test_name, threshold=3): + """Check for CPU usage spikes on glusterd + + Args: + nodes(list): Servers on which memory leaks have to be checked + test_name(str): Name of testcase for which memory leaks has to be checked + + Kwargs: + threshold(int): Accepted amount of instances of 100% CPU usage + (Default:3) + + Returns: + bool: True if CPU spikes are more than threshold else False + """ + is_there_a_spike = [] + for node in nodes: + dataframe = create_dataframe_from_csv(node, 'glusterd', test_name) + if dataframe.empty: + return False + + # Call function to check for cpu spikes + cpu_spikes = _check_for_cpu_usage_spikes( + dataframe, node, 'glusterd', threshold) + if cpu_spikes: + g.log.error("CPU usage spikes observed more than " + "threshold %d on node %s for glusterd", + threshold, node) + is_there_a_spike.append(cpu_spikes) + + return any(is_there_a_spike) + + +def check_for_cpu_usage_spikes_on_glusterfs(nodes, test_name, threshold=3): + """Check for CPU usage spikes on glusterfs + + Args: + nodes(list): Servers on which memory leaks have to be checked + test_name(str): Name of testcase for which memory leaks has to be checked + + Kwargs: + threshold(int): Accepted amount of instances of 100% CPU usage + (Default:3) + + Returns: + bool: True if CPU spikes are more than threshold else False + + NOTE: + This function should be exuected with the volumes present on the cluster. + """ + is_there_a_spike = [] + for node in nodes: + # Get the volume status on the node + volume_status = get_volume_status(node) + dataframe = create_dataframe_from_csv(node, 'glusterfs', test_name) + if dataframe.empty: + return False + + for volume in volume_status.keys(): + for process in volume_status[volume][node].keys(): + # Skiping if process isn't Self-heal Deamon + if process != 'Self-heal Daemon': + continue + + # Call function to check for cpu spikes + cpu_spikes = _check_for_cpu_usage_spikes( + dataframe, node, 'glusterfs', threshold, volume_status, + volume, 'Self-heal Daemon') + if cpu_spikes: + g.log.error("CPU usage spikes observed more than " + "threshold %d on node %s on volume %s for shd", + threshold, node, volume) + is_there_a_spike.append(cpu_spikes) + + return any(is_there_a_spike) + + +def check_for_cpu_usage_spikes_on_glusterfsd(nodes, test_name, threshold=3): + """Check for CPU usage spikes in glusterfsd + + Args: + nodes(list): Servers on which memory leaks have to be checked + test_name(str): Name of testcase for which memory leaks has to be checked + + Kwargs: + threshold(int): Accepted amount of instances of 100% CPU usage + (Default:3) + + Returns: + bool: True if CPU spikes are more than threshold else False + + NOTE: + This function should be exuected with the volumes present on the cluster. + """ + is_there_a_spike = [] + for node in nodes: + # Get the volume status on the node + volume_status = get_volume_status(node) + dataframe = create_dataframe_from_csv(node, 'glusterfsd', test_name) + if dataframe.empty: + return False + + for volume in volume_status.keys(): + for process in volume_status[volume][node].keys(): + # Skiping if process isn't brick process + if process in ('Self-heal Daemon', 'Quota Daemon'): + continue + + # Call function to check for cpu spikes + cpu_spikes = _check_for_cpu_usage_spikes( + dataframe, node, 'glusterfsd', threshold, volume_status, + volume, process) + if cpu_spikes: + g.log.error("CPU usage spikes observed more than " + "threshold %d on node %s on volume %s for " + "brick process %s", + threshold, node, volume, process) + is_there_a_spike.append(cpu_spikes) + + return any(is_there_a_spike) + + +def check_for_cpu_usage_spikes_on_glusterfs_fuse(nodes, test_name, + threshold=3): + """Check for CPU usage spikes on glusterfs fuse + + Args: + nodes(list): Servers on which memory leaks have to be checked + test_name(str): Name of testcase for which memory leaks has to be checked + + Kwargs: + threshold(int): Accepted amount of instances of 100% CPU usage + (Default:3) + + Returns: + bool: True if CPU spikes are more than threshold else False + + NOTE: + This function should be executed when the volume is still mounted. + """ + is_there_a_spike = [] + for node in nodes: + # Get the volume status on the node + dataframe = create_dataframe_from_csv(node, 'glusterfs', test_name) + if dataframe.empty: + return False + + # Call function to check for cpu spikes + cpu_spikes = _check_for_cpu_usage_spikes( + dataframe, node, 'glusterfs', threshold) + if cpu_spikes: + g.log.error("CPU usage spikes observed more than " + "threshold %d on node %s for client", + threshold, node) + is_there_a_spike.append(cpu_spikes) + + return any(is_there_a_spike) diff --git a/glustolibs-io/glustolibs/io/utils.py b/glustolibs-io/glustolibs/io/utils.py index 52c2c7df0..16ee93f21 100755 --- a/glustolibs-io/glustolibs/io/utils.py +++ b/glustolibs-io/glustolibs/io/utils.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2015-2020 Red Hat, Inc. <http://www.redhat.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,20 +17,26 @@ """ Description: Helper library for io modules. """ +from multiprocessing import Pool import os import subprocess + from glusto.core import Glusto as g +from glustolibs.gluster.glusterfile import file_exists from glustolibs.gluster.mount_ops import GlusterMount -from multiprocessing import Pool from glustolibs.gluster.volume_libs import get_subvols +from glustolibs.misc.misc_libs import upload_scripts -def collect_mounts_arequal(mounts): +def collect_mounts_arequal(mounts, path=''): """Collects arequal from all the mounts Args: mounts (list): List of all GlusterMount objs. + Kwargs: + path (str): Path whose arequal is to be calculated. + Defaults to root of mountpoint Returns: tuple(bool, list): On success returns (True, list of arequal-checksums of each mount) @@ -45,11 +51,11 @@ def collect_mounts_arequal(mounts): g.log.info("Start collecting arequal-checksum from all mounts") all_mounts_procs = [] for mount_obj in mounts: + total_path = os.path.join(mount_obj.mountpoint, path) g.log.info("arequal-checksum of mount %s:%s", mount_obj.client_system, - mount_obj.mountpoint) - cmd = "arequal-checksum -p %s -i .trashcan" % mount_obj.mountpoint - proc = g.run_async(mount_obj.client_system, cmd, - user=mount_obj.user) + total_path) + cmd = "arequal-checksum -p %s -i .trashcan" % total_path + proc = g.run_async(mount_obj.client_system, cmd, user=mount_obj.user) all_mounts_procs.append(proc) all_mounts_arequal_checksums = [] _rc = True @@ -68,7 +74,7 @@ def collect_mounts_arequal(mounts): def log_mounts_info(mounts): - """Logs mount information like df, stat, ls + """Log mount information like df, stat, ls Args: mounts (list): List of all GlusterMount objs. @@ -83,22 +89,22 @@ def log_mounts_info(mounts): # Mount Info g.log.info("Look For Mountpoint:\n") cmd = "mount | grep %s" % mount_obj.mountpoint - _, _, _ = g.run(mount_obj.client_system, cmd) + g.run(mount_obj.client_system, cmd) # Disk Space Usage g.log.info("Disk Space Usage Of Mountpoint:\n") cmd = "df -h %s" % mount_obj.mountpoint - _, _, _ = g.run(mount_obj.client_system, cmd) + g.run(mount_obj.client_system, cmd) # Long list the mountpoint g.log.info("List Mountpoint Entries:\n") cmd = "ls -ld %s" % mount_obj.mountpoint - _, _, _ = g.run(mount_obj.client_system, cmd) + g.run(mount_obj.client_system, cmd) # Stat mountpoint g.log.info("Mountpoint Status:\n") cmd = "stat %s" % mount_obj.mountpoint - _, _, _ = g.run(mount_obj.client_system, cmd) + g.run(mount_obj.client_system, cmd) def get_mounts_stat(mounts): @@ -119,9 +125,8 @@ def get_mounts_stat(mounts): for mount_obj in mounts: g.log.info("Stat of mount %s:%s", mount_obj.client_system, mount_obj.mountpoint) - cmd = ("find %s | xargs stat" % (mount_obj.mountpoint)) - proc = g.run_async(mount_obj.client_system, cmd, - user=mount_obj.user) + cmd = "find %s | xargs stat" % (mount_obj.mountpoint) + proc = g.run_async(mount_obj.client_system, cmd, user=mount_obj.user) all_mounts_procs.append(proc) _rc = True for i, proc in enumerate(all_mounts_procs): @@ -157,7 +162,7 @@ def list_all_files_and_dirs_mounts(mounts): for mount_obj in mounts: g.log.info("Listing files and dirs on %s:%s", mount_obj.client_system, mount_obj.mountpoint) - cmd = ("find %s | grep -ve '%s'" % (mount_obj.mountpoint, ignore_dirs)) + cmd = "find %s | grep -ve '%s'" % (mount_obj.mountpoint, ignore_dirs) proc = g.run_async(mount_obj.client_system, cmd, user=mount_obj.user) all_mounts_procs.append(proc) _rc = True @@ -194,7 +199,7 @@ def view_snaps_from_mount(mounts, snaps): for mount_obj in mounts: g.log.info("Viewing '.snaps' on %s:%s", mount_obj.client_system, mount_obj.mountpoint) - cmd = ("ls -1 %s/.snaps" % mount_obj.mountpoint) + cmd = "ls -1 %s/.snaps" % mount_obj.mountpoint proc = g.run_async(mount_obj.client_system, cmd, user=mount_obj.user) all_mounts_procs.append(proc) @@ -229,7 +234,7 @@ def view_snaps_from_mount(mounts, snaps): def validate_io_procs(all_mounts_procs, mounts): - """Validates whether IO was successful or not + """Validate whether IO was successful or not. Args: all_mounts_procs (list): List of open connection descriptor as @@ -316,19 +321,16 @@ def cleanup_mounts(mounts): for mount_obj in mounts: g.log.info("Cleaning up data from %s:%s", mount_obj.client_system, mount_obj.mountpoint) - if (not mount_obj.mountpoint or - (os.path.realpath(os.path.abspath(mount_obj.mountpoint)) - == '/')): + if (not mount_obj.mountpoint or (os.path.realpath(os.path.abspath( + mount_obj.mountpoint)) == '/')): g.log.error("%s on %s is not a valid mount point", mount_obj.mountpoint, mount_obj.client_system) continue cmd = "rm -rf %s/*" % (mount_obj.mountpoint) - proc = g.run_async(mount_obj.client_system, cmd, - user=mount_obj.user) + proc = g.run_async(mount_obj.client_system, cmd, user=mount_obj.user) all_mounts_procs.append(proc) valid_mounts.append(mount_obj) - g.log.info("rm -rf on all clients is complete. Validating " - "deletion now...") + g.log.info("rm -rf on all clients is complete. Validating deletion now...") # Get cleanup status _rc_rmdir = True @@ -355,8 +357,7 @@ def cleanup_mounts(mounts): for mount_obj in mounts: cmd = ("find %s -mindepth 1 | grep -ve '%s'" % (mount_obj.mountpoint, ignore_dirs)) - proc = g.run_async(mount_obj.client_system, cmd, - user=mount_obj.user) + proc = g.run_async(mount_obj.client_system, cmd, user=mount_obj.user) all_mounts_procs.append(proc) # Get cleanup status @@ -383,8 +384,7 @@ def cleanup_mounts(mounts): def run_bonnie(servers, directory_to_run, username="root"): - """ - Module to run bonnie test suite on the given servers. + """Run bonnie test suite on the given servers. Args: servers (list): servers in which tests to be run. @@ -459,8 +459,7 @@ def run_bonnie(servers, directory_to_run, username="root"): def run_fio(servers, directory_to_run): - """ - Module to run fio test suite on the given servers. + """Run fio test suite on the given servers. Args: servers (list): servers in which tests to be run. @@ -536,15 +535,14 @@ def run_fio(servers, directory_to_run): def run_mixed_io(servers, io_tools, directory_to_run): - """ - Module to run different io patterns on each given servers. + """Run different io patterns on each given servers. Args: servers (list): servers in which tests to be run. io_tools (list): different io tools. Currently fio, bonnie are - supported. + supported. directory_to_run (list): directory path where tests will run for - each server. + each server. Returns: bool: True, if test passes in all servers, False otherwise @@ -565,8 +563,7 @@ def run_mixed_io(servers, io_tools, directory_to_run): for items in zip(servers, io_tools): server_io_dict[items[0]] = items[1] - io_dict = {'fio': run_fio, - 'bonnie': run_bonnie} + io_dict = {'fio': run_fio, 'bonnie': run_bonnie} func_list = [] for index, server in enumerate(servers): @@ -586,8 +583,7 @@ def run_mixed_io(servers, io_tools, directory_to_run): def is_io_procs_fail_with_rofs(self, all_mounts_procs, mounts): - """ - Checks whether IO failed with Read-only file system error + """Check whether IO failed with Read-only file system error. Args: all_mounts_procs (list): List of open connection descriptor as @@ -619,8 +615,8 @@ def is_io_procs_fail_with_rofs(self, all_mounts_procs, mounts): g.log.info("EXPECTED : IO Failed on %s:%s", self.mounts[i].client_system, self.mounts[i].mountpoint) - if ("Read-only file system" in err or - "Read-only file system" in out): + if ("Read-only file system" in err + or "Read-only file system" in out): g.log.info("EXPECTED : Read-only file system in output") io_results[proc] = True else: @@ -637,8 +633,7 @@ def is_io_procs_fail_with_rofs(self, all_mounts_procs, mounts): def is_io_procs_fail_with_error(self, all_mounts_procs, mounts, mount_type): - """ - Checks whether IO failed with connection error + """Check whether IO failed with connection error. Args: all_mounts_procs (list): List of open connection descriptor as @@ -672,8 +667,8 @@ def is_io_procs_fail_with_error(self, all_mounts_procs, mounts, mount_type): self.mounts[i].client_system, self.mounts[i].mountpoint) if mount_type == "glusterfs": - if ("Transport endpoint is not connected" in err or - "Transport endpoint is not connected" in out): + if ("Transport endpoint is not connected" in err + or "Transport endpoint is not connected" in out): g.log.info("EXPECTED : Transport endpoint is not connected" " in output") io_results[proc] = True @@ -683,8 +678,7 @@ def is_io_procs_fail_with_error(self, all_mounts_procs, mounts, mount_type): "not found in output") io_results[proc] = False if mount_type == "nfs": - if ("Input/output error" in err or - "Input/output error" in out): + if "Input/output error" in err or "Input/output error" in out: g.log.info("EXPECTED : Input/output error in output") io_results[proc] = True else: @@ -702,8 +696,7 @@ def is_io_procs_fail_with_error(self, all_mounts_procs, mounts, mount_type): def compare_dir_structure_mount_with_brick(mnthost, mntloc, brick_list, type): - """ Compare directory structure from mount point with brick path along - with stat parameter + """Compare mount point dir structure with brick path along with stat param.. Args: mnthost (str): hostname or ip of mnt system @@ -725,8 +718,8 @@ def compare_dir_structure_mount_with_brick(mnthost, mntloc, brick_list, type): if type == 2: statformat = '%A' - command = ("find %s -mindepth 1 -type d | xargs -r stat -c '%s'" - % (mntloc, statformat)) + command = "find %s -mindepth 1 -type d | xargs -r stat -c '%s'" % ( + mntloc, statformat) rcode, rout, _ = g.run(mnthost, command) all_dir_mnt_perm = rout.strip().split('\n') @@ -736,7 +729,8 @@ def compare_dir_structure_mount_with_brick(mnthost, mntloc, brick_list, type): "xargs -r stat -c '%s'" % (brick_path, statformat)) rcode, rout, _ = g.run(brick_node, command) all_brick_dir_perm = rout.strip().split('\n') - retval = cmp(all_dir_mnt_perm, all_brick_dir_perm) + retval = (all_dir_mnt_perm > all_brick_dir_perm) - ( + all_dir_mnt_perm < all_brick_dir_perm) if retval != 0: return False @@ -769,8 +763,7 @@ def check_arequal_bricks_replicated(mnode, volname): subvol_brick_list = subvols_dict['volume_subvols'][i] node, brick_path = subvol_brick_list[0].split(':') command = ('arequal-checksum -p %s ' - '-i .glusterfs -i .landfill -i .trashcan' - % brick_path) + '-i .glusterfs -i .landfill -i .trashcan' % brick_path) ret, arequal, _ = g.run(node, command) if ret != 0: g.log.error("Failed to calculate arequal for first brick" @@ -782,21 +775,297 @@ def check_arequal_bricks_replicated(mnode, volname): for brick in subvol_brick_list[1:]: node, brick_path = brick.split(':') command = ('arequal-checksum -p %s ' - '-i .glusterfs -i .landfill -i .trashcan' - % brick_path) + '-i .glusterfs -i .landfill -i .trashcan' % brick_path) ret, brick_arequal, _ = g.run(node, command) if ret != 0: - g.log.error('Failed to get arequal on brick %s' - % brick) + g.log.error('Failed to get arequal on brick %s' % brick) return False g.log.info('Getting arequal for %s is successful', brick) brick_total = brick_arequal.splitlines()[-1].split(':')[-1] # compare arequal of first brick of subvol with all brick other # bricks in subvol if first_brick_total != brick_total: - g.log.error('Arequals for subvol and %s are not equal' - % brick) + g.log.error('Arequals for subvol and %s are not equal' % brick) return False g.log.info('Arequals for subvol and %s are equal', brick) g.log.info('All arequals are equal for volume %s', volname) return True + + +def run_crefi(client, mountpoint, number, breadth, depth, thread=5, + random_size=False, fop='create', filetype='text', + minfs=10, maxfs=500, single=False, multi=False, size=100, + interval=100, nameBytes=10, random_filename=True): + """Run crefi on a given mount point and generate I/O. + + Args: + client(str): Client on which I/O has to be performed. + mountpoint(str): Mount point where the client is mounted. + number(int): Number of files to be created. + breadth(int): Number of directories in one level. + depth(int): Number of levels of directories. + + Kwargs: + thread(int): Number of threads used to generate fop. + random_size(bool): Random size of the file between min and max. + fop(str): fop can be [create|rename|chmod|chown|chgrp|symlink|hardlink| + truncate|setxattr] this specifies the type of fop to be + executed by default it is create. + filetype(str): filetype can be [text|sparse|binary|tar] this specifies + the type of file by default it is text. + minfs(int): If random is set to true then this value has to be altered + to change minimum file size. (Value is in KB) + maxfs(int): If random is set to true then this value has to be altered + to change maximum file size. (Value is in KB) + single(bool): Create files in a single directory. + multi(bool): Create files in sub-dir and sub-sub dir. + size(int): Size of the files to be created. (Value is in KB) + interval(int): Print number files created of interval. + nameBytes(int): Number of bytes for filename. (Value is in Bytes) + random_filename(bool): It creates files with random names, if set to + False it creates files with file name file1, + file2 and so on. + + Returns: + bool: True if I/O was sucessfully otherwise False. + + NOTE: + To use this function it is a prerequisite to have crefi installed + on all the clients. Please use the below command to install it: + $ pip install crefi + $ pip install pyxattr + """ + + # Checking value of fop. + list_of_fops = ["create", "rename", "chmod", "chown", "chgrp", "symlink", + "hardlink", "truncate", "setxattr"] + if fop not in list_of_fops: + g.log.error("fop value is not valid.") + return False + + # Checking value of filetype. + list_of_filetypes = ["text", "sparse", "binary", "tar"] + if filetype not in list_of_filetypes: + g.log.error("filetype is not a valid file type.") + return False + + # Checking if single and multi both are set to true. + if single and multi: + g.log.error("single and mutli both can't be true.") + return False + + # Checking if file size and random size arguments are given together. + if (size > 100 or size < 100) and random_size: + g.log.error("Size and Random size can't be used together.") + return False + + # Checking if minfs is greater than or equal to maxfs. + if random_size and (minfs >= maxfs): + g.log.error("minfs shouldn't be greater than or equal to maxfs.") + return False + + # Creating basic command. + command = "crefi %s -n %s -b %s -d %s " % ( + mountpoint, number, breadth, depth) + + # Checking thread value and adding it, If it is greater or smaller than 5. + if thread > 5 or thread < 5: + command = command + ("-T %s " % thread) + + # Checking if random size is true or false. + if random_size: + command = command + "--random " + if minfs > 10 or minfs < 10: + command = command + ("--min %s " % minfs) + if maxfs > 500 or maxfs < 500: + command = command + ("--max %s " % maxfs) + + # Checking fop and adding it if not create. + if fop != "create": + command = command + ("--fop %s " % fop) + + # Checking if size if greater than or less than 100. + if size > 100 or size < 100: + command = command + ("--size %s " % size) + + # Checking if single or mutli is true. + if single: + command = command + "--single " + if multi: + command = command + "--multi " + + # Checking if random_filename is false. + if not random_filename: + command = command + "-R " + + # Checking if print interval is greater than or less than 100. + if interval > 100 or interval < 100: + command = command + ("-I %s " % interval) + + # Checking if name Bytes is greater than or less than 10. + if nameBytes > 10 or nameBytes < 10: + command = command + ("-l %s " % nameBytes) + + # Checking filetype and setting it if not + # text. + if filetype != "text": + command = command + ("-t %s " % filetype) + + # Running the command on the client node. + ret, _, _ = g.run(client, command) + if ret: + g.log.error("Failed to run crefi on %s." % client) + return False + return True + + +def run_cthon(mnode, volname, clients, dir_name): + """This function runs the cthon test suite. + + Args: + mnode (str) : IP of the server exporting the gluster volume. + volname (str) : The volume name. + clients (list) : List of client machines where + the test needs to be run. + dir_name (str) : Directory where the repo + is cloned. + + Returns: + bool : True if the cthon test passes successfully + False otherwise. + """ + param_list = ['-b', '-g', '-s', '-l'] + vers_list = ['4.0', '4.1'] + + for client in clients: + g.log.info("Running tests on client %s" % client) + for vers in vers_list: + g.log.info("Running tests on client version %s" % vers) + for param in param_list: + # Initialising the test_type that will be running + if param == '-b': + test_type = "Basic" + elif param == '-g': + test_type = "General" + elif param == '-s': + test_type = "Special" + else: + test_type = "Lock" + g.log.info("Running %s test" % test_type) + cmd = "cd /root/%s; ./server %s -o vers=%s -p %s -N 1 %s;" % ( + dir_name, param, vers, volname, mnode) + ret, _, _ = g.run(client, cmd) + if ret: + g.log.error("Error with %s test" % test_type) + return False + else: + g.log.info("%s test successfully passed" % test_type) + return True + + +def upload_file_dir_ops(clients): + """Upload file_dir_ops.py to all the clients. + + Args: + clients(list): List of client machines where we need to upload + the script. + + Returns: + bool: True if script is uploaded successfully + False otherwise. + """ + + g.log.info("Upload io scripts to clients %s for running IO on " + "mounts", clients) + file_dir_ops_path = ("/usr/share/glustolibs/io/scripts/" + "file_dir_ops.py") + + if not upload_scripts(clients, file_dir_ops_path): + g.log.error("Failed to upload IO scripts to clients %s" % + clients) + return False + + g.log.info("Successfully uploaded IO scripts to clients %s", + clients) + return True + + +def open_file_fd(mountpoint, time, client, start_range=0, + end_range=0): + """Open FD for a file and write to file. + + Args: + mountpoint(str): The mount point where the FD of file is to + be opened. + time(int): The time to wait after opening an FD. + client(str): The client from which FD is to be opened. + + Kwargs: + start_range(int): The start range of the open FD. + (Default: 0) + end_range(int): The end range of the open FD. + (Default: 0) + + Returns: + proc(object): Returns a process object + + NOTE: + Before opening FD, check the currently used fds on the + system as only a limited number of fds can be opened on + a system at a given time for each process. + """ + if not (start_range and end_range): + cmd = ("cd {}; exec 30<> file_openfd ; sleep {};" + "echo 'xyz' >&30".format(mountpoint, time)) + else: + cmd = ('cd {}; for i in `seq {} {}`;' + ' do eval "exec $i<>file_openfd$i"; sleep {};' + ' echo "Write to open FD" >&$i; done'.format( + mountpoint, start_range, end_range, time)) + proc = g.run_async(client, cmd) + return proc + + +def run_linux_untar(clients, mountpoint, dirs=('.')): + """Run linux kernal untar on a given mount point + + Args: + clients(str|list): Client nodes on which I/O + has to be started. + mountpoint(str): Mount point where the volume is + mounted. + Kwagrs: + dirs(tuple): A tuple of dirs where untar has to + started. (Default:('.')) + Returns: + list: Returns a list of process object else None + """ + # Checking and convering clients to list. + if not isinstance(clients, list): + clients = [clients] + + list_of_procs = [] + for client in clients: + # Download linux untar to root, so that it can be + # utilized in subsequent run_linux_untar() calls. + cmd = ("wget https://cdn.kernel.org/pub/linux/kernel/" + "v5.x/linux-5.4.54.tar.xz") + if not file_exists(client, '/root/linux-5.4.54.tar.xz'): + ret, _, _ = g.run(client, cmd) + if ret: + return None + + for directory in dirs: + # copy linux tar to dir + cmd = ("cp /root/linux-5.4.54.tar.xz {}/{}" + .format(mountpoint, directory)) + ret, _, _ = g.run(client, cmd) + if ret: + return None + # Start linux untar + cmd = ("cd {}/{};tar -xvf linux-5.4.54.tar.xz" + .format(mountpoint, directory)) + proc = g.run_async(client, cmd) + list_of_procs.append(proc) + + return list_of_procs diff --git a/glustolibs-io/setup.py b/glustolibs-io/setup.py index 41655dad6..9ee90b15b 100644 --- a/glustolibs-io/setup.py +++ b/glustolibs-io/setup.py @@ -1,5 +1,5 @@ -#!/usr/bin/python -# Copyright (c) 2016 Red Hat, Inc. +#!/usr/bin/env python +# Copyright (c) 2016-2019 Red Hat, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -15,9 +15,11 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # + +from distutils import dir_util import os + from setuptools import setup, find_packages -from distutils import dir_util version = '0.1.2' name = 'glustolibs-io' @@ -26,29 +28,33 @@ setup( name=name, version=version, description='Glusto - Red Hat I/O Libraries', - license='GPLv2+', + license='GPLv3+', author='Red Hat, Inc.', author_email='gluster-devel@gluster.org', url='http://www.gluster.org', packages=find_packages(), classifiers=[ - 'Development Status :: 4 - Beta' - 'Environment :: Console' - 'Intended Audience :: Developers' - 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)' - 'Operating System :: POSIX :: Linux' - 'Programming Language :: Python' - 'Programming Language :: Python :: 2' - 'Programming Language :: Python :: 2.6' - 'Programming Language :: Python :: 2.7' - 'Topic :: Software Development :: Testing' + 'Development Status :: 4 - Beta', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU General Public License v3 or ' + 'later (GPLv3+)', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Topic :: Software Development :: Testing', ], install_requires=['glusto'], - dependency_links=['http://github.com/loadtheaccumulator/glusto/tarball/master#egg=glusto'], + dependency_links=[ + 'http://github.com/loadtheaccumulator/glusto/tarball/master#egg=glusto' + ], namespace_packages = ['glustolibs'] ) try: dir_util.copy_tree('./shared_files', '/usr/share/glustolibs/io') -except: +except Exception: pass diff --git a/glustolibs-io/shared_files/scripts/fd_writes.py b/glustolibs-io/shared_files/scripts/fd_writes.py index 87358f45a..e3ebccb63 100755 --- a/glustolibs-io/shared_files/scripts/fd_writes.py +++ b/glustolibs-io/shared_files/scripts/fd_writes.py @@ -15,14 +15,15 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from __future__ import print_function import argparse -import random -import os -import time -import string import datetime from multiprocessing import Process +import os +import random +import string import sys +import time def is_root(path): @@ -35,9 +36,9 @@ def is_root(path): True if path is '/' , False otherwise """ if os.path.realpath(os.path.abspath(path)) == '/': - print ("Directory '%s' is the root of filesystem. " - "Not performing any operations on the root of filesystem" % - os.path.abspath(path)) + print("Directory '%s' is the root of filesystem. " + "Not performing any operations on the root of filesystem" % ( + os.path.abspath(path))) return True else: return False @@ -72,26 +73,25 @@ def create_dir(dir_path): try: os.makedirs(dir_abs_path) except (OSError, IOError): - print "Unable to create dir: %s" % dir_abs_path + print("Unable to create dir: %s" % dir_abs_path) return 1 return 0 def fd_write_file(filename, file_size, chunk_sizes_list, write_time, delay_between_writes=10, log_level='INFO'): - """Write random data to the file until write_time - """ + """Write random data to the file until write_time.""" rc = 0 time_counter = 0 try: fd = open(filename, "w+b") - fd.seek(file_size-1) - fd.write("0") + fd.seek(file_size - 1) + fd.write(bytes(str("0").encode("utf-8"))) fd.flush() except IOError as e: - print ("Unable to open file %s for writing : %s" % (filename, - e.strerror)) + print("Unable to open file %s for writing : %s" % ( + filename, e.strerror)) return 1 while time_counter < write_time: @@ -102,18 +102,18 @@ def fd_write_file(filename, file_size, chunk_sizes_list, write_time, range(current_chunk_size))) offset = random.randint(0, (actual_file_size - current_chunk_size)) if log_level.upper() == 'DEBUG': - print ("\tFileName: %s, File Size: %s, " - "Writing to offset: %s, " - "Data Length: %d, Time Counter: %d" % - (filename, actual_file_size, offset, len(write_data), - time_counter)) + print("\tFileName: %s, File Size: %s, " + "Writing to offset: %s, " + "Data Length: %d, Time Counter: %d" % ( + filename, actual_file_size, offset, len(write_data), + time_counter)) fd.seek(offset) - fd.write(write_data) + fd.write(bytes(str(write_data).encode("utf-8"))) fd.seek(0) fd.flush() except IOError as e: - print ("Unable to write to file '%s' : %s at time count: %dS" % - (filename, e.strerror, time_counter)) + print("Unable to write to file '%s' : %s at time count: %dS" % ( + filename, e.strerror, time_counter)) rc = 1 time.sleep(delay_between_writes) @@ -129,11 +129,11 @@ def fd_writes(args): base_file_name = args.base_file_name file_sizes_list = args.file_sizes_list if file_sizes_list: - file_sizes_list = filter(None, args.file_sizes_list.split(",")) + file_sizes_list = list(filter(None, args.file_sizes_list.split(","))) chunk_sizes_list = args.chunk_sizes_list if chunk_sizes_list: - chunk_sizes_list = map(int, filter(None, - args.chunk_sizes_list.split(","))) + chunk_sizes_list = list( + map(int, filter(None, args.chunk_sizes_list.split(",")))) write_time = int(args.write_time) delay_between_writes = int(args.delay_between_writes) log_level = args.log_level @@ -150,11 +150,11 @@ def fd_writes(args): file_sizes_dict = { 'k': 1024, 'K': 1024, - 'm': 1024*1024, - 'M': 1024*1024, - 'g': 1024*1024*1024, - 'G': 1024*1024*1024 - } + 'm': 1024 ** 2, + 'M': 1024 ** 2, + 'g': 1024 ** 3, + 'G': 1024 ** 3, + } file_sizes_expanded_list = [] for size in file_sizes_list: @@ -240,15 +240,15 @@ if __name__ == "__main__": parser.set_defaults(func=fd_writes) - print "Starting Script: %s" % ' '.join(sys.argv) - print "StarTime :'%s' " % (datetime.datetime.now()) + print("Starting Script: %s" % ' '.join(sys.argv)) + print("StarTime :'%s' " % datetime.datetime.now()) test_start_time = datetime.datetime.now().replace(microsecond=0) args = parser.parse_args() rc = args.func(args) test_end_time = datetime.datetime.now().replace(microsecond=0) - print "Execution time: %s" % (test_end_time - test_start_time) - print "EndTime :'%s' " % (datetime.datetime.now()) + print("Execution time: %s" % (test_end_time - test_start_time)) + print("EndTime :'%s' " % datetime.datetime.now()) sys.exit(rc) diff --git a/glustolibs-io/shared_files/scripts/file_dir_ops.py b/glustolibs-io/shared_files/scripts/file_dir_ops.py index 96e53262d..908a48c8e 100755 --- a/glustolibs-io/shared_files/scripts/file_dir_ops.py +++ b/glustolibs-io/shared_files/scripts/file_dir_ops.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2015-2018 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2015-2019 Red Hat, Inc. <http://www.redhat.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,18 +20,22 @@ """ from __future__ import print_function -import os import argparse -import sys -import random -import string +import contextlib import datetime from multiprocessing import Process -import subprocess -from docx import Document -import contextlib +from multiprocessing.pool import ThreadPool +import os import platform +import random import shutil +import string +import subprocess +import sys + +from docx import Document +import numpy as np +from sh import rsync as sh_rsync if platform.system() == "Windows": path_sep = "\\" @@ -49,9 +53,9 @@ def is_root(path): True if path is '/' , False otherwise """ if os.path.realpath(os.path.abspath(path)) == '/': - print ("Directory '%s' is the root of filesystem. " - "Not performing any operations on the root of filesystem" % - os.path.abspath(path)) + print("Directory '%s' is the root of filesystem. " + "Not performing any operations on the root of filesystem" % ( + os.path.abspath(path))) return True else: return False @@ -106,7 +110,7 @@ def create_dir(dir_path): try: os.makedirs(dir_abs_path) except (OSError, IOError): - print ("Unable to create dir: %s" % dir_abs_path) + print("Unable to create dir: %s" % dir_abs_path) return 1 return 0 @@ -138,16 +142,16 @@ def create_dirs(dir_path, depth, num_of_dirs, num_of_files=0, base_file_name, file_types) except (OSError, IOError) as e: if 'File exists' not in e.strerror: - print ("Unable to create dir '%s' : %s" - % (dir_path, e.strerror)) + print("Unable to create dir '%s' : %s" % ( + dir_path, e.strerror)) with open("/tmp/file_dir_ops_create_dirs_rc", "w") as fd: try: fd.write("1") fd.flush() fd.close() - except IOError as e: - print ("Unable to write the rc to the " - "/tmp/file_dir_ops_create_dirs_rc file") + except IOError: + print("Unable to write the rc to the " + "/tmp/file_dir_ops_create_dirs_rc file") if depth == 0: return 0 for i in range(num_of_dirs): @@ -183,9 +187,10 @@ def create_deep_dirs(args): for i in range(dirname_start_num, (dirname_start_num + dir_length)): num_of_dirs = random.choice(range(1, max_num_of_dirs + 1)) process_dir_path = os.path.join(dir_path, "user%d" % i) - process_list.append(Process(target=create_dirs, - args=(process_dir_path, dir_depth, - num_of_dirs))) + process_list.append(Process( + target=create_dirs, + args=(process_dir_path, dir_depth, num_of_dirs) + )) for each_process in process_list: each_process.start() @@ -237,11 +242,11 @@ def create_deep_dirs_with_files(args): for i in range(dirname_start_num, (dirname_start_num + dir_length)): num_of_dirs = random.choice(range(1, max_num_of_dirs + 1)) process_dir_path = os.path.join(dir_path, "user%d" % i) - process_list.append(Process(target=create_dirs, - args=(process_dir_path, dir_depth, - num_of_dirs, num_of_files, - fixed_file_size, base_file_name, - file_types))) + process_list.append(Process( + target=create_dirs, + args=(process_dir_path, dir_depth, num_of_dirs, num_of_files, + fixed_file_size, base_file_name, file_types) + )) for each_process in process_list: each_process.start() @@ -256,6 +261,48 @@ def create_deep_dirs_with_files(args): return int(rc) +def _create_file(file_abs_path, file_type, file_size): + rc = 0 + + if file_type == 'txt': + file_abs_path += ".txt" + + with open(file_abs_path, "w+") as new_file: + try: + new_file.write(''.join( + np.random.choice(list(string.printable), file_size))) + new_file.flush() + new_file.close() + except IOError as err: + print("Unable to write to file '%s' : %s" % ( + file_abs_path, err.strerror)) + rc = 1 + + elif file_type == 'docx': + file_abs_path += ".docx" + try: + document = Document() + str_to_write = list(string.ascii_letters + string.digits) + file_str = ''.join(np.random.choice(str_to_write, file_size)) + document.add_paragraph(file_str) + document.save(file_abs_path) + except Exception as err: + print("Unable to write to file '%s' : %s" % ( + file_abs_path, err.strerror)) + rc = 1 + + elif file_type == 'empty_file': + try: + with open(file_abs_path, "w+") as new_file: + new_file.close() + except IOError as err: + print("Unable to write to file '%s' : %s" % ( + file_abs_path, err.strerror)) + rc = 1 + + return rc + + def _create_files(dir_path, num_of_files, fixed_file_size=None, base_file_name='testfile', file_types='txt'): rc = 0 @@ -264,62 +311,38 @@ def _create_files(dir_path, num_of_files, fixed_file_size=None, '1k': 1024, '10k': 10240, '512k': 524288, - '1M': 1048576 - } + '1M': 1048576, + } # Create dir_path rc = create_dir(dir_path) if rc != 0: return rc - for count in range(num_of_files): - fname = base_file_name + str(count) - fname_abs_path = os.path.join(dir_path, fname) - if fixed_file_size is None: - file_size = ( - file_sizes_dict[random.choice(list(file_sizes_dict.keys()))]) - else: - try: - file_size = file_sizes_dict[fixed_file_size] - except KeyError as e: - print ("File sizes can be [1k, 10k, 512k, 1M]") - return 1 + fname_abs_path = os.path.join(dir_path, base_file_name) + if fixed_file_size is None: + # this generator yields file tuples: (file name, file type, file size) + files = ((fname_abs_path + str(num), + random.choice(file_types_list), + random.choice(list(file_sizes_dict.values()))) + for num in range(num_of_files)) + else: + try: + files = ((fname_abs_path + str(num), + random.choice(file_types_list), + file_sizes_dict[fixed_file_size]) + for num in range(num_of_files)) + except KeyError: + print("File sizes can be [1k, 10k, 512k, 1M]") + return 1 - type = random.choice(file_types_list) - if type == 'txt': - fname_abs_path = fname_abs_path + ".txt" + # Thread per filetype (for now) + pool = ThreadPool(len(file_types_list)) + ret = pool.map(lambda file_tuple: _create_file(*file_tuple), files) + pool.close() + pool.join() + rc = 1 if any(ret) else 0 - with open(fname_abs_path, "w+") as fd: - try: - fd.write(''.join(random.choice(string.printable) for x in - range(file_size))) - fd.flush() - fd.close() - except IOError as e: - print ("Unable to write to file '%s' : %s" % - (fname_abs_path, e.strerror)) - rc = 1 - elif type == 'docx': - fname_abs_path = fname_abs_path + ".docx" - try: - document = Document() - str_to_write = string.ascii_letters + string.digits - file_str = (''.join(random.choice(str_to_write) - for x in range(file_size))) - document.add_paragraph(file_str) - document.save(fname_abs_path) - except Exception as e: - print ("Unable to write to file '%s' : %s" % - (fname_abs_path, e.strerror)) - rc = 1 - elif type == 'empty_file': - try: - with open(fname_abs_path, "w+") as fd: - fd.close() - except IOError as e: - print ("Unable to write to file '%s' : %s" % - (fname_abs_path, e.strerror)) - rc = 1 return rc @@ -367,7 +390,7 @@ def rename(args): # Check if dir_path exists if not path_exists(dir_path): - print ("Directory '%s' does not exist" % dir_path) + print("Directory '%s' does not exist" % dir_path) return 1 rc = 0 @@ -381,7 +404,7 @@ def rename(args): os.rename(old, new) except OSError: rc = 1 - print ("Unable to rename %s -> %s" % (old, new)) + print("Unable to rename %s -> %s" % (old, new)) # rename dirs if dirName != dir_path: @@ -391,19 +414,18 @@ def rename(args): os.rename(old, new) except OSError: rc = 1 - print ("Unable to rename %s -> %s" % (old, new)) + print("Unable to rename %s -> %s" % (old, new)) return rc def ls(args): - """Recursively list all the files/dirs under 'dir' - """ + """Recursively list all the files/dirs under 'dir'.""" dir_path = os.path.abspath(args.dir) log_file_name = args.log_file_name # Check if dir_path exists if not path_exists(dir_path): - print ("Directory '%s' does not exist" % dir_path) + print("Directory '%s' does not exist" % dir_path) return 1 with open_file_to_write(log_file_name) as file_handle: @@ -423,12 +445,10 @@ def ls(args): def _get_path_stats(path): - """Get the stat of a specified path. - """ + """Get the stat of a specified path.""" rc = 0 path = os.path.abspath(args.path) file_stats = {} - file_stats = {} if platform.system() == "Linux": cmd = "stat -c " + "'%A %U %G' " + path @@ -455,8 +475,8 @@ def _get_path_stats(path): 'mtime': stat.st_mtime, 'ctime': stat.st_ctime, 'inode': stat.st_ino, - 'stat': stat - }) + 'stat': stat, + }) except Exception: rc = 1 err = "Unable to get the stat of path %s" % path @@ -465,41 +485,39 @@ def _get_path_stats(path): def get_path_stats(args): - """Get file/dir Stat - """ + """Get file/dir Stat.""" path = os.path.abspath(args.path) recursive = args.recursive log_file_name = args.log_file_name # Check if dir_path exists if not path_exists(path): - print ("PATH '%s' does not exist" % path) + print("PATH '%s' does not exist" % path) return 1 file_stats = {} if os.path.isfile(path): - file_stats[path] = (_get_path_stats(path)) + file_stats[path] = _get_path_stats(path) if os.path.isdir(path): if recursive: for dirName, subdirList, fileList in os.walk(path, topdown=False): - file_stats[dirName] = (_get_path_stats(dirName)) + file_stats[dirName] = _get_path_stats(dirName) for fname in fileList: fname_abs_path = os.path.join(dirName, fname) - file_stats[fname_abs_path] = (_get_path_stats( - fname_abs_path)) + file_stats[fname_abs_path] = _get_path_stats( + fname_abs_path) else: - file_stats[path] = (_get_path_stats(path)) + file_stats[path] = _get_path_stats(path) rc = 0 with open_file_to_write(log_file_name) as file_handle: if log_file_name: time_str = _get_current_time() - file_handle.write("Starting 'stat %s' : %s" % ( - path, time_str)) + file_handle.write("Starting 'stat %s' : %s" % (path, time_str)) for key in file_stats.keys(): file_handle.write("\nFile: %s" % key) ret, file_stat, err = file_stats[key] @@ -510,8 +528,7 @@ def get_path_stats(args): file_handle.write("\t%s\n" % file_stat) if log_file_name: time_str = _get_current_time() - file_handle.write("Ending 'stat %s' : %s" % ( - path, time_str)) + file_handle.write("Ending 'stat %s' : %s" % (path, time_str)) file_handle.write("\n") return rc @@ -531,7 +548,7 @@ def compress(args): # Check if dir_path exists if not path_exists(dir_path): - print ("Directory '%s' does not exist" % dir_path) + print("Directory '%s' does not exist" % dir_path) return 1 # Create dir_path @@ -546,16 +563,16 @@ def compress(args): proc_list = [] for each_dir in dirs: if compress_type == '7z': - file_name = (dest_dir + path_sep + - os.path.basename(each_dir) + "_7z.7z") + file_name = dest_dir + path_sep + os.path.basename( + each_dir) + "_7z.7z" cmd = "7z a -t7z " + file_name + " " + each_dir elif compress_type == 'gzip': - tmp_file_name = (dir_path + path_sep + - os.path.basename(each_dir) + "_tar.tar") - file_name = (dest_dir + path_sep + - os.path.basename(each_dir) + "_tgz.tgz") - cmd = ("7z a -ttar -so " + tmp_file_name + " " + - each_dir + " | 7z a -si " + file_name) + tmp_file_name = dir_path + path_sep + os.path.basename( + each_dir) + "_tar.tar" + file_name = dest_dir + path_sep + os.path.basename( + each_dir) + "_tgz.tgz" + cmd = ("7z a -ttar -so " + tmp_file_name + " " + + each_dir + " | 7z a -si " + file_name) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) proc_list.append(proc) @@ -570,12 +587,12 @@ def compress(args): file_name = dest_dir + path_sep + os.path.basename(dir_path) + "_7z.7z" cmd = "7z a -t7z " + file_name + " " + dir_path elif compress_type == 'gzip': - tmp_file_name = (dest_dir + path_sep + os.path.basename(dir_path) + - "_tar.tar") - file_name = (dest_dir + path_sep + os.path.basename(dir_path) + - "_tgz.tgz") - cmd = ("7z a -ttar -so " + tmp_file_name + " " + dir_path + - " | 7z a -si " + file_name) + tmp_file_name = (dest_dir + path_sep + os.path.basename(dir_path) + + "_tar.tar") + file_name = dest_dir + path_sep + os.path.basename( + dir_path) + "_tgz.tgz" + cmd = ("7z a -ttar -so " + tmp_file_name + " " + dir_path + + " | 7z a -si " + file_name) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) proc.communicate() @@ -587,13 +604,12 @@ def compress(args): def uncompress(args): - """UnCompress the given compressed file - """ + """UnCompress the given compressed file.""" compressed_file = os.path.abspath(args.compressed_file) dest_dir = args.dest_dir date_time = datetime.datetime.now().strftime("%I_%M%p_%B_%d_%Y") - cmd = ("7z x " + compressed_file + " -o" + dest_dir + path_sep + - "uncompress_" + date_time + " -y") + cmd = ("7z x " + compressed_file + " -o" + dest_dir + path_sep + + "uncompress_" + date_time + " -y") proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) proc.communicate() @@ -605,13 +621,12 @@ def uncompress(args): def uncompress_dir(args): - """UnCompress all compressed files in destination directory - """ + """UnCompress all compressed files in destination directory.""" dir_path = os.path.abspath(args.dir) dest_dir = args.dest_dir date_time = datetime.datetime.now().strftime("%I_%M%p_%B_%d_%Y") - cmd = ("7z x " + dir_path + " -o" + dest_dir + path_sep + - "uncompress_" + date_time + " -y") + cmd = ("7z x " + dir_path + " -o" + dest_dir + path_sep + + "uncompress_" + date_time + " -y") proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) proc.communicate() @@ -623,7 +638,7 @@ def uncompress_dir(args): def create_hard_links(args): - """Creates hard link""" + """Create hard link.""" src_dir = os.path.abspath(args.src_dir) dest_dir = args.dest_dir @@ -633,7 +648,7 @@ def create_hard_links(args): # Check if src_dir exists if not path_exists(src_dir): - print ("Directory '%s' does not exist" % src_dir) + print("Directory '%s' does not exist" % src_dir) return 1 # Create dir_path @@ -650,8 +665,8 @@ def create_hard_links(args): rc = create_dir(dest_dir + path_sep + tmp_dir) if rc != 0: rc = 1 - link_file = (dest_dir + path_sep + tmp_dir + path_sep + - new_fname + "_h") + link_file = (dest_dir + path_sep + tmp_dir + path_sep + + new_fname + "_h") target_file = os.path.join(dir_name, fname) if platform.system() == "Windows": cmd = "mklink /H " + link_file + " " + target_file @@ -702,9 +717,7 @@ def read(args): def copy(args): - """ - Copies files/dirs under 'dir' to destination directory - """ + """Copy files/dirs under 'dir' to destination directory.""" src_dir = os.path.abspath(args.src_dir) dest_dir = args.dest_dir @@ -714,7 +727,7 @@ def copy(args): # Check if src_dir exists if not path_exists(src_dir): - print ("Directory '%s' does not exist" % src_dir) + print("Directory '%s' does not exist" % src_dir) return 1 # Create dest_dir @@ -735,8 +748,8 @@ def copy(args): if dir_name != src_dir: try: src = dir_name - dst = (dest_dir + path_sep + - os.path.basename(os.path.normpath(src))) + dst = (dest_dir + path_sep + + os.path.basename(os.path.normpath(src))) shutil.copytree(src, dst) except OSError: rc = 1 @@ -744,9 +757,7 @@ def copy(args): def delete(args): - """ - Deletes files/dirs under 'dir' - """ + """Delete files/dirs under 'dir'.""" dir_path = os.path.abspath(args.dir) # Check if dir_path is '/' @@ -755,7 +766,7 @@ def delete(args): # Check if dir_path exists if not path_exists(dir_path): - print ("Directory '%s' does not exist" % dir_path) + print("Directory '%s' does not exist" % dir_path) return 1 rc = 0 @@ -774,8 +785,137 @@ def delete(args): return rc +sizes_dict = { + '1k': 1024, + '10k': 10240, + '512k': 524288, + '1M': 1048576, + '0.5k': 513 +} + + +def append(args): + """ + Appends all files under 'dir' with randomly sized data. + """ + dir_path = os.path.abspath(args.dir) + if not path_exists(args.dir): + return 1 + rc = 0 + + for dir_name, subdir_list, file_list in os.walk(dir_path, topdown=False): + for fname in file_list: + append_size = sizes_dict[ + random.choice(list(sizes_dict.keys()))] + try: + file = os.path.join(dir_name, fname) + with open(file, "a") as fd: + try: + fd.write(''.join(random.choice(string.printable) + for x in range(append_size))) + fd.flush() + except IOError as e: + print("Unable to append to file '%s' : %s" % + (file, e.strerror)) + rc = 1 + + except OSError: + rc = 1 + + return rc + + +def overwrite(args): + """ + Truncates everything present and overwrites the file with new data. + """ + dir_path = os.path.abspath(args.dir) + if not path_exists(args.dir): + return 1 + rc = 0 + + for dir_name, subdir_list, file_list in os.walk(dir_path, topdown=False): + for fname in file_list: + new_size = sizes_dict[ + random.choice(list(sizes_dict.keys()))] + try: + file = os.path.join(dir_name, fname) + with open(file, "w+") as fd: + try: + fd.write(''.join(random.choice(string.printable) + for x in range(new_size))) + fd.flush() + except IOError as e: + print("Unable to write to file '%s' : %s" % + (file, e.strerror)) + rc = 1 + except OSError: + rc = 1 + return rc + + +def truncate(args): + """ + Truncates files to a certain size calculated randomly. + """ + dir_path = os.path.abspath(args.dir) + if not path_exists(args.dir): + return 1 + rc = 0 + + for dir_name, subdir_list, file_list in os.walk(dir_path, topdown=False): + for fname in file_list: + try: + file = os.path.join(dir_name, fname) + with open(file, "a+") as fd: + try: + fsize = os.path.getsize(file) + new_size = random.randrange( + 0, fsize//random.choice([2, 3, 4, 5])) + fd.truncate(new_size) + + except IOError as e: + print("Unable to truncate file '%s' : %s" % + (file, e.strerror)) + rc = 1 + except OSError: + rc = 1 + return rc + + +def rsync(args): + """ + rsync files from source to destination. + """ + src_dir = os.path.abspath(args.src_dir) + remote_dir = args.remote_dir + + if platform.system() == "Windows": + print("rsync not supported on Windows,Exiting!") + return 1 + + # Check if src_dir exists + if not path_exists(src_dir): + print("Directory '%s' does not exist" % src_dir) + return 1 + + # Create dest_dir + rc = create_dir(remote_dir) + if rc != 0: + return rc + rc = 0 + + try: + sh_rsync("-r", remote_dir, src_dir) + + except Exception as e: + print("Can't rsync! : %s" % e.strerror) + rc = 1 + return rc + + if __name__ == "__main__": - print ("Starting File/Dir Ops: %s" % _get_current_time()) + print("Starting File/Dir Ops: %s" % _get_current_time()) test_start_time = datetime.datetime.now().replace(microsecond=0) parser = argparse.ArgumentParser( @@ -1019,7 +1159,66 @@ if __name__ == "__main__": help="Directory on which operations has to be performed") read_parser.set_defaults(func=read) - # copy all files/directories under dir + # Appends files under dir + append_parser = subparsers.add_parser( + 'append', + help=("Appends data to already created files. "), + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + append_parser.add_argument( + '--log-file', help="Output log filename to log the " + "contents of file", + metavar=('log_file'), dest='log_file', + type=str, default=default_log_file) + append_parser.add_argument( + 'dir', metavar='DIR', type=str, + help="Directory on which operations has to be performed") + append_parser.set_defaults(func=append) + + # Overwrites files under dir + overwrite_parser = subparsers.add_parser( + 'overwrite', + help=("Overwrites existing files with new data "), + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + overwrite_parser.add_argument( + '--log-file', help="Output log filename to log the " + "contents of file", + metavar=('log_file'), dest='log_file', + type=str, default=default_log_file) + overwrite_parser.add_argument( + 'dir', metavar='DIR', type=str, + help="Directory on which operations has to be performed") + overwrite_parser.set_defaults(func=overwrite) + + # rsync dir to a remote directory + rsyncs_parser = subparsers.add_parser( + 'rsync', + help=("Rsync all dirs in a remote location to 'dir'. "), + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + rsyncs_parser.add_argument( + '--remote-dir', help="Remote location to rsync from)", + metavar=('remote_dir'), dest='remote_dir', + type=str) + rsyncs_parser.add_argument( + 'src_dir', metavar='src_dir', type=str, + help="Directory on which operations has to be performed") + rsyncs_parser.set_defaults(func=rsync) + + # Truncates files under dir + truncate_parser = subparsers.add_parser( + 'truncate', + help=("Truncates existing files "), + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + truncate_parser.add_argument( + '--log-file', help="Output log filename to log the " + "contents of file", + metavar=('log_file'), dest='log_file', + type=str, default=default_log_file) + truncate_parser.add_argument( + 'dir', metavar='DIR', type=str, + help="Directory on which operations has to be performed") + truncate_parser.set_defaults(func=truncate) + + # Copy all files/directories under dir copy_parser = subparsers.add_parser( 'copy', help=("Copy all files/directories under 'dir'. "), @@ -1047,6 +1246,6 @@ if __name__ == "__main__": rc = args.func(args) test_end_time = datetime.datetime.now().replace(microsecond=0) - print ("Execution time: %s" % (test_end_time - test_start_time)) - print ("Ending File/Dir Ops %s" % _get_current_time()) + print("Execution time: %s" % (test_end_time - test_start_time)) + print("Ending File/Dir Ops %s" % _get_current_time()) sys.exit(rc) diff --git a/glustolibs-io/shared_files/scripts/file_lock.py b/glustolibs-io/shared_files/scripts/file_lock.py new file mode 100644 index 000000000..e29fd1b1d --- /dev/null +++ b/glustolibs-io/shared_files/scripts/file_lock.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# Copyright (C) 2020 Red Hat, Inc. <http://www.redhat.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from fcntl import flock, LOCK_EX, LOCK_NB, LOCK_UN +from time import sleep +from argparse import ArgumentParser + + +def get_file_lock(args): + """ + Gets the lock to a file and releases it after timeout + """ + file_name = args.f + timeout = args.t + f = open(file_name, 'w') + flock(f.fileno(), LOCK_EX | LOCK_NB) + sleep(int(timeout)) + flock(f.fileno(), LOCK_UN) + + +if __name__ == "__main__": + file_lock_parser = ArgumentParser( + prog="file_lock.py", description="Program to validate file lock ops") + + file_lock_req_args = file_lock_parser.add_argument_group( + 'required named arguments') + file_lock_req_args.add_argument( + '-f', type=str, required=True, + help="File on which lock has to be applied") + file_lock_req_args.add_argument( + '-t', help="time for which lock has to be retained", type=int, + required=True) + + file_lock_parser.set_defaults(func=get_file_lock) + + args = file_lock_parser.parse_args() + rc = args.func(args) diff --git a/glustolibs-io/shared_files/scripts/generate_io.py b/glustolibs-io/shared_files/scripts/generate_io.py index d07bda7b0..d80389fd3 100755 --- a/glustolibs-io/shared_files/scripts/generate_io.py +++ b/glustolibs-io/shared_files/scripts/generate_io.py @@ -15,29 +15,29 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import subprocess -import re -import time +""" +Script for generating IO on client +""" + +from __future__ import print_function +import argparse +import datetime import multiprocessing -import tempfile import os +import re import shutil import signal -import argparse +import subprocess import sys +import tempfile +import time import yaml -import datetime -ONE_GB_BYTES = 1073741824.0 - -""" -Script for generating IO on client -""" +ONE_GB_BYTES = float(1024 ** 3) def get_disk_usage(path): - """ - This module gets disk usage of the given path + """Get disk usage of the given path. Args: path (str): path for which disk usage to be calculated @@ -45,7 +45,6 @@ def get_disk_usage(path): Returns: dict: disk usage in dict format on success None Type, on failure - """ cmd = 'stat -f ' + path @@ -73,30 +72,27 @@ def get_disk_usage(path): print("Regex mismatch in get_disk_usage()") return None - usage_info = dict() - keys = ['b_size', 'b_total', 'b_free', 'b_avail', 'i_total', 'i_free'] - val = list(match.groups()) - info = dict(zip(keys, val)) - usage_info['total'] = ((int(info['b_total']) * int(info['b_size'])) / - ONE_GB_BYTES) - usage_info['free'] = ((int(info['b_free']) * int(info['b_size'])) / - ONE_GB_BYTES) - usage_info['used_percent'] = (100 - (100.0 * usage_info['free'] / - usage_info['total'])) - usage_info['total_inode'] = int(info['i_total']) - usage_info['free_inode'] = int(info['i_free']) - usage_info['used_percent_inode'] = ((100 - - (100.0 * usage_info['free_inode']) / - usage_info['total_inode'])) + keys = ('b_size', 'b_total', 'b_free', 'b_avail', 'i_total', 'i_free') + values = list(match.groups()) + data = dict(zip(keys, values)) + usage_info = {'total': ( + int(data['b_total']) * int(data['b_size']) // ONE_GB_BYTES)} + usage_info['free'] = ( + int(data['b_free']) * int(data['b_size']) // ONE_GB_BYTES) + usage_info['used_percent'] = ( + 100 - (100.0 * usage_info['free'] // usage_info['total'])) + usage_info['total_inode'] = int(data['i_total']) + usage_info['free_inode'] = int(data['i_free']) + usage_info['used_percent_inode'] = ( + 100 - (100.0 * usage_info['free_inode']) // usage_info['total_inode']) usage_info['used'] = usage_info['total'] - usage_info['free'] - usage_info['used_inode'] = (usage_info['total_inode'] - - usage_info['free_inode']) + usage_info['used_inode'] = ( + usage_info['total_inode'] - usage_info['free_inode']) return usage_info def get_disk_used_percent(dirname): - """ - Module to get disk used percent + """Get disk used percentage. Args: dirname (str): absolute path of directory @@ -107,21 +103,18 @@ def get_disk_used_percent(dirname): Example: get_disk_used_percent("/mnt/glusterfs") - """ output = get_disk_usage(dirname) if output is None: - print("Failed to get disk used percent for %s" - % dirname) + print("Failed to get disk used percent for %s" % dirname) return None return output['used_percent'] def check_if_percent_to_fill_or_timeout_is_met(dirname, percent_to_fill, timeout): - """ - Module to check if percent to fill or timeout is met. + """Check if percent to fill or timeout is met. Args: dirname (str): absolute path of directory @@ -133,8 +126,7 @@ def check_if_percent_to_fill_or_timeout_is_met(dirname, percent_to_fill, is met, False otherwise Example: - check_if_percent_to_fill_or_timeout_is_met("/mnt/glusterfs", - 10, 60) + check_if_percent_to_fill_or_timeout_is_met("/mnt/glusterfs", 10, 60) """ flag = 0 count = 0 @@ -145,11 +137,11 @@ def check_if_percent_to_fill_or_timeout_is_met(dirname, percent_to_fill, if int(percent_to_fill) > int(used): remaining_to_fill = int(percent_to_fill) - int(used) - print("Remaining space left to fill data in directory %s is %s" - % (dirname, str(remaining_to_fill))) + print("Remaining space left to fill data in directory %s is %s" % ( + dirname, str(remaining_to_fill))) time_str = datetime.datetime.now().strftime('%Y_%m_%d_%H_%M_%S') - print("Directory %s used percent at time %s: %s" - % (dirname, time_str, used)) + print("Directory %s used percent at time %s: %s" % ( + dirname, time_str, used)) if int(percent_to_fill) <= int(used): flag = 1 break @@ -157,8 +149,8 @@ def check_if_percent_to_fill_or_timeout_is_met(dirname, percent_to_fill, count = count + 5 else: print("Directory %s is filled with given percent already. " - "Percentage filled: %s" - % (dirname, str(percent_to_fill))) + "Percentage filled: %s" % ( + dirname, str(percent_to_fill))) flag = 1 break @@ -169,19 +161,15 @@ def check_if_percent_to_fill_or_timeout_is_met(dirname, percent_to_fill, else: print("Timeout %s seconds reached before filling directory with " "given percentage %s" % (str(timeout), str(percent_to_fill))) - return True - return False + return False def run_check_if_percent_to_fill_or_timeout_is_met(dirname, percent_to_fill, timeout, event): - """ - Helper Module to check if percent to fill or timeout is met. - """ - ret = check_if_percent_to_fill_or_timeout_is_met(dirname, - percent_to_fill, - timeout) + """Check if percent to fill or timeout is met.""" + ret = check_if_percent_to_fill_or_timeout_is_met( + dirname, percent_to_fill, timeout) if ret: event.set() return True @@ -189,10 +177,8 @@ def run_check_if_percent_to_fill_or_timeout_is_met(dirname, return False -def run_fio(proc_queue, script_path, dirname, - job_files_list, log_file): - """ - Module to invoke IOs using fio tool +def run_fio(proc_queue, script_path, dirname, job_files_list, log_file): + """Invoke IOs using fio tool. Args: proc_queue (obj): multiprocessing queue object @@ -204,7 +190,6 @@ def run_fio(proc_queue, script_path, dirname, Returns: bool: True, if fio starts to write data and stops when it gets "STOP" string in queue, False otherwise - """ tmpdir = tempfile.mkdtemp() job_files_list_to_run = [] @@ -213,23 +198,17 @@ def run_fio(proc_queue, script_path, dirname, shutil.copy(job_file, job_file_to_run) job_files_list_to_run.append(job_file_to_run) + python_bin = "/usr/bin/env python%d" % sys.version_info.major + cmd = "%s %s --job-files '%s' %s" % ( + python_bin, script_path, ' '.join(job_files_list_to_run), dirname) if log_file is not None: with open(log_file, "w") as fd: time_str = datetime.datetime.now().strftime('%Y_%m_%d_%H_%M_%S') - title = ("=========STARTING FIO-" + time_str + - "=======\n") + title = ("=========STARTING FIO-" + time_str + "=======\n") fd.write(title) fd.close() - cmd = ("python " + script_path + - " --job-files '" + ' '.join(job_files_list_to_run) + "' " + - dirname + " >> " + log_file + " 2>&1") - - else: - cmd = ("python " + script_path + - " --job-files '" + ' '.join(job_files_list_to_run) + - "' " + dirname) - p = subprocess.Popen(cmd, shell=True, - preexec_fn=os.setsid) + cmd += " >> %s 2>&1" % log_file + p = subprocess.Popen(cmd, shell=True, preexec_fn=os.setsid) time.sleep(10) if p is None: print("Unable to trigger IO using fio") @@ -241,8 +220,7 @@ def run_fio(proc_queue, script_path, dirname, with open(log_file, "a") as fd: time_str = (datetime.datetime.now(). strftime('%Y_%m_%d_%H_%M_%S')) - title = ("=========ENDING FIO-" + time_str + - "=======\n") + title = ("=========ENDING FIO-" + time_str + "=======\n") fd.write(title) fd.close() break @@ -251,10 +229,8 @@ def run_fio(proc_queue, script_path, dirname, return True -def start_populate_data(mount_point, io_dict, - percent_to_fill, timeout): - """ - Starts populating data on the directory +def start_populate_data(mount_point, io_dict, percent_to_fill, timeout): + """Start populating data on a directory. Args: mount_point(str): Directory name to fill data @@ -264,29 +240,23 @@ def start_populate_data(mount_point, io_dict, Returns: bool: returns True, if IO succeeds. False, otherwise - """ dirname = mount_point m = multiprocessing.Manager() event = m.Event() - proc_list = [] - proc_queue = [] - + proc_list, proc_queue = [], [] for each_io in io_dict.keys(): q = multiprocessing.Queue() proc_queue.append(q) workload_type = io_dict[each_io]['workload_type'] - proc = multiprocessing.Process(target=(io_dict[each_io] - ['function_addr']), - args=(q, - (io_dict[each_io] - ['script_path']), - dirname, - (io_dict[each_io]['job_files'] - [workload_type]), - io_dict[each_io]['log_file'])) + proc = multiprocessing.Process( + target=io_dict[each_io]['function_addr'], + args=(q, io_dict[each_io]['script_path'], dirname, + io_dict[each_io]['job_files'][workload_type], + io_dict[each_io]['log_file']) + ) proc_list.append(proc) time.sleep(5) proc.start() @@ -304,8 +274,7 @@ def start_populate_data(mount_point, io_dict, def stop_populate_data(proc_list, proc_queue, mevent=None): - """ - Stops populating data on the directory + """Stop populating data on a directory. Args: proc_list (list): List of processes to kill @@ -338,27 +307,22 @@ def stop_populate_data(proc_list, proc_queue, mevent=None): proc.terminate() return True except Exception as e: - print("Exception occurred in stop_populate_data(): %s" - % e) + print("Exception occurred in stop_populate_data(): %s" % e) return False def call_get_disk_usage(args): - """ - Main method for getting disk usage - """ + """Main method for getting disk usage.""" disk_usage = get_disk_usage(args.dir) if disk_usage is None: return 1 - print disk_usage + print(disk_usage) return 0 def call_start_populate_data(args): - """ - Main method for populating data - """ + """Main method for populating data.""" dirname = args.dir config_file_list = args.c.split() @@ -386,24 +350,18 @@ def call_start_populate_data(args): # case4: If -i | -w | -i and -w is not specified , run all the tools # specified in the config file - if args.i is not None: - io_list = args.i.split() - else: - io_list = [] - + io_list = [] if args.i is None else args.i.split() workload_type = "" if workload is not None: - if (('workload' in config_data['io'] and - config_data['io']['workload'] and - workload in config_data['io']['workload'])): + if workload in (config_data['io'].get('workload', []) or []): if not io_list: io_list = config_data['io']['workload'][workload] else: io_list_from_user = io_list - io_list_for_given_workload = (config_data['io'] - ['workload'][workload]) - io_list = (list(set(io_list_from_user). - intersection(io_list_for_given_workload))) + io_list_for_given_workload = ( + config_data['io']['workload'][workload]) + io_list = (list(set(io_list_from_user).intersection( + io_list_for_given_workload))) workload_type = workload else: if not io_list: @@ -425,41 +383,41 @@ def call_start_populate_data(args): time_str = datetime.datetime.now().strftime('%Y_%m_%d_%H_%M_%S') log_file = filename + "_" + time_str + file_ext - print "GENERATE IO Log file: %s" % log_file + print("GENERATE IO Log file: %s" % log_file) - if('io' in config_data and 'tools' in config_data['io']): + if 'io' in config_data and 'tools' in config_data['io']: config_data_io = dict(config_data['io']['tools']) else: - print "io tools info is not given in config file" + print("io tools info is not given in config file") return 1 - if('io' in config_data and 'scripts' in config_data['io']): + if 'io' in config_data and 'scripts' in config_data['io']: config_data_io.update(config_data['io']['scripts']) else: - print "io scripts info is not given in config file" + print("io scripts info is not given in config file") return 1 io_details = {} for io in io_list: if io in config_data_io.keys(): config_data_io[io]['function_addr'] = eval("run_" + io) - config_data_io[io]['log_file'] = (log_file_dir + "/" + - io + "_log.log") + config_data_io[io]['log_file'] = ( + log_file_dir + "/" + io + "_log.log") config_data_io[io]['workload_type'] = workload_type io_details[io] = config_data_io[io] else: - print ("The IO tool/script - '%s' details not present in config " - "file. Skipping the IO - '%s'" % (io, io)) + print("The IO tool/script - '%s' details not present in config " + "file. Skipping the IO - '%s'" % (io, io)) if not io_details: - print "Config file doesn't have IO details for %s" % ','.join(io_list) + print("Config file doesn't have IO details for %s" % ','.join(io_list)) return 1 # Starts generating IO # If -t and -p bot are passed as options, runs all the io's as specified # until '-t' or '-p' is reached. i.e which ever reaches first. ret = start_populate_data(dirname, io_details, percent, timeout) - print "Disk Usage Details of %s: %s" % (dirname, get_disk_usage(dirname)) + print("Disk Usage Details of %s: %s" % (dirname, get_disk_usage(dirname))) fd_list = [] for io in io_details.keys(): @@ -472,8 +430,8 @@ def call_start_populate_data(args): for each_fh in fd_list: fd.write(each_fh.read()) each_fh.close() - fd.write("\nDisk Usage Details of %s: %s" % (dirname, - get_disk_usage(dirname))) + fd.write("\nDisk Usage Details of %s: %s" % ( + dirname, get_disk_usage(dirname))) fd.close() if ret: @@ -483,39 +441,35 @@ def call_start_populate_data(args): if __name__ == "__main__": - print "Starting IO Generation..." + print("Starting IO Generation...") test_start_time = datetime.datetime.now().replace(microsecond=0) - write_data_parser = argparse.ArgumentParser(prog="generate_io.py", - description=("Program for " - "generating io")) + write_data_parser = argparse.ArgumentParser( + prog="generate_io.py", description="Program for generating io") write_data_required_parser = write_data_parser.add_argument_group( - 'required named arguments') - + 'required named arguments') write_data_required_parser.add_argument( 'dir', metavar='DIR', type=str, help="Directory on which operations has to be performed") - write_data_required_parser.add_argument('-c', help="space separated list " - "of config files", - required=True) - write_data_parser.add_argument('-i', help="space separated list of " - "io tools") + write_data_required_parser.add_argument( + '-c', help="space separated list of config files", required=True) + write_data_parser.add_argument( + '-i', help="space separated list of io tools") write_data_parser.add_argument('-w', help="Workload type") - write_data_parser.add_argument('-p', help="percentage to fill the" - "directory", - type=int, default=100) - write_data_parser.add_argument('-t', help="timeout value in seconds.", - type=int) + write_data_parser.add_argument( + '-p', help="percentage to fill the directory", type=int, default=100) + write_data_parser.add_argument( + '-t', help="timeout value in seconds.", type=int) default_log_file = "/var/tmp/generate_io/generate_io.log" - write_data_parser.add_argument('-l', help="log file name.", - default=default_log_file) + write_data_parser.add_argument( + '-l', help="log file name.", default=default_log_file) write_data_parser.set_defaults(func=call_start_populate_data) args = write_data_parser.parse_args() rc = args.func(args) test_end_time = datetime.datetime.now().replace(microsecond=0) - print "Execution time: %s" % (test_end_time - test_start_time) - print "Ending IO Generation" + print("Execution time: %s" % (test_end_time - test_start_time)) + print("Ending IO Generation") sys.exit(rc) diff --git a/glustolibs-io/shared_files/scripts/memory_and_cpu_logger.py b/glustolibs-io/shared_files/scripts/memory_and_cpu_logger.py new file mode 100644 index 000000000..d2ee80d6c --- /dev/null +++ b/glustolibs-io/shared_files/scripts/memory_and_cpu_logger.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# Copyright (C) 2020 Red Hat, Inc. <http://www.redhat.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +A tool to monitor and log memory consumption processes. +""" +from __future__ import print_function + +import argparse +import csv +from time import sleep +import subprocess + + +def run_command(cmd): + """ + Run command using Popen and return output + """ + ret = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, shell=True) + output = ret.stdout.read().decode('utf8').split('\n')[:-1] + return output + + +def get_memory_and_cpu_consumption(proc_name): + """ + Get the memory and cpu consumed by a given process + """ + # The command gives an output as shown below: + # [2020-08-07 09:34:48] 16422 0.0 9.99609 + # + # Where, + # [2020-08-07 09:34:48] is UTC timestamp. + # 16422 is the process ID. + # 0.0 is the CPU usage. + # 9.99609 is memory consumption in MB. + cmd = ("ps u -p `pgrep " + proc_name + "` | " + "awk 'NR>1 && $11~/" + proc_name + "$/{print " + "strftime(\"[%Y-%d-%m %H:%M:%S]\", " + "systime(), 1), $2,$3,$6/1024}'") + memory_and_cpu_consumed = run_command(cmd) + return memory_and_cpu_consumed + + +def main(): + """ + Main function of the tool. + """ + # Setting up command line arguments + parser = argparse.ArgumentParser( + description="A tool to log memory usage of a given process" + ) + parser.add_argument( + "-p", "--process_name", type=str, dest="process_name", required=True, + help="Name of process for which cpu and memory is to be logged") + parser.add_argument( + "-i", "--interval", type=int, dest="interval", default=60, + help="Time interval to wait between consecutive logs(Default:60)") + parser.add_argument( + "-c", "--count", type=int, dest="count", default=10, + help="Number of times memory and CPU has to be logged (Default:10)") + parser.add_argument( + '-t', '--testname', type=str, dest="testname", required=True, + help="Test name for which memory is logged") + args = parser.parse_args() + + # Declare all three parameters + process_name = args.process_name + count = args.count + interval = args.interval + + # Generating CSV file header + with open('{}.csv'.format(process_name), 'a') as file: + csv_writer_obj = csv.writer(file) + csv_writer_obj.writerow([args.testname, '', '', '']) + csv_writer_obj.writerow([ + 'Time stamp', 'Process ID', 'CPU Usage', 'Memory Usage']) + + # Taking memory output for a given + # number of times + for counter in range(0, count): + print("Iteration: {}".format(counter)) + data = get_memory_and_cpu_consumption(process_name) + + # Logging information to csv file + for line in data: + info = line.split(" ") + csv_writer_obj.writerow([" ".join(info[:2]), info[2], + info[3], info[4]]) + sleep(interval) + + +if __name__ == "__main__": + main() diff --git a/glustolibs-io/shared_files/tools/fio/run_fio.py b/glustolibs-io/shared_files/tools/fio/run_fio.py index ea20175cb..f65ad93d3 100644 --- a/glustolibs-io/shared_files/tools/fio/run_fio.py +++ b/glustolibs-io/shared_files/tools/fio/run_fio.py @@ -15,17 +15,16 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import os import argparse import fileinput +import os import re import subprocess import time def generate_workload_using_fio(root_dirname, ini_file): - """ - Populates data in the given directory using fio tool. + """Populate data in the given directory using fio tool. Args: root_dirname (str): Directory name @@ -54,16 +53,15 @@ if __name__ == "__main__": # http://git.kernel.dk/?p=fio.git;a=blob;f=README; # h=5fa37f3eed33a15a15a38836cf0080edc81688fd;hb=HEAD - parser = argparse.ArgumentParser(prog="test_fio.py", - description=("Generate workload " - "using fio")) + parser = argparse.ArgumentParser( + prog="test_fio.py", + description=("Generate workload using fio")) parser.add_argument( 'dir', metavar='DIR', type=str, help="Directory on which IO has to be performed") - parser.add_argument('--job-files', - metavar=('job_files'), dest='job_files', - help="space separated absolute paths of " - "ini job files", required=True) + parser.add_argument( + '--job-files', metavar=('job_files'), dest='job_files', + help="space separated absolute paths of ini job files", required=True) args = parser.parse_args() root_dirname = args.dir ini_files_list = args.job_files.split() |