Ticket #4056: Profiler2.patch

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

Profiler2 core changes.

  • source/ps/Profiler2.cpp

     
    8080        std::stringstream stream;
    8181
    8282        std::string uri = request_info->uri;
    83         if (uri == "/overview")
     83
     84        if (uri == "/download")
    8485        {
     86            profiler->SaveToFile();
     87        }
     88        else if (uri == "/overview")
     89        {
    8590            profiler->ConstructJSONOverview(stream);
    8691        }
    8792        else if (uri == "/query")
     
    191196void CProfiler2::ShutDownHTTP()
    192197{
    193198    LOGMESSAGERENDER("Shutting down profiler2 HTTP server");
     199
    194200    if (m_MgContext)
    195201    {
    196202        mg_stop(m_MgContext);
     
    219225
    220226    ENSURE(!m_GPU); // must shutdown GPU before profiler
    221227
     228    SaveToFile();
     229
    222230    if (m_MgContext)
    223231    {
    224232        mg_stop(m_MgContext);
     
    301309}
    302310
    303311CProfiler2::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())
     312m_Profiler(profiler), m_Name(name), m_BufferPos0(0), m_BufferPos1(0), m_LastTime(timer_Time()), m_HeldDepth(0)
    305313{
    306314    m_Buffer = new u8[BUFFER_SIZE];
    307315    memset(m_Buffer, ITEM_NOP, BUFFER_SIZE);
     
    355363    Write(ITEM_ATTRIBUTE, buffer, 4 + len);
    356364}
    357365
     366size_t CProfiler2::ThreadStorage::HoldLevel()
     367{
     368    return m_HeldDepth;
     369}
    358370
     371void CProfiler2::ThreadStorage::PutOnHold()
     372{
     373    m_HeldDepth++;
     374    m_HoldBuffers[m_HeldDepth - 1].clear();
     375}
     376
     377void CProfiler2::ThreadStorage::HoldToBuffer()
     378{
     379    if (m_HeldDepth > 1)
     380    {
     381        // copy onto buffer below
     382        HoldBuffer& copied = m_HoldBuffers[m_HeldDepth - 1];
     383        HoldBuffer& target = m_HoldBuffers[m_HeldDepth - 2];
     384        if (target.pos + copied.pos > HOLD_BUFFER_SIZE)
     385            return; // too much data, too bad
     386
     387        memcpy(&target.buffer[target.pos], copied.buffer, copied.pos);
     388
     389        target.pos += copied.pos;
     390    }
     391    else if (m_HeldDepth)
     392    {
     393        u32 size = m_HoldBuffers[m_HeldDepth - 1].pos;
     394        u32 start = m_BufferPos0;
     395        if (start + size > BUFFER_SIZE)
     396        {
     397            m_BufferPos0 = size;
     398            COMPILER_FENCE;
     399            memset(m_Buffer + start, 0, BUFFER_SIZE - start);
     400            start = 0;
     401        }
     402        else
     403        {
     404            m_BufferPos0 = start + size;
     405            COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer
     406        }
     407        memcpy(&m_Buffer[start], m_HoldBuffers[m_HeldDepth - 1].buffer, size);
     408        COMPILER_FENCE; // must write m_BufferPos1 after m_Buffer
     409        m_BufferPos1 = start + size;
     410    }
     411    m_HeldDepth--;
     412}
     413void CProfiler2::ThreadStorage::ThrowawayHoldBuffer()
     414{
     415    if (!m_HeldDepth)
     416        return;
     417    m_HeldDepth--;
     418}
     419
    359420void CProfiler2::ConstructJSONOverview(std::ostream& stream)
    360421{
    361422    TIMER(L"profile2 overview");
     
    438499            pos += sizeof(item);
    439500            if (lastTime >= 0)
    440501            {
    441                 lastTime = lastTime + (double)item.dt;
    442                 visitor.OnEvent(lastTime, item.id);
     502                //lastTime = lastTime + (double)item.dt;
     503                visitor.OnEvent(lastTime + (double)item.dt, item.id);
    443504            }
    444505            break;
    445506        }
     
    450511            pos += sizeof(item);
    451512            if (lastTime >= 0)
    452513            {
    453                 lastTime = lastTime + (double)item.dt;
    454                 visitor.OnEnter(lastTime, item.id);
     514                //lastTime = lastTime + (double)item.dt;
     515                visitor.OnEnter(lastTime + (double)item.dt, item.id);
    455516            }
    456517            break;
    457518        }
     
    462523            pos += sizeof(item);
    463524            if (lastTime >= 0)
    464525            {
    465                 lastTime = lastTime + (double)item.dt;
    466                 visitor.OnLeave(lastTime, item.id);
     526                //lastTime = lastTime + (double)item.dt;
     527                visitor.OnLeave(lastTime + (double)item.dt, item.id);
    467528            }
    468529            break;
    469530        }
     
    596657    }
    597658    stream << "\n]});\n";
    598659}
     660
     661CProfile2SpikeRegion::CProfile2SpikeRegion(const char* name, double spikeLimit) : m_Name(name), m_Limit(spikeLimit)
     662{
     663    g_Profiler2.HoldMessages();
     664    COMPILER_FENCE;
     665    g_Profiler2.RecordRegionEnter(m_Name);
     666    m_StartTime = g_Profiler2.GetTime();
     667}
     668CProfile2SpikeRegion::~CProfile2SpikeRegion()
     669{
     670    double time = g_Profiler2.GetTime();
     671    g_Profiler2.RecordRegionLeave(m_Name);
     672    bool shouldWrite = time - m_StartTime > m_Limit;
     673
     674    g_Profiler2.StopHoldingMessages(shouldWrite);
     675}
  • source/ps/Profiler2.h

     
    124124    // TODO: what's a good size?
    125125    // TODO: different threads might want different sizes
    126126    static const size_t BUFFER_SIZE = 1024*1024;
     127    static const size_t HOLD_BUFFER_SIZE = 16 * 1024;
    127128
    128129    /**
    129130     * Class instantiated in every registered thread.
     
    153154            // (to save memory) without suffering from precision problems
    154155            SItem_dt_id item = { (float)(t - m_LastTime), id };
    155156            Write(type, &item, sizeof(item));
    156             m_LastTime = t;
     157            //m_LastTime = t;
    157158        }
    158159
    159160        void RecordFrameStart(double t)
     
    172173            va_end(argp);
    173174        }
    174175
     176        size_t HoldLevel();
     177        void PutOnHold();
     178        void HoldToBuffer();
     179        void ThrowawayHoldBuffer();
     180
    175181        CProfiler2& GetProfiler()
    176182        {
    177183            return m_Profiler;
     
    195201         */
    196202        void Write(EItem type, const void* item, u32 itemSize)
    197203        {
     204            if (m_HeldDepth > 0)
     205            {
     206                WriteHold(type, item, itemSize);
     207                return;
     208            }
    198209            // See m_BufferPos0 etc for comments on synchronisation
    199210
    200211            u32 size = 1 + itemSize;
     
    224235            m_BufferPos1 = start + size;
    225236        }
    226237
     238        void WriteHold(EItem type, const void* item, u32 itemSize)
     239        {
     240            u32 size = 1 + itemSize;
     241            if (m_HoldBuffers[m_HeldDepth-1].pos+ size > HOLD_BUFFER_SIZE)
     242                return; // we held on too much data, ignore the rest
     243       
     244            m_HoldBuffers[m_HeldDepth-1].buffer[m_HoldBuffers[m_HeldDepth-1].pos] = (u8)type;
     245            memcpy(&m_HoldBuffers[m_HeldDepth-1].buffer[m_HoldBuffers[m_HeldDepth-1].pos+ 1], item, itemSize);
     246
     247            m_HoldBuffers[m_HeldDepth-1].pos += size;
     248        }
     249
    227250        CProfiler2& m_Profiler;
    228251        std::string m_Name;
    229252
    230253        double m_LastTime; // used for computing relative times
    231254
     255        bool m_Hold; // are we holding messages until release/scrap?;
     256
    232257        u8* m_Buffer;
    233258
     259        struct HoldBuffer
     260        {
     261            friend class ThreadStorage;
     262        public:
     263            HoldBuffer()
     264            {
     265                buffer = new u8[HOLD_BUFFER_SIZE];
     266                memset(buffer, ITEM_NOP, HOLD_BUFFER_SIZE);
     267                pos = 0;
     268            }
     269            ~HoldBuffer()
     270            {
     271                delete[] buffer;
     272            }
     273            void clear()
     274            {
     275                pos = 0;
     276            }
     277            u8* buffer;
     278            u16 pos;
     279        };
     280
     281        HoldBuffer m_HoldBuffers[8];
     282        size_t m_HeldDepth;
     283
    234284        // To allow hopefully-safe reading of the buffer from a separate thread,
    235285        // without any expensive synchronisation in the recording thread,
    236286        // two copies of the current buffer write position are stored.
     
    327377        GetThreadStorage().Record(ITEM_ENTER, GetTime(), id);
    328378    }
    329379
     380    void RecordRegionEnter(const char* id, double time)
     381    {
     382        GetThreadStorage().Record(ITEM_ENTER, time, id);
     383    }
     384
    330385    void RecordRegionLeave(const char* id)
    331386    {
    332387        GetThreadStorage().Record(ITEM_LEAVE, GetTime(), id);
     
    346401    void RecordGPURegionLeave(const char* id);
    347402
    348403    /**
     404    * Hold onto messages until a call to release or write the held messages.
     405    */
     406    size_t HoldLevel()
     407    {
     408        return GetThreadStorage().HoldLevel();
     409    }
     410
     411    void HoldMessages()
     412    {
     413        GetThreadStorage().PutOnHold();
     414    }
     415
     416    void StopHoldingMessages(bool writeToBuffer)
     417    {
     418        if (writeToBuffer)
     419            GetThreadStorage().HoldToBuffer();
     420        else
     421            GetThreadStorage().ThrowawayHoldBuffer();
     422    }
     423
     424    /**
    349425     * Call in any thread to produce a JSON representation of the general
    350426     * state of the application.
    351427     */
     
    424500    {
    425501        g_Profiler2.RecordRegionLeave(m_Name);
    426502    }
     503protected:
     504    const char* m_Name;
     505};
     506
     507/**
     508* Scope-based enter/leave helper.
     509*/
     510class CProfile2SpikeRegion
     511{
     512public:
     513    CProfile2SpikeRegion(const char* name, double spikeLimit);
     514    ~CProfile2SpikeRegion();
    427515private:
    428516    const char* m_Name;
     517    double m_Limit;
     518    double m_StartTime;
    429519};
    430520
    431521/**
     
    455545 */
    456546#define PROFILE2(region) CProfile2Region profile2__(region)
    457547
     548#define PROFILE2_IFSPIKE(region, limit) CProfile2SpikeRegion profile2__(region, limit)
     549
    458550#define PROFILE2_GPU(region) CProfile2GPURegion profile2gpu__(region)
    459551
    460552/**
  • source/tools/profiler2/profiler2.html

     
    1212</style>
    1313
    1414<button onclick="refresh()">Refresh</button>
     15<button onclick="SaveAsFile()">Save as File</button>
     16
     17<button onclick="gather_stable_data()">Condense</button>
     18
    1519<br>
    1620<div id="timelines"></div>
    1721
  • source/tools/profiler2/profiler2.js

     
    5151
    5252var g_data;
    5353
     54var g_last_stable_frame = 0;
     55var g_stable_data = {};
     56var g_statistical_data = {};
     57
    5458function refresh()
    5559{
    5660    if (1)
    5761        refresh_live();
    5862    else
    59         refresh_jsonp('../../../binaries/system/profile2.jsonp');
     63        refresh_jsonp('C:/Users/ironweb-15/AppData/Local/0ad/logs/profile2.jsonp');
    6064}
    6165
    6266function concat_events(data)
     
    109113            threads.forEach(function(thread) {
    110114                refresh_thread(thread, callback_data);
    111115            });
     116            setTimeout(refresh, 2000);
    112117        },
    113118        error: function (jqXHR, textStatus, errorThrown) {
    114119            alert('Failed to connect to server ("'+textStatus+'")');
     120            refresh=refresh_jsonp
    115121        }
    116122    });
    117123}
     
    143149        }
    144150    });
    145151}
     152function gather_stable_data()
     153{
     154    if (!g_stable_data.threads)
     155    {
     156        g_stable_data = {};
     157        g_statistical_data = {};
     158        for (let thread in g_data.threads)
     159        {
     160            g_stable_data[thread] = {"frames" : [], "events" : []}
     161            g_statistical_data[thread] = {"evt_dur_avg" : {}, "evt_dur_total" : {}}
     162        }
     163    }
    146164
     165    for (let thread in g_data.threads)
     166    {
     167        let in_data = g_data.threads[thread].processed_events;
     168        if (!in_data)
     169            in_data = compute_intervals(g_data.threads[thread].data.events, {})
     170        if (!in_data)
     171            continue;
     172        let out_data = g_stable_data[thread];
     173        let startTime = 0;
     174
     175        for (let i = 0; i < in_data.frames.length; ++i)
     176            if (in_data.frames[i].t0 > g_last_stable_frame)
     177            {
     178                startTime = in_data.frames[i].t0;
     179                out_data.frames = out_data.frames.concat(in_data.frames.slice(i));
     180                break;
     181            }
     182        g_last_stable_frame = in_data.frames[in_data.frames.length-1].t1;
     183        // get all relevant events
     184        for (let evt in in_data.intervals)
     185        {
     186            let event = in_data.intervals[evt];
     187            let time = event.t0;
     188            if (time < startTime)
     189                continue;
     190
     191            if (!out_data.events[event.id])
     192            {
     193                out_data.events[event.id] = [];
     194                g_statistical_data[thread].evt_dur_avg[event.id] = event.duration;
     195                g_statistical_data[thread].evt_dur_total[event.id] = 0;
     196            }
     197            g_statistical_data[thread].evt_dur_avg[event.id] = g_statistical_data[thread].evt_dur_avg[event.id] * 0.99 + event.duration * 0.01;
     198            g_statistical_data[thread].evt_dur_total[event.id] += event.duration;
     199
     200            if (event.duration > 5e-3)
     201            {
     202                out_data.events.push(event);
     203            }
     204        }
     205    }
     206    var blob = new Blob([JSON.stringify(g_statistical_data)], {type: "application/json"});
     207    var url  = URL.createObjectURL(blob);
     208    window.open(url);
     209    var blob = new Blob([JSON.stringify(g_stable_data)], {type: "application/json"});
     210    var url  = URL.createObjectURL(blob);
     211    window.open(url);
     212}
     213
     214function SaveAsFile()
     215{
     216    $.ajax({
     217        url: 'http://127.0.0.1:8000/download',
     218        success: function () {
     219        },
     220        error: function (jqXHR, textStatus, errorThrown) {
     221        }
     222    });
     223}
     224
    147225function rebuild_canvases()
    148226{
    149227    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);
     228    g_data.canvas_zoom = $('<canvas width="1600" height="192"></canvas>').get(0);
     229    g_data.canvas_typical = $('<canvas width="600" height="600"></canvas>').get(0);
    151230    g_data.text_output = $('<pre></pre>').get(0);
    152231   
    153232    set_frames_zoom_handlers(g_data.canvas_frames);
     
    160239    });
    161240    $('#timelines').append(g_data.canvas_zoom);
    162241    $('#timelines').append(g_data.text_output);
     242    $('#timelines').append(g_data.canvas_typical);
    163243}
    164244
    165245function update_display(range)
     
    190270        set_zoom_handlers(processed_main, processed_data, thread.canvas, g_data.canvas_zoom);
    191271        set_tooltip_handlers(thread.canvas);
    192272    });
     273
     274    var processed_main = compute_intervals(main_events, {"numframes":50});
     275    display_typical_frame(processed_main, g_data.canvas_typical);
    193276 }
    194277
    195278function display_top_items(data, output)
     
    226309    var tmin, tmax;
    227310   
    228311    var frames = [];
    229     var last_frame_time = undefined;
     312    var last_frame_time_start = undefined;
     313    var last_frame_time_end = undefined;
    230314    for (var i = 0; i < data.length; ++i)
    231315    {
    232316        if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart')
    233317        {
    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;
     318            if (last_frame_time_end)
     319                frames.push({'t0': last_frame_time_start, 't1': last_frame_time_end});
     320            last_frame_time_start = data[i][1];
    238321        }
     322        if (data[i][0] == ITEM_LEAVE && data[i][2] == 'frame')
     323        {
     324            last_frame_time_end = data[i][1];
     325        }
    239326    }
    240327   
    241328    if (range.numframes)
     
    360447    var stack = [];
    361448    var lastT = 0;
    362449    var lastWasEvent = false;
     450 
    363451    for (var i = start; i <= end; ++i)
    364452    {
    365453        if (data[i][0] == ITEM_EVENT)
     
    372460        }
    373461        else if (data[i][0] == ITEM_ENTER)
    374462        {
    375 //            if (data[i][1] < lastT)
    376 //                console.log('Time went backwards: ' + (data[i][1] - lastT));
     463            if (data[i][1] < lastT)
     464                console.log('Time went backwards: ' + (data[i][1] - lastT));
    377465
    378466            stack.push({'t0': data[i][1], 'id': data[i][2]});
    379467
     
    382470        }
    383471        else if (data[i][0] == ITEM_LEAVE)
    384472        {
    385 //            if (data[i][1] < lastT)
    386 //                console.log('Time went backwards: ' + (data[i][1] - lastT));
     473            if (data[i][1] < lastT)
     474                console.log('Time went backwards: ' + (data[i][1] - lastT));
    387475
    388476            lastT = data[i][1];
    389477            lastWasEvent = false;
     
    390478           
    391479            if (!stack.length)
    392480                continue;
     481
    393482            var interval = stack.pop();
    394483
     484
     485
    395486            if (data[i][2] != interval.id && data[i][2] != '(ProfileStop)')
    396                 alert('inconsistent interval ids ('+interval.id+' / '+data[i][2]+')');
     487                console.log('inconsistent interval ids ('+interval.id+' / '+data[i][2]+')');
    397488
    398489            if (!g_used_colours[interval.id])
    399490                g_used_colours[interval.id] = new_colour(num_colours++);
     
    404495            interval.duration = interval.t1 - interval.t0;
    405496            interval.depth = stack.length;
    406497           
     498            if (data[i][2] == "Post Message" && interval.duration < 0.001)
     499            {
     500                console.log("wrongtime at " + JSON.stringify(data[i]));
     501                console.log("Stack level " + stack.length);
     502            }
    407503            intervals.push(interval);
    408504        }
    409505        else if (data[i][0] == ITEM_ATTRIBUTE)
     
    454550        }
    455551    };
    456552   
    457 //    var y_per_second = 1000;
    458     var y_per_second = 100;
     553    // log 100 scale, skip < 15 ms (60fps)
     554    var scale = x => 1 - Math.max(0, Math.log(1 + (x-15)/10) / Math.log(100));
    459555
    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 
    474556    ctx.strokeStyle = 'rgb(0, 0, 0)';
    475557    ctx.fillStyle = 'rgb(255, 255, 255)';
    476558    for (var i = 0; i < data.frames.length; ++i)
     
    481563        var x0 = xpadding + dx*(frame.t0 - tmin);
    482564        var x1 = x0 + dx*duration;
    483565        var y1 = canvas.height;
    484         var y0 = y1 - duration*y_per_second;
     566        var y0 = y1 * scale(duration*1000);
    485567       
    486568        ctx.beginPath();
    487569        ctx.rect(x0, y0, x1-x0, y1-y0);
     
    504586        });
    505587    }
    506588
     589    [16, 33, 200, 500].forEach(function(t) {
     590        var y1 = canvas.height;
     591        var y0 = y1 * scale(t);
     592        var y = Math.floor(y0) + 0.5;
     593
     594        ctx.beginPath();
     595        ctx.moveTo(xpadding, y);
     596        ctx.lineTo(canvas.width - xpadding, y);
     597        ctx.strokeStyle = 'rgb(255, 0, 0)';
     598        ctx.stroke();
     599        ctx.fillStyle = 'rgb(255, 0, 0)';
     600        ctx.fillText(t+'ms', 0, y-2);
     601    });
     602
    507603    ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)';
    508604    ctx.fillStyle = 'rgba(128, 128, 255, 0.2)';
    509605    ctx.beginPath();
     
    534630       
    535631        var x = time_to_x(event.t);
    536632        var y = 32;
     633
     634        if (x < 2)
     635            continue;
    537636       
    538637        var x0 = x;
    539638        var x1 = x;
     
    611710    var precision = -3;
    612711    while ((tmax-tmin)*Math.pow(10, 3+precision) < 25)
    613712        ++precision;
     713    if (precision > 20)
     714        precision = 20;
     715    if (precision < 0)
     716        precision = 0;
    614717    var ticks_per_sec = Math.pow(10, 3+precision);
    615718    var major_tick_interval = 5;
    616719    for (var i = 0; i < (tmax-tmin)*ticks_per_sec; ++i)
     
    634737        if (interval.tmax <= tmin || interval.tmin > tmax)
    635738            continue;
    636739
     740        var x0 = Math.floor(time_to_x(interval.t0));
     741        var x1 = Math.floor(time_to_x(interval.t1));
     742        //if (x1-x0 < 1)
     743        //    continue;
     744        var y0 = padding_top + interval.depth * BAR_SPACING;
     745        var y1 = y0 + BAR_SPACING;
     746       
    637747        var label = interval.id;
    638748        if (interval.attrs)
    639749        {
     
    642752            else
    643753                label += ' [...]';
    644754        }
    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        
     755
    650756        ctx.fillStyle = interval.colour;
    651757        ctx.strokeStyle = 'black';
    652758        ctx.beginPath();
     
    713819    ctx.restore();
    714820}
    715821
     822function display_typical_frame(in_data, canvas)
     823{
     824    canvas._tooltips = [];
     825
     826    var ctx = canvas.getContext('2d');
     827    ctx.clearRect(0, 0, canvas.width, canvas.height);
     828    ctx.save();
     829
     830    ctx.font = '12px sans-serif';
     831
     832    var xpadding = 8;
     833    var padding_top = 40;
     834    var width = canvas.width - xpadding*2;
     835    var height = canvas.height - padding_top - 4;
     836       
     837    function time_to_x(t)
     838    {
     839        return xpadding + (t - tmin) / (tmax - tmin) * width;
     840    }
     841   
     842    function x_to_time(x)
     843    {
     844        return tmin + (x - xpadding) * (tmax - tmin) / width;
     845    }
     846
     847    ctx.save();
     848
     849    var totalTime = {};
     850    var data = {};
     851    var tt = 0;
     852    var accrued = {};       
     853    for (var i = 0; i < in_data.intervals.length; ++i)
     854    {
     855        var interval = in_data.intervals[i];
     856        let label = interval.id;
     857        if (!totalTime[label])
     858        {
     859            totalTime[label] = [0,0];
     860            data[label] = interval;
     861            tt = 0;
     862            accrued[interval.depth] = 0;
     863        }
     864        if (interval.depth === 0)
     865            tt += interval.duration;
     866        totalTime[label][0]++;
     867        totalTime[label][1]+=interval.duration;
     868    }
     869
     870    for (let i in totalTime)
     871    {
     872        var interval = totalTime[i];
     873        var x0 = xpadding + (width/5) * data[i].depth;
     874        var x1 = x0 + (width/5);
     875        var y0 = padding_top + accrued[data[i].depth]/tt * height;
     876        var y1 = y0 + interval[1]/tt * height;
     877        accrued[data[i].depth] += interval[1];
     878
     879        if (interval[1]/tt[data[i].depth] * height < 1)
     880            continue;
     881
     882        var label = i;
     883
     884        ctx.fillStyle = data[i].colour;
     885        ctx.strokeStyle = 'black';
     886        ctx.beginPath();
     887        ctx.rect(x0-0.5, y0-0.5, x1-x0, y1-y0);
     888        ctx.fill();
     889        ctx.stroke();
     890        ctx.fillStyle = 'black';
     891        ctx.fillText(label, x0+2, y0+16-4, Math.max(1, x1-x0-4));
     892       
     893        canvas._tooltips.push({
     894            'x0': x0, 'x1': x1,
     895            'y0': y0, 'y1': y1,
     896            'text': function(i) { return function() {
     897                var t = '<b>' + data[i].id + '</b><br>';
     898                t += 'Length: ' + time_label(data[i].duration) + '<br>';
     899                if (data[i].attrs)
     900                {
     901                    data[i].attrs.forEach(function(attr) {
     902                        t += attr + '<br>';
     903                    });
     904                }
     905                return t;
     906            }} (i)
     907        });
     908
     909    }   
     910    ctx.restore();
     911}
     912
    716913function set_frames_zoom_handlers(canvas0)
    717914{
    718915    function do_zoom(event)
     
    723920        var relativeY = event.pageY - this.offsetTop;
    724921       
    725922//        var width = 0.001 + 0.5 * relativeY / canvas0.height;
    726         var width = 0.001 + 5 * relativeY / canvas0.height;
     923        var width = relativeY / canvas0.height;
     924        width = width*width;
     925        width *= zdata.x_to_t(canvas0.width)/10;
    727926
    728927        var tavg = zdata.x_to_t(relativeX);
    729928        var tmax = tavg + width/2;
     
    759958        }
    760959       
    761960        var relativeX = event.pageX - this.offsetLeft;
    762         var relativeY = event.pageY - this.offsetTop;
    763         var width = 8 + 64 * relativeY / canvas0.height;
     961        var relativeY = (event.pageY + this.offsetTop) / canvas0.height;
     962        relativeY = relativeY - 0.5;
     963        relativeY *= 10;
     964        relativeY *= relativeY;
     965        var width = relativeY / canvas0.height;
     966        width = width*width;
     967        width = 6 + width * x_to_time(canvas0.width)/10;
    764968        var zoom = { tmin: x_to_time(relativeX-width/2), tmax: x_to_time(relativeX+width/2) };
    765969        display_hierarchy(main_data, data, canvas0, hdata.range, zoom);
    766970        display_hierarchy(main_data, data, canvas1, zoom, undefined);