bool CmdLineParser::_ReadParametersFromCmdLine()

in CmdLineParser/CmdLineParser.cpp [718:1909]


bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch, bool& fXMLProfile)
{
    int nParamCnt = argc - 1;
    const char** args = argv + 1;
    bool fError = false;

    TimeSpan timeSpan;

    //
    // Pass 1 - determine parameter set type: cmdline specification or XML, and preparse targets/blocksize
    //

    ParseState isXMLSet = ParseState::Unknown;

    ParseState isXMLResultFormat = ParseState::Unknown;
    ParseState isProfileOnly = ParseState::Unknown;
    ParseState isVerbose = ParseState::Unknown;
    ParseState isRandomSeed = ParseState::Unknown;
    ParseState isWarmupTime = ParseState::Unknown;
    ParseState isDurationTime = ParseState::Unknown;
    ParseState isCooldownTime = ParseState::Unknown;

    ULONG randomSeedValue = 0;
    ULONG warmupTime = 0;
    ULONG durationTime = 0;
    ULONG cooldownTime = 0;
    const char *xmlProfile = nullptr;

    //
    // Find all target specifications. Note that this assumes all non-target
    // parameters are single tokens; e.g. "-Hsomevalue" and never "-H somevalue".
    // Targets follow parameter specifications.
    //

    vector<Target> vTargets;
    for (int i = 0, inTargets = false; i < nParamCnt; ++i)
    {
        if (!_IsSwitchChar(args[i][0]))
        {
            inTargets = true;

            Target target;
            target.SetPath(args[i]);
            vTargets.push_back(target);
        }
        else if (inTargets)
        {
            fprintf(stderr, "ERROR: parameters (%s) must come before targets on the command line\n", args[i]);
            return false;
        }
    }

    //
    // Find composable and dependent parameters as we resolve the parameter set.
    //

    for (int i = 0; i < nParamCnt; ++i)
    {
        if (_IsSwitchChar(args[i][0]))
        {
            const char *arg = &args[i][2];

            switch(args[i][1])
            {

            case 'b':

                // Block size does not compose with XML profile spec
                if (isXMLSet == ParseState::True)
                {
                    fprintf(stderr, "ERROR: -b is not compatible with -X XML profile specification\n");
                    return false;
                }
                else
                {
                    UINT64 ullBlockSize;
                    if (_GetSizeInBytes(arg, ullBlockSize, nullptr) && ullBlockSize < MAXUINT32)
                    {
                        for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                        {
                            i->SetBlockSizeInBytes((DWORD)ullBlockSize);
                        }
                    }
                    else
                    {
                        fprintf(stderr, "ERROR: invalid block size passed to -b\n");
                        return false;
                    }
                    _dwBlockSize = (DWORD)ullBlockSize;

                    isXMLSet = ParseState::False;
                }
                break;

            case 'C':
                {
                    int c = atoi(arg);
                    if (c >= 0)
                    {
                        cooldownTime = c;
                        isCooldownTime = ParseState::True;
                    }
                    else
                    {
                        fprintf(stderr, "ERROR: invalid cooldown time (-C): '%s'\n", arg);
                        return false;
                    }
                }
                break;

            case 'd':
                {
                    int c = atoi(arg);
                    if (c >= 0)
                    {
                        durationTime = c;
                        isDurationTime = ParseState::True;
                    }
                    else
                    {
                        fprintf(stderr, "ERROR: invalid measured duration time (-d): '%s'\n", arg);
                        return false;
                    }
                }
                break;

            case 'W':
                {
                    int c = atoi(arg);
                    if (c >= 0)
                    {
                        warmupTime = c;
                        isWarmupTime = ParseState::True;
                    }
                    else
                    {
                        fprintf(stderr, "ERROR: invalid warmup time (-W): '%s'\n", arg);
                        return false;
                    }
                }
                break;

            case 'R':

                // re-output profile only (no run)
                if ('p' == *arg)
                {
                    isProfileOnly = ParseState::True;
                    ++arg;
                }

                if ('\0' != *arg)
                {
                    // Explicit results format
                    if (strcmp(arg, "xml") == 0)
                    {
                        isXMLResultFormat = ParseState::True;
                    }
                    else if (strcmp(arg, "text") != 0)
                    {
                        fprintf(stderr, "ERROR: invalid results format (-R): '%s'\n", arg);
                        return false;
                    }
                    else
                    {
                        isXMLResultFormat = ParseState::False;
                    }
                }
                else
                {
                    // allow for -Rp shorthand for default profile-only format
                    if (isProfileOnly != ParseState::True)
                    {
                        fprintf(stderr, "ERROR: unspecified results format -R: use [p]<text|xml>\n");
                        return false;
                    }
                }
                break;

            case 'v':

                isVerbose = ParseState::True;
                break;

            case 'X':

                if (isXMLSet == ParseState::Unknown)
                {
                    isXMLSet = ParseState::True;
                }
                else
                {
                    fprintf(stderr, "ERROR: multiple XML profiles specified (-X)\n");
                    return false;
                }
                xmlProfile = arg;
                break;

            case 'z':
                {
                    char *endPtr = nullptr;

                    if (*arg == '\0')
                    {
                        randomSeedValue = (ULONG) GetTickCount64();
                    }
                    else
                    {
                        randomSeedValue = strtoul(arg, &endPtr, 10);
                        if (*endPtr != '\0')
                        {
                            fprintf(stderr, "ERROR: invalid random seed value '%s' specified - must be a valid 32 bit integer\n", arg);
                            return false;
                        }
                    }

                    isRandomSeed = ParseState::True;
                }
                break;

            default:
                // no other switches are valid in combination with -X
                // if we've seen X, this means it is bad
                // if not, we know it will not be X
                if (isXMLSet == ParseState::True)
                {
                    fprintf(stderr, "ERROR: invalid XML profile specification; parameter %s not compatible with -X\n", args[i]);
                    return false;
                }
                else
                {
                    isXMLSet = ParseState::False;
                }
            }
        }
    }

    // XML profile?
    if (isXMLSet == ParseState::True)
    {
        if (!_ReadParametersFromXmlFile(xmlProfile, pProfile, &vTargets))
        {
            return false;
        }
    }

    //
    // Apply profile common parameters - note that results format is unmodified if R not explicitly provided
    //

    if (isXMLResultFormat == ParseState::True)
    {
        pProfile->SetResultsFormat(ResultsFormat::Xml);
    }
    else if (isXMLResultFormat == ParseState::False)
    {
        pProfile->SetResultsFormat(ResultsFormat::Text);
    }

    if (isProfileOnly == ParseState::True)
    {
        pProfile->SetProfileOnly(true);
    }

    if (isVerbose == ParseState::True)
    {
        pProfile->SetVerbose(true);
    }

    //
    // Apply timespan common composable parameters
    //

    if (isXMLSet == ParseState::True)
    {
        for (auto& ts : const_cast<vector<TimeSpan> &>(pProfile->GetTimeSpans()))
        {
            if (isRandomSeed == ParseState::True)   { ts.SetRandSeed(randomSeedValue); }
            if (isWarmupTime == ParseState::True)   { ts.SetWarmup(warmupTime); }
            if (isDurationTime == ParseState::True) { ts.SetDuration(durationTime); }
            if (isCooldownTime == ParseState::True) { ts.SetCooldown(cooldownTime); }
        }
    }
    else
    {
        if (isRandomSeed == ParseState::True)   { timeSpan.SetRandSeed(randomSeedValue); }
        if (isWarmupTime == ParseState::True)   { timeSpan.SetWarmup(warmupTime); }
        if (isDurationTime == ParseState::True) { timeSpan.SetDuration(durationTime); }
        if (isCooldownTime == ParseState::True) { timeSpan.SetCooldown(cooldownTime); }
    }

    // Now done if XML profile
    if (isXMLSet == ParseState::True)
    {
        fXMLProfile = true;
        return true;
    }

    //
    // Parse full command line for profile
    //

    // initial parse for cache/writethrough
    // these are built up across the entire cmd line and applied at the end.
    // this allows for conflicts to be thrown for mixed -h/-S as needed.
    TargetCacheMode t = TargetCacheMode::Undefined;
    WriteThroughMode w = WriteThroughMode::Undefined;
    MemoryMappedIoMode m = MemoryMappedIoMode::Undefined;
    MemoryMappedIoFlushMode f = MemoryMappedIoFlushMode::Undefined;

    bool bExit = false;
    while (nParamCnt)
    {
        const char* arg = *args;
        const char* const carg = arg;     // save for error reporting, arg is modified during parse

        // Targets follow parameters on command line. If this is a target, we are done now.
        if (!_IsSwitchChar(*arg))
        {
            break;
        }

        // skip switch character, provide length
        ++arg;
        const size_t argLen = strlen(arg);

        switch (*arg)
        {
        case '?':
            _DisplayUsageInfo(argv[0]);
            exit(0);

        case 'a':    //affinity
            //-a1,2,3,4 (assign threads to cpus 1,2,3,4 (round robin))
            if (!_ParseAffinity(arg, &timeSpan))
            {
                fError = true;
            }
            break;

        case 'b':    //block size
            // nop - block size has been taken care of before the loop
            break;

        case 'B':    //base file offset (offset from the beginning of the file)
            if (*(arg + 1) != '\0')
            {
                UINT64 cb;
                if (_GetSizeInBytes(arg + 1, cb, nullptr))
                {
                    for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                    {
                        i->SetBaseFileOffsetInBytes(cb);
                    }
                }
                else
                {
                    fprintf(stderr, "ERROR: invalid base file offset passed to -B\n");
                    fError = true;
                }
            }
            else
            {
                fError = true;
            }
            break;

        case 'c':    //create file of the given size
            if (*(arg + 1) != '\0')
            {
                UINT64 cb;
                if (_GetSizeInBytes(arg + 1, cb, nullptr))
                {
                    for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                    {
                        i->SetFileSize(cb);
                        i->SetCreateFile(true);
                    }
                }
                else
                {
                    fprintf(stderr, "ERROR: invalid file size passed to -c\n");
                    fError = true;
                }
            }
            else
            {
                fError = true;
            }
            break;

        case 'C':    //cool down time - pass 1 composable
            break;

        case 'd':    //duration - pass 1 composable
            break;

        case 'D':    //standard deviation
            {
                timeSpan.SetCalculateIopsStdDev(true);

                int x = atoi(arg + 1);
                if (x > 0)
                {
                    timeSpan.SetIoBucketDurationInMilliseconds(x);
                }
            }
            break;

        case 'e':    //etw
            if (!_ParseETWParameter(arg, pProfile))
            {
                fError = true;
            }
            break;

        case 'f':
            if (isdigit(*(arg + 1)))
            {
                UINT64 cb;
                if (_GetSizeInBytes(arg + 1, cb, nullptr))
                {
                    for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                    {
                        i->SetMaxFileSize(cb);
                    }
                }
                else
                {
                    fprintf(stderr, "ERROR: invalid max file size passed to -f\n");
                    fError = true;
                }
            }
            else
            {
                if ('\0' == *(arg + 1))
                {
                    fError = true;
                }
                else
                {
                    // while -frs (or -fsr) are generally conflicting intentions as far as
                    // the OS is concerned, do not enforce
                    while (*(++arg) != '\0')
                    {
                        switch (*arg)
                        {
                        case 'r':
                            for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                            {
                                i->SetRandomAccessHint(true);
                            }
                            break;
                        case 's':
                            for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                            {
                                i->SetSequentialScanHint(true);
                            }
                            break;
                        case 't':
                            for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                            {
                                i->SetTemporaryFileHint(true);
                            }
                            break;
                        default:
                            fError = true;
                            break;
                        }
                    }
                }
            }
            break;

        case 'F':    //total number of threads
            {
                int c = atoi(arg + 1);
                if (c > 0)
                {
                    timeSpan.SetThreadCount(c);
                }
                else
                {
                    fError = true;
                }
            }
            break;

        case 'g':    //throughput in bytes per millisecond (gNNN) OR iops (gNNNi)
            {
                // units?
                bool isBpms = false;
                if (isdigit(arg[argLen - 1]))
                {
                    isBpms = true;
                }
                else if (arg[argLen - 1] != 'i')
                {
                    // not IOPS, so its bad
                    fError = true;
                }

                if (!fError)
                {
                    int c = atoi(arg + 1);
                    if (c > 0)
                    {
                        for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                        {
                            if (isBpms)
                            {
                                i->SetThroughput(c);
                            }
                            else
                            {
                                i->SetThroughputIOPS(c);
                            }
                        }
                    }
                    else
                    {
                        fError = true;
                    }
                }
            }
            break;

        case 'h':    // compat: disable os cache and set writethrough; now equivalent to -Sh
            if (t == TargetCacheMode::Undefined &&
                w == WriteThroughMode::Undefined)
            {
                t = TargetCacheMode::DisableOSCache;
                w = WriteThroughMode::On;
            }
            else
            {
                fprintf(stderr, "ERROR: -h conflicts with earlier specification of cache/writethrough\n");
                fError = true;
            }
            break;

        case 'i':    //number of IOs to issue before think time
            {
                int c = atoi(arg + 1);
                if (c > 0)
                {
                    for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                    {
                        i->SetBurstSize(c);
                        i->SetUseBurstSize(true);
                    }
                }
                else
                {
                    fError = true;
                }
            }
            break;

        case 'j':    //time to wait between bursts of IOs
            {
                int c = atoi(arg + 1);
                if (c > 0)
                {
                    for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                    {
                        i->SetThinkTime(c);
                        i->SetEnableThinkTime(true);
                    }
                }
                else
                {
                    fError = true;
                }
            }
            break;

        case 'I':   //io priority
            {
                int x = atoi(arg + 1);
                if (x > 0 && x < 4)
                {
                    PRIORITY_HINT hint[] = { IoPriorityHintVeryLow, IoPriorityHintLow, IoPriorityHintNormal };
                    for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                    {
                        i->SetIOPriorityHint(hint[x - 1]);
                    }
                }
                else
                {
                    fError = true;
                }
            }
            break;

        case 'l':    //large pages
            for (auto i = vTargets.begin(); i != vTargets.end(); i++)
            {
                i->SetUseLargePages(true);
            }
            break;

        case 'L':    //measure latency
            timeSpan.SetMeasureLatency(true);
            break;

        case 'n':    //disable affinity (by default simple affinity is turned on)
            timeSpan.SetDisableAffinity(true);
            break;

        case 'N':
            if (!_ParseFlushParameter(arg, &f))
            {
                fError = true;
            }
            break;

        case 'o':    //request count (1==synchronous)
            {
                int c = atoi(arg + 1);
                if (c > 0)
                {
                    for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                    {
                        i->SetRequestCount(c);
                    }
                }
                else
                {
                    fError = true;
                }
            }
            break;

        case 'O':   //total number of IOs/thread - for use with -F
            {
                int c = atoi(arg + 1);
                if (c > 0)
                {
                    timeSpan.SetRequestCount(c);
                }
                else
                {
                    fError = true;
                }
            }
            break;

        case 'p':    //start async IO operations with the same offset
            //makes sense only for -o2 and greater
            for (auto i = vTargets.begin(); i != vTargets.end(); i++)
            {
                i->SetUseParallelAsyncIO(true);
            }
            break;

        case 'P':    //show progress every x IO operations
            {
                int c = atoi(arg + 1);
                if (c < 1)
                {
                    c = 65536;
                }
                pProfile->SetProgress(c);
            }
            break;

        case 'r':    //random access
            {
                // mixed random/sequential pct split?
                if (*(arg + 1) == 's')
                {
                    int c = 0;

                    ++arg;
                    if (*(arg + 1) == '\0')
                    {
                        fprintf(stderr, "ERROR: no random percentage passed to -rs\n");
                        fError = true;
                    }
                    else
                    {
                        c = atoi(arg + 1);
                        if (c <= 0 || c > 100)
                        {
                            fprintf(stderr, "ERROR: random percentage passed to -rs should be between 1 and 100\n");
                            fError = true;
                        }
                    }
                    if (!fError)
                    {
                        for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                        {
                            // if random ratio is unset and actual alignment is already specified,
                            // -s was used: don't allow this for clarity of intent
                            if (!i->GetRandomRatio() &&
                                i->GetBlockAlignmentInBytes(true))
                            {
                                fprintf(stderr, "ERROR: use -r to specify IO alignment when using mixed random/sequential IO (-rs)\n");
                                fError = true;
                                break;
                            }
                            // if random ratio was already set to something other than 100% (-r)
                            // then -rs was specified multiple times: catch and block this
                            if (i->GetRandomRatio() &&
                                i->GetRandomRatio() != 100)
                            {
                                fprintf(stderr, "ERROR: mixed random/sequential IO (-rs) specified multiple times\n");
                                fError = true;
                                break;
                            }
                            // Note that -rs100 is the same as -r. It will not result in the <RandomRatio> element
                            // in the XML profile; we will still only emit/accept 1-99 there.
                            //
                            // Saying -rs0 (sequential) would create an ambiguity between that and -r[nnn]. Rather
                            // than bend the intepretation of -r[nnn] for the special case of -rs0 we will error
                            // it out in the bounds check above.
                            i->SetRandomRatio(c);
                        }
                    }
                }

                // random distribution

                else if (*(arg + 1) == 'd')
                {
                    // advance past the d
                    arg += 2;

                    fError = !_ParseRandomDistribution(arg, vTargets);
                }

                // random block alignment
                // if mixed random/sequential not already specified, set to 100%
                else
                {

                    UINT64 cb = _dwBlockSize;
                    if (*(arg + 1) != '\0')
                    {
                        if (!_GetSizeInBytes(arg + 1, cb, nullptr) || (cb == 0))
                        {
                            fprintf(stderr, "ERROR: invalid alignment passed to -r\n");
                            fError = true;
                        }
                    }
                    if (!fError)
                    {
                        for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                        {
                            // Do not override -rs specification
                            if (!i->GetRandomRatio())
                            {
                                i->SetRandomRatio(100);
                            }
                            // Multiple -rNN?
                            // Note that -rs100 -r[NN] will pass since -rs does not set alignment.
                            // We are only validating a single -rNN specification.
                            else if (i->GetRandomRatio() == 100 &&
                                     i->GetBlockAlignmentInBytes(true))
                            {
                                fprintf(stderr, "ERROR: random IO (-r) specified multiple times\n");
                                fError = true;
                                break;
                            }
                            // -s already set the alignment?
                            if (i->GetBlockAlignmentInBytes(true))
                            {
                                fprintf(stderr, "ERROR: sequential IO (-s) conflicts with random IO (-r/-rs)\n");
                                fError = true;
                                break;
                            }
                            i->SetBlockAlignmentInBytes(cb);
                        }
                    }
                }
            }
            break;

        case 'R':   // output profile/results format engine - handled in pass 1
            break;

        case 's':    //stride size
            {
                int idx = 1;

                if ('i' == *(arg + idx))
                {
                    // do interlocked sequential mode
                    // ISSUE-REVIEW: this does nothing if -r is specified
                    // ISSUE-REVIEW: this does nothing if -p is specified
                    // ISSUE-REVIEW: this does nothing if we are single-threaded
                    for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                    {
                        i->SetUseInterlockedSequential(true);
                    }

                    idx++;
                }

                for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                {
                    // conflict -s with -rs/-s
                    if (i->GetRandomRatio())
                    {
                        if (i->GetRandomRatio() == 100) {
                            fprintf(stderr, "ERROR: sequential IO (-s) conflicts with random IO (-r/-rs)\n");
                        }
                        else
                        {
                            fprintf(stderr, "ERROR: use -r to specify IO alignment for -rs\n");
                        }
                        fError = true;
                        break;
                    }

                    // conflict with multiple -s
                    if (i->GetBlockAlignmentInBytes(true))
                    {
                        fprintf(stderr, "ERROR: sequential IO (-s) specified multiple times\n");
                        fError = true;
                        break;
                    }
                }

                if (*(arg + idx) != '\0')
                {
                    UINT64 cb;
                    // Note that we allow -s0, as unusual as that would be.
                    // The counter-case of -r0 is invalid and checked for.
                    if (_GetSizeInBytes(arg + idx, cb, nullptr))
                    {
                        for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                        {
                            i->SetBlockAlignmentInBytes(cb);
                        }
                    }
                    else
                    {
                        fprintf(stderr, "ERROR: invalid stride size passed to -s\n");
                        fError = true;
                    }
                }
                else
                {
                    // explicitly pass through the block size so that we can detect
                    // -rs/-s intent conflicts when attempting to set -rs
                    for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                    {
                        i->SetBlockAlignmentInBytes(i->GetBlockSizeInBytes());
                    }
                }
            }
            break;

        case 'S':   //control os/hw/remote caching and writethrough
            {
                // parse flags - it is an error to multiply specify either property, which
                //   can be detected simply by checking if we move one from !undefined.
                //   this also handles conflict cases.
                int idx;
                for (idx = 1; !fError && *(arg + idx) != '\0'; idx++)
                {
                    switch (*(arg + idx))
                    {
                    case 'b':
                        if (t == TargetCacheMode::Undefined)
                        {
                            t = TargetCacheMode::Cached;
                        }
                        else
                        {
                            fprintf(stderr, "ERROR: -Sb conflicts with earlier specification of cache mode\n");
                            fError = true;
                        }
                        break;
                    case 'h':
                        if (t == TargetCacheMode::Undefined &&
                            w == WriteThroughMode::Undefined &&
                            m == MemoryMappedIoMode::Undefined)
                        {
                            t = TargetCacheMode::DisableOSCache;
                            w = WriteThroughMode::On;
                        }
                        else
                        {
                            fprintf(stderr, "ERROR: -Sh conflicts with earlier specification of cache/writethrough/memory mapped\n");
                            fError = true;
                        }
                        break;
                    case 'm':
                        if (m == MemoryMappedIoMode::Undefined &&
                            t != TargetCacheMode::DisableOSCache)
                        {
                            m = MemoryMappedIoMode::On;
                        }
                        else
                        {
                            fprintf(stderr, "ERROR: -Sm conflicts with earlier specification of memory mapped IO/unbuffered IO\n");
                            fError = true;
                        }
                        break;
                    case 'r':
                        if (t == TargetCacheMode::Undefined)
                        {
                            t = TargetCacheMode::DisableLocalCache;
                        }
                        else
                        {
                            fprintf(stderr, "ERROR: -Sr conflicts with earlier specification of cache mode\n");
                            fError = true;
                        }
                        break;
                    case 'u':
                        if (t == TargetCacheMode::Undefined &&
                            m == MemoryMappedIoMode::Undefined)
                        {
                            t = TargetCacheMode::DisableOSCache;
                        }
                        else
                        {
                            fprintf(stderr, "ERROR: -Su conflicts with earlier specification of cache mode/memory mapped IO\n");
                            fError = true;
                        }
                        break;
                    case 'w':
                        if (w == WriteThroughMode::Undefined)
                        {
                            w = WriteThroughMode::On;
                        }
                        else
                        {
                            fprintf(stderr, "ERROR -Sw conflicts with earlier specification of write through\n");
                            fError = true;
                        }
                        break;
                    default:
                        fprintf(stderr, "ERROR: unrecognized option provided to -S\n");
                        fError = true;
                        break;
                    }
                }

                // bare -S, parse loop did not advance
                if (!fError && idx == 1)
                {
                    if (t == TargetCacheMode::Undefined &&
                        m == MemoryMappedIoMode::Undefined)
                    {
                        t = TargetCacheMode::DisableOSCache;
                    }
                    else
                    {
                        fprintf(stderr, "ERROR: -S conflicts with earlier specification of cache mode\n");
                        fError = true;
                    }
                }
            }
            break;

        case 't':    //number of threads per file
            {
                int c = atoi(arg + 1);
                if (c > 0)
                {
                    for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                    {
                        i->SetThreadsPerFile(c);
                    }
                }
                else
                {
                    fError = true;
                }
            }
            break;

        case 'T':    //offsets between threads reading the same file
            {
                UINT64 cb;
                if (_GetSizeInBytes(arg + 1, cb, nullptr) && (cb > 0))
                {
                    for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                    {
                        i->SetThreadStrideInBytes(cb);
                    }
                }
                else
                {
                    fprintf(stderr, "ERROR: invalid offset passed to -T\n");
                    fError = true;
                }
            }
            break;

        case 'v':    //verbose mode - handled in pass 1
            break;

        case 'w':    //write test [default=read]
            {
                int c = 0;

                if (*(arg + 1) == '\0')
                {
                    fprintf(stderr, "ERROR: no write ratio passed to -w\n");
                    fError = true;
                }
                else
                {
                    c = atoi(arg + 1);
                    if (c < 0 || c > 100)
                    {
                        fprintf(stderr, "ERROR: write ratio passed to -w must be between 0 and 100 (percent)\n");
                        fError = true;
                    }
                }
                if (!fError)
                {
                    for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                    {
                        i->SetWriteRatio(c);
                    }
                }
            }
            break;

        case 'W':    //warm up time - pass 1 composable
            break;

        case 'x':    //completion routines
            timeSpan.SetCompletionRoutines(true);
            break;

        case 'y':    //external synchronization
            switch (*(arg + 1))
            {

            case 's':
                _hEventStarted = CreateEvent(NULL, TRUE, FALSE, arg + 2);
                if (NULL == _hEventStarted)
                {
                    fprintf(stderr, "Error creating/opening start notification event: '%s'\n", arg + 2);
                    exit(1);    // TODO: this class shouldn't terminate the process
                }
                break;

            case 'f':
                _hEventFinished = CreateEvent(NULL, TRUE, FALSE, arg + 2);
                if (NULL == _hEventFinished)
                {
                    fprintf(stderr, "Error creating/opening finish notification event: '%s'\n", arg + 2);
                    exit(1);    // TODO: this class shouldn't terminate the process
                }
                break;

            case 'r':
                synch->hStartEvent = CreateEvent(NULL, TRUE, FALSE, arg + 2);
                if (NULL == synch->hStartEvent)
                {
                    fprintf(stderr, "Error creating/opening wait-for-start event: '%s'\n", arg + 2);
                    exit(1);    // TODO: this class shouldn't terminate the process
                }
                break;

            case 'p':
                synch->hStopEvent = CreateEvent(NULL, TRUE, FALSE, arg + 2);
                if (NULL == synch->hStopEvent)
                {
                    fprintf(stderr, "Error creating/opening force-stop event: '%s'\n", arg + 2);
                    exit(1);    // TODO: this class shouldn't terminate the process
                }
                break;

            case 'e':
                {
                    HANDLE hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, arg + 2);
                    if (NULL == hEvent)
                    {
                        fprintf(stderr, "Error opening event '%s'\n", arg + 2);
                        exit(1);    // TODO: this class shouldn't terminate the process
                    }
                    if (!SetEvent(hEvent))
                    {
                        fprintf(stderr, "Error setting event '%s'\n", arg + 2);
                        exit(1);    // TODO: this class shouldn't terminate the process
                    }
                    CloseHandle(hEvent);
                    printf("Succesfully set event: '%s'\n", arg + 2);
                    bExit = true;
                    break;
                }

            default:
                fError = true;
            }

        case 'z':    //random seed - pass 1 composable
            break;

        case 'Z':    //zero write buffers
            if (*(arg + 1) == '\0')
            {
                for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                {
                    i->SetZeroWriteBuffers(true);
                }
            }
            else if (*(arg + 1) == 'r' && *(arg + 2) == '\0')
            {
                timeSpan.SetRandomWriteData(true);
            }
            else
            {
                UINT64 cb = 0;
                string sPath;
                if (_GetRandomDataWriteBufferData(string(arg + 1), cb, sPath) && (cb > 0))
                {
                    for (auto i = vTargets.begin(); i != vTargets.end(); i++)
                    {
                        i->SetRandomDataWriteBufferSize(cb);
                        i->SetRandomDataWriteBufferSourcePath(sPath);
                    }
                }
                else
                {
                    fprintf(stderr, "ERROR: invalid size passed to -Z\n");
                    fError = true;
                }
            }
            break;

        default:
            fprintf(stderr, "ERROR: invalid option: '%s'\n", carg);
            return false;
        }

        if (fError)
        {
            // note: original pointer to the cmdline argument, without parse movement
            fprintf(stderr, "ERROR: incorrectly provided option: '%s'\n", carg);
            return false;
        }

        --nParamCnt;
        ++args;
    }

    //
    // exit if a user specified an action which was already satisfied and doesn't require running test
    //
    if (bExit)
    {
        printf("Now exiting...\n");
        exit(1);                        // TODO: this class shouldn't terminate the process
    }

    if (vTargets.size() < 1)
    {
        fprintf(stderr, "ERROR: need to provide at least one filename\n");
        return false;
    }

    // apply resultant cache/writethrough/memory mapped io modes to the targets
    for (auto i = vTargets.begin(); i != vTargets.end(); i++)
    {
        if (t != TargetCacheMode::Undefined)
        {
            i->SetCacheMode(t);
        }
        if (w != WriteThroughMode::Undefined)
        {
            i->SetWriteThroughMode(w);
        }
        if (m != MemoryMappedIoMode::Undefined)
        {
            i->SetMemoryMappedIoMode(m);
        }
        if (f != MemoryMappedIoFlushMode::Undefined)
        {
            i->SetMemoryMappedIoFlushMode(f);
        }
    }

    // ... and apply targets to the timespan
    for (auto i = vTargets.begin(); i != vTargets.end(); i++)
    {
        timeSpan.AddTarget(*i);
    }
    pProfile->AddTimeSpan(timeSpan);

    return true;
}