Skip to content

Commit 3a05691

Browse files
lavocattMsarawan
authored andcommitted
[#180] monitoring: broker metrics for restricted mode
Display broker metrics for restricted mode. The user needs to activate the monitoring capabilities when creating the broker.
1 parent e0c6263 commit 3a05691

File tree

21 files changed

+1373
-19
lines changed

21 files changed

+1373
-19
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This project is a ActiveMQ Artemis Self Provisioning Plugin to the Administrator
1010
- [Installing the cert-manager operator](#installing-the-cert-manager-operator)
1111
- [Installing trust-manager](#installing-trust-manager)
1212
- [Setting up Chain of Trust](#setting-up-chain-of-trust)
13+
- [Metrics (User Workload Monitoring)](#metrics-user-workload-monitoring)
1314
- [Running the plugin](#running-the-plugin)
1415
- [Trusting Self-Signed Certificates](#trusting-self-signed-certificates-for-websocket-hot-reloading)
1516
- [PKI Setup and Cleanup Scripts](#pki-setup-and-cleanup-scripts)
@@ -120,6 +121,30 @@ yarn chain-of-trust setup
120121

121122
For detailed options, cleanup procedures, and manual commands, see the [PKI Setup and Cleanup Scripts](#pki-setup-and-cleanup-scripts) section.
122123

124+
### Metrics (User Workload Monitoring)
125+
126+
Broker metrics in user namespaces require OpenShift user workload monitoring.
127+
Enable it with the following ConfigMap:
128+
129+
```bash
130+
cat <<'EOF' | kubectl apply -f -
131+
apiVersion: v1
132+
kind: ConfigMap
133+
metadata:
134+
name: cluster-monitoring-config
135+
namespace: openshift-monitoring
136+
data:
137+
config.yaml: |
138+
enableUserWorkload: true
139+
EOF
140+
```
141+
142+
Verify:
143+
144+
```bash
145+
kubectl get pods -n openshift-user-workload-monitoring
146+
```
147+
123148
### Running the plugin
124149

125150
#### Download the secrets so that the bridge can authenticate the user with the api server backend

locales/en/plugin__activemq-artemis-self-provisioning-plugin.json

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,53 @@
3737
"CPU Usage": "CPU Usage",
3838
"Memory Usage": "Memory Usage",
3939
"Memory usage": "Memory usage",
40+
"Pending Messages": "Pending Messages",
41+
"Total Produced": "Total Produced",
42+
"Throughput": "Throughput",
43+
"Messages": "Messages",
44+
"Messages per second": "Messages per second",
4045
"Bytes": "Bytes",
4146
"Time": "Time",
4247
"Information about {{title}}": "Information about {{title}}",
4348
"Metrics data is loading": "Metrics data is loading",
4449
"Data unavailable": "Data unavailable",
50+
"Waiting for the first scrape. This can take up to a minute after enabling monitoring.": "Waiting for the first scrape. This can take up to a minute after enabling monitoring.",
4551
"All Metrics": "All Metrics",
4652
"Memory Usage Metrics": "Memory Usage Metrics",
4753
"CPU Usage Metrics": "CPU Usage Metrics",
54+
"Broker Metrics": "Broker Metrics",
4855
"Metrics": "Metrics",
56+
"Monitoring": "Monitoring",
57+
"Enable monitoring to create a Prometheus client certificate, metrics service, and ServiceMonitor.": "Enable monitoring to create a Prometheus client certificate, metrics service, and ServiceMonitor.",
58+
"Enable monitoring to create Prometheus resources.": "Enable monitoring to create Prometheus resources.",
59+
"Enable monitoring to create the metrics service.": "Enable monitoring to create the metrics service.",
60+
"Enable monitoring to create the ServiceMonitor.": "Enable monitoring to create the ServiceMonitor.",
61+
"Prometheus certificate": "Prometheus certificate",
62+
"Metrics service": "Metrics service",
63+
"ServiceMonitor": "ServiceMonitor",
64+
"Monitoring prerequisites": "Monitoring prerequisites",
65+
"User workload monitoring must be enabled cluster-wide. The namespace must be labeled to allow scraping.": "User workload monitoring must be enabled cluster-wide. The namespace must be labeled to allow scraping.",
66+
"Open service": "Open service",
67+
"Open ServiceMonitor": "Open ServiceMonitor",
68+
"User monitoring label": "User monitoring label",
69+
"Open namespace": "Open namespace",
70+
"Ensuring Prometheus certificate...": "Ensuring Prometheus certificate...",
71+
"Ensuring metrics service...": "Ensuring metrics service...",
72+
"Ensuring ServiceMonitor...": "Ensuring ServiceMonitor...",
73+
"Ensuring namespace label for user monitoring...": "Ensuring namespace label for user monitoring...",
74+
"Removing namespace label for user monitoring...": "Removing namespace label for user monitoring...",
75+
"Prometheus certificate is ready.": "Prometheus certificate is ready.",
76+
"Metrics service is ready.": "Metrics service is ready.",
77+
"ServiceMonitor is ready.": "ServiceMonitor is ready.",
78+
"Namespace is labeled for user monitoring.": "Namespace is labeled for user monitoring.",
79+
"Namespace is not labeled for monitoring.": "Namespace is not labeled for monitoring.",
80+
"Namespace label removed.": "Namespace label removed.",
81+
"Failed to create Prometheus certificate.": "Failed to create Prometheus certificate.",
82+
"Failed to create metrics service.": "Failed to create metrics service.",
83+
"Failed to create ServiceMonitor.": "Failed to create ServiceMonitor.",
84+
"Enable monitoring to label the namespace for scraping.": "Enable monitoring to label the namespace for scraping.",
85+
"Failed to label namespace for monitoring.": "Failed to label namespace for monitoring.",
86+
"Failed to remove namespace label.": "Failed to remove namespace label.",
4987
"0": "Refresh Off",
5088
"15s": "15 seconds",
5189
"30s": "30 seconds",

src/brokers/broker-details/components/Overview/Metrics/Metrics.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { FC, useReducer } from 'react';
22
import { CardBrokerMemoryUsageMetricsContainer } from './components/CardBrokerMemoryUsageMetrics/CardBrokerMemoryUsageMetrics.container';
33
import { MetricsActions } from './components/MetricsActions/MetricsActions';
44
import { CardBrokerCPUUsageMetricsContainer } from './components/CardBrokerCPUUsageMetrics/CardBrokerCPUUsageMetrics.container';
5+
import { CardBrokerPendingMessagesMetricsContainer } from './components/CardBrokerPendingMessagesMetrics/CardBrokerPendingMessagesMetrics.container';
6+
import { CardBrokerTotalProducedMetricsContainer } from './components/CardBrokerTotalProducedMetrics/CardBrokerTotalProducedMetrics.container';
7+
import { CardBrokerThroughputMetricsContainer } from './components/CardBrokerThroughputMetrics/CardBrokerThroughputMetrics.container';
58
import { MetricsType, MetricsState, MetricsAction } from './utils/types';
69
import { MetricsLayout } from './components/MetricsLayout/MetricsLayout';
710

@@ -44,6 +47,13 @@ export const Metrics: FC<{ name: string; namespace: string; size: number }> = ({
4447
<CardBrokerMemoryUsageMetricsContainer state={state} />
4548
}
4649
metricsCPUUsage={<CardBrokerCPUUsageMetricsContainer state={state} />}
50+
metricsPendingMessages={
51+
<CardBrokerPendingMessagesMetricsContainer state={state} />
52+
}
53+
metricsTotalProduced={
54+
<CardBrokerTotalProducedMetricsContainer state={state} />
55+
}
56+
metricsThroughput={<CardBrokerThroughputMetricsContainer state={state} />}
4757
metricsActions={<MetricsActions state={state} dispatch={dispatch} />}
4858
/>
4959
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { FC, useState, useMemo, useCallback } from 'react';
2+
import { parsePrometheusDuration } from '../../../../Overview/Metrics/utils/prometheus';
3+
import { getMaxSamplesForSpan, formatNumber } from '../../utils/format';
4+
import { pendingMessagesQuery } from '../../utils/queries';
5+
import { MetricsPolling } from '../MetricsPolling/MetricsPolling';
6+
import { useTranslation } from '@app/i18n/i18n';
7+
import { CardQueryBrowser } from '../CardQueryBrowser/CardQueryBrowser';
8+
import { PrometheusResponse } from '@openshift-console/dynamic-plugin-sdk';
9+
import { MetricsState } from '../../utils/types';
10+
import { MetricsErrorBoundary } from '../../MetricsErrorBoundary';
11+
12+
type CardBrokerPendingMessagesMetricsContainerProps = {
13+
state: MetricsState;
14+
};
15+
16+
type AxisDomain = [number, number];
17+
18+
export const CardBrokerPendingMessagesMetricsContainer: FC<
19+
CardBrokerPendingMessagesMetricsContainerProps
20+
> = ({ state }) => {
21+
const { t } = useTranslation();
22+
23+
const [xDomain] = useState<AxisDomain>();
24+
const [results, setResults] = useState<{
25+
[key: number]: {
26+
result: PrometheusResponse | null;
27+
loaded: boolean;
28+
loadError: unknown | null;
29+
};
30+
}>({});
31+
32+
const queries = useMemo(
33+
() =>
34+
[...Array(state.size)].map((_, i) =>
35+
pendingMessagesQuery(state.name, state.namespace, i),
36+
),
37+
[state.size, state.name, state.namespace],
38+
);
39+
40+
const handleMetricResult = useCallback(
41+
(
42+
index: number,
43+
result: PrometheusResponse | null,
44+
loaded: boolean,
45+
loadError: unknown | null,
46+
) => {
47+
setResults((prev) => ({
48+
...prev,
49+
[index]: { result, loaded, loadError },
50+
}));
51+
},
52+
[],
53+
);
54+
55+
const { metricsResult, loaded, errorObject } = useMemo(() => {
56+
const resultsArray = Object.values(results);
57+
58+
const loaded =
59+
queries.length > 0 &&
60+
resultsArray.length === queries.length &&
61+
resultsArray.every((r) => r.loaded);
62+
63+
const errorObject =
64+
resultsArray.find((r) => r.loadError)?.loadError || null;
65+
66+
const metricsResult = resultsArray
67+
.map((r) => r.result)
68+
.filter((res): res is PrometheusResponse => !!res);
69+
70+
return { metricsResult, loaded, errorObject };
71+
}, [results, queries]);
72+
73+
const samples = getMaxSamplesForSpan(parsePrometheusDuration(state.span));
74+
const endTime = xDomain?.[1];
75+
76+
const yTickFormat = useCallback(
77+
(tick: number) => formatNumber(String(tick)),
78+
[],
79+
);
80+
81+
return (
82+
<>
83+
{queries.map((query, i) => (
84+
<MetricsPolling
85+
key={i}
86+
query={query}
87+
index={i}
88+
namespace={state.namespace}
89+
span={parsePrometheusDuration(state.span)}
90+
samples={samples}
91+
endTime={endTime}
92+
delay={parsePrometheusDuration(state.pollTime)}
93+
onResult={handleMetricResult}
94+
/>
95+
))}
96+
<MetricsErrorBoundary>
97+
<CardQueryBrowser
98+
isInitialLoading={false}
99+
backendUnavailable={false}
100+
allMetricsSeries={metricsResult}
101+
span={parsePrometheusDuration(state.span)}
102+
isLoading={!loaded}
103+
fixedXDomain={xDomain}
104+
samples={samples}
105+
formatSeriesTitle={(labels) => labels.pod}
106+
title={t('Pending Messages')}
107+
helperText={t('Pending Messages')}
108+
dataTestId={'metrics-broker-pending-messages'}
109+
yTickFormat={yTickFormat}
110+
label={'\n\n\n\n' + t('Messages')}
111+
ariaTitle={t('Pending Messages')}
112+
error={errorObject}
113+
/>
114+
</MetricsErrorBoundary>
115+
</>
116+
);
117+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { FC, useState, useMemo, useCallback } from 'react';
2+
import { parsePrometheusDuration } from '../../../../Overview/Metrics/utils/prometheus';
3+
import { getMaxSamplesForSpan, formatNumber } from '../../utils/format';
4+
import { throughputQuery } from '../../utils/queries';
5+
import { MetricsPolling } from '../MetricsPolling/MetricsPolling';
6+
import { useTranslation } from '@app/i18n/i18n';
7+
import { CardQueryBrowser } from '../CardQueryBrowser/CardQueryBrowser';
8+
import { PrometheusResponse } from '@openshift-console/dynamic-plugin-sdk';
9+
import { MetricsState } from '../../utils/types';
10+
import { MetricsErrorBoundary } from '../../MetricsErrorBoundary';
11+
12+
type CardBrokerThroughputMetricsContainerProps = {
13+
state: MetricsState;
14+
};
15+
16+
type AxisDomain = [number, number];
17+
18+
export const CardBrokerThroughputMetricsContainer: FC<
19+
CardBrokerThroughputMetricsContainerProps
20+
> = ({ state }) => {
21+
const { t } = useTranslation();
22+
23+
const [xDomain] = useState<AxisDomain>();
24+
const [results, setResults] = useState<{
25+
[key: number]: {
26+
result: PrometheusResponse | null;
27+
loaded: boolean;
28+
loadError: unknown | null;
29+
};
30+
}>({});
31+
32+
const queries = useMemo(
33+
() =>
34+
[...Array(state.size)].map((_, i) =>
35+
throughputQuery(state.name, state.namespace, i),
36+
),
37+
[state.size, state.name, state.namespace],
38+
);
39+
40+
const handleMetricResult = useCallback(
41+
(
42+
index: number,
43+
result: PrometheusResponse | null,
44+
loaded: boolean,
45+
loadError: unknown | null,
46+
) => {
47+
setResults((prev) => ({
48+
...prev,
49+
[index]: { result, loaded, loadError },
50+
}));
51+
},
52+
[],
53+
);
54+
55+
const { metricsResult, loaded, errorObject } = useMemo(() => {
56+
const resultsArray = Object.values(results);
57+
58+
const loaded =
59+
queries.length > 0 &&
60+
resultsArray.length === queries.length &&
61+
resultsArray.every((r) => r.loaded);
62+
63+
const errorObject =
64+
resultsArray.find((r) => r.loadError)?.loadError || null;
65+
66+
const metricsResult = resultsArray
67+
.map((r) => r.result)
68+
.filter((res): res is PrometheusResponse => !!res);
69+
70+
return { metricsResult, loaded, errorObject };
71+
}, [results, queries]);
72+
73+
const samples = getMaxSamplesForSpan(parsePrometheusDuration(state.span));
74+
const endTime = xDomain?.[1];
75+
76+
const yTickFormat = useCallback(
77+
(tick: number) => formatNumber(String(tick), 2, 'mps'),
78+
[],
79+
);
80+
81+
return (
82+
<>
83+
{queries.map((query, i) => (
84+
<MetricsPolling
85+
key={i}
86+
query={query}
87+
index={i}
88+
namespace={state.namespace}
89+
span={parsePrometheusDuration(state.span)}
90+
samples={samples}
91+
endTime={endTime}
92+
delay={parsePrometheusDuration(state.pollTime)}
93+
onResult={handleMetricResult}
94+
/>
95+
))}
96+
<MetricsErrorBoundary>
97+
<CardQueryBrowser
98+
isInitialLoading={false}
99+
backendUnavailable={false}
100+
allMetricsSeries={metricsResult}
101+
span={parsePrometheusDuration(state.span)}
102+
isLoading={!loaded}
103+
fixedXDomain={xDomain}
104+
samples={samples}
105+
formatSeriesTitle={(labels) => labels.pod}
106+
title={t('Throughput')}
107+
helperText={t('Throughput')}
108+
dataTestId={'metrics-broker-throughput'}
109+
yTickFormat={yTickFormat}
110+
label={'\n\n\n\n' + t('Messages per second')}
111+
ariaTitle={t('Throughput')}
112+
error={errorObject}
113+
/>
114+
</MetricsErrorBoundary>
115+
</>
116+
);
117+
};

0 commit comments

Comments
 (0)