NodeJS - express server, pm2 cluser and nginx balancing - multiple threads - node.js

I'm trying to set up express server to listen on multiple threads. I've used pm2 to set up application on ports 3000, 3001, 3002, 3003 but requests are still waiting for each other...
express application index.js:
const express = require('express')
const axios = require('axios')
const app = express()
app.get('/', async (req, res) => {
console.log('-----> GOT REQUEST -> ' + (new Date()).getTime());
let resp = await axios.get("here some correct http get");
res.send("Hello world!")
console.log(' Response time: ' + (new Date()).getTime());
})
let instance = +process.env.NODE_APP_INSTANCE || 0;
let port = (+process.env.PORT || 3000) + instance;
app.listen(port, () => console.log('Example app listening on port ' + port))
so every instance is on another port. Now it's time for nginx:
upstream test_upstream {
least_conn;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
server 127.0.0.1:3003;
}
server {
listen 8000;
location / {
proxy_hide_header Access-Control-Allow-Origin;
add_header Access-Control-Allow-Origin * always;
proxy_hide_header Access-Control-Allow-Methods;
add_header Access-Control-Allow-Methods "GET,POST,DELETE,PUT,OPTIONS" always;
proxy_hide_header Access-Control-Allow-Headers;
add_header Access-Control-Allow-Headers "Authorization, X-Requested-With, Content-Type" always;
proxy_hide_header Access-Control-Allow-Credentials;
add_header Access-Control-Allow-Credentials "true" always;
if ($request_method = OPTIONS ) { # Allow CORS
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET,POST,DELETE,PUT,OPTIONS";
add_header Access-Control-Allow-Headers "Authorization, X-Requested-With, Content-Type";
add_header Access-Control-Allow-Credentials "true" always;
add_header Content-Length 0;
add_header Content-Type text/plain;
add_header Allow GET,POST,DELETE,PUT,OPTIONS;
return 200;
}
proxy_pass http://test_upstream;
}
}
So far so good. My environment:
node v 10.3.0
cpu cores 8, but i'm using only 4 instances
Ok, started application:
┌──────────┬────┬─────────┬───────┬────────┬─────────┬────────┬─────┬───────────┬───────────────┬──────────┐
│ App name │ id │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ user │ watching │
├──────────┼────┼─────────┼───────┼────────┼─────────┼────────┼─────┼───────────┼───────────────┼──────────┤
│ index │ 0 │ cluster │ 57069 │ online │ 6 │ 17m │ 0% │ 39.7 MB │ administrator │ disabled │
│ index │ 1 │ cluster │ 57074 │ online │ 6 │ 17m │ 0% │ 39.0 MB │ administrator │ disabled │
│ index │ 2 │ cluster │ 57091 │ online │ 6 │ 17m │ 0% │ 37.5 MB │ administrator │ disabled │
│ index │ 3 │ cluster │ 57097 │ online │ 6 │ 17m │ 0% │ 38.8 MB │ administrator │ disabled │
└──────────┴────┴─────────┴───────┴────────┴─────────┴────────┴─────┴───────────┴───────────────┴──────────┘
Now it's time to invoke it. I want to send multiple requests at the same time:
async sendRequest() {
const startTime = performance.now();
console.log("Sending request");
const els = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const promises = els.map(() => axios.get("http://my_server_with_nginx:8000"));
let results = await Promise.all(promises);
console.log(results);
const stopTime = performance.now();
console.log("Time of request: " + (stopTime - startTime));
}
And test looks like this:
And finally node app log:
0|index | -----> GOT REQUEST -> 1527796135425
0|index | Response time: 1527796135572
1|index | -----> GOT REQUEST -> 1527796135595
1|index | Response time: 1527796135741
2|index | -----> GOT REQUEST -> 1527796135766
2|index | Response time: 1527796136354
3|index | -----> GOT REQUEST -> 1527796136381
3|index | Response time: 1527796136522
0|index | -----> GOT REQUEST -> 1527796136547
0|index | Response time: 1527796136678
1|index | -----> GOT REQUEST -> 1527796136702
1|index | Response time: 1527796136844
2|index | -----> GOT REQUEST -> 1527796136868
2|index | Response time: 1527796137026
3|index | -----> GOT REQUEST -> 1527796137098
3|index | Response time: 1527796137238
0|index | -----> GOT REQUEST -> 1527796137263
0|index | Response time: 1527796137395
1|index | -----> GOT REQUEST -> 1527796137419
1|index | Response time: 1527796137560
As we can see, it's correctly balancing requests to nodes, but somewhere has stalled. How to force it to run in parallel?

