//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 2000
//
//  File:       buildtimes.js
//
//  Contents:   
//              
//              
//
//----------------------------------------------------------------------------


//
// Globals
//

var g_reDate           = new RegExp("[a-zA-Z0-9_-]+\\t(\\d{4})/(\\d{2})/(\\d{2}):(\\d{2}):(\\d{2}):(\\d{2})");
var g_reStartBuildExe  = new RegExp("\\(([a-zA-Z]+)\\) Build for [a-zA-Z]+ started. PID=(\\d+)");
var g_rePassComplete   = new RegExp("\\(([a-zA-Z]+)\\) pass (\\d) complete on pid (\\d+)");
var g_reTaskStep       = new RegExp("\\(([a-zA-Z]+)\\) TASK STEPPING ");
var g_reProcessExit    = new RegExp("\\(([a-zA-Z]+)\\) Process id (\\d+) exited");
var g_reStartPost      = new RegExp("\\(([a-zA-Z]+)\\) postbuild task thread for Root started.");
var g_reExitPost       = new RegExp("\\(([a-zA-Z]+)\\) postbuild for Root completed.");
var g_reStartBuild     = new RegExp("STARTING PASS 0 of build.");
var g_reExitBuild      = new RegExp("WAIT FOR NEXTPASS 2 of waitbuild");

var g_reStartCFPBTS    = new RegExp("Received copyfilesfrompostbuildtoslave command from remote machine");
var g_reStartCFTPB     = new RegExp("CopyFilesToPostBuild\\(([a-zA-Z]+)\\).*There are (\\d+) files");
var g_reLastCopyCFTPB  = new RegExp("RoboCopyCopyFile\\(([a-zA-Z]+)\\)");
var g_reEndCFTPB       = new RegExp("(Received) nextpass command from remote machine.");


var g_reBuildType      = new RegExp("razzle.cmd");

// NTAXP01	2000/04/13:17:50:01	taskBuildFiles(strSDRoot)	(Root) Build for Root started. PID=548
// NTAXP01	2000/04/13:17:56:20	ParseBuildMessages(pid)	(Root) pass 0 complete on pid 548
// NTAXP01	2000/04/13:18:45:20	ParseBuildMessages(pid)	(Root) pass 1 complete on pid 548
// NTAXP01	2000/04/13:21:59:11	ParseBuildMessages(pid)	(Root) pass 2 complete on pid 548
// NTAXP01	2000/04/13:21:59:12	ParseBuildMessages(pid)	(Root) Process id 548 exited!
// NTAXP01	2000/04/13:18:44:53	taskBuildFiles(strSDRoot)	(Root) TASK STEPPING Root
// AXP64FRE	2000/04/13:17:49:27	MyRunLocalCommand(strCmd)	(Root) RunLocalCommand('cmd /c d:\nt\Tools\razzle.cmd win64 free officialbuild no_certcheck no_sdrefresh no_title & sdinit && sd -s sync  2>&1', 'd:\nt\.', 'Sync Root', 'true', 'true', 'false')

// NTBLD04	2000/04/25:14:20:20	mtscript_js::OnRemoteExec(cmd)	Received copyfilesfrompostbuildtoslave command from remote machine.
// NTBLD04	2000/04/25:14:19:55	CopyFilesToPostBuild(aPublishedEnlistments)	    (#0) There are 3 files
// NTBLD04	2000/04/25:14:19:55	RoboCopyCopyFile(srcdir)	RoboCopy file d:\nt\public\sdk\lib\i386\AutoDiscovery.tlb to \\NTBLD03\d$\nt\Public\sdk\lib\i386\AutoDiscovery.tlb
// NTBLD04	2000/04/25:14:20:05	mtscript_js::OnRemoteExec(cmd)	Received nextpass command from remote machine.

var g_aMatches =
[
    { re: g_reStartBuildExe, fn: NewDepot},
    { re: g_rePassComplete , fn: PassComplete},
    { re: g_reTaskStep     , fn: TaskStep},
    { re: g_reProcessExit  , fn: ProcessExit},
    { re: g_reStartPost    , fn: PostStart},
    { re: g_reExitPost     , fn: PostExit},
    { re: g_reStartBuild   , fn: ElapsedStart},
    { re: g_reExitBuild    , fn: ElapsedExit},
    { re: g_reStartCFPBTS  , fn: StartCFPBTS},
    { re: g_reStartCFTPB   , fn: StartCFTPB},
    { re: g_reLastCopyCFTPB, fn: LastFileCFTPB},
    { re: g_reEndCFTPB     , fn: EndCFTPB}
];

