Skip to content

Commit 0a8fcdc

Browse files
authored
Merge pull request #28 from headlamp-k8s/pvcmetrics
prometheus: Add chart for PVC
2 parents 469d0d1 + 341ba58 commit 0a8fcdc

File tree

2 files changed

+204
-31
lines changed

2 files changed

+204
-31
lines changed

prometheus/src/common.tsx

Lines changed: 194 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,43 @@ const useStyles = makeStyles(theme => ({
3030
},
3131
}));
3232

33+
function InstallPrometheusBanner() {
34+
const classes = useStyles();
35+
const pluginSettings = usePluginSettings();
36+
37+
return (
38+
<Grid
39+
container
40+
spacing={2}
41+
direction="column"
42+
justifyContent="center"
43+
alignItems="center"
44+
className={classes.skeletonBox}
45+
>
46+
<Grid item>
47+
<Typography variant="h5">Install Prometheus for accessing metrics charts</Typography>
48+
</Grid>
49+
<Grid item>
50+
<Typography>
51+
<Link href={learnMoreLink} target="_blank">
52+
Learn more about enabling advanced charts.
53+
</Link>
54+
</Typography>
55+
</Grid>
56+
<Grid item>
57+
<Button
58+
className={classes.dismissButton}
59+
size="small"
60+
variant="contained"
61+
onClick={() => pluginSettings.setIsVisible(false)}
62+
>
63+
Dismiss
64+
</Button>
65+
</Grid>
66+
</Grid>
67+
)
68+
}
69+
3370
export function GenericMetricsChart(props: {
3471
cpuQuery: string;
3572
memoryQuery: string;
@@ -45,7 +82,6 @@ export function GenericMetricsChart(props: {
4582
INSTALLED,
4683
}
4784

48-
const classes = useStyles();
4985
const pluginSettings = usePluginSettings();
5086
const [chartVariant, setChartVariant] = useState<string>('cpu');
5187
const [refresh, setRefresh] = useState<boolean>(true);
@@ -160,35 +196,99 @@ export function GenericMetricsChart(props: {
160196
<Alert severity="warning">Error fetching prometheus Info</Alert>
161197
</Box>
162198
) : (
163-
<Grid
164-
container
165-
spacing={2}
166-
direction="column"
167-
justifyContent="center"
168-
alignItems="center"
169-
className={classes.skeletonBox}
170-
>
171-
<Grid item>
172-
<Typography variant="h5">Install Prometheus for accessing metrics charts</Typography>
173-
</Grid>
174-
<Grid item>
175-
<Typography>
176-
<Link href={learnMoreLink} target="_blank">
177-
Learn more about enabling advanced charts.
178-
</Link>
179-
</Typography>
180-
</Grid>
181-
<Grid item>
182-
<Button
183-
className={classes.dismissButton}
184-
size="small"
185-
variant="contained"
186-
onClick={() => pluginSettings.setIsVisible(false)}
187-
>
188-
Dismiss
189-
</Button>
190-
</Grid>
191-
</Grid>
199+
<InstallPrometheusBanner />
200+
)}
201+
</SectionBox>
202+
);
203+
}
204+
205+
export function DiskMetricsChart(props: {
206+
usageQuery?: string;
207+
capacityQuery?: string;
208+
}) {
209+
enum prometheusState {
210+
UNKNOWN,
211+
LOADING,
212+
ERROR,
213+
INSTALLED,
214+
}
215+
216+
const pluginSettings = usePluginSettings();
217+
const [refresh, setRefresh] = useState<boolean>(true);
218+
219+
const [prometheusInfo, setPrometheusInfo] = useState<{
220+
podName: string;
221+
podNamespace: string;
222+
} | null>(null);
223+
const [state, setState] = useState<prometheusState>(prometheusState.LOADING);
224+
225+
useEffect(() => {
226+
(async () => {
227+
try {
228+
const [isInstalled, podName, namespace] = await isPrometheusInstalled();
229+
if (isInstalled) {
230+
setPrometheusInfo({ podName, podNamespace: namespace });
231+
setState(prometheusState.INSTALLED);
232+
} else {
233+
setPrometheusInfo(null);
234+
setState(prometheusState.UNKNOWN);
235+
}
236+
} catch (e) {
237+
setState(prometheusState.ERROR);
238+
return;
239+
}
240+
})();
241+
}, []);
242+
243+
if (!pluginSettings.isVisible) {
244+
return null;
245+
}
246+
247+
return (
248+
<SectionBox>
249+
<Box
250+
display="flex"
251+
justifyContent="space-around"
252+
alignItems="center"
253+
style={{ marginBottom: '0.5rem', margin: '0 auto', width: '0%' }}
254+
255+
>
256+
{state === prometheusState.INSTALLED
257+
? [
258+
<Box>Disk</Box>,
259+
<Box pl={2}>
260+
<IconButton
261+
onClick={() => {
262+
setRefresh(refresh => !refresh);
263+
}}
264+
size="Big"
265+
>
266+
{refresh ? <Icon icon="mdi:pause" /> : <Icon icon="mdi:play" />}
267+
</IconButton>
268+
</Box>,
269+
]
270+
: []}
271+
</Box>
272+
273+
{prometheusInfo ? (
274+
<Box style={{ justifyContent: 'center', display: 'flex', height: '40vh', width: '80%', margin: '0 auto'}}>
275+
<DiskChart
276+
usageQuery={props.usageQuery}
277+
capacityQuery={props.capacityQuery}
278+
autoRefresh={refresh}
279+
prometheusPrefix={`${prometheusInfo.podNamespace}/pods/${prometheusInfo.podName}`}
280+
/>
281+
</Box>
282+
) : state === prometheusState.LOADING ? (
283+
<Box m={2}>
284+
<Loader title="Loading Prometheus Info" />
285+
</Box>
286+
) : state === prometheusState.ERROR ? (
287+
<Box m={2}>
288+
<Alert severity="warning">Error fetching prometheus Info</Alert>
289+
</Box>
290+
) : (
291+
<InstallPrometheusBanner />
192292
)}
193293
</SectionBox>
194294
);
@@ -442,6 +542,70 @@ export function NetworkChart(props: {
442542
);
443543
}
444544

545+
export function DiskChart(props: {
546+
usageQuery: string;
547+
capacityQuery: string;
548+
prometheusPrefix: string;
549+
autoRefresh: boolean;
550+
}) {
551+
const xTickFormatter = createTickTimestampFormatter();
552+
const theme = useTheme();
553+
554+
return (
555+
<Chart
556+
plots={[
557+
{
558+
query: props.usageQuery,
559+
name: 'usage',
560+
strokeColor: '#CDC300',
561+
fillColor: '#FFF178',
562+
dataProcessor: dataProcessor,
563+
},
564+
{
565+
query: props.capacityQuery,
566+
name: 'capacity',
567+
strokeColor: '#006B58',
568+
fillColor: '#98F6DC',
569+
dataProcessor: dataProcessor,
570+
},
571+
]}
572+
xAxisProps={{
573+
dataKey: 'timestamp',
574+
tickLine: false,
575+
tick: props => {
576+
const value = xTickFormatter(props.payload.value);
577+
return (
578+
value !== '' && (
579+
<g
580+
transform={`translate(${props.x},${props.y})`}
581+
fill={theme.palette.chartStyles.labelColor}
582+
>
583+
<text x={0} y={10} dy={0} textAnchor="middle">
584+
{value}
585+
</text>
586+
</g>
587+
)
588+
);
589+
},
590+
}}
591+
yAxisProps={{
592+
domain: ['dataMin', 'auto'],
593+
tick: ({ x, y, payload }) => (
594+
<g transform={`translate(${x},${y})`} fill={theme.palette.chartStyles.labelColor}>
595+
<text x={-35} y={0} dy={0} textAnchor="middle">
596+
{formatBytes(payload.value)}
597+
</text>
598+
</g>
599+
),
600+
width: 80,
601+
}}
602+
fetchMetrics={fetchMetrics}
603+
CustomTooltip={CustomTooltipFormatBytes}
604+
{...props}
605+
/>
606+
);
607+
}
608+
445609
export function FilesystemChart(props: {
446610
readQuery: string;
447611
writeQuery: string;

prometheus/src/index.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
registerDetailsViewHeaderActionsProcessor,
55
registerDetailsViewSectionsProcessor,
66
} from '@kinvolk/headlamp-plugin/lib';
7-
import { GenericMetricsChart } from './common';
7+
import { DiskMetricsChart, GenericMetricsChart } from './common';
88
import { ChartEnabledKinds } from './util';
99
import VisibilityButton from './VisibilityButton';
1010

@@ -38,6 +38,15 @@ function PrometheusMetrics(resource: DetailsViewSectionProps) {
3838
/>
3939
);
4040
}
41+
42+
if (resource.kind === 'PersistentVolumeClaim') {
43+
return (
44+
<DiskMetricsChart
45+
usageQuery={`sum(kubelet_volume_stats_used_bytes{namespace='${resource.jsonData.metadata.namespace}',persistentvolumeclaim='${resource.jsonData.metadata.name}'}) by (persistentvolumeclaim, namespace)`}
46+
capacityQuery={`sum(kubelet_volume_stats_capacity_bytes{namespace='${resource.jsonData.metadata.namespace}',persistentvolumeclaim='${resource.jsonData.metadata.name}'}) by (persistentvolumeclaim, namespace)`}
47+
/>
48+
);
49+
}
4150
}
4251

4352
registerDetailsViewSectionsProcessor(function addSubheaderSection(resource, sections) {

0 commit comments

Comments
 (0)