Skip to content

Commit 1152672

Browse files
committed
resource calculator
1 parent 6d2a182 commit 1152672

File tree

7 files changed

+228
-2
lines changed

7 files changed

+228
-2
lines changed

web/locales/en/plugin__netobserv-plugin.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,18 @@
162162
"S": "S",
163163
"XS": "XS",
164164
"None": "None",
165+
"Cluster metrics": "Cluster metrics",
166+
"Bandwidth": "Bandwidth",
167+
"Nodes": "Nodes",
168+
"Namespaces": "Namespaces",
169+
"Pods": "Pods",
170+
"Estimation": "Estimation",
171+
"Sampling": "Sampling",
172+
"vCPU": "vCPU",
173+
"Memory": "Memory",
174+
"Storage": "Storage",
175+
"Latency overhead": "Latency overhead",
176+
"(current)": "(current)",
165177
"There is some issue in this form view. Please select \"YAML view\" for full control.": "There is some issue in this form view. Please select \"YAML view\" for full control.",
166178
"Note: Some fields may not be represented in this form view. Please select \"YAML view\" for full control.": "Note: Some fields may not be represented in this form view. Please select \"YAML view\" for full control.",
167179
"Remove {{singularLabel}}": "Remove {{singularLabel}}",
@@ -189,8 +201,8 @@
189201
"Filters": "Filters",
190202
"Options": "Options",
191203
"Pipeline": "Pipeline",
192-
"Storage": "Storage",
193204
"Integration": "Integration",
205+
"Consumption": "Consumption",
194206
"Review": "Review",
195207
"Network Observability FlowMetric setup": "Network Observability FlowMetric setup",
196208
"You can create custom metrics out of the flowlogs data using the FlowMetric API. In every flowlogs data that is collected, there are a number of fields labeled per log, such as source name and destination name. These fields can be leveraged as Prometheus labels to enable the customization of cluster information on your dashboard.\nThis setup will guide you on the common aspects of the FlowMetric configuration.": "You can create custom metrics out of the flowlogs data using the FlowMetric API. In every flowlogs data that is collected, there are a number of fields labeled per log, such as source name and destination name. These fields can be leveraged as Prometheus labels to enable the customization of cluster information on your dashboard.\nThis setup will guide you on the common aspects of the FlowMetric configuration.",
@@ -345,7 +357,6 @@
345357
"from": "from",
346358
"in": "in",
347359
"Configuration": "Configuration",
348-
"Sampling": "Sampling",
349360
"Max chunk age": "Max chunk age",
350361
"Version": "Version",
351362
"Number": "Number",

