// ----------------------------------------------------------------------------
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ----------------------------------------------------------------------------

//-----------------------------------------------------------------------
// Simulate the file reading portion of the specweb99 test suite
//    randomly pick files from the content generated by wafgen99
//    read them from disk
//-----------------------------------------------------------------------

using System;
using System.Text;
using System.Collections;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;

#if SINGULARITY
using Microsoft.Singularity;
using Microsoft.Singularity.Directory;
using Microsoft.Singularity.V1.Services;
using Microsoft.Singularity.Io;
using Microsoft.Singularity.Channels;
using Microsoft.SingSharp;
using FileSystem.Utils;
using Singularity.Application.SPECWeb99;
using Microsoft.Contracts;
using Microsoft.Contracts;
using Microsoft.SingSharp.Reflection;
using Microsoft.Singularity.Applications;
using Microsoft.Singularity.Io;
using Microsoft.Singularity.Configuration;
[assembly: Transform(typeof(ApplicationResourceTransform))]

#endif

namespace Singularity.Application.SPECWeb99.WebFiles
{
    class Defaults
    {
        internal const string Root          = "/fs";
        internal const string RelativePath  = "file_set";
        internal const int    BlockSize     = 16384;
        internal const int    Connections   = 10;
        internal const int    RunSeconds    = 10;
        internal const int    WarmUpSeconds = 2;
    };

#if SINGULARITY
    [ConsoleCategory(HelpMessage="Null Disk r/w performance test", DefaultAction=true)]
    internal class Parameters
    {
        [InputEndpoint("data")]
        public readonly TRef<UnicodePipeContract.Exp:READY> Stdin;

        [OutputEndpoint("data")]
        public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;

        [LongParameter( "b", Default=Defaults.BlockSize, HelpMessage="Block Size to be read.")]
        internal long blockSize;

        [LongParameter( "c", Default=Defaults.Connections, HelpMessage="Set number of connections to simulate.")]
        internal long connections;

        [LongParameter( "f", Default=0, HelpMessage="Forced iterations.")]
        internal long forcedIterations;

        [LongParameter( "k", Default=-1, HelpMessage="Restrict files to this class number.")]
        internal long classSizeNum;

        [LongParameter( "r", Default=Defaults.RunSeconds, HelpMessage="Run time in seconds.")]
        internal long runTime;

        [LongParameter( "t", Default=1, HelpMessage="Number of threads to use/")]
        internal long numThreads;

        [LongParameter( "v", Default=0, HelpMessage="Verbosity level")]
        internal long verbose;

        [LongParameter( "w", Default=Defaults.WarmUpSeconds, HelpMessage="Warmup time in seconds.")]
        internal long rampTime;

        [BoolParameter( "x", Default=false, HelpMessage="no read -- just bind")]
        internal bool testMode;

        [StringParameter("directory", Mandatory=true, Position = 0, Default = Defaults.Root, HelpMessage="path to find wafgen99 data")]
        internal string contentPrefix;

        reflective internal Parameters();

        internal int AppMain() {
            return SpecMain.AppMain(this);
          }
    }
#endif
#if !SINGULARITY
    class FileReader
    {
        //const uint FILE_FLAG_NO_BUFFERING = 0x20000000;
        const uint FILE_FLAG_NO_BUFFERING = 0;
        const uint GENERIC_READ = 0x80000000;
        const uint OPEN_EXISTING = 3;
        IntPtr handle;

        [DllImport("kernel32", SetLastError=true)]
        private static extern unsafe IntPtr CreateFile(
            string FileName,                    // file name
            uint DesiredAccess,                 // access mode
            uint ShareMode,                     // share mode
            uint SecurityAttributes,            // Security Attributes
            uint CreationDisposition,           // how to create
            uint FlagsAndAttributes,            // file attributes
            int hTemplateFile                   // handle to template file
            );

        [DllImport("kernel32", SetLastError=true)]
        private static extern unsafe bool ReadFile(
            IntPtr hFile,                       // handle to file
            void* pBuffer,                      // data buffer
            int NumberOfBytesToRead,            // number of bytes to read
            int* pNumberOfBytesRead,            // number of bytes read
            int Overlapped                      // overlapped buffer
            );

        [DllImport("kernel32", SetLastError=true)]
        private static extern unsafe bool CloseHandle(
            IntPtr hObject                      // handle to object
            );

