diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f564c51 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,25 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +ij_any_block_comment_at_first_column = false +ij_any_line_comment_at_first_column = false +ij_any_block_comment_add_space = true +ij_typescript_use_double_quotes = false +ij_javascript_use_double_quotes = false +ij_sass_use_double_quotes = false +ij_html_quote_style = double + +[*.json] +indent_size = 2 + +[*.yml] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..e2febac --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,27 @@ +name: Build Docker Container +on: + push: + branches: + - master + +permissions: + contents: read + packages: write + +jobs: + build-api: + name: Build Docker Container + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build Docker image + run: docker build -t ghcr.io/frumentumnl/responsetimer:latest . + - name: Push Docker image + run: docker push ghcr.io/frumentumnl/responsetimer diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e6db21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +node_modules/ +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5cce558 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM node:18.16.0-alpine +COPY . /app +WORKDIR /app +CMD ["yarn", "start"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..20c0a8f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Noah van der Aa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..124c397 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# responsetimer + +A simple script that tracks the time an endpoint takes to respond and posts the results to statuspage.io + +### Configuration + +All configuration is done via environment variables. + +```properties +# Your API key for statuspage.io +API_KEY= +# The id of the page the metric is on +PAGE_ID= +# The id of the metric to post the timings results to +METRIC_ID= +# The URL to monitor +TARGET_URL=https://frumentum.nl +# The interval in seconds to check the URL, defaults to 60 +INTERVAL=60 +# The number of times to check the URL before conducting the final measurement, defaults to 5 +# see the section below for further explanation +WARMUP_COUNT=5 +``` + +> [!NOTE] +> responsetimer sends a few requests to the URL before conducting the final measurement. +> This is done to ensure actions like DNS lookups do not affect the actual response time. +> The number of requests is determined by the `WARMUP_COUNT` environment variable, and can be set to `0` to completely +> disable the warmup feature. diff --git a/package.json b/package.json new file mode 100644 index 0000000..1fd5477 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "responsetimer", + "version": "1.0.0", + "main": "src/index.js", + "repository": "https://github.com/FrumentumNL/responsetimer.git", + "type": "module", + "license": "MIT", + "scripts": { + "start": "node src/index.js" + } +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..325f542 --- /dev/null +++ b/src/index.js @@ -0,0 +1,61 @@ +const API_KEY = process.env.API_KEY; +const PAGE_ID = process.env.PAGE_ID; +const METRIC_ID = process.env.METRIC_ID; +const TARGET_URL = process.env.TARGET_URL; +const INTERVAL = Number(process.env.INTERVAL ?? '60'); +const WARMUP_COUNT = parseInt(process.env.WARMUP_COUNT ?? '5'); + +let msg = []; +if (!API_KEY) msg.push('- Missing api key. Please set the API_KEY env variable.'); +if (!PAGE_ID) msg.push('- Missing page id. Please set the PAGE_ID env variable.'); +if (!METRIC_ID) msg.push('- Missing metric id. Please set the METRIC_ID env variable.'); +if (!TARGET_URL) msg.push('- Missing target url. Please set the TARGET_URL env variable.'); + +if (msg.length !== 0) { + console.error('Encountered one or more configuration errors:') + console.error(msg.join('\n')); + process.exit(0); +} + +let request = async () => await fetch(TARGET_URL, { + headers: { + 'User-Agent': 'Mozilla/5.0 (compatible; responsetimer/1.0.0; +https://github.com/FrumentumNL/responsetimer)' + } +}); + +let run = async () => { + if (WARMUP_COUNT > 0) { + console.log(`Sending warmup requests to ${TARGET_URL}...`); + for (let i = 0; i < WARMUP_COUNT; i++) { + await request(); + performance.now(); + } + console.log('Warmed up!'); + } + + console.log(`Sending requests to ${TARGET_URL}...`); + let start = performance.now(); + await request(); + let time = performance.now() - start; + console.log(`Done! Took ${time}ms`); + + console.log('Posting times to statuspage...'); + let resp = await fetch(`https://api.statuspage.io/v1/pages/${PAGE_ID}/metrics/${METRIC_ID}/data.json`, { + method: 'POST', + headers: { + Authorization: `OAuth ${API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + data: { + timestamp: Math.floor(Date.now() / 1000), + value: time + } + }) + }); + if (!resp.ok) throw new Error(`Failed to post time to statuspage: ${await resp.text()}`); + console.log('Posted times to statuspage!'); +}; + +await run(); +if (INTERVAL > 0) setInterval(run, INTERVAL * 1000);