2025-04-01 10:38:02 +09:00

185 lines
6.3 KiB
TypeScript

import { css, cx } from '@emotion/css';
import { PureComponent } from 'react';
import { MetadataInspectorProps, rangeUtil } from '@grafana/data';
import { stylesFactory } from '@grafana/ui';
import { config } from 'app/core/config';
import { GraphiteDatasource } from '../datasource';
import { getRollupNotice, getRuntimeConsolidationNotice, parseSchemaRetentions } from '../meta';
import { GraphiteOptions, GraphiteQuery, MetricTankSeriesMeta } from '../types';
export type Props = MetadataInspectorProps<GraphiteDatasource, GraphiteQuery, GraphiteOptions>;
export interface State {
index: number;
}
export class MetricTankMetaInspector extends PureComponent<Props, State> {
renderMeta(meta: MetricTankSeriesMeta, key: string) {
const styles = getStyles();
const buckets = parseSchemaRetentions(meta['schema-retentions']);
const rollupNotice = getRollupNotice([meta]);
const runtimeNotice = getRuntimeConsolidationNotice([meta]);
const normFunc = (meta['consolidator-normfetch'] ?? '').replace('Consolidator', '');
const totalSeconds = buckets.reduce(
(acc, bucket) => acc + (bucket.retention ? rangeUtil.intervalToSeconds(bucket.retention) : 0),
0
);
return (
<div className={styles.metaItem} key={key}>
<div className={styles.metaItemHeader}>
Schema: {meta['schema-name']}
<div className="small muted">Series count: {meta.count}</div>
</div>
<div className={styles.metaItemBody}>
<div className={styles.step}>
<div className={styles.stepHeading}>Step 1: Fetch</div>
<div className={styles.stepDescription}>
First data is fetched, either from raw data archive or a rollup archive
</div>
{rollupNotice && <p>{rollupNotice.text}</p>}
{!rollupNotice && <p>No rollup archive was used</p>}
<div>
{buckets.map((bucket, index) => {
const bucketLength = bucket.retention ? rangeUtil.intervalToSeconds(bucket.retention) : 0;
const lengthPercent = (bucketLength / totalSeconds) * 100;
const isActive = index === meta['archive-read'];
return (
<div key={bucket.retention} className={styles.bucket}>
<div className={styles.bucketInterval}>{bucket.interval}</div>
<div
className={cx(styles.bucketRetention, { [styles.bucketRetentionActive]: isActive })}
style={{ flexGrow: lengthPercent }}
/>
<div style={{ flexGrow: 100 - lengthPercent }}>{bucket.retention}</div>
</div>
);
})}
</div>
</div>
<div className={styles.step}>
<div className={styles.stepHeading}>Step 2: Normalization</div>
<div className={styles.stepDescription}>
Normalization happens when series with different intervals between points are combined.
</div>
{meta['aggnum-norm'] > 1 && <p>Normalization did occur using {normFunc}</p>}
{meta['aggnum-norm'] === 1 && <p>No normalization was needed</p>}
</div>
<div className={styles.step}>
<div className={styles.stepHeading}>Step 3: Runtime consolidation</div>
<div className={styles.stepDescription}>
If there are too many data points at this point Metrictank will consolidate them down to below max data
points (set in queries tab).
</div>
{runtimeNotice && <p>{runtimeNotice.text}</p>}
{!runtimeNotice && <p>No runtime consolidation</p>}
</div>
</div>
</div>
);
}
render() {
const { data } = this.props;
// away to dedupe them
const seriesMetas: Record<string, MetricTankSeriesMeta> = {};
for (const series of data) {
const seriesMetaList: MetricTankSeriesMeta[] | undefined = series?.meta?.custom?.seriesMetaList;
if (seriesMetaList) {
for (const metaItem of seriesMetaList) {
// key is to dedupe as many series will have identitical meta
const key = `${JSON.stringify(metaItem)}`;
if (seriesMetas[key]) {
seriesMetas[key].count += metaItem.count;
} else {
seriesMetas[key] = metaItem;
}
}
}
}
if (Object.keys(seriesMetas).length === 0) {
return <div>No response meta data</div>;
}
return (
<div>
<h2 className="page-heading">Metrictank Lineage</h2>
{Object.keys(seriesMetas).map((key) => this.renderMeta(seriesMetas[key], key))}
</div>
);
}
}
const getStyles = stylesFactory(() => {
const { theme } = config;
const borderColor = theme.isDark ? theme.palette.gray25 : theme.palette.gray85;
const background = theme.isDark ? theme.palette.dark1 : theme.palette.white;
const headerBg = theme.isDark ? theme.palette.gray15 : theme.palette.gray85;
return {
metaItem: css({
background: background,
border: `1px solid ${borderColor}`,
marginBottom: theme.spacing.md,
}),
metaItemHeader: css({
background: headerBg,
padding: `${theme.spacing.xs} ${theme.spacing.md}`,
fontSize: theme.typography.size.md,
display: 'flex',
justifyContent: 'space-between',
}),
metaItemBody: css({
padding: theme.spacing.md,
}),
stepHeading: css({
fontSize: theme.typography.size.md,
}),
stepDescription: css({
fontSize: theme.typography.size.sm,
color: theme.colors.textWeak,
marginBottom: theme.spacing.sm,
}),
step: css({
marginBottom: theme.spacing.lg,
'&:last-child': {
marginBottom: 0,
},
}),
bucket: css({
display: 'flex',
marginBottom: theme.spacing.sm,
borderRadius: theme.border.radius.sm,
}),
bucketInterval: css({
flexGrow: 0,
width: '60px',
}),
bucketRetention: css({
background: `linear-gradient(0deg, ${theme.palette.blue85}, ${theme.palette.blue95})`,
textAlign: 'center',
color: theme.palette.white,
marginRight: theme.spacing.md,
borderRadius: theme.border.radius.sm,
}),
bucketRetentionActive: css({
background: `linear-gradient(0deg, ${theme.palette.greenBase}, ${theme.palette.greenShade})`,
}),
};
});