Skip to content

Commit

Permalink
Add semicolons and small refactors in logs and loops
Browse files Browse the repository at this point in the history
  • Loading branch information
Juan Carlos Martin committed Nov 22, 2023
1 parent 9680a7c commit 4bf714a
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 89 deletions.
9 changes: 6 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ FROM node:18-alpine

RUN mkdir -p /usr/src/app && \
chown -R node:node /usr/src/app
WORKDIR /usr/src/app

USER node

WORKDIR /usr/src/app

COPY --chown=node:node package.json /usr/src/app
COPY --chown=node:node package-lock.json /usr/src/app
RUN npm install

COPY . /usr/src/app

RUN npm install

CMD ["node", "src/index.js"]
147 changes: 74 additions & 73 deletions src/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,43 @@ async function pruneGroups(pushgatewayUrl, pruneThresholdSeconds) {
try {
metrics = await getMetrics(pushgatewayUrl);
} catch (e) {
throw new Error(`GET /metrics from ${pushgatewayUrl} 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, 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, pushgatewayUrl)
} catch (e) {
logger.error(`Pruning group ${filteredGroup} failed.`)
}
const groupings = parseGroupings(metrics);
const filteredGroupings = filterOldGroupings(groupings, pruneThresholdSeconds);
logger.info(`Found ${groupings.length} grouping(s), of which ${filteredGroupings.length} will be pruned`);

await Promise.all(filteredGroupings.map(async (filteredGroup) => {
try {
await deleteGrouping(filteredGroup, pushgatewayUrl);
} catch (e) {
logger.error(`Pruning group ${filteredGroup} failed.`);
}
)
}
));

logger.info('Pruning process finished');
}
logger.info('Pruning process finished');
}

function resolve(envVar, defaultValue) {
logger.debug(`resolve(${envVar}, ${defaultValue})`)
const envValue = process.env[envVar]
logger.debug(`Resolving environment variable '${envVar}' (default-value='${defaultValue}')`);

const envValue = process.env[envVar];
if (!!envValue) {
logger.debug(`found env value ${envValue}`)
const tryInt = parseInt(envValue)
if (!isNaN(tryInt))
return tryInt
return envValue
logger.debug(`Found environment variable '${envVar}' with value '${envValue}'`);
const tryInt = parseInt(envValue);
return isNaN(tryInt) ? envValue : tryInt;
}
logger.debug(`returning default value ${defaultValue}`)
return defaultValue

logger.debug(`No environment value found for '${envVar}'. Returning default value '${defaultValue}'`);
return defaultValue;
}

async function getMetrics(pushgatewayUrl) {
logger.debug('getMetrics()')
logger.debug('Trying to get metrics from pushgateway...');

const getMetricsResponse = await axios.get(pushgatewayUrl + 'metrics', {
timeout: 2000
});
Expand All @@ -67,101 +66,103 @@ async function getMetrics(pushgatewayUrl) {

function parseGroupings(metrics) {
logger.debug('parseGroupings()');
const lines = metrics.split('\n')
const pushGroups = []
const lines = metrics.split('\n');
const pushGroups = [];
for (let i = 0; i < lines.length; ++i) {
const line = lines[i]
const line = lines[i];
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)
const labels = parseLabels(line.substring(line.indexOf('{') + 1, line.indexOf('}')));
const timestamp = new Date(parseFloat(line.substring(line.indexOf('}') + 1).trim()) * 1000);
pushGroups.push({
timestamp: timestamp,
labels: labels
})
}
}
for (let i = 0; i < pushGroups.length; ++i)
logger.debug('Grouping', pushGroups[i])
return pushGroups
for (let i = 0; i < pushGroups.length; ++i) {
logger.debug('Grouping', pushGroups[i]);
}
return pushGroups;
}

function parseLabels(labels) {
logger.debug(`parseLabels(${labels}`)
logger.debug(`parseLabels(${labels}`);
if (!labels.trim()) {
logger.debug('no labels found')
return {}
logger.debug('no labels found');
return {};
}
const labelList = labels.split(',')
const labelMap = {}
const labelList = labels.split(',');
const labelMap = {};
for (let i = 0; i < labelList.length; ++i) {
const keyValue = labelList[i].split('=')
let value = keyValue[1]
if (value.startsWith('"'))
value = value.substring(1, value.length - 1)
labelMap[keyValue[0]] = value
const keyValue = labelList[i].split('=');
let value = keyValue[1];
if (value.startsWith('"')) {
value = value.substring(1, value.length - 1);
}
labelMap[keyValue[0]] = value;
}
return labelMap
return labelMap;
}

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

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

const job = grouping.labels.job
const job = grouping.labels.job;
// This will most probably be "instance"
const labelName = findLabelName(grouping.labels)
if (!labelName)
throw new Error(`Grouping from job ${job} does not have suitable labels (e.g. instance)`)
const labelValue = grouping.labels[labelName]
const labelName = findFirstNonJobLabel(grouping.labels);
if (!labelName) {
throw new Error(`Grouping from job ${job} does not have suitable labels (e.g. instance)`);
}
const labelValue = grouping.labels[labelName];
if (!labelValue) {
logger.info(`Did not delete grouping from job ${job} because value of label ${labelName} is empty.`)
return;
}

const url = pushgatewayUrl + encodeURIComponent(`metrics/job/${job}/${labelName}/${labelValue}`)
logger.debug(`Delete URL: ${url}`)
const url = pushgatewayUrl + encodeURIComponent(`metrics/job/${job}/${labelName}/${labelValue}`);
logger.debug(`Delete URL: ${url}`);
const deleteResponse = await axios.delete(url, {
timeout: 2000
});

if (!deleteResponse || deleteResponse && (deleteResponse.status >= 300)) {
logger.debug(`ERROR: DELETE ${url} failed`)
let msg = 'unknown failure'
logger.debug(`ERROR: DELETE ${url} failed`);
let msg = 'unknown failure';
if (deleteResponse) {
msg = `unexpected status code ${deleteResponse.status}`
msg = `unexpected status code ${deleteResponse.status}`;
}
logger.debug(msg)
throw new Error(`DELETE ${url} failed: ${msg}`)
logger.debug(msg);
throw new Error(`DELETE ${url} failed: ${msg}`);
}

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

function findLabelName(labels) {
for (let propName in labels) {
if (propName === 'job')
continue
return propName
}
return null
function findFirstNonJobLabel(labels) {
const nonJobLabel = Object.keys(labels)
.find(propName => propName !== 'job');
return nonJobLabel || null;
}

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

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

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

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 PRUNE_THRESHOLD_SECONDS = resolve('PRUNE_THRESHOLD', 600);
logger.info(`Prune threshold: ${PRUNE_THRESHOLD_SECONDS} seconds.`);

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

module.exports = {
pruneGroups,
Expand Down
4 changes: 2 additions & 2 deletions src/logger.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const winston = require('winston')
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
32 changes: 31 additions & 1 deletion test/functions.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const axios = require('axios');
const { resolve, pruneGroups, parseLabels } = require('./../src/functions')
const { resolve, pruneGroups, parseLabels, findFirstNonJobLabel } = require('./../src/functions')

jest.mock('axios');

Expand Down Expand Up @@ -155,5 +155,35 @@ describe('Functions test', () => {
expect(responseValue).toEqual(expectedValue);
});
});

describe('Find first non "job" label tests', () => {
test('should return the first non-"job" label name', () => {
const labels1 = { job: 'Developer', department: 'IT' };
expect(findFirstNonJobLabel(labels1)).toBe('department');

const labels2 = { job: 'Manager', location: 'Office' };
expect(findFirstNonJobLabel(labels2)).toBe('location');
});

test('should return the first non-"job" label name among multiple labels', () => {
const labels = { job: 'Developer', department: 'IT', location: 'Remote', team: 'Frontend' };
expect(findFirstNonJobLabel(labels)).toBe('department');
});

test('should return null if there are no non-"job" labels', () => {
const labels = { job: 'Designer' };
expect(findFirstNonJobLabel(labels)).toBeNull();
});

test('should return null for an empty object', () => {
const labels = {};
expect(findFirstNonJobLabel(labels)).toBeNull();
});

test('should handle objects with "job" property at the end', () => {
const labels = { department: 'HR', job: 'Recruiter' };
expect(findFirstNonJobLabel(labels)).toBe('department');
});
});
})

0 comments on commit 4bf714a

Please sign in to comment.