timedrift.py 8.6 KB
Newer Older
L
Lucas Meneghel Rodrigues 已提交
1 2
import logging
import time
3 4

import aexpect
X
Xu Han 已提交
5 6 7

from avocado.utils import process

8 9
from virttest import utils_test

10

11
def run(test, params, env):
12 13 14 15 16 17 18 19 20 21 22 23 24 25
    """
    Time drift test (mainly for Windows guests):

    1) Log into a guest.
    2) Take a time reading from the guest and host.
    3) Run load on the guest and host.
    4) Take a second time reading.
    5) Stop the load and rest for a while.
    6) Take a third time reading.
    7) If the drift immediately after load is higher than a user-
    specified value (in %), fail.
    If the drift after the rest period is higher than a user-specified value,
    fail.

L
Lucas Meneghel Rodrigues 已提交
26 27 28
    :param test: QEMU test object.
    :param params: Dictionary with test parameters.
    :param env: Dictionary with the test environment.
29 30 31 32 33 34 35
    """
    # Helper functions
    def set_cpu_affinity(pid, mask):
        """
        Set the CPU affinity of all threads of the process with PID pid.
        Do this recursively for all child processes as well.

L
Lucas Meneghel Rodrigues 已提交
36 37 38
        :param pid: The process ID.
        :param mask: The CPU affinity mask.
        :return: A dict containing the previous mask for each thread.
39
        """
X
Xu Han 已提交
40
        tids = process.system_output("ps -L --pid=%s -o lwp=" % pid,
41
                                     verbose=False, ignore_status=True).split()
42 43
        prev_masks = {}
        for tid in tids:
X
Xu Han 已提交
44 45
            prev_mask = process.system_output("taskset -p %s" % tid,
                                              verbose=False).split()[-1]
46
            prev_masks[tid] = prev_mask
X
Xu Han 已提交
47 48
            process.system("taskset -p %s %s" % (mask, tid), verbose=False)
        children = process.system_output("ps --ppid=%s -o pid=" % pid,
49 50
                                         verbose=False,
                                         ignore_status=True).split()
51 52 53 54 55 56 57 58
        for child in children:
            prev_masks.update(set_cpu_affinity(child, mask))
        return prev_masks

    def restore_cpu_affinity(prev_masks):
        """
        Restore the CPU affinity of several threads.

L
Lucas Meneghel Rodrigues 已提交
59
        :param prev_masks: A dict containing TIDs as keys and masks as values.
60 61
        """
        for tid, mask in prev_masks.items():
62 63
            process.system("taskset -p %s %s" % (mask, tid), verbose=False,
                           ignore_status=True)
64 65 66

    vm = env.get_vm(params["main_vm"])
    vm.verify_alive()
67 68 69 70 71 72 73 74

    boot_option_added = params.get("boot_option_added")
    boot_option_removed = params.get("boot_option_removed")
    if boot_option_added or boot_option_removed:
        utils_test.update_boot_option(vm,
                                      args_removed=boot_option_removed,
                                      args_added=boot_option_added)

75 76 77 78 79
    timeout = int(params.get("login_timeout", 360))
    session = vm.wait_for_login(timeout=timeout)

    # Collect test parameters:
    # Command to run to get the current time
80
    time_command = params["time_command"]
81
    # Filter which should match a string to be passed to time.strptime()
82
    time_filter_re = params["time_filter_re"]
83
    # Time format for time.strptime()
84 85 86 87
    time_format = params["time_format"]
    guest_load_command = params["guest_load_command"]
    guest_load_stop_command = params["guest_load_stop_command"]
    host_load_command = params["host_load_command"]
88 89 90 91 92 93 94 95 96
    guest_load_instances = int(params.get("guest_load_instances", "1"))
    host_load_instances = int(params.get("host_load_instances", "0"))
    # CPU affinity mask for taskset
    cpu_mask = params.get("cpu_mask", "0xFF")
    load_duration = float(params.get("load_duration", "30"))
    rest_duration = float(params.get("rest_duration", "10"))
    drift_threshold = float(params.get("drift_threshold", "200"))
    drift_threshold_after_rest = float(params.get("drift_threshold_after_rest",
                                                  "200"))
