michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /*********************************************************************** michael@0: ** michael@0: ** Contact: AOF michael@0: ** michael@0: ** Name: ranfile.c michael@0: ** michael@0: ** Description: Test to hammer on various components of NSPR michael@0: ** Modification History: michael@0: ** 20-May-97 AGarcia- Converted the test to accomodate the debug_mode flag. michael@0: ** The debug mode will print all of the printfs associated with this test. michael@0: ** The regress mode will be the default mode. Since the regress tool limits michael@0: ** the output to a one line status:PASS or FAIL,all of the printf statements michael@0: ** have been handled with an if (debug_mode) statement. michael@0: ** 04-June-97 AGarcia removed the Test_Result function. Regress tool has been updated to michael@0: ** recognize the return code from tha main program. michael@0: ***********************************************************************/ michael@0: michael@0: michael@0: /*********************************************************************** michael@0: ** Includes michael@0: ***********************************************************************/ michael@0: /* Used to get the command line option */ michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "rccv.h" michael@0: #include "rcthread.h" michael@0: #include "rcfileio.h" michael@0: #include "rclock.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: static PRFileDesc *output; michael@0: static PRIntn debug_mode = 0; michael@0: static PRIntn failed_already = 0; michael@0: michael@0: class HammerData michael@0: { michael@0: public: michael@0: typedef enum { michael@0: sg_go, sg_stop, sg_done} Action; michael@0: typedef enum { michael@0: sg_okay, sg_open, sg_close, sg_delete, sg_write, sg_seek} Problem; michael@0: michael@0: virtual ~HammerData(); michael@0: HammerData(RCLock* lock, RCCondition *cond, PRUint32 clip); michael@0: virtual PRUint32 Random(); michael@0: michael@0: Action action; michael@0: Problem problem; michael@0: PRUint32 writes; michael@0: RCInterval timein; michael@0: friend class Hammer; michael@0: private: michael@0: RCLock *ml; michael@0: RCCondition *cv; michael@0: PRUint32 limit; michael@0: michael@0: PRFloat64 seed; michael@0: }; /* HammerData */ michael@0: michael@0: class Hammer: public HammerData, public RCThread michael@0: { michael@0: public: michael@0: virtual ~Hammer(); michael@0: Hammer(RCThread::Scope scope, RCLock* lock, RCCondition *cond, PRUint32 clip); michael@0: michael@0: private: michael@0: void RootFunction(); michael@0: michael@0: }; michael@0: michael@0: static PRInt32 pageSize = 1024; michael@0: static const char* baseName = "./"; michael@0: static const char *programName = "Random File"; michael@0: michael@0: /*********************************************************************** michael@0: ** PRIVATE FUNCTION: Random michael@0: ** DESCRIPTION: michael@0: ** Generate a pseudo-random number michael@0: ** INPUTS: None michael@0: ** OUTPUTS: None michael@0: ** RETURN: A pseudo-random unsigned number, 32-bits wide michael@0: ** SIDE EFFECTS: michael@0: ** Updates random seed (a static) michael@0: ** RESTRICTIONS: michael@0: ** None michael@0: ** MEMORY: NA michael@0: ** ALGORITHM: michael@0: ** Uses the current interval timer value, promoted to a 64 bit michael@0: ** float as a multiplier for a static residue (which begins michael@0: ** as an uninitialized variable). The result is bits [16..48) michael@0: ** of the product. Seed is then updated with the return value michael@0: ** promoted to a float-64. michael@0: ***********************************************************************/ michael@0: PRUint32 HammerData::Random() michael@0: { michael@0: PRUint32 rv; michael@0: PRUint64 shift; michael@0: RCInterval now = RCInterval(RCInterval::now); michael@0: PRFloat64 random = seed * (PRFloat64)((PRIntervalTime)now); michael@0: LL_USHR(shift, *((PRUint64*)&random), 16); michael@0: LL_L2UI(rv, shift); michael@0: seed = (PRFloat64)rv; michael@0: return rv; michael@0: } /* HammerData::Random */ michael@0: michael@0: Hammer::~Hammer() { } michael@0: michael@0: Hammer::Hammer( michael@0: RCThread::Scope scope, RCLock* lock, RCCondition *cond, PRUint32 clip): michael@0: HammerData(lock, cond, clip), RCThread(scope, RCThread::joinable, 0) { } michael@0: michael@0: HammerData::~HammerData() { } michael@0: michael@0: HammerData::HammerData(RCLock* lock, RCCondition *cond, PRUint32 clip) michael@0: { michael@0: ml = lock; michael@0: cv = cond; michael@0: writes = 0; michael@0: limit = clip; michael@0: seed = 0x58a9382; michael@0: action = HammerData::sg_go; michael@0: problem = HammerData::sg_okay; michael@0: timein = RCInterval(RCInterval::now); michael@0: } /* HammerData::HammerData */ michael@0: michael@0: michael@0: /*********************************************************************** michael@0: ** PRIVATE FUNCTION: Hammer::RootFunction michael@0: ** DESCRIPTION: michael@0: ** Hammer on the file I/O system michael@0: ** INPUTS: A pointer to the thread's private data michael@0: ** OUTPUTS: None michael@0: ** RETURN: None michael@0: ** SIDE EFFECTS: michael@0: ** Creates, accesses and deletes a file michael@0: ** RESTRICTIONS: michael@0: ** (Currently) must have file create permission in "/usr/tmp". michael@0: ** MEMORY: NA michael@0: ** ALGORITHM: michael@0: ** This function is a root of a thread michael@0: ** 1) Creates a (hopefully) unique file in /usr/tmp/ michael@0: ** 2) Writes a zero to a random number of sequential pages michael@0: ** 3) Closes the file michael@0: ** 4) Reopens the file michael@0: ** 5) Seeks to a random page within the file michael@0: ** 6) Writes a one byte on that page michael@0: ** 7) Repeat steps [5..6] for each page in the file michael@0: ** 8) Close and delete the file michael@0: ** 9) Repeat steps [1..8] until told to stop michael@0: ** 10) Notify complete and return michael@0: ***********************************************************************/ michael@0: void Hammer::RootFunction() michael@0: { michael@0: PRUint32 index; michael@0: RCFileIO file; michael@0: char filename[30]; michael@0: const char zero = 0; michael@0: PRStatus rv = PR_SUCCESS; michael@0: michael@0: limit = (Random() % limit) + 1; michael@0: michael@0: (void)sprintf(filename, "%ssg%04p.dat", baseName, this); michael@0: michael@0: if (debug_mode) PR_fprintf(output, "Starting work on %s\n", filename); michael@0: michael@0: while (PR_TRUE) michael@0: { michael@0: PRUint64 bytes; michael@0: PRUint32 minor = (Random() % limit) + 1; michael@0: PRUint32 random = (Random() % limit) + 1; michael@0: PRUint32 pages = (Random() % limit) + 10; michael@0: while (minor-- > 0) michael@0: { michael@0: problem = sg_okay; michael@0: if (action != sg_go) goto finished; michael@0: problem = sg_open; michael@0: rv = file.Open(filename, PR_RDWR|PR_CREATE_FILE, 0666); michael@0: if (PR_FAILURE == rv) goto finished; michael@0: for (index = 0; index < pages; index++) michael@0: { michael@0: problem = sg_okay; michael@0: if (action != sg_go) goto close; michael@0: problem = sg_seek; michael@0: bytes = file.Seek(pageSize * index, RCFileIO::set); michael@0: if (bytes != pageSize * index) goto close; michael@0: problem = sg_write; michael@0: bytes = file.Write(&zero, sizeof(zero)); michael@0: if (bytes <= 0) goto close; michael@0: writes += 1; michael@0: } michael@0: problem = sg_close; michael@0: rv = file.Close(); michael@0: if (rv != PR_SUCCESS) goto purge; michael@0: michael@0: problem = sg_okay; michael@0: if (action != sg_go) goto purge; michael@0: michael@0: problem = sg_open; michael@0: rv = file.Open(filename, PR_RDWR, 0666); michael@0: if (PR_FAILURE == rv) goto finished; michael@0: for (index = 0; index < pages; index++) michael@0: { michael@0: problem = sg_okay; michael@0: if (action != sg_go) goto close; michael@0: problem = sg_seek; michael@0: bytes = file.Seek(pageSize * index, RCFileIO::set); michael@0: if (bytes != pageSize * index) goto close; michael@0: problem = sg_write; michael@0: bytes = file.Write(&zero, sizeof(zero)); michael@0: if (bytes <= 0) goto close; michael@0: writes += 1; michael@0: random = (random + 511) % pages; michael@0: } michael@0: problem = sg_close; michael@0: rv = file.Close(); michael@0: if (rv != PR_SUCCESS) goto purge; michael@0: problem = sg_delete; michael@0: rv = file.Delete(filename); michael@0: if (rv != PR_SUCCESS) goto finished; michael@0: } michael@0: } michael@0: michael@0: close: michael@0: (void)file.Close(); michael@0: purge: michael@0: (void)file.Delete(filename); michael@0: finished: michael@0: RCEnter scope(ml); michael@0: action = HammerData::sg_done; michael@0: cv->Notify(); michael@0: michael@0: if (debug_mode) PR_fprintf(output, "Ending work on %s\n", filename); michael@0: michael@0: return; michael@0: } /* Hammer::RootFunction */ michael@0: michael@0: static Hammer* hammer[100]; michael@0: /*********************************************************************** michael@0: ** PRIVATE FUNCTION: main michael@0: ** DESCRIPTION: michael@0: ** Hammer on the file I/O system michael@0: ** INPUTS: The usual argc and argv michael@0: ** argv[0] - program name (not used) michael@0: ** argv[1] - the number of virtual_procs to execute the major loop michael@0: ** argv[2] - the number of threads to toss into the batch michael@0: ** argv[3] - the clipping number applied to randoms michael@0: ** default values: max_virtual_procs = 2, threads = 10, limit = 57 michael@0: ** OUTPUTS: None michael@0: ** RETURN: None michael@0: ** SIDE EFFECTS: michael@0: ** Creates, accesses and deletes lots of files michael@0: ** RESTRICTIONS: michael@0: ** (Currently) must have file create permission in "/usr/tmp". michael@0: ** MEMORY: NA michael@0: ** ALGORITHM: michael@0: ** 1) Fork a "Thread()" michael@0: ** 2) Wait for 'interleave' seconds michael@0: ** 3) For [0..'threads') repeat [1..2] michael@0: ** 4) Mark all objects to stop michael@0: ** 5) Collect the threads, accumulating the results michael@0: ** 6) For [0..'max_virtual_procs') repeat [1..5] michael@0: ** 7) Print accumulated results and exit michael@0: ** michael@0: ** Characteristic output (from IRIX) michael@0: ** Random File: Using max_virtual_procs = 2, threads = 10, limit = 57 michael@0: ** Random File: [min [avg] max] writes/sec average michael@0: ***********************************************************************/ michael@0: PRIntn main (PRIntn argc, char *argv[]) michael@0: { michael@0: RCLock ml; michael@0: PLOptStatus os; michael@0: RCCondition cv(&ml); michael@0: PRUint32 writesMax = 0, durationTot = 0; michael@0: RCThread::Scope thread_scope = RCThread::local; michael@0: PRUint32 writes, writesMin = 0x7fffffff, writesTot = 0; michael@0: PRIntn active, poll, limit = 0, max_virtual_procs = 0, threads = 0, virtual_procs; michael@0: RCInterval interleave(RCInterval::FromMilliseconds(10000)), duration(0); michael@0: michael@0: const char *where[] = {"okay", "open", "close", "delete", "write", "seek"}; michael@0: michael@0: PLOptState *opt = PL_CreateOptState(argc, argv, "Gdl:t:i:"); michael@0: while (PL_OPT_EOL != (os = PL_GetNextOpt(opt))) michael@0: { michael@0: if (PL_OPT_BAD == os) continue; michael@0: switch (opt->option) michael@0: { michael@0: case 0: michael@0: baseName = opt->value; michael@0: break; michael@0: case 'G': /* global threads */ michael@0: thread_scope = RCThread::global; michael@0: break; michael@0: case 'd': /* debug mode */ michael@0: debug_mode = 1; michael@0: break; michael@0: case 'l': /* limiting number */ michael@0: limit = atoi(opt->value); michael@0: break; michael@0: case 't': /* number of threads */ michael@0: threads = atoi(opt->value); michael@0: break; michael@0: case 'i': /* iteration counter */ michael@0: max_virtual_procs = atoi(opt->value); michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: PL_DestroyOptState(opt); michael@0: output = PR_GetSpecialFD(PR_StandardOutput); michael@0: michael@0: /* main test */ michael@0: michael@0: cv.SetTimeout(interleave); michael@0: michael@0: if (max_virtual_procs == 0) max_virtual_procs = 2; michael@0: if (limit == 0) limit = 57; michael@0: if (threads == 0) threads = 10; michael@0: michael@0: if (debug_mode) PR_fprintf(output, michael@0: "%s: Using %d virtual processors, %d threads, limit = %d and %s threads\n", michael@0: programName, max_virtual_procs, threads, limit, michael@0: (thread_scope == RCThread::local) ? "LOCAL" : "GLOBAL"); michael@0: michael@0: for (virtual_procs = 0; virtual_procs < max_virtual_procs; ++virtual_procs) michael@0: { michael@0: if (debug_mode) michael@0: PR_fprintf(output, michael@0: "%s: Setting number of virtual processors to %d\n", michael@0: programName, virtual_procs + 1); michael@0: RCPrimordialThread::SetVirtualProcessors(virtual_procs + 1); michael@0: for (active = 0; active < threads; active++) michael@0: { michael@0: hammer[active] = new Hammer(thread_scope, &ml, &cv, limit); michael@0: hammer[active]->Start(); /* then make it roll */ michael@0: RCThread::Sleep(interleave); /* start them slowly */ michael@0: } michael@0: michael@0: /* michael@0: * The last thread started has had the opportunity to run for michael@0: * 'interleave' seconds. Now gather them all back in. michael@0: */ michael@0: { michael@0: RCEnter scope(&ml); michael@0: for (poll = 0; poll < threads; poll++) michael@0: { michael@0: if (hammer[poll]->action == HammerData::sg_go) /* don't overwrite done */ michael@0: hammer[poll]->action = HammerData::sg_stop; /* ask him to stop */ michael@0: } michael@0: } michael@0: michael@0: while (active > 0) michael@0: { michael@0: for (poll = 0; poll < threads; poll++) michael@0: { michael@0: ml.Acquire(); michael@0: while (hammer[poll]->action < HammerData::sg_done) cv.Wait(); michael@0: ml.Release(); michael@0: michael@0: if (hammer[poll]->problem == HammerData::sg_okay) michael@0: { michael@0: duration = RCInterval(RCInterval::now) - hammer[poll]->timein; michael@0: writes = hammer[poll]->writes * 1000 / duration; michael@0: if (writes < writesMin) writesMin = writes; michael@0: if (writes > writesMax) writesMax = writes; michael@0: writesTot += hammer[poll]->writes; michael@0: durationTot += duration; michael@0: } michael@0: else michael@0: { michael@0: if (debug_mode) PR_fprintf(output, michael@0: "%s: test failed %s after %ld seconds\n", michael@0: programName, where[hammer[poll]->problem], duration); michael@0: else failed_already=1; michael@0: } michael@0: active -= 1; /* this is another one down */ michael@0: (void)hammer[poll]->Join(); michael@0: hammer[poll] = NULL; michael@0: } michael@0: } michael@0: if (debug_mode) PR_fprintf(output, michael@0: "%s: [%ld [%ld] %ld] writes/sec average\n", michael@0: programName, writesMin, michael@0: writesTot * 1000 / durationTot, writesMax); michael@0: } michael@0: michael@0: failed_already |= (PR_FAILURE == RCPrimordialThread::Cleanup()); michael@0: PR_fprintf(output, "%s\n", (failed_already) ? "FAIL\n" : "PASS\n"); michael@0: return failed_already; michael@0: } /* main */