nuttx-apps/testing/smart_test/smart_test.c

708 lines
19 KiB
C

/****************************************************************************
* apps/system/smart_test/smart_test.c
*
* Copyright (C) 2013, 2015 Ken Pettit. All rights reserved.
* Author: Ken Pettit <pettitkd@gmail.com>
*
* 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 NuttX 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Private data
****************************************************************************/
static int *g_linePos;
static int *g_lineLen;
static int g_seekCount = 0;
static int g_writeCount = 0;
static int g_circCount = 0;
static int g_lineCount = 2000;
static int g_recordLen = 64;
static int g_eraseCount = 32;
static int g_totalRecords = 40000;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: smart_create_test_file
*
* Description: Creates the test file with test data that we will use
* to conduct the test.
*
****************************************************************************/
static int smart_create_test_file(char *filename)
{
FILE *fd;
int x;
char string[80];
/* Try to open the file */
printf("Creating file %s for write mode\n", filename);
fd = fopen(filename, "w+");
if (fd == NULL)
{
printf("Unable to create file %s\n", filename);
return -ENOENT;
}
/* Write data to the file. The data consists of a bunch of
* lines, each with a line number and the offset within the
* file where that file starts.
*/
printf("Writing test data. %d lines to write\n", g_lineCount);
for (x = 0; x < g_lineCount; x++)
{
g_linePos[x] = ftell(fd);
sprintf(string, "This is line %d at offset %d\n", x, g_linePos[x]);
g_lineLen[x] = strlen(string);
fprintf(fd, "%s", string);
printf("\r%d", x);
fflush(stdout);
}
/* Close the file */
printf("\r\nDone.\n");
fclose(fd);
return OK;
}
/****************************************************************************
* Name: smart_seek_test
*
* Description: Conducts a seek test on the file.
*
****************************************************************************/
static int smart_seek_test(char *filename)
{
FILE *fd;
char readstring[80];
char cmpstring[80];
int index;
int x;
int ret = OK;
fd = fopen(filename, "r");
if (fd == NULL)
{
printf("Unable to open file %s\n", filename);
return -ENOENT;
}
printf("Performing %d random seek tests\n", g_seekCount);
srand(23);
for (x = 0; x < g_seekCount; x++)
{
/* Get random line to seek to */
index = rand();
while (index >= g_lineCount)
{
index -= g_lineCount;
}
fseek(fd, g_linePos[index], SEEK_SET);
fread(readstring, 1, g_lineLen[index], fd);
readstring[g_lineLen[index]] = '\0';
sprintf(cmpstring, "This is line %d at offset %d\n",
index, g_linePos[index]);
if (strcmp(readstring, cmpstring) != 0)
{
printf("\nSeek error on line %d\n", index);
printf ("\t Expected \"%s\"\n", cmpstring);
printf ("\t Received \"%s\"\n", readstring);
ret = -1;
}
printf("\r%d", x);
fflush(stdout);
}
printf("\r%d", x);
fflush(stdout);
fclose(fd);
return ret;
}
/****************************************************************************
* Name: smart_append_test
*
* Description: Conducts an append test on the file.
*
****************************************************************************/
static int smart_append_test(char *filename)
{
FILE *fd;
int pos;
char readstring[80];
fd = fopen(filename, "a+");
if (fd == NULL)
{
printf("Unable to open file %s\n", filename);
return -ENOENT;
}
/* Now write some data to the end of the file */
fprintf(fd, "This is a test of the append.\n");
pos = ftell(fd);
/* Now seek to the end of the file and ensure that is where
* pos is.
*/
fseek(fd, 0, SEEK_END);
if (ftell(fd) != pos)
{
printf("Error opening for append ... data not at EOF\n");
}
/* Now seek to that position and read the data back */
fseek(fd, 30, SEEK_END);
fread(readstring, 1, 30, fd);
readstring[30] = '\0';
if (strcmp(readstring, "This is a test of the append.\n") != 0)
{
printf("\nAppend test failed\n");
}
else
{
printf("\nAppend test passed\n");
}
fclose(fd);
return OK;
}
/****************************************************************************
* Name: smart_seek_with_write_test
*
* Description: Conducts an append test on the file.
*
****************************************************************************/
static int smart_seek_with_write_test(char *filename)
{
FILE *fd;
char temp;
char readstring[80];
char cmpstring[80];
int c;
int s1;
int s2;
int len;
int x;
int index;
int pass = TRUE;
fd = fopen(filename, "r+");
if (fd == NULL)
{
printf("Unable to open file %s\n", filename);
return -ENOENT;
}
printf("Performing %d random seek with write tests\n",
g_writeCount);
index = 0;
for (x = 0; x < g_writeCount; x++)
{
#if 0
/* Get a random value */
index = rand();
while (index >= g_lineCount)
{
index -= g_lineCount;
}
#endif
/* Read the data into the buffer */
fseek(fd, g_linePos[index], SEEK_SET);
fread(readstring, 1, g_lineLen[index], fd);
readstring[g_lineLen[index]] = '\0';
/* Scramble the data in the line */
len = strlen(readstring);
for (c = 0; c < 100; c++)
{
s1 = rand() % len;
s2 = rand() % len;
temp = readstring[s1];
readstring[s1] = readstring[s2];
readstring[s2] = temp;
}
/* Now write the data back to the file */
fseek(fd, g_linePos[index], SEEK_SET);
fwrite(readstring, 1, g_lineLen[index], fd);
fflush(fd);
/* Now read the data back and compare it */
fseek(fd, g_linePos[index], SEEK_SET);
fread(cmpstring, 1, g_lineLen[index], fd);
cmpstring[g_lineLen[index]] = '\0';
if (strcmp(readstring, cmpstring) != 0)
{
printf("\nCompare failure on line %d, offset %d\n",
index, g_linePos[index]);
printf("\tExpected \"%s\"", cmpstring);
printf("\rReceived \"%s\"", readstring);
fseek(fd, g_linePos[index], SEEK_SET);
fread(cmpstring, 1, g_lineLen[index], fd);
pass = FALSE;
break;
}
printf("\r%d", x);
fflush(stdout);
/* On to next line */
if (++index >= g_lineCount)
{
index = 0;
}
}
if (pass)
{
printf("\nPass\n");
}
else
{
printf("\nFail\n");
}
fclose(fd);
return OK;
}
/****************************************************************************
* Name: smart_circular_log_test
*
* Description: Conducts a record based circular log seek / update test
*
* NOTE!!!
* In the test below, we use open / write, not fopen / fwrite. If the
* fopen / fwrite routines were used instead, they would provide us with
* stream buffer management which we DO NOT WANT. This will throw off
* the SMARTFS write sizes which will not generate the optimized wear
* operations.
*
****************************************************************************/
static int smart_circular_log_test(char *filename)
{
int fd;
char *buffer;
char *cmpbuf;
int s1;
int x;
int recordNo;
int bufSize;
int pass = TRUE;
/* Calculate the size of our granular "erase record" writes */
bufSize = g_recordLen * g_eraseCount;
if (bufSize == 0)
{
printf("Invalid record parameters\n");
return -EINVAL;
}
/* Allocate memory for the record */
buffer = malloc(bufSize);
if (buffer == NULL)
{
printf("Unable to allocate memory for record storage\n");
return -ENOMEM;
}
/* Allocate memory for the compare buffer */
cmpbuf = malloc(g_recordLen);
if (cmpbuf == NULL)
{
printf("Unable to allocate memory for record storage\n");
free(buffer);
return -ENOMEM;
}
/* Open the circular log file */
fd = open(filename, O_RDWR | O_CREAT);
if (fd == -1)
{
printf("Unable to create file %s\n", filename);
free(buffer);
free(cmpbuf);
return -ENOENT;
}
/* Now fill the circular log with dummy 0xFF data */
printf("Creating circular log with %d records\n", g_totalRecords);
memset(buffer, 0xFF, g_recordLen);
for (x = 0; x < g_totalRecords; x++)
{
write(fd, buffer, g_recordLen);
}
close(fd);
/* Now reopen the file for read/write mode */
fd = open(filename, O_RDWR);
if (fd == -1)
{
printf("Unable to open file %s\n", filename);
free(buffer);
free(cmpbuf);
return -ENOENT;
}
printf("Performing %d circular log record update tests\n",
g_circCount);
/* Start at record number zero and start updating log entries */
recordNo = 0;
for (x = 0; x < g_circCount; x++)
{
/* Fill a new record with random data */
for (s1=0; s1 < g_recordLen; s1++)
{
buffer[s1] = rand() & 0xFF;
}
/* Set the first byte of the record (flag byte) to 0xFF */
buffer[0] = 0xFF;
/* Seek to the record location in the file */
lseek(fd, g_recordLen*recordNo, SEEK_SET);
/* Write the new record to the file */
if ((recordNo & (g_eraseCount-1)) == 0)
{
/* Every g_eraseCount records we will write a larger
* buffer with our record and padded with 0xFF to
* the end of our larger buffer.
*/
memset(&buffer[g_recordLen], 0xFF, bufSize-g_recordLen);
write(fd, buffer, bufSize);
}
else
{
/* Just write a single record */
write(fd, buffer, g_recordLen);
}
/* Now perform a couple of simulated flag updates */
lseek(fd, g_recordLen*recordNo, SEEK_SET);
buffer[0] = 0xFE;
write(fd, buffer, 1);
lseek(fd, g_recordLen*recordNo, SEEK_SET);
buffer[0] = 0xFC;
write(fd, buffer, 1);
/* Now read the data back and compare it */
lseek(fd, g_recordLen*recordNo, SEEK_SET);
read(fd, cmpbuf, g_recordLen);
for (s1 = 0; s1 < g_recordLen; s1++)
{
if (buffer[s1] != cmpbuf[s1])
{
printf("\nCompare failure in record %d, offset %d\n",
recordNo, recordNo*g_recordLen+s1);
printf("\tExpected \"%02x\"", cmpbuf[s1]);
printf("\rReceived \"%02x\"", buffer[s1]);
pass = FALSE;
break;
}
}
printf("\r%d", x);
fflush(stdout);
/* Increment to the next record */
if (++recordNo >= g_totalRecords)
{
recordNo = 0;
}
}
if (pass)
{
printf("\nPass\n");
}
else
{
printf("\nFail\n");
}
close(fd);
free(buffer);
free(cmpbuf);
return OK;
}
/****************************************************************************
* Name: smart_usage
*
* Description: Displays usage information for the command.
*
****************************************************************************/
static void smart_usage(void)
{
fprintf(stderr, "usage: smart_test [-c COUNT] [-s SEEKCOUNT] [-w WRITECOUNT] smart_mounted_filename\n\n");
fprintf(stderr, "DESCRIPTION\n");
fprintf(stderr, " Conducts various stress tests to validate SMARTFS operation.\n");
fprintf(stderr, " Please choose one or more of -c, -s, or -w to conduct tests.\n\n");
fprintf(stderr, "OPTIONS\n");
fprintf(stderr, " -c COUNT\n");
fprintf(stderr, " Performs a circular log style test where a fixed number of fixed\n");
fprintf(stderr, " length records are written and then overwritten with new data.\n");
fprintf(stderr, " Uses the -r, -e and -t options to specify the parameters of the \n");
fprintf(stderr, " record geometry and update operation. The COUNT parameter sets\n");
fprintf(stderr, " the number of record updates to perform.\n\n");
fprintf(stderr, " -s SEEKCOUNT\n");
fprintf(stderr, " Performs a simple seek test where to validate the SMARTFS seek\n");
fprintf(stderr, " operation. Uses the -l option to specify the number of test\n");
fprintf(stderr, " lines to write to the test file. The SEEKCOUNT parameter sets\n");
fprintf(stderr, " the number of seek/read operations to perform.\n\n");
fprintf(stderr, " -w WRITECOUNT\n");
fprintf(stderr, " Performs a seek/write/seek/read test where to validate the SMARTFS\n");
fprintf(stderr, " seek/write operation. Uses the -l option to specify the number of\n");
fprintf(stderr, " test lines to write to the test file. The WRITECOUNT parameter sets\n");
fprintf(stderr, " the number of seek/write operations to perform.\n\n");
fprintf(stderr, " -l LINECOUNT\n");
fprintf(stderr, " Sets the number of lines of test data to write to the test file\n");
fprintf(stderr, " during seek and seek/write tests.\n\n");
fprintf(stderr, " -r RECORDLEN\n");
fprintf(stderr, " Sets the length of each log record during circular log tests.\n\n");
fprintf(stderr, " -e ERASECOUNT\n");
fprintf(stderr, " Sets the erase granularity for overwriting old circular log entries.\n");
fprintf(stderr, " Setting this value to 16, for instance, would cause every 16th record\n");
fprintf(stderr, " update to write a single record followed by 15 records with all 0xFF\n");
fprintf(stderr, " content. This helps SMARTFS perform better wear leveling and reduces\n");
fprintf(stderr, " the number of FLASH block erases significantly.\n\n");
fprintf(stderr, " -t TOTALRECORDS\n");
fprintf(stderr, " Sets the total number of records in the circular log test file.\n\n");
}
/****************************************************************************
* Public Functions
****************************************************************************/
int main(int argc, FAR char *argv[])
{
int ret, opt;
/* Argument given? */
while ((opt = getopt(argc, argv, "c:e:l:r:s:t:w:")) != -1)
{
switch (opt)
{
case 'c':
g_circCount = atoi(optarg);
break;
case 'e':
g_eraseCount = atoi(optarg);
break;
case 'l':
g_lineCount = atoi(optarg);
break;
case 'r':
g_recordLen = atoi(optarg);
break;
case 's':
g_seekCount = atoi(optarg);
break;
case 't':
g_totalRecords = atoi(optarg);
break;
case 'w':
g_writeCount = atoi(optarg);
break;
default: /* '?' */
smart_usage();
exit(EXIT_FAILURE);
}
}
if (argc < 2 || (g_seekCount + g_writeCount + g_circCount == 0))
{
smart_usage();
return -1;
}
/* Allocate memory for the test */
g_linePos = malloc(g_lineCount * sizeof(int));
if (g_linePos == NULL)
{
return -1;
}
g_lineLen = malloc(g_lineCount * sizeof(int));
if (g_lineLen == NULL)
{
free(g_linePos);
return -1;
}
/* Test if performing seek test or write test */
if (g_seekCount > 0 || g_writeCount > 0)
{
/* Create a test file */
if ((ret = smart_create_test_file(argv[optind])) < 0)
{
goto err_out_with_mem;
}
/* Conduct a seek test? */
if (g_seekCount > 0 )
{
if ((ret = smart_seek_test(argv[optind])) < 0)
{
goto err_out_with_mem;
}
}
/* Conduct an append test */
if ((ret = smart_append_test(argv[optind])) < 0)
{
goto err_out_with_mem;
}
/* Conduct a seek with write test? */
if (g_writeCount > 0)
{
if ((ret = smart_seek_with_write_test(argv[optind])) < 0)
{
goto err_out_with_mem;
}
}
}
/* Perform a "circular log" test */
if ((ret = smart_circular_log_test(argv[optind])) < 0)
{
goto err_out_with_mem;
}
err_out_with_mem:
/* Free the memory */
free(g_linePos);
free(g_lineLen);
return ret;
}