AnyChart-NodeJS icon indicating copy to clipboard operation
AnyChart-NodeJS copied to clipboard

Chart producing server and memory leaks

Open Leximus opened this issue 6 years ago • 4 comments

I created a simple chart producing server (see svr.js below) witch accepts js code and feeds it to anychart and that returns SVG to requestor, run it on windows and after formatting <1000 charts (using format100charts.py, chart generation js is from anycharts examples) it has failed because of not being able to allocate memory. After that I monitored memory usage and run it with memory profiling. After producing 100 charts memory consumption raised by ~1Gb and went on until nodejs failed to allocate more.

Am I doing something wrong or is there a memory leak?

If you want I can attach memory profiling results but probably you can get better ones ( without obfuscation :) ) in ~30mins with two scripts below.

svr.js:

var chartFormattingServer = (function(){

	const http = require('http')
	const port = 3000

	var processChartCallback = function(response, body) {
		
		var anychartExport = require('anychart-nodejs');
		anychartExport.exportTo(body, 'svg').then(function(image) {
			response.end(image);
		}, function(generationError) {
			anychartExport=null;
		  console.log(generationError);
		});
		anychartExport=null;
	}

	const requestHandler = (request, response) => {
	   if (request.method == 'POST') {
			var body = '';

			request.on('data', function (data) {
				body += data;

				// Too much POST data, kill the connection!
				// 1e6 === 1 * Math.pow(10, 6) === 1 * 1000000 ~~~ 1MB
				if (body.length > 1e6)
					request.connection.destroy();
			});

			request.on('end', function () {
				processChartCallback(response, body)
			});
		}

	}

	const server = http.createServer(requestHandler)

	server.listen(port, (err) => {
	  if (err) {
		return console.log('something bad has happened', err)
	  }

	  console.log('server is listening on ${port}')
	})
})();

format100charts.py:

import requests
from time import time
from multiprocessing import Pool

ADDRESS = "localhost"
PORT = 3000

chart = """// create data set on our data
    var dataSet = anychart.data.set([
        ['Nail polish', 12814, 3054, 4376, 4229],
        ['Eyebrow pencil', 13012, 5067, 3987, 3932],
        ['Rouge', 11624, 7004, 3574, 5221],
        ['Lipstick', 8814, 9054, 4376, 9256],
        ['Eyeshadows', 12998, 12043, 4572, 3308],
        ['Eyeliner', 12321, 15067, 3417, 5432],
        ['Foundation', 10342, 10119, 5231, 13701],
        ['Lip gloss', 22998, 12043, 4572, 4008],
        ['Mascara', 11261, 10419, 6134, 18712]
    ]);

    // map data for the first series, take x from the zero area and value from the first area of data set
    var seriesData_1 = dataSet.mapAs({'x': 0, 'value': 1});

    // map data for the second series, take x from the zero area and value from the second area of data set
    var seriesData_2 = dataSet.mapAs({'x': 0, 'value': 2});

    // map data for the second series, take x from the zero area and value from the third area of data set
    var seriesData_3 = dataSet.mapAs({'x': 0, 'value': 3});

    // map data for the fourth series, take x from the zero area and value from the fourth area of data set
    var seriesData_4 = dataSet.mapAs({'x': 0, 'value': 4});

    // create area chart
    var chart = anychart.area3d();

    // force chart to stack values by Y scale.
    chart.yScale().stackMode('percent');

    // turn on the crosshair
    var crosshair = chart.crosshair();
    crosshair.enabled(true)
            .yStroke(null)
            .xStroke('#fff')
            .zIndex(99);
    crosshair.yLabel().enabled(false);
    crosshair.xLabel().enabled(false);

    // set chart title text settings
    chart.title('Regional ratio of cosmetic products sales');

    // set yAxis labels formatting, force it to add % to values
    chart.yAxis(0).labels().format('{%Value}%');

    // helper function to setup label settings for all series
    var setupSeries = function (series, name) {
        series.name(name)
                .stroke('3 #fff 1');
        series.markers().zIndex(100);
        series.hovered().stroke('3 #fff 1');
        series.hovered().markers()
                .enabled(true)
                .type('circle')
                .size(4)
                .stroke('1.5 #fff');
    };

    // temp variable to store series instance
    var series;

    // create first series with mapped data
    series = chart.area(seriesData_1);
    setupSeries(series, 'Florida');

    // create second series with mapped data
    series = chart.area(seriesData_2);
    setupSeries(series, 'Texas');

    // create third series with mapped data
    series = chart.area(seriesData_3);
    setupSeries(series, 'Arizona');

    // create fourth series with mapped data
    series = chart.area(seriesData_4);
    setupSeries(series, 'Nevada');

    // set interactivity and toolitp settings
    chart.interactivity().hoverMode('by-x');
    chart.tooltip().displayMode('union');

    // turn on legend
    chart.legend()
            .enabled(true)
            .fontSize(13)
            .padding([0, 0, 25, 0]);

    // set container id for the chart
    chart.container('container');

    // initiate chart drawing
    chart.draw();"""
startTime = time()
	
for i in range(99):
	iterStartTime = time()
	r = requests.post('http://' + ADDRESS + ':' + str(PORT), data = chart)
	iterEndTime = time()
	print (str(i) + " processed in " + str(iterEndTime-iterStartTime))
	
endTime = time()
	
print("Sequentially processed 100 charts in " + str(endTime-startTime))

Leximus avatar Nov 20 '19 11:11 Leximus

@Leximus If the post requests are executed async, it can overload the server (100 processes simultaneously). Try to execute requests synchronously, just for a test.

Shestac92 avatar Nov 22 '19 02:11 Shestac92

The problem is I'm already sending tasks one by one. And significant part of memory consumed by each formatting remains and not being freed. By looking at memory profiling it seems like some contexts of function calls remain even after formatting but that's more like a guess. Please try yourself, it's easily reproducible and shouldn't take too long.

Leximus avatar Nov 25 '19 14:11 Leximus

@Leximus Thank you for the clarification! We will check this out and update you as soon as we get results.

Shestac92 avatar Nov 26 '19 02:11 Shestac92

@Shestac92 Any updates on this?

Leximus avatar Jan 14 '20 18:01 Leximus