It turns out that everything works just great.
Problem was in the browser. When browser send the same http get requests they are queued. To change that i had to change invocation:
const els = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const promises = els.map(() => axios.get("http://my_server_with_nginx:8000"));
to this:
const els = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const promises = els.map((el) => axios.get(`http://my_server_with_nginx:8000?number=${el}`));

Are you sure that the requests are really on all cores? I mean to test this you will need to use a synchronous function. If you use async methods, the requests will be still accomplished asynchronously even on one thread.

Related

How can I print Cypress base Url in the `Git bash CLI console`

How can I print Cypress base Url in the Git bash CLI console while running the Cypress test ? Could someone please advise ?
When running headless, the browser logs don't show in the terminal, but those from the Cypress node process (aka cy.task()) do show up.
I assume the baseUrl is changing during the test, here is an example that does that and logs the current value using a task.
The configured value is http://localhost:3000, the test changes it to http://localhost:3001.
cypress.config.js
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
console.log('Printing baseUrl - during setup', config.baseUrl)
on('task', {
logBaseUrl(baseUrl) {
console.log('Printing baseUrl - from task', baseUrl)
return null
}
})
},
baseUrl: 'http://localhost:3000'
},
})
test
it('logs baseUrl to the terminal in run mode', () => {
console.log('Printing baseUrl - directly from test', Cypress.config('baseUrl')) // no show
Cypress.config('baseUrl', 'http://localhost:3001')
cy.task('logBaseUrl', Cypress.config('baseUrl'))
})
terminal
Printing baseUrl - during setup http://localhost:3000
====================================================================================================
Running: log-baseurl.cy.js (1 of 1)
Printing baseUrl - from task http://localhost:3001
√ logs baseUrl to the terminal in run mode (73ms)
1 passing (115ms)
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1 │
│ Passing: 1 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: true │
│ Duration: 0 seconds │
│ Spec Ran: log-baseurl.cy.js │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
====================================================================================================
(Run Finished)
Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ √ log-baseurl.cy.js 108ms 1 1 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
√ All specs passed! 108ms 1 1 - - -
Done in 18.49s.
You can use a nodejs command console.log(Cypress.config().baseUrl).
That does not require Git Bash though, only nodejs installed on Your Windows.
Or you can add in your tests cy.config().baseUrl.

Request are not distributed across their worker process

