Chart producing server and memory leaks
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 If the post requests are executed async, it can overload the server (100 processes simultaneously). Try to execute requests synchronously, just for a test.
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 Thank you for the clarification! We will check this out and update you as soon as we get results.
@Shestac92 Any updates on this?