Ticket #4056: Profiler2.2.patch

File Profiler2.2.patch, 47.7 KB (added by wraitii, 8 years ago)

Updated version

  • source/ps/Profiler2.cpp

     
    3131#include "third_party/mongoose/mongoose.h"
    3232
    3333#include <iomanip>
     34#include <unordered_map>
    3435
    3536CProfiler2 g_Profiler2;
    3637
     
    8081        std::stringstream stream;
    8182
    8283        std::string uri = request_info->uri;
    83         if (uri == "/overview")
     84
     85        if (uri == "/download")
    8486        {
     87            profiler->SaveToFile();
     88        }
     89        else if (uri == "/overview")
     90        {
    8591            profiler->ConstructJSONOverview(stream);
    8692        }
    8793        else if (uri == "/query")
     
    191197void CProfiler2::ShutDownHTTP()
    192198{
    193199    LOGMESSAGERENDER("Shutting down profiler2 HTTP server");
     200
    194201    if (m_MgContext)
    195202    {
    196203        mg_stop(m_MgContext);
     
    219226
    220227    ENSURE(!m_GPU); // must shutdown GPU before profiler
    221228
     229    SaveToFile();
     230
    222231    if (m_MgContext)
    223232    {
    224233        mg_stop(m_MgContext);
     
    301310}
    302311
    303312CProfiler2::ThreadStorage::ThreadStorage(CProfiler2& profiler, const std::string& name) :
    304     m_Profiler(profiler), m_Name(name), m_BufferPos0(0), m_BufferPos1(0), m_LastTime(timer_Time())
     313m_Profiler(profiler), m_Name(name), m_BufferPos0(0), m_BufferPos1(0), m_LastTime(timer_Time()), m_HeldDepth(0)
    305314{
    306315    m_Buffer = new u8[BUFFER_SIZE];
    307316    memset(m_Buffer, ITEM_NOP, BUFFER_SIZE);
     
    312321    delete[] m_Buffer;
    313322}
    314323
     324void CProfiler2::ThreadStorage::Write(EItem type, const void* item, u32 itemSize)
     325{
     326    if (m_HeldDepth > 0)
     327    {
     328        WriteHold(type, item, itemSize);
     329        return;
     330    }
     331    // See m_BufferPos0 etc for comments on synchronisation
     332
     333    u32 size = 1 + itemSize;
     334    u32 start = m_BufferPos0;
     335    if (start + size > BUFFER_SIZE)
     336    {
     337        // The remainder of the buffer is too small - fill the rest
     338        // with NOPs then start from offset 0, so we don't have to
     339        // bother splitting the real item across the end of the buffer
     340
     341        m_BufferPos0 = size;
     342        COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer
     343
     344        memset(m_Buffer + start, 0, BUFFER_SIZE - start);
     345        start = 0;
     346    }
     347    else
     348    {
     349        m_BufferPos0 = start + size;
     350        COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer
     351    }
     352
     353    m_Buffer[start] = (u8)type;
     354    memcpy(&m_Buffer[start + 1], item, itemSize);
     355
     356    COMPILER_FENCE; // must write m_BufferPos1 after m_Buffer
     357    m_BufferPos1 = start + size;
     358}
     359
     360void CProfiler2::ThreadStorage::WriteHold(EItem type, const void* item, u32 itemSize)
     361{
     362    u32 size = 1 + itemSize;
     363
     364    if (m_HoldBuffers[m_HeldDepth - 1].pos + size > CProfiler2::HOLD_BUFFER_SIZE)
     365        return; // we held on too much data, ignore the rest
     366
     367    m_HoldBuffers[m_HeldDepth - 1].buffer[m_HoldBuffers[m_HeldDepth - 1].pos] = (u8)type;
     368    memcpy(&m_HoldBuffers[m_HeldDepth - 1].buffer[m_HoldBuffers[m_HeldDepth - 1].pos + 1], item, itemSize);
     369
     370    m_HoldBuffers[m_HeldDepth - 1].pos += size;
     371}
     372
    315373std::string CProfiler2::ThreadStorage::GetBuffer()
    316374{
    317375    // Called from an arbitrary thread (not the one writing to the buffer).
     
    355413    Write(ITEM_ATTRIBUTE, buffer, 4 + len);
    356414}
    357415
     416size_t CProfiler2::ThreadStorage::HoldLevel()
     417{
     418    return m_HeldDepth;
     419}
    358420
     421u8 CProfiler2::ThreadStorage::HoldType()
     422{
     423    return m_HoldBuffers[m_HeldDepth - 1].type;
     424}
     425
     426void CProfiler2::ThreadStorage::PutOnHold(u8 newType)
     427{
     428    m_HeldDepth++;
     429    m_HoldBuffers[m_HeldDepth - 1].clear();
     430    m_HoldBuffers[m_HeldDepth - 1].setType(newType);
     431}
     432
     433// this flattens the stack, use it sensibly
     434void rewriteBuffer(u8* buffer, u32& bufferSize)
     435{
     436    double startTime = timer_Time();
     437
     438    u32 size = bufferSize;
     439    u32 readPos = 0;
     440
     441    double initialTime = -1;
     442    double total_time = -1;
     443    const char* regionName;
     444    std::set<std::string> topLevelArgs;
     445
     446    typedef std::tuple<const char*, double, std::set<std::string> > infoPerType;
     447    std::unordered_map<std::string, infoPerType> timeByType;
     448    std::vector<double> last_time_stack;
     449    std::vector<const char*> last_names;
     450
     451    // never too many hacks
     452    std::string current_attribute = "";
     453    std::map<std::string, double> time_per_attribute;
     454
     455    // Let's read the first event
     456    {
     457        u8 type = buffer[readPos];
     458        ++readPos;
     459        if (type != CProfiler2::ITEM_ENTER)
     460        {
     461            debug_warn("Profiler2: Condensing a region should run into ITEM_ENTER first");
     462            return; // do nothing
     463        }
     464        CProfiler2::SItem_dt_id item;
     465        memcpy(&item, buffer + readPos, sizeof(item));
     466        readPos += sizeof(item);
     467
     468        regionName = item.id;
     469        last_names.push_back(item.id);
     470        initialTime = (double)item.dt;
     471    }
     472    int enter = 1;
     473    int leaves = 0;
     474    // Read subsequent events. Flatten hierarchy because it would get too complicated otherwise.
     475    // To make sure time doesn't bloat, subtract time from nested events
     476    while (readPos < size)
     477    {
     478        u8 type = buffer[readPos];
     479        ++readPos;
     480
     481        switch (type)
     482        {
     483        case CProfiler2::ITEM_NOP:
     484        {
     485            // ignore
     486            break;
     487        }
     488        case CProfiler2::ITEM_SYNC:
     489        {
     490            debug_warn("Aggregated regions should not be used across frames");
     491            // still try to act sane
     492            readPos += sizeof(double);
     493            readPos += sizeof(CProfiler2::RESYNC_MAGIC);
     494            break;
     495        }
     496        case CProfiler2::ITEM_EVENT:
     497        {
     498            // skip for now
     499            readPos += sizeof(CProfiler2::SItem_dt_id);
     500            break;
     501        }
     502        case CProfiler2::ITEM_ENTER:
     503        {
     504            enter++;
     505            CProfiler2::SItem_dt_id item;
     506            memcpy(&item, buffer + readPos, sizeof(item));
     507            readPos += sizeof(item);
     508            last_time_stack.push_back((double)item.dt);
     509            last_names.push_back(item.id);
     510            current_attribute = "";
     511            break;
     512        }
     513        case CProfiler2::ITEM_LEAVE:
     514        {
     515            CProfiler2::SItem_dt_id item;
     516            memcpy(&item, buffer + readPos, sizeof(item));
     517            readPos += sizeof(item);
     518
     519            leaves++;
     520            if (last_names.empty())
     521            {
     522                // we somehow lost the first entry in the process
     523                debug_warn("alert %i %i %li", readPos, enter, holdLevel);
     524            }
     525
     526            last_names.pop_back();
     527
     528            if (last_time_stack.empty())
     529            {
     530                // this is the leave for the whole scope
     531                total_time = item.dt;
     532                break;
     533            }
     534            double time = (double)item.dt - last_time_stack.back();
     535
     536            std::string name = std::string(item.id);
     537            auto TimeForType = timeByType.find(name);
     538            if (TimeForType == timeByType.end())
     539            {
     540                // keep reference to the original char pointer to make sure we don't break things down the line
     541                std::get<0>(timeByType[name]) = item.id;
     542                std::get<1>(timeByType[name]) = 0;
     543            }
     544            std::get<1>(timeByType[name]) += time;
     545
     546            last_time_stack.pop_back();
     547            // if we were nested, subtract our time from the below scope by making it look like it starts later
     548            if (!last_time_stack.empty())
     549                last_time_stack.back() += time;
     550
     551            if (!current_attribute.empty())
     552            {
     553                time_per_attribute[current_attribute] += time;
     554            }
     555
     556            break;
     557        }
     558        case CProfiler2::ITEM_ATTRIBUTE:
     559        {
     560            // skip for now
     561            u32 len;
     562            memcpy(&len, buffer + readPos, sizeof(len));
     563            ENSURE(len <= CProfiler2::MAX_ATTRIBUTE_LENGTH);
     564            readPos += sizeof(len);
     565           
     566            char message[CProfiler2::MAX_ATTRIBUTE_LENGTH] = {0};
     567            memcpy(&message[0], buffer + readPos, len);
     568            CStr mess = CStr((const char*)message, len);
     569            if (!last_names.empty())
     570            {
     571                auto it = timeByType.find(std::string(last_names.back()));
     572                if (it == timeByType.end())
     573                    topLevelArgs.insert(mess);
     574                else
     575                    std::get<2>(timeByType[std::string(last_names.back())]).insert(mess);
     576            }
     577            readPos += len;
     578            current_attribute = mess;
     579            break;
     580        }
     581        default:
     582            debug_warn(L"Invalid profiler item when parsing buffer");
     583            continue;
     584        }
     585    }
     586
     587    // rewrite the buffer
     588    // what we rewrite will always be smaller than the current buffer's size
     589    u32 writePos = 0;
     590    double curTime = initialTime;
     591    // the region enter
     592    {
     593        CProfiler2::SItem_dt_id item = { curTime, regionName };
     594        buffer[writePos] = (u8)CProfiler2::ITEM_ENTER;
     595        memcpy(buffer + writePos + 1, &item, sizeof(item));
     596        writePos += sizeof(item) + 1;
     597        // add a nanosecond for sanity
     598        curTime += 0.000001;
     599    }
     600    // sub-events, aggregated
     601    for (auto& type : timeByType)
     602    {
     603        CProfiler2::SItem_dt_id item = { curTime, std::get<0>(type.second) };
     604        buffer[writePos] = (u8)CProfiler2::ITEM_ENTER;
     605        memcpy(buffer + writePos + 1, &item, sizeof(item));
     606        writePos += sizeof(item) + 1;
     607
     608        // write relevant attributes if present
     609        for (const auto& attrib : std::get<2>(type.second))
     610        {
     611            buffer[writePos] = (u8)CProfiler2::ITEM_ATTRIBUTE;
     612            writePos++;
     613            std::string basic = attrib;
     614            auto time_attrib = time_per_attribute.find(attrib);
     615            if (time_attrib != time_per_attribute.end())
     616                basic += " " + CStr::FromInt(1000000*time_attrib->second) + "us";
     617
     618            u32 length = basic.size();
     619            memcpy(buffer + writePos, &length, sizeof(length));
     620            writePos += sizeof(length);
     621            memcpy(buffer + writePos, basic.c_str(), length);
     622            writePos += length;
     623        }
     624
     625        curTime += std::get<1>(type.second);
     626        item.dt = curTime;
     627        buffer[writePos] = (u8)CProfiler2::ITEM_LEAVE;
     628        memcpy(buffer + writePos + 1, &item, sizeof(item));
     629        writePos += sizeof(item) + 1;
     630    }
     631    // the region leave
     632    {
     633        if (total_time < 0)
     634        {
     635            total_time = curTime + 0.000001;
     636
     637            buffer[writePos] = (u8)CProfiler2::ITEM_ATTRIBUTE;
     638            writePos++;
     639            u32 length = sizeof("buffer overflow");
     640            memcpy(buffer + writePos, &length, sizeof(length));
     641            writePos += sizeof(length);
     642            memcpy(buffer + writePos, "buffer overflow", length);
     643            writePos += length;
     644        }
     645        else if (total_time < curTime)
     646        {
     647            // this seems to happen on rare occasions.
     648            curTime = total_time;
     649        }
     650        CProfiler2::SItem_dt_id item = { total_time, regionName };
     651        buffer[writePos] = (u8)CProfiler2::ITEM_LEAVE;
     652        memcpy(buffer + writePos + 1, &item, sizeof(item));
     653        writePos += sizeof(item) + 1;
     654    }
     655    // Time of computation
     656    {
     657        CProfiler2::SItem_dt_id item = { curTime, "CondenseBuffer" };
     658        buffer[writePos] = (u8)CProfiler2::ITEM_ENTER;
     659        memcpy(buffer + writePos + 1, &item, sizeof(item));
     660        writePos += sizeof(item) + 1;
     661    }
     662    {
     663        CProfiler2::SItem_dt_id item = { curTime + timer_Time() - startTime, "CondenseBuffer" };
     664        buffer[writePos] = (u8)CProfiler2::ITEM_LEAVE;
     665        memcpy(buffer + writePos + 1, &item, sizeof(item));
     666        writePos += sizeof(item) + 1;
     667        // add a nanosecond for sanity
     668        curTime += 0.000001;
     669    }
     670    bufferSize = writePos;
     671}
     672
     673void CProfiler2::ThreadStorage::HoldToBuffer(bool condensed)
     674{
     675    ENSURE(m_HeldDepth);
     676    if (condensed)
     677    {
     678        // rewrite the buffer to show aggregated data
     679        rewriteBuffer(m_HoldBuffers[m_HeldDepth - 1].buffer, m_HoldBuffers[m_HeldDepth - 1].pos);
     680    }
     681
     682    if (m_HeldDepth > 1)
     683    {
     684        // copy onto buffer below
     685        HoldBuffer& copied = m_HoldBuffers[m_HeldDepth - 1];
     686        HoldBuffer& target = m_HoldBuffers[m_HeldDepth - 2];
     687        if (target.pos + copied.pos > HOLD_BUFFER_SIZE)
     688            return; // too much data, too bad
     689
     690        memcpy(&target.buffer[target.pos], copied.buffer, copied.pos);
     691
     692        target.pos += copied.pos;
     693    }
     694    else
     695    {
     696        u32 size = m_HoldBuffers[m_HeldDepth - 1].pos;
     697        u32 start = m_BufferPos0;
     698        if (start + size > BUFFER_SIZE)
     699        {
     700            m_BufferPos0 = size;
     701            COMPILER_FENCE;
     702            memset(m_Buffer + start, 0, BUFFER_SIZE - start);
     703            start = 0;
     704        }
     705        else
     706        {
     707            m_BufferPos0 = start + size;
     708            COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer
     709        }
     710        memcpy(&m_Buffer[start], m_HoldBuffers[m_HeldDepth - 1].buffer, size);
     711        COMPILER_FENCE; // must write m_BufferPos1 after m_Buffer
     712        m_BufferPos1 = start + size;
     713    }
     714    m_HeldDepth--;
     715}
     716void CProfiler2::ThreadStorage::ThrowawayHoldBuffer()
     717{
     718    if (!m_HeldDepth)
     719        return;
     720    m_HeldDepth--;
     721}
     722
    359723void CProfiler2::ConstructJSONOverview(std::ostream& stream)
    360724{
    361725    TIMER(L"profile2 overview");
     
    438802            pos += sizeof(item);
    439803            if (lastTime >= 0)
    440804            {
    441                 lastTime = lastTime + (double)item.dt;
    442                 visitor.OnEvent(lastTime, item.id);
     805                //lastTime = lastTime + (double)item.dt;
     806                visitor.OnEvent(lastTime + (double)item.dt, item.id);
    443807            }
    444808            break;
    445809        }
     
    450814            pos += sizeof(item);
    451815            if (lastTime >= 0)
    452816            {
    453                 lastTime = lastTime + (double)item.dt;
    454                 visitor.OnEnter(lastTime, item.id);
     817                //lastTime = lastTime + (double)item.dt;
     818                visitor.OnEnter(lastTime + (double)item.dt, item.id);
    455819            }
    456820            break;
    457821        }
     
    462826            pos += sizeof(item);
    463827            if (lastTime >= 0)
    464828            {
    465                 lastTime = lastTime + (double)item.dt;
    466                 visitor.OnLeave(lastTime, item.id);
     829                //lastTime = lastTime + (double)item.dt;
     830                visitor.OnLeave(lastTime + (double)item.dt, item.id);
    467831            }
    468832            break;
    469833        }
     
    522886    void OnLeave(double time, const char* id)
    523887    {
    524888        m_Stream << "[3," << std::fixed << std::setprecision(9) << time;
     889
    525890        m_Stream << ",\"" << CStr(id).EscapeToPrintableASCII() << "\"],\n";
    526891    }
    527892
     
    596961    }
    597962    stream << "\n]});\n";
    598963}
     964
     965CProfile2SpikeRegion::CProfile2SpikeRegion(const char* name, double spikeLimit) :
     966    m_Name(name), m_Limit(spikeLimit), m_PushedHold(true)
     967{
     968    if (g_Profiler2.HoldLevel() < 8 &&  g_Profiler2.HoldType() != CProfiler2::ThreadStorage::BUFFER_AGGREGATE)
     969        g_Profiler2.HoldMessages(CProfiler2::ThreadStorage::BUFFER_SPIKE);
     970    else
     971        m_PushedHold = false;
     972    COMPILER_FENCE;
     973    g_Profiler2.RecordRegionEnter(m_Name);
     974    m_StartTime = g_Profiler2.GetTime();
     975}
     976CProfile2SpikeRegion::~CProfile2SpikeRegion()
     977{
     978    double time = g_Profiler2.GetTime();
     979    g_Profiler2.RecordRegionLeave(m_Name);
     980    bool shouldWrite = time - m_StartTime > m_Limit;
     981
     982    if (m_PushedHold)
     983        g_Profiler2.StopHoldingMessages(shouldWrite);
     984}
     985
     986CProfile2AggregatedRegion::CProfile2AggregatedRegion(const char* name, double spikeLimit) :
     987    m_Name(name), m_Limit(spikeLimit), m_PushedHold(true)
     988{
     989    if (g_Profiler2.HoldLevel() < 8 && g_Profiler2.HoldType() != CProfiler2::ThreadStorage::BUFFER_AGGREGATE)
     990        g_Profiler2.HoldMessages(CProfiler2::ThreadStorage::BUFFER_AGGREGATE);
     991    else
     992        m_PushedHold = false;
     993    COMPILER_FENCE;
     994    g_Profiler2.RecordRegionEnter(m_Name);
     995    m_StartTime = g_Profiler2.GetTime();
     996}
     997CProfile2AggregatedRegion::~CProfile2AggregatedRegion()
     998{
     999    double time = g_Profiler2.GetTime();
     1000    g_Profiler2.RecordRegionLeave(m_Name);
     1001    bool shouldWrite = time - m_StartTime > m_Limit;
     1002
     1003    if (m_PushedHold)
     1004        g_Profiler2.StopHoldingMessages(shouldWrite, true);
     1005}
  • source/ps/Profiler2.h

     
    9191class CProfiler2
    9292{
    9393    friend class CProfiler2GPU_base;
    94 
     94    friend class CProfile2SpikeRegion;
     95    friend class CProfile2AggregatedRegion;
    9596public:
    9697    // Items stored in the buffers:
    9798
     
    123124private:
    124125    // TODO: what's a good size?
    125126    // TODO: different threads might want different sizes
    126     static const size_t BUFFER_SIZE = 1024*1024;
     127    static const size_t BUFFER_SIZE = 10*1024*1024;
     128    static const size_t HOLD_BUFFER_SIZE = 128 * 1024;
    127129
    128130    /**
    129131     * Class instantiated in every registered thread.
     
    135137        ThreadStorage(CProfiler2& profiler, const std::string& name);
    136138        ~ThreadStorage();
    137139   
     140        enum { BUFFER_NORMAL, BUFFER_SPIKE, BUFFER_AGGREGATE };
     141
    138142        void RecordSyncMarker(double t)
    139143        {
    140144            // Store the magic string followed by the absolute time
     
    153157            // (to save memory) without suffering from precision problems
    154158            SItem_dt_id item = { (float)(t - m_LastTime), id };
    155159            Write(type, &item, sizeof(item));
    156             m_LastTime = t;
     160            //m_LastTime = t;
    157161        }
    158162
    159163        void RecordFrameStart(double t)
     
    172176            va_end(argp);
    173177        }
    174178
     179        size_t HoldLevel();
     180        u8 HoldType();
     181        void PutOnHold(u8 type);
     182        void HoldToBuffer(bool condensed);
     183        void ThrowawayHoldBuffer();
     184
    175185        CProfiler2& GetProfiler()
    176186        {
    177187            return m_Profiler;
     
    193203        /**
    194204         * Store an item into the buffer.
    195205         */
    196         void Write(EItem type, const void* item, u32 itemSize)
    197         {
    198             // See m_BufferPos0 etc for comments on synchronisation
     206        void Write(EItem type, const void* item, u32 itemSize);
    199207
    200             u32 size = 1 + itemSize;
    201             u32 start = m_BufferPos0;
    202             if (start + size > BUFFER_SIZE)
    203             {
    204                 // The remainder of the buffer is too small - fill the rest
    205                 // with NOPs then start from offset 0, so we don't have to
    206                 // bother splitting the real item across the end of the buffer
     208        void WriteHold(EItem type, const void* item, u32 itemSize);
    207209
    208                 m_BufferPos0 = size;
    209                 COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer
    210 
    211                 memset(m_Buffer + start, 0, BUFFER_SIZE - start);
    212                 start = 0;
    213             }
    214             else
    215             {
    216                 m_BufferPos0 = start + size;
    217                 COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer
    218             }
    219 
    220             m_Buffer[start] = (u8)type;
    221             memcpy(&m_Buffer[start + 1], item, itemSize);
    222            
    223             COMPILER_FENCE; // must write m_BufferPos1 after m_Buffer
    224             m_BufferPos1 = start + size;
    225         }
    226 
    227210        CProfiler2& m_Profiler;
    228211        std::string m_Name;
    229212
     
    231214
    232215        u8* m_Buffer;
    233216
     217        struct HoldBuffer
     218        {
     219            friend class ThreadStorage;
     220        public:
     221            HoldBuffer()
     222            {
     223                buffer = new u8[HOLD_BUFFER_SIZE];
     224                memset(buffer, ITEM_NOP, HOLD_BUFFER_SIZE);
     225                pos = 0;
     226            }
     227            ~HoldBuffer()
     228            {
     229                delete[] buffer;
     230            }
     231            void clear()
     232            {
     233                pos = 0;
     234            }
     235            void setType(u8 newType)
     236            {
     237                type = newType;
     238            }
     239            u8* buffer;
     240            u32 pos;
     241            u8 type;
     242        };
     243
     244        HoldBuffer m_HoldBuffers[8];
     245        size_t m_HeldDepth;
     246
    234247        // To allow hopefully-safe reading of the buffer from a separate thread,
    235248        // without any expensive synchronisation in the recording thread,
    236249        // two copies of the current buffer write position are stored.
     
    327340        GetThreadStorage().Record(ITEM_ENTER, GetTime(), id);
    328341    }
    329342
     343    void RecordRegionEnter(const char* id, double time)
     344    {
     345        GetThreadStorage().Record(ITEM_ENTER, time, id);
     346    }
     347
    330348    void RecordRegionLeave(const char* id)
    331349    {
    332350        GetThreadStorage().Record(ITEM_LEAVE, GetTime(), id);
     
    346364    void RecordGPURegionLeave(const char* id);
    347365
    348366    /**
     367    * Hold onto messages until a call to release or write the held messages.
     368    */
     369    size_t HoldLevel()
     370    {
     371        return GetThreadStorage().HoldLevel();
     372    }
     373
     374    u8 HoldType()
     375    {
     376        return GetThreadStorage().HoldType();
     377    }
     378
     379    void HoldMessages(u8 type)
     380    {
     381        GetThreadStorage().PutOnHold(type);
     382    }
     383
     384    void StopHoldingMessages(bool writeToBuffer, bool condensed = false)
     385    {
     386        if (writeToBuffer)
     387            GetThreadStorage().HoldToBuffer(condensed);
     388        else
     389            GetThreadStorage().ThrowawayHoldBuffer();
     390    }
     391
     392    /**
    349393     * Call in any thread to produce a JSON representation of the general
    350394     * state of the application.
    351395     */
     
    424468    {
    425469        g_Profiler2.RecordRegionLeave(m_Name);
    426470    }
     471protected:
     472    const char* m_Name;
     473};
     474
     475/**
     476* Scope-based enter/leave helper.
     477*/
     478class CProfile2SpikeRegion
     479{
     480public:
     481    CProfile2SpikeRegion(const char* name, double spikeLimit);
     482    ~CProfile2SpikeRegion();
    427483private:
    428484    const char* m_Name;
     485    double m_Limit;
     486    double m_StartTime;
     487    bool m_PushedHold;
    429488};
    430489
    431490/**
     491* Scope-based enter/leave helper.
     492*/
     493class CProfile2AggregatedRegion
     494{
     495public:
     496    CProfile2AggregatedRegion(const char* name, double spikeLimit);
     497    ~CProfile2AggregatedRegion();
     498private:
     499    const char* m_Name;
     500    double m_Limit;
     501    double m_StartTime;
     502    bool m_PushedHold;
     503};
     504
     505/**
    432506 * Scope-based GPU enter/leave helper.
    433507 */
    434508class CProfile2GPURegion
     
    455529 */
    456530#define PROFILE2(region) CProfile2Region profile2__(region)
    457531
     532#define PROFILE2_IFSPIKE(region, limit) CProfile2SpikeRegion profile2__(region, limit)
     533
    458534#define PROFILE2_GPU(region) CProfile2GPURegion profile2gpu__(region)
    459535
    460536/**
  • source/tools/profiler2/profiler2.html

     
    66$(refresh);
    77</script>
    88<style>
    9 canvas { border: 1px #ddd solid; }
     9canvas { border: 1px #ddd solid; display:block;}
    1010#tooltip { background: #ffd; padding: 4px; font: 12px sans-serif; border: 1px #880 solid; }
    1111#tooltip.long { -moz-column-count: 2; }
     12#choices p { margin:2px; cursor:pointer;}
     13#choices p:hover { background:#eee;}
     14#choices p.active { background:#fee;}
    1215</style>
    1316
    1417<button onclick="refresh()">Refresh</button>
     18<button onclick="SaveAsFile()">Save as File</button>
     19
     20<button onclick="gather_stable_data()">Condense</button>
     21
    1522<br>
    1623<div id="timelines"></div>
    1724
     25<div id="infodepth">
     26<button onclick="output_graph_for_type('FlushDestroyedComponents')">output_graph_for_type</button>
     27<button onclick="output_time_graph_for_type('FlushDestroyedComponents')">output_time_graph_for_type</button>
     28<input type="file" id="ComparisonFile" name="files[]" />
     29<div style="float:right; overflow-y:scroll; width:600px;height:600px" id="choices">
     30<h3>Choices</h3>
     31</div>
     32    <canvas id="typical" width="600" height="600"></canvas>
     33</div>
    1834<div id="tooltip" style="position: absolute; visibility: hidden"></div>
    1935
    2036Search: <input id="regionsearch" oninput="search_regions(this.value)">
  • source/tools/profiler2/profiler2.js

     
    77var ITEM_LEAVE = 3;
    88var ITEM_ATTRIBUTE = 4;
    99
     10var g_used_colours = {};
    1011
     12
     13var g_data;
     14
     15var g_last_stable_frame = 0;
     16var g_stable_data = {};
     17var g_statistical_data = {};
     18
     19var g_ActiveElement;
     20
    1121function hslToRgb(h, s, l, a)
    1222{
    1323    var r, g, b;
     
    4656    return hslToRgb(hs[id % hs.length], ss[Math.floor(id / hs.length) % ss.length], ls[Math.floor(id / (hs.length*ss.length)) % ls.length], 1);
    4757}
    4858
    49 var g_used_colours = {};
    50 
    51 
    52 var g_data;
    53 
    5459function refresh()
    5560{
    56     if (1)
     61    if (0)
    5762        refresh_live();
    5863    else
    59         refresh_jsonp('../../../binaries/system/profile2.jsonp');
     64        refresh_jsonp('C:/Users/ironweb-15/AppData/Local/0ad/logs/profile2.jsonp');
    6065}
    6166
    6267function concat_events(data)
     
    8893
    8994        rebuild_canvases();
    9095        update_display(range);
     96        recompute_choices(0);
    9197    };
    9298
    9399    script.src = url;
     
    109115            threads.forEach(function(thread) {
    110116                refresh_thread(thread, callback_data);
    111117            });
     118            setTimeout(refresh, 2000);
    112119        },
    113120        error: function (jqXHR, textStatus, errorThrown) {
    114121            alert('Failed to connect to server ("'+textStatus+'")');
     122            refresh=refresh_jsonp
    115123        }
    116124    });
    117125}
     
    143151        }
    144152    });
    145153}
     154function gather_stable_data()
     155{
     156    if (!g_stable_data.threads)
     157    {
     158        g_stable_data = {};
     159        g_statistical_data = {};
     160        for (let thread in g_data.threads)
     161        {
     162            g_stable_data[thread] = {"frames" : [], "events" : []}
     163            g_statistical_data[thread] = {"evt_dur_avg" : {}, "evt_dur_total" : {}}
     164        }
     165    }
    146166
     167    for (let thread in g_data.threads)
     168    {
     169        let in_data = g_data.threads[thread].processed_events;
     170        if (!in_data)
     171            in_data = compute_intervals(g_data.threads[thread].data.events, {})
     172        if (!in_data)
     173            continue;
     174        let out_data = g_stable_data[thread];
     175        let startTime = 0;
     176
     177        for (let i = 0; i < in_data.frames.length; ++i)
     178            if (in_data.frames[i].t0 > g_last_stable_frame)
     179            {
     180                startTime = in_data.frames[i].t0;
     181                out_data.frames = out_data.frames.concat(in_data.frames.slice(i));
     182                break;
     183            }
     184        g_last_stable_frame = in_data.frames[in_data.frames.length-1].t1;
     185        // get all relevant events
     186        for (let evt in in_data.intervals)
     187        {
     188            let event = in_data.intervals[evt];
     189            let time = event.t0;
     190            if (time < startTime)
     191                continue;
     192
     193            if (!out_data.events[event.id])
     194            {
     195                out_data.events[event.id] = [];
     196                g_statistical_data[thread].evt_dur_avg[event.id] = event.duration;
     197                g_statistical_data[thread].evt_dur_total[event.id] = 0;
     198            }
     199            g_statistical_data[thread].evt_dur_avg[event.id] = g_statistical_data[thread].evt_dur_avg[event.id] * 0.99 + event.duration * 0.01;
     200            g_statistical_data[thread].evt_dur_total[event.id] += event.duration;
     201
     202            if (event.duration > 5e-3)
     203            {
     204                out_data.events.push(event);
     205            }
     206        }
     207    }
     208    var blob = new Blob([JSON.stringify(g_statistical_data)], {type: "application/json"});
     209    var url  = URL.createObjectURL(blob);
     210    window.open(url);
     211    var blob = new Blob([JSON.stringify(g_stable_data)], {type: "application/json"});
     212    var url  = URL.createObjectURL(blob);
     213    window.open(url);
     214}
     215
     216function SaveAsFile()
     217{
     218    $.ajax({
     219        url: 'http://127.0.0.1:8000/download',
     220        success: function () {
     221        },
     222        error: function (jqXHR, textStatus, errorThrown) {
     223        }
     224    });
     225}
     226
    147227function rebuild_canvases()
    148228{
    149229    g_data.canvas_frames = $('<canvas width="1600" height="128"></canvas>').get(0);
    150     g_data.canvas_zoom = $('<canvas width="1600" height="128"></canvas>').get(0);
     230    g_data.canvas_zoom = $('<canvas width="1600" height="192"></canvas>').get(0);
    151231    g_data.text_output = $('<pre></pre>').get(0);
    152232   
    153233    set_frames_zoom_handlers(g_data.canvas_frames);
     
    160240    });
    161241    $('#timelines').append(g_data.canvas_zoom);
    162242    $('#timelines').append(g_data.text_output);
     243    $('#timelines').append(g_data.canvas_typical);
    163244}
    164245
    165246function update_display(range)
     
    190271        set_zoom_handlers(processed_main, processed_data, thread.canvas, g_data.canvas_zoom);
    191272        set_tooltip_handlers(thread.canvas);
    192273    });
    193  }
     274}
    194275
    195276function display_top_items(data, output)
    196277{
     
    226307    var tmin, tmax;
    227308   
    228309    var frames = [];
    229     var last_frame_time = undefined;
     310    var last_frame_time_start = undefined;
     311    var last_frame_time_end = undefined;
    230312    for (var i = 0; i < data.length; ++i)
    231313    {
    232314        if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart')
    233315        {
    234             var t = data[i][1];
    235             if (last_frame_time)
    236                 frames.push({'t0': last_frame_time, 't1': t});
    237             last_frame_time = t;
     316            if (last_frame_time_end)
     317                frames.push({'t0': last_frame_time_start, 't1': last_frame_time_end});
     318            last_frame_time_start = data[i][1];
    238319        }
     320        if (data[i][0] == ITEM_LEAVE && data[i][2] == 'frame')
     321        {
     322            last_frame_time_end = data[i][1];
     323        }
    239324    }
    240325   
    241326    if (range.numframes)
     
    360445    var stack = [];
    361446    var lastT = 0;
    362447    var lastWasEvent = false;
     448 
    363449    for (var i = start; i <= end; ++i)
    364450    {
    365451        if (data[i][0] == ITEM_EVENT)
     
    373459        else if (data[i][0] == ITEM_ENTER)
    374460        {
    375461//            if (data[i][1] < lastT)
    376 //                console.log('Time went backwards: ' + (data[i][1] - lastT));
     462//                console.log('Time - ENTER  went backwards: ' + (data[i][1] - lastT) + " - " + JSON.stringify(data[i]));
    377463
    378464            stack.push({'t0': data[i][1], 'id': data[i][2]});
    379465
     
    383469        else if (data[i][0] == ITEM_LEAVE)
    384470        {
    385471//            if (data[i][1] < lastT)
    386 //                console.log('Time went backwards: ' + (data[i][1] - lastT));
     472//                console.log('Time - LEAVE  went backwards: ' + (data[i][1] - lastT) + " - " + JSON.stringify(data[i]));
    387473
    388474            lastT = data[i][1];
    389475            lastWasEvent = false;
     
    390476           
    391477            if (!stack.length)
    392478                continue;
     479
    393480            var interval = stack.pop();
    394481
     482
     483
    395484            if (data[i][2] != interval.id && data[i][2] != '(ProfileStop)')
    396                 alert('inconsistent interval ids ('+interval.id+' / '+data[i][2]+')');
     485                console.log('inconsistent interval ids ('+interval.id+' / '+data[i][2]+')');
    397486
    398487            if (!g_used_colours[interval.id])
    399488                g_used_colours[interval.id] = new_colour(num_colours++);
     
    403492            interval.t1 = data[i][1];
    404493            interval.duration = interval.t1 - interval.t0;
    405494            interval.depth = stack.length;
    406            
    407495            intervals.push(interval);
     496            if (interval.id == "Script" && interval.attrs && interval.attrs.length)
     497            {
     498                let curT = interval.t0;
     499                for (let subItem in interval.attrs)
     500                {
     501                    let sub = interval.attrs[subItem];
     502                    if (sub.search("buffer") != -1)
     503                        continue;
     504                    let newInterv = {};
     505                    newInterv.t0 = curT;
     506                    newInterv.duration = +sub.replace(/.+? ([.0-9]+)us/, "$1")/1000000;
     507                    newInterv.t1 = curT + newInterv.duration;
     508                    curT += newInterv.duration;
     509                    newInterv.id = "Script:" + sub.replace(/(.+?) ([.0-9]+)us/, "$1");
     510                    newInterv.colour = g_used_colours[interval.id];
     511                    newInterv.depth = interval.depth+1;
     512                    intervals.push(newInterv);
     513               }
     514            }
    408515        }
    409516        else if (data[i][0] == ITEM_ATTRIBUTE)
    410517        {
     
    454561        }
    455562    };
    456563   
    457 //    var y_per_second = 1000;
    458     var y_per_second = 100;
     564    // log 100 scale, skip < 15 ms (60fps)
     565    var scale = x => 1 - Math.max(0, Math.log(1 + (x-15)/10) / Math.log(100));
    459566
    460     [16, 33, 200, 500].forEach(function(t) {
    461         var y1 = canvas.height;
    462         var y0 = y1 - t/1000*y_per_second;
    463         var y = Math.floor(y0) + 0.5;
    464 
    465         ctx.beginPath();
    466         ctx.moveTo(xpadding, y);
    467         ctx.lineTo(canvas.width - xpadding, y);
    468         ctx.strokeStyle = 'rgb(255, 0, 0)';
    469         ctx.stroke();
    470         ctx.fillStyle = 'rgb(255, 0, 0)';
    471         ctx.fillText(t+'ms', 0, y-2);
    472     });
    473 
    474567    ctx.strokeStyle = 'rgb(0, 0, 0)';
    475568    ctx.fillStyle = 'rgb(255, 255, 255)';
    476569    for (var i = 0; i < data.frames.length; ++i)
     
    481574        var x0 = xpadding + dx*(frame.t0 - tmin);
    482575        var x1 = x0 + dx*duration;
    483576        var y1 = canvas.height;
    484         var y0 = y1 - duration*y_per_second;
     577        var y0 = y1 * scale(duration*1000);
    485578       
    486579        ctx.beginPath();
    487580        ctx.rect(x0, y0, x1-x0, y1-y0);
     
    504597        });
    505598    }
    506599
     600    [16, 33, 200, 500].forEach(function(t) {
     601        var y1 = canvas.height;
     602        var y0 = y1 * scale(t);
     603        var y = Math.floor(y0) + 0.5;
     604
     605        ctx.beginPath();
     606        ctx.moveTo(xpadding, y);
     607        ctx.lineTo(canvas.width - xpadding, y);
     608        ctx.strokeStyle = 'rgb(255, 0, 0)';
     609        ctx.stroke();
     610        ctx.fillStyle = 'rgb(255, 0, 0)';
     611        ctx.fillText(t+'ms', 0, y-2);
     612    });
     613
    507614    ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)';
    508615    ctx.fillStyle = 'rgba(128, 128, 255, 0.2)';
    509616    ctx.beginPath();
     
    534641       
    535642        var x = time_to_x(event.t);
    536643        var y = 32;
     644
     645        if (x < 2)
     646            continue;
    537647       
    538648        var x0 = x;
    539649        var x1 = x;
     
    611721    var precision = -3;
    612722    while ((tmax-tmin)*Math.pow(10, 3+precision) < 25)
    613723        ++precision;
     724    if (precision > 20)
     725        precision = 20;
     726    if (precision < 0)
     727        precision = 0;
    614728    var ticks_per_sec = Math.pow(10, 3+precision);
    615729    var major_tick_interval = 5;
    616730    for (var i = 0; i < (tmax-tmin)*ticks_per_sec; ++i)
     
    634748        if (interval.tmax <= tmin || interval.tmin > tmax)
    635749            continue;
    636750
     751        var x0 = Math.floor(time_to_x(interval.t0));
     752        var x1 = Math.floor(time_to_x(interval.t1));
     753        //if (x1-x0 < 1)
     754        //    continue;
     755        var y0 = padding_top + interval.depth * BAR_SPACING;
     756        var y1 = y0 + BAR_SPACING;
     757       
    637758        var label = interval.id;
    638759        if (interval.attrs)
    639760        {
     
    642763            else
    643764                label += ' [...]';
    644765        }
    645         var x0 = Math.floor(time_to_x(interval.t0));
    646         var x1 = Math.floor(time_to_x(interval.t1));
    647         var y0 = padding_top + interval.depth * BAR_SPACING;
    648         var y1 = y0 + BAR_SPACING;
    649        
     766
    650767        ctx.fillStyle = interval.colour;
    651768        ctx.strokeStyle = 'black';
    652769        ctx.beginPath();
     
    723840        var relativeY = event.pageY - this.offsetTop;
    724841       
    725842//        var width = 0.001 + 0.5 * relativeY / canvas0.height;
    726         var width = 0.001 + 5 * relativeY / canvas0.height;
     843        var width = relativeY / canvas0.height;
     844        width = width*width;
     845        width *= zdata.x_to_t(canvas0.width)/10;
    727846
    728847        var tavg = zdata.x_to_t(relativeX);
    729848        var tmax = tavg + width/2;
     
    759878        }
    760879       
    761880        var relativeX = event.pageX - this.offsetLeft;
    762         var relativeY = event.pageY - this.offsetTop;
    763         var width = 8 + 64 * relativeY / canvas0.height;
     881        var relativeY = (event.pageY + this.offsetTop) / canvas0.height;
     882        relativeY = relativeY - 0.5;
     883        relativeY *= 10;
     884        relativeY *= relativeY;
     885        var width = relativeY / canvas0.height;
     886        width = width*width;
     887        width = 6 + width * x_to_time(canvas0.width)/10;
    764888        var zoom = { tmin: x_to_time(relativeX-width/2), tmax: x_to_time(relativeX+width/2) };
    765889        display_hierarchy(main_data, data, canvas0, hdata.range, zoom);
    766890        display_hierarchy(main_data, data, canvas1, zoom, undefined);
     
    821945    $(canvas).mousemove(function(event) {
    822946        do_tooltip.call(this, event);
    823947    });
     948    $(canvas).mouseleave(function(event) {
     949        $('#tooltip').css('visibility', 'hidden');
     950    });
    824951}
    825952
    826953function search_regions(query)
     
    846973    {
    847974        out.append($('<tr><td>?</td><td>' + found[i].id + '</td><td>' + (found[i].duration*1000) + '</td></tr>'));
    848975    }
    849 }
    850  No newline at end of file
     976}
     977
     978function output_graph_for_type(type, useLog)
     979{
     980    // Search through out processed intervals for those matching type and keep them in a vector.
     981    var canvas = document.getElementById("typical");
     982    var context = canvas.getContext("2d");
     983    context.clearRect(0, 0, canvas.width, canvas.height);
     984    canvas._tooltips = [];
     985
     986    var events = [];
     987    var eventsByFrame = [];
     988    var useLogScale = useLog;
     989
     990    var max = 0;
     991    var avg = 0;
     992    var data = compute_intervals(g_data.threads[0].data.events, {});
     993    var currFrame = 0;
     994    for (var i = 0; i < data.intervals.length; i++)
     995    {
     996        let interval = data.intervals[i];
     997        if (interval.id == type)
     998        {
     999            let frame = currFrame;
     1000            eventsByFrame.push(0);
     1001            while (frame < data.frames.length && data.frames[frame].t1 < interval.t0)
     1002            {
     1003                eventsByFrame.push(0);
     1004                frame++
     1005            }
     1006            eventsByFrame[frame] += interval.duration;
     1007        }
     1008    };
     1009    let event_count = 0;
     1010    for (var i = 0; i < eventsByFrame.length; i++)
     1011    {
     1012        if (!eventsByFrame[i])
     1013            continue;
     1014        event_count++;
     1015        events.push(eventsByFrame[i]);
     1016        avg += +eventsByFrame[i];
     1017        if (eventsByFrame[i] > max)
     1018            max = eventsByFrame[i];
     1019    }
     1020    console.log(avg);
     1021    avg /= event_count;
     1022
     1023    if (useLogScale == undefined && max > avg * 3)
     1024        useLogScale = true;
     1025    events.sort();
     1026
     1027    var maxXScale = Math.ceil(max*20)/20;
     1028    var maxYScaleNb = 0;
     1029    var maxYScaleTime = 0;
     1030   
     1031    let p = 0;
     1032    for (var i = 0; i < canvas.width; i++)
     1033    {
     1034        let nbForX = 0;
     1035        let timeForX = 0;
     1036        while (events[p] < maxXScale*(i/canvas.width))
     1037        {
     1038            timeForX += events[p];
     1039            nbForX++;
     1040            p++;
     1041        }
     1042        if (Math.ceil(nbForX/100.0)*100.0 > maxYScaleNb)
     1043            maxYScaleNb = Math.ceil(nbForX/100.0)*100.0;
     1044        if (Math.ceil(timeForX*10)/10 > maxYScaleTime)
     1045            maxYScaleTime = Math.ceil(timeForX*10)/10;
     1046    }
     1047    p = 0;
     1048    var maxx = max * (events.length);
     1049    for (var i = 0; i < canvas.width; i++)
     1050    {
     1051        let nbForX = 0;
     1052        let timeForX = 0;
     1053
     1054
     1055        let i0 = i/canvas.width*maxXScale;
     1056        let i1 = (i+1)/canvas.width*maxXScale;
     1057
     1058        if (useLogScale)
     1059        {
     1060            i0 = maxXScale - Math.log10(1 + (1-i/canvas.width) * 9)*maxXScale;
     1061            i1 = maxXScale - Math.log10(1 + (1-(i+1)/canvas.width) * 9)*maxXScale;
     1062        }
     1063
     1064        while (events[p] < i1)
     1065        {
     1066            timeForX += events[p];
     1067            nbForX++;
     1068            p++;
     1069        }
     1070
     1071        context.globalCompositeOperation = "lighter";
     1072
     1073        context.beginPath();
     1074        context.fillStyle="rgba(0,0,255,1)";
     1075        context.fillRect(i,canvas.height,1,-timeForX/maxYScaleTime*canvas.height);
     1076
     1077        context.beginPath();
     1078        context.fillStyle="rgba(255,0,0,1)";
     1079        context.fillRect(i,canvas.height,1,-nbForX/maxYScaleNb*canvas.height);
     1080
     1081        canvas._tooltips.push({
     1082                'x0': i, 'x1': i+1,
     1083                'y0': 0, 'y1': canvas.height,
     1084                'text': function(nb,t) { return function() { return (nb + " events at " + Math.round(t*10000)/10 + "ms"); } }(nbForX,i1)
     1085            });
     1086    }
     1087    if (useLogScale)
     1088    {
     1089        for (var i = 1; i < 4; i++)
     1090        {
     1091            let xv = i/5;
     1092            let val = maxXScale - Math.log10(1 + (1-xv) * 9)*maxXScale;
     1093            context.beginPath();
     1094            context.lineWidth="1";
     1095            context.strokeStyle = "rgba(0,0,0,0.2)";
     1096            context.moveTo(xv*canvas.width,20);
     1097            context.lineTo(xv*canvas.width,canvas.height);
     1098            context.stroke();
     1099            context.fillStyle = "gray";
     1100            context.font = "10px Arial";
     1101            context.textAlign="center";
     1102            context.fillText(Math.round(val*1000) + "ms",xv*canvas.width,12);
     1103        };
     1104    }
     1105    set_tooltip_handlers(canvas);
     1106}
     1107
     1108
     1109function output_time_graph_for_type(type, useLog)
     1110{
     1111    // Search through out processed intervals for those matching type and keep them in a vector.
     1112    var canvas = document.getElementById("typical");
     1113    var context = canvas.getContext("2d");
     1114    context.clearRect(0, 0, canvas.width, canvas.height);
     1115    canvas._tooltips = [];
     1116
     1117    TimeByFrame = [];
     1118   
     1119    var max = 0;
     1120    var avg = 0;
     1121    var currFrame = 0;
     1122    var data = compute_intervals(g_data.threads[0].data.events, {});
     1123    for (var i = 0; i < data.intervals.length; i++)
     1124    {
     1125        TimeByFrame.push(0);
     1126        let interval = data.intervals[i];
     1127        if (interval.id == type)
     1128        {
     1129            let frame = currFrame;
     1130            while (frame < data.frames.length && data.frames[frame].t1 < interval.t0)
     1131                frame++
     1132            TimeByFrame[frame] += interval.duration;
     1133            if (TimeByFrame[frame] > max)
     1134                max = TimeByFrame[frame];
     1135            avg += interval.duration;
     1136            currFrame = frame;
     1137        }
     1138    };
     1139
     1140    for (var i = 0; i < data.frames.length; i++)
     1141    {
     1142        let xScale = data.frames.length/canvas.width;
     1143
     1144        let y = TimeByFrame[i]/max;
     1145        if (max > avg*5)
     1146            y = Math.log10(1 + TimeByFrame[i]/max * 9);
     1147
     1148        context.beginPath();
     1149        context.lineWidth="6";
     1150        context.fillStyle="rgba(0,0,255,1)";
     1151        context.fillRect(i/xScale,canvas.height,1/xScale,-y*canvas.height);
     1152
     1153        canvas._tooltips.push({
     1154                'x0': i/xScale, 'x1': (i+1)/xScale,
     1155                'y0': 0, 'y1': canvas.height,
     1156                'text': function(frame,t) { return function() { return ("Frame " + frame + ": " + t*1000 + "ms"); } }(i,TimeByFrame[i])
     1157            });
     1158    }
     1159    set_tooltip_handlers(canvas);
     1160}
     1161
     1162function recompute_choices(thread)
     1163{
     1164    var choices = document.getElementById("choices");
     1165    var types = {};
     1166    var data = compute_intervals(g_data.threads[thread].data.events, {});
     1167    for (let i = 0; i < data.intervals.length; i++)
     1168        types[data.intervals[i].id] = 0;
     1169   
     1170    var sorted_keys = Object.keys(types).sort();
     1171
     1172    for (let key in sorted_keys)
     1173    {
     1174        let type = sorted_keys[key];
     1175        let p = document.createElement("p");
     1176        p.textContent = type;
     1177
     1178        choices.appendChild(p);
     1179        p.onclick = function()
     1180        {
     1181            choices.parentNode.querySelector("button:first-child").onclick = function() { output_graph_for_type(type); };
     1182            choices.parentNode.querySelector("button:last-of-type").onclick = function() { output_time_graph_for_type(type); };
     1183
     1184            var allP = choices.querySelectorAll("p");
     1185            for (let item in allP)
     1186            {
     1187                allP[item] . className = "";
     1188            }
     1189            p.className = "active";
     1190            g_ActiveElement = p.textContent;
     1191        }
     1192    }
     1193}
     1194
     1195function compareTwoTypes(type, dataB)
     1196{
     1197    TimeByFrameA = [];
     1198    TimeByFrameB = [];
     1199   
     1200    var maxA = 0;
     1201    var maxB = 0;
     1202    var avgA = [];
     1203    var avgB = [];
     1204    var countA = 0;
     1205    var countB = 0;
     1206
     1207    var dataA = compute_intervals(g_data.threads[0].data.events, {});
     1208
     1209    let ComputePerFrame = function(data, TimeByFrame)
     1210    {
     1211        let currFrame = 0;
     1212        for (var i = 0; i < data.intervals.length; i++)
     1213        {
     1214            TimeByFrame.push(0);
     1215            let interval = data.intervals[i];
     1216            if (interval.id == type)
     1217            {
     1218                while (currFrame < data.frames.length && data.frames[currFrame].t1 < interval.t0)
     1219                    currFrame++
     1220                TimeByFrame[currFrame] += interval.duration;
     1221            }
     1222        };
     1223    }
     1224    ComputePerFrame(dataA,TimeByFrameA);
     1225    ComputePerFrame(dataB,TimeByFrameB);
     1226    let statA = {"avg":[],"max":0,"count":0};
     1227    let statB = {"avg":[],"max":0,"count":0};
     1228    let StatPerFrame = function(statRet, TimeByFrame)
     1229    {
     1230        for (var i = 0; i < TimeByFrame.length; i++)
     1231        {
     1232            if (!TimeByFrame[i])
     1233                continue;
     1234            statRet.avg.push(TimeByFrame[i]);
     1235            if (TimeByFrame[i] > statRet.max)
     1236                statRet.max = TimeByFrame[i];
     1237            statRet.count++;
     1238        }
     1239    }
     1240    StatPerFrame(statA,TimeByFrameA);
     1241    StatPerFrame(statB,TimeByFrameB);
     1242
     1243    // pick the median
     1244    statA.avg.sort();
     1245    statB.avg.sort();
     1246    statA.avg = statA.avg[Math.round(statA.avg.length/2)];
     1247    statB.avg = statB.avg[Math.round(statB.avg.length/2)];
     1248
     1249    if (!statA.avg)
     1250        statA.avg = 0;
     1251    if (!statB.avg)
     1252        statB.avg = 0;
     1253
     1254    let canvas = null;
     1255
     1256    // compare frame by frame.
     1257    // draw a canvas with two lines.
     1258    canvas = document.createElement("canvas");
     1259    canvas.width = 1000;
     1260    canvas.height = 400;
     1261    let context = canvas.getContext("2d");
     1262
     1263    context.globalCompositeOperation = "lighten";
     1264
     1265    let framesBetter = 0;
     1266    let betterSum = 0;
     1267    let avgDiffPerFrame = 0;
     1268
     1269    let maxAB = Math.max(statA.max,statB.max);
     1270    let nbFrames = Math.min(dataA.frames.length,dataB.frames.length);
     1271    let logScale = x => Math.log10(1 + x * 9);
     1272    let improvLine = 0,lastImprovLine = 0;
     1273    // Draw frame by frame for each;
     1274    for (var i = 0; i < nbFrames - 1; i++)
     1275    {
     1276        improvLine += TimeByFrameA[i] - TimeByFrameB[i];
     1277        avgDiffPerFrame += TimeByFrameA[i] - TimeByFrameB[i];
     1278        if (TimeByFrameA[i] < TimeByFrameB[i])
     1279            betterSum++;
     1280        else if (TimeByFrameA[i] > TimeByFrameB[i])
     1281            betterSum--;
     1282
     1283        if (TimeByFrameA[i] <= TimeByFrameB[i])
     1284            framesBetter++;
     1285
     1286        let y0 = TimeByFrameA[i]/maxAB * canvas.height;
     1287        let y1 = TimeByFrameA[i+1]/maxAB * canvas.height;
     1288        let x0 = i/dataB.frames.length*canvas.width;
     1289        let x1 = (i+1)/dataB.frames.length*canvas.width;
     1290        if (maxAB > statA.avg*5 && maxAB > statB.avg*5)
     1291        {
     1292            y0 = logScale(TimeByFrameA[i]/maxAB) * canvas.height;
     1293            y1 = logScale(TimeByFrameA[i+1]/maxAB) * canvas.height;
     1294        }
     1295        context.beginPath();
     1296        context.lineWidth=0.5;
     1297        context.moveTo(x0, canvas.height - y0);
     1298        context.lineTo(x1, canvas.height - y1);
     1299        context.strokeStyle = "red";
     1300        context.stroke();
     1301
     1302        y0 = TimeByFrameB[i]/maxAB * canvas.height;
     1303        y1 = TimeByFrameB[i+1]/maxAB * canvas.height;
     1304        x0 = i/dataB.frames.length*canvas.width;
     1305        x1 = (i+1)/dataB.frames.length*canvas.width;
     1306        if (maxAB > statA.avg*5 && maxAB > statB.avg*5)
     1307        {
     1308            y0 = logScale(TimeByFrameB[i]/maxAB) * canvas.height;
     1309            y1 = logScale(TimeByFrameB[i+1]/maxAB) * canvas.height;
     1310        }
     1311
     1312        context.beginPath();
     1313        context.lineWidth=0.5;
     1314        context.moveTo(x0, canvas.height - y0);
     1315        context.lineTo(x1, canvas.height - y1);
     1316        context.strokeStyle = "blue";
     1317        context.stroke();
     1318
     1319        context.beginPath();
     1320        context.lineWidth=0.5;
     1321        context.moveTo(x0, 200 + lastImprovLine/nbFrames*10000);
     1322        context.lineTo(x1, 200 + improvLine/nbFrames*10000);
     1323        context.strokeStyle = "green";
     1324        context.stroke();
     1325
     1326        lastImprovLine = improvLine;
     1327    }
     1328    let res = document.getElementById("ComparisonResult");
     1329    if (res)
     1330        res.parentNode.removeChild(res);
     1331    res = document.createElement("div");
     1332    res.id="ComparisonResult";
     1333
     1334    let smRound = (x, y) => Math.round(x*y*1000)/y;
     1335
     1336    res.innerHTML = "<h4>Comparison of " + type + "</h4>";
     1337    res.innerHTML += "<p>Max: " + smRound(statA.max,10) + "ms - " + statB.max + "ms</p>";
     1338    res.innerHTML += "<p>Average: " + statA.avg + " - " + statB.avg + "</p>";
     1339    res.innerHTML += "<p>Count: " + statA.count + " - " + statB.count + "</p>";
     1340    res.innerHTML += "<p>Average difference per frame: " + avgDiffPerFrame/nbFrames*1000 + "ms</p>"
     1341    res.innerHTML += "<p>In number of frames: " + betterSum + "</p>";
     1342    res.innerHTML += "<p>% of Frames where current is better: " + Math.round(framesBetter/nbFrames*100) + "%</p>";
     1343    res.appendChild(canvas);
     1344
     1345    document.querySelector("body").appendChild(res);
     1346}
     1347
     1348function compareWith(other)
     1349{
     1350    // let's compute. If we fail at any point, warn
     1351    try
     1352    {
     1353        let out_data_other;
     1354        var script = document.createElement('script');
     1355        window.profileDataCB = function(data)
     1356        {
     1357            script.parentNode.removeChild(script);
     1358
     1359            let events = concat_events(data.threads[0].data);
     1360            out_data_other = compute_intervals(events, {});
     1361            if (!out_data_other.intervals.length)
     1362            {
     1363                console.log("failed to parse intervals");
     1364                return;
     1365            }
     1366            // We have intervals. Let's compare them with the active
     1367            compareTwoTypes(g_ActiveElement, out_data_other);
     1368        };
     1369
     1370        script.innerHTML = other;
     1371        document.body.appendChild(script);
     1372
     1373    } catch(e)
     1374    {
     1375        console.log("Failed to Parse File");
     1376    }
     1377}
     1378
     1379function loadComparisonFile(evt) {
     1380    var file = evt.target.files[0];
     1381    if (!file)
     1382        return;
     1383    evt.target.value = null;
     1384    var reader = new FileReader();
     1385
     1386    reader.onload = (function(theFile) {
     1387        return function(e)
     1388        {
     1389            compareWith(e.target.result);
     1390        };
     1391    })(file);
     1392
     1393    reader.readAsText(file);
     1394}
     1395
     1396window.onload = function()  { document.getElementById('ComparisonFile').addEventListener('change', loadComparisonFile, false); }
     1397 No newline at end of file