diff --git a/docs/internal/contributing/README.md b/docs/internal/contributing/README.md
index 833522748c..44b84262cd 100644
--- a/docs/internal/contributing/README.md
+++ b/docs/internal/contributing/README.md
@@ -35,18 +35,26 @@ Use `make lint` to ensure formatting is correct.
## Building Grafana Pyroscope
-To build:
+These are steps for building Pyroscope in single binary mode. See [development.md](./development.md) for more advanced methods building and running Pyroscope locally for development.
+
+### Building a binary
+
+To build a binary, run:
```
make go/bin
```
+### Building tests
+
To run the unit test suite:
```
make go/test
```
+### Building a docker image
+
To build the docker image use:
```
@@ -76,62 +84,34 @@ brew install pkg-config cairo pango libpng jpeg giflib librsvg
See https://github.com/Automattic/node-canvas/issues/1662
-#### Running examples locally
-replace `image: grafana/pyroscope` with the local tag name you got from docker-image/pyroscope/build (i.e):
+## Running Grafana Pyroscope locally
-```
- pyroscope:
- image: us.gcr.io/kubernetes-dev/pyroscope:main-470125e1-WIP
- ports:
- - '4040:4040'
-```
-
-#### Run with Pyroscope with embedded Grafana + Explore Profiles
+### Running examples
-In order to quickly test the whole stack it is possible to run an embedded Grafana by using target parameter:
+First, find the example you want to run and move to that directory. For example:
```
-go run ./cmd/pyroscope --target all,embedded-grafana
+cd examples/language-sdk-instrumentation/golang-push/rideshare
```
-This will start additional to Pyroscope on `:4040`, the embedded Grafana on port `:4041`.
+Then in the `docker-compose.yml` file, replace the image for the `pyroscope` service with the image built in the [Building a docker image](###building-a-docker-image) section.
-#### Front end development
-
-**Versions for development tools**:
-- Node v18
-- Yarn v1.22
-
-The front end code is all located in the `public/app` directory, although its `plugin.json`
-file exists at the repository root.
-
-To run the local front end source code:
-```sh
-yarn
-yarn dev
+```yaml
+pyroscope:
+ image: us.gcr.io/kubernetes-dev/pyroscope:main-470125e1-WIP
```
-This will install / update front end dependencies and launch a process that will build
-the front end code, launch a pyroscope web app service at `http://localhost:4041`,
-and keep that web app updated any time you save the front end source code.
-The resulting web app will not initially be connected to a pyroscope server,
-so all attempts to fetch data will fail.
+### Run with embedded Grafana + Explore Profiles
-To launch a pyroscope server for development purposes:
-```sh
-yarn backend:dev
-```
+In order to quickly test the whole stack it is possible to run an embedded Grafana by using target parameter:
-This yarn script actually runs the following:
-```sh
-make build run 'PARAMS=--config.file ./cmd/pyroscope/pyroscope.yaml'
+```
+go run ./cmd/pyroscope --target all,embedded-grafana
```
-It will take a while for this process to build and start serving pyroscope data, but
-once it is fully active, the pyroscope web app service at `http://localhost:4041`
-will be able to interact with it.
+In addition to starting Pyroscope on `:4040`, this starts an embedded Grafana on port `:4041`.
-### Dependency management
+## Dependency management
We use [Go modules](https://golang.org/cmd/go/#hdr-Modules__module_versions__and_more) to manage dependencies on external packages.
However, we don't commit the `vendor/` folder.
@@ -154,6 +134,7 @@ make go/mod
Commit the changes to `go.mod` and `go.sum` before submitting your pull request.
+
## Documentation
The Grafana Pyroscope documentation is compiled into a website published at [grafana.com](https://grafana.com/).
diff --git a/docs/internal/contributing/development.md b/docs/internal/contributing/development.md
new file mode 100644
index 0000000000..8c419c9144
--- /dev/null
+++ b/docs/internal/contributing/development.md
@@ -0,0 +1,116 @@
+# Developing Grafana Pyroscope
+
+This document contains helpful tips and tricks when developing Pyroscope.
+
+## Run Pyroscope using a debugger
+
+A debugger allows you to step through the code and inspect variables and understand control flow as the program is running.
+
+### VSCode debugger
+
+To attach a debugger to Pyroscope from VSCode, perform the following steps in the root of the Pyroscope project:
+
+1. `mkdir .vscode`
+1. `touch .vscode/tasks.json`
+1. Add the following configuration. This creates a build task in VSCode which will build the Pyroscope binary with debug symbols.
+ ```jsonc
+ {
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "type": "shell",
+ // Name this whatever you like.
+ "label": "pyroscope: build debug",
+ "command": "make",
+ "args": [
+ "EMBEDASSETS=\"\"",
+ "go/bin-pyroscope-debug"
+ ],
+ "group": "build",
+ "detail": "Build debug Pyroscope server"
+ }
+ ]
+ }
+ ```
+1. `touch .vscode/launch.json`
+1. Add the following configuration. This will add a launch task which will launch the debug Pyroscope binary and attach a debugger.
+ ```jsonc
+ {
+ "version": "0.2.0",
+ "configurations": [
+ {
+ // Name this whatever you like.
+ "name": "Launch debug Pyroscope server",
+ "type": "go",
+ "request": "launch",
+ "mode": "exec",
+ "program": "./pyroscope",
+ "args": [
+ "-config.file",
+ // Point this to a Pyroscope configuration file.
+ "./.vscode/config.yaml",
+ ],
+ "env": {
+ // env vars go here. For example:
+ // "GITHUB_CLIENT_ID": "my_client_id"
+ },
+ // This must match the name of the build task.
+ "preLaunchTask": "pyroscope: build debug",
+ }
+ ]
+ }
+ ```
+1. `touch .vscode/config.yaml`
+1. Add the following configuration. These are the settings your Pyroscope instance will use.
+ ```yaml
+ server:
+ http_listen_port: 4040
+
+ # Not necessary, but this can help avoid some system limits when making
+ # certain queries.
+ limits:
+ max_query_length: 0
+ max_query_lookback: 0
+ ```
+1. You should be able to open the "Run and Debug" menu and select "Launch debug Pyroscope server" from the drop down.
+
+1. Now when you set breakpoints in the gutter, the debugger will break appropriately and logs will be emitted in the "DEBUG CONSOLE" terminal tab.
+
+
+## Run a local Pyroscope frontend
+
+Pyroscope ships with an embedded UI. This is UI is stable and largely in maintenance mode. If you find a need to develop the UI, follow these steps to run it without embedding it into the Pyroscope binary.
+
+You will need these tools:
+
+- Node v18
+- Yarn v1.22
+
+The frontend code is all located in the `public/app` directory, although its `plugin.json`
+file exists at the repository root.
+
+To run the local frontend source code:
+```sh
+yarn
+yarn dev
+```
+
+This will install/update the frontend dependencies and launch a process that will build the frontend code, launch a pyroscope web app service at `http://localhost:4041`, and keep that web app updated any time you save the frontend source code. The resulting web app will not initially be connected to a Pyroscope server,
+so all attempts to fetch data will fail.
+
+To launch a Pyroscope server for development purposes:
+```sh
+yarn backend:dev
+```
+
+This yarn script actually runs the following:
+```sh
+make build run 'PARAMS=--config.file ./cmd/pyroscope/pyroscope.yaml'
+```
+
+> ![NOTE]
+> Alternatively, you can connect to any Pyroscope instance as long as it is running on `:4040`. If you need to connect to a Pyroscope instance running on a different port, please modify the [webpack config](https://github.com/grafana/pyroscope/blob/main/scripts/webpack/webpack.dev.js#L14-L15) accordingly.
+
+It will take a while for this process to build and start serving pyroscope data, but
+once it is fully active, the pyroscope web app service at `http://localhost:4041`
+will be able to interact with it.
diff --git a/docs/internal/images/pyroscope-debugger-running.png b/docs/internal/images/pyroscope-debugger-running.png
new file mode 100644
index 0000000000..249e303f44
Binary files /dev/null and b/docs/internal/images/pyroscope-debugger-running.png differ
diff --git a/docs/internal/images/pyroscope-launch-debugger.png b/docs/internal/images/pyroscope-launch-debugger.png
new file mode 100644
index 0000000000..40f74f9d3b
Binary files /dev/null and b/docs/internal/images/pyroscope-launch-debugger.png differ
diff --git a/examples/README.md b/examples/README.md
index defd7b08d2..6234fb2421 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -3,6 +3,7 @@
Choose a language folder to select an example for your language of choice.
# How Pyroscope works
+
Pyroscope identifies performance issues in your application by continuously profiling the code.
If you've never used a profiler before, then welcome!
diff --git a/examples/language-sdk-instrumentation/load.js b/examples/language-sdk-instrumentation/load.js
new file mode 100644
index 0000000000..ee11f8a28b
--- /dev/null
+++ b/examples/language-sdk-instrumentation/load.js
@@ -0,0 +1,18 @@
+import { check } from 'k6';
+import http from 'k6/http';
+
+export const options = {
+ duration: '5m',
+ vus: 3,
+};
+
+const URL = __ENV.TARGET_URL || 'http://localhost:5000';
+
+export default function() {
+ for (const endpoint of ['car', 'scooter', 'bike']) {
+ const res = http.get(`${URL}/${endpoint}`);
+ check(res, {
+ 'status is 200': (r) => r.status === 200,
+ });
+ }
+}
diff --git a/examples/language-sdk-instrumentation/nodejs/express-pull/.gitignore b/examples/language-sdk-instrumentation/nodejs/express-pull/.gitignore
new file mode 100644
index 0000000000..febbb5c964
--- /dev/null
+++ b/examples/language-sdk-instrumentation/nodejs/express-pull/.gitignore
@@ -0,0 +1,2 @@
+/node_modules
+/build
diff --git a/examples/language-sdk-instrumentation/nodejs/express-ts-inline/.gitignore b/examples/language-sdk-instrumentation/nodejs/express-ts-inline/.gitignore
new file mode 100644
index 0000000000..febbb5c964
--- /dev/null
+++ b/examples/language-sdk-instrumentation/nodejs/express-ts-inline/.gitignore
@@ -0,0 +1,2 @@
+/node_modules
+/build
diff --git a/examples/language-sdk-instrumentation/nodejs/express-ts-inline/Dockerfile b/examples/language-sdk-instrumentation/nodejs/express-ts-inline/Dockerfile
index b8694b5bd1..82d665fef6 100644
--- a/examples/language-sdk-instrumentation/nodejs/express-ts-inline/Dockerfile
+++ b/examples/language-sdk-instrumentation/nodejs/express-ts-inline/Dockerfile
@@ -3,10 +3,11 @@ FROM node:latest
WORKDIR /app
COPY package.json yarn.lock .
+RUN yarn
+
COPY tsconfig.json .
-RUN yarn
COPY *.ts .
RUN yarn build
-ENV DEBUG=pyroscope
-CMD ["yarn", "run", "run"]
+ENV DEBUG=pyroscope
+CMD ["yarn", "start"]
diff --git a/examples/language-sdk-instrumentation/nodejs/express-ts-inline/index.ts b/examples/language-sdk-instrumentation/nodejs/express-ts-inline/index.ts
index d85af51fb2..88a5d0375b 100644
--- a/examples/language-sdk-instrumentation/nodejs/express-ts-inline/index.ts
+++ b/examples/language-sdk-instrumentation/nodejs/express-ts-inline/index.ts
@@ -6,17 +6,18 @@ const Pyroscope = require('@pyroscope/nodejs');
const SourceMapper = Pyroscope.default.SourceMapper;
const port = process.env['PORT'] || 5000;
-
const region = process.env['REGION'] || 'default';
+const appName = process.env['APP_NAME'] || 'express-ts-inline';
+const pyroscopeUrl = process.env['PYROSCOPE_URL'] || 'http://pyroscope:4040';
const app = express();
app.use(morgan('dev'));
-app.get('/', (req, res) => {
+app.get('/', (_, res) => {
res.send('Available routes are: /bike, /car, /scooter');
});
-const genericSearchHandler = (p: number) => (req: any, res: any) => {
+const genericSearchHandler = (p: number) => (_: any, res: any) => {
const time = +new Date() + p * 1000;
let i = 0;
while (+new Date() < time) {
@@ -30,11 +31,13 @@ app.get('/bike', function bikeSearchHandler(req, res) {
genericSearchHandler(0.2)(req, res)
);
});
+
app.get('/car', function carSearchHandler(req, res) {
Pyroscope.wrapWithLabels({ vehicle: 'car' }, () =>
genericSearchHandler(1)(req, res)
);
});
+
app.get('/scooter', function scooterSearchHandler(req, res) {
Pyroscope.wrapWithLabels({ vehicle: 'scooter' }, () =>
genericSearchHandler(0.5)(req, res)
@@ -44,14 +47,14 @@ app.get('/scooter', function scooterSearchHandler(req, res) {
SourceMapper.create(['.'])
.then((sourceMapper) => {
Pyroscope.init({
- appName: 'nodejs',
- serverAddress: 'http://pyroscope:4040',
+ appName: appName,
+ serverAddress: pyroscopeUrl,
sourceMapper: sourceMapper,
tags: { region },
});
Pyroscope.start();
})
- .catch((e) => {
+ .catch((e: any) => {
console.error(e);
});
diff --git a/examples/language-sdk-instrumentation/nodejs/express-ts-inline/package.json b/examples/language-sdk-instrumentation/nodejs/express-ts-inline/package.json
index 2c7a384eb3..034596f050 100644
--- a/examples/language-sdk-instrumentation/nodejs/express-ts-inline/package.json
+++ b/examples/language-sdk-instrumentation/nodejs/express-ts-inline/package.json
@@ -5,7 +5,10 @@
"scripts": {
"build": "tsc",
"test": "echo \"Error: no test specified\" && exit 1",
- "run": "node build/index.js"
+ "start": "node build/index.js",
+ "start:local": "yarn build && PYROSCOPE_URL=http://localhost:4040 yarn start",
+ "up": "yarn down && docker compose up --build --force-recreate --no-deps",
+ "down": "docker compose down"
},
"author": "",
"license": "Apache-2.0",
diff --git a/examples/language-sdk-instrumentation/nodejs/express-ts/.gitignore b/examples/language-sdk-instrumentation/nodejs/express-ts/.gitignore
new file mode 100644
index 0000000000..febbb5c964
--- /dev/null
+++ b/examples/language-sdk-instrumentation/nodejs/express-ts/.gitignore
@@ -0,0 +1,2 @@
+/node_modules
+/build
diff --git a/examples/language-sdk-instrumentation/nodejs/express-ts/Dockerfile b/examples/language-sdk-instrumentation/nodejs/express-ts/Dockerfile
index b8694b5bd1..82d665fef6 100644
--- a/examples/language-sdk-instrumentation/nodejs/express-ts/Dockerfile
+++ b/examples/language-sdk-instrumentation/nodejs/express-ts/Dockerfile
@@ -3,10 +3,11 @@ FROM node:latest
WORKDIR /app
COPY package.json yarn.lock .
+RUN yarn
+
COPY tsconfig.json .
-RUN yarn
COPY *.ts .
RUN yarn build
-ENV DEBUG=pyroscope
-CMD ["yarn", "run", "run"]
+ENV DEBUG=pyroscope
+CMD ["yarn", "start"]
diff --git a/examples/language-sdk-instrumentation/nodejs/express-ts/index.ts b/examples/language-sdk-instrumentation/nodejs/express-ts/index.ts
index 0ac251d9d6..7619b58f50 100644
--- a/examples/language-sdk-instrumentation/nodejs/express-ts/index.ts
+++ b/examples/language-sdk-instrumentation/nodejs/express-ts/index.ts
@@ -6,17 +6,18 @@ import Pyroscope from '@pyroscope/nodejs';
const SourceMapper = Pyroscope.SourceMapper;
const port = process.env['PORT'] || 5000;
-
const region = process.env['REGION'] || 'default';
+const appName = process.env['APP_NAME'] || 'express-ts';
+const pyroscopeUrl = process.env['PYROSCOPE_URL'] || 'http://pyroscope:4040';
const app = express();
app.use(morgan('dev'));
-app.get('/', (req, res) => {
+app.get('/', (_, res) => {
res.send('Available routes are: /bike, /car, /scooter');
});
-const genericSearchHandler = (p: number) => (req: any, res: any) => {
+const genericSearchHandler = (p: number) => (_: any, res: any) => {
const time = +new Date() + p * 1000;
let i = 0;
while (+new Date() < time) {
@@ -30,11 +31,13 @@ app.get('/bike', function bikeSearchHandler(req, res) {
genericSearchHandler(0.2)(req, res)
);
});
+
app.get('/car', function carSearchHandler(req, res) {
Pyroscope.wrapWithLabels({ vehicle: 'car' }, () =>
genericSearchHandler(1)(req, res)
);
});
+
app.get('/scooter', function scooterSearchHandler(req, res) {
Pyroscope.wrapWithLabels({ vehicle: 'scooter' }, () =>
genericSearchHandler(0.5)(req, res)
@@ -44,8 +47,8 @@ app.get('/scooter', function scooterSearchHandler(req, res) {
SourceMapper.create(['.'])
.then((sourceMapper) => {
Pyroscope.init({
- appName: 'nodejs',
- serverAddress: 'http://pyroscope:4040',
+ appName: appName,
+ serverAddress: pyroscopeUrl,
sourceMapper: sourceMapper,
tags: { region },
});
diff --git a/examples/language-sdk-instrumentation/nodejs/express-ts/package.json b/examples/language-sdk-instrumentation/nodejs/express-ts/package.json
index 2c7a384eb3..034596f050 100644
--- a/examples/language-sdk-instrumentation/nodejs/express-ts/package.json
+++ b/examples/language-sdk-instrumentation/nodejs/express-ts/package.json
@@ -5,7 +5,10 @@
"scripts": {
"build": "tsc",
"test": "echo \"Error: no test specified\" && exit 1",
- "run": "node build/index.js"
+ "start": "node build/index.js",
+ "start:local": "yarn build && PYROSCOPE_URL=http://localhost:4040 yarn start",
+ "up": "yarn down && docker compose up --build --force-recreate --no-deps",
+ "down": "docker compose down"
},
"author": "",
"license": "Apache-2.0",
diff --git a/examples/language-sdk-instrumentation/nodejs/express/.gitignore b/examples/language-sdk-instrumentation/nodejs/express/.gitignore
new file mode 100644
index 0000000000..febbb5c964
--- /dev/null
+++ b/examples/language-sdk-instrumentation/nodejs/express/.gitignore
@@ -0,0 +1,2 @@
+/node_modules
+/build
diff --git a/examples/language-sdk-instrumentation/nodejs/express/Dockerfile b/examples/language-sdk-instrumentation/nodejs/express/Dockerfile
index 708be4d4c9..28c6c1ab9a 100644
--- a/examples/language-sdk-instrumentation/nodejs/express/Dockerfile
+++ b/examples/language-sdk-instrumentation/nodejs/express/Dockerfile
@@ -8,4 +8,4 @@ COPY index.js .
ENV DEBUG=pyroscope
ENV PYROSCOPE_WALL_COLLECT_CPU_TIME=true
-CMD ["node", "index.js"]
+CMD ["node", "index.js"]
diff --git a/examples/language-sdk-instrumentation/nodejs/express/index.js b/examples/language-sdk-instrumentation/nodejs/express/index.js
index ac29ee3df3..16fa648969 100644
--- a/examples/language-sdk-instrumentation/nodejs/express/index.js
+++ b/examples/language-sdk-instrumentation/nodejs/express/index.js
@@ -2,19 +2,20 @@
const Pyroscope = require('@pyroscope/nodejs');
const port = process.env['PORT'] || 5000;
-
const region = process.env['REGION'] || 'default';
+const appName = process.env['APP_NAME'] || 'express';
+const pyroscopeUrl = process.env['PYROSCOPE_URL'] || 'http://pyroscope:4040';
const express = require('express');
const morgan = require('morgan');
const app = express();
app.use(morgan('dev'));
-app.get('/', (req, res) => {
+app.get('/', (_, res) => {
res.send('Available routes are: /bike, /car, /scooter');
});
-const genericSearchHandler = (p) => (req, res) => {
+const genericSearchHandler = (p) => (_, res) => {
const time = +new Date() + p * 1000;
let i = 0;
while (+new Date() < time) {
@@ -24,11 +25,10 @@ const genericSearchHandler = (p) => (req, res) => {
};
Pyroscope.init({
- appName: 'nodejs',
- serverAddress: process.env['PYROSCOPE_SERVER'] || 'http://pyroscope:4040',
+ appName: appName,
+ serverAddress: pyroscopeUrl,
tags: { region },
});
-
Pyroscope.start();
app.get('/bike', function bikeSearchHandler(req, res) {
@@ -36,11 +36,13 @@ app.get('/bike', function bikeSearchHandler(req, res) {
genericSearchHandler(0.5)(req, res)
);
});
+
app.get('/car', function carSearchHandler(req, res) {
Pyroscope.wrapWithLabels({ vehicle: 'car' }, () =>
genericSearchHandler(1)(req, res)
);
});
+
app.get('/scooter', function scooterSearchHandler(req, res) {
Pyroscope.wrapWithLabels({ vehicle: 'scooter' }, () =>
genericSearchHandler(0.25)(req, res)
diff --git a/examples/language-sdk-instrumentation/nodejs/express/package.json b/examples/language-sdk-instrumentation/nodejs/express/package.json
index d97ad3668a..a391a0e12a 100644
--- a/examples/language-sdk-instrumentation/nodejs/express/package.json
+++ b/examples/language-sdk-instrumentation/nodejs/express/package.json
@@ -5,12 +5,15 @@
"main": "index.js",
"scripts": {
"start": "node index.js",
- "test": "echo \"Error: no test specified\" && exit 1"
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "start:local": "PYROSCOPE_URL=http://localhost:4040 yarn start",
+ "up": "yarn down && docker compose up --build --force-recreate --no-deps",
+ "down": "docker compose down"
},
"author": "",
"license": "Apache-2.0",
"dependencies": {
- "@pyroscope/nodejs": "v0.3.11",
+ "@pyroscope/nodejs": "v0.4.0",
"express": "^4.19.2",
"morgan": "^1.10.0"
},
diff --git a/examples/language-sdk-instrumentation/nodejs/tinyhttp/.gitignore b/examples/language-sdk-instrumentation/nodejs/tinyhttp/.gitignore
new file mode 100644
index 0000000000..febbb5c964
--- /dev/null
+++ b/examples/language-sdk-instrumentation/nodejs/tinyhttp/.gitignore
@@ -0,0 +1,2 @@
+/node_modules
+/build
diff --git a/examples/language-sdk-instrumentation/nodejs/tinyhttp/Dockerfile b/examples/language-sdk-instrumentation/nodejs/tinyhttp/Dockerfile
new file mode 100644
index 0000000000..85b325a90f
--- /dev/null
+++ b/examples/language-sdk-instrumentation/nodejs/tinyhttp/Dockerfile
@@ -0,0 +1,10 @@
+FROM node:latest
+
+WORKDIR /app
+
+COPY package.json yarn.lock .
+RUN yarn install
+
+COPY index.js .
+
+CMD ["node", "index.js"]
diff --git a/examples/language-sdk-instrumentation/nodejs/tinyhttp/docker-compose.yml b/examples/language-sdk-instrumentation/nodejs/tinyhttp/docker-compose.yml
new file mode 100644
index 0000000000..a6d3fd6c60
--- /dev/null
+++ b/examples/language-sdk-instrumentation/nodejs/tinyhttp/docker-compose.yml
@@ -0,0 +1,46 @@
+version: '3.9'
+services:
+ pyroscope:
+ image: grafana/pyroscope
+ ports:
+ - 4040:4040
+ us-east:
+ ports:
+ - 5000
+ environment:
+ - REGION=us-east
+ build:
+ context: .
+ eu-north:
+ ports:
+ - 5000
+ environment:
+ - REGION=eu-north
+ build:
+ context: .
+ ap-south:
+ ports:
+ - 5000
+ environment:
+ - REGION=ap-south
+ build:
+ context: .
+ load-generator:
+ build:
+ context: ../
+ dockerfile: Dockerfile.load-generator
+ depends_on:
+ - us-east
+ - eu-north
+ - ap-south
+ grafana:
+ image: grafana/grafana:latest
+ environment:
+ - GF_INSTALL_PLUGINS=grafana-pyroscope-app
+ - GF_AUTH_ANONYMOUS_ENABLED=true
+ - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
+ - GF_AUTH_DISABLE_LOGIN_FORM=true
+ volumes:
+ - ./grafana-provisioning:/etc/grafana/provisioning
+ ports:
+ - 3000:3000
diff --git a/examples/language-sdk-instrumentation/nodejs/tinyhttp/grafana-provisioning/datasources/pyroscope.yml b/examples/language-sdk-instrumentation/nodejs/tinyhttp/grafana-provisioning/datasources/pyroscope.yml
new file mode 100644
index 0000000000..6f04d797c8
--- /dev/null
+++ b/examples/language-sdk-instrumentation/nodejs/tinyhttp/grafana-provisioning/datasources/pyroscope.yml
@@ -0,0 +1,14 @@
+---
+apiVersion: 1
+datasources:
+ - uid: local-pyroscope
+ type: grafana-pyroscope-datasource
+ name: Pyroscope
+ url: http://pyroscope:4040
+ jsonData:
+ keepCookies: [GitSession]
+ # Uncomment these if using with Grafana Cloud
+ # basicAuth: true
+ # basicAuthUser: '123456'
+ # secureJsonData:
+ # basicAuthPassword: PASSWORD
diff --git a/examples/language-sdk-instrumentation/nodejs/tinyhttp/grafana-provisioning/plugins/explore-profiles.yml b/examples/language-sdk-instrumentation/nodejs/tinyhttp/grafana-provisioning/plugins/explore-profiles.yml
new file mode 100644
index 0000000000..0d1302202e
--- /dev/null
+++ b/examples/language-sdk-instrumentation/nodejs/tinyhttp/grafana-provisioning/plugins/explore-profiles.yml
@@ -0,0 +1,11 @@
+---
+apiVersion: 1
+apps:
+ - type: grafana-pyroscope-app
+ jsonData:
+ backendUrl: http://pyroscope:4040
+ # uncomment this if sending data to Grafana Cloud
+ # basicAuthUser: '123456'
+ secureJsonData:
+ # uncomment this if sending data to Grafana Cloud
+ # basicAuthPassword: PASSWORD
diff --git a/examples/language-sdk-instrumentation/nodejs/tinyhttp/index.js b/examples/language-sdk-instrumentation/nodejs/tinyhttp/index.js
new file mode 100644
index 0000000000..0847e03126
--- /dev/null
+++ b/examples/language-sdk-instrumentation/nodejs/tinyhttp/index.js
@@ -0,0 +1,45 @@
+import { init, start } from '@pyroscope/nodejs';
+import { App } from '@tinyhttp/app';
+import { logger } from '@tinyhttp/logger';
+
+const port = process.env['PORT'] || 5000;
+const region = process.env['REGION'] || 'default';
+const appName = process.env['APP_NAME'] || 'tinyhttp';
+const pyroscopeUrl = process.env['PYROSCOPE_URL'] || 'http://pyroscope:4040';
+
+init({
+ appName: appName,
+ serverAddress: pyroscopeUrl,
+ tags: { region },
+});
+start();
+
+const app = new App();
+app.use(logger());
+
+app.get('/', (_, res) => {
+ res.send('Available routes are: /bike, /car, /scooter');
+})
+
+const genericSearchHandler = (p) => (_, res) => {
+ const time = +new Date() + p * 1000;
+ let i = 0;
+ while (+new Date() < time) {
+ i = i + Math.random();
+ }
+ res.send('Vehicle found');
+};
+
+app.get('/bike', (req, res) => {
+ genericSearchHandler(0.5)(req, res);
+});
+
+app.get('/car', (req, res) => {
+ genericSearchHandler(1)(req, res);
+});
+
+app.get('/scooter', (req, res) => {
+ genericSearchHandler(0.25)(req, res);
+});
+
+app.listen(port, () => console.log(`Started on http://localhost:${port}`));
diff --git a/examples/language-sdk-instrumentation/nodejs/tinyhttp/package.json b/examples/language-sdk-instrumentation/nodejs/tinyhttp/package.json
new file mode 100644
index 0000000000..1bca2b75cc
--- /dev/null
+++ b/examples/language-sdk-instrumentation/nodejs/tinyhttp/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "rideshare-app-express",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "type": "module",
+ "scripts": {
+ "start": "node index.js",
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "start:local": "PYROSCOPE_URL=http://localhost:4040 yarn start",
+ "up": "yarn down && docker compose up --build --force-recreate --no-deps",
+ "down": "docker compose down"
+ },
+
+ "author": "",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@pyroscope/nodejs": "^0.4.0",
+ "@tinyhttp/app": "^2.4.0",
+ "@tinyhttp/logger": "^2.0.0"
+ }
+}
diff --git a/examples/language-sdk-instrumentation/nodejs/tinyhttp/yarn.lock b/examples/language-sdk-instrumentation/nodejs/tinyhttp/yarn.lock
new file mode 100644
index 0000000000..1a646c821f
--- /dev/null
+++ b/examples/language-sdk-instrumentation/nodejs/tinyhttp/yarn.lock
@@ -0,0 +1,307 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@datadog/pprof@^5.3.0":
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/@datadog/pprof/-/pprof-5.3.0.tgz#c2f58d328ecced7f99887f1a559d7fe3aecb9219"
+ integrity sha512-53z2Q3K92T6Pf4vz4Ezh8kfkVEvLzbnVqacZGgcbkP//q0joFzO8q00Etw1S6NdnCX0XmX08ULaF4rUI5r14mw==
+ dependencies:
+ delay "^5.0.0"
+ node-gyp-build "<4.0"
+ p-limit "^3.1.0"
+ pprof-format "^2.1.0"
+ source-map "^0.7.4"
+
+"@pyroscope/nodejs@^0.3.11":
+ version "0.3.11"
+ resolved "https://registry.yarnpkg.com/@pyroscope/nodejs/-/nodejs-0.3.11.tgz#d3ffed8423b628701d06cdc6ef97fd5943d91939"
+ integrity sha512-xqxUDrzgdfTic4QU3FyvPvO3iAF63zEEI+gXgBBNA6MrJVOJxaEDJkeOGnH0AT7yG/vLJVmSeo4+VyIKrCmztw==
+ dependencies:
+ "@datadog/pprof" "^5.3.0"
+ axios "^0.28.0"
+ debug "^4.3.3"
+ form-data "^4.0.0"
+ regenerator-runtime "^0.13.11"
+ source-map "^0.7.3"
+
+"@tinyhttp/accepts@2.2.3":
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/accepts/-/accepts-2.2.3.tgz#be7601206eeda8bd8350ad82a2307808efcb7831"
+ integrity sha512-9pQN6pJAJOU3McmdJWTcyq7LLFW8Lj5q+DadyKcvp+sxMkEpktKX5sbfJgJuOvjk6+1xWl7pe0YL1US1vaO/1w==
+ dependencies:
+ mime "4.0.4"
+ negotiator "^0.6.3"
+
+"@tinyhttp/app@^2.4.0":
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/app/-/app-2.4.0.tgz#d91b3f36146d2dc63cf8b1492b94488ecea3cc6d"
+ integrity sha512-vOPiCemQRJq5twnl06dde6XnWiNbVMdVRFJWW/yC/9G0qgvV2TvzNNTxrdlz6YmyB7vIC7Fg3qS6m6gx8RbBNQ==
+ dependencies:
+ "@tinyhttp/cookie" "2.1.1"
+ "@tinyhttp/proxy-addr" "2.2.0"
+ "@tinyhttp/req" "2.2.4"
+ "@tinyhttp/res" "2.2.4"
+ "@tinyhttp/router" "2.2.3"
+ header-range-parser "1.1.3"
+ regexparam "^2.0.2"
+
+"@tinyhttp/content-disposition@2.2.2":
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/content-disposition/-/content-disposition-2.2.2.tgz#1207d18bdd59e1cd38ecf2493ee187f4f592ebe7"
+ integrity sha512-crXw1txzrS36huQOyQGYFvhTeLeG0Si1xu+/l6kXUVYpE0TjFjEZRqTbuadQLfKGZ0jaI+jJoRyqaWwxOSHW2g==
+
+"@tinyhttp/content-type@^0.1.4":
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/content-type/-/content-type-0.1.4.tgz#112bce3b564213e0ed43fa76fccca4237be3a634"
+ integrity sha512-dl6f3SHIJPYbhsW1oXdrqOmLSQF/Ctlv3JnNfXAE22kIP7FosqJHxkz/qj2gv465prG8ODKH5KEyhBkvwrueKQ==
+
+"@tinyhttp/cookie-signature@2.1.1":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/cookie-signature/-/cookie-signature-2.1.1.tgz#ae2caad6aec4ec51d42e7d852ae34a04196d5138"
+ integrity sha512-VDsSMY5OJfQJIAtUgeQYhqMPSZptehFSfvEEtxr+4nldPA8IImlp3QVcOVuK985g4AFR4Hl1sCbWCXoqBnVWnw==
+
+"@tinyhttp/cookie@2.1.1":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/cookie/-/cookie-2.1.1.tgz#50ad664f732357a466a14cdc888e88e80dc3440e"
+ integrity sha512-h/kL9jY0e0Dvad+/QU3efKZww0aTvZJslaHj3JTPmIPC9Oan9+kYqmh3M6L5JUQRuTJYFK2nzgL2iJtH2S+6dA==
+
+"@tinyhttp/encode-url@2.1.1":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/encode-url/-/encode-url-2.1.1.tgz#a52bdbd75f541455190d1a16b0a81374c8dc587d"
+ integrity sha512-AhY+JqdZ56qV77tzrBm0qThXORbsVjs/IOPgGCS7x/wWnsa/Bx30zDUU/jPAUcSzNOzt860x9fhdGpzdqbUeUw==
+
+"@tinyhttp/etag@2.1.2":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/etag/-/etag-2.1.2.tgz#34fc91933bd1acce3cda3a64e5352ce5514abe4e"
+ integrity sha512-j80fPKimGqdmMh6962y+BtQsnYPVCzZfJw0HXjyH70VaJBHLKGF+iYhcKqzI3yef6QBNa8DKIPsbEYpuwApXTw==
+
+"@tinyhttp/forwarded@2.1.1":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/forwarded/-/forwarded-2.1.1.tgz#dbf2cae75a1737b88b71c2a2d1931e5e9ced73c3"
+ integrity sha512-nO3kq0R1LRl2+CAMlnggm22zE6sT8gfvGbNvSitV6F9eaUSurHP0A8YZFMihSkugHxK+uIegh1TKrqgD8+lyGQ==
+
+"@tinyhttp/logger@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/logger/-/logger-2.0.0.tgz#64c0e53497abc82765c17289a05cd75edbd4c04a"
+ integrity sha512-8DfLQjGDIaIJeivYamVrrpmwmsGwS8wt2DGvzlcY5HEBagdiI4QJy/veAFcUHuaJqufn4wLwmn4q5VUkW8BCpQ==
+ dependencies:
+ colorette "^2.0.20"
+ dayjs "^1.11.10"
+ http-status-emojis "^2.2.0"
+
+"@tinyhttp/proxy-addr@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/proxy-addr/-/proxy-addr-2.2.0.tgz#82487f25af4d320d79e613bbecac17c7ad69c8f2"
+ integrity sha512-WM/PPL9xNvrs7/8Om5nhKbke5FHrP3EfjOOR+wBnjgESfibqn0K7wdUTnzSLp1lBmemr88os1XvzwymSgaibyA==
+ dependencies:
+ "@tinyhttp/forwarded" "2.1.1"
+ ipaddr.js "^2.2.0"
+
+"@tinyhttp/req@2.2.4":
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/req/-/req-2.2.4.tgz#c9dfc40e3a3b3cc1eb48bdc137b5d0cd71501057"
+ integrity sha512-lQAZIAo0NOeghxFOZS57tQzxpHSPPLs9T68Krq2BncEBImKwqaDKUt7M9Y5Kb+rvC/GwIL3LeErhkg7f5iG4IQ==
+ dependencies:
+ "@tinyhttp/accepts" "2.2.3"
+ "@tinyhttp/type-is" "2.2.4"
+ "@tinyhttp/url" "2.1.1"
+ header-range-parser "^1.1.3"
+
+"@tinyhttp/res@2.2.4":
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/res/-/res-2.2.4.tgz#ed79d511f21d6ef226ae907bdecc26f69c93583c"
+ integrity sha512-ETBRShnO19oJyIg2XQHQoofXPWeTXPAuwnIVYkU8WaftvXd/Vz4y5+WFQDHUzKlmdGOw5fAFnrEU7pIVMeFeVA==
+ dependencies:
+ "@tinyhttp/content-disposition" "2.2.2"
+ "@tinyhttp/cookie" "2.1.1"
+ "@tinyhttp/cookie-signature" "2.1.1"
+ "@tinyhttp/encode-url" "2.1.1"
+ "@tinyhttp/req" "2.2.4"
+ "@tinyhttp/send" "2.2.3"
+ "@tinyhttp/vary" "^0.1.3"
+ es-escape-html "^0.1.1"
+ mime "4.0.4"
+
+"@tinyhttp/router@2.2.3":
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/router/-/router-2.2.3.tgz#a29a33da89ae7365f5897aed20f44663509273a4"
+ integrity sha512-O0MQqWV3Vpg/uXsMYg19XsIgOhwjyhTYWh51Qng7bxqXixxx2PEvZWnFjP7c84K7kU/nUX41KpkEBTLnznk9/Q==
+
+"@tinyhttp/send@2.2.3":
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/send/-/send-2.2.3.tgz#726c400af76c62963bd71fe92e6e6838f35a7996"
+ integrity sha512-o4cVHHGQ8WjVBS8UT0EE/2WnjoybrfXikHwsRoNlG1pfrC/Sd01u1N4Te8cOd/9aNGLr4mGxWb5qTm2RRtEi7g==
+ dependencies:
+ "@tinyhttp/content-type" "^0.1.4"
+ "@tinyhttp/etag" "2.1.2"
+ mime "4.0.4"
+
+"@tinyhttp/type-is@2.2.4":
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/type-is/-/type-is-2.2.4.tgz#8f5a30bb3cdc93dd02f399e152d88b815b0efc99"
+ integrity sha512-7F328NheridwjIfefBB2j1PEcKKABpADgv7aCJaE8x8EON77ZFrAkI3Rir7pGjopV7V9MBmW88xUQigBEX2rmQ==
+ dependencies:
+ "@tinyhttp/content-type" "^0.1.4"
+ mime "4.0.4"
+
+"@tinyhttp/url@2.1.1":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/url/-/url-2.1.1.tgz#77fa8963f5b698bacbdc6912407f946d32c793e1"
+ integrity sha512-POJeq2GQ5jI7Zrdmj22JqOijB5/GeX+LEX7DUdml1hUnGbJOTWDx7zf2b5cCERj7RoXL67zTgyzVblBJC+NJWg==
+
+"@tinyhttp/vary@^0.1.3":
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/@tinyhttp/vary/-/vary-0.1.3.tgz#f5bea4769f380c43a158832a8daad8e8b186757c"
+ integrity sha512-SoL83sQXAGiHN1jm2VwLUWQSQeDAAl1ywOm6T0b0Cg1CZhVsjoiZadmjhxF6FHCCY7OHHVaLnTgSMxTPIDLxMg==
+
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+ integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
+
+axios@^0.28.0:
+ version "0.28.1"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.28.1.tgz#2a7bcd34a3837b71ee1a5ca3762214b86b703e70"
+ integrity sha512-iUcGA5a7p0mVb4Gm/sy+FSECNkPFT4y7wt6OM/CDpO/OnNCvSs3PoMG8ibrC9jRoGYU0gUK5pXVC4NPXq6lHRQ==
+ dependencies:
+ follow-redirects "^1.15.0"
+ form-data "^4.0.0"
+ proxy-from-env "^1.1.0"
+
+colorette@^2.0.20:
+ version "2.0.20"
+ resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
+ integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
+
+combined-stream@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
+ integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
+ dependencies:
+ delayed-stream "~1.0.0"
+
+dayjs@^1.11.10:
+ version "1.11.13"
+ resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c"
+ integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
+
+debug@^4.3.3:
+ version "4.3.7"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
+ integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
+ dependencies:
+ ms "^2.1.3"
+
+delay@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d"
+ integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==
+
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+ integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
+
+es-escape-html@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/es-escape-html/-/es-escape-html-0.1.1.tgz#9a582d49754ec6204524952c76a383fe5f03c1c0"
+ integrity sha512-yUx1o+8RsG7UlszmYPtks+dm6Lho2m8lgHMOsLJQsFI0R8XwUJwiMhM1M4E/S8QLeGyf6MkDV/pWgjQ0tdTSyQ==
+
+follow-redirects@^1.15.0:
+ version "1.15.9"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
+ integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
+
+form-data@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
+ integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.8"
+ mime-types "^2.1.12"
+
+header-range-parser@1.1.3, header-range-parser@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/header-range-parser/-/header-range-parser-1.1.3.tgz#6414b5b12e3b645d29d85225a58fd207d66d30ef"
+ integrity sha512-B9zCFt3jH8g09LR1vHL4pcAn8yMEtlSlOUdQemzHMRKMImNIhhszdeosYFfNW0WXKQtXIlWB+O4owHJKvEJYaA==
+
+http-status-emojis@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/http-status-emojis/-/http-status-emojis-2.2.0.tgz#2cf3316f0c1610c4fc94c6fccdada35aa70f992a"
+ integrity sha512-ompKtgwpx8ff0hsbpIB7oE4ax1LXoHmftsHHStMELX56ivG3GhofTX8ZHWlUaFKfGjcGjw6G3rPk7dJRXMmbbg==
+
+ipaddr.js@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8"
+ integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==
+
+mime-db@1.52.0:
+ version "1.52.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.12:
+ version "2.1.35"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
+mime@4.0.4:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-4.0.4.tgz#9f851b0fc3c289d063b20a7a8055b3014b25664b"
+ integrity sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==
+
+ms@^2.1.3:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+negotiator@^0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
+ integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
+
+node-gyp-build@<4.0:
+ version "3.9.0"
+ resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.9.0.tgz#53a350187dd4d5276750da21605d1cb681d09e25"
+ integrity sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A==
+
+p-limit@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
+ integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+ dependencies:
+ yocto-queue "^0.1.0"
+
+pprof-format@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/pprof-format/-/pprof-format-2.1.0.tgz#acc8d7773bcf4faf0a3d3df11bceefba7ac06664"
+ integrity sha512-0+G5bHH0RNr8E5hoZo/zJYsL92MhkZjwrHp3O2IxmY8RJL9ooKeuZ8Tm0ZNBw5sGZ9TiM71sthTjWoR2Vf5/xw==
+
+proxy-from-env@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
+ integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+
+regenerator-runtime@^0.13.11:
+ version "0.13.11"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
+ integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
+
+regexparam@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-2.0.2.tgz#a0f6aa057c67b1c9c09508c45823c0755b1f6e58"
+ integrity sha512-A1PeDEYMrkLrfyOwv2jwihXbo9qxdGD3atBYQA9JJgreAx8/7rC6IUkWOw2NQlOxLp2wL0ifQbh1HuidDfYA6w==
+
+source-map@^0.7.3, source-map@^0.7.4:
+ version "0.7.4"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
+ integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
+
+yocto-queue@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
+ integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/examples/load.js b/examples/load.js
new file mode 100644
index 0000000000..ee11f8a28b
--- /dev/null
+++ b/examples/load.js
@@ -0,0 +1,18 @@
+import { check } from 'k6';
+import http from 'k6/http';
+
+export const options = {
+ duration: '5m',
+ vus: 3,
+};
+
+const URL = __ENV.TARGET_URL || 'http://localhost:5000';
+
+export default function() {
+ for (const endpoint of ['car', 'scooter', 'bike']) {
+ const res = http.get(`${URL}/${endpoint}`);
+ check(res, {
+ 'status is 200': (r) => r.status === 200,
+ });
+ }
+}