Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(gatsby): Add a memory test suite command to the memory benchmark #34810

Merged
merged 17 commits into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions benchmarks/memory/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
output
.docker.memusage
16 changes: 15 additions & 1 deletion benchmarks/memory/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
FROM node:14-buster
ARG jemalloc
ENV NODE_ENV=production
ENV CI=1
ENV GATSBY_CPU_COUNT=4
RUN apt-get update -y && apt-get upgrade -y && apt-get install git curl npm -y
RUN npm i -g gatsby-cli gatsby-dev-cli
WORKDIR /usr/src/app

# set heap to 16gb just to catch all test cases
ENV NODE_OPTIONS="--max-old-space-size=16368"

RUN echo "\n\necho \"Welcome to the Gatsby Memory benchmark container!\\n - /usr/src/gatsby : Your local gatsby repo\\n - /usr/src/app : The memory benchmark gatsby site\\n\"" > /root/.bashrc

RUN if [ "$jemalloc" = "1" ]; then \
echo "Using jemalloc for memory allocation" && \
apt-get update && apt-get install -y libjemalloc-dev=5.1.0-3 && \
echo "/usr/lib/x86_64-linux-gnu/libjemalloc.so" >> /etc/ld.so.preload && \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking, just future opportunity:

Maybe we can just install jemalloc always in image and then have --use-jemalloc as toggle for cli (we could set LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so env var in test runner script to enable it), this could limit how often we have to (re)build image when testing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. I think if we revisit this, that's a good improvement.

echo "\n\necho \"This container is using jemelloc.\\n\"" >> /root/.bashrc; \
fi


WORKDIR /usr/src/app

# set up gatsby-dev
RUN gatsby-dev --set-path-to-repo /usr/src/gatsby

Expand Down
59 changes: 49 additions & 10 deletions benchmarks/memory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,62 @@ Within the container, two points to your local filesystem are mounted:
- /usr/src/gatsby : Your local gatsby repo
- /usr/src/site : The memory benchmark gatsby site

If you'd like to configure `jemalloc` to run within the container, set the `JEMALLOC=1` env var when building the docker container.

## Commands

### Tests

#### yarn test --memory X --num-nodes Y --node-size Z

Runs a test build within a docker container with the given memory allotment.
Within our gatsby-node, we'll create X nodes with a string property of size Y.

Example: running a build with 1000 nodes of 1mb each, in a docker container with 8gb of memory.

```
$ yarn test --memory 8g --num-nodes 500 --node-size 1m
```

#### yarn test-suite --name some-name --suite [incremental|exhaustive]

Runs through test suites defined in `scripts/test-suite.js` and outputs results to `output/some-name`.
Output includes a `results.csv` with a summary of all builds, as well as breakdowns for each memory configuration.

##### incremental

Incremental tests run builds with a `node-size` of 1m. For each memory allotment, it will start with 100
nodes in the build and increment by 100 on each success. The test will stop when all builds in a given
configuration fail.
See `incrementalConfig` in `scripts/test-suite.js` to customize test sets.

##### exhaustive

Exhaustive tests are just that, exhaustive. It will measure the time/success of every combination given.
See `exhaustiveConfig` in `scripts/test-suite.js` to customize test sets.

### Docker

These commands are used for interfacing with docker and have built-in utilities for managing the docker container.

#### yarn docker:build

Builds the container used for testing.
If you'd like to configure `jemalloc` to run within the container, set the `JEMALLOC=1` env var.

Example:

```
$ JEMALLOC=1 yarn docker:build
```

#### yarn docker:remove

Removes the docker image.

#### yarn docker:rebuild

Shorthand for remove + build.

#### yarn docker:start

Expand Down Expand Up @@ -81,17 +128,9 @@ When starting working with this benchmark:

- start `yarn watch` (possibly with `--scope`) in monorepo
- start `gatsby-dev` outside of docker in benchmark directory (just like with regular site)
- `yarn docker:connect` to get inside docker
- `npm rebuild` to rebuild binaries inside docker
- `yarn test --memory 8g --num-nodes 1000 --node-size 1m`

And repeat as many times as you want:

- make changes to `gatsby` source code as you normally would
- run `yarn gatsby:build` inside docker

## Testing

TODO

- How to configure memory limits
- Where to look
- run your `yarn test` command again
17 changes: 14 additions & 3 deletions benchmarks/memory/gatsby-node.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
const { cpuCoreCount } = require(`gatsby-core-utils`)