var g_strBuildType;
var g_strMachine;
var g_aStrFileNames = new Array();
var g_FSObj         = new ActiveXObject("Scripting.FileSystemObject");
var g_hDepots       = new Object;
var g_hTimers       = new Object;
var g_fMerged       = true; // Merge "root" and "mergedcomponents" report
var g_fPrintElapsed = false;
var g_fPrintPost    = false;
var g_CFObj;
// Add new methods...
Error.prototype.toString = Error_ToString;
//
// First, parse command line arguments
//


ParseArguments(WScript.Arguments);
main();
WScript.Quit(0);

function ParseArguments(Arguments)
{
    var strArg;
    var chArg0;
    var chArg1;
    var argn;

    for(argn = 0; argn < Arguments.length; argn++)
    {
        strArg = Arguments(argn);
        chArg0 = strArg.charAt(0);
        chArg1 = strArg.toLowerCase().slice(1);

        if (chArg0 != '-' && chArg0 != '/')
        {
            g_aStrFileNames[g_aStrFileNames.length] = Arguments(argn);
        }
        else
        {
            switch(chArg1)
            {
                case 'm':
                    g_fMerged = true;
                    break;
                case 's':
                    g_fMerged = false;
                    break;
                case 'e':
                    g_fPrintElapsed = true;
                    break;
                case 'p':
                    g_fPrintPost = true;
                    break;
                case '?':
                    LogMsg("HELP");
                    Usage();
                    break;
                default:
                    LogMsg("HELP " + strArg);
                    Usage();
                    break;
            }
        }
    }
    if (g_aStrFileNames.length < 1)
        Usage();
}

// PadDigits(n, cDigits)
// Return a string representation of the given
// number. Leading zeros are added to make the
// string cDigits long.
function PadDigits(n, cDigits)
{
    var strDigits = '';
    var i;
    var strNumber = n.toString();

    for(i = 0; i < cDigits - strNumber.length; ++i)
        strDigits += '0';

    return strDigits + n;
}

// PadString(str, cDigits)
function PadString(str, cDigits)
{
    var strDigits = '';
    var i;

    for(i = 0; i < cDigits - str.length; ++i)
        strDigits += ' ';

    return str + strDigits;
}

function Error_ToString()
{
    var i;
    var str = 'Exception(';

    /*
        Only some error messages get filled in for "ex".
        Specifically the text for disk full never seems
        to get set by functions such as CreateTextFile().


     */
    if (this.number != null && this.description == "")
    {
        switch(this.number)
        {
            case -2147024784:
                this.description = "There is not enough space on the disk.";
                break;
            case -2147024894:
                this.description = "The system cannot find the file specified.";
                break;
            case -2147023585:
                this.description = "There are currently no logon servers available to service the logon request.";
                break;
            case -2147023170:
                this.description = "The remote procedure call failed.";
                break;
            case -2147024837:
                this.description = "An unexpected network error occurred";
                break;
            default:
                this.description = "Error text not set for (" + this.number + ")";
                break;
        }
    }
    for(i in this)
    {
        str += i + ": " + this[i] + " ";
    }
    return str + ")";
}

function LogMsg(msg)
{
    WScript.Echo(msg);
}

function Usage()
{
    LogMsg('Usage: buildtimes [-s] [-e] [-p] path\\filename*.log');
    LogMsg('       -s   Do not merge build time of "root" and "mergedcomponents"');
    LogMsg('       -e   Print total elasped build time');
    LogMsg('       -p   Print postbuild time');
    WScript.Quit(0);
}

//////////////////////////////////////
//////////////////////////////////////
// The DEPOT object
//////////////////////////////////////
//////////////////////////////////////
function Depot(strDepotName, nDepotPID)
{
    this.strName  = strDepotName;
    this.nPID     = nDepotPID;
    this.nElapsed = 0;
    this.nLast    = 0; // used for postbuild

    Depot.prototype.Begin       = Depot_Begin;
    Depot.prototype.SetLast     = Depot_SetLast;
    Depot.prototype.End         = Depot_End;
    Depot.prototype.End2        = Depot_End2;
    Depot.prototype.Terminate   = Depot_Terminate;
    Depot.prototype.GetStrStatus = Depot_GetStrStatus;
}

