/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ /* * WiFi load, scan, associate, unload stress test * * Repeatedly executes the following sequence: * * 1. Load WiFi driver * 2. Start supplicant * 3. Random delay * 4. Obtain supplicant status (optional) * 5. Stop supplicant * 6. Unload WiFi driver * * The "Obtain supplicant status" step is optional and is pseudo * randomly performed 50% of the time. The default range of * delay after start supplicant is intentionally selected such * that the obtain supplicant status and stop supplicant steps * may be performed while the WiFi driver is still performing a scan * or associate. The default values are given by DEFAULT_DELAY_MIN * and DEFAULT_DELAY_MAX. Other values can be specified through the * use of the -d and -D command-line options. * * Each sequence is refered to as a pass and by default an unlimited * number of passes are performed. An override of the range of passes * to be executed is available through the use of the -s (start) and * -e (end) command-line options. Can also specify a single specific * pass via the -p option. There is also a default time in which the * test executes, which is given by DEFAULT_DURATION and can be overriden * through the use of the -t command-line option. */ #include <assert.h> #include <errno.h> #include <libgen.h> #include <math.h> #define _GNU_SOURCE #include <sched.h> #include <stdio.h> #include <stdlib.h> #include <time.h> #include <unistd.h> #include <sys/syscall.h> #include <sys/types.h> #include <sys/wait.h> #include <hardware_legacy/wifi.h> #define LOG_TAG "wifiLoadScanAssocTest" #include <utils/Log.h> #include <testUtil.h> #define DEFAULT_START_PASS 0 #define DEFAULT_END_PASS 999 #define DEFAULT_DURATION FLT_MAX // A fairly long time, so that // range of passes will have // precedence #define DEFAULT_DELAY_MIN 0.0 // Min delay after start supplicant #define DEFAULT_DELAY_MAX 20.0 // Max delay after start supplicant #define DELAY_EXP 150.0 // Exponent which determines the // amount by which values closer // to DELAY_MIN are favored. #define CMD_STATUS "wpa_cli status 2>&1" #define CMD_STOP_FRAMEWORK "stop 2>&1" #define CMD_START_FRAMEWORK "start 2>&1" #define MAXSTR 100 #define MAXCMD 500 typedef unsigned int bool_t; #define true (0 == 0) #define false (!true) // File scope variables cpu_set_t availCPU; unsigned int numAvailCPU; float delayMin = DEFAULT_DELAY_MIN; float delayMax = DEFAULT_DELAY_MAX; bool_t driverLoadedAtStart; // Command-line mutual exclusion detection flags. // Corresponding flag set true once an option is used. bool_t eFlag, sFlag, pFlag; // File scope prototypes static void init(void); static void randDelay(void); static void randBind(const cpu_set_t *availSet, int *chosenCPU); /* * Main * * Performs the following high-level sequence of operations: * * 1. Command-line parsing * * 2. Initialization * * 3. Execute passes that repeatedly perform the WiFi load, scan, * associate, unload sequence. * * 4. Restore state of WiFi driver to state it was at the * start of the test. * * 5. Restart framework */ int main(int argc, char *argv[]) { FILE *fp; int rv, opt; int cpu; char *chptr; unsigned int pass; char cmd[MAXCMD]; float duration = DEFAULT_DURATION; unsigned int startPass = DEFAULT_START_PASS, endPass = DEFAULT_END_PASS; struct timeval startTime, currentTime, delta; testSetLogCatTag(LOG_TAG); // Parse command line arguments while ((opt = getopt(argc, argv, "d:D:s:e:p:t:?")) != -1) { switch (opt) { case 'd': // Minimum Delay delayMin = strtod(optarg, &chptr); if ((*chptr != '\0') || (delayMin < 0.0)) { testPrintE("Invalid command-line specified minimum delay " "of: %s", optarg); exit(1); } break; case 'D': // Maximum Delay delayMax = strtod(optarg, &chptr); if ((*chptr != '\0') || (delayMax < 0.0)) { testPrintE("Invalid command-line specified maximum delay " "of: %s", optarg); exit(2); } break; case 't': // Duration duration = strtod(optarg, &chptr); if ((*chptr != '\0') || (duration < 0.0)) { testPrintE("Invalid command-line specified duration of: %s", optarg); exit(3); } break; case 's': // Starting Pass if (sFlag || pFlag) { testPrintE("Invalid combination of command-line options,"); if (sFlag) { testPrintE(" -s flag specified multiple times."); } else { testPrintE(" -s and -p flags are mutually exclusive."); } exit(10); } sFlag = true; startPass = strtoul(optarg, &chptr, 10); if (*chptr != '\0') { testPrintE("Invalid command-line specified starting pass " "of: %s", optarg); exit(4); } break; case 'e': // Ending Pass if (eFlag || pFlag) { testPrintE("Invalid combination of command-line options,"); if (sFlag) { testPrintE(" -e flag specified multiple times."); } else { testPrintE(" -e and -p flags are mutually exclusive."); } exit(11); } eFlag = true; endPass = strtoul(optarg, &chptr, 10); if (*chptr != '\0') { testPrintE("Invalid command-line specified ending pass " "of: %s", optarg); exit(5); } break; case 'p': // Single Specific Pass if (pFlag || sFlag || eFlag) { testPrintE("Invalid combination of command-line options,"); if (pFlag) { testPrintE(" -p flag specified multiple times."); } else { testPrintE(" -p and -%c flags are mutually exclusive.", (sFlag) ? 's' : 'e'); } exit(12); } pFlag = true; endPass = startPass = strtoul(optarg, &chptr, 10); if (*chptr != '\0') { testPrintE("Invalid command-line specified pass " "of: %s", optarg); exit(13); } break; case '?': default: testPrintE(" %s [options]", basename(argv[0])); testPrintE(" options:"); testPrintE(" -s Starting pass"); testPrintE(" -e Ending pass"); testPrintE(" -p Specific single pass"); testPrintE(" -t Duration"); testPrintE(" -d Delay min"); testPrintE(" -D Delay max"); exit(((optopt == 0) || (optopt == '?')) ? 0 : 6); } } if (delayMax < delayMin) { testPrintE("Unexpected maximum delay less than minimum delay"); testPrintE(" delayMin: %f delayMax: %f", delayMin, delayMax); exit(7); } if (endPass < startPass) { testPrintE("Unexpected ending pass before starting pass"); testPrintE(" startPass: %u endPass: %u", startPass, endPass); exit(8); } if (argc != optind) { testPrintE("Unexpected command-line postional argument"); testPrintE(" %s [-s start_pass] [-e end_pass] [-d duration]", basename(argv[0])); exit(9); } testPrintI("duration: %g", duration); testPrintI("startPass: %u", startPass); testPrintI("endPass: %u", endPass); testPrintI("delayMin: %f", delayMin); testPrintI("delayMax: %f", delayMax); init(); // For each pass gettimeofday(&startTime, NULL); for (pass = startPass; pass <= endPass; pass++) { // Stop if duration of work has already been performed gettimeofday(¤tTime, NULL); delta = tvDelta(&startTime, ¤tTime); if (tv2double(&delta) > duration) { break; } testPrintI("==== Starting pass: %u", pass); // Use a pass dependent sequence of random numbers srand48(pass); // Load WiFi Driver randBind(&availCPU, &cpu); if ((rv = wifi_load_driver()) != 0) { testPrintE("CPU: %i wifi_load_driver() failed, rv: %i\n", cpu, rv); exit(20); } testPrintI("CPU: %i wifi_load_driver succeeded", cpu); // Start Supplicant randBind(&availCPU, &cpu); if ((rv = wifi_start_supplicant(false)) != 0) { testPrintE("CPU: %i wifi_start_supplicant() failed, rv: %i\n", cpu, rv); exit(21); } testPrintI("CPU: %i wifi_start_supplicant succeeded", cpu); // Sleep a random amount of time randDelay(); /* * Obtain WiFi Status * Half the time skip this step, which helps increase the * level of randomization. */ if (testRandBool()) { rv = snprintf(cmd, sizeof(cmd), "%s", CMD_STATUS); if (rv >= (signed) sizeof(cmd) - 1) { testPrintE("Command too long for: %s\n", CMD_STATUS); exit(22); } testExecCmd(cmd); } // Stop Supplicant randBind(&availCPU, &cpu); if ((rv = wifi_stop_supplicant(false)) != 0) { testPrintE("CPU: %i wifi_stop_supplicant() failed, rv: %i\n", cpu, rv); exit(23); } testPrintI("CPU: %i wifi_stop_supplicant succeeded", cpu); // Unload WiFi Module randBind(&availCPU, &cpu); if ((rv = wifi_unload_driver()) != 0) { testPrintE("CPU: %i wifi_unload_driver() failed, rv: %i\n", cpu, rv); exit(24); } testPrintI("CPU: %i wifi_unload_driver succeeded", cpu); testPrintI("==== Completed pass: %u", pass); } // If needed restore WiFi driver to state it was in at the // start of the test. It is assumed that it the driver // was loaded, then the wpa_supplicant was also running. if (driverLoadedAtStart) { // Load driver if ((rv = wifi_load_driver()) != 0) { testPrintE("main load driver failed, rv: %i", rv); exit(25); } // Start supplicant if ((rv = wifi_start_supplicant(false)) != 0) { testPrintE("main start supplicant failed, rv: %i", rv); exit(26); } // Obtain WiFi Status rv = snprintf(cmd, sizeof(cmd), "%s", CMD_STATUS); if (rv >= (signed) sizeof(cmd) - 1) { testPrintE("Command too long for: %s\n", CMD_STATUS); exit(22); } testExecCmd(cmd); } // Start framework rv = snprintf(cmd, sizeof(cmd), "%s", CMD_START_FRAMEWORK); if (rv >= (signed) sizeof(cmd) - 1) { testPrintE("Command too long for: %s\n", CMD_START_FRAMEWORK); exit(27); } testExecCmd(cmd); testPrintI("Successfully completed %u passes", pass - startPass); return 0; } /* * Initialize * * Perform testcase initialization, which includes: * * 1. Determine which CPUs are available for use * * 2. Determine total number of available CPUs * * 3. Stop framework * * 4. Determine whether WiFi driver is loaded and if so * stop wpa_supplicant and unload WiFi driver. */ void init(void) { int rv; unsigned int n1; char cmd[MAXCMD]; // Use whichever CPUs are available at start of test rv = sched_getaffinity(0, sizeof(availCPU), &availCPU); if (rv != 0) { testPrintE("init sched_getaffinity failed, rv: %i errno: %i", rv, errno); exit(40); } // How many CPUs are available numAvailCPU = 0; for (n1 = 0; n1 < CPU_SETSIZE; n1++) { if (CPU_ISSET(n1, &availCPU)) { numAvailCPU++; } } testPrintI("numAvailCPU: %u", numAvailCPU); // Stop framework rv = snprintf(cmd, sizeof(cmd), "%s", CMD_STOP_FRAMEWORK); if (rv >= (signed) sizeof(cmd) - 1) { testPrintE("Command too long for: %s\n", CMD_STOP_FRAMEWORK); exit(41); } testExecCmd(cmd); // Is WiFi driver loaded? // If so stop the wpa_supplicant and unload the driver. driverLoadedAtStart = is_wifi_driver_loaded(); testPrintI("driverLoadedAtStart: %u", driverLoadedAtStart); if (driverLoadedAtStart) { // Stop wpa_supplicant // Might already be stopped, in which case request should // return immediately with success. if ((rv = wifi_stop_supplicant(false)) != 0) { testPrintE("init stop supplicant failed, rv: %i", rv); exit(42); } testPrintI("Stopped wpa_supplicant"); if ((rv = wifi_unload_driver()) != 0) { testPrintE("init unload driver failed, rv: %i", rv); exit(43); } testPrintI("WiFi driver unloaded"); } } /* * Random Delay * * Delays for a random amount of time within the range given * by the file scope variables delayMin and delayMax. The * selected amount of delay can come from any part of the * range, with a bias towards values closer to delayMin. * The amount of bias is determined by the setting of DELAY_EXP. * The setting of DELAY_EXP should always be > 1.0, with higher * values causing a more significant bias toward the value * of delayMin. */ void randDelay(void) { const unsigned long nanosecspersec = 1000000000; float fract, biasedFract, amt; struct timeval startTime, endTime; // Obtain start time gettimeofday(&startTime, NULL); // Determine random amount to sleep. // Values closer to delayMin are prefered by an amount // determined by the value of DELAY_EXP. fract = testRandFract(); biasedFract = pow(DELAY_EXP, fract) / pow(DELAY_EXP, 1.0); amt = delayMin + ((delayMax - delayMin) * biasedFract); // Delay testDelay(amt); // Obtain end time and display delta gettimeofday(&endTime, NULL); testPrintI("delay: %.2f", (float) (tv2double(&endTime) - tv2double(&startTime))); } static void randBind(const cpu_set_t *availSet, int *chosenCPU) { int rv; cpu_set_t cpuset; int chosenAvail, avail, cpu, currentCPU; // Randomly bind to a CPU // Lower 16 bits from random number generator thrown away, // because the low-order bits tend to have the same sequence for // different seed values. chosenAvail = testRandMod(numAvailCPU); CPU_ZERO(&cpuset); avail = 0; for (cpu = 0; cpu < CPU_SETSIZE; cpu++) { if (CPU_ISSET(cpu, availSet)) { if (chosenAvail == avail) { CPU_SET(cpu, &cpuset); break; } avail++; } } assert(cpu < CPU_SETSIZE); sched_setaffinity(0, sizeof(cpuset), &cpuset); // Confirm executing on requested CPU if ((currentCPU = sched_getcpu()) < 0) { testPrintE("randBind sched_getcpu() failed, rv: %i errno: %i", currentCPU, errno); exit(80); } if (currentCPU != cpu) { testPrintE("randBind executing on unexpected CPU %i, expected %i", currentCPU, cpu); exit(81); } // Let the caller know which CPU was chosen *chosenCPU = cpu; }