I was just experimenting worker process hence try this:
const http = require("http");
const cluster = require("cluster");
const CPUs = require("os").cpus();
const numCPUs = CPUs.length;
if (cluster.isMaster) {
console.log("This is the master process: ", process.pid);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on("exit", (worker) => {
console.log(`worker process ${process.pid} has died`);
console.log(`Only ${Object.keys(cluster.workers).length} remaining...`);
});
} else {
http
.createServer((req, res) => {
res.end(`process: ${process.pid}`);
if (req.url === "/kill") {
process.exit();
}
console.log(`serving from ${process.pid}`);
})
.listen(3000);
}
I use loadtest to check "Are Request distributed across their worker process?" But I got same process.pid
This is the master process: 6984
serving from 13108
serving from 13108
serving from 13108
serving from 13108
serving from 13108
...
Even when I kill one of them, I get the same process.pid
worker process 6984 has died
Only 3 remaining...
serving from 5636
worker process 6984 has died
Only 2 remaining...
worker process 6984 has died
Only 1 remaining...
How I am getting same process.pid when I killed that? And Why my requests are not distributed across their worker process?
Even when I use pm2 to test cluster mood using:
$ pm2 start app.js -i 3
[PM2] Starting app.js in cluster_mode (3 instances)
[PM2] Done.
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ name │ mode │ ↺ │ status │ cpu │ memory │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
│ 0 │ app │ cluster │ 0 │ online │ 0% │ 31.9mb │
│ 1 │ app │ cluster │ 0 │ online │ 0% │ 31.8mb │
│ 2 │ app │ cluster │ 0 │ online │ 0% │ 31.8mb │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
for loadtest -n 50000 http://localhost:3000 I check pm2 monit:
$ pm2 monit
┌─ Process List ───────────────────────────────────────────────────┐┌── app Logs ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│[ 0] app Mem: 43 MB CPU: 34 % online ││ │
│[ 1] app Mem: 28 MB CPU: 0 % online ││ │
│[ 2] app Mem: 27 MB CPU: 0 % online ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
└──────────────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
┌─ Custom Metrics ─────────────────────────────────────────────────┐┌─ Metadata ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Heap Size 20.81 MiB ││ App Name app │
│ Heap Usage 45.62 % ││ Namespace default │
│ Used Heap Size 9.49 MiB ││ Version N/A │
│ Active requests 0 ││ Restarts 0 │
└──────────────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
left/right: switch boards | up/down/mouse: scroll | Ctrl-C: exit To go further check out https://pm2.io/
But surprisingly, app1 and app2 never hit any request as well as it didn't show any app log.
Update 1
I still couldn't figure out any solution. If any further query need please ask for that. I faced that issue first time. That's why maybe I was unable to represent where the exact problem occurring.
Update 2
After getting some answer I try to test it again with a simple node server:
Using pm2 without any config:
Using config suggested from #Naor Tedgi's answer:
Now the server is not running at all.
I got this
Probably it is OS related,I am on Ubuntu 20.04.
you don't have cluster mode enabled if you want to use pm2 as load balancer you need to add
exec_mode cluster
add this config file name it config.js
module.exports = {
apps : [{
script : "app.js",
instances : "max",
exec_mode : "cluster"
}]
}
and run pm2 start config.js
then all CPU usage will divide equally
tasted on
os macOS Catalina 10.15.7
node v14.15.4
Not sure why, but it seems that for whatever reason cluster doesn't behave on your machine the way it should.
In lieu of using node.js for balancing you can go for nginx then. On nginx part it's fairly easy if one of the available strategies is enough for you: http://nginx.org/en/docs/http/load_balancing.html
Then you need to make sure that your node processes are assigned different ports. In pm2 you can either use https://pm2.keymetrics.io/docs/usage/environment/ to either manually increment port based on the instance id or delegate it fully to pm2.
Needless to say, you'll have to send your requests to nginx in this case.

Getting error when running Postman Script via Newman