        [DllImport("kernel32", SetLastError=true)]
        private static extern unsafe uint GetFileSize(
            IntPtr hObject,                     // handle to object
            uint *pFileSizeHigh                 // receives high 32-bits of file size.
            );

        public bool Open(string FileName) {
            // open the existing file for reading
            handle = CreateFile(
                FileName,
                GENERIC_READ,
                0,
                0,
                OPEN_EXISTING,
                FILE_FLAG_NO_BUFFERING,
                0);

            if (handle != IntPtr.Zero && handle != ((IntPtr) (-1))) {
                return true;
            }
            else {
                return false;
            }
        }

        public unsafe int Read(byte[] buffer, int index, int count) {
            int n = 0;
            fixed (byte* p = buffer) {
                if (!ReadFile(handle, p + index, count, &n, 0)) {
                    return 0;
                }
            }
            return n;
        }

        public bool Close() {
            // close file handle
            return CloseHandle(handle);
        }

        public unsafe int Size() {
            return (int)GetFileSize(handle, null);
        }
    }
#endif

    public class FileSpec
    {
        public int dirNum, classNum, fileNum;
        public long size;
    }

    public class UrlSet
    {
        public int pos;
        public FileSpec[]/*!*/ urls;

        public UrlSet(int size)
        {
            pos = 0;
            urls = new FileSpec[size];
        }
    }

    public class Client
    {
        //////////////////////////////////
        // Constants
        private const int    FILES_PER_CLASS       = 9;
        private const int    MAX_PATH              = 256;
        private const int    LINESIZE              = 64;
        private const int    MARKER_FREQ           = 4096;
        private const int    BUF_SIZE              = 1024*1024; // 1MB
        private  const int   MAX_DECK              = 1000;
        //////////////////////////////////
        // Pseudo-constants
        private static readonly int[] FILE_ORDER = {4,3,5,2,6,1,7,8,0};
        private static readonly int[] CLASS_BASE_SIZE = {1024, 10240, 102400, 1024000};
        private static readonly double[] CLASS_PROB = {0.35, 0.50, 0.14, 0.01};
        private static readonly int MAX_CLASS_COUNT = CLASS_BASE_SIZE.Length;

        //////////////////////////////////
        // static variables
        public static string pathPrefix   = "";
        public static int [] fileBucket;

        //////////////////////////////////
        // Instance variables
        public Random.RandomState randomState;
        public bool     generatingUrls;
        public Mutex/*!*/   globalLock;
        public Mutex/*!*/   syncLock;
        public UrlSet/*!*/   urlDeck;
        public UrlSet/*!*/   freeDeck;
        public UrlSet/*!*/   spareDeck;
#if SINGULARITY
        public static TRef<DirectoryServiceContract.Imp:Ready> namespaceRootT;
#else
      /// <summary>
      ///  will not work for multiple threads!!!
      /// </summary>
       private static FileReader reader = new FileReader();
#endif
        private long    dirCount     = 0;
        private long    fileCount    = 0;
        private long    repeatCount  = 0;
        private static long    blockSize    = 0;
        private long    totalSize    = 0;
        private long    fileSizeNum  = 0;
        private long    classSizeNum = 0;
        private long    threadNum    = 1;
        private long    verbose      = 0;
        private bool    dumpTable    = false;
        private bool    testMode = false;
        private int     runTime;
        private int     rampTime;
        private int     forcedIterations;

        private LocalState[] workerLocalStates;
        private StringBuilder sb;
        public static WorkerState     currentState;

        Random.ZipfState zipfDir;
        Random.ZipfState zipfFile;
        Random rand;

        public enum WorkerState
        {
            Start = 0,
            Rampup = 1,
            Run = 2,
            Done = 3,
        }

        // per-thread state
        private class LocalState
        {
            public WorkerState State;
            public DateTime StartTime;
            public long bindCount;
            public TimeSpan bindTime;
            public long readCount;
            public TimeSpan readTime;
            public TimeSpan totalTime;
            public long bytesRead;
            public int forcedIterations;

            public LocalState(int forcedIterations)
            {
                bytesRead = 0;
                bindTime = new TimeSpan(0);
                readTime = new TimeSpan(0);
                totalTime = new TimeSpan(0);

                readCount = 0;
                bindCount = 0;
                this.forcedIterations = forcedIterations;
            }
        }

