middleware/controllers/nodeExporter.js (171 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
const axios = require('axios');
const { buildUrl } = require('../utils/urlHelper');
const { getEnv } = require('../utils/envParser');
const logger = require('../utils/logger');
/**
* Fetches CPU usage data from Node Exporter and sends it as a response.
*
* @async
* @function getCpuUsage
* @param {Object} req - The HTTP request object.
* @param {Object} req.body - The request body containing query parameters.
* @param {string} req.body.query - The PromQL query to fetch CPU usage data.
* @param {string} [req.body.from="now-5m"] - The start time for the query range (default: 5 minutes ago).
* @param {string} [req.body.until="now"] - The end time for the query range (default: now).
* @param {number} [req.body.step=28] - The step interval in seconds for the query (default: 28 seconds).
* @param {Object} res - The HTTP response object.
* @returns {Promise<void>} Sends the filtered CPU usage data or an error response.
*/
async function getCpuUsage(req, res) {
const { query, from = "now-5m", until = "now", step = 28 } = req.body;
logger.debug(req.body);
const baseUrl = buildUrl(getEnv("NODE_EXPORTER_BASE_URL"), {
query,
start: from,
end: until,
step,
});
const config = {
method: 'get',
maxBodyLength: Infinity,
url: baseUrl,
};
try {
const response = await axios.request(config);
const data = response?.data?.data?.result.filter((val) => val?.metric?.groupname === 'kv_service');
const responseData = data.length > 0 ? data[0]?.values : [];
return res.send({ data: responseData });
} catch (error) {
logger.error(error);
return res.status(400).send({
error: error.message || "An error occurred",
});
}
}
/**
* Fetches Disk IOPS (Input/Output Operations Per Second) data from Node Exporter.
*
* @async
* @function getDiskIOPS
* @param {Object} req - The HTTP request object containing query parameters in the body.
* @param {Object} res - The HTTP response object.
* @returns {Promise<void>} Sends the formatted Disk IOPS data or an error response.
*/
async function getDiskIOPS(req, res) {
const { from = "now-12h", until = "now", step = 28 } = req.body;
const baseUrl = buildUrl(getEnv("NODE_EXPORTER_BASE_URL"), {
query: "rate(node_disk_writes_completed_total{device='vda',job='node_exporter'}[5m])",
start: from,
end: until,
step,
});
const config = {
method: 'get',
maxBodyLength: Infinity,
url: baseUrl,
};
try {
const response = await axios.request(config);
const data = response?.data?.data?.result.filter(
(val) => val?.metric?.device === "vda" && val?.metric?.job === "node_exporter"
);
const formattedResponseData = data.length > 0
? data[0]?.values.map(([timestamp, value]) => ({
time: new Date(Number(timestamp) * 1000).toISOString(),
value: parseFloat(value).toFixed(2),
}))
: [];
return res.send({ data: formattedResponseData });
} catch (error) {
logger.error(error);
return res.status(400).send({
error: error.message || "An error occurred while fetching the DiskIOPS data",
});
}
}
/**
* Fetches Disk Wait Time data (read and write) from Node Exporter.
*
* @async
* @function getDiskWaitTime
* @param {Object} req - The HTTP request object containing query parameters in the body.
* @param {Object} res - The HTTP response object.
* @returns {Promise<void>} Sends the formatted Disk Wait Time data or an error response.
*/
async function getDiskWaitTime(req, res) {
const { from = "now-5m", until = "now", step = 28 } = req.body;
try {
const queries = {
readWaitTime: "rate(node_disk_read_time_seconds_total{device='vda'}[5m]) / rate(node_disk_reads_completed_total{device='vda'}[5m])",
writeWaitTime: "rate(node_disk_write_time_seconds_total{device='vda'}[5m]) / rate(node_disk_writes_completed_total{device='vda'}[5m])",
};
const responses = await Promise.all(
Object.entries(queries).map(async ([key, query]) => {
const baseUrl = buildUrl(getEnv("NODE_EXPORTER_BASE_URL"), {
query,
start: from,
end: until,
step,
});
const response = await axios.get(baseUrl);
return {
key,
values: response?.data?.data?.result?.[0]?.values || [],
};
})
);
const formattedData = responses.reduce((acc, { key, values }) => {
acc[key] = values.map(([timestamp, value]) => ({
time: new Date(timestamp * 1000).toISOString(),
value: parseFloat(value),
}));
return acc;
}, {});
return res.send({ data: formattedData });
} catch (error) {
logger.error(error);
return res.status(400).send({
error: error.message || "An error occurred while fetching Disk Wait Time data",
});
}
}
/**
* Fetches the time spent doing I/O operations from Node Exporter.
*
* @async
* @function getTimeSpentDoingIO
* @param {Object} req - The HTTP request object containing query parameters in the body.
* @param {Object} res - The HTTP response object.
* @returns {Promise<void>} Sends the formatted I/O time data or an error response.
*/
async function getTimeSpentDoingIO(req, res) {
const { from = "now-5m", until = "now", step = 28 } = req.body;
const query = "rate(node_disk_io_time_seconds_total{device='vda', job='node_exporter'}[5m])";
try {
const baseUrl = buildUrl(getEnv("NODE_EXPORTER_BASE_URL"), {
query,
start: from,
end: until,
step,
});
const config = {
method: 'get',
url: baseUrl,
};
const response = await axios.request(config);
const result = response?.data?.data?.result?.[0]?.values || [];
const formattedData = result.map(([timestamp, value]) => ({
time: new Date(timestamp * 1000).toISOString(),
value: parseFloat(value),
}));
return res.send({ data: formattedData });
} catch (error) {
logger.error("Error fetching Disk IO Time data:", error);
return res.status(400).send({
error: error.message || "Failed to fetch Disk IO Time data",
});
}
}
/**
* Fetches Disk Read and Write Merged data from Node Exporter.
*
* @async
* @function getDiskRWData
* @param {Object} req - The HTTP request object containing query parameters in the body.
* @param {Object} res - The HTTP response object.
* @returns {Promise<void>} Sends the formatted Disk Read and Write Merged data or an error response.
*/
async function getDiskRWData(req, res) {
const { from = "now-5m", until = "now", step = 28 } = req.body;
try {
const queries = {
readMerged: "rate(node_disk_reads_merged_total{device='vda'}[5m])",
writeMerged: "rate(node_disk_writes_merged_total{device='vda'}[5m])",
};
const responses = await Promise.all(
Object.entries(queries).map(async ([key, query]) => {
const baseUrl = buildUrl(getEnv("NODE_EXPORTER_BASE_URL"), {
query,
start: from,
end: until,
step,
});
const response = await axios.get(baseUrl);
return {
key,
values: response?.data?.data?.result?.[0]?.values || [],
};
})
);
const formattedData = responses.reduce((acc, { key, values }) => {
acc[key] = values.map(([timestamp, value]) => ({
time: new Date(timestamp * 1000).toISOString(),
value: parseFloat(value),
}));
return acc;
}, {});
return res.send({ data: formattedData });
} catch (error) {
logger.error(error);
return res.status(400).send({
error: error.message || "An error occurred while fetching Disk Merged Data",
});
}
}
module.exports = {
getCpuUsage,
getDiskIOPS,
getDiskWaitTime,
getTimeSpentDoingIO,
getDiskRWData,
};