I am trying to run the following Postman Script via Newman to write the response to file:
//verify http response code
pm.test("Report Generated", function () {
pm.response.to.have.status(200);
});
var fs = require('fs');
var outputFilename = 'C:/Users/archit.goyal/Downloads/spaceReport.csv';
fs.writeFileSync(outputFilename, pm.response.text());
The request gives a response but getting the following error when writing to file:
1? TypeError in test-script
┌─────────────────────────┬──────────┬──────────┐
│ │ executed │ failed │
├─────────────────────────┼──────────┼──────────┤
│ iterations │ 1 │ 0 │
├─────────────────────────┼──────────┼──────────┤
│ requests │ 20 │ 0 │
├─────────────────────────┼──────────┼──────────┤
│ test-scripts │ 20 │ 1 │
├─────────────────────────┼──────────┼──────────┤
│ prerequest-scripts │ 0 │ 0 │
├─────────────────────────┼──────────┼──────────┤
│ assertions │ 2 │ 0 │
├─────────────────────────┴──────────┴──────────┤
│ total run duration: 1m 48.3s │
├───────────────────────────────────────────────┤
│ total data received: 1.24MB (approx) │
├───────────────────────────────────────────────┤
│ average response time: 5.3s │
└───────────────────────────────────────────────┘
# failure detail
1. TypeError fs.writeFileSync is not a function
at test-script
inside "3i_BMS_Amortization_Schedule / GetReport"
Please help
Postman itself cannot execute scripts like that. To save your responses of all the api requests, you can create a nodeJS server which will call the api through newman and then save the response to a local file. Here is an example -
var fs = require('fs'),
newman = require('newman'),
allResponse=[],
outputFilename = 'C:/Users/archit.goyal/Downloads/spaceReport.csv';
newman.run({
collection: '//your_collection_name.json',
iterationCount : 1
})
.on('request', function (err, args) {
if (!err) {
//console.log(args); // --> args contain ALL the data newman provides to this script.
var responseBody = args.response.stream,
response = responseBody.toString();
allResponse.push(JSON.parse(response));
}
})
.on('done', function (err, summary) {
fs.writeFile(outputFilename,"");
for(var i =0;i<allResponse.length;i++)
{
fs.appendFileSync(outputFilename,JSON.stringify(allResponse[i],null,5));
}
});
Note that, the above code will save only the responses. Other data like request , or URl can be extracted in a similar manner. To run this, install newman in the same directory as the script, then run the script using -
node file_name.js

how to set a variable automatically zero for each midnight in node js

how to set a variable automatically zero for each midnight in node js is it possible to use node-schedule-npm ? if so please help me how to do it
initially var count= 0; when action is performed, it will get incremented throughout the day, for next day it should be automatically set to zero.
You can use node-cron from npm.
$ npm install --save node-cron
Here is an example code
var cron = require('node-cron');
cron.schedule('* * * * *', function(){
console.log('running a task every minute');
});
# ┌────────────── second (optional)
# │ ┌──────────── minute
# │ │ ┌────────── hour
# │ │ │ ┌──────── day of month
# │ │ │ │ ┌────── month
# │ │ │ │ │ ┌──── day of week
# │ │ │ │ │ │
# │ │ │ │ │ │
# * * * * * *
you can try this
var cron = require('node-schedule');
cron.scheduleJob('00 00 12 * * 1-7',function setVotesToZero(req, res, next) {
db.users.find().toArray(function(err, vote) {
if (err) return next(err);
db.users.update({}, {
$set: {
count : 0
},
}, function(err, result) {
if (err) return next(err);
res.send({
status: 'success',
});
});
})
})

Node.js formatted console output