97 98
    test_duration = float(params.get("test_duration", "60"))
    interval_gettime = float(params.get("interval_gettime", "20"))
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
    guest_load_sessions = []
    host_load_sessions = []

    try:
        # Set the VM's CPU affinity
        prev_affinity = set_cpu_affinity(vm.get_shell_pid(), cpu_mask)

        try:
            # Open shell sessions with the guest
            logging.info("Starting load on guest...")
            for i in range(guest_load_instances):
                load_session = vm.login()
                # Set output func to None to stop it from being called so we
                # can change the callback function and the parameters it takes
                # with no problems
                load_session.set_output_func(None)
                load_session.set_output_params(())
                load_session.set_output_prefix("(guest load %d) " % i)
                load_session.set_output_func(logging.debug)
                guest_load_sessions.append(load_session)

            # Get time before load
            # (ht stands for host time, gt stands for guest time)
            (ht0, gt0) = utils_test.get_time(session,
123 124 125
                                             time_command,
                                             time_filter_re,
                                             time_format)
126 127 128 129 130 131 132 133

            # Run some load on the guest
            for load_session in guest_load_sessions:
                load_session.sendline(guest_load_command)

            # Run some load on the host
            logging.info("Starting load on host...")
            for i in range(host_load_instances):
134 135 136 137 138
                load_cmd = aexpect.run_bg(host_load_command,
                                          output_func=logging.debug,
                                          output_prefix="(host load %d) " % i,
                                          timeout=0.5)
                host_load_sessions.append(load_cmd)
139
                # Set the CPU affinity of the load process
140
                pid = load_cmd.get_pid()
141 142 143 144 145 146
                set_cpu_affinity(pid, cpu_mask)

            # Sleep for a while (during load)
            logging.info("Sleeping for %s seconds...", load_duration)
            time.sleep(load_duration)

147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
            start_time = time.time()
            while (time.time() - start_time) < test_duration:
                # Get time delta after load
                (ht1, gt1) = utils_test.get_time(session,
                                                 time_command,
                                                 time_filter_re,
                                                 time_format)

                # Report results
                host_delta = ht1 - ht0
                guest_delta = gt1 - gt0
                drift = 100.0 * (host_delta - guest_delta) / host_delta
                logging.info("Host duration: %.2f", host_delta)
                logging.info("Guest duration: %.2f", guest_delta)
                logging.info("Drift: %.2f%%", drift)
                time.sleep(interval_gettime)
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182

        finally:
            logging.info("Cleaning up...")
            # Restore the VM's CPU affinity
            restore_cpu_affinity(prev_affinity)
            # Stop the guest load
            if guest_load_stop_command:
                session.cmd_output(guest_load_stop_command)
            # Close all load shell sessions
            for load_session in guest_load_sessions:
                load_session.close()
            for load_session in host_load_sessions:
                load_session.close()

        # Sleep again (rest)
        logging.info("Sleeping for %s seconds...", rest_duration)
        time.sleep(rest_duration)

        # Get time after rest
        (ht2, gt2) = utils_test.get_time(session,
183 184 185
                                         time_command,
                                         time_filter_re,
                                         time_format)
186 187 188

    finally:
        session.close()
189 190 191 192 193
        # remove flags add for this test.
        if boot_option_added or boot_option_removed:
            utils_test.update_boot_option(vm,
                                          args_removed=boot_option_added,
                                          args_added=boot_option_removed)
194 195 196 197 198 199

    # Report results
    host_delta_total = ht2 - ht0
    guest_delta_total = gt2 - gt0
    drift_total = 100.0 * (host_delta_total - guest_delta_total) / host_delta
    logging.info("Total host duration including rest: %.2f", host_delta_total)
L
Lucas Meneghel Rodrigues 已提交
200 201
    logging.info(
        "Total guest duration including rest: %.2f", guest_delta_total)
202 203 204 205
    logging.info("Total drift after rest: %.2f%%", drift_total)

    # Fail the test if necessary
    if abs(drift) > drift_threshold:
206
        test.fail("Time drift too large: %.2f%%" % drift)
207
    if abs(drift_total) > drift_threshold_after_rest:
208 209
        test.fail("Time drift too large after rest period: %.2f%%"
                  % drift_total)