web/moduleMapper/dummy.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
12
/* eslint-disable @typescript-eslint/no-explicit-any */
23
import {
34
K8sGroupVersionKind,
45
K8sModel,
56
K8sResourceKind,
67
K8sResourceKindReference,
78
NamespaceBarProps,
9+
PrometheusPollProps,
10+
PrometheusResponse,
811
ResourceIconProps,
912
ResourceLinkProps,
1013
ResourceYAMLEditorProps
@@ -261,3 +264,58 @@ export const ResourceYAMLEditor: React.FC<ResourceYAMLEditorProps> = ({
261264
/>
262265
</>);
263266
};
267+
268+
export enum K8sResourceConditionStatus {
269+
True = "True",
270+
False = "False",
271+
Unknown = "Unknown"
272+
}
273+
274+
export enum PrometheusEndpoint {
275+
LABEL = "api/v1/label",
276+
QUERY = "api/v1/query",
277+
QUERY_RANGE = "api/v1/query_range",
278+
RULES = "api/v1/rules",
279+
TARGETS = "api/v1/targets"
280+
}
281+
282+
export function usePrometheusPoll(props: PrometheusPollProps) {
283+
console.log("usePrometheusPoll", props);
284+
285+
const [response, setResponse] = React.useState<PrometheusResponse | null>(null);
286+
287+
React.useEffect(() => {
288+
// simulate a loading
289+
if (response == null) {
290+
setTimeout(() => {
291+
setResponse({
292+
status: "success",
293+
data: {
294+
resultType: "vector",
295+
result: [
296+
{
297+
metric: {
298+
node: "node-1",
299+
namespace: "ns-1",
300+
pod: "pod-1",
301+
},
302+
value: [
303+
1745832954.698,
304+
"2670.494073495783"
305+
]
306+
},
307+
],
308+
}
309+
});
310+
}, 1000);
311+
}
312+
}, [response]);
313+
314+
return React.useMemo(() => {
315+
if (response == null) {
316+
return [null, false, null];
317+
} else {
318+
return [response, true, null];
319+
}
320+
}, [response]);
321+
}

web/src/app.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626
import { BarsIcon } from '@patternfly/react-icons';
2727
import React from 'react';
2828
import { BrowserRouter, Link } from 'react-router-dom';
29+
import { GetFlowCollectorJS } from './components/forms/config/templates';
30+
import Consumption from './components/forms/consumption';
2931
import FlowCollectorForm from './components/forms/flowCollector';
3032
import FlowCollectorStatus from './components/forms/flowCollector-status';
3133
import FlowCollectorWizard from './components/forms/flowCollector-wizard';
@@ -69,6 +71,10 @@ export const pages = [
6971
id: 'flowCollector',
7072
name: 'FlowCollector form'
7173
},
74+
{
75+
id: 'flowCollector-consumption',
76+
name: 'FlowCollector consumption'
77+
},
7278
{
7379
id: 'flowCollector-status',
7480
name: 'FlowCollector status'
@@ -193,6 +199,8 @@ export const App: React.FunctionComponent = () => {
193199
return <FlowCollectorWizard />;
194200
case 'flowCollector':
195201
return <FlowCollectorForm />;
202+
case 'flowCollector-consumption':
203+
return <Consumption flowCollector={GetFlowCollectorJS()} />;
196204
case 'flowCollector-status':
197205
return <FlowCollectorStatus />;
198206
case 'flowMetric-wizard':
@@ -216,6 +224,12 @@ export const App: React.FunctionComponent = () => {
216224
case 'flowMetric-wizard':
217225
case 'flowMetric':
218226
return <>{content}</>;
227+
case 'flowCollector-consumption':
228+
return (
229+
<PageSection id="pageSection" className={isDark ? 'dark' : 'light'}>
230+
{content}
231+
</PageSection>
232+
);
219233
default:
220234
return (
221235
<PageSection id="consolePageSection" className={`tab' ${isDark ? 'dark' : 'light'}`}>
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import {
2+
K8sResourceKind,
3+
PrometheusEndpoint,
4+
PrometheusResponse,
5+
usePrometheusPoll
6+
} from '@openshift-console/dynamic-plugin-sdk';
7+
import { Flex, FlexItem, Spinner, Text, TextVariants } from '@patternfly/react-core';
8+
import { WarningTriangleIcon } from '@patternfly/react-icons';
9+
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
10+
import _ from 'lodash';
11+
import React, { FC } from 'react';
12+
import { useTranslation } from 'react-i18next';
13+
import './forms.css';
14+
15+
export type ResourceCalculatorProps = {
16+
flowCollector: K8sResourceKind | null;
17+
};
18+
19+
export const Consumption: FC<ResourceCalculatorProps> = ({ flowCollector }) => {
20+
const { t } = useTranslation('plugin__netobserv-plugin');
21+
22+
const [receivedPackets, rpLoaded, rpError] = usePrometheusPoll({
23+
endpoint: PrometheusEndpoint.QUERY,
24+
query: `sort_desc(sum(irate(container_network_receive_packets_total{cluster="",namespace=~".+"}[4h])) by (node,namespace,pod))`
25+
});
26+
27+
const [transmittedPackets, tpLoaded, tpError] = usePrometheusPoll({
28+
endpoint: PrometheusEndpoint.QUERY,
29+
query: `sort_desc(sum(irate(container_network_transmit_packets_total{cluster="",namespace=~".+"}[4h])) by (node,namespace,pod))`
30+
});
31+
32+
const getCurrentSampling = React.useCallback(() => {
33+
return flowCollector?.spec?.agent?.ebpf?.sampling || 50;
34+
}, [flowCollector?.spec?.agent?.ebpf?.sampling]);
35+
36+
const getSamplings = React.useCallback(() => {
37+
const current = getCurrentSampling();
38+
let samplings = [1, 50, 100, 250];
39+
if (!samplings.includes(current)) {
40+
samplings.push(current);
41+
samplings = _.sortBy(samplings);
42+
}
43+
return samplings;
44+
}, [getCurrentSampling]);
45+
46+
const loadingComponent = () => <Spinner size="lg" />;
47+
48+
const errorComponent = () => <WarningTriangleIcon />;
49+
50+
const value = (response?: PrometheusResponse) => {
51+
if (!response) {
52+
return 0;
53+
}
54+
return _.sumBy(response.data.result, r => Number(r.value![1]));
55+
};
56+
57+
const labelsCount = React.useCallback(
58+
(label: string) => {
59+
if (!rpLoaded) {
60+
return loadingComponent();
61+
} else if (rpError) {
62+
return errorComponent();
63+
} else if (!receivedPackets) {
64+
return t('n/a');
65+
}
66+
return _.uniq(_.map(receivedPackets.data.result, r => r.metric[label])).length;
67+
// eslint-disable-next-line react-hooks/exhaustive-deps
68+
},
69+
[receivedPackets, rpError, rpLoaded]
70+
);
71+
72+
return (
73+
<Flex direction={{ default: 'column' }}>
74+
<FlexItem className="calculator-item">
75+
<Text component={TextVariants.h2}>{t('Cluster metrics')}</Text>
76+
<Table>
77+
<Thead>
78+
<Tr>
79+
<Th>{t('Bandwidth')}</Th>
80+
<Th>{t('Nodes')}</Th>
81+
<Th>{t('Namespaces')}</Th>
82+
<Th>{t('Pods')}</Th>
83+
</Tr>
84+
</Thead>
85+
<Tbody>
86+
<Tr>
87+
<Td>
88+
{!rpLoaded || !tpLoaded
89+
? loadingComponent()
90+
: rpError || tpError
91+
? errorComponent()
92+
: `${Math.round(value(receivedPackets) + value(transmittedPackets))} pps`}
93+
</Td>
94+
<Td>{labelsCount('node')}</Td>
95+
<Td>{labelsCount('namespace')}</Td>
96+
<Td>{labelsCount('pod')}</Td>
97+
</Tr>
98+
</Tbody>
99+
</Table>
100+
</FlexItem>
101+
<FlexItem>
102+
<Text component={TextVariants.h2}>{t('Estimation')}</Text>
103+
<Table>
104+
<Thead>
105+
<Tr>
106+
<Th>{t('Sampling')}</Th>
107+
<Th>{t('vCPU')}</Th>
108+
<Th>{t('Memory')}</Th>
109+
<Th>{t('Storage')}</Th>
110+
<Th>{t('Latency overhead')}</Th>
111+
</Tr>
112+
</Thead>
113+
<Tbody>
114+
{getSamplings().map((sampling, i) => {
115+
const current = getCurrentSampling() === sampling;
116+
return (
117+
<Tr key={i} isRowSelected={current}>
118+
<Td>{`${sampling} ${current ? t('(current)') : ''}`}</Td>
119+
<Td></Td>
120+
<Td></Td>
121+
<Td></Td>
122+
<Td></Td>
123+
</Tr>
124+
);
125+
})}
126+
</Tbody>
127+
</Table>
128+
</FlexItem>
129+
</Flex>
130+
);
131+
};
132+
133+
export default Consumption;

web/src/components/forms/flowCollector-wizard.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import DynamicLoader, { navigate } from '../dynamic-loader/dynamic-loader';
1111
import { FlowCollectorSchema } from './config/schema';
1212
import { GetFlowCollectorJS } from './config/templates';
1313
import { FlowCollectorUISchema } from './config/uiSchema';
14+
import Consumption from './consumption';
1415
import { DynamicForm } from './dynamic-form/dynamic-form';
1516
import './forms.css';
1617
import ResourceWatcher, { Consumer } from './resource-watcher';
@@ -155,6 +156,9 @@ export const FlowCollectorWizard: FC<FlowCollectorWizardProps> = props => {
155156
<WizardStep name={t('Integration')} id="console">
156157
{form()}
157158
</WizardStep>
159+
<WizardStep name={t('Consumption')} id="consumption">
160+
<Consumption flowCollector={data} />
161+
</WizardStep>
158162
<WizardStep
159163
name={t('Review')}
160164
id="review-step"

web/src/components/forms/forms.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,8 @@
5353
flex: 1;
5454
margin-left: 1.5rem;
5555
margin-right: 1.5rem;
56+
}
57+
58+
.calculator-item {
59+
padding-bottom: 1.5rem;
5660
}

web/src/components/forms/pipeline.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
K8sResourceConditionStatus,
55
K8sResourceKind
66
} from '@openshift-console/dynamic-plugin-sdk';
7+
78
import {
89
DefaultTaskGroup,
910
DEFAULT_EDGE_TYPE,
@@ -30,6 +31,7 @@ import {
3031
VisualizationSurface,
3132
WhenDecorator
3233
} from '@patternfly/react-topology';
34+
3335
import { t } from 'i18next';
3436
import _ from 'lodash';
3537
import * as React from 'react';

0 commit comments

Comments
 (0)