michael@0: /******************************************************************** michael@0: * COPYRIGHT: michael@0: * Copyright (c) 2002-2012, International Business Machines Corporation and michael@0: * others. All Rights Reserved. michael@0: ********************************************************************/ michael@0: michael@0: // Defines _XOPEN_SOURCE for access to POSIX functions. michael@0: // Must be before any other #includes. michael@0: #include "uposixdefs.h" michael@0: michael@0: #include "unicode/uperf.h" michael@0: #include "uoptions.h" michael@0: #include "cmemory.h" michael@0: #include michael@0: #include michael@0: michael@0: #if !UCONFIG_NO_CONVERSION michael@0: michael@0: UPerfFunction::~UPerfFunction() {} michael@0: michael@0: static const char delim = '/'; michael@0: static int32_t execCount = 0; michael@0: UPerfTest* UPerfTest::gTest = NULL; michael@0: static const int MAXLINES = 40000; michael@0: const char UPerfTest::gUsageString[] = michael@0: "Usage: %s [OPTIONS] [FILES]\n" michael@0: "\tReads the input file and prints out time taken in seconds\n" michael@0: "Options:\n" michael@0: "\t-h or -? or --help this usage text\n" michael@0: "\t-v or --verbose print extra information when processing files\n" michael@0: "\t-s or --sourcedir source directory for files followed by path\n" michael@0: "\t followed by path\n" michael@0: "\t-e or --encoding encoding of source files\n" michael@0: "\t-u or --uselen perform timing analysis on non-null terminated buffer using length\n" michael@0: "\t-f or --file-name file to be used as input data\n" michael@0: "\t-p or --passes Number of passes to be performed. Requires Numeric argument.\n" michael@0: "\t Cannot be used with --time\n" michael@0: "\t-i or --iterations Number of iterations to be performed. Requires Numeric argument\n" michael@0: "\t-t or --time Threshold time for looping until in seconds. Requires Numeric argument.\n" michael@0: "\t Cannot be used with --iterations\n" michael@0: "\t-l or --line-mode The data file should be processed in line mode\n" michael@0: "\t-b or --bulk-mode The data file should be processed in file based.\n" michael@0: "\t Cannot be used with --line-mode\n" michael@0: "\t-L or --locale Locale for the test\n"; michael@0: michael@0: enum michael@0: { michael@0: HELP1, michael@0: HELP2, michael@0: VERBOSE, michael@0: SOURCEDIR, michael@0: ENCODING, michael@0: USELEN, michael@0: FILE_NAME, michael@0: PASSES, michael@0: ITERATIONS, michael@0: TIME, michael@0: LINE_MODE, michael@0: BULK_MODE, michael@0: LOCALE, michael@0: OPTIONS_COUNT michael@0: }; michael@0: michael@0: michael@0: static UOption options[OPTIONS_COUNT+20]={ michael@0: UOPTION_HELP_H, michael@0: UOPTION_HELP_QUESTION_MARK, michael@0: UOPTION_VERBOSE, michael@0: UOPTION_SOURCEDIR, michael@0: UOPTION_ENCODING, michael@0: UOPTION_DEF( "uselen", 'u', UOPT_NO_ARG), michael@0: UOPTION_DEF( "file-name", 'f', UOPT_REQUIRES_ARG), michael@0: UOPTION_DEF( "passes", 'p', UOPT_REQUIRES_ARG), michael@0: UOPTION_DEF( "iterations", 'i', UOPT_REQUIRES_ARG), michael@0: UOPTION_DEF( "time", 't', UOPT_REQUIRES_ARG), michael@0: UOPTION_DEF( "line-mode", 'l', UOPT_NO_ARG), michael@0: UOPTION_DEF( "bulk-mode", 'b', UOPT_NO_ARG), michael@0: UOPTION_DEF( "locale", 'L', UOPT_REQUIRES_ARG) michael@0: }; michael@0: michael@0: UPerfTest::UPerfTest(int32_t argc, const char* argv[], UErrorCode& status) michael@0: : _argc(argc), _argv(argv), _addUsage(NULL), michael@0: ucharBuf(NULL), encoding(""), michael@0: uselen(FALSE), michael@0: fileName(NULL), sourceDir("."), michael@0: lines(NULL), numLines(0), line_mode(TRUE), michael@0: buffer(NULL), bufferLen(0), michael@0: verbose(FALSE), bulk_mode(FALSE), michael@0: passes(1), iterations(0), time(0), michael@0: locale(NULL) { michael@0: init(NULL, 0, status); michael@0: } michael@0: michael@0: UPerfTest::UPerfTest(int32_t argc, const char* argv[], michael@0: UOption addOptions[], int32_t addOptionsCount, michael@0: const char *addUsage, michael@0: UErrorCode& status) michael@0: : _argc(argc), _argv(argv), _addUsage(addUsage), michael@0: ucharBuf(NULL), encoding(""), michael@0: uselen(FALSE), michael@0: fileName(NULL), sourceDir("."), michael@0: lines(NULL), numLines(0), line_mode(TRUE), michael@0: buffer(NULL), bufferLen(0), michael@0: verbose(FALSE), bulk_mode(FALSE), michael@0: passes(1), iterations(0), time(0), michael@0: locale(NULL) { michael@0: init(addOptions, addOptionsCount, status); michael@0: } michael@0: michael@0: void UPerfTest::init(UOption addOptions[], int32_t addOptionsCount, michael@0: UErrorCode& status) { michael@0: //initialize the argument list michael@0: U_MAIN_INIT_ARGS(_argc, _argv); michael@0: michael@0: resolvedFileName = NULL; michael@0: michael@0: // add specific options michael@0: int32_t optionsCount = OPTIONS_COUNT; michael@0: if (addOptionsCount > 0) { michael@0: memcpy(options+optionsCount, addOptions, addOptionsCount*sizeof(UOption)); michael@0: optionsCount += addOptionsCount; michael@0: } michael@0: michael@0: //parse the arguments michael@0: _remainingArgc = u_parseArgs(_argc, (char**)_argv, optionsCount, options); michael@0: michael@0: // copy back values for additional options michael@0: if (addOptionsCount > 0) { michael@0: memcpy(addOptions, options+OPTIONS_COUNT, addOptionsCount*sizeof(UOption)); michael@0: } michael@0: michael@0: // Now setup the arguments michael@0: if(_argc==1 || options[HELP1].doesOccur || options[HELP2].doesOccur) { michael@0: status = U_ILLEGAL_ARGUMENT_ERROR; michael@0: return; michael@0: } michael@0: michael@0: if(options[VERBOSE].doesOccur) { michael@0: verbose = TRUE; michael@0: } michael@0: michael@0: if(options[SOURCEDIR].doesOccur) { michael@0: sourceDir = options[SOURCEDIR].value; michael@0: } michael@0: michael@0: if(options[ENCODING].doesOccur) { michael@0: encoding = options[ENCODING].value; michael@0: } michael@0: michael@0: if(options[USELEN].doesOccur) { michael@0: uselen = TRUE; michael@0: } michael@0: michael@0: if(options[FILE_NAME].doesOccur){ michael@0: fileName = options[FILE_NAME].value; michael@0: } michael@0: michael@0: if(options[PASSES].doesOccur) { michael@0: passes = atoi(options[PASSES].value); michael@0: } michael@0: if(options[ITERATIONS].doesOccur) { michael@0: iterations = atoi(options[ITERATIONS].value); michael@0: if(options[TIME].doesOccur) { michael@0: status = U_ILLEGAL_ARGUMENT_ERROR; michael@0: return; michael@0: } michael@0: } else if(options[TIME].doesOccur) { michael@0: time = atoi(options[TIME].value); michael@0: } else { michael@0: iterations = 1000; // some default michael@0: } michael@0: michael@0: if(options[LINE_MODE].doesOccur) { michael@0: line_mode = TRUE; michael@0: bulk_mode = FALSE; michael@0: } michael@0: michael@0: if(options[BULK_MODE].doesOccur) { michael@0: bulk_mode = TRUE; michael@0: line_mode = FALSE; michael@0: } michael@0: michael@0: if(options[LOCALE].doesOccur) { michael@0: locale = options[LOCALE].value; michael@0: } michael@0: michael@0: int32_t len = 0; michael@0: if(fileName!=NULL){ michael@0: //pre-flight michael@0: ucbuf_resolveFileName(sourceDir, fileName, NULL, &len, &status); michael@0: resolvedFileName = (char*) uprv_malloc(len); michael@0: if(resolvedFileName==NULL){ michael@0: status= U_MEMORY_ALLOCATION_ERROR; michael@0: return; michael@0: } michael@0: if(status == U_BUFFER_OVERFLOW_ERROR){ michael@0: status = U_ZERO_ERROR; michael@0: } michael@0: ucbuf_resolveFileName(sourceDir, fileName, resolvedFileName, &len, &status); michael@0: ucharBuf = ucbuf_open(resolvedFileName,&encoding,TRUE,FALSE,&status); michael@0: michael@0: if(U_FAILURE(status)){ michael@0: printf("Could not open the input file %s. Error: %s\n", fileName, u_errorName(status)); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: ULine* UPerfTest::getLines(UErrorCode& status){ michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: if (lines != NULL) { michael@0: return lines; // don't do it again michael@0: } michael@0: lines = new ULine[MAXLINES]; michael@0: int maxLines = MAXLINES; michael@0: numLines=0; michael@0: const UChar* line=NULL; michael@0: int32_t len =0; michael@0: for (;;) { michael@0: line = ucbuf_readline(ucharBuf,&len,&status); michael@0: if(line == NULL || U_FAILURE(status)){ michael@0: break; michael@0: } michael@0: lines[numLines].name = new UChar[len]; michael@0: lines[numLines].len = len; michael@0: memcpy(lines[numLines].name, line, len * U_SIZEOF_UCHAR); michael@0: michael@0: numLines++; michael@0: len = 0; michael@0: if (numLines >= maxLines) { michael@0: maxLines += MAXLINES; michael@0: ULine *newLines = new ULine[maxLines]; michael@0: if(newLines == NULL) { michael@0: fprintf(stderr, "Out of memory reading line %d.\n", (int)numLines); michael@0: status= U_MEMORY_ALLOCATION_ERROR; michael@0: delete []lines; michael@0: return NULL; michael@0: } michael@0: michael@0: memcpy(newLines, lines, numLines*sizeof(ULine)); michael@0: delete []lines; michael@0: lines = newLines; michael@0: } michael@0: } michael@0: return lines; michael@0: } michael@0: const UChar* UPerfTest::getBuffer(int32_t& len, UErrorCode& status){ michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: len = ucbuf_size(ucharBuf); michael@0: buffer = (UChar*) uprv_malloc(U_SIZEOF_UCHAR * (len+1)); michael@0: u_strncpy(buffer,ucbuf_getBuffer(ucharBuf,&bufferLen,&status),len); michael@0: buffer[len]=0; michael@0: len = bufferLen; michael@0: return buffer; michael@0: } michael@0: UBool UPerfTest::run(){ michael@0: if(_remainingArgc==1){ michael@0: // Testing all methods michael@0: return runTest(); michael@0: } michael@0: UBool res=FALSE; michael@0: // Test only the specified fucntion michael@0: for (int i = 1; i < _remainingArgc; ++i) { michael@0: if (_argv[i][0] != '-') { michael@0: char* name = (char*) _argv[i]; michael@0: if(verbose==TRUE){ michael@0: //fprintf(stdout, "\n=== Handling test: %s: ===\n", name); michael@0: //fprintf(stdout, "\n%s:\n", name); michael@0: } michael@0: char* parameter = strchr( name, '@' ); michael@0: if (parameter) { michael@0: *parameter = 0; michael@0: parameter += 1; michael@0: } michael@0: execCount = 0; michael@0: res = runTest( name, parameter ); michael@0: if (!res || (execCount <= 0)) { michael@0: fprintf(stdout, "\n---ERROR: Test doesn't exist: %s!\n", name); michael@0: return FALSE; michael@0: } michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: UBool UPerfTest::runTest(char* name, char* par ){ michael@0: UBool rval; michael@0: char* pos = NULL; michael@0: michael@0: if (name) michael@0: pos = strchr( name, delim ); // check if name contains path (by looking for '/') michael@0: if (pos) { michael@0: path = pos+1; // store subpath for calling subtest michael@0: *pos = 0; // split into two strings michael@0: }else{ michael@0: path = NULL; michael@0: } michael@0: michael@0: if (!name || (name[0] == 0) || (strcmp(name, "*") == 0)) { michael@0: rval = runTestLoop( NULL, NULL ); michael@0: michael@0: }else if (strcmp( name, "LIST" ) == 0) { michael@0: this->usage(); michael@0: rval = TRUE; michael@0: michael@0: }else{ michael@0: rval = runTestLoop( name, par ); michael@0: } michael@0: michael@0: if (pos) michael@0: *pos = delim; // restore original value at pos michael@0: return rval; michael@0: } michael@0: michael@0: michael@0: void UPerfTest::setPath( char* pathVal ) michael@0: { michael@0: this->path = pathVal; michael@0: } michael@0: michael@0: // call individual tests, to be overriden to call implementations michael@0: UPerfFunction* UPerfTest::runIndexedTest( int32_t /*index*/, UBool /*exec*/, const char* & /*name*/, char* /*par*/ ) michael@0: { michael@0: // to be overriden by a method like: michael@0: /* michael@0: switch (index) { michael@0: case 0: name = "First Test"; if (exec) FirstTest( par ); break; michael@0: case 1: name = "Second Test"; if (exec) SecondTest( par ); break; michael@0: default: name = ""; break; michael@0: } michael@0: */ michael@0: fprintf(stderr,"*** runIndexedTest needs to be overriden! ***"); michael@0: return NULL; michael@0: } michael@0: michael@0: michael@0: UBool UPerfTest::runTestLoop( char* testname, char* par ) michael@0: { michael@0: int32_t index = 0; michael@0: const char* name; michael@0: UBool run_this_test; michael@0: UBool rval = FALSE; michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: UPerfTest* saveTest = gTest; michael@0: gTest = this; michael@0: int32_t loops = 0; michael@0: double t=0; michael@0: int32_t n = 1; michael@0: long ops; michael@0: do { michael@0: this->runIndexedTest( index, FALSE, name ); michael@0: if (!name || (name[0] == 0)) michael@0: break; michael@0: if (!testname) { michael@0: run_this_test = TRUE; michael@0: }else{ michael@0: run_this_test = (UBool) (strcmp( name, testname ) == 0); michael@0: } michael@0: if (run_this_test) { michael@0: UPerfFunction* testFunction = this->runIndexedTest( index, TRUE, name, par ); michael@0: execCount++; michael@0: rval=TRUE; michael@0: if(testFunction==NULL){ michael@0: fprintf(stderr,"%s function returned NULL", name); michael@0: return FALSE; michael@0: } michael@0: ops = testFunction->getOperationsPerIteration(); michael@0: if (ops < 1) { michael@0: fprintf(stderr, "%s returned an illegal operations/iteration()\n", name); michael@0: return FALSE; michael@0: } michael@0: if(iterations == 0) { michael@0: n = time; michael@0: // Run for specified duration in seconds michael@0: if(verbose==TRUE){ michael@0: fprintf(stdout,"= %s calibrating %i seconds \n", name, (int)n); michael@0: } michael@0: michael@0: //n *= 1000; // s => ms michael@0: //System.out.println("# " + meth.getName() + " " + n + " sec"); michael@0: int32_t failsafe = 1; // last resort for very fast methods michael@0: t = 0; michael@0: while (t < (int)(n * 0.9)) { // 90% is close enough michael@0: if (loops == 0 || t == 0) { michael@0: loops = failsafe; michael@0: failsafe *= 10; michael@0: } else { michael@0: //System.out.println("# " + meth.getName() + " x " + loops + " = " + t); michael@0: loops = (int)((double)n / t * loops + 0.5); michael@0: if (loops == 0) { michael@0: fprintf(stderr,"Unable to converge on desired duration"); michael@0: return FALSE; michael@0: } michael@0: } michael@0: //System.out.println("# " + meth.getName() + " x " + loops); michael@0: t = testFunction->time(loops,&status); michael@0: if(U_FAILURE(status)){ michael@0: printf("Performance test failed with error: %s \n", u_errorName(status)); michael@0: break; michael@0: } michael@0: } michael@0: } else { michael@0: loops = iterations; michael@0: } michael@0: michael@0: double min_t=1000000.0, sum_t=0.0; michael@0: long events = -1; michael@0: michael@0: for(int32_t ps =0; ps < passes; ps++){ michael@0: fprintf(stdout,"= %s begin " ,name); michael@0: if(verbose==TRUE){ michael@0: if(iterations > 0) { michael@0: fprintf(stdout, "%i\n", (int)loops); michael@0: } else { michael@0: fprintf(stdout, "%i\n", (int)n); michael@0: } michael@0: } else { michael@0: fprintf(stdout, "\n"); michael@0: } michael@0: t = testFunction->time(loops, &status); michael@0: if(U_FAILURE(status)){ michael@0: printf("Performance test failed with error: %s \n", u_errorName(status)); michael@0: break; michael@0: } michael@0: sum_t+=t; michael@0: if(tgetEventsPerIteration(); michael@0: //print info only in verbose mode michael@0: if(verbose==TRUE){ michael@0: if(events == -1){ michael@0: fprintf(stdout, "= %s end: %f loops: %i operations: %li \n", name, t, (int)loops, ops); michael@0: }else{ michael@0: fprintf(stdout, "= %s end: %f loops: %i operations: %li events: %li\n", name, t, (int)loops, ops, events); michael@0: } michael@0: }else{ michael@0: if(events == -1){ michael@0: fprintf(stdout,"= %s end %f %i %li\n", name, t, (int)loops, ops); michael@0: }else{ michael@0: fprintf(stdout,"= %s end %f %i %li %li\n", name, t, (int)loops, ops, events); michael@0: } michael@0: } michael@0: } michael@0: if(verbose && U_SUCCESS(status)) { michael@0: double avg_t = sum_t/passes; michael@0: if (loops == 0 || ops == 0) { michael@0: fprintf(stderr, "%s did not run\n", name); michael@0: } michael@0: else if(events == -1) { michael@0: fprintf(stdout, "%%= %s avg: %.4g loops: %i avg/op: %.4g ns\n", michael@0: name, avg_t, (int)loops, (avg_t*1E9)/(loops*ops)); michael@0: fprintf(stdout, "_= %s min: %.4g loops: %i min/op: %.4g ns\n", michael@0: name, min_t, (int)loops, (min_t*1E9)/(loops*ops)); michael@0: } michael@0: else { michael@0: fprintf(stdout, "%%= %s avg: %.4g loops: %i avg/op: %.4g ns avg/event: %.4g ns\n", michael@0: name, avg_t, (int)loops, (avg_t*1E9)/(loops*ops), (avg_t*1E9)/(loops*events)); michael@0: fprintf(stdout, "_= %s min: %.4g loops: %i min/op: %.4g ns min/event: %.4g ns\n", michael@0: name, min_t, (int)loops, (min_t*1E9)/(loops*ops), (min_t*1E9)/(loops*events)); michael@0: } michael@0: } michael@0: delete testFunction; michael@0: } michael@0: index++; michael@0: }while(name); michael@0: michael@0: gTest = saveTest; michael@0: return rval; michael@0: } michael@0: michael@0: /** michael@0: * Print a usage message for this test class. michael@0: */ michael@0: void UPerfTest::usage( void ) michael@0: { michael@0: puts(gUsageString); michael@0: if (_addUsage != NULL) { michael@0: puts(_addUsage); michael@0: } michael@0: michael@0: UBool save_verbose = verbose; michael@0: verbose = TRUE; michael@0: fprintf(stdout,"Test names:\n"); michael@0: fprintf(stdout,"-----------\n"); michael@0: michael@0: int32_t index = 0; michael@0: const char* name = NULL; michael@0: do{ michael@0: this->runIndexedTest( index, FALSE, name ); michael@0: if (!name) michael@0: break; michael@0: fprintf(stdout, "%s\n", name); michael@0: index++; michael@0: }while (name && (name[0] != 0)); michael@0: verbose = save_verbose; michael@0: } michael@0: michael@0: michael@0: michael@0: michael@0: void UPerfTest::setCaller( UPerfTest* callingTest ) michael@0: { michael@0: caller = callingTest; michael@0: if (caller) { michael@0: verbose = caller->verbose; michael@0: } michael@0: } michael@0: michael@0: UBool UPerfTest::callTest( UPerfTest& testToBeCalled, char* par ) michael@0: { michael@0: execCount--; // correct a previously assumed test-exec, as this only calls a subtest michael@0: testToBeCalled.setCaller( this ); michael@0: return testToBeCalled.runTest( path, par ); michael@0: } michael@0: michael@0: UPerfTest::~UPerfTest(){ michael@0: if(lines!=NULL){ michael@0: delete[] lines; michael@0: } michael@0: if(buffer!=NULL){ michael@0: uprv_free(buffer); michael@0: } michael@0: if(resolvedFileName!=NULL){ michael@0: uprv_free(resolvedFileName); michael@0: } michael@0: ucbuf_close(ucharBuf); michael@0: } michael@0: michael@0: #endif