1/********************************************************************
2 * COPYRIGHT:
3 * Copyright (c) 2002-2012, International Business Machines Corporation and
4 * others. All Rights Reserved.
5 ********************************************************************/
6
7// Defines _XOPEN_SOURCE for access to POSIX functions.
8// Must be before any other #includes.
9#include "uposixdefs.h"
10
11#include "unicode/uperf.h"
12#include "uoptions.h"
13#include "cmemory.h"
14#include <stdio.h>
15#include <stdlib.h>
16
17#if !UCONFIG_NO_CONVERSION
18
19UPerfFunction::~UPerfFunction() {}
20
21static const char delim = '/';
22static int32_t execCount = 0;
23UPerfTest* UPerfTest::gTest = NULL;
24static const int MAXLINES = 40000;
25const char UPerfTest::gUsageString[] =
26    "Usage: %s [OPTIONS] [FILES]\n"
27    "\tReads the input file and prints out time taken in seconds\n"
28    "Options:\n"
29    "\t-h or -? or --help   this usage text\n"
30    "\t-v or --verbose      print extra information when processing files\n"
31    "\t-s or --sourcedir    source directory for files followed by path\n"
32    "\t                     followed by path\n"
33    "\t-e or --encoding     encoding of source files\n"
34    "\t-u or --uselen       perform timing analysis on non-null terminated buffer using length\n"
35    "\t-f or --file-name    file to be used as input data\n"
36    "\t-p or --passes       Number of passes to be performed. Requires Numeric argument.\n"
37    "\t                     Cannot be used with --time\n"
38    "\t-i or --iterations   Number of iterations to be performed. Requires Numeric argument\n"
39    "\t-t or --time         Threshold time for looping until in seconds. Requires Numeric argument.\n"
40    "\t                     Cannot be used with --iterations\n"
41    "\t-l or --line-mode    The data file should be processed in line mode\n"
42    "\t-b or --bulk-mode    The data file should be processed in file based.\n"
43    "\t                     Cannot be used with --line-mode\n"
44    "\t-L or --locale       Locale for the test\n";
45
46enum
47{
48    HELP1,
49    HELP2,
50    VERBOSE,
51    SOURCEDIR,
52    ENCODING,
53    USELEN,
54    FILE_NAME,
55    PASSES,
56    ITERATIONS,
57    TIME,
58    LINE_MODE,
59    BULK_MODE,
60    LOCALE,
61    OPTIONS_COUNT
62};
63
64
65static UOption options[OPTIONS_COUNT+20]={
66    UOPTION_HELP_H,
67    UOPTION_HELP_QUESTION_MARK,
68    UOPTION_VERBOSE,
69    UOPTION_SOURCEDIR,
70    UOPTION_ENCODING,
71    UOPTION_DEF( "uselen",        'u', UOPT_NO_ARG),
72    UOPTION_DEF( "file-name",     'f', UOPT_REQUIRES_ARG),
73    UOPTION_DEF( "passes",        'p', UOPT_REQUIRES_ARG),
74    UOPTION_DEF( "iterations",    'i', UOPT_REQUIRES_ARG),
75    UOPTION_DEF( "time",          't', UOPT_REQUIRES_ARG),
76    UOPTION_DEF( "line-mode",     'l', UOPT_NO_ARG),
77    UOPTION_DEF( "bulk-mode",     'b', UOPT_NO_ARG),
78    UOPTION_DEF( "locale",        'L', UOPT_REQUIRES_ARG)
79};
80
81UPerfTest::UPerfTest(int32_t argc, const char* argv[], UErrorCode& status)
82        : _argc(argc), _argv(argv), _addUsage(NULL),
83          ucharBuf(NULL), encoding(""),
84          uselen(FALSE),
85          fileName(NULL), sourceDir("."),
86          lines(NULL), numLines(0), line_mode(TRUE),
87          buffer(NULL), bufferLen(0),
88          verbose(FALSE), bulk_mode(FALSE),
89          passes(1), iterations(0), time(0),
90          locale(NULL) {
91    init(NULL, 0, status);
92}
93
94UPerfTest::UPerfTest(int32_t argc, const char* argv[],
95                     UOption addOptions[], int32_t addOptionsCount,
96                     const char *addUsage,
97                     UErrorCode& status)
98        : _argc(argc), _argv(argv), _addUsage(addUsage),
99          ucharBuf(NULL), encoding(""),
100          uselen(FALSE),
101          fileName(NULL), sourceDir("."),
102          lines(NULL), numLines(0), line_mode(TRUE),
103          buffer(NULL), bufferLen(0),
104          verbose(FALSE), bulk_mode(FALSE),
105          passes(1), iterations(0), time(0),
106          locale(NULL) {
107    init(addOptions, addOptionsCount, status);
108}
109
110void UPerfTest::init(UOption addOptions[], int32_t addOptionsCount,
111                     UErrorCode& status) {
112    //initialize the argument list
113    U_MAIN_INIT_ARGS(_argc, _argv);
114
115    resolvedFileName = NULL;
116
117    // add specific options
118    int32_t optionsCount = OPTIONS_COUNT;
119    if (addOptionsCount > 0) {
120        memcpy(options+optionsCount, addOptions, addOptionsCount*sizeof(UOption));
121        optionsCount += addOptionsCount;
122    }
123
124    //parse the arguments
125    _remainingArgc = u_parseArgs(_argc, (char**)_argv, optionsCount, options);
126
127    // copy back values for additional options
128    if (addOptionsCount > 0) {
129        memcpy(addOptions, options+OPTIONS_COUNT, addOptionsCount*sizeof(UOption));
130    }
131
132    // Now setup the arguments
133    if(_argc==1 || options[HELP1].doesOccur || options[HELP2].doesOccur) {
134        status = U_ILLEGAL_ARGUMENT_ERROR;
135        return;
136    }
137
138    if(options[VERBOSE].doesOccur) {
139        verbose = TRUE;
140    }
141
142    if(options[SOURCEDIR].doesOccur) {
143        sourceDir = options[SOURCEDIR].value;
144    }
145
146    if(options[ENCODING].doesOccur) {
147        encoding = options[ENCODING].value;
148    }
149
150    if(options[USELEN].doesOccur) {
151        uselen = TRUE;
152    }
153
154    if(options[FILE_NAME].doesOccur){
155        fileName = options[FILE_NAME].value;
156    }
157
158    if(options[PASSES].doesOccur) {
159        passes = atoi(options[PASSES].value);
160    }
161    if(options[ITERATIONS].doesOccur) {
162        iterations = atoi(options[ITERATIONS].value);
163        if(options[TIME].doesOccur) {
164            status = U_ILLEGAL_ARGUMENT_ERROR;
165            return;
166        }
167    } else if(options[TIME].doesOccur) {
168        time = atoi(options[TIME].value);
169    } else {
170        iterations = 1000; // some default
171    }
172
173    if(options[LINE_MODE].doesOccur) {
174        line_mode = TRUE;
175        bulk_mode = FALSE;
176    }
177
178    if(options[BULK_MODE].doesOccur) {
179        bulk_mode = TRUE;
180        line_mode = FALSE;
181    }
182
183    if(options[LOCALE].doesOccur) {
184        locale = options[LOCALE].value;
185    }
186
187    int32_t len = 0;
188    if(fileName!=NULL){
189        //pre-flight
190        ucbuf_resolveFileName(sourceDir, fileName, NULL, &len, &status);
191        resolvedFileName = (char*) uprv_malloc(len);
192        if(resolvedFileName==NULL){
193            status= U_MEMORY_ALLOCATION_ERROR;
194            return;
195        }
196        if(status == U_BUFFER_OVERFLOW_ERROR){
197            status = U_ZERO_ERROR;
198        }
199        ucbuf_resolveFileName(sourceDir, fileName, resolvedFileName, &len, &status);
200        ucharBuf = ucbuf_open(resolvedFileName,&encoding,TRUE,FALSE,&status);
201
202        if(U_FAILURE(status)){
203            printf("Could not open the input file %s. Error: %s\n", fileName, u_errorName(status));
204            return;
205        }
206    }
207}
208
209ULine* UPerfTest::getLines(UErrorCode& status){
210    if (U_FAILURE(status)) {
211        return NULL;
212    }
213    if (lines != NULL) {
214        return lines;  // don't do it again
215    }
216    lines     = new ULine[MAXLINES];
217    int maxLines = MAXLINES;
218    numLines=0;
219    const UChar* line=NULL;
220    int32_t len =0;
221    for (;;) {
222        line = ucbuf_readline(ucharBuf,&len,&status);
223        if(line == NULL || U_FAILURE(status)){
224            break;
225        }
226        lines[numLines].name  = new UChar[len];
227        lines[numLines].len   = len;
228        memcpy(lines[numLines].name, line, len * U_SIZEOF_UCHAR);
229
230        numLines++;
231        len = 0;
232        if (numLines >= maxLines) {
233            maxLines += MAXLINES;
234            ULine *newLines = new ULine[maxLines];
235            if(newLines == NULL) {
236                fprintf(stderr, "Out of memory reading line %d.\n", (int)numLines);
237                status= U_MEMORY_ALLOCATION_ERROR;
238                delete []lines;
239                return NULL;
240            }
241
242            memcpy(newLines, lines, numLines*sizeof(ULine));
243            delete []lines;
244            lines = newLines;
245        }
246    }
247    return lines;
248}
249const UChar* UPerfTest::getBuffer(int32_t& len, UErrorCode& status){
250    if (U_FAILURE(status)) {
251        return NULL;
252    }
253    len = ucbuf_size(ucharBuf);
254    buffer =  (UChar*) uprv_malloc(U_SIZEOF_UCHAR * (len+1));
255    u_strncpy(buffer,ucbuf_getBuffer(ucharBuf,&bufferLen,&status),len);
256    buffer[len]=0;
257    len = bufferLen;
258    return buffer;
259}
260UBool UPerfTest::run(){
261    if(_remainingArgc==1){
262        // Testing all methods
263        return runTest();
264    }
265    UBool res=FALSE;
266    // Test only the specified fucntion
267    for (int i = 1; i < _remainingArgc; ++i) {
268        if (_argv[i][0] != '-') {
269            char* name = (char*) _argv[i];
270            if(verbose==TRUE){
271                //fprintf(stdout, "\n=== Handling test: %s: ===\n", name);
272                //fprintf(stdout, "\n%s:\n", name);
273            }
274            char* parameter = strchr( name, '@' );
275            if (parameter) {
276                *parameter = 0;
277                parameter += 1;
278            }
279            execCount = 0;
280            res = runTest( name, parameter );
281            if (!res || (execCount <= 0)) {
282                fprintf(stdout, "\n---ERROR: Test doesn't exist: %s!\n", name);
283                return FALSE;
284            }
285        }
286    }
287    return res;
288}
289UBool UPerfTest::runTest(char* name, char* par ){
290    UBool rval;
291    char* pos = NULL;
292
293    if (name)
294        pos = strchr( name, delim ); // check if name contains path (by looking for '/')
295    if (pos) {
296        path = pos+1;   // store subpath for calling subtest
297        *pos = 0;       // split into two strings
298    }else{
299        path = NULL;
300    }
301
302    if (!name || (name[0] == 0) || (strcmp(name, "*") == 0)) {
303        rval = runTestLoop( NULL, NULL );
304
305    }else if (strcmp( name, "LIST" ) == 0) {
306        this->usage();
307        rval = TRUE;
308
309    }else{
310        rval = runTestLoop( name, par );
311    }
312
313    if (pos)
314        *pos = delim;  // restore original value at pos
315    return rval;
316}
317
318
319void UPerfTest::setPath( char* pathVal )
320{
321    this->path = pathVal;
322}
323
324// call individual tests, to be overriden to call implementations
325UPerfFunction* UPerfTest::runIndexedTest( int32_t /*index*/, UBool /*exec*/, const char* & /*name*/, char* /*par*/ )
326{
327    // to be overriden by a method like:
328    /*
329    switch (index) {
330        case 0: name = "First Test"; if (exec) FirstTest( par ); break;
331        case 1: name = "Second Test"; if (exec) SecondTest( par ); break;
332        default: name = ""; break;
333    }
334    */
335    fprintf(stderr,"*** runIndexedTest needs to be overriden! ***");
336    return NULL;
337}
338
339
340UBool UPerfTest::runTestLoop( char* testname, char* par )
341{
342    int32_t    index = 0;
343    const char*   name;
344    UBool  run_this_test;
345    UBool  rval = FALSE;
346    UErrorCode status = U_ZERO_ERROR;
347    UPerfTest* saveTest = gTest;
348    gTest = this;
349    int32_t loops = 0;
350    double t=0;
351    int32_t n = 1;
352    long ops;
353    do {
354        this->runIndexedTest( index, FALSE, name );
355        if (!name || (name[0] == 0))
356            break;
357        if (!testname) {
358            run_this_test = TRUE;
359        }else{
360            run_this_test = (UBool) (strcmp( name, testname ) == 0);
361        }
362        if (run_this_test) {
363            UPerfFunction* testFunction = this->runIndexedTest( index, TRUE, name, par );
364            execCount++;
365            rval=TRUE;
366            if(testFunction==NULL){
367                fprintf(stderr,"%s function returned NULL", name);
368                return FALSE;
369            }
370            ops = testFunction->getOperationsPerIteration();
371            if (ops < 1) {
372                fprintf(stderr, "%s returned an illegal operations/iteration()\n", name);
373                return FALSE;
374            }
375            if(iterations == 0) {
376                n = time;
377                // Run for specified duration in seconds
378                if(verbose==TRUE){
379                    fprintf(stdout,"= %s calibrating %i seconds \n", name, (int)n);
380                }
381
382                //n *=  1000; // s => ms
383                //System.out.println("# " + meth.getName() + " " + n + " sec");
384                int32_t failsafe = 1; // last resort for very fast methods
385                t = 0;
386                while (t < (int)(n * 0.9)) { // 90% is close enough
387                    if (loops == 0 || t == 0) {
388                        loops = failsafe;
389                        failsafe *= 10;
390                    } else {
391                        //System.out.println("# " + meth.getName() + " x " + loops + " = " + t);
392                        loops = (int)((double)n / t * loops + 0.5);
393                        if (loops == 0) {
394                            fprintf(stderr,"Unable to converge on desired duration");
395                            return FALSE;
396                        }
397                    }
398                    //System.out.println("# " + meth.getName() + " x " + loops);
399                    t = testFunction->time(loops,&status);
400                    if(U_FAILURE(status)){
401                        printf("Performance test failed with error: %s \n", u_errorName(status));
402                        break;
403                    }
404                }
405            } else {
406                loops = iterations;
407            }
408
409            double min_t=1000000.0, sum_t=0.0;
410            long events = -1;
411
412            for(int32_t ps =0; ps < passes; ps++){
413                fprintf(stdout,"= %s begin " ,name);
414                if(verbose==TRUE){
415                    if(iterations > 0) {
416                        fprintf(stdout, "%i\n", (int)loops);
417                    } else {
418                        fprintf(stdout, "%i\n", (int)n);
419                    }
420                } else {
421                    fprintf(stdout, "\n");
422                }
423                t = testFunction->time(loops, &status);
424                if(U_FAILURE(status)){
425                    printf("Performance test failed with error: %s \n", u_errorName(status));
426                    break;
427                }
428                sum_t+=t;
429                if(t<min_t) {
430                    min_t=t;
431                }
432                events = testFunction->getEventsPerIteration();
433                //print info only in verbose mode
434                if(verbose==TRUE){
435                    if(events == -1){
436                        fprintf(stdout, "= %s end: %f loops: %i operations: %li \n", name, t, (int)loops, ops);
437                    }else{
438                        fprintf(stdout, "= %s end: %f loops: %i operations: %li events: %li\n", name, t, (int)loops, ops, events);
439                    }
440                }else{
441                    if(events == -1){
442                        fprintf(stdout,"= %s end %f %i %li\n", name, t, (int)loops, ops);
443                    }else{
444                        fprintf(stdout,"= %s end %f %i %li %li\n", name, t, (int)loops, ops, events);
445                    }
446                }
447            }
448            if(verbose && U_SUCCESS(status)) {
449                double avg_t = sum_t/passes;
450                if (loops == 0 || ops == 0) {
451                    fprintf(stderr, "%s did not run\n", name);
452                }
453                else if(events == -1) {
454                    fprintf(stdout, "%%= %s avg: %.4g loops: %i avg/op: %.4g ns\n",
455                            name, avg_t, (int)loops, (avg_t*1E9)/(loops*ops));
456                    fprintf(stdout, "_= %s min: %.4g loops: %i min/op: %.4g ns\n",
457                            name, min_t, (int)loops, (min_t*1E9)/(loops*ops));
458                }
459                else {
460                    fprintf(stdout, "%%= %s avg: %.4g loops: %i avg/op: %.4g ns avg/event: %.4g ns\n",
461                            name, avg_t, (int)loops, (avg_t*1E9)/(loops*ops), (avg_t*1E9)/(loops*events));
462                    fprintf(stdout, "_= %s min: %.4g loops: %i min/op: %.4g ns min/event: %.4g ns\n",
463                            name, min_t, (int)loops, (min_t*1E9)/(loops*ops), (min_t*1E9)/(loops*events));
464                }
465            }
466            delete testFunction;
467        }
468        index++;
469    }while(name);
470
471    gTest = saveTest;
472    return rval;
473}
474
475/**
476* Print a usage message for this test class.
477*/
478void UPerfTest::usage( void )
479{
480    puts(gUsageString);
481    if (_addUsage != NULL) {
482        puts(_addUsage);
483    }
484
485    UBool save_verbose = verbose;
486    verbose = TRUE;
487    fprintf(stdout,"Test names:\n");
488    fprintf(stdout,"-----------\n");
489
490    int32_t index = 0;
491    const char* name = NULL;
492    do{
493        this->runIndexedTest( index, FALSE, name );
494        if (!name)
495            break;
496        fprintf(stdout, "%s\n", name);
497        index++;
498    }while (name && (name[0] != 0));
499    verbose = save_verbose;
500}
501
502
503
504
505void UPerfTest::setCaller( UPerfTest* callingTest )
506{
507    caller = callingTest;
508    if (caller) {
509        verbose = caller->verbose;
510    }
511}
512
513UBool UPerfTest::callTest( UPerfTest& testToBeCalled, char* par )
514{
515    execCount--; // correct a previously assumed test-exec, as this only calls a subtest
516    testToBeCalled.setCaller( this );
517    return testToBeCalled.runTest( path, par );
518}
519
520UPerfTest::~UPerfTest(){
521    if(lines!=NULL){
522        delete[] lines;
523    }
524    if(buffer!=NULL){
525        uprv_free(buffer);
526    }
527    if(resolvedFileName!=NULL){
528        uprv_free(resolvedFileName);
529    }
530    ucbuf_close(ucharBuf);
531}
532
533#endif
534