function Depot_Begin(dateVal)
{
    if (this.bBegin)
        throw new Error(-1, "Multiple begins without end on depot " + this.strName);
    this.nBegin = dateVal;
}

function Depot_SetLast(dateVal)
{
    if (!this.nBegin)
        throw new Error(-1, "SetLast without begin on depot " + this.strName);

    this.nLast = dateVal;
}

function Depot_End(dateVal)
{
    if (!this.nBegin)
        throw new Error(-1, "End without begin on depot " + this.strName);

    var delta = dateVal - this.nBegin;
    delete this.nBegin;
    this.nElapsed += delta;
}

function Depot_End2(dateVal)
{
    if (!this.nBegin)
        throw new Error(-1, "End without begin on depot " + this.strName);

    var delta = dateVal - this.nBegin;
    delete this.nBegin;
    LogMsg("DELTA " + this.strName + " = " + delta);
    this.nElapsed += delta;
}

function Depot_Terminate(dateVal)
{
}

function Depot_GetStrStatus()
{
    var seconds = Math.floor(this.nElapsed / 1000);
    var s = seconds % 60;
    var m = Math.floor(seconds / 60) % 60;
    var h = Math.floor(seconds / 60 / 60);

    var strElapsed = PadDigits(h, 2) + ":" + PadDigits(m, 2) + ":" + PadDigits(s, 2);
    return PadString(this.strName, 16) + " " + strElapsed;// + ", " + (this.nElapsed / 1000);
    
}
//////////////////////////////////////
//////////////////////////////////////
//////////////////////////////////////

function NewDepot(dateVal, strDepotName, a)
{
    var nPID = a[0];
    if (g_hDepots[strDepotName])
    {
        if (strDepotName == "root")
        {
            var depot = g_hDepots[strDepotName];
            depot.Begin(dateVal);
        }
        else
            throw new Error(-1, "Duplicate depot information for depot " + strDepotName);
    }
    var depot = new Depot(strDepotName, nPID);
    depot.Begin(dateVal);

    g_hDepots[strDepotName] = depot;
}

function PassComplete(dateVal, strDepotName, a)
{
    var nPass = a[0];
    var nPID = a[1];
    var depot = g_hDepots[strDepotName];
    if (!depot)
        throw new Error(-1, "PassComplete on missing depot "  + strDepotName);

    depot.End(dateVal);
}

function TaskStep(dateVal, strDepotName)
{
    var depot = g_hDepots[strDepotName];
    if (!depot)
        throw new Error(-1, "TaskStep on missing depot "  + strDepotName);

    depot.Begin(dateVal);
}

function ProcessExit(dateVal, strDepotName, a)
{
    var nPID = a[0];
    var depot = g_hDepots[strDepotName];
    if (!depot)
        throw new Error(-1, "ProcessExit on missing depot "  + strDepotName);

    depot.Terminate(dateVal);
}

function PostStart(dateVal, strDepotName)
{
    if (g_fPrintPost)
    {
        var depot = new Depot('postbuild', 0);
        depot.Begin(dateVal);

        g_hTimers['postbuild'] = depot;
    }
}

function PostExit(dateVal, strDepotName)
{
    if (g_fPrintPost)
    {
        var depot = g_hTimers['postbuild'];
        if (!depot)
            throw new Error(-1, "TaskStep on missing depot "  + 'postbuild');

        depot.End(dateVal);
    }
}

function ElapsedStart(dateVal, strDepotName)
{
    if (g_fPrintElapsed)
    {
        var depot = new Depot('entirebuild', 0);
        depot.Begin(dateVal);

        g_hTimers['entirebuild'] = depot;
    }
}

function ElapsedExit(dateVal, strDepotName)
{
    if (g_fPrintElapsed)
    {
        var depot = g_hTimers['entirebuild'];
        if (!depot)
            throw new Error(-1, "TaskStep on missing depot "  + 'entirebuild');

        depot.End(dateVal);
    }
}

function StartCFPBTS(dateVal, strDepotName)
{
    var depot;
    if (true)
    {
        if (g_CFObj)
            EndCFTPB(dateVal, strDepotName);

        depot = g_hTimers['toslave'];
        if (!depot)
            depot = new Depot('toslave', 0);

        depot.Begin(dateVal);
        depot.SetLast(dateVal);

        g_hTimers['toslave'] = depot;

        g_CFObj = depot;
//        LogMsg("StartCFPBTS at " + ( new Date(dateVal)));
    }
}

