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