        [NotDelayed]
        public Client(string/*!*/ prefix, long dir,
                      long bSize, long fSize,
                      long threads, long verbose,
                      long cSize, bool test,
                      int rampTime, int runTime,
                      int forcedIterations)
        {
            dirCount        = dir;
            pathPrefix      = prefix;
            fileSizeNum     = fSize;
            threadNum       = threads;
            classSizeNum    = cSize;
            blockSize       = bSize;
            this.verbose    = verbose;
            testMode        = test;
            this.rampTime   = rampTime;
            this.runTime    = runTime;
            this.forcedIterations = forcedIterations;

            globalLock      = new Mutex();
            syncLock        = new Mutex();
            urlDeck         = new UrlSet(MAX_DECK);
            freeDeck        = new UrlSet(MAX_DECK);
            spareDeck       = new UrlSet(MAX_DECK);
            fileBucket      = new int [FILES_PER_CLASS];
            sb              = new StringBuilder(512);

#if SINGULARITY
            namespaceRootT = new TRef<DirectoryServiceContract.Imp:Ready>(
                DirectoryService.NewClientEndpoint());
#else
#endif
            workerLocalStates = new LocalState[threads];

#if SINGULARITY
            base();
#endif // SINGULARITY
        }

        //------------------------------------------------------------------
        // ReadFile
        //  open and read its content of fileName
        //  throw the content away (no copying, no converting etc...)
        //------------------------------------------------------------------

        // TODO: The way this is called is rather bizarre. Fix it.
        // a Client spawns LocalWorker objects that call back to this
        // function. Should be moved into the LoadWorker class

 #if SINGULARITY
        private byte[]/*!*/ in ExHeap ReadFile(string/*!*/ fileName,
                [Claims] byte[]/*!*/ in ExHeap inBuf,
                ref LocalState local
                )
        {
            if (local == null) {
                Console.WriteLine("ReadFile:  local state is null!");
                return inBuf;
            }

            FileContract.Imp conn = null;
            DateTime start;
            DateTime beginTotal;
            TimeSpan btime = new TimeSpan(0);

            beginTotal = DateTime.Now;

            // only perform timing while in the run state

            DirectoryServiceContract.Imp:Ready! ds = ((!)Client.namespaceRootT).Acquire();
            ErrorCode errorCode = ErrorCode.Unknown;
            try {
                if (currentState == WorkerState.Run) {
                    start = DateTime.Now;
                    conn = FileUtils.OpenFile(fileName, ds, out errorCode);
                    btime =  DateTime.Now - start;
                    local.bindTime += btime;
                    local.bindCount++;
                }
                else {
                    conn = FileUtils.OpenFile(fileName, ds, out errorCode);
                }
            }
            finally {
                Client.namespaceRootT.Release(ds);
            }

            if (conn == null) {
                Console.WriteLine("Failed to open file '{0}' code={1}",fileName, SdsUtils.ErrorCodeToString(errorCode));
                throw new Exception(" file missing");
                return inBuf;
            }

            try {
                int error  = 0;
                long filePos  = 0;
                long  bytesRead = 0;

                start = DateTime.Now;

                while (!testMode) {
                    conn.SendRead(inBuf, 0, filePos, inBuf.Length);
                    conn.RecvAckRead(out inBuf, out bytesRead, out error);
                    local.readCount++;
                    if (error != 0) {
                        throw new Exception(" Encountered error while reading " + fileName);
                        break;
                    }
                    if (bytesRead == 0) break;
                    filePos += bytesRead;
                }

                if (currentState == WorkerState.Run) {
                    local.bytesRead += filePos;
                    TimeSpan x =  DateTime.Now - start;
                    local.readTime += x;
                }
            }
            finally {
                delete conn;
            }

            if (currentState == WorkerState.Run) {
                DateTime end = DateTime.Now;
                local.totalTime += end - beginTotal;
            }
            return inBuf;
        }
#endif

