Compare commits
8 Commits
Author | SHA1 | Date |
---|---|---|
Nemo | 631d7bfd7d | |
Nemo | 66bf617826 | |
Nemo | 8f4bc06ba7 | |
Nemo | 8baa2d5234 | |
Nemo | 50587cbd1c | |
Nemo | 2e10596783 | |
Nemo | 4bd0bad2af | |
Nemo | 67e2b1bc82 |
|
@ -0,0 +1,86 @@
|
||||||
|
on: push
|
||||||
|
name: CI
|
||||||
|
jobs:
|
||||||
|
npm-tests:
|
||||||
|
env:
|
||||||
|
CHROME_BIN: /usr/bin/chromium
|
||||||
|
runs-on: [self-hosted]
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node: ['14', '16', '18']
|
||||||
|
name: Node Tests
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: ${{matrix.node}}
|
||||||
|
- run: npm install
|
||||||
|
- name: test
|
||||||
|
continue-on-error: true
|
||||||
|
id: test
|
||||||
|
run: npm test
|
||||||
|
- name: retry
|
||||||
|
continue-on-error: true
|
||||||
|
id: retry1
|
||||||
|
if: steps.test.outcome=='failure'
|
||||||
|
run: npm test
|
||||||
|
- name: set the status
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
if ${{ steps.test.outcome=='success' || steps.retry1.outcome=='success' }}; then
|
||||||
|
echo "Tests Passed"
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
docker-tests:
|
||||||
|
runs-on: [self-hosted]
|
||||||
|
timeout-minutes: 5
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Docker test
|
||||||
|
run: |
|
||||||
|
wget https://github.com/tkp1n/chromium-ci/raw/41510dc154c4184f7e09461ba76f86f61c460070/seccomp/chromium.json
|
||||||
|
docker build -t prom-act-test .
|
||||||
|
container_name=$(docker run --security-opt seccomp=chromium.json --detach prom-act-test)
|
||||||
|
echo $container_name
|
||||||
|
docker inspect $container_name
|
||||||
|
docker ps --no-trunc
|
||||||
|
until [ "`docker inspect -f {{.State.Health.Status}} $container_name`" == "healthy" ]; do
|
||||||
|
sleep 10;
|
||||||
|
echo "Waiting for container to be healthy"
|
||||||
|
docker inspect -f {{.State.Health}} $container_name
|
||||||
|
docker logs $container_name
|
||||||
|
done;
|
||||||
|
docker inspect $container_name
|
||||||
|
sleep 5
|
||||||
|
# Show usage
|
||||||
|
docker exec $container_name wget -q -O- localhost:3000/metrics | grep act_fup
|
||||||
|
# Check logs
|
||||||
|
docker logs $container_name
|
||||||
|
# Stop and kill the test container
|
||||||
|
docker stop $container_name && docker rm $container_name && docker rmi prom-act-test
|
||||||
|
publish:
|
||||||
|
needs: [npm-tests, docker-tests]
|
||||||
|
runs-on: [self-hosted]
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Push to GitHub Container Registry
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
if: ${{github.ref_name != 'master'}}
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
tags: ghcr.io/${{ github.repository }}:${{github.ref_name}}
|
||||||
|
- name: Push to GitHub Container Registry (latest)
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
if: ${{github.ref_name == 'master'}}
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
tags: ghcr.io/${{ github.repository }}:latest
|
|
@ -1,2 +1,3 @@
|
||||||
node_modules
|
node_modules
|
||||||
screenshots/
|
screenshots/
|
||||||
|
chromium.json
|
||||||
|
|
36
Dockerfile
36
Dockerfile
|
@ -1,15 +1,43 @@
|
||||||
FROM schliflo/docker-puppeteer:21.3.4
|
FROM alpine
|
||||||
|
|
||||||
LABEL maintainer "Nemo <docker@captnemo.in>"
|
LABEL maintainer "Nemo <docker@captnemo.in>"
|
||||||
|
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
chromium \
|
||||||
|
nss \
|
||||||
|
freetype \
|
||||||
|
harfbuzz \
|
||||||
|
ca-certificates \
|
||||||
|
ttf-freefont \
|
||||||
|
nodejs \
|
||||||
|
npm
|
||||||
|
|
||||||
|
# Tell Puppeteer to skip installing Chrome. We'll be using the installed package.
|
||||||
|
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
|
||||||
|
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser \
|
||||||
|
CHROME_BIN=/usr/bin/chromium-browser
|
||||||
|
|
||||||
|
# Add user so we don't need --no-sandbox.
|
||||||
|
RUN addgroup -S pptruser && adduser -S -G pptruser pptruser \
|
||||||
|
&& addgroup pptruser audio \
|
||||||
|
&& addgroup pptruser video \
|
||||||
|
&& mkdir -p /home/pptruser/Downloads /app \
|
||||||
|
&& chown -R pptruser:pptruser /home/pptruser \
|
||||||
|
&& chown -R pptruser:pptruser /app
|
||||||
|
|
||||||
|
USER pptruser
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package.json package-lock.json /app/
|
COPY --chown=pptruser package.json package-lock.json /app/
|
||||||
|
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
COPY index.js server.js prom.js *.md /app/
|
COPY --chown=pptruser index.js server.js prom.js *.md /app/
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/node", "server.js"]
|
ENTRYPOINT ["/usr/bin/node", "server.js"]
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=1m --timeout=5s \
|
||||||
|
CMD wget -q http://localhost:3000/ || exit 1
|
||||||
|
|
36
README.md
36
README.md
|
@ -1,11 +1,9 @@
|
||||||
# prometheus-act-exporter
|
# prometheus-act-exporter
|
||||||
|
|
||||||
![Docker Image Version (latest semver)](https://img.shields.io/docker/v/captn3m0/prometheus-act-exporter) ![Docker Image Size (latest semver)](https://img.shields.io/docker/image-size/captn3m0/prometheus-act-exporter) [![npm version](https://badge.fury.io/js/prometheus-act-exporter.svg)](https://badge.fury.io/js/prometheus-act-exporter) [![License: WTFPL](https://img.shields.io/badge/License-WTFPL-blue.svg)](http://www.wtfpl.net/) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)
|
![Docker Image Version (latest semver)](https://img.shields.io/docker/v/captn3m0/prometheus-act-exporter) ![Docker Image Size (latest semver)](https://img.shields.io/docker/image-size/captn3m0/prometheus-act-exporter) [![npm version](https://badge.fury.io/js/prometheus-act-exporter.svg)](https://badge.fury.io/js/prometheus-act-exporter) [![License: WTFPL](https://img.shields.io/badge/License-WTFPL-blue.svg)](http://www.wtfpl.net/) [![CI](https://github.com/captn3m0/prometheus-act-exporter/actions/workflows/ci.yml/badge.svg)](https://github.com/captn3m0/prometheus-act-exporter/actions/workflows/ci.yml)
|
||||||
|
|
||||||
Exposes your current ACT FUP usage as prometheus metrics. Scrapes the data from the ACT Portal website by using [puppeteer](https://developers.google.com/web/tools/puppeteer/). This only supports [ACT Fibernet](https://www.actcorp.in/) in India.
|
Exposes your current ACT FUP usage as prometheus metrics. Scrapes the data from the ACT Portal website by using [puppeteer](https://developers.google.com/web/tools/puppeteer/). This only supports [ACT Fibernet](https://www.actcorp.in/) in India.
|
||||||
|
|
||||||
**Note**: Broken since the portal got redesigned and now requires authentication.
|
|
||||||
|
|
||||||
- Supports flexibytes
|
- Supports flexibytes
|
||||||
- Reports aggregate metrics as well
|
- Reports aggregate metrics as well
|
||||||
- Only tested for ACT Bangalore connections.
|
- Only tested for ACT Bangalore connections.
|
||||||
|
@ -44,17 +42,7 @@ act_fup_aggregate_total_bytes 900000000
|
||||||
|
|
||||||
Install it with `npm i prometheus-act-exporter`.
|
Install it with `npm i prometheus-act-exporter`.
|
||||||
|
|
||||||
```js
|
See `test.js` for sample usage.
|
||||||
const act = require("prometheus-act-exporter");
|
|
||||||
let m = await act.getUsage();
|
|
||||||
// Returns
|
|
||||||
// {
|
|
||||||
// live: { usedBytes: 0, totalBytes: 800000000 },
|
|
||||||
// flexibytes: { usedBytes: 102580000, totalBytes: 100000000 },
|
|
||||||
// aggregate: { usedBytes: 102580000, totalBytes: 900000000 }
|
|
||||||
// }
|
|
||||||
// calculations made assuming ACT is using SI GB (exactly 1 billion bytes)
|
|
||||||
```
|
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
|
@ -70,20 +58,14 @@ You can pass the following environment variables:
|
||||||
|
|
||||||
If running via Docker, here are some simple cookbook configurations:
|
If running via Docker, here are some simple cookbook configurations:
|
||||||
|
|
||||||
`docker run -it -p 3000:3000 -e captn3m0/prometheus-act-exporter`
|
|
||||||
|
|
||||||
Run a simple test server locally in debug mode and test it on `http://localhost:3000/metrics`
|
|
||||||
|
|
||||||
## Node
|
|
||||||
|
|
||||||
```sh
|
|
||||||
export DISABLE_HEADLESS=1
|
|
||||||
# Change to the correct invocation
|
|
||||||
export CHROME_BIN=$(which chromium)
|
|
||||||
npm install
|
|
||||||
node server.js
|
|
||||||
curl localhost:3000/metrics
|
|
||||||
```
|
```
|
||||||
|
wget https://github.com/tkp1n/chromium-ci/raw/41510dc154c4184f7e09461ba76f86f61c460070/seccomp/chromium.json
|
||||||
|
docker run --security-opt seccomp=chromium.json -it -p 3000:3000 -e captn3m0/prometheus-act-exporter
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the above uses a [minimal secure seccomp profile for running Chromium inside Docker](https://github.com/docker/for-linux/issues/496#issuecomment-441149510).
|
||||||
|
|
||||||
|
**Warning**: You should [not be running](https://ndportmann.com/chrome-in-docker/) this using `--privileged` or `--cap-add=SYS_ADMIN`, or `--no-sandbox`.
|
||||||
|
|
||||||
# LICENSE
|
# LICENSE
|
||||||
|
|
||||||
|
|
31
index.js
31
index.js
|
@ -1,5 +1,4 @@
|
||||||
const puppeteer = require("puppeteer-core");
|
const puppeteer = require("puppeteer-core");
|
||||||
const containerized = require("containerized");
|
|
||||||
|
|
||||||
const MY_PACKAGE_SELECTOR_ID =
|
const MY_PACKAGE_SELECTOR_ID =
|
||||||
'table[style="margin-top:-10px;"] tr:first-child+tr';
|
'table[style="margin-top:-10px;"] tr:first-child+tr';
|
||||||
|
@ -28,12 +27,15 @@ async function getUsage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await page.goto(MY_ACCOUNT_URL, {timeout: 5000});
|
await page.goto(MY_ACCOUNT_URL, {timeout: 10000});
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
|
await page.waitForSelector(MY_PACKAGE_SELECTOR_ID)
|
||||||
await page.click(MY_PACKAGE_SELECTOR_ID);
|
await page.click(MY_PACKAGE_SELECTOR_ID);
|
||||||
// Wait for the page to switch
|
// Wait for the page to switch
|
||||||
await page.waitForFunction(
|
await page.waitForFunction(()=>{
|
||||||
'document.querySelector(".dtl-header-text").innerText === "My Package"'
|
return document.querySelector(".dtl-header-text").innerText === "My Package" &&
|
||||||
);
|
document.getElementById('_ACTMyAccount_WAR_ACTMyAccountportlet_:processingPanel').style.display === 'none'
|
||||||
|
});
|
||||||
|
|
||||||
dataUsage = await page.evaluate(sel => {
|
dataUsage = await page.evaluate(sel => {
|
||||||
let elements = document.getElementsByClassName(sel);
|
let elements = document.getElementsByClassName(sel);
|
||||||
|
@ -71,6 +73,7 @@ async function getUsage() {
|
||||||
metrics = dataUsage;
|
metrics = dataUsage;
|
||||||
return metrics
|
return metrics
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
throw new Error("Failed scraping data from ACT");
|
throw new Error("Failed scraping data from ACT");
|
||||||
} finally {
|
} finally {
|
||||||
page.close();
|
page.close();
|
||||||
|
@ -78,10 +81,7 @@ async function getUsage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function chromeLaunchConfig() {
|
function chromeLaunchConfig() {
|
||||||
let defaultArgs = [];
|
let defaultArgs = ['--disable-dev-shm-usage'];
|
||||||
if (containerized()) {
|
|
||||||
defaultArgs = ["--no-sandbox", "--disable-setuid-sandbox"];
|
|
||||||
}
|
|
||||||
var options = {
|
var options = {
|
||||||
// These are set for Docker usage
|
// These are set for Docker usage
|
||||||
// https://github.com/alekzonder/docker-puppeteer#before-usage
|
// https://github.com/alekzonder/docker-puppeteer#before-usage
|
||||||
|
@ -106,12 +106,11 @@ function chromeLaunchConfig() {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Async IIFE FTW
|
|
||||||
(async () => {
|
|
||||||
browser = await puppeteer.launch(chromeLaunchConfig());
|
|
||||||
console.log("Browser Initialized");
|
|
||||||
})();
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getUsage: getUsage
|
getUsage: getUsage,
|
||||||
|
onReady: async (cb)=>{
|
||||||
|
browser = await puppeteer.launch(chromeLaunchConfig());
|
||||||
|
console.log("Browser Initialized");
|
||||||
|
cb(browser)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,7 +4,7 @@
|
||||||
"description": "Exports ACT Fibernet data usage as prometheus metrics",
|
"description": "Exports ACT Fibernet data usage as prometheus metrics",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "node test.js"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"actcorp",
|
"actcorp",
|
||||||
|
@ -14,16 +14,15 @@
|
||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+ssh://git@git.captnemo.in/nemo/prometheus-act-exporter.git"
|
"url": "git+ssh://github.com/captn3m0/prometheus-act-exporter.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://git.captnemo.in/nemo/prometheus-act-exporter/issues"
|
"url": "https://github.com/captn3m0/prometheus-act-exporter/issues"
|
||||||
},
|
},
|
||||||
"author": "Nemo <npm@captnemo.in>",
|
"author": "Nemo <npm@captnemo.in>",
|
||||||
"license": "WTFPL",
|
"license": "WTFPL",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"containerized": "^1.0.2",
|
|
||||||
"prom-client": "^14.0.1",
|
"prom-client": "^14.0.1",
|
||||||
"puppeteer-core": "^21.0.1"
|
"puppeteer-core": "^17.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
54
server.js
54
server.js
|
@ -3,32 +3,15 @@ const port = 3000;
|
||||||
const metrics = require("./index");
|
const metrics = require("./index");
|
||||||
const promFormatter = require("./prom");
|
const promFormatter = require("./prom");
|
||||||
|
|
||||||
CACHE = {};
|
CACHE = null;
|
||||||
|
|
||||||
const requestHandler = async (req, res) => {
|
const requestHandler = async (req, res) => {
|
||||||
let date = new Date(Date.now()).toLocaleString();
|
|
||||||
console.log(`${date}: ${req.url}`);
|
|
||||||
switch (req.url) {
|
switch (req.url) {
|
||||||
case "/metrics":
|
case "/metrics":
|
||||||
res.setHeader("Content-Type", promFormatter.contentType);
|
res.setHeader("Content-Type", promFormatter.contentType);
|
||||||
metrics.getUsage().then(
|
promFormatter.format(CACHE).then((data) => {
|
||||||
(data) => {
|
res.end(data);
|
||||||
console.log(data);
|
});
|
||||||
console.log("Setting cache");
|
|
||||||
CACHE = data;
|
|
||||||
promFormatter.format(data).then((data) => {
|
|
||||||
res.end(data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.log(err);
|
|
||||||
console.log("Got error, using cache");
|
|
||||||
console.log(CACHE);
|
|
||||||
promFormatter.format(CACHE).then((data) => {
|
|
||||||
res.end(data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
res.writeHead(302, {
|
res.writeHead(302, {
|
||||||
|
@ -41,10 +24,29 @@ const requestHandler = async (req, res) => {
|
||||||
|
|
||||||
const server = http.createServer(requestHandler);
|
const server = http.createServer(requestHandler);
|
||||||
|
|
||||||
server.listen(port, (err) => {
|
metrics.onReady((browser) => {
|
||||||
if (err) {
|
let t;
|
||||||
return console.log("could not initialize web server", err);
|
(function refreshCache() {
|
||||||
}
|
metrics.getUsage().then((data) => {
|
||||||
|
let date = new Date(Date.now()).toLocaleString();
|
||||||
|
console.log(`${date}: Updated Cache`);
|
||||||
|
// Start server now if this is the first run
|
||||||
|
if (!CACHE) {
|
||||||
|
server.listen(port, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return console.log("could not initialize web server", err);
|
||||||
|
}
|
||||||
|
console.log(`server is listening on ${port}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
CACHE = data;
|
||||||
|
}).finally(()=>{
|
||||||
|
t = setTimeout(refreshCache, 15 * 60 * 1000);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
console.log(`server is listening on ${port}`);
|
process.on("exit", function () {
|
||||||
|
browser.close();
|
||||||
|
clearTimeout(t)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
const act = require("./index");
|
||||||
|
const assert = require('assert')
|
||||||
|
act.onReady(async (browser)=>{
|
||||||
|
metrics = await act.getUsage();
|
||||||
|
console.log(metrics);
|
||||||
|
for (i of ['live', 'aggregate', 'flexibytes']) {
|
||||||
|
console.log(i)
|
||||||
|
assert.ok(typeof metrics[i]['usedBytes'] === 'number')
|
||||||
|
assert.ok(typeof metrics[i]['totalBytes'] === 'number')
|
||||||
|
}
|
||||||
|
browser.close();
|
||||||
|
});
|
Loading…
Reference in New Issue