Skip to content

Commit c9cd299

Browse files
authored
chore: add memory profiling in streams examples (#946)
1 parent 1ace146 commit c9cd299

File tree

9 files changed

+2524
-0
lines changed

9 files changed

+2524
-0
lines changed

stream_mem_stress_test/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Node modules
2+
node_modules/
3+
4+
# Large files
5+
mem_leak_data1/*
6+
mem_leak_data_default/*

stream_mem_stress_test/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Streams - Memory Stress Test and Memory Profiling
2+
This directory contains test code for running a memory profiler when using the `encryptStream` and `decryptUnsignedMessageStream`.
3+
This directory contains everything you need to run a memory profiler on these two operations.
4+
5+
## Requirements
6+
- Node >= 12
7+
- Chrome Browser
8+
9+
## How to run the application and memory profiler
10+
1. For easier debugging open two chrome windows
11+
1. One where you can look at the profiler
12+
2. One where you can navigate through the application paths.
13+
1. On Chrome, navigate to: `chrome://inspect/#devices`
14+
1. Make sure you are in the `stream_mem_stress_test` directory.
15+
1. Start debugger and server by running `npm run start`
16+
1. On the devices page click on `inspect` for the remote target that just appeared
17+
1. Navigate to the Memory tab. You will have three options:
18+
1. Heap snapshot
19+
- Useful to focus on a specific action during runtime.
20+
1. Allocation instrumentation on timeline
21+
- Works better for our stress tests since we can see memory allocation and
22+
garbage collection during runtime
23+
1. Allocation Sampling (not useful for our test)
24+
1. Navigate to any of the provided paths and watch memory allocation and garbage collection in
25+
real time 🍿

stream_mem_stress_test/index.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import express from 'express'
4+
import { kmsDecryptStream, kmsEncryptStream } from './memory'
5+
6+
const app = express()
7+
const leak = []
8+
9+
app.get('/', (req, res) => {
10+
// Quick little Test so we can check both in browser and in terminal
11+
res.write("Test")
12+
res.send()
13+
console.log("Back in home")
14+
})
15+
16+
/** This path is here to see how a memory leak would look like in the memory profiler.
17+
* To test it you can run `npm run load-mem` to see memory get allocated but never
18+
* garbage collected
19+
*/
20+
app.get('/now', (req, res) => {
21+
let resp = JSON.stringify({ now: new Date() })
22+
leak.push(JSON.parse(resp))
23+
res.writeHead(200, { 'Content-Type': 'application/json' })
24+
res.write(resp)
25+
res.end()
26+
})
27+
28+
/** Path to read 1mb file, parameter to readFile takes any file in the current directory
29+
* so you can test with any file you'd like. We supply 2 test files for you to use.
30+
*/
31+
app.get('/readRandom_1mb', (req, res) => {
32+
res.write("Attempt to encrypt 1mb random")
33+
// you can optionaly pass in a frame size, otherwise uses the default frame size of 4096 bytes
34+
readFile('./random_1mb.txt')
35+
res.send()
36+
})
37+
38+
// Path to decrypt 1mb encrypted file
39+
app.get('/readRandom_1mbEnc', (req, res) => {
40+
res.write("Attempt to decrypt ./random_1mb.txt.encrypted")
41+
readEncryptedFile('./random_1mb.txt.encrypted')
42+
res.send()
43+
44+
})
45+
46+
// Path to encrypt 5mb file
47+
app.get('/readRandom_5mb', (req, res) => {
48+
res.write("Attempt to encrypt 5mb random")
49+
readFile('./random_5mb.txt', 1)
50+
res.send()
51+
})
52+
53+
// Path to decrypt 5mb encrypted file
54+
app.get('/readRandom_5mbEnc', (req, res) => {
55+
res.write("Attempt to decrypt ./random_5mb.txt.encrypted")
56+
readEncryptedFile('./random_5mb.txt.encrypted')
57+
res.send()
58+
59+
})
60+
61+
/** Path to encrypt 1Gb file, not included in this directory because size exceeds GitHub transfer size limit.
62+
* If you want to create a 1Gb file of random data, you can do so by running
63+
* `dd if=/dev/urandom of=rand_1gb.txt bs=1024 count=1024000` on a linux system
64+
*/
65+
app.get('/readRandom_1gb', (req, res) => {
66+
res.write("Attempt to encrypt large random")
67+
readFile('./rand_1gb.txt')
68+
res.send()
69+
})
70+
71+
// Path to decrypt 1mb encrypted file
72+
app.get('/readRandom_1gbEnc', (req, res) => {
73+
res.write("Attempt to encrypt large random")
74+
kmsDecryptStream('./rand_1gb.txt.encrypted')
75+
res.send()
76+
})
77+
78+
app.listen(3000, () => {
79+
console.log("Listening on port 3000");
80+
})
81+
82+
async function readFile(filename: string, framesize?: number) {
83+
await kmsEncryptStream(filename, framesize);
84+
}
85+
86+
async function readEncryptedFile(filename: string) {
87+
await kmsDecryptStream(filename)
88+
}

stream_mem_stress_test/memory.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import {
4+
KmsKeyringNode,
5+
buildClient,
6+
CommitmentPolicy
7+
} from '@aws-crypto/client-node'
8+
import {AlgorithmSuiteIdentifier} from '@aws-crypto/material-management'
9+
10+
/* This builds client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy.
11+
* REQUIRE_ENCRYPT_REQUIRE_DECRYPT enforces that this client only encrypts using committing algorithm suites
12+
* and enforces that this client will only decrypt encrypted messages
13+
* that were created with a committing algorithm suite.
14+
* This is the default if you build `buildClient()`.
15+
*/
16+
const { encryptStream, decryptUnsignedMessageStream } = buildClient(
17+
CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
18+
)
19+
20+
import { finished } from 'stream'
21+
import { createReadStream, createWriteStream } from 'fs'
22+
import { promisify } from 'util'
23+
const finishedAsync = promisify(finished)
24+
25+
/* A KMS CMK is required to generate the data key.
26+
* You need kms:GenerateDataKey permission on the CMK in generatorKeyId.
27+
* This key is public, DO NOT USE in production environment.
28+
*/
29+
const generatorKeyId =
30+
'arn:aws:kms:us-west-2:658956600833:alias/EncryptDecrypt'
31+
32+
// configure keyring with CMK(s) you wish to work with
33+
const keyring = new KmsKeyringNode({ generatorKeyId })
34+
35+
/**
36+
* Encryption Context is very useful if you want to assert things about the encrypted data
37+
* See: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
38+
*/
39+
const context = {
40+
stage: 'demo',
41+
purpose: 'streaming memory stress test',
42+
origin: 'us-west-2',
43+
}
44+
45+
/**
46+
* kmsEncryptStream will use the encryptStream function and create a pipeline to encrypt a stream of data
47+
* from a file and write it to destination `./{filename}.encrypted`
48+
* @param filename string of file name you wish to encrypt
49+
* @param framesize optional parameter to determine frame size; default is 4096 bytes
50+
*/
51+
export async function kmsEncryptStream(filename: string, framesize?: number) {
52+
const readable = createReadStream(filename)
53+
const encFile = filename + '.encrypted'
54+
const writeable = createWriteStream(encFile)
55+
56+
// pipeline of read stream
57+
readable.pipe(
58+
encryptStream(keyring, {
59+
/**
60+
* Since we are streaming, and assuming that the encryption and decryption contexts
61+
* are equally trusted, using an unsigned algorithm suite is faster and avoids
62+
* the possibility of processing plaintext before the signature is verified.
63+
*/
64+
suiteId:
65+
AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA512_COMMIT_KEY,
66+
encryptionContext: context,
67+
frameLength: framesize
68+
})
69+
).pipe(writeable.on('finish', () => {
70+
// output from encryptStream will get piped to writeable
71+
console.log(`The new file name is ${encFile}.`);
72+
}))
73+
74+
await finishedAsync(writeable)
75+
console.log("Finished Encrypting");
76+
77+
}
78+
/**
79+
* kmsDecryptStream will take a filename and create a decryption stream to decrypt contents of file
80+
* and write it to destination `./{filename}.decrypted
81+
* @param filename string of file name you wish to encrypt
82+
*/
83+
export async function kmsDecryptStream(filename: string) {
84+
const readable = createReadStream(filename)
85+
const decFile = filename + '.decrypted'
86+
const writeable = createWriteStream(decFile)
87+
88+
readable.pipe(
89+
/**
90+
* decryptUnsignedMessageStream is recommended when streaming if you don't need
91+
* digital signatures.
92+
*/
93+
decryptUnsignedMessageStream(keyring)
94+
).pipe(writeable.on('finish', () => {
95+
console.log(`The new file name is ${decFile}.`);
96+
}))
97+
98+
await finishedAsync(writeable)
99+
console.log("Finished Decrypting")
100+
}
101+

0 commit comments

Comments
 (0)