Skip to content

Commit 26e4fe0

Browse files
committed
Merge pull request #334 from nodejs/add/guides
Add guides to docs
2 parents 6f83666 + 423e529 commit 26e4fe0

File tree

7 files changed

+110
-59
lines changed

7 files changed

+110
-59
lines changed

build.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ function buildlocale (source, locale) {
106106
knowledgeBase: {
107107
pattern: 'knowledge/**/*.md',
108108
refer: false
109+
},
110+
guides: {
111+
pattern: 'docs/guides/!(index).md',
112+
refer: false
109113
}
110114
}))
111115
.use(markdown(markedOptions))

layouts/docs.hbs

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,7 @@
88
<div id="main">
99
<div class="container has-side-nav">
1010

11-
<aside>
12-
<ul>
13-
<li{{#equals path site.docs.link}} class="active"{{/equals}}>
14-
<a href="/{{site.locale}}/{{site.docs.link}}/">{{site.docs.text}}</a>
15-
</li>
16-
<li{{#equals path site.docs.es6.link}} class="active"{{/equals}}>
17-
<a href="/{{site.locale}}/{{site.docs.es6.link}}/">{{site.docs.es6.text}}</a>
18-
</li>
19-
<li{{#equals path site.docs.faq.link}} class="active"{{/equals}}>
20-
<a href="/{{site.locale}}/{{site.docs.faq.link}}/">{{site.docs.faq.text}}</a>
21-
</li>
22-
<li{{#equals path site.docs.api.link}} class="active"{{/equals}}>
23-
<a href="{{site.docs.api.link}}">{{site.docs.api.text}}</a>
24-
</li>
25-
</ul>
26-
</aside>
11+
{{> docs-menu }}
2712

2813
<article>
2914
{{{ contents }}}

layouts/guides-index.hbs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!DOCTYPE html>
2+
<html lang="{{site.locale}}">
3+
{{> html-head }}
4+
5+
<body>
6+
{{> header }}
7+
8+
<div id="main">
9+
<div class="container has-side-nav">
10+
11+
{{> docs-menu}}
12+
13+
<article>
14+
{{{ contents }}}
15+
16+
<ul class="guides-index">
17+
{{#each collections.guides}}
18+
<li><a href="/{{../site.locale}}/{{ path }}/">{{ title }}</a></li>
19+
{{/each}}
20+
</ul>
21+
</article>
22+
23+
</div>
24+
</div>
25+
26+
{{> footer }}
27+
</body>
28+
</html>

layouts/partials/docs-menu.hbs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<aside>
2+
<ul>
3+
<li{{#equals path site.docs.link}} class="active"{{/equals}}>
4+
<a href="/{{site.locale}}/{{site.docs.link}}/">{{site.docs.text}}</a>
5+
</li>
6+
<li{{#equals path site.docs.es6.link}} class="active"{{/equals}}>
7+
<a href="/{{site.locale}}/{{site.docs.es6.link}}/">{{site.docs.es6.text}}</a>
8+
</li>
9+
<li{{#equals path site.docs.faq.link}} class="active"{{/equals}}>
10+
<a href="/{{site.locale}}/{{site.docs.faq.link}}/">{{site.docs.faq.text}}</a>
11+
</li>
12+
<li{{#equals path site.docs.api.link}} class="active"{{/equals}}>
13+
<a href="{{site.docs.api.link}}">{{site.docs.api.text}}</a>
14+
</li>
15+
<li{{#startswith path site.docs.guides.link}} class="active"{{/startswith}}>
16+
<a href="/{{site.locale}}/{{site.docs.guides.link}}">{{site.docs.guides.text}}</a>
17+
</li>
18+
</ul>
19+
</aside>

locale/en/docs/guides/index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
title: Guides
3+
layout: guides-index.hbs
4+
---
5+
6+
# Guides
Lines changed: 48 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,87 @@
1-
## Easy profiling for Node.js Applications
2-
1+
---
2+
title: Easy profiling for Node.js Applications
3+
layout: docs.hbs
4+
---
5+
6+
# Easy profiling for Node.js Applications
7+
38
There are many third party tools available for profiling Node.js applications but, in many cases, the easiest option is to use the Node.js built in profiler. The built in profiler uses the [profiler inside V8](https://developers.google.com/v8/profiler_example) which samples the stack at regular intervals during program execution. It records the results of these samples, along with important optimization events such as jit compiles, as a series of ticks:
49

5-
```
10+
```
611
code-creation,LazyCompile,0,0x2d5000a337a0,396,"bp native array.js:1153:16",0x289f644df68,~
712
code-creation,LazyCompile,0,0x2d5000a33940,716,"hasOwnProperty native v8natives.js:198:30",0x289f64438d0,~
813
code-creation,LazyCompile,0,0x2d5000a33c20,284,"ToName native runtime.js:549:16",0x289f643bb28,~
914
code-creation,Stub,2,0x2d5000a33d40,182,"DoubleToIStub"
1015
code-creation,Stub,2,0x2d5000a33e00,507,"NumberToStringStub"
1116
```
12-
17+
1318
In the past you need the V8 source code to be able to interpret the ticks. Luckily, tools have recently been introduced into Node.js 4.1.1 that facilitate the consumption of this information without separately building V8 from source. Let's see how the built-in profiler can help provide insight into application performance.
14-
19+
1520
To illustrate the use of the tick profiler, we will work with a simple Express application. Our application will have two handlers, one for adding new users to our system:
1621

1722
```javascript
1823
app.get('/newUser', function (req, res) {
1924
var username = req.query.username || '';
2025
var password = req.query.password || '';
21-
26+
2227
username = username.replace(/[!@#$%^&*]/g, '');
23-
28+
2429
if (!username || !password || users.username) {
2530
return res.sendStatus(400);
2631
}
27-
32+
2833
var salt = crypto.randomBytes(128).toString('base64');
2934
var hash = crypto.pbkdf2Sync(password, salt, 10000, 512);
30-
35+
3136
users[username] = {
3237
salt: salt,
3338
hash: hash
3439
};
35-
40+
3641
res.sendStatus(200);
3742
});
3843
```
39-
44+
4045
and another for validating user authentication attempts:
41-
46+
4247
```javascript
4348
app.get('/auth', function (req, res) {
4449
var username = req.query.username || '';
4550
var password = req.query.password || '';
46-
51+
4752
username = username.replace(/[!@#$%^&*]/g, '');
48-
53+
4954
if (!username || !password || !users[username]) {
5055
return res.sendStatus(400);
5156
}
52-
57+
5358
var hash = crypto.pbkdf2Sync(password, users[username].salt, 10000, 512);
54-
59+
5560
if (users[username].hash.toString() === hash.toString()) {
5661
res.sendStatus(200);
5762
} else {
5863
res.sendStatus(401);
5964
}
6065
});
6166
```
62-
67+
6368
*Please note that these are NOT recommended handlers for authenticating users in your Node.js applications and are used purely for illustration purposes. You should not be trying to design your own cryptographic authentication mechanisms in general. It is much better to use existing, proven authentication solutions.*
64-
69+
6570
Now assume that we've deployed our application and users are complaining about high latency on requests. We can easily run the app with the built in profiler:
6671

6772
```
6873
NODE_ENV=production node --prof app.js
6974
```
70-
75+
7176
and put some load on the server using ab:
7277

73-
```
78+
```
7479
curl -X GET "http://localhost:8080/newUser?username=matt&password=password"
7580
ab -k -c 20 -n 250 "http://localhost:8080/auth?username=matt&password=password"
7681
```
77-
82+
7883
and get an ab output of:
79-
84+
8085
```
8186
Concurrency Level: 20
8287
Time taken for tests: 46.932 seconds
@@ -91,7 +96,7 @@ Time per request: 187.728 [ms] (mean, across all concurrent requests)
9196
Transfer rate: 1.05 [Kbytes/sec] received
9297
9398
...
94-
99+
95100
Percentage of the requests served within a certain time (ms)
96101
50% 3755
97102
66% 3804
@@ -103,15 +108,15 @@ Percentage of the requests served within a certain time (ms)
103108
99% 3875
104109
100% 4225 (longest request)
105110
```
106-
111+
107112
From this output, we see that we're only managing to serve about 5 requests per second and that the average request takes just under 4 seconds round trip. In a real world example, we could be doing lots of work in many functions on behalf of a user request but even in our simple example, time could be lost compiling regular expressions, generating random salts, generating unique hashes from user passwords, or inside the Express framework itself.
108-
113+
109114
Since we ran our application using the --prof option, a tick file was generated in the same directory as your local run of the application. It should have the form isolate-0x124353456789-v8.log. In order to make sense of this file, we need to use the tick processor included in the Node.js source at <nodejs_dir>/tools/v8-prof/tick-processor.js. It is important that the version of the tick-processor that you run comes from the same version of node source as version of node used to generate the isolate file. This will no longer be a concern when the tick processor is [installed by default](https://github.com/nodejs/node/pull/3032). The raw tick output can be processed using this tool by running:
110115

111116
```
112117
node <path_to_nodejs_src>/tools/v8-prof/tick-processor.js isolate-0x101804c00-v8.log >processed.txt
113118
```
114-
119+
115120
Opening processed.txt in your favorite text editor will give you a few different types of information. The file is broken up into sections which are again broken up by language. First, we look at the summary section and see:
116121

117122
```
@@ -123,51 +128,51 @@ Opening processed.txt in your favorite text editor will give you a few different
123128
767 2.0% Shared libraries
124129
215 0.6% Unaccounted
125130
```
126-
131+
127132
This tells us that 97% of all samples gathered occurred in C++ code and that when viewing other sections of the processed output we should pay most attention to work being done in C++ (as opposed to Javascript). With this in mind, we next find the [C++] section which contains information about which C++ functions are taking the most CPU time and see:
128-
133+
129134
```
130135
[C++]:
131136
ticks total nonlib name
132137
19557 51.8% 52.9% node::crypto::PBKDF2(v8::FunctionCallbackInfo<v8::Value> const&)
133138
4510 11.9% 12.2% _sha1_block_data_order
134139
3165 8.4% 8.6% _malloc_zone_malloc
135140
```
136-
141+
137142
We see that the top 3 entries account for 72.1% of CPU time taken by the program. From this output, we immediately see that at least 51.8% of CPU time is taken up by a function called PBKDF2 which corresponds to our hash generation from a user's password. However, it may not be immediately obvious how the lower two entries factor into our application (or if it is we will pretend otherwise for the sake of example). To better understand the relationship between these functions, we will next look at the [Bottom up (heavy) profile] section which provides information about the primary callers of each function. Examining this section, we find:
138143

139144
```
140145
ticks parent name
141146
19557 51.8% node::crypto::PBKDF2(v8::FunctionCallbackInfo<v8::Value> const&)
142147
19557 100.0% v8::internal::Builtins::~Builtins()
143148
19557 100.0% LazyCompile: ~pbkdf2 crypto.js:557:16
144-
149+
145150
4510 11.9% _sha1_block_data_order
146151
4510 100.0% LazyCompile: *pbkdf2 crypto.js:557:16
147152
4510 100.0% LazyCompile: *exports.pbkdf2Sync crypto.js:552:30
148-
153+
149154
3165 8.4% _malloc_zone_malloc
150155
3161 99.9% LazyCompile: *pbkdf2 crypto.js:557:16
151156
3161 100.0% LazyCompile: *exports.pbkdf2Sync crypto.js:552:30
152157
```
153-
158+
154159
Parsing this section takes a little more work than the raw tick counts above. Within each of the "call stacks" above, the percentage in the parent column tells you the percentage of samples for which the function in the row above was called by the function in the current row. For example, in the middle "call stack" above for _sha1_block_data_order, we see that _sha1_block_data_order occurred in 11.9% of samples, which we knew from the raw counts above. However, here, we can also tell that it was always called by the pbkdf2 function inside the Node.js crypto module. We see that similarly, _malloc_zone_malloc was called almost exclusively by the same pbkdf2 function. Thus, using the information in this view, we can tell that our hash computation from the user's password accounts not only for the 51.8% from above but also for all CPU time in the top 3 most sampled functions since the calls to _sha1_block_data_order and _malloc_zone_malloc were made on behalf of the pbkdf2 function.
155-
160+
156161
At this point, it is very clear that the password based hash generation should be the target of our optimization. Thankfully, you've fully internalized the benefits of asynchronous programming (https://nodesource.com/blog/why-asynchronous) and you realize that the work to generate a hash from the user's password is being done in a synchronous way and thus tying down the event loop. This prevents us from working on other incoming requests while computing a hash.
157-
162+
158163
To remedy this issue, you make a small modification to the above handlers to use the asynchronous version of the pbkdf2 function:
159164

160165
```javascript
161166
app.get('/auth', function (req, res) {
162167
var username = req.query.username || '';
163168
var password = req.query.password || '';
164-
169+
165170
username = username.replace(/[!@#$%^&*]/g, '');
166-
171+
167172
if (!username || !password || !users[username]) {
168173
return res.sendStatus(400);
169174
}
170-
175+
171176
crypto.pbkdf2(password, users[username].salt, 10000, 512, function(err, hash) {
172177
if (users[username].hash.toString() === hash.toString()) {
173178
res.sendStatus(200);
@@ -177,9 +182,9 @@ app.get('/auth', function (req, res) {
177182
});
178183
});
179184
```
180-
185+
181186
A new run of the ab benchmark above with the asynchronous version of your app yields:
182-
187+
183188
```
184189
Concurrency Level: 20
185190
Time taken for tests: 12.846 seconds
@@ -192,9 +197,9 @@ Requests per second: 19.46 [#/sec] (mean)
192197
Time per request: 1027.689 [ms] (mean)
193198
Time per request: 51.384 [ms] (mean, across all concurrent requests)
194199
Transfer rate: 3.82 [Kbytes/sec] received
195-
200+
196201
...
197-
202+
198203
Percentage of the requests served within a certain time (ms)
199204
50% 1018
200205
66% 1035
@@ -206,7 +211,7 @@ Percentage of the requests served within a certain time (ms)
206211
99% 1071
207212
100% 1079 (longest request)
208213
```
209-
214+
210215
Yay! Your app is now serving about 20 requests per second, roughly 4 times more than it was with the synchronous hash generation. Additionally, the average latency is down from the 4 seconds before to just over 1 second.
211-
216+
212217
Hopefully, through the performance investigation of this (admittedly contrived) example, you've seen how the V8 tick processor can help you gain a better understanding of the performance of your Node.js applications.

locale/en/site.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@
7474
"link": "/api/",
7575
"text": "API"
7676
},
77+
"guides": {
78+
"link": "docs/guides",
79+
"text": "Guides"
80+
},
7781
"knowledge": {
7882
"link": "knowledge",
7983
"text": "Knowledge Base"

0 commit comments

Comments
 (0)