AnsweredAssumed Answered

Turning the Layer List widget on causes performance degradation

Question asked by hrastche on Mar 22, 2017
Latest reply on Oct 26, 2017 by cynthiak

Greetings, I came across this issue as the layers in my web map kept growing: turning the Layer List widget (LLW) on causes a longer and longer delay between the map.setExtent call and the actual AGS request.

 

It happens under Web AppBuilder 2.2 and 2.3 with AGS 10.3.1.

Interestingly it does not happen under Web AppBuilder Portal Edition (Portal 10.3.1).

 

Has anyone had a similar experience and if so can you please share how it was resolved (short of reducing the number of layers in the map)?

 

Here are all the details.

 

This is what one zoom call looks like with LLW off.

    • setExtent - mapUpdateStart ------ mapUpdateEnd

This is what one zoom call looks like with LLW on.

    • setExtent -------------------------------- mapUpdateStart ------ mapUpdateEnd

 

Duration is represented by dashes like so ---, the more dashes there are the longer the duration (1 dash = 1 second).

setExtent is when map.setExtent call is made in the WAB application.

mapUpdateStart is when the map update starts and the application makes the call to AGS to fetch the images for all layers, i.e. map.on('update-start', ...

mapUpdateEnd is when all layer requests have received their responses and have been updated on the map, i.e. map.on('update-end',...

 

Here are the averages from a few apps with LLW on:

App

Number of layers

in web map

mapUpdateStart -

setExtent (sec.)

mapUpdateStop -

mapUpdateStart (sec.)

mapUpdateStop -

setExtent (sec.)

11665203.723.7
24902.12.74.8
32380.80.31.1

You can see in the above that the more layers there are in a web map (regardless of whether they are on or off), the longer it takes for the application to traverse all layers before it even makes the request to AGS.

 

Here are the averages from the same apps with the LLW off:

App

Number of layers

in web map

mapUpdateStart -

setExtent (sec.)

mapUpdateStop -

mapUpdateStart (sec.)

mapUpdateStop -

setExtent (sec.)

116650.53.13.6
24900.73.23.9
32380.20.30.5

The application spends hardly any time thinking about it (0.2-0.7 sec.) before it sends out the AGS request.

 

Here are the averages for app 1 under Web AppBuilder Portal Edition:

App

Number of layers

in web map

mapUpdateStart -

setExtent (sec.)

mapUpdateStop -

mapUpdateStart (sec.)

mapUpdateStop -

mapUpdateStart (sec.)

11665 LLW on0.51.11.6
11665 LLW off0.41.41.8

Interestingly, LLW on or off does not have any effect when the same web map was added to an application built with the built-into Portal for ArcGIS WAB (10.3.1). I compared the WAB-PE LLW and the WAB-DE LLW source code and it is indeed very different.

 

Looking at the CPU profile of a single extent change request in Chrome pointed to the traversal function on line 126 in the …\jimu.js\LayerInfos\LayerInfo.js file which is part of the core rather than the LLW.

    1. It calls itself recursively for all sublayers in every group layer.
    2. For every map extent change (zoom in/out but not pan) having LLW on causes all layers to be traversed first before the actual request is made. 
    3. Once the LLW is on, closing it down does not stop the performance issue.

 

CPU profile of a single map extent request with LLW on.

I have a suspicion that this behaviour might have been introduced in the WAB-DE 2.x editions, as (from memory) this was not happening under WAB-DE 1.x (the number of layers were almost as many back then).

 

My test method was to write JavaScript code which:

  1. Can be run within the Developer console of the browsers after the application has loaded.
  2. Records a set of extent changes.
  3. Plays back the extent changes and records the time taken between the setExtent request, the start of the map update and the end of the map update.
  4. Summarises the captured metrics.
  5. I also monitored network traffic, memory and CPU usage within the Developer console.
  6. I ran the tests in Firefox Developer edition, then reproduced a subset of the results in Chrome and IE just to make sure that the results are consistent between browsers.
  7. I modified the LLW widget code to count traversal calls for some of the 2.3 apps.

 

This is the code I pasted in the web console of FF, IE, Chrome to get the above numbers.

 

var WidgetManager = require("jimu/WidgetManager");
var Extent = require("esri/geometry/Extent");
var wm = WidgetManager.getInstance();
var map = wm.getAllWidgets()[0].map;

//Fires when one or more layers begins updating their content. There is a gap between the set-extent and update-start event and it can be quite large.
//Fires after layers that are updating their content have completed.
var listener1 = map.on('update-start',
    function(e){
        startTime = new Date().getTime()/1000;
        updateStarts.push(startTime);
        console.log('start: ' + new Date().getTime()/1000);
    });
var listener2 = map.on('update-end',
    function(e){
        endTime = new Date().getTime()/1000;
        updateEnds.push(endTime);
        console.log( metrics1.length, 'stop: ' + (endTime - startTime), endTime - startExtentTime, startTime - startExtentTime, zzz);
        metrics1.push(endTime - startTime);
        metrics2.push(endTime - startExtentTime);
        if (metrics1.length == extents.length){              
            console.log(metrics1);
            console.log(metrics2);
            calculateStats();
        }
    });
 

var breakPeriod = 10; //seconds allowed for the operation to complete before the next operation is called
//this needs to be greater than the longest possible response time otherwise the results may be skewed

extents = [{"xmin":16675516.081326026,"ymin":-3434556.727452047,"xmax":16678386.052872181,"ymax":-3433499.7466703802,"spatialReference":{"wkid":102100}} ,
{"xmin":16676258.057948027,"ymin":-3434093.029386024,"xmax":16677693.043721423,"ymax":-3433564.538995073,"spatialReference":{"wkid":102100}} ,
{"xmin":16676723.39821615,"ymin":-3433886.261252783,"xmax":16677440.89110253,"ymax":-3433622.0160574247,"spatialReference":{"wkid":102100}} ,
{"xmin":16676994.846705677,"ymin":-3433865.547116706,"xmax":16677174.219927272,"ymax":-3433799.4858178664,"spatialReference":{"wkid":102100}} ,
{"xmin":16676367.040429777,"ymin":-3434096.7616627617,"xmax":16677802.026203172,"ymax":-3433568.271271811,"spatialReference":{"wkid":102100}} ,
{"xmin":16665604.64713091,"ymin":-3438060.439594304,"xmax":16688564.419502039,"ymax":-3429604.5933402684,"spatialReference":{"wkid":102100}} ,
{"xmin":16680025.567412717,"ymin":-3436310.1510793893,"xmax":16682895.538958872,"ymax":-3435253.1702977223,"spatialReference":{"wkid":102100}} ,
{"xmin":16680207.403936861,"ymin":-3436121.7457480263,"xmax":16681642.389710257,"ymax":-3435593.2553570755,"spatialReference":{"wkid":102100}} ,
{"xmin":16680648.260469358,"ymin":-3435925.726571889,"xmax":16681365.753355738,"ymax":-3435661.481376531,"spatialReference":{"wkid":102100}} ,
{"xmin":16675267.063819608,"ymin":-3437907.5655377777,"xmax":16686746.950005488,"ymax":-3433679.6424106425,"spatialReference":{"wkid":102100}} ,
{"xmin":16678862.58997068,"ymin":-3437970.2677874863,"xmax":16681732.561516834,"ymax":-3436913.2870058194,"spatialReference":{"wkid":102100}} ,
{"xmin":16680329.971906213,"ymin":-3437658.100158295,"xmax":16681047.464792592,"ymax":-3437393.854962937,"spatialReference":{"wkid":102100}} ];

function calculateStats(){
    script = require('dojo/io/script');
    var deferred = script.get({url:'//cdn.jsdelivr.net/jstat/latest/jstat.min.js'});
    deferred.then(function() {
        s1=jStat(metrics1);
        s2=jStat(metrics2);
        console.log(s1.min(), s1.mean(), s1.median(), s1.max(), s1.sum(), s1.stdev(), metrics1.length);
        console.log(s2.min(), s2.mean(), s2.median(), s2.max(), s2.sum(), s2.stdev(), metrics2.length);
        var m1 = [];
        var m2 = [];
        for (var i=0; i<updateEnds.length; ++i){
            m1.push(updateEnds[i] - updateStarts[i]);
            m2.push(updateEnds[i] - extentStarts[i]);
        }
        s1=jStat(m1);
        s2=jStat(m2);
        console.log(s1.min(), s1.mean(), s1.median(), s1.max(), s1.sum(), s1.stdev(), m1.length);
        console.log(s2.min(), s2.mean(), s2.median(), s2.max(), s2.sum(), s2.stdev(), m2.length);
    });
}


function setExtent(i){
    var e = new Extent(extents[i]);
    startExtentTime = new Date().getTime()/1000;
    extentStarts.push(startExtentTime);
    map.setExtent(e);
    startExtentTime2 = new Date().getTime()/1000;
    console.log('before after setExtent', startExtentTime2 - startExtentTime);
    if (i == extents.length){
        //we are done so remove the listeners so that they don't double up
        //listener1.remove();
        //listener2.remove();
    }
}

extentStarts = [];
updateStarts = [];
updateEnds = [];
metrics1 = [];
metrics2 = [];
for (i=0;i<extents.length; i++){
    setTimeout(setExtent.bind(null, i), breakPeriod*1000*i);
}

 

Any help will be greatly appreciated! Derek Law

 

Thanks,
Stoyan.

Outcomes