| /**************************************************************************** |
| * 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 <nuttx/progmem.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 specifiy 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 |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_BUILD_KERNEL |
| int main(int argc, FAR char *argv[]) |
| #else |
| int smart_test_main(int argc, char *argv[]) |
| #endif |
| { |
| 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; |
| } |