oracle / oraclejet

Oracle JET is a modular JavaScript Extension Toolkit for developers working on client-side applications.
https://oracle.com/jet
Other
494 stars 115 forks source link

Performance Issues with Gantt Drag and Drop on Firefox 81.0.1 (32bit) #69

Open psommer-hri opened 3 years ago

psommer-hri commented 3 years ago

Hello Oracle JET Team,

I have the following issue with Jet Version 9.1.0 and Firefox:

Current Behaviour: When dragging tasks in a gantt chart with many tasks on a row, the "ghost" following the mouse curser lags behind considerably. I could confirm this behaviour with Firefox 81.0.1 (32bit) in the Cookbook (see Steps to Reproduce). The issue does not appear in Firefox 78.3.1 ESR.

Expected Behaviour: No Lag.

Setps to Reproduce:

  1. Open the Gantt Chart Performance Demo in the Cookbook (https://www.oracle.com/webfolder/technetwork/jet/jetCookbook.html?component=gantt&demo=performance)
  2. Enable Drag and Drop in the demo.html file. (Set the dnd.move.tasks attribute to "enabled".)
  3. Hit the "Apply Changes" Button
  4. Set the "Number of Rows" to 10 and the "Number of Tasks Per Row" to 100 (or more)
  5. Drag any of the tasks around

On a side note: The Link to the JET Discussion Forum under the contributing guidelines (https://github.com/oracle/oraclejet/blob/master/CONTRIBUTING.md) is broken, which is why I am posting here.

Kind Regards, Philip

peppertech commented 3 years ago

Thanks for the heads up Philip, The Community Forums updated their software and broke all of the links as well as they are not properly doing redirects. The new URL is: https://community.oracle.com/tech/developers/categories/16409-oracle-jet

I'm slowly getting all of the links updated as we move toward the v9.2.0 release next week. I had completely forgotten about this one, so thanks again for pointing that out as well.

We'll take a look at the Gantt performance issue and get back to you as soon as possible. My guess is that it's a regression in Firefox itself since it's working fine in an old version and in Chrome still, but we'll dig in and see what we can find.

psommer-hri commented 3 years ago

Thanks for the prompt response. I'm happy to see the JET team is on top of things! My money would also be on Firefox, but I don't have the information required to open a proper issue with them.

wlouie1 commented 3 years ago

I’m on Firefox 81.0.1 64bit (Max OS), and the ghost image lag is not noticeable for me at 100 tasks per row. I do start noticing lag at 200+ tasks per row though on my machine. I have not tried the 32bit version or Firefox 78.3.1 ESR, but I also don’t see any console errors or obvious changes in the Firefox release notes between those versions that would cause differences.

Potential short-term workaround: I poked around Bugzilla, and saw a number of Firefox performance complaints on SVG filters (e.g. https://bugzilla.mozilla.org/show_bug.cgi?id=1578964). Can you try turning off SVG filters on the tasks and see if there are performance improvements? I see some improvements on my end, but perhaps the benefits would be more noticeable on the 32bit version of Firefox. You can do this by setting task-defaults.svg-style='[[{"filter":"none"}]]’ on the gantt. The downside is the task color will look a little darker.

psommer-hri commented 3 years ago

Hi wlouie1,

First off, I realized my testing on FF78 was done with a 64bit install, so I repeated the tests. It seems the difference is merely a performance gain for 64 bit. I then tested with an ancient Version (FF 63, 32bit) as well. I have attached profiles with 10 rows @250 tasks for comparison as well as a screen recording. Download it here: https://share.hri-itc.com/index.php/s/qbdsXMFboJYr628

The difference I found between the old and new FF Version was that the old Version seems to handle the view update differently. Old Versions have the ghost stutter and jump behind the cursor for few frames, while in the newer versions the movement is much smoother, but lags behind the cursor for longer. You can see this especially in the recordings.

Disabling SVG filters does seem to help a fair bit, but I would not consider it smooth.

Of course I originally did not stumble across this problem in the cookbook but in one of our live apps, which we integrate in Oracle JD Edwards. I have added a profile of the application here as well, but do not want to share the UI recording publicly. Should you need it, contact me at psommer@hri-itc.com. I sought to reproduce the effect in the cookbook and thought I found the culprit there.

What I found in the profile for the live app, in addition to the cookbook profiles, was that there are a lot of Promise callbacks executing from ojTable._draw originating from a DvTGantt._showDragFeedBackTooltip call. The live app has a custom tooltip template containing an ojTable element which is filled asynchronously via a Rest Call. However, the Rest call is not executed when dragging a task over the other (just as the tooltip does not appear). The data displayed in the table does not change, so I am unsure as to why there are this many updates.

Thanks for the help.

Kind Regards, Philip

wlouie1 commented 3 years ago

Thanks Philip, this is very helpful information! We can reproduce a similar lag on Windows using 32 bit Firefox; other browsers such as Chrome and Safari don't seem to have this issue. I've tried a number of things, but I don't think there's a good workaround you can use today. We have filed an issue on our end to look into optimizing this.

Just for fun, can you try using WebRender on Firefox to see if it helps? You can do that via about:config -> set gfx.webrender.all to true, and restart Firefox. I don't have access to a Windows machine right this moment, but I tried on 64 bit 81.0.1 Firefox on Mac OS for 500 tasks per row, and the lag reduced significantly for me. My understanding is Firefox will enable it by default at some point in a future release.

Regarding drag tooltips, do you have you any performance issues with having the tooltip (containing the ojTable) show up just by hovering over a task? What about when hovering over the task, and moving your cursor around within the bounds of the task?

psommer-hri commented 3 years ago

Hey wlouie1,

Thank you for looking into this. I didn't expect a quick solution, but thought you guys could track down and report the issue to Firefox better than me.

I don't see any significant improvements when using WebRenderer in FF 32 bit. (I restarted FF after switching the config setting.)

Regarding Tooltips, there is no noticeble lag (in the sense we saw before when dragging the task) when moving the cursor over the task.

Again, find recordings and profiles (all FF 81.0.1 32 Bit) here: https://share.hri-itc.com/index.php/s/KqbobqZ9MJPPkYF

In the Live App profile, I hovered the task two times: The first time at about 1300ms. At 1312ms you can see the Rest request fire from ProductionTask.prototype.getF3111Data and I guess it completes (via async callback) at 1483ms. (I'm not sure how asynchronous callbacks are displayed in the profiler.) After that, the values get cached and the request is not fired again.

What catches my eye is that OJET seems to create a new (table?) component during the hover each time the OnMouseMove event fires. E.g. compare the cookbook profile at 1508ms to the live app profile at 1881ms: The first block for the OnMouseMove is almost identical except for the getCustomTooltip call. That one also calls apply bindingsToDescendants (knockout? Is a new viemodel instantiated?). Then after that, the cookbook profile is "done", whereas the live app now seems to create a table component.

To double check, I also tried with the "Tooltip Template" Cookbook Demo and the CreateComponent fires there as well. (Albeit without the ojTable.queueTask calls after that. Probably because the demo uses a simpler oj-status-meter-gauge.)

The template for the live app looks like this:

<template slot="tooltipTemplate" data-oj-as="tooltip">
        <div class="tooltipContainer">
            <div>
                <div class="tooltipDocumentInfo">
                    <oj-bind-text value="[[tooltip.itemData.docoTooltip]]" :class="oj-text-color-primary"></oj-bind-text>
                </div>
                <div class="tooltipStatusInfo">
                    <oj-bind-text value="[[tooltip.itemData.srstTooltip]]" :class="oj-text-color-primary"></oj-bind-text>
                </div>
                <div class="tooltipItemInfo">
                    <oj-bind-text value="[[tooltip.itemData.litmTooltip]]" :class="oj-text-color-primary"></oj-bind-text>
                </div>
            </div>
            <oj-table :id="[[$uniqueId + '_tooltip_table']]" aria-label="Table of Components"
                data='[[tooltip.itemData.getPartsProvider()]]' columns-default='{"sortable": "disabled"}' columns='[{"headerText": "ITM",
                                                "field": "F3111_CPIL"},
                                                {"headerText": "Qty",
                                                "field": "F3111_UORG"},
                                                {"headerText": "UoM",
                                                "field": "F3111_UM"},
                                                {"headerText": "Lot",
                                                "field": "F3111_LOTN"},
                                                {"headerText": "Loc",
                                                "field": "F3111_LOCN"},
                                                {"headerText": "%",
                                                "field": "F3111_URAB"}
                                                ]' style='width: auto; height:auto;'>
            </oj-table>
            <div class="tooltipSalesOrderInfo">
                <oj-bind-text value="[[tooltip.itemData.soTooltip]]" :class="oj-text-color-primary"></oj-bind-text>
            </div>
            <div class="tooltipShipToInfo">
                <oj-bind-text value="[[tooltip.itemData.shipToTooltip]]" :class="oj-text-color-primary"></oj-bind-text>
            </div>
        </div>
    </template>

The getPartsProvider Method implements the caching (for getF3111Data call):

ProductionTask.prototype.getPartsProvider = function () {
    const self = this;

    if (!self.partsProvider) {
      self.parts = ko.observableArray([]);
      self.partsProvider = new ArrayDataProvider(self.parts, {
        idAttribute: 'F3111_CPIL',
      });
      self.getF3111Data();
    }
    return self.partsProvider;
  };

Kind Regards, Philip

wlouie1 commented 3 years ago

Hey Philip thanks again for the information and recordings, and for trying out WebRender. If we can narrow down a minimal repro case we'll report to Firefox. Regardless, we'll look into optimizing the drag feedback behavior across all browsers.

Regarding tooltips, our current implementation re-executes the template on every mouse move (and drag over) event as you observed. Whether we should limit re-executions is an optimization we'll need to look into. In the drag over case, the tooltip is likely not a major contributer to the lag you're observing; I tried disabling the tooltip in the cookbook demo, and still see the same amount of drag feedback lag on Firefox.

If you're not seeing any tooltip lag via task hover in your live app, then you probably don't need to worry much about it. But if this is something you want to optimize, I would suggest 1) replacing the relatively heavy oj-table with a normal HTML table/divs if possible, and/or 2) use a tooltip renderer function instead for greater caching flexibility (see also https://www.oracle.com/webfolder/technetwork/jet/jetCookbook.html?component=gantt&demo=tooltip). For example, instead of using the tooltip template slot, I can do something like this with knockout templates + tooltip renderer function that caches the template content between task hovers in https://www.oracle.com/webfolder/technetwork/jet/jetCookbook.html?component=gantt&demo=tooltipTemplate:

HTML:

<div id='gantt-container'>
    <oj-gantt
        ...
        tooltip.renderer="[[tooltipFunction]]">
        <!-- ... remove the tooltip template slot ...  -->
    </oj-gantt>

    <!-- same as original template, but replaced "tooltip" with "$context" and reference to view model with "$parent"  -->
    <script type="text/html" id="tooltipTemplate">
        <div>
            <div style = "float:left; padding: 10px 8px 10px 3px">
                <span style="font-weight: bold"><oj-bind-text value="[['Assigned: ' + $context.itemData.resource]]"></oj-bind-text></span><br/>
                <span><oj-bind-text value="[['Start Date: ' + $parent.dateConverter.format($context.data.start)]]"></oj-bind-text></span><br/>
                <span><oj-bind-text value="[['End Date: ' + $parent.dateConverter.format($context.data.end)]]"></oj-bind-text></span>
            </div>
            <oj-status-meter-gauge id="gauge"
                        min="0"
                        max="100"
                        value="{{$context.itemData.progressValue}}"
                        orientation="circular"
                        color="[[$context.color]]"
                        readonly
                        style=
                        "float:right;
                        padding-top:5px;
                        width:50px;
                        height:50px;">
            </oj-status-meter-gauge>
        </div>
    </script>
</div>

JS:

// add 'ojs/ojknockouttemplateutils' to require
require([...'ojs/ojconverter-datetime', 'ojs/ojknockouttemplateutils', 'ojs/ojknockout',...'],  
    function(..., DateTimeConverter, KnockoutTemplateUtils)
    {
      function ViewModel() 
      {
        ...
        var tooltipCache = { prevVisitedItem: null, prevTooltip: null };
        this.tooltipFunction = function(dataContext) {
          // only execute template when visiting a new task
          if (dataContext.itemData.id != tooltipCache.prevVisitedItem) {
            tooltipCache.prevVisitedItem = dataContext.itemData.id;
            tooltipCache.prevTooltip = KnockoutTemplateUtils.getRenderer('tooltipTemplate', false)(dataContext);
          }
          return tooltipCache.prevTooltip;
        }
        ...
      };
...

If we have updates we'll post here.

psommer-hri commented 3 years ago

Hey JET-Team,

It's been a while and the problem is still plaguing our application (under FF only). Redesigning the task/template structure would likely be major effort for us, so I thought I might raise the issue again. Any updates on your side?

Kind Regards, Philip