Skip to content

Commit

Permalink
Created src folder and moved code to this folder
Browse files Browse the repository at this point in the history
  • Loading branch information
Juan Carlos Martin committed Nov 13, 2023
1 parent 749295b commit c50bd70
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 38 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:18-alpine
FROM --platform=linux/amd64 node:18-alpine

RUN mkdir -p /usr/src/app && \
chown -R node:node /usr/src/app
Expand All @@ -10,4 +10,4 @@ RUN npm install

COPY . /usr/src/app

CMD ["node", "index.js"]
CMD ["node", "src/index.js"]
2 changes: 1 addition & 1 deletion build/build-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fullImageName="${imageName}:${releaseTag}"
pushd release/${releaseTag}
tar xvzf release.tgz
pushd pushgateway-pruner-${releaseTag}
docker build -t ${fullImageName} .
docker build --platform=linux/amd64 -t ${fullImageName} .
popd

echo "======================================================================="
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
"node": ">=18.0.0"
},
"scripts": {
"test": "jest"
"test": "jest --runInBand"
},
"dependencies": {
"axios": "1.5.1",
"axios": "1.6.1",
"prom-client": "15.0.0",
"winston": "3.11.0"
},
Expand Down
42 changes: 15 additions & 27 deletions index.js → src/functions.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,28 @@
'use strict'

const axios = require('axios')
const logger = require('./logger')

let PUSHGATEWAY_URL = resolve('PUSHGATEWAY_URL', 'http://localhost:9091')
if (!PUSHGATEWAY_URL.endsWith('/'))
PUSHGATEWAY_URL += '/'
logger.info(`Pushgateway URL: ${PUSHGATEWAY_URL}`)

const INTERVAL_SECONDS = resolve('PRUNE_INTERVAL', 60)
const PRUNE_THRESHOLD_SECONDS = resolve('PRUNE_THRESHOLD', 600)
logger.info(`Prune interval: ${INTERVAL_SECONDS} seconds.`)
logger.info(`Prune threshold: ${PRUNE_THRESHOLD_SECONDS} seconds.`)
const METRIC_NAME = 'push_time_seconds';

