Time web requests in node.js - node.js

Given Node's async nature it is difficult to time a series of web requests. How would I fire off 100 webrequests and figure out how long each individual request takes? Knowing the OS will only allow a few concurrent web request, how do I get the timeing for each individual webrequest, removing the time spent waiting for the other connections to complete. I was hoping the socket event was fired when the request launched but it seems that the socket event is fired after the connection has been established.
var http = require('http');
var urls = [
'/cameron',
'/sara',
'...',
// Time a url collection.
function timeUrl(url, calback) {
var options = {
host: 'www.examplesite.com',
port: 80,
path: ''
};
var times = [];
times.push({'text': 'start', 'time':Date.now()});
http.get(options, function(res) {
times.push({'text': 'response', 'time':Date.now()});
var result = '';
res.on('data', function(chunk) {
result += chunk.length ;
// result += chunk;
});
res.on('end', function() {
times.push({'text': 'end', 'time': Date.now(), 'body': result, 'statusCode': res.statusCode}); // ,
calback(times);
});
}).on('error', function(e) {
calback();
console.log("Got error: " + e.message);
times.push({'error':Date.now()});
}).on('socket', function (response) {
times.push({'text': 'socket', 'time':Date.now()});
});
}
for (var i = 0; i < urls.length; i++) {
var url = urls[i];
timeUrl(url, function(times) {
console.log(url);
for (var i = 0; i < times.length; i++) {
console.log(times[i].text, times[i].time - times[1].time , 'ms');
}
console.log('statusCode:', times[times.length -1].statusCode, 'Response Size:', times[times.length -1].body);
console.log('-');
});
}

If you're worried about OS concurrency just introduce maximum concurrency (throttling) into your requests instead of trying to guess when exactly the OS has started. I'm skipping over some minor details like error handling and using the excellent async.js library:
var http = require('http')
, async = require('async')
, CONCURRENCY = 5 // edit to fit your OS concurrency limit
, results = {}
, urls = [
'/cameron',
'/sara',
'/...'
];
// Time a url collection.
function timeUrl(url, callback) {
var options = { host: 'www.examplesite.com', port: 80 }
, start = Date.now()
, socket = null;
options.path = url;
http.get(options, function(res) {
var response = Date.now()
, size = 0;
res.on('data', function(chunk) { size += chunk.length; });
res.on('end', function() {
var end = Date.now();
results[url] = { start: start, socket: socket, response: response, end: end, size: size };
callback();
});
}).on('error', function(e) {
results[url] = { start: start, socket: socket, error: Date.now(), stack: e };
callback();
}).on('socket', function () {
socket = Date.now();
});
}
async.forEachLimit(urls, CONCURRENCY, timeUrl, function() {
console.log(JSON.stringify(results));
});

For ease of use, doing what you seem to want to do, I've not seen anything beat Nodetime.

Related

Nodejs generating duplicate http-requests

Hope some can help with my issue. i'm using below nodejs code from this SAP Tutorial to read Sensor values post them per HTTP. All works pretty fine, but for the fact that every record is posted twice(see Screenshot). i'm not versed with server-side JS and don't know why the duplicates.Agreed, the values not aways the same, but for further processing i'd like to have single datasets per timestamp. Could someone please help me locate the issue and if possible, provide a solution/workaround?
Also the script reads and transmits the data every 10s. Am looking for a way to set the interval to maybe 3mins. I would appreciate every bit of help here as well
/* sensorTag IR Temperature sensor example
* Craig Cmehil, SAP SE (c) 2015
*/
/* Choose the proper HTTP or HTTPS, SAP Cloud Platformrequires HTTPS */
var http = require('https');
var SensorTag = require('sensortag');
var lv_temp;
var lv_humid;
var lv_deviceid = "";
var DEBUG_VALUE = true;
var xtimestamp;
var date = new Date();
var time = date.getTime ();
// SAP Cloud Platform connection details
var portIoT = 443;
var pathIoT = '/com.sap.iotservices.mms/v1/api/http/data/';
var hostIoT = 'iotmmsXXXXXXXXXXtrial.hanatrial.ondemand.com';
var authStrIoT = 'Bearer XXXXXXXXXXXX';
var deviceId = 'XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX';
var messageTypeID = 'XXXXXXXXXXXX';
var options = {
host: hostIoT,
port: portIoT,
path: pathIoT + deviceId,
agent: false,
headers: {
'Authorization': authStrIoT,
'Content-Type': 'application/json;charset=utf-8',
'Accept': '*/*'
},
method: 'POST',
};
/***************************************************************/
/* Coding to access TI SensorTag and values of various sensors */
/***************************************************************/
console.log("If not yet activated, then press the power button.");
SensorTag.discover(function(tag) {
tag.on('disconnect', function() {
console.log('disconnected!');
process.exit(0);
});
function connectExecute() {
console.log('Connect Device and Execute Sensors');
tag.connectAndSetUp(enableSensors);
}
function enableSensors() {
/* Read device specifics */
tag.readDeviceName(function(error, deviceName) {
console.log('Device Name = ' + deviceName);
});
tag.readSystemId(function(error, systemId) {
console.log('System ID = ' + systemId);
lv_deviceid = systemId;
});
tag.readSerialNumber(function(error, serialNumber) {
console.log('Serial Number = ' + serialNumber);
});
tag.readFirmwareRevision(function(error, firmwareRevision) {
console.log('Firmware Rev = ' + firmwareRevision);
});
tag.readHardwareRevision(function(error, hardwareRevision) {
console.log('Hardware Rev = ' + hardwareRevision);
});
tag.readHardwareRevision(function(error, softwareRevision) {
console.log('Software Revision = ' + softwareRevision);
});
tag.readManufacturerName(function(error, manufacturerName) {
console.log('Manufacturer = ' + manufacturerName);
});
/* Enable Sensors */
console.log("Enabling sensors:");
console.log('\tenableIRTemperatureSensor');
tag.enableIrTemperature(notifyMe);
console.log('\tenableHumidity');
tag.enableHumidity(notifyMe);
console.log("*********************************************");
console.log(" To stop press both buttons on the SensorTag ");
console.log("*********************************************");
}
function notifyMe() {
tag.notifySimpleKey(listenForButton);
setImmediate(function loop () {
tag.readIrTemperature(function(error, objectTemperature, ambientTemperature){
lv_obj = objectTemperature.toFixed(1);
lv_ambient = ambientTemperature.toFixed(1);
});
tag.readHumidity(function(error, temperature, humidity) {
lv_temp = temperature.toFixed(1);
lv_humid = humidity.toFixed(1);
});
if(DEBUG_VALUE)
console.log("Sending Data: " + lv_deviceid + " " + lv_temp + " " + lv_humid);
setSensorData(lv_temp, lv_humid);
setTimeout(loop, 10000);
});
}
function listenForButton() {
tag.on('simpleKeyChange', function(left, right) {
if (left && right) {
tag.disconnect();
}
});
}
connectExecute();
});
/******************************************************************/
/* FUNCTION to get Temperature from the Sensor & update into HANA */
/******************************************************************/
function setSensorData(lv_temp,lv_humid){
date = new Date();
time =date.getTime();
var data = {
"mode":"sync",
"messageType": messageTypeID,
"messages": [{
"timestamp": time,
"temperature": lv_temp,
"humidity": lv_humid
}]
};
var strData = JSON.stringify(data);
if(DEBUG_VALUE)
console.log("Data: " + strData);
if(strData.length > 46){
if(DEBUG_VALUE)
console.log("Sending Data to server");
/* Process HTTP or HTTPS request */
options.agent = new http.Agent(options);
var request_callback = function(response) {
var body = '';
response.on('data', function (data) {
body += data;
});
response.on('end', function () {
if(DEBUG_VALUE)
console.log("REQUEST END:", response.statusCode);
});
response.on('error', function(e) {
console.error(e);
});
}
var request = http.request(options, request_callback);
request.on('error', function(e) {
console.error(e);
});
request.write(strData);
request.end();
}else{
if(DEBUG_VALUE)
console.log("Incomplete Data");
}
}
It should only be posting once to the system but twice to the screen.
In the function notifyMe you need to change the line
setTimeout(loop, 10000);
Change the number to the interval you want it to delay before posting again.

Synchronous http request in node js?

I'm looking for a simple way to perform synchronous http-requests in node.js, but it's still getting async responses ...
I've realised that node.js is recommended to async jobs, but in my case,
I need the synchronous response to call other functions that use this data, if it's null/undefined, I can't proceed with the flow...
What's the better way to do that?
Here's my code:
function callCellId(data) {
console.log("Data: " + data);
var towers = [],
rscp = [];
var request = require('sync-request');
for (var x = 0; x < data.length; x++) {
console.log("Request data: \n");
rscp[x] = data[x].rscp;
var res = request('POST', 'http://opencellid.org/cell/get?key=xxxxx&mcc=' + data[x].mcc + '&mnc=' + data[x].mnc + '&lac=' + data[x].LAC + '&cellid=' + data[x].cellID + '&format=json');
console.log("loop " + x);
data = res.getBody().toString();
console.log("rsp: " + data);
towers[x] = {
'latitude': data.lat,
'longitude': data.lon,
'rscp': rscp[x],
'signal': data.averageSignalStrength
};
}
console.log("Content for triangulation" + JSON.stringify(towers));
return towers;
}
Using async in a loop cloud be tricky.
I solved this without external libraries using generators:
LoopOperation: function() {
//define generator with the main loop
var loopIterator = function*() {
for (var index = 0; index < 10; index++) {
var result = yield asyncOperation( (res) => loopIterator.next(res)); //do something asyc and execute () => loopIterator.next() when done as callback
console.log(result);
}
}();
loopIterator.next(); //start loop
}
Since the nodejs nature is async, every time we need some sync call (like this nested request stack), we're able to use promises
"A Promise is an object representing the eventual completion or failure of an asynchronous operation"
reference
I.E:
const request = require('request-promise');
function callCellId(data) {
let towers = [];
let options = {
url: 'http://opencellid.org/cell/get',
method: 'POST',
json: true
};
data.forEach(location => {
options.body = {
key: 'YOUR_PRIVATE_KEY',
mcc: location.mcc,
mnc: location.mnc,
lac: location.lac,
cellId: location.cellID
}
request(options).then(cellInfo => {
towers.push({
latitude: cellInfo.lat,
longitude: cellInfo.lon,
rscp: location.rscp,
signal: cellInfo.averageSignalStrength
});
}).catch(err => {
console.log('Could not get cellId Info for',location);
console.log(err);
});
});
return towers;
}

nodejs read file and make http request

There is a Nodejs script to read a group of files one by one. And for each file, read the documents line by line, after read a line, it will make an http post require to send the line to a remote server. Then read the next line. The question is the script will miss some lines.
Thank you.
It seems that lr.pause(); just hide line event, instead of pause the read file process.
var fs = require('fs');
var http = require('http');
var JSON = require('JSON');
var S = require('string');
var uuid = require('node-uuid');
var readline = require('readline');
var httpsync = require('httpsync');
var LineByLineReader = require('line-by-line');
var sleep = require('sleep');
function postES(_path,data,id,lr){
var post_data = JSON.stringify(data);
var post_options = {
host: _host,
port: _port,
path: _path,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': post_data.length
}
};
var post_req = http.request(post_options, function(res) {
res.setEncoding('utf8');
res.on('data', function(data) {
console.log(data);
});
res.on('end', function() {
console.log("end");
// resume read line
lr.resume();
});
});
post_req.on('error', function(data) {
console.log("error,post."+data+post_data);
// resume read line
lr.resume();
});
post_req.write(post_data);
post_req.end();
}
function readlineFunSession(line,id,lr) {
var _data={};
// compose _data object
postES('/cs/session/'+_data["sessionid"],_data,id,lr);
}
function readfileFun(files,start,end,id,readlineFun) {
if(start<end && start<files.length){
var lr = new LineByLineReader(files[start],{encoding:'utf8',skipEmptyLines:true});
lr.on('error', function (e) {
console.log('error,LineByLineReader.'+e.toString());
});
lr.on('line', function (line) {
// pause read line
lr.pause();
try{
readlineFun(line,id,lr);
}catch(e){
console.log('error,line.'+e.toString());
}
});
lr.on('end', function () {
readfileFun(files,++start,end,id,readlineFun);
});
}
}
// var files is an arry of files
// this function try to go throgh file[0],file[1],file[2],......,file[10],
readfileFun(files,0,10,"ID-1",readlineFunSession);
Do a series of action where next action should run after only the current finish in nodejs is a bit difficult due its asynchronous paradigm, one way you can do is using sync maker npm like fiber or waterfall,
but other simple (and stupid) way you can do is create dummy worker manager, make your nodejs run infinitely, while every (time interval), check if the current progress is done, run next action if it did done.
btw while you can't make request to become sync, you can read file synchronously, so in your case, i think you should read all lines in all files to become one big array of line.
var jswget = require("jswget");
var arrayoflines = ["line1", "line2", "line3"];
var counter = 0;
var inProgress = false;
var request = function(){
if (arrayoflines.length == 0) {
// no more line, should exit
process.exit();
}
if (inProgress) {
// if previous work is not completed then skip.
return;
}
// get first line, and remove it from array index
var current_line = arrayoflines.shift();
inProgress = true;
jswget({
url: "http://someurl:3000/somepath?q1=" + current_line,
method: 'POST',
formdata: some_postdata,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': post_data.length
},
onsuccess: function(responsetext, req, res){
// success requesting, should do next line
},
onerror: function(err, req){
// oops, error occurred, but we will do next line nevertheless
},
onend: function(){
// success or not, the request is end, so we should prepare for next request
counter+=1;
inProgress = false;
}
})
}
setInterval(function(){
request();
}, 100)
This may help you...
With Node 0.12, it's possible to do this synchronously now:
var fs = require('fs');
var path = require('path');
// Buffer mydata
var BUFFER = bufferFile('../public/mydata.txt');
function bufferFile(relPath) {
return fs.readFileSync(path.join(__dirname, relPath)); // zzzz....
}
fs is the file system. readFileSync() returns a Buffer, or string if you ask.
fs correctly assumes relative paths are a security issue. path is a work-around.
To load as a string, specify the encoding:
return fs.readFileSync(path,{ encoding: 'utf8' });

