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, };