        ///
        /// generate a new "deck" of random files
        ///
        public void GenerateFileSpecs()
        {
            FileSpec fs;
            long size, linkCount;
            uint flags;
            double totalProb = 0.0;


            UrlSet/*!*/ fr = freeDeck;
            UrlSet/*!*/ sp = spareDeck;

            //
            // Ensure the spare deck is fully allocated
            //     if there are any elements in the free pool use them
            //     otherwise we need a new allocations
            //
            globalLock.WaitOne();
#if verbose
            Console.WriteLine("gendeck: {0} freed slots spare={1},",
                fr.pos,sp.pos,urlDeck.pos);
            //DebugStub.Break();
#endif
            // fill up the spare deck.  Start with using
            // items from the free deck. If none are available
            // allocate them.

            // NOTE allocation should mostly happen during the initialization phase

            for (int i = 0; i < fr.urls.Length; i++) {
                if (fr.pos == 0) {
                    //if ( sp.urls[i] != null) DebugStub.Break();
                    sp.urls[i] = new FileSpec();
                    if (sp.urls[i] == null) {
                        throw new Exception ("no memory!");
                    }
                    // spit out diagnostics if we are allocating during the run phase
                    if (currentState == WorkerState.Run)  Console.Write(" @{0}",i);
                }
                else {
                    sp.urls[i] = fr.urls[--fr.pos];
                }
            }
            globalLock.ReleaseMutex();

            // Now that we have a spare deck with the needed number of items
            // fill in their values according to spec.

            // The loop immediately below will generate s set of files conforming to the
            // requested probabilities but will not be random.

            for (int classNo = 0; classNo < MAX_CLASS_COUNT; classNo++) {
                totalProb += CLASS_PROB[classNo];
                while (sp.pos < totalProb * sp.urls.Length) {
                    fs = sp.urls[sp.pos++];
                    if (fs == null) {
                        throw new Exception(" spare[" + sp.pos + "] is null!");
                    }
                    fs.classNum = classNo;
                    fs.fileNum  = FILE_ORDER[rand.spec_zipf(zipfFile)];
                    fs.dirNum   = rand.spec_zipf(zipfDir);

                    fs.size = CLASS_BASE_SIZE[fs.classNum] * (fs.fileNum +1) / 10;

                    if (verbose > 4) {
                        sb.Length = 0; //reset the buffer
                        sb.Append(pathPrefix);
                        sb.Append("/dir");
                        sb.Append(fs.dirNum.ToString("d05"));
                        sb.Append("/class");
                        sb.Append(fs.classNum);
                        sb.Append("_");
                        sb.Append(fs.fileNum);
                        Console.WriteLine("size {0} file={1}",fs.size,sb.ToString());
                    }
                }
                if (verbose > 1) {
                    Console.WriteLine("Generating class[{0}] prob={1:f2} urlCount={2}",
                        classNo, CLASS_PROB[classNo], sp.pos);
                }
            }

            // Randomize deck
            for (int i = 0; i < sp.pos; i++) {
                 /*^ assert this.randomState != null; ^*/
                 /*^ assert this.rand != null; ^*/
                int pos = rand.Spec_nrandom(randomState) % (sp.pos - i);
                FileSpec obj  = sp.urls[sp.pos- i - 1];
                sp.urls[sp.pos - i - 1] = sp.urls[pos];
                sp.urls[pos] = obj;
            }
        }

        //------------------------------------------------------------------
        // Generate Load
        //  creates the random list of files to be done every iteration
        //  Reads contents of entire file for every file in the list
        //  This list is generated so we can see if each loop has identical timings
        //------------------------------------------------------------------
        private class LoadWorker
        {
            private Client hostClient;
            private LocalState localState;
            private readonly ArrayList fs;
            private StringBuilder sb;
            private int readSize;
            private Cache cache;
            private int id; // local thread index