function StartCFTPB(dateVal, strDepotName)
{
    var depot;
    if (true)
    {
        if (g_CFObj)
            EndCFTPB(dateVal, strDepotName);

        depot = g_hTimers['topostbuild'];
        if (!depot)
            depot = new Depot('topostbuild', 0);

        depot.Begin(dateVal);
        depot.SetLast(dateVal);

        g_hTimers['topostbuild'] = depot;

        g_CFObj = depot;
        //LogMsg("StartCFTPB  at " + ( new Date(dateVal)));
    }
}

function LastFileCFTPB(dateVal, strDepotName)
{
    if (true)
    {
        if (g_CFObj)
        {
            g_CFObj.SetLast(dateVal);
        }
    }
}

function EndCFTPB(dateVal, strDepotName)
{
    if (true)
    {
        var depot = null;
        if (g_CFObj)
        {
            depot = g_CFObj;
            //LogMsg("EndCFTPB    at " + ( new Date(depot.nLast)));
            depot.End(depot.nLast);
            g_CFObj = null;
        }
    }
}

function ParseDate(strLine)
{
    var a = g_reDate.exec(strLine);
    if (!a)
    {
        throw new Error(-1, "Incorrect date format: " + strLine);
    }
    var d = new Date(a[1], a[2] - 1, a[3], a[4], a[5], a[6]);

    return d.getTime();
}


function GetBuildType(strLine)
{
    //Build Type:         free
    //Build Platform:     64bit
    //Incremental Build:  false
    //Build Manager:      BUILDCON1
    //PostBuild Machine:  AXP64FRE

    var str;
    var n;
    var m;
    var fWin64 = false;
    var fFree  = false;

    n = strLine.indexOf("\t");
    if (n >= 0)
    {
        g_strMachine = strLine.slice(0, n);
    }
    n = strLine.indexOf("razzle.cmd", 0);
    if (n > 0)
    { // Extract the stuff between "razzle.cmd" and the first "&"
        m = strLine.indexOf("&", n);
        str = strLine.slice(n, m);

        n = str.indexOf("win64");
        if (n >= 0)
            fWin64 = true;
        n = str.indexOf("free");
        if (n >= 0)
            fFree = true;

        g_strBuildType = (fWin64 ? "64bit " : "32bit ") + (fFree ? "free" : "checked");
    }
}

function MergeRoot(str)
{
    str = str.toLowerCase();
    if (g_fMerged && str == "mergedcomponents")
        return "root";

    return str;
}

function ParseLogFile(strFileName)
{
    var strDepotName;
    var hFile = g_FSObj.OpenTextFile(strFileName, 1, false);

    var a;
    var nLine = 0;
    var i;
    try
    {
        while(1)
        {
            r = hFile.ReadLine();
            ++nLine;

            if (!g_strBuildType)
            {
                if (g_reBuildType.test(r))
                    GetBuildType(r);
            }
//            debugger;
            for(i = 0; i < g_aMatches.length; ++i)
            {
                a = g_aMatches[i].re.exec(r);
                if (a != null)
                {
                    if (a.length > 1)
                        strDepotName = MergeRoot(a[1]);
                    else
                        strDepotName = '';
                    a = a.slice(2);
                    g_aMatches[i].fn(ParseDate(r), strDepotName, a);
                    break;
                }
            }
        }
    }
    catch(ex)
    {
        if (ex.number != -2146828226) // Input past end
        {
            LogMsg("Exception in ParseLogFile " + strFileName + ", line=" + nLine + ", ex=" + ex);
            LogMsg("I is " + i);
        }
    }
    hFile.Close();
}

function main()
{
    var i;
    var strDepot;
    var depot;

    for(i = 0; i < g_aStrFileNames.length; ++i)
    {
        g_strMachine   = null;
        g_strBuildType = null;
        g_hDepots      = new Object;
        g_hTimers      = new Object;

        ParseLogFile(g_aStrFileNames[i]);

        if (!g_strMachine)
            g_strMachine = g_aStrFileNames[i];

        g_strMachine = PadString(PadString(g_strMachine, 12) + g_strBuildType, 24);
        //LogMsg(g_strMachine + ": " + g_strBuildType);
        for(strDepot in g_hDepots)
        {
            LogMsg(g_strMachine + ": Depot: " + g_hDepots[strDepot].GetStrStatus());
        }
        for(strDepot in g_hTimers)
        {
            LogMsg(g_strMachine + ":        " + g_hTimers[strDepot].GetStrStatus());
        }
    }
}