small nodejs process is eating 113m memory

I am trying to save data from an external API into a mongodb using nodejs. The script feels really lightweight to me, but for some reason it's using a lot of RAM (from top):
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2626 root 20 0 756m 113m 7148 S 6.5 11.4 3:11.74 nodejs
This is what the script does in psuedo code:
each 5 seconds
fetch 3 JSON lists through an API
for all new items in list
store in mongo
[edit]
The JSON lists are aprox. 10kb each. So I don't think it has to do with keeping that in memory until I processed the items.
[/edit]
the (light) dependencies are:
querystring
https
underscore
mongodb (the native client)
moment
I wrote it as simple functions, when returning they should give back all the memory they used right?
Here is the whole script:
var querystring = require("querystring");
var https = require('https');
var fetch = function(cur, callback) {
cur = cur.toLowerCase().replace('/', '_');
var options = {
host: 'data.fxbtc.com',
path: '/api?op=query_last_trades&count=100&symbol=' + cur,
method: 'GET',
headers: {
'User-Agent': 'Mozilla/4.0 (compatible; node.js client)'
}
};
var req = https.request(options, function(res) {
res.setEncoding('utf8');
var buffer = '';
res.on('data', function(data) {
buffer += data;
});
res.on('end', function() {
try {
var json = JSON.parse(buffer);
} catch (err) {
return callback(err);
}
callback(null, json);
});
});
req.end();
}
var currencies = [
'BTC/CNY',
'LTC/CNY',
'LTC/BTC'
];
var LAST_TRADE = {
'BTC/CNY': 0,
'LTC/CNY': 0,
'LTC/BTC': 0
}
var _ = require('underscore');
var mongo = require('mongodb');
var moment = require('moment');
var init = function(next) {
mongo.connect('mongodb://127.0.0.1:27017/coindata', next);
}
var now = function() {
return moment().format('YYYY-MM-DD HH:mm:ss');
}
console.log(now(), 'STARTING');
setInterval(function() {
console.log(now(), 'alive')
}, 60000)
var collections = {};
var forever = function(err, db) {
if(err) throw err;
_.each(currencies, function(cur, i) {
collections[cur] = db.collection('fxbtc_' + cur);
collections[cur].ensureIndex({fid: 1}, {unique: true}, console.log);
setTimeout(function() {
console.log(now(), 'registering', cur);
setInterval(check(cur), 5 * 1000);
}, i * 1000);
});
}
var check = function(cur) {
return function() {
fetch(cur, function(err, trades) {
if(err) return console.log(now(), 'ERROR-FETCH', err);
trades = _.map(trades.datas, function(trade) {
return {
date: new Date(trade.date * 1000),
price: parseFloat(trade.rate),
amount: parseFloat(trade.vol),
fid: parseInt(trade.ticket)
}
});
trades = _.filter(trades, function(trade) {
return trade.fid > LAST_TRADE[cur];
});
var fids = _.pluck(trades, 'fid');
fids.push(LAST_TRADE[cur]);
LAST_TRADE[cur] = _.max(fids);
if(!trades.length)
return;
console.log(now(), 'storing:', trades.length, 'in', cur);
collections[cur].insert(trades, function(err, docs) {
if(err && err.code !== 11000) console.log(now(), 'ERROR-STORE', err);
});
});
}
}
init(forever);
Are there any obvious memory leaks in this script? How do I go about finding the source of all the used memory?
The project I am working on is polling a lot of different API services (15+) and storing all latest changes.
My initial thought was to write a small script for each different service, which has a loop inside that should stay up forever. The problem (as described above) is that somehow the memory would grow to 40 - 120mb (depending on a couple of things) per service and my system would run out of RAM.
This is how I solved it now:
instead of keeping a process per service alive I rewrote all scripts to run only once and wrote a master script that is responsible for running each script per service script after x amount of time:
var cp = require('child_process');
var moment = require('moment');
var i = 0;
var watch = function(options) {
i++;
setTimeout(function() {
var fid = 0;
setInterval(function() {
var worker = cp.fork('./process_' + options.exchange + '.js');
worker.send(fid);
worker.once('message', function(new_fid) {
fid = new_fid;
worker.kill();
});
}, options.interval);
}, i * 3000);
}
And then I register all different services like so:
watch({exchange: 'bitcurex', interval: +moment.duration(9, 'minutes')});
It has been running for a little over 10 hours or now with little to no memory footprint (I can't find it in top).

Increasing max_frame rate in amqp.js -- Hitting limits in buffer copy

I have a situation where I have about 50 listeners on 50 'direct' exchanges. The client and the server are in javascript (node.js) . It is using the node-amqp from postwait .
Things work fairly well at low frequency of messages. Once the message frequency increases ~ 5000 messages per minute then there is a buffer copy error being shown in amqp.js
From what I could trace the max_frame_size in amqp.js is fixed to 131072 .
I just tried to double the value from 128k to 256k . But doing so causes the node.js to silently fail without starting up. There is no error message. I am assuming that I also have to change the corresponding value (max_frame) in the rabbit.config file.
Do I have to do anything else to increase this value . Any other suggestions will also be appreciated.
I have attached the minimal code to simulate the error . Run the commands below in 2 windows to simulate the error
node engine-so-client.js -c 200 -p 12000
node server-so.js
File server-so.js
var util= require('util')
var amqp = require('amqp');
var express = require ('express')
function httpServer(exchange) {
console.log("In httpServer start %s",exchange.name);
var port;
app = express.createServer();
app.get('/message/:routingKey/:message',function(req,res) {
exchange.publish(req.params.routingKey,{'d' : req.params.message});
res.send('Published the message '+req.params.message+'\n');
});
app.get('/register/:socket/:routingKey',function(req,res) {
var queue1 = conn.queue('',
{autoDelete: false, durable: true, exclusive: true},
function() {
console.log("Queue1 Callback");
queue1.subscribe(
function(message) {
console.log("subscribe Callback for "+req.params.routingKey + " " + message.d);
});
console.log("Queue Callback Binding with "+req.params.routingKey);
queue1.bind(exchange.name,req.params.routingKey);
});
res.send('Started the socket at '+req.params.socket+'\n');
});
app.listen(3000);
app.use(express.logger());
console.log('Started server on port %s', app.address().port);
}
function setup() {
console.log("Setup");
var exchange = conn.exchange('cf2-demo',
{'type': 'direct', durable: false}, function() {
var queue = conn.queue('',
{autoDelete: false, durable: true, exclusive: true},
function() {
console.log("Queue Callback Binding with test key");
queue.bind(exchange.name,'testKey');
});
queue.on('queueBindOk',
function() { httpServer(exchange); });
});
console.log("Completed setup %s", exchange.name);
}
var conn = amqp.createConnection({host:'localhost',
login:'guest',
password:'guest'},
{defaultExchangeName: "cf2-demo"});
conn.on('ready',setup);
File engine-so-client.js
var program = require('commander.js');
var util = require('util');
var http = require('http');
program
.version('0.0.1')
.option('-h, --host <host>', 'Host running server', String,'localhost')
.option('-p, --port <port>', 'Port to open to connect messages on',Number,12000)
.option('-k, --key <key>,', 'Routing Key to be used',String,'key1')
.option('-c, --count <count>','Iteration count',Number,50)
.option('-m, --mesg <mesg>','Message prefix',String,'hello')
.option('-t, --timeout', 'Timeout in ms between message posts')
.parse(process.argv);
function setup(host,port,key,mesg) {
var client = http.createClient(3000, host);
var request = client.request('GET','/register/'+port+"/"+key);
request.end();
request.on('response', function(response) {
response.on('data', function(chunk) {
postMessage(host,port,key,mesg,1);
});
});
}
function postMessage(host,port,key,mesg,count) {
var timeNow = new Date().getTime();
var mesgNow = mesg+"-"+count+"-"+port;
console.log("Type: Sent Mesg, Message: %s, Time: %s",mesgNow,timeNow);
var client1 = http.createClient(3000, host);
var request1 = client1.request('GET','/message/'+key+"/"+mesgNow);
request1.end();
count++;
if (count <100) {
setTimeout( function() { postMessage(host,port,key,mesg,count); }, 1000 );
}
}
var port = program.port;
var host = program.host;
var key = program.key;
var mesg = program.mesg;
var count = program.count;
var keys = ['key1','key2','key3','key4','key5'];
var messages = ['hello','world','good','morning','bye'];
var start=port;
for (i=0; i<count; i++) {
var index = i%keys.length;
var socket = start + i;
setup(host,socket,keys[index],messages[index]);
}
Error attached
buffer.js:494
throw new Error('sourceEnd out of bounds');
^
Error: sourceEnd out of bounds
at Buffer.copy (buffer.js:494:11)
at frame (/home/hvram/programs/node_modules/node-amqp/amqp.js:170:10)
at header (/home/hvram/programs/node_modules/node-amqp/amqp.js:160:14)
at frameEnd (/home/hvram/programs/node_modules/node-amqp/amqp.js:205:16)
at frame (/home/hvram/programs/node_modules/node-amqp/amqp.js:172:14)
at header (/home/hvram/programs/node_modules/node-amqp/amqp.js:160:14)
at frameEnd (/home/hvram/programs/node_modules/node-amqp/amqp.js:205:16)
at frame (/home/hvram/programs/node_modules/node-amqp/amqp.js:172:14)
at header (/home/hvram/programs/node_modules/node-amqp/amqp.js:160:14)
at frameEnd (/home/hvram/programs/node_modules/node-amqp/amqp.js:205:16)

Resources