            public LoadWorker(Client parent, ref LocalState localState, int readSize, int id)
            {
                hostClient      = parent;
                this.localState = localState;
                sb              = new StringBuilder(512);
                this.readSize   = readSize;
                cache           = new Cache(20, 20 *2);
                this.id         = id;
            }
#if SINGULARITY
            public byte []/*!*/ in ExHeap ProcessUrl(
                                       FileSpec fileSpec,
                                       [Claims] byte []/*!*/ in ExHeap buf) {
#else
            public void ProcessUrl(FileSpec fileSpec, byte[] buf, ref LocalState local ) {
#endif
                if (fileSpec == null) throw new Exception ("Attempting to read from null fileSpec");
#if SINGULARITY
                sb.Length = 0; //reset the buffer
                sb.Append(pathPrefix);
                sb.Append("/dir");
                sb.Append(fileSpec.dirNum.ToString("d05"));
                sb.Append("/class");
                sb.Append(fileSpec.classNum);
                sb.Append("_");
                sb.Append(fileSpec.fileNum);
                string path = sb.ToString();

                if (hostClient.verbose > 2) {
                    Console.WriteLine("reading {0}", path);
                }
                buf = hostClient.ReadFile(path, buf, ref localState);
#else
                sb.Length = 0; //reset the buffer
                sb.Append(pathPrefix);
                sb.Append("\\dir");
                sb.Append(fileSpec.dirNum.ToString("d05"));
                sb.Append("\\class");
                sb.Append(fileSpec.classNum);
                sb.Append("_");
                sb.Append(fileSpec.fileNum);
                string path = sb.ToString();

                int error = 0;
                //Console.Write(".");
                DateTime beginTotal = DateTime.Now;
                if (!reader.Open(path)) {
                    Console.WriteLine("open failed: {0}", path);
                    throw new Exception("open failure: "+path);
                }

                DateTime beginRead = DateTime.Now;
                for (long pos = 0; pos < fileSpec.size;) {
                    int bytes = reader.Read(buf, 0, buf.Length);
                    //Console.Write(".");
                    if (bytes == 0) {
                        Console.WriteLine("bytes read={0}, error={1} pos={2}",
                            bytes, error, pos);
                        break;
                    }
                    pos += bytes;
                    //if  (pos != fileSpec.size) throw new Exception (" wrong size pos=" + pos + " bytes=" + bytes);
                }
                reader.Close();
                if (currentState == WorkerState.Run) {
                    DateTime end = DateTime.Now;
                    local.totalTime += end - beginTotal;
                    local.bindTime += beginRead - beginTotal;
                    local.readTime += end - beginRead ;
                    local.bytesRead += fileSpec.size;
                }
#endif
                Client.fileBucket[fileSpec.fileNum]++;
                path = null;
#if SINGULARITY
                return buf;
#endif
            }

            public void RunLoad()
            {
#if SINGULARITY
                byte [] in ExHeap buf = new[ExHeap] byte[readSize];
#else
                // Allocate one one buffer for all operations
                byte[] buf = new byte [blockSize];
#endif
                long size;

                // if loop we've been told to quit, but run at least the forcedIterations.
                for (int iterations = 0;; iterations++) {
                    if (iterations >= localState.forcedIterations &&
                        localState.State == WorkerState.Done) {
                        break;
                    }

                    FileSpec fs  = cache.GetUrl(hostClient,id);
#if SINGULARITY
                    buf = ProcessUrl(fs, buf);
#else
                    ProcessUrl(fs,buf, ref localState);
#endif
                    cache.FreeUrl(fs);
                }
#if SINGULARITY
                delete buf;
#endif
            }
        }

        public void InitializeGlobalState()
        {
            // the following initialization is taken from  specweb99/load_generate and client.c
            rand = new Random();
            int first_seed = 2;
            randomState = new Random.RandomState(first_seed);
            zipfDir = rand.spec_zipf_setup(randomState, (int)dirCount, 1.0);
            zipfFile = rand.spec_zipf_setup(randomState, FILES_PER_CLASS, 1.0);
            generatingUrls = false;
        }

        public long GatherStats(out long totalFiles)
        {
            long total = 0;
            totalFiles = 0;
            Console.WriteLine("Thread stats:");
            for (int i = 0; i < threadNum; i++) {
                LocalState temp = workerLocalStates[i];
                if (temp != null) {
                    double  t1 = (double)temp.totalTime.Ticks / TimeSpan.TicksPerSecond;
                    double  t2 = (double)temp.bindTime.Ticks / TimeSpan.TicksPerSecond;
                    double  t3 = (double)temp.readTime.Ticks / TimeSpan.TicksPerSecond;
                    string summ = String.Format(
                        " [{0}] Total: {1:f3} bind: {2:f3}, read: {3:f3} bytes: {4}",
                        i, t1, t2, t3, temp.bytesRead);
                    Console.WriteLine(summ);
#if SINGULARITY
                    DebugStub.Print(summ);
#endif // SINGULARITY
                    //Console.WriteLine(" bind count ={0}, read count ={1}",temp.bindCount, temp.readCount);
                   total += temp.bytesRead;
                   totalFiles += temp.bindCount;
                }
            }
            return total;
        }