const NUM_NODES = parseInt(process.env.NUM_NODES || 300, 10)
const NUM_KEYS_IN_LARGE_SIZE_OBJ = parseInt(process.env.BUILD_LARGE_OBJECT_COUNT || 1024, 10)
const NUM_NODES = parseInt(process.env.BUILD_NUM_NODES || 300, 10)
const LARGE_FIELD_SIZE_RAW = process.env.BUILD_STRING_NODE_SIZE || '1m'

// convert raw size to number
const regexpSize = /([0-9]+)([kmg])?/;
const match = LARGE_FIELD_SIZE_RAW.match(regexpSize);
const suffixSizes = ['k', 'm', 'g'];
let bytesMultiplier = 1;
if (match.length > 2 && suffixSizes.indexOf(match[2]) >= 0) {
bytesMultiplier = 2 ** ((suffixSizes.indexOf(match[2]) + 1) * 10)
}
const LARGE_FIELD_SIZE = parseInt(match[1], 10) * bytesMultiplier;

const NUM_KEYS_IN_LARGE_SIZE_OBJ = 1024

exports.sourceNodes = async ({ actions, reporter }) => {
const contentDigest = Date.now().toString() // make each sourcing mark everything as dirty
Expand All @@ -25,7 +36,7 @@ exports.sourceNodes = async ({ actions, reporter }) => {
number2: NUM_NODES - i,
number3: i % 20,
largeSizeObj,
largeSizeString: `x`.repeat(1024 * 1024),
largeSizeString: `x`.repeat(LARGE_FIELD_SIZE),
internal: {
contentDigest,
type: `Test`,
Expand Down
8 changes: 6 additions & 2 deletions benchmarks/memory/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@
"gatsby:develop": "NODE_ENV=development yarn gatsby develop -H 0.0.0.0 -p 9000",
"gatsby:build:debug": "node --nolazy --inspect-brk=0.0.0.0:9229 node_modules/.bin/gatsby build",
"gatsby:develop:debug": "NODE_ENV=development node --nolazy --inspect-brk=0.0.0.0:9229 node_modules/.bin/gatsby develop -H 0.0.0.0 -p 9000",
"docker:build": "docker build -t gatsby-memory .",
"docker:build": "docker build -t gatsby-memory . --build-arg jemalloc=$JEMALLOC",
"docker:remove": "docker image rm -f gatsby-memory",
"docker:rebuild": "yarn docker:stop; yarn docker:remove && yarn docker:build",
"docker:start": "./scripts/docker-start",
"docker:connect": "./scripts/docker-connect",
"docker:start-and-connect": "./scripts/docker-start && sleep 1 && ./scripts/docker-connect",
"docker:stop": "./scripts/docker-stop",
"docker:stats": "./scripts/docker-stats"
"docker:stats": "./scripts/docker-stats",
"test": "node scripts/test.js",
"test-suite": "node scripts/test-suite.js"
},
"repository": {
"type": "git",
Expand Down
11 changes: 8 additions & 3 deletions benchmarks/memory/scripts/docker-start
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,21 @@ if [ -n "$DOCKER_ID" ]; then
return 1
fi

MEMORY_LIMIT="${DOCKER_MEMORY_LIMIT:-2g}"

DOCKER_ID=$(\
docker run -td \
--mount type=bind,source="$(pwd)/../..",target=/usr/src/gatsby \
--mount type=bind,source="$(pwd)",target=/usr/src/app \
--publish 9229:9229 \
--publish 9000:9000 \
--memory="2g" \
--memory-swap="2g" \
--memory="${MEMORY_LIMIT}" \
--memory-swap="${MEMORY_LIMIT}" \
gatsby-memory \
| head -c 12 \
)

echo "\nStarted container id ${DOCKER_ID}! Run \`yarn docker:connect\` to connect to the container.\n"
sleep 1
docker exec $DOCKER_ID bash -c "/usr/src/app/scripts/docker-write-memory &"

echo "\nStarted container id ${DOCKER_ID} with ${MEMORY_LIMIT} of memory! Run \`yarn docker:connect\` to connect to the container.\n"
13 changes: 13 additions & 0 deletions benchmarks/memory/scripts/docker-write-memory
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
while true; do
PROCESS="node"

# find all node processes
PROCESS_USAGES=$(ps -eo rss,pid,euser,args:100 --sort %mem | grep -v grep | grep -i "${PROCESS}" | awk '{print $1}')

# sum the usage
SUM_USAGE=$(echo "$PROCESS_USAGES" | awk '{s+=$1} END {printf "%.0f\n", s}')

# write to file
echo -e "$SUM_USAGE" > /usr/src/app/.docker.memusage
sleep .25
done
Loading