Is there a simple built-in way to output formatted data to console in Node.js?
Indent, align field to left or right, add leading zeros?
Two new(1) built in methods String.Prototype.padStart and String.Prototype.padEnd were introduced in ES2017 (ES8) which perform the required padding functions.
(1) node >= 8.2.1 (or >= 7.5.0 if run with the --harmony flag)
Examples from the mdn page:
'abc'.padStart(10); // " abc"
'abc'.padStart(10, "foo"); // "foofoofabc"
'abc'.padStart(6,"123465"); // "123abc"
'abc'.padStart(8, "0"); // "00000abc"
'abc'.padStart(1); // "abc"
'abc'.padEnd(10); // "abc "
'abc'.padEnd(10, "foo"); // "abcfoofoof"
'abc'.padEnd(6, "123456"); // "abc123"
'abc'.padEnd(1); // "abc"
For indenting a json onto the console try using JSON.stringify. The third parameter provides the indention required.
JSON.stringify({ a:1, b:2, c:3 }, null, 4);
// {
// "a": 1,
// "b": 2,
// "c": 3
// }
If the data is tabular, then the simplest way would be to do it with console.table
https://nodejs.org/dist/latest-v10.x/docs/api/console.html#console_console_table_tabulardata_properties
This is the code.
console.table(
COMMANDS.map(command => {
return {
"Long Option": command.long_option,
"Short Option": command.short_option,
Description: command.description
};
})
);
You don't need external libraries for it.
Here is sample output. You only need to pass an array object.
Not only in Nodejs, but it also works in chrome.
https://developer.mozilla.org/en-US/docs/Web/API/Console/table
There's nothing built into NodeJS to do this. The "closest" you'd come is util.format, which still doesn't do much unfortunately (reference).
You'll need to look into other modules to provide a richer formatting experience. For example: sprintf.
Sprintf-js allows both positional (0, 1, 2) arguments and named arguments.
A few examples of padding and alignment:
var sprintf=require("sprintf-js").sprintf;
console.log(sprintf("Space Padded => %10.2f", 123.4567));
console.log(sprintf(" _ Padded => %'_10.2f", 123.4567));
console.log(sprintf(" 0 Padded => %010.2f", 123.4567));
console.log(sprintf(" Left align => %-10.2f", 123.4567));
Results:
Space Padded => 123.46
_ Padded => ____123.46
0 Padded => 0000123.46
Left align => 123.46
If you have simpler needs you can look into util.format. It can generate string from various parameters. If you want printf like formatting you can use either sprintf package or sprintf-js package.
Take a look at Log4JS, which is an attempt at a functional port of Log4j
You might also like string-kit and terminal-kit.
https://www.npmjs.com/package/string-kit
https://www.npmjs.com/package/terminal-kit
https://blog.soulserv.net/terminal-friendly-application-with-node-js-part-ii-moving-and-editing/
If I combine util.format and "".padStart/"".padEnd together,as described in posts above, then I get what I wated:
> console.log(util.format("%s%s","Name:".padEnd(10), "John Wall"))
Name: John Wall
A version of console.table for NodeJS with align.
⚠ This is only for console version! ⚠
/**
* #param {NonNullable<{
* [key: string]: string
* }>} data
*/
const consoleTable = (data) => {
if (typeof data === 'object') {
const e = Object.entries(data);
let kl = 0;
let vl = 0;
for (const [k, v] of e) {
if (k.length > kl) {
kl = k.length;
}
const s = JSON.stringify(v);
const l = s ? s.length : 0;
if (l > vl) {
vl = l;
}
}
/** #type {{ [key: string] : string }} */
const result = {};
for (const [k, v] of e) {
const s = JSON.stringify(v);
result[k.padStart(kl)] = s ? s.padEnd(vl) : v;
}
console.table(result);
} else {
console.table(data);
}
};
Output example:
┌─────────────────┬────────────────────────────────────────────────────────────────────────────────────────┐
│ (index) │ Values │
├─────────────────┼────────────────────────────────────────────────────────────────────────────────────────┤
│ CONFIG_FILE │ '"/Users/ecuomo/projects/zzzzz/xxxxxxxx/tttttttttt-web/jjjjjjjjjj-commons/config.env"' │
│ NODE_ENV │ '"development" ' │
│ APP_ENV │ '"local-dev-edu" ' │
│ APP_VERSION │ '"0-local-ecuomo" ' │
│ BASE_URL │ '"http://localhost:3000" ' │
│ CDN_BASE_URL │ '"http://localhost:3000/static" ' │
│ API_BASE_URL │ '"http://localhost:3000/api" ' │
│ MONGO │ '"mongodb://mongo:27017/xxxxxxxxxxprod" ' │
│ MYSQL_HOST │ '"mysql" ' │
│ MYSQL_PORT │ '3306 ' │
│ MYSQL_DB_ETL │ '"xxxxxxxxxx_etl" ' │
│ MYSQL_DB_STATS │ '"xxxxxxxxxx_stats" ' │
│ MYSQL_DB_ZAPIER │ '"xxxxxxxxxx_yyyyyy" ' │
│ POSTGRES_HOST │ '"postgres" ' │
│ POSTGRES_PORT │ '5432 ' │
│ POSTGRES_DB │ '"xxxxxxxxxx" ' │
│ REDIS_HOST │ '"redis" ' │
│ REDIS_PORT │ '"6379" ' │
│ REDIS_MASTER │ undefined │
│ REDIS_MODE │ '"single" ' │
└─────────────────┴────────────────────────────────────────────────────────────────────────────────────────┘

Resources