        ///
        /// Main load generation routine
        /// manages Global state machine  via timers etc.
        public void GenerateLoad()
        {
            ArrayList/*!*/ urlList;
            totalSize = 0;
            double megaBytes;
            InitializeGlobalState();

            //if (testMode) return;
            totalSize *= threadNum;
            //megaBytes = ( (double) (totalSize) / (double) (1024*1024) );

#if SINGULARITY
            long startIrqCount;
            long startSwitchCount;
            long startKernelGcCount;
#endif
            int  startGcCount;
            long startGcMillis;
            long startGcBytes;
            DateTime startTime;

            long endGcMillis;
            long endGcBytes;
            DateTime endTime;
            //DateTime globalStartTime = DateTime.Now;

            // Create all the workers
            LoadWorker[] workers = new LoadWorker[threadNum];
            Thread[] threads = new Thread[threadNum];

            for (int i = 0; i < threadNum; i++) {
                workerLocalStates[i] = new LocalState(forcedIterations);
                workers[i] = new LoadWorker(this, ref workerLocalStates[i],(int) blockSize,i);
            }

            // Create the threads
            for (int i = 0; i < threadNum; i++) {
                LocalState temp = workerLocalStates[i];
                if (temp != null) temp.State = WorkerState.Rampup;
                threads[i] = new Thread(new ThreadStart(workers[i].RunLoad));
            }

            if (rampTime != 0) {
                // ramp-up time isn't part of benchmark.

                Console.WriteLine("Entering Rampup state");
                // Run all the threads
                currentState = WorkerState.Rampup;
                for (int i = 0; i < threadNum; i++) {
                    Thread t = threads[i];
                     /*^ assume t!= null; ^*/
                    t.Start();
                }

                // sleep for some time and change states from Rampup to Run
                Thread.Sleep(rampTime*1000);
                Console.WriteLine("\nEntering Run state");
                globalLock.WaitOne();

                // start perf counting after we have the lock
#if SINGULARITY
                GC.PerformanceCounters(out startGcCount,
                                       out startGcMillis,
                                       out startGcBytes);

                startTime = DateTime.Now;
                startIrqCount = ProcessService.GetKernelInterruptCount();
                startSwitchCount = ProcessService.GetContextSwitchCount();
                startKernelGcCount = ProcessService.GetKernelGcCount();
#else
                startTime = DateTime.Now;
#endif
            }
            else {
                // ramp-up time is part of benchmark.
#if SINGULARITY
                GC.PerformanceCounters(out startGcCount,
                                       out startGcMillis,
                                       out startGcBytes);

                startTime = DateTime.Now;
                startIrqCount = ProcessService.GetKernelInterruptCount();
                startSwitchCount = ProcessService.GetContextSwitchCount();
                startKernelGcCount = ProcessService.GetKernelGcCount();
#else
                startTime = DateTime.Now;
#endif

                Console.WriteLine("Starting threads");
                // Run all the threads
                currentState = WorkerState.Rampup;
                for (int i = 0; i < threadNum; i++) {
                    Thread t = threads[i];
                     /*^ assume t!= null; ^*/
                    t.Start();
                }

                // sleep for some time and change states from Rampup to Run
                Thread.Sleep(rampTime*1000);
                Console.WriteLine("\nEntering Run state");
                globalLock.WaitOne();
            }

            // Workers are ready to run.
            currentState = WorkerState.Run;

            for (int i = 0; i < threadNum; i++) {
                LocalState temp = workerLocalStates[i];
                if (temp != null) temp.State = WorkerState.Run;
            }
            globalLock.ReleaseMutex();


            // sleep for some time and change states from Run to Done
            Thread.Sleep(runTime*1000);
            globalLock.WaitOne();
            currentState = WorkerState.Done;
            for (int i = 0; i < threadNum; i++) {
                LocalState temp = workerLocalStates[i];
                if (temp != null) temp.State = WorkerState.Done;
            }
            globalLock.ReleaseMutex();

#if SINGULARITY
            bool wasActive = Monitoring.isActive();
            if  (wasActive) Console.WriteLine("May want to reset Monitoring state -- was active");
            Monitoring.setActive(false);
            // Wait for all the threads to complete
            for (int i = 0; i < threadNum; i++) {
                Thread t = threads[i];
                 /*^ assume t!= null; ^*/
                t.Join();
            }
#endif

            // REVIEW: Should stats happen before or after thread join?

            long totalFiles;
            long totalBytes = GatherStats(out totalFiles);

            megaBytes = ( (double) (totalBytes) / (double) (1024*1024) );

            int endGcCount;
#if SINGULARITY
            endTime = DateTime.Now;
            GC.PerformanceCounters(out endGcCount,
                                    out endGcMillis,
                                    out endGcBytes);
#else
           endTime = DateTime.Now;
#endif


            TimeSpan elapsed = endTime - startTime;
            double delta = (double)elapsed.Ticks / TimeSpan.TicksPerSecond;
            string summ =
                String.Format(
                    "Class:{0:d} Block:{1:d} Elapsed: {2:f2} MBps: {3:f3}, Files/s: {4:f3}",
                    classSizeNum,
                    blockSize,
                    delta,
                    (double)(megaBytes / delta),
                    (double)(totalFiles / delta));

#if SINGULARITY
            string info =
                String.Format(
                    "[AppGC:- cnt:{0} bytes:{1} Kern:- ints:{2} swi:{3} gcs:{4}]",
                    endGcCount - startGcCount,
                    endGcBytes - startGcBytes,
                    ProcessService.GetKernelInterruptCount() - startIrqCount,
                    ProcessService.GetContextSwitchCount() - startSwitchCount,
                    ProcessService.GetKernelGcCount() - startKernelGcCount);
#else
            string info = "not implemented";
#endif
            if (verbose > 0) {
                Console.WriteLine();
                Console.WriteLine(summ);
                Console.WriteLine(info);
            }


            if (verbose > 1) {
                long ftotal = 0;
                for (int i = 0; i < 9; i++) {
                    ftotal += fileBucket[i];
                }

                Console.WriteLine("Actual Zipf file distribution:");
                for (int i = 0; i < 9; i++) {
                    double foo = (double) (fileBucket[i] * 100) / (double) ftotal;
                    Console.WriteLine("   FileBucket[{0}] = {1:d6}. %={2:f2}",
                        i, fileBucket[i], foo);
                }
            }
#if SINGULARITY
            DebugStub.WriteLine(summ);
            DebugStub.WriteLine(info);
#endif
       }
    } // client

