/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * $Id$ * * @author Andy Heninger, IBM */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include void clearFileInfoMemory(); #ifdef HAVE_PTHREAD #include #include #include //------------------------------------------------------------------------------ // // UNIX specific code for starting threads // //------------------------------------------------------------------------------ extern "C" { typedef void (*ThreadFunc)(void *); typedef void *(*pthreadfunc)(void *); class ThreadFuncs // This class isolates OS dependent threading { // functions from the rest of ThreadTest program. public: static void Sleep(int millis); static void startThread(ThreadFunc, void *param); }; void ThreadFuncs::Sleep(int millis) { int seconds = millis/1000; if (seconds <= 0) seconds = 1; #if defined(SOLARIS) // somehow the sleep hangs on Solaris // so ignore the call #else ::sleep(seconds); #endif } void ThreadFuncs::startThread(ThreadFunc func, void *param) { int x; pthread_t tId; //thread_t tId; #if defined(_HP_UX) && defined(XML_USE_DCE) x = pthread_create( &tId, pthread_attr_default, (pthreadfunc)func, param); #else pthread_attr_t attr; pthread_attr_init(&attr); x = pthread_create( &tId, &attr, (pthreadfunc)func, param); #endif if (x == -1) { fprintf(stderr, "Error starting thread. Errno = %d\n", errno); clearFileInfoMemory(); exit(-1); } } } // end of extern "C" #else //------------------------------------------------------------------------------ // // Windows specific code for starting threads // //------------------------------------------------------------------------------ #include #include typedef DWORD (WINAPI *ThreadFunc)(void *); class ThreadFuncs // This class isolates OS dependent threading { // functions from the rest of ThreadTest program. public: static void Sleep(int millis) {::Sleep(millis);}; static void startThread(ThreadFunc, void *param); }; void ThreadFuncs::startThread(ThreadFunc func, void *param) { HANDLE tHandle; DWORD threadID; tHandle = CreateThread(0, // Security Attributes, 0x10000, // Stack Size, func, // Starting Address. param, // Parmeters 0, // Creation Flags, &threadID); // Thread ID (Can not be null on 95/98) if (tHandle == 0) { fprintf(stderr, "Error starting thread. Errno = %d\n", errno); clearFileInfoMemory(); exit(-1); } // Set the priority of the working threads low, so that the UI of the running system will // remain responsive. SetThreadPriority(tHandle, THREAD_PRIORITY_IDLE); } #endif //------------------------------------------------------------------------------ // // struct InFileInfo One of these structs will be set up for each file listed // on the command line. Once set, the data is unchanging // and can safely be referenced by the test threads without // use of synchronization. // //------------------------------------------------------------------------------ struct InFileInfo { char *fileName; XMLCh *uFileName; // When doing an in-memory parse, avoid transcoding file name // each time through. char *fileContent; // If doing an in-memory parse, this field points // to an allocated string containing the entire file // contents. Otherwise it's 0. size_t fileSize; // The file length. Only initialized when doing // an in-memory test. int checkSum; // The XML checksum. Set up by the main thread for // each file before the worker threads are started. }; //------------------------------------------------------------------------------ // // struct threadInfo Holds information specific to an individual thread. // One of these is set up for each thread in the test. // The main program monitors the threads by looking // at the status stored in these structs. // //------------------------------------------------------------------------------ struct ThreadInfo { bool fHeartBeat; // Set true by the thread each time it finishes // parsing a file. bool fInProgress; // Set to false by the thread when parse in progress unsigned int fParses; // Number of parses completed. int fThreadNum; // Identifying number for this thread. ThreadInfo() { fHeartBeat = false; fInProgress = false; fParses = 0; fThreadNum = -1; } }; XERCES_CPP_NAMESPACE_USE //------------------------------------------------------------------------------ // // struct runInfo Holds the info extracted from the command line. // There is only one of these, and it is static, and // unchanging once the command line has been parsed. // During the test, the threads will access this info without // any synchronization. // //------------------------------------------------------------------------------ const int MAXINFILES = 25; struct RunInfo { bool doGrammarCaching; bool quiet; bool verbose; bool stopNow; bool dom; bool sax; bool reuseParser; bool inMemory; bool dumpOnErr; bool doSchema; bool schemaFullChecking; bool doNamespaces; bool doInitialParse; bool doNamespacePrefixes; // SAX2 SAXParser::ValSchemes valScheme; int numThreads; int totalTime; int numInputFiles; unsigned int numParses; InFileInfo files[MAXINFILES]; }; // //------------------------------------------------------------------------------ // // Global Data // //------------------------------------------------------------------------------ RunInfo gRunInfo; ThreadInfo *gThreadInfo; /** Grammar caching thread testing */ MemoryManager* gpMemMgr = 0; XMLGrammarPool* gp = 0; #ifdef HELPER_ROUTINES // Routines which maybe helpful for debugging static void printString(const XMLCh *str) { char *s = XMLString::transcode(str); printf("%s", s); delete s; } #define CHARS_PER_LINE 40 #define BYTES_PER_LINE 16 /* * DumpLine: Dump out a buffer (address and length) to stderr. */ static void DumpLine(char* address, int length) { int i, c, charCount=0; if (length % 4) length += 4; fprintf(stderr, "%8.8p: ", address); for (i=0; i < length/4; ++i) { fprintf(stderr, "%8.8X ", ((int*)address)[i]); charCount += 9; } for (i=charCount; i < CHARS_PER_LINE; ++i) { putc(' ', stderr); } fprintf(stderr, "| "); for (i=0; i < length; ++i) { c = address[i]; c = (isprint(c) ? c : '.'); fprintf(stderr, "%c", c); } fprintf(stderr, "\n"); } /* * dump: dump out a buffer (address and length) to stderr by dumping out * a line at a time (DumpLine), until the buffer is written out. */ static void dump(void* generalAddress, int length) { int curr = 0; char* address = (char*) generalAddress; while (&address[curr] < &address[length-BYTES_PER_LINE]) { DumpLine(&address[curr], BYTES_PER_LINE); curr += BYTES_PER_LINE; } if (curr < length) { DumpLine(&address[curr], length-curr); } fflush(stderr); } #endif //------------------------------------------------------------------------------ // // class ThreadParser Bundles together a SAX parser and the SAX handlers // and contains the API that the rest of this test // program uses for creating parsers and doing parsing. // // Multiple instances of this class can operate concurrently // in different threads. // //------------------------------------------------------------------------------- class ThreadParser { public: class SAXHandler; class SAX2Handler; SAXHandler* fSAXHandler; SAX2Handler* fSAX2Handler; ErrorHandler* fDOMErrorHandler; // This is the API used by the rest of the test program ThreadParser(); ~ThreadParser(); int parse(int fileNum); // Parse the specified file. fileNum is an index // into the gRunInfo.files array. // return the XML checksum, or // 0 if a parse error occurred. int getCheckSum() { return fCheckSum; } int reCheck(); // Try to compute the checksum again. // for DOM, re-walk the tree. // for SAX, can't do, just return previous value. void domPrint(); // including any children. Default (no param) // version dumps the entire document. void addToCheckSum(const XMLCh *chars, XMLSize_t len=(XMLSize_t)-1); // These are the SAX call-back functions that this class implements. Can be used // for SAX and SAX2. void characters(const XMLCh* const chars, const XMLSize_t length) { addToCheckSum(chars, length); } void ignorableWhitespace(const XMLCh* const chars, const XMLSize_t length) { addToCheckSum(chars, length); } void resetDocument() { } void warning(const SAXParseException& exc) { fprintf(stderr, "*** Warning "); fflush(stderr); throw exc; } void error(const SAXParseException& exc) { fprintf(stderr, "*** Error "); fflush(stderr); throw exc; } void fatalError(const SAXParseException& exc) { fprintf(stderr, "***** Fatal error "); fflush(stderr); throw exc; } // Create a nested class that can inherit from HandlerBase // for SAX startElement callbacks. class SAXHandler : public HandlerBase { public: ThreadParser* SAXInstance; void startElement(const XMLCh* const name, AttributeList& attributes); }; // Create a nested class that can inherit from DefaultHandler // for SAX2 startElement callbacks. class SAX2Handler : public DefaultHandler { public: ThreadParser* SAX2Instance; void startElement(const XMLCh* const uri, const XMLCh* const localname, const XMLCh* const qname, const Attributes& attributes); }; private: int fCheckSum; SAXParser* fSAXParser; SAX2XMLReader* fSAX2Parser; XercesDOMParser* fXercesDOMParser; XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument * fDoc; ThreadParser(const ThreadParser &); // No copy constructor const ThreadParser & operator =(const ThreadParser &); // No assignment. void domCheckSum(const DOMNode *); }; // // ThreadParser constructor. Invoked by the threads of the test program // to create parsers. // ThreadParser::ThreadParser() { fSAXParser = 0; fSAX2Parser = 0; fXercesDOMParser = 0; fSAXHandler = 0; fSAX2Handler = 0; fDOMErrorHandler = 0; fDoc = 0; fCheckSum = 0; if (gRunInfo.dom) { // Set up to use a DOM parser /** Grammar caching thread testing */ if (gp) { fXercesDOMParser = new XercesDOMParser(0, XMLPlatformUtils::fgMemoryManager, gp); fXercesDOMParser->cacheGrammarFromParse(true); fXercesDOMParser->useCachedGrammarInParse(true); } else { fXercesDOMParser = new XercesDOMParser; } switch (gRunInfo.valScheme) { case SAXParser::Val_Never: fXercesDOMParser->setValidationScheme(XercesDOMParser::Val_Never); break; case SAXParser::Val_Auto: fXercesDOMParser->setValidationScheme(XercesDOMParser::Val_Auto); break; default: //SAXParser::Val_Always: fXercesDOMParser->setValidationScheme(XercesDOMParser::Val_Always); break; } fXercesDOMParser->setDoSchema(gRunInfo.doSchema); fXercesDOMParser->setHandleMultipleImports (true); fXercesDOMParser->setValidationSchemaFullChecking(gRunInfo.schemaFullChecking); fXercesDOMParser->setDoNamespaces(gRunInfo.doNamespaces); fDOMErrorHandler = (ErrorHandler*) new HandlerBase(); fXercesDOMParser->setErrorHandler(fDOMErrorHandler); } else if (gRunInfo.sax) { // Set up to use a SAX1 parser. /** Grammar caching thread testing */ if (gp) { fSAXParser = new SAXParser(0, XMLPlatformUtils::fgMemoryManager, gp); fSAXParser->cacheGrammarFromParse(true); fSAXParser->useCachedGrammarInParse(true); } else { fSAXParser = new SAXParser(); } fSAXParser->setValidationScheme(gRunInfo.valScheme); fSAXParser->setDoSchema(gRunInfo.doSchema); fSAXParser->setHandleMultipleImports (true); fSAXParser->setValidationSchemaFullChecking(gRunInfo.schemaFullChecking); fSAXParser->setDoNamespaces(gRunInfo.doNamespaces); fSAXHandler = new ThreadParser::SAXHandler(); fSAXHandler->SAXInstance = this; fSAXParser->setDocumentHandler(fSAXHandler); fSAXParser->setErrorHandler(fSAXHandler); } else { // Set up to use a SAX2 parser. /** Grammar caching thread testing */ if (gp) { fSAX2Parser = XMLReaderFactory::createXMLReader(gpMemMgr, gp); fSAX2Parser->setFeature(XMLUni::fgXercesCacheGrammarFromParse,true); fSAX2Parser->setFeature(XMLUni::fgXercesUseCachedGrammarInParse,true); } else { fSAX2Parser = XMLReaderFactory::createXMLReader(); } fSAX2Parser->setFeature(XMLUni::fgSAX2CoreNameSpaces,(gRunInfo.doNamespaces)); fSAX2Parser->setFeature(XMLUni::fgXercesSchema,(gRunInfo.doSchema)); fSAX2Parser->setFeature(XMLUni::fgXercesHandleMultipleImports, true); fSAX2Parser->setFeature(XMLUni::fgXercesSchemaFullChecking,(gRunInfo.schemaFullChecking)); switch (gRunInfo.valScheme) { case SAXParser::Val_Never: fSAX2Parser->setFeature(XMLUni::fgSAX2CoreValidation, false); break; case SAXParser::Val_Auto: fSAX2Parser->setFeature(XMLUni::fgSAX2CoreValidation, true); fSAX2Parser->setFeature(XMLUni::fgXercesDynamic, true); break; default: //SAXParser::Val_Always: fSAX2Parser->setFeature(XMLUni::fgSAX2CoreValidation, true); fSAX2Parser->setFeature(XMLUni::fgXercesDynamic, false); break; } fSAX2Parser->setFeature(XMLUni::fgSAX2CoreNameSpacePrefixes,(gRunInfo.doNamespacePrefixes)); fSAX2Handler = new ThreadParser::SAX2Handler(); fSAX2Handler->SAX2Instance = this; fSAX2Parser->setContentHandler(fSAX2Handler); fSAX2Parser->setErrorHandler(fSAX2Handler); } } ThreadParser::~ThreadParser() { delete fSAXParser; delete fSAX2Parser; delete fXercesDOMParser; delete fSAXHandler; delete fSAX2Handler; delete fDOMErrorHandler; } //------------------------------------------------------------------------ // // parse - This is the method that is invoked by the rest of // the test program to actually parse an XML file. // //------------------------------------------------------------------------ int ThreadParser::parse(int fileNum) { MemBufInputSource *mbis = 0; InFileInfo *fInfo = &gRunInfo.files[fileNum]; bool errors = false; fCheckSum = 0; if (gRunInfo.inMemory) { mbis = new MemBufInputSource((const XMLByte *) fInfo->fileContent, fInfo->fileSize, fInfo->uFileName, false); } try { if (gRunInfo.dom) { // Do a DOM parse fXercesDOMParser->resetDocumentPool(); if (gRunInfo.inMemory) fXercesDOMParser->parse(*mbis); else fXercesDOMParser->parse(fInfo->fileName); fDoc = fXercesDOMParser->getDocument(); domCheckSum(fDoc); } else if (gRunInfo.sax) { // Do a SAX1 parse if (gRunInfo.inMemory) fSAXParser->parse(*mbis); else fSAXParser->parse(fInfo->fileName); } else { // Do a SAX2 parse if (gRunInfo.inMemory) fSAX2Parser->parse(*mbis); else fSAX2Parser->parse(fInfo->fileName); } } catch (const OutOfMemoryException&) { fprintf(stderr, " during parsing: %s\n OutOfMemoryException.\n", fInfo->fileName); errors = true; } catch (const XMLException& e) { char *exceptionMessage = XMLString::transcode(e.getMessage()); fprintf(stderr, " during parsing: %s\n Exception message is: %s\n", fInfo->fileName, exceptionMessage); XMLString::release(&exceptionMessage); errors = true; } catch (const DOMException& toCatch) { fprintf(stderr, " during parsing: %s\n DOMException code is: %i\n", fInfo->fileName, toCatch.code); errors = true; } catch (const SAXParseException& e) { char *exceptionMessage = XMLString::transcode(e.getMessage()); fprintf(stderr, " during parsing: %s\n Exception message is: %s\n", fInfo->fileName, exceptionMessage); XMLString::release(&exceptionMessage); errors = true; } catch (...) { fprintf(stderr, "Unexpected exception during parsing\n"); errors = true; } delete mbis; if (errors) { fflush(stderr); return 0; // if errors occurred, return zero as if checksum = 0; } return fCheckSum; } // // addToCheckSum - private function, used within ThreadParser in // computing the checksum of the XML file. // // Unichar Strings to be added to the checksum // can either be null terminated (omit len param, which // will then default to -1), or provide an explicit // length. // void ThreadParser::addToCheckSum(const XMLCh *chars, XMLSize_t len) { if (len == (XMLSize_t)-1) { // Null terminated string. while (*chars != 0) { fCheckSum = fCheckSum*5 + *chars; chars++; } } else { // String with character count. XMLSize_t i; for (i=0; iaddToCheckSum(name); XMLSize_t n = attributes.getLength(); XMLSize_t i; for (i=0; iaddToCheckSum(attNam); const XMLCh *attVal = attributes.getValue(i); SAXInstance->addToCheckSum(attVal); } } // // startElement - our SAX2 handler callback function for startElement. // Update the document checksum with the element name // and any attribute names and values. // void ThreadParser::SAX2Handler::startElement(const XMLCh *const /*uri*/, const XMLCh *const localname, const XMLCh *const /*qname*/, const Attributes& attributes) { SAX2Instance->addToCheckSum(localname); XMLSize_t n = attributes.getLength(); XMLSize_t i; for (i=0; iaddToCheckSum(attNam); const XMLCh *attVal = attributes.getValue(i); SAX2Instance->addToCheckSum(attVal); } } // // domCheckSum - Compute the check sum for a DOM node. // Works recursively - initially called with a document node. // void ThreadParser::domCheckSum(const DOMNode *node) { const XMLCh *s; DOMNode *child; DOMNamedNodeMap *attributes; switch (node->getNodeType() ) { case DOMNode::ELEMENT_NODE: { s = node->getNodeName(); // the element name attributes = node->getAttributes(); // Element's attributes XMLSize_t numAttributes = attributes->getLength(); XMLSize_t i; for (i=0; iitem(i)); addToCheckSum(s); // Content and Children for (child=node->getFirstChild(); child!=0; child=child->getNextSibling()) domCheckSum(child); break; } case DOMNode::ATTRIBUTE_NODE: { s = node->getNodeName(); // The attribute name addToCheckSum(s); s = node->getNodeValue(); // The attribute value if (s != 0) addToCheckSum(s); break; } case DOMNode::TEXT_NODE: case DOMNode::CDATA_SECTION_NODE: { s = node->getNodeValue(); addToCheckSum(s); break; } case DOMNode::ENTITY_REFERENCE_NODE: case DOMNode::DOCUMENT_NODE: { // For entity references and the document, nothing is dirctly // added to the checksum, but we do want to process the chidren nodes. // for (child=node->getFirstChild(); child!=0; child=child->getNextSibling()) domCheckSum(child); break; } } } // // Recompute the checksum. Meaningful only for DOM, will tell us whether // a failure is transient, or whether the DOM data is permanently corrupted. // int ThreadParser::reCheck() { if (gRunInfo.dom) { fCheckSum = 0; domCheckSum(fDoc); } return fCheckSum; } // // domPrint - Dump the contents of a DOM node. // For debugging failures, when all else fails. // Works recursively - initially called with a document node. // void ThreadParser::domPrint() { printf("Begin DOMPrint ...\n"); if (gRunInfo.dom) { try { XMLCh tempStr[100]; XMLString::transcode("LS", tempStr, 99); DOMImplementation *impl = DOMImplementationRegistry::getDOMImplementation(tempStr); DOMLSSerializer *theSerializer = ((DOMImplementationLS*)impl)->createLSSerializer(); DOMLSOutput *theOutput = ((DOMImplementationLS*)impl)->createLSOutput(); XMLFormatTarget *myFormTarget = new StdOutFormatTarget(); theOutput->setByteStream(myFormTarget); DOMNode *doc = fXercesDOMParser->getDocument(); theSerializer->write(doc,theOutput); delete myFormTarget; theSerializer->release(); theOutput->release(); } catch (...) { // do nothing } } printf("End DOMPrint\n"); } //---------------------------------------------------------------------- // // parseCommandLine Read through the command line, and save all // of the options in the gRunInfo struct. // // Display the usage message if the command line // is no good. // // Probably ought to be a member function of RunInfo. // //---------------------------------------------------------------------- void parseCommandLine(int argc, char **argv) { gRunInfo.doGrammarCaching = false; gRunInfo.quiet = false; // Set up defaults for run. gRunInfo.verbose = false; gRunInfo.stopNow = false; gRunInfo.dom = false; gRunInfo.sax = true; gRunInfo.reuseParser = false; gRunInfo.inMemory = false; gRunInfo.dumpOnErr = false; gRunInfo.doSchema = false; gRunInfo.schemaFullChecking = false; gRunInfo.doNamespaces = false; gRunInfo.doInitialParse = false; gRunInfo.doNamespacePrefixes = false; gRunInfo.valScheme = SAXParser::Val_Auto; gRunInfo.numThreads = 2; gRunInfo.totalTime = 0; gRunInfo.numInputFiles = 0; gRunInfo.numParses = 0; try // Use exceptions for command line syntax errors. { int argnum = 1; while (argnum < argc) { if (strcmp(argv[argnum], "-quiet") == 0) gRunInfo.quiet = true; else if (strcmp(argv[argnum], "-verbose") == 0) gRunInfo.verbose = true; else if (strncmp(argv[argnum], "-v=", 3) == 0) { const char* const parm = &argv[argnum][3]; if (!strcmp(parm, "never")) gRunInfo.valScheme = SAXParser::Val_Never; else if (!strcmp(parm, "auto")) gRunInfo.valScheme = SAXParser::Val_Auto; else if (!strcmp(parm, "always")) gRunInfo.valScheme = SAXParser::Val_Always; else { fprintf(stderr, "Unrecognized -v option \"%s\"\n", parm); throw 1; } } else if (strcmp(argv[argnum], "-v") == 0) { fprintf(stderr, "Please note the -v option has been changed to -v=[always | never | auto]\n"); fprintf(stderr, "ThreadTest will continue with -v=always\n"); gRunInfo.valScheme = SAXParser::Val_Always; } else if (strcmp(argv[argnum], "-s") == 0) gRunInfo.doSchema = true; else if (strcmp(argv[argnum], "-f") == 0) gRunInfo.schemaFullChecking = true; else if (strcmp(argv[argnum], "-n") == 0) gRunInfo.doNamespaces = true; else if (strcmp(argv[argnum], "-p") == 0) gRunInfo.doNamespacePrefixes = true; else if (!strncmp(argv[argnum], "-parser=", 8)) { const char* const parm = &argv[argnum][8]; if (!strcmp(parm, "dom")) { gRunInfo.dom = true; gRunInfo.sax = false; } else if (!strcmp(parm, "sax")) { gRunInfo.dom = false; gRunInfo.sax = true; } else if (!strcmp(parm, "sax2")) { gRunInfo.dom = false; gRunInfo.sax = false; } else { fprintf(stderr, "Unrecognized -parser option \"%s\"\n", parm); throw 1; } } else if (strcmp(argv[argnum], "-init") == 0) gRunInfo.doInitialParse = true; else if (strcmp(argv[argnum], "-reuse") == 0) gRunInfo.reuseParser = true; else if (strcmp(argv[argnum], "-dump") == 0) gRunInfo.dumpOnErr = true; else if (strcmp(argv[argnum], "-mem") == 0) gRunInfo.inMemory = true; else if (strcmp(argv[argnum], "-threads") == 0) { ++argnum; if (argnum >= argc) { fprintf(stderr, "Invalid -threads option (missing # of threads)\n"); throw 1; } gRunInfo.numThreads = atoi(argv[argnum]); if (gRunInfo.numThreads < 0) { fprintf(stderr, "Invalid -threads option (negative # of threads)\n"); throw 1; } } else if (strcmp(argv[argnum], "-time") == 0) { ++argnum; if (argnum >= argc) { fprintf(stderr, "Invalid -time option (missing time value)\n"); throw 1; } gRunInfo.totalTime = atoi(argv[argnum]); if (gRunInfo.totalTime < 1) { fprintf(stderr, "Invalid -time option (time value < 1)\n"); throw 1; } } else if (strcmp(argv[argnum], "-gc") == 0) gRunInfo.doGrammarCaching = true; else if (strcmp(argv[argnum], "-parses") == 0) { ++argnum; if (argnum >= argc) { fprintf(stderr, "Invalid -parses option (missing # of parses)\n"); throw 1; } int temp = atoi(argv[argnum]); if (temp < 0) { fprintf(stderr, "Invalid -parses option (negative # of parses)\n"); throw 1; } gRunInfo.numParses = temp; } else if (argv[argnum][0] == '-') { fprintf(stderr, "Unrecognized command line option. Scanning \"%s\"\n", argv[argnum]); throw 1; } else { gRunInfo.numInputFiles++; if (gRunInfo.numInputFiles >= MAXINFILES) { fprintf(stderr, "Too many input files. Limit is %d\n", MAXINFILES); throw 1; } gRunInfo.files[gRunInfo.numInputFiles-1].fileName = argv[argnum]; } argnum++; } // We've made it through the command line. // Verify that at least one input file to be parsed was specified. if (gRunInfo.numInputFiles == 0) { fprintf(stderr, "No input XML file specified on command line.\n"); throw 1; }; if (gRunInfo.numParses && gRunInfo.totalTime) { fprintf(stderr, "Both -parses nnn and -time nnn were specified. Ignoring -time nnn.\n"); } } catch (int) { fprintf(stderr, "usage: ThreadTest [-v] [-threads nnn] [-time nnn] [-quiet] [-verbose] xmlfile...\n" " -v=xxx Validation scheme [always | never | auto]. Default is AUTO.\n" " -n Enable namespace processing. Defaults to off.\n" " -s Enable schema processing. Defaults to off.\n" " -f Enable full schema constraint checking. Defaults to off.\n" " -parser=xxx Parser Type [dom | sax | sax2]. Default is SAX (SAX1).\n" " -p Enable namespace prefixes. Defaults to off.\n" " (Only used with -parser=sax2, ignored otherwise.)\n" " -quiet Suppress periodic status display.\n" " -verbose Display extra messages.\n" " -reuse Retain and reuse parser. Default creates new for each parse.\n" " -threads nnn Number of threads. Default is 2.\n" " -time nnn Total time to run, in seconds. Default is forever.\n" " -parses nnn Run for nnn parses instead of time. Default is to use time\n" " -dump Dump DOM tree on error.\n" " -mem Read files into memory once only, and parse them from there.\n" " -gc Enable grammar caching (i.e. grammar cached and used in subsequent parses). Defaults to off.\n" " -init Perform an initial parse of the file(s) before starting up the individual threads.\n\n" ); exit(1); } } //--------------------------------------------------------------------------- // // ReadFilesIntoMemory For use when parsing from memory rather than // reading the files each time, here is the code that // reads the files into local memory buffers. // // This function is only called once, from the main // thread, before all of the worker threads are started. // //--------------------------------------------------------------------------- void ReadFilesIntoMemory() { int fileNum; FILE *fileF; size_t t; if (gRunInfo.inMemory) { for (fileNum = 0; fileNum uFileName = XMLString::transcode(fInfo->fileName); fileF = fopen( fInfo->fileName, "rb" ); if (fileF == 0) { fprintf(stderr, "Can not open file \"%s\".\n", fInfo->fileName); clearFileInfoMemory(); exit(-1); } fseek(fileF, 0, SEEK_END); fInfo->fileSize = ftell(fileF); fseek(fileF, 0, SEEK_SET); fInfo->fileContent = new char[fInfo->fileSize + 1]; t = fread(fInfo->fileContent, 1, fInfo->fileSize, fileF); if (t != fInfo->fileSize) { fprintf(stderr, "Error reading file \"%s\".\n", fInfo->fileName); clearFileInfoMemory(); exit(-1); } fclose(fileF); fInfo->fileContent[fInfo->fileSize] = 0; } } } void clearFileInfoMemory() { int fileNum; if (gRunInfo.inMemory) { for (fileNum = 0; fileNum uFileName); delete [] fInfo->fileContent; } } } //---------------------------------------------------------------------- // // threadMain The main function for each of the swarm of test threads. // Run in an infinite loop, parsing each of the documents // given on the command line in turn. // //---------------------------------------------------------------------- #ifdef HAVE_PTHREAD extern "C" { void threadMain (void *param) #else unsigned long WINAPI threadMain (void *param) #endif { ThreadInfo *thInfo = (ThreadInfo *)param; ThreadParser *thParser = 0; if (gRunInfo.verbose) printf("Thread #%d: starting\n", thInfo->fThreadNum); int docNum = gRunInfo.numInputFiles; // // Each time through this loop, one file will be parsed and its checksum // computed and compared with the precomputed value for that file. // while (gRunInfo.stopNow == false) { if (gRunInfo.numParses == 0 || thInfo->fParses < gRunInfo.numParses) { thInfo->fInProgress = true; if (thParser == 0) thParser = new ThreadParser; docNum++; if (docNum >= gRunInfo.numInputFiles) docNum = 0; InFileInfo *fInfo = &gRunInfo.files[docNum]; if (gRunInfo.verbose ) printf("Thread #%d: parse %d starting file %s\n", thInfo->fThreadNum, thInfo->fParses, fInfo->fileName); int checkSum = 0; checkSum = thParser->parse(docNum); // For the case where we skip the preparse we will have nothing to // compare the first parse's results to ... so if this looks like first // parse move the checkSum back into the gRunInfo data for this file. if (gRunInfo.files[docNum].checkSum == 0) { gRunInfo.files[docNum].checkSum = checkSum; } else if (checkSum != gRunInfo.files[docNum].checkSum) { if (checkSum == 0) { // parse returns 0 if there was an error so do this to get the real // checksum value checkSum = thParser->getCheckSum(); } fprintf(stderr, "\nThread %d: Parse Check sum error on file \"%s\" for parse # %d. Expected %x, got %x\n", thInfo->fThreadNum, fInfo->fileName, thInfo->fParses, fInfo->checkSum, checkSum); double totalParsesCompleted = 0; for (int threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) { totalParsesCompleted += gThreadInfo[threadNum].fParses; } fprintf(stderr, "Total number of parses completed is %f.\n", totalParsesCompleted); // Revisit - let the loop continue to run? int secondTryCheckSum = thParser->reCheck(); fprintf(stderr, " Retry checksum is %x\n", secondTryCheckSum); if (gRunInfo.dumpOnErr && gRunInfo.dom) { thParser->domPrint(); } fflush(stdout); fflush(stderr); clearFileInfoMemory(); exit(-1); } if (gRunInfo.reuseParser == false) { delete thParser; thParser = 0; } thInfo->fHeartBeat = true; thInfo->fParses++; thInfo->fInProgress = false; } else { ThreadFuncs::Sleep(1000); } } delete thParser; #ifdef HAVE_PTHREAD return; } #else return 0; #endif } //---------------------------------------------------------------------- // // main // //---------------------------------------------------------------------- int main (int argc, char **argv) { parseCommandLine(argc, argv); // // Initialize the XML system. // try { XMLPlatformUtils::Initialize(); } catch (...) { fprintf(stderr, "Exception from XMLPlatfromUtils::Initialize.\n"); return 1; } /** Grammar caching thread testing */ // Initialize memory manger and grammar pool // set doInitialParse to true so that the first parse will cache the // grammar and it'll be used in subsequent parses if (gRunInfo.doSchema == true && gRunInfo.doNamespaces == true && gRunInfo.doGrammarCaching == true) { gpMemMgr = new MemoryManagerImpl(); gp = new XMLGrammarPoolImpl(gpMemMgr); gRunInfo.doInitialParse = true; } // // If we will be parsing from memory, read each of the input files // into memory now. // ReadFilesIntoMemory(); // Initialize checksums to zero so we can check first parse and if // zero then we need to move first parse's checksum into array. This // is for the cse where we skip the initial parse. for (int n = 0; n < gRunInfo.numInputFiles; n++) { gRunInfo.files[n].checkSum = 0; } if (gRunInfo.doInitialParse) { // // While we are still single threaded, parse each of the documents // once, to check for errors, and to note the checksum. // Blow off the rest of the test if there are errors. // ThreadParser *mainParser = new ThreadParser; int n; bool errors = false; int cksum; for (n = 0; n < gRunInfo.numInputFiles; n++) { char *fileName = gRunInfo.files[n].fileName; if (gRunInfo.verbose) printf("%s checksum is ", fileName); cksum = mainParser->parse(n); if (cksum == 0) { fprintf(stderr, "An error occurred while initially parsing %s\n", fileName); errors = true; }; gRunInfo.files[n].checkSum = cksum; if (gRunInfo.verbose ) printf("%x\n", cksum); if (gRunInfo.dumpOnErr && errors && gRunInfo.dom) { mainParser->domPrint(); } } delete mainParser; if (errors) { fprintf(stderr, "Quitting due to error incurred during initial parse\n"); clearFileInfoMemory(); return 1; } } // // Fire off the requested number of parallel threads // if (gRunInfo.numThreads == 0) { clearFileInfoMemory(); exit(0); } gThreadInfo = new ThreadInfo[gRunInfo.numThreads]; int threadNum; for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) { gThreadInfo[threadNum].fThreadNum = threadNum; ThreadFuncs::startThread(threadMain, &gThreadInfo[threadNum]); } if (gRunInfo.numParses) { bool notDone; while (true) { ThreadFuncs::Sleep(1000); notDone = false; for (threadNum = 0; threadNum < gRunInfo.numThreads; threadNum++) { if (gThreadInfo[threadNum].fParses < gRunInfo.numParses) notDone = true; } if (notDone == false) { break; } } } else { // // Loop, watching the heartbeat of the worker threads. // Each second, display "+" when all threads have completed a parse // display "." if some thread hasn't since previous "+" // unsigned long startTime = XMLPlatformUtils::getCurrentMillis(); int elapsedSeconds = 0; while (gRunInfo.totalTime == 0 || gRunInfo.totalTime > elapsedSeconds) { ThreadFuncs::Sleep(1000); if (gRunInfo.quiet == false && gRunInfo.verbose == false) { char c = '+'; for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) { if (gThreadInfo[threadNum].fHeartBeat == false) { c = '.'; break; } } fputc(c, stdout); fflush(stdout); if (c == '+') for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) gThreadInfo[threadNum].fHeartBeat = false; } elapsedSeconds = (XMLPlatformUtils::getCurrentMillis() - startTime) / 1000; } } // // Time's up, we are done. (We only get here if this was a timed run) // Tally up the total number of parses completed by each of the threads. // gRunInfo.stopNow = true; // set flag, which will cause worker threads to stop. // // Make sure all threads are done before terminate // for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) { while (gThreadInfo[threadNum].fInProgress == true) { ThreadFuncs::Sleep(1000); } if (gRunInfo.verbose) printf("Thread #%d: is finished.\n", threadNum); } // // We are done! Count the number of parse and terminate the program // double totalParsesCompleted = 0; for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) { totalParsesCompleted += gThreadInfo[threadNum].fParses; // printf("%f ", totalParsesCompleted); } if (gRunInfo.quiet == false) { if (gRunInfo.numParses) { printf("\n%8.0f total parses were completed.\n", totalParsesCompleted); } else { double parsesPerMinute = totalParsesCompleted / (double(gRunInfo.totalTime) / double(60)); printf("\n%8.2f parses per minute.\n", parsesPerMinute); } } // delete grammar pool and memory manager if (gp) { delete gp; delete gpMemMgr; } XMLPlatformUtils::Terminate(); clearFileInfoMemory(); delete [] gThreadInfo; printf("Test Run Successfully\n"); return 0; }