import Percentage from './Percentage.js'; import PropTypes from 'prop-types'; import _ from 'lodash'; const getPodCategorization = pod => { if (pod.added && pod.status === "Running") { return "good"; } else if (pod.status === "Pending" || pod.status === "Running") { return "default"; } else if (pod.status === "Failed") { return "poor"; } return ""; // Terminating | Succeeded | Unknown }; export const getSuccessRateClassification = (rate, successRateLabels = srArcClassLabels) => { if (_.isNull(rate)) { return successRateLabels.default; } if (rate < 0.9) { return successRateLabels.poor; } else if (rate < 0.95) { return successRateLabels.warning; } else { return successRateLabels.good; } }; const srArcClassLabels = { good: "good", warning: "warning", poor: "poor", default: "default" }; const getTotalRequests = row => { let success = parseInt(_.get(row, ["stats", "successCount"], 0), 10); let failure = parseInt(_.get(row, ["stats", "failureCount"], 0), 10); return success + failure; }; const getRequestRate = row => { if (_.isEmpty(row.stats)) { return null; } let seconds = 0; if (row.timeWindow === "10s") { seconds = 10; } if (row.timeWindow === "1m") { seconds = 60; } if (row.timeWindow === "10m") { seconds = 600; } if (row.timeWindow === "1h") { seconds = 3600; } if (seconds === 0) { return null; } else { return getTotalRequests(row) / seconds; } }; const getSuccessRate = row => { if (_.isEmpty(row.stats)) { return null; } let success = parseInt(row.stats.successCount, 10); let failure = parseInt(row.stats.failureCount, 10); if (success + failure === 0) { return null; } else { return success / (success + failure); } }; const getTlsRequestPercentage = row => { if (_.isEmpty(row.stats)) { return null; } let tlsRequests = parseInt(_.get(row, ["stats", "tlsRequestCount"], 0), 10); return new Percentage(tlsRequests, getTotalRequests(row)); }; const getLatency = row => { if (_.isEmpty(row.stats)) { return {}; } else { return { P50: parseInt(row.stats.latencyMsP50, 10), P95: parseInt(row.stats.latencyMsP95, 10), P99: parseInt(row.stats.latencyMsP99, 10), }; } }; export const getPodsByDeployment = pods => { return _(pods) .reject(p => _.isEmpty(p.deployment) || p.controlPlane) .groupBy('deployment') .map((componentPods, name) => { let podsWithStatus = _.chain(componentPods) .map(p => { return _.merge({}, p, { value: getPodCategorization(p) }); }) .reject(p => _.isEmpty(p.value)) .value(); return { name: name, added: _.every(componentPods, 'added'), pods: _.sortBy(podsWithStatus, 'name') }; }) .reject(p => _.isEmpty(p.pods)) .sortBy('name') .value(); }; export const getComponentPods = componentPods => { return _.chain(componentPods) .map( p => { return { name: p.name, value: getPodCategorization(p) }; }) .reject(p => _.isEmpty(p.value)) .sortBy("name") .value(); }; const processStatTable = table => { return _(table.podGroup.rows).map(row => { let runningPodCount = parseInt(row.runningPodCount, 10); let meshedPodCount = parseInt(row.meshedPodCount, 10); return { key: `${row.resource.namespace}-${row.resource.type}-${row.resource.name}`, name: row.resource.name, namespace: row.resource.namespace, type: row.resource.type, totalRequests: getTotalRequests(row), requestRate: getRequestRate(row), successRate: getSuccessRate(row), latency: getLatency(row), tlsRequestPercent: getTlsRequestPercentage(row), added: runningPodCount > 0 && meshedPodCount > 0, pods: { totalPods: row.runningPodCount, meshedPods: row.meshedPodCount, meshedPercentage: new Percentage(meshedPodCount, runningPodCount) }, errors: row.errorsByPod }; }) .compact() .sortBy("name") .value(); }; export const processTopRoutesResults = rows => { return _.map(rows, row => ({ route: row.route || "UNKNOWN", authority: row.authority, totalRequests: getTotalRequests(row), requestRate: getRequestRate(row), successRate: getSuccessRate(row), latency: getLatency(row), tlsRequestPercent: getTlsRequestPercentage(row), } )); }; export const processSingleResourceRollup = rawMetrics => { let result = processMultiResourceRollup(rawMetrics); if (_.size(result) > 1) { console.error("Multi metric returned; expected single metric."); } if (_.isEmpty(result)) { return []; } return _.values(result)[0]; }; export const processMultiResourceRollup = rawMetrics => { if (_.isEmpty(rawMetrics.ok) || _.isEmpty(rawMetrics.ok.statTables)) { return {}; } let metricsByResource = {}; _.each(rawMetrics.ok.statTables, table => { if (_.isEmpty(table.podGroup.rows)) { return; } // assumes all rows in a podGroup have the same resource type let resource = _.get(table, ["podGroup", "rows", 0, "resource", "type"]); metricsByResource[resource] = processStatTable(table); }); return metricsByResource; }; export const excludeResourcesFromRollup = (rollupMetrics, resourcesToExclude) => { _.each(resourcesToExclude, resource => { delete rollupMetrics[resource]; }); return rollupMetrics; }; export const emptyMetric = { key: "", name: "", namespace: "", type: "", totalRequests: null, requestRate: null, successRate: null, latency: null, tlsRequestPercent: null, added: false, pods: { totalPods: null, meshedPods: null, meshedPercentage: null } }; export const metricsPropType = PropTypes.shape({ ok: PropTypes.shape({ statTables: PropTypes.arrayOf(PropTypes.shape({ podGroup: PropTypes.shape({ rows: PropTypes.arrayOf(PropTypes.shape({ failedPodCount: PropTypes.string, meshedPodCount: PropTypes.string, resource: PropTypes.shape({ name: PropTypes.string, namespace: PropTypes.string, type: PropTypes.string, }).isRequired, runningPodCount: PropTypes.string, stats: PropTypes.shape({ failureCount: PropTypes.string, latencyMsP50: PropTypes.string, latencyMsP95: PropTypes.string, latencyMsP99: PropTypes.string, tlsRequestCount: PropTypes.string, successCount: PropTypes.string, }), timeWindow: PropTypes.string, }).isRequired), }), }).isRequired).isRequired, }), }); export const processedMetricsPropType = PropTypes.shape({ name: PropTypes.string.isRequired, namespace: PropTypes.string.isRequired, totalRequests: PropTypes.number, requestRate: PropTypes.number, successRate: PropTypes.number, });