    public class SpecMain
    {
        //--------------------------------------------------
        //--------------------------------------------------
        private static int ConnectionsToDirectories(int conns)
        {
            return ((int) (25 + (((400000.0 / 122000.0) * conns)/5.0)));
        }

        static bool ParseNumber(string/*!*/ arg,
                                string/*!*/ name,
                                out long value)
        {
            // arg should look like "[-][A-z]:[0-9]*"
            if (arg.Length >= 4) {
                try {
                    value = Int64.Parse(arg.Substring(3));
                    return true;
                }
                catch (FormatException) {
                    Console.WriteLine("format ex");
                }
                catch (OverflowException) {
                    Console.WriteLine("overflow ex");
                }
            }
            Console.WriteLine("Could not parse {0}, {1}", name,arg.Substring(3));
            value = 0;
            return false;
        }

        private static void Usage()
        {
            Console.WriteLine("Usage: specweb99 client [options] content-root");
            Console.WriteLine("  randomly read files generated by wafgen99");
            Console.WriteLine("Options:");
            Console.WriteLine(
                "  /b:<n>      Block Size to be read [{0}].",
                Defaults.WarmUpSeconds
                );
            Console.WriteLine(
                "  /c:<n>      Set number of connections to simulate [{0}].",
                Defaults.Connections
                );
            Console.WriteLine("  /k:<n>      Restrict files to this class number.");
            Console.WriteLine("  /f:<n>      Forced iterations [0].");
            Console.WriteLine("  /r:<n>      run time in seconds [{0}].",
                              Defaults.RunSeconds);
            Console.WriteLine("  /t:<n>      Number of threads to use [1].");
            Console.WriteLine("  /x          no read -- just bind");
            Console.WriteLine("  /w:<n>      warmup time in seconds[5.]");
        }

#if SINGULARITY
        internal static int AppMain(Parameters! config)
        {

            long dirCount           = (long) (ConnectionsToDirectories((int)config.connections) -1 ); //zero based
            long blockSize          = config.blockSize;
            long fileSizeNum        = -1;
            long classSizeNum       = config.classSizeNum;
            long verbose            = config.verbose;
            long numThreads         = config.numThreads;
            bool testMode           = config.testMode;
            long runTime            = config.runTime;
            long rampTime           = config.rampTime;
            long forcedIterations   = config.forcedIterations;
            string contentPrefix    = config.contentPrefix + "/" + Defaults.RelativePath;
            if (forcedIterations != 0) {
                rampTime = 0;
                runTime = 0;
            }

            Console.WriteLine(" args: content=" + contentPrefix +" dir=" + dirCount + " block size="+blockSize);
            Console.WriteLine("       verbose="+verbose+" FileSizeNum="+fileSizeNum+" ClassSizeNum="+classSizeNum);
            Console.WriteLine("       threads="+numThreads+" forcedIterations="+forcedIterations);
            //DateTime start = DateTime.Now;

            Client c = new Client(contentPrefix, dirCount,  blockSize, fileSizeNum,
                                  numThreads, verbose, classSizeNum,
                                  testMode, (int) rampTime, (int)runTime,
                                  (int)forcedIterations);

            if (c != null) {
                c.GenerateLoad();
            }

            //TimeSpan elapsed = DateTime.Now - beginRunTime;
            //Console.WriteLine("Elapsed time: "+elapsed);
            return 0;

        }
#else
        //--------------------------------------------------
        // Windows Main routine
        //--------------------------------------------------
        public static int Main(string[]/*!*/ args)
        {
            string contentPrefix = Defaults.Root;
            long dirCount     = (long) (ConnectionsToDirectories(Defaults.Connections) -1); //zero based
            long blockSize    = Defaults.Connections;
            long fileSizeNum  = -1;
            long classSizeNum  = -1;
            long verbose = 1;
            long numThreads = 1;
            bool testMode = false;
            long runTime = Defaults.RunSeconds;
            long rampTime  = Defaults.WarmUpSeconds;
            long forcedIterations = 0;

            // Temporaries for command-line parsing
            bool  needHelp = args.Length == 0;

            int i;
            for (i = 1; i < args.Length; i++) {
                string/*!*/ arg = (string /*!*/) args[i];

                if (arg.Length < 2 || (arg[0] != '-' && arg[0] != '/')) {
                    Console.WriteLine("Invalid argument: {0}", arg);
                    Usage();
                    return -1;
                }

                long temp;
                switch (Char.ToLower(arg[1])) {
                    case '?':
                    case 'h':
                        needHelp = true;
                        break;
                    case 'b':
                        ParseNumber(arg, "block size", out blockSize);
                        break;
                    case 'c':
                        long connections;
                        ParseNumber(arg, "# of connections", out connections);
                        dirCount = (long) (ConnectionsToDirectories((int)connections) -1 ); //zero based

                        break;
                    case 'f':
                        ParseNumber(arg, "# of forced iterations", out forcedIterations);
                        rampTime = 0;
                        runTime = 0;
                        break;
                    case 'k':
                        ParseNumber(arg, "class number (size) to be read", out classSizeNum);
                        break;
                    case 'r':
                        ParseNumber(arg, "Run time", out runTime);
                        break;
                    case 't':
#if SINGULARITY
                        ParseNumber(arg, "number of threads to use", out numThreads);
#else
                        Console.WriteLine("Multi-thread not spported on NT -- ignoring");
#endif
                        break;
                    case 'v':
                        ParseNumber(arg, "verbosity level", out verbose);
                        break;
                    case 'w':
                        ParseNumber(arg, "Warmup time", out rampTime);
                        break;
                    case 'x':
                        testMode = true;
                        break;
                    default:
                        contentPrefix = arg;
                        break;
                }
            } // for args

            contentPrefix += "/" + Defaults.RelativePath;

            if (needHelp) {
                Usage();
                return -1;
            }

            Console.WriteLine(" args: content=" + contentPrefix +" dir=" + dirCount + " block size="+blockSize);
            Console.WriteLine("       verbose="+verbose+" FileSizeNum="+fileSizeNum+" ClassSizeNum="+classSizeNum);
            Console.WriteLine("       threads="+numThreads+" forcedIterations="+forcedIterations);
            //DateTime start = DateTime.Now;

            Client c = new Client(contentPrefix, dirCount,  blockSize, fileSizeNum,
                                  numThreads, verbose, classSizeNum,
                                  testMode, (int) rampTime, (int)runTime,
                                  (int)forcedIterations);

            if (c != null) {
                c.GenerateLoad();
            }

            //TimeSpan elapsed = DateTime.Now - beginRunTime;
            //Console.WriteLine("Elapsed time: "+elapsed);
            return 0;
        } // main
#endif
    } //specmain class
} //namespace
