nuttx-apps/testing/sd_stress/sd_stress_main.c
Stuart Ianna 4576b3923c testing/sd_stress: Port sdstress testing utility from PX4-Autopilot.
Add port of the sdstress utility from PX4-Autopilot:  https://github.com/PX4/PX4-Autopilot

This tool is useful for verifying correct operation of SD, EMMC, or other mountpoints.
2023-10-25 15:41:27 +08:00

405 lines
10 KiB
C

/****************************************************************************
* apps/testing/sd_stress/sd_stress_main.c
*
* Original Licence:
*
* Copyright (c) 2016-2021 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/* Originally ported from PX4 https://github.com/PX4/PX4-Autopilot,
* with the following additions:
*
* - The number of files can be specified from the command line.
* - Bytes are written and read back from created files to verify integrity.
* - The bytes written are obtained from a static set,
* rather than a constant 0xAA
* - The results are reported as a floating point, millisecond value.
*/
/****************************************************************************
* Included Files
****************************************************************************/
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <nuttx/clock.h>
static const size_t MAX_PATH_LEN = 52;
static const char *TEMPDIR = CONFIG_TESTING_SD_STRESS_DEVICE"/stress";
static const char *TEMPDIR2 = CONFIG_TESTING_SD_STRESS_DEVICE"/moved";
static const char *TEMPFILE = "tmp";
const size_t max_runs = 10000;
const size_t min_runs = 1;
const size_t default_runs = 32;
const size_t max_bytes = 10000;
const size_t min_bytes = 1;
const size_t default_bytes = 4096;
const size_t max_files = 999;
const size_t min_files = 1;
const size_t default_files = 64;
/****************************************************************************
* Private Functions
****************************************************************************/
static void usage(void)
{
printf("Stress test on a mount point\n");
printf(CONFIG_TESTING_SD_STRESS_PROGNAME ": [-r] [-b] [-f]\n");
printf(" -r Number of runs (%u-%u), default %u\n",
min_runs, max_runs, default_runs);
printf(" -b Number of bytes (%u-%u), default %u\n",
min_bytes, max_bytes, default_bytes);
printf(" -f Number of files (%u-%u), default %u\n",
min_files, max_files, default_files);
}
static bool create_dir(const char *path)
{
int ret = mkdir(TEMPDIR, S_IRWXU | S_IRWXG | S_IRWXO);
if (ret < 0)
{
printf("mkdir %s failed, ret: %d, errno: %d -> %s\n",
path, ret, errno, strerror(errno));
return false;
}
return true;
}
static bool remove_dir(const char *path)
{
int ret = rmdir(TEMPDIR2);
if (ret < 0)
{
printf("rmdir %s failed, ret: %d, errno: %d -> %s\n",
path, ret, errno, strerror(errno));
return false;
}
return true;
}
static bool create_files(const char *dir, const char *name,
size_t num_files, char *bytes, size_t num_bytes)
{
bool passed = true;
if (num_files > 999)
{
printf("too many files\n");
return false;
}
char *read_bytes = (char *)malloc(num_bytes);
if (!read_bytes)
{
printf("malloc failed for read bytes bufffer\n");
return false;
}
size_t path_len = strlen(dir) + strlen(name);
if (path_len + 5 >= MAX_PATH_LEN)
{
printf("path name too long\n");
return false;
}
for (size_t i = 0; i < num_files; ++i)
{
char path[MAX_PATH_LEN];
snprintf(path, MAX_PATH_LEN, "%s/%s%03u", dir, name, i);
memset(read_bytes, 0x0, num_bytes);
/* Fill the file with a set of incrementing bytes */
for (size_t j = 0; j < num_bytes; j++)
{
bytes[j] = (bytes[j] + i) & 0xff;
}
int fd = open(path, O_CREAT | O_RDWR);
if (fd < 0)
{
printf("open %s failed, errno: %d -> %s\n",
path, errno, strerror(errno));
passed = false;
break;
}
int ret = write(fd, bytes, num_bytes);
if (ret != (int)num_bytes)
{
printf("write %s failed, ret: %d, errno %d -> %s\n",
path, ret, errno, strerror(errno));
passed = false;
break;
}
ret = lseek(fd, 0, SEEK_SET);
if (ret < 0)
{
printf("lseek %s failed, ret: %d, errno %d -> %s\n",
path, ret, errno, strerror(errno));
passed = false;
break;
}
ret = read(fd, read_bytes, num_bytes);
if (ret != (int)num_bytes)
{
printf("read %s failed, ret: %d, errno %d -> %s\n",
path, ret, errno, strerror(errno));
passed = false;
break;
}
if (memcmp(read_bytes, bytes, num_bytes) != 0)
{
printf("read and write buffers are not the same\n");
passed = false;
break;
}
ret = close(fd);
if (ret < 0)
{
printf("close %s failed, ret: %d, errno %d -> %s\n",
path, ret, errno, strerror(errno));
passed = false;
break;
}
}
free(read_bytes);
return passed;
}
static bool remove_files(const char *dir, const char *name,
size_t num_files)
{
if (num_files > 999)
{
printf("too many files\n");
return false;
}
size_t path_len = strlen(dir) + strlen(name);
if (path_len + 5 >= MAX_PATH_LEN)
{
printf("path name too long\n");
return false;
}
for (size_t i = 0; i < num_files; ++i)
{
char path[MAX_PATH_LEN];
snprintf(path, MAX_PATH_LEN, "%s/%s%03u", dir, name, i);
int ret = unlink(path);
if (ret < 0)
{
printf("unlink %s failed, ret: %d, errno %d -> %s\n",
path, ret, errno, strerror(errno));
return false;
}
}
return true;
}
static bool rename_dir(const char *old_dir, const char *new_dir)
{
int ret = rename(old_dir, new_dir);
if (ret < 0)
{
printf("rename %s to %s failed, ret: %d, errno %d -> %s\n",
old_dir, new_dir, ret, errno, strerror(errno));
return false;
}
return true;
}
struct timespec get_abs_time(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts;
}
uint64_t get_time_delta(const struct timespec *start,
const struct timespec *end)
{
uint64_t elapsed;
elapsed = (((uint64_t)end->tv_sec * NSEC_PER_SEC) + end->tv_nsec);
elapsed -= (((uint64_t)start->tv_sec * NSEC_PER_SEC) + start->tv_nsec);
return elapsed / 1000;
}
float get_elapsed_time_ms(const struct timespec *start)
{
struct timespec now = get_abs_time();
return get_time_delta(start, &now) / (float)USEC_PER_MSEC;
}
/****************************************************************************
* Public Functions
****************************************************************************/
int main(int argc, char *argv[])
{
int ch;
int ret;
size_t num_runs = default_runs;
size_t num_bytes = default_bytes;
size_t num_files = default_files;
float total_time;
float elapsed_time;
struct timespec start;
char * bytes;
while ((ch = getopt(argc, argv, "r:b:f:")) != EOF)
{
switch (ch)
{
case 'r':
num_runs = strtol(optarg, NULL, 0);
break;
case 'b':
num_bytes = strtol(optarg, NULL, 0);
break;
case 'f':
num_files = strtol(optarg, NULL, 0);
break;
default:
usage();
return -1;
break;
}
}
if (num_bytes > max_bytes || num_bytes < min_bytes)
{
printf("Bytes outside allowable range\n");
usage();
exit(EXIT_FAILURE);
}
if (num_runs > max_runs || num_runs < min_runs)
{
printf("Runs outside allowable range\n");
usage();
exit(EXIT_FAILURE);
}
if (num_files > max_files || num_files < min_files)
{
printf("File count outside allowable range\n");
usage();
exit(EXIT_FAILURE);
}
printf("Start stress test with %u files, %u bytes and %u iterations.\n",
num_files, num_bytes, num_runs);
bytes = (char *)malloc(num_bytes);
ret = 0;
if (!bytes)
{
printf("Failed to allocate byte buffer.\n");
exit(EXIT_FAILURE);
}
for (size_t i = 0; i < num_bytes; i++)
{
bytes[i] = i & 0xff;
}
total_time = 0;
for (size_t i = 0; i < num_runs; ++i)
{
start = get_abs_time();
const bool result =
create_dir(TEMPDIR)
&& create_files(TEMPDIR, TEMPFILE, num_files, bytes, num_bytes)
&& rename_dir(TEMPDIR, TEMPDIR2)
&& remove_files(TEMPDIR2, TEMPFILE, num_files)
&& remove_dir(TEMPDIR2);
elapsed_time = get_elapsed_time_ms(&start);
total_time += elapsed_time;
printf("iteration %u took %.3f ms: %s\n", i,
elapsed_time, result ? "OK" : "FAIL");
if (!result)
{
ret = -1;
break;
}
}
printf("Test %s: Average time: %.3f ms\n",
ret ? "FAIL" : "OK", total_time / num_runs);
free(bytes);
return ret;
}