async function pruneGroups() {
async function pruneGroups(pushgatewayUrl, pruneThresholdSeconds) {
logger.info('Starting prune process...');

// Get metrics request from Prometheus push gateway
let metrics = null;
try {
metrics = await getMetrics(PUSHGATEWAY_URL);
metrics = await getMetrics(pushgatewayUrl);
} catch (e) {
throw new Error(`GET /metrics from ${PUSHGATEWAY_URL} failed. Cause: ${e}`)
throw new Error(`GET /metrics from ${pushgatewayUrl} failed. Cause: ${e}`)
}

// Get 'push_time_seconds' groups and filter the ones that are above pruneThresholdSeconds
const groupings = parseGroupings(metrics)
const filteredGroupings = filterOldGroupings(groupings)
const filteredGroupings = filterOldGroupings(groupings, pruneThresholdSeconds)
logger.info(`Found ${groupings.length} grouping(s), of which ${filteredGroupings.length} will be pruned`)

if (filteredGroupings.length > 0) {
filteredGroupings.map((filteredGroup) => {
try {
deleteGrouping(filteredGroup)
deleteGrouping(filteredGroup, pushgatewayUrl)
} catch (e) {
logger.error(`Pruning group ${filteredGroup} failed.`)
}
Expand All @@ -57,9 +47,9 @@ function resolve(envVar, defaultValue) {
return defaultValue
}

async function getMetrics() {
async function getMetrics(pushgatewayUrl) {
logger.debug('getMetrics()')
const getMetricsResponse = await axios.get(PUSHGATEWAY_URL + 'metrics', {
const getMetricsResponse = await axios.get(pushgatewayUrl + 'metrics', {
timeout: 2000
});

Expand All @@ -81,7 +71,7 @@ function parseGroupings(metrics) {
const pushGroups = []
for (let i = 0; i < lines.length; ++i) {
const line = lines[i]
if (line.startsWith("push_time_seconds")) {
if (line.startsWith(METRIC_NAME)) {
const labels = parseLabels(line.substring(line.indexOf('{') + 1, line.indexOf('}')))
const timestamp = new Date(parseFloat(line.substring(line.indexOf('}') + 1).trim()) * 1000)
pushGroups.push({
Expand Down Expand Up @@ -113,12 +103,12 @@ function parseLabels(labels) {
return labelMap
}

function filterOldGroupings(groupings) {
function filterOldGroupings(groupings, pruneThresholdSeconds) {
logger.debug('filterOldGroupings()');
const filteredGroupings = []
const now = new Date()
for (let i = 0; i < groupings.length; ++i) {
if ((now - groupings[i].timestamp) > PRUNE_THRESHOLD_SECONDS * 1000) {
if ((now - groupings[i].timestamp) > pruneThresholdSeconds * 1000) {
filteredGroupings.push(groupings[i])
}
}
Expand All @@ -127,7 +117,7 @@ function filterOldGroupings(groupings) {
return filteredGroupings
}

async function deleteGrouping(grouping) {
async function deleteGrouping(grouping, pushgatewayUrl) {
logger.debug('deleteGrouping()', grouping)

const job = grouping.labels.job
Expand All @@ -141,7 +131,7 @@ async function deleteGrouping(grouping) {
return;
}

const url = PUSHGATEWAY_URL + encodeURIComponent(`metrics/job/${job}/${labelName}/${labelValue}`)
const url = pushgatewayUrl + encodeURIComponent(`metrics/job/${job}/${labelName}/${labelValue}`)
logger.debug(`Delete URL: ${url}`)
const deleteResponse = await axios.delete(url, {
timeout: 2000
Expand All @@ -159,7 +149,6 @@ async function deleteGrouping(grouping) {

logger.debug(`DELETE ${url} succeeded, status code ${deleteResponse.status}`)
logger.info('Deleted grouping', grouping.labels)
return;
}

function findLabelName(labels) {
Expand All @@ -171,9 +160,8 @@ function findLabelName(labels) {
return null
}

const interval = setInterval(pruneGroups, INTERVAL_SECONDS * 1000)

module.exports = {
resolve,
pruneGroups,
interval
parseLabels
}
22 changes: 22 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const { resolve, pruneGroups } = require('./functions')
const logger = require('./logger')

let PUSHGATEWAY_URL = resolve('PUSHGATEWAY_URL', 'http://localhost:9091')
if (!PUSHGATEWAY_URL.endsWith('/'))
PUSHGATEWAY_URL += '/'
logger.info(`Pushgateway URL: ${PUSHGATEWAY_URL}`)

const INTERVAL_SECONDS = resolve('PRUNE_INTERVAL', 60)
const PRUNE_THRESHOLD_SECONDS = resolve('PRUNE_THRESHOLD', 600)
logger.info(`Prune interval: ${INTERVAL_SECONDS} seconds.`)
logger.info(`Prune threshold: ${PRUNE_THRESHOLD_SECONDS} seconds.`)

const interval = setInterval(
() => pruneGroups(PUSHGATEWAY_URL, PRUNE_THRESHOLD_SECONDS),
INTERVAL_SECONDS * 1000
)

module.exports = {
pruneGroups,
interval
}
2 changes: 1 addition & 1 deletion logger.js → src/logger.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const winston = require('winston')

let logLevel = process.env.DEBUG == 'true' ? 'debug' : 'info'
let logLevel = process.env.DEBUG === 'true' ? 'debug' : 'info'

const logger = winston.createLogger({
transports: [
Expand Down
82 changes: 82 additions & 0 deletions test/functions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const { resolve, parseLabels } = require('./../src/functions')

function mockProcessEnv(envVar, envValue) {
process.env[envVar] = envValue;
}

describe('Functions test', () => {
describe('Resolve function test', () => {
const envVar = 'TEST_ENV_VAR';
const defaultValue = 'default_value';
const defaultProcessEnvValues = { ...process.env };

beforeEach(() => {
jest.resetModules();
process.env = { ...defaultProcessEnvValues };
});

afterAll(() => {
process.env = defaultProcessEnvValues; // Restore old environment
});

test('Return process env value string when specified', () => {
const expectedValue = 'expected_value';
mockProcessEnv(envVar, expectedValue);

const responseValue = resolve(envVar, defaultValue);

expect(responseValue).toBe(expectedValue);
});

test('Return process env value integer when specified', () => {
const expectedValue = 123;
mockProcessEnv(envVar, expectedValue);

const responseValue = resolve(envVar, defaultValue);

expect(responseValue).toBe(expectedValue);
});

test('Return default process env value when no specified', () => {
const responseValue = resolve(envVar, defaultValue);

expect(responseValue).toBe(defaultValue);
});
});

describe('Parse labels function test', () => {
test('Return the specific labels', () => {
const stringLabels = 'instance="instance_1",job="application_1"';
const expectedValue = {
instance: 'instance_1',
job: 'application_1'
};

const responseValue = parseLabels(stringLabels);

expect(responseValue).toEqual(expectedValue);
});

test('Return empty string in labels that does not have a value', () => {
const stringLabels = 'instance="",job="application_1"';
const expectedValue = {
instance: '',
job: 'application_1'
};

const responseValue = parseLabels(stringLabels);

expect(responseValue).toEqual(expectedValue);
});

test('Return empty if no labels', () => {
const stringLabels = '';
const expectedValue = {};

const responseValue = parseLabels(stringLabels);

expect(responseValue).toEqual(expectedValue);
});
});
})

6 changes: 5 additions & 1 deletion test/index.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const axios = require('axios')
const { pruneGroups, interval } = require('./../index')
const { pruneGroups, interval } = require('./../src/index')

jest.mock('axios');

Expand Down Expand Up @@ -39,6 +39,10 @@ describe('PushGateway Pruner', () => {
mockDeleteMetricsResponse();
});

beforeEach(() => {
expect(axios.delete).toHaveBeenCalledTimes(0);
})

afterEach(() => {
jest.useRealTimers();

Expand Down

0 comments on commit c50bd70

Please sign in to comment.