Skip to content

Commit

Permalink
Bug 1856351: Fix build details page charts
Browse files Browse the repository at this point in the history
Update build details page charts to only show utilization data from when the build ran.
  • Loading branch information
TheRealJon committed Oct 9, 2020
1 parent c39421d commit 7d4e944
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 100 deletions.
26 changes: 15 additions & 11 deletions frontend/public/components/build.tsx
Expand Up @@ -127,40 +127,44 @@ export const BuildNumberLink = ({ build }) => {
};

const BuildGraphs = requirePrometheus(({ build }) => {
const podName = _.get(build, ['metadata', 'annotations', 'openshift.io/build.pod-name']);
const podName = build.metadata.annotations?.['openshift.io/build.pod-name'];
if (!podName) {
return null;
}

const namespace = build.metadata.namespace;

const commonGraphProps = {
startTime: build?.status?.startTimestamp && new Date(build.status.startTimestamp).getTime(),
endTime:
build?.status?.completionTimestamp && new Date(build.status.completionTimestamp).getTime(),
namespace,
};
return (
<>
<div className="row">
<div className="col-md-12 col-lg-4">
<Area
title="Memory Usage"
humanize={humanizeBinaryBytes}
byteDataType={ByteDataTypes.BinaryBytes}
namespace={namespace}
humanize={humanizeBinaryBytes}
query={`sum(container_memory_working_set_bytes{pod='${podName}',namespace='${namespace}',container=''}) BY (pod, namespace)`}
title="Memory Usage"
{...commonGraphProps}
/>
</div>
<div className="col-md-12 col-lg-4">
<Area
title="CPU Usage"
humanize={humanizeCpuCores}
namespace={namespace}
query={`pod:container_cpu_usage:sum{pod='${podName}',container='',namespace='${namespace}'}`}
title="CPU Usage"
{...commonGraphProps}
/>
</div>
<div className="col-md-12 col-lg-4">
<Area
title="Filesystem"
humanize={humanizeBinaryBytes}
byteDataType={ByteDataTypes.BinaryBytes}
namespace={namespace}
humanize={humanizeBinaryBytes}
query={`pod:container_fs_usage_bytes:sum{pod='${podName}',container='',namespace='${namespace}'}`}
title="Filesystem"
{...commonGraphProps}
/>
</div>
</div>
Expand Down
81 changes: 55 additions & 26 deletions frontend/public/components/graphs/area.tsx
Expand Up @@ -19,15 +19,18 @@ import { PrometheusEndpoint } from './helpers';
import { PrometheusGraph, PrometheusGraphLink } from './prometheus-graph';
import { usePrometheusPoll } from './prometheus-poll-hook';
import { areaTheme } from './themes';
import { DataPoint, CursorVoronoiContainer } from './';
import { mapLimitsRequests } from './utils';
import {
DataPoint,
CursorVoronoiContainer,
DEFAULT_PROMETHEUS_SAMPLES,
DEFAULT_PROMETHEUS_TIMESPAN,
} from './';
import { defaultXMutator, getRangeVectorTimespan, mapLimitsRequests } from './utils';
import { GraphEmpty } from './graph-empty';
import { ChartLegendTooltip } from './tooltip';

const DEFAULT_HEIGHT = 180;
const DEFAULT_SAMPLES = 60;
const DEFAULT_TICK_COUNT = 3;
const DEFAULT_TIMESPAN = 60 * 60 * 1000; // 1 hour

export enum AreaChartStatus {
ERROR = 'ERROR',
Expand Down Expand Up @@ -132,7 +135,7 @@ export const AreaChart: React.FC<AreaChartProps> = ({
padding={padding}
{...(allZero && { domain: { y: [0, 1] } })}
>
{xAxis && <ChartAxis tickCount={tickCount} tickFormat={formatDate} />}
{xAxis && <ChartAxis tickCount={tickCount} tickFormat={(tick) => formatDate(tick)} />}
{yAxis && <ChartAxis dependentAxis tickCount={tickCount} tickFormat={tickFormat} />}
<ChartGroup>
{processedData.map((datum, index) => (
Expand All @@ -158,48 +161,72 @@ export const Area: React.FC<AreaProps> = ({
query,
limitQuery,
requestedQuery,
samples = DEFAULT_SAMPLES,
samples = DEFAULT_PROMETHEUS_SAMPLES,
timeout,
timespan = DEFAULT_TIMESPAN,
...rest
}) => {
const [utilization, , utilizationLoading] = usePrometheusPoll({
const endTime = rest.endTime ?? Date.now();
const timespan = rest.timespan ?? DEFAULT_PROMETHEUS_TIMESPAN;
const startTime = rest.startTime ?? endTime - timespan;
const commonPrometheusPollProps = {
endpoint: PrometheusEndpoint.QUERY_RANGE,
endTime,
namespace,
query,
samples,
startTime,
timeout,
timespan,
};

const [utilization, , utilizationLoading] = usePrometheusPoll({
query,
...commonPrometheusPollProps,
});
const [limit, , limitLoading] = usePrometheusPoll({
endpoint: PrometheusEndpoint.QUERY_RANGE,
namespace,
query: limitQuery,
samples,
timeout,
timespan,
...commonPrometheusPollProps,
});
const [requested, , requestedLoading] = usePrometheusPoll({
endpoint: PrometheusEndpoint.QUERY_RANGE,
namespace,
query: requestedQuery,
samples,
timeout,
timespan,
...commonPrometheusPollProps,
});
const { data, chartStyle } = mapLimitsRequests(utilization, limit, requested);
const loading =
utilizationLoading &&
(limitQuery ? limitLoading : true) &&
(requestedQuery ? requestedLoading : true);

const loading = _.every([utilizationLoading, limitLoading, requestedLoading], Boolean);
const [data, chartStyle, subminute, subsecond] = React.useMemo(() => {
if (loading) {
return [];
}
const dataTimespans = [utilization, limit, requested].map(getRangeVectorTimespan);
const dataTimespan = Math.min(...dataTimespans);
const isSubminute = dataTimespan < 60 * 1000;
const isSubsecond = dataTimespan < 1000;
const xMutator = isSubminute || isSubsecond ? (x) => new Date(x) : defaultXMutator;
const res = mapLimitsRequests(utilization, limit, requested, xMutator);
return [res.data, res.chartStyle, isSubminute, isSubsecond];
}, [limit, loading, requested, utilization]);

// Change the default formatDate function when we need to show seconds.
const formatDate = React.useCallback(
(d) => rest.formatDate?.(d) ?? twentyFourHourTime(d, subminute, subsecond),
// showSeconds ? twentyFourHourTimeWithSeconds : twentyFourHourTime,
[rest.formatDate, subminute, subsecond],
);

return (
<AreaChart data={data} loading={loading} query={query} chartStyle={chartStyle} {...rest} />
<AreaChart
{...rest}
chartStyle={chartStyle}
data={data}
formatDate={formatDate}
loading={loading}
query={query}
/>
);
};

export type AreaChartProps = {
className?: string;
formatDate?: (date: Date) => string;
formatDate?: (date: Date, showSeconds?: boolean, showDecimal?: boolean) => string;
humanize?: Humanize;
height?: number;
loading?: boolean;
Expand All @@ -217,9 +244,11 @@ export type AreaChartProps = {
};

type AreaProps = AreaChartProps & {
endTime?: number;
namespace?: string;
query: string;
samples?: number;
startTime?: number;
timeout?: string;
timespan?: number;
byteDataType?: ByteDataTypes;
Expand Down
43 changes: 29 additions & 14 deletions frontend/public/components/graphs/helpers.ts
@@ -1,40 +1,49 @@
import * as _ from 'lodash-es';

import { PROMETHEUS_BASE_PATH, PROMETHEUS_TENANCY_BASE_PATH } from './index';
import {
PROMETHEUS_BASE_PATH,
PROMETHEUS_TENANCY_BASE_PATH,
DEFAULT_PROMETHEUS_SAMPLES,
DEFAULT_PROMETHEUS_TIMESPAN,
} from './index';

export enum PrometheusEndpoint {
LABEL = 'api/v1/label',
QUERY = 'api/v1/query',
QUERY_RANGE = 'api/v1/query_range',
}

export const getPrometheusQueryEndTimestamp = () => Date.now();

// Range vector queries require end, start, and step search params
const getRangeVectorSearchParams = (
timespan: number,
endTime: number = getPrometheusQueryEndTimestamp(),
samples: number = 60,
): URLSearchParams => {
const getRangeVectorSearchParams = ({
endTime = getPrometheusQueryEndTimestamp(),
samples = DEFAULT_PROMETHEUS_SAMPLES,
startTime,
timespan = DEFAULT_PROMETHEUS_TIMESPAN,
}: RangeVectorSearchParamProps): URLSearchParams => {
const params = new URLSearchParams();
if (timespan > 0) {
params.append('start', `${(endTime - timespan) / 1000}`);
params.append('end', `${endTime / 1000}`);
params.append('step', `${timespan / samples / 1000}`);
}
const span = startTime ? endTime - startTime : timespan;
params.append('start', `${(endTime - span) / 1000}`);
params.append('end', `${endTime / 1000}`);
params.append('step', `${span / samples / 1000}`);
return params;
};

const getSearchParams = ({
endpoint,
endTime,
startTime,
timespan,
samples,
...params
}: PrometheusURLProps): URLSearchParams => {
const searchParams =
endpoint === PrometheusEndpoint.QUERY_RANGE
? getRangeVectorSearchParams(timespan, endTime, samples)
? getRangeVectorSearchParams({
endTime,
samples,
startTime,
timespan,
})
: new URLSearchParams();
_.each(params, (value, key) => value && searchParams.append(key, value.toString()));
return searchParams;
Expand All @@ -57,6 +66,12 @@ type PrometheusURLProps = {
namespace?: string;
query: string;
samples?: number;
startTime?: number;
timeout?: string;
timespan?: number;
};

type RangeVectorSearchParamProps = Pick<
PrometheusURLProps,
'endTime' | 'samples' | 'startTime' | 'timespan'
>;
3 changes: 2 additions & 1 deletion frontend/public/components/graphs/index.tsx
Expand Up @@ -7,7 +7,8 @@ export const PROMETHEUS_BASE_PATH = window.SERVER_FLAGS.prometheusBaseURL;
export const PROMETHEUS_TENANCY_BASE_PATH = window.SERVER_FLAGS.prometheusTenancyBaseURL;
export const ALERT_MANAGER_BASE_PATH = window.SERVER_FLAGS.alertManagerBaseURL;
export const ALERT_MANAGER_TENANCY_BASE_PATH = 'api/alertmanager-tenancy'; // remove it once it get added to SERVER_FLAGS

export const DEFAULT_PROMETHEUS_SAMPLES = 60;
export const DEFAULT_PROMETHEUS_TIMESPAN = 60 * 60 * 1000; // 1 hour
// Components
export * from './require-prometheus';
export { errorStatus, Status } from './status';
Expand Down
25 changes: 16 additions & 9 deletions frontend/public/components/graphs/prometheus-poll-hook.ts
@@ -1,22 +1,28 @@
import { useURLPoll } from '../utils/url-poll-hook';
import { getPrometheusURL, PrometheusEndpoint } from './helpers';
import { PrometheusResponse } from '.';

const DEFAULT_SAMPLES = 60;
const DEFAULT_TIMESPAN = 60 * 60 * 1000; // 1 hour
import { DEFAULT_PROMETHEUS_SAMPLES, DEFAULT_PROMETHEUS_TIMESPAN, PrometheusResponse } from '.';

export const usePrometheusPoll = ({
delay,
endpoint,
endTime = undefined,
endTime = Date.now(),
namespace,
query,
samples = DEFAULT_SAMPLES,
samples = DEFAULT_PROMETHEUS_SAMPLES,
startTime,
timeout,
timespan = DEFAULT_TIMESPAN,
timespan = DEFAULT_PROMETHEUS_TIMESPAN,
}: PrometheusPollProps) => {
const url = getPrometheusURL({ endpoint, endTime, namespace, query, samples, timeout, timespan });

const url = getPrometheusURL({
endpoint,
endTime,
namespace,
query,
samples,
startTime,
timeout,
timespan,
});
return useURLPoll<PrometheusResponse>(url, delay, endTime, query, timespan);
};

Expand All @@ -27,6 +33,7 @@ type PrometheusPollProps = {
namespace?: string;
query: string;
samples?: number;
startTime?: number;
timeout?: string;
timespan?: number;
};
4 changes: 2 additions & 2 deletions frontend/public/components/graphs/stack.tsx
Expand Up @@ -120,7 +120,7 @@ export const StackChart: React.FC<AreaChartProps> = ({
padding={padding}
theme={theme}
>
{xAxis && <ChartAxis tickCount={tickCount} tickFormat={formatDate} />}
{xAxis && <ChartAxis tickCount={tickCount} tickFormat={(tick) => formatDate(tick)} />}
{yAxis && <ChartAxis dependentAxis tickCount={tickCount} tickFormat={tickFormat} />}
<ChartStack height={height} width={width}>
{processedData.map((datum, index) => (
Expand Down Expand Up @@ -160,7 +160,7 @@ export const Stack: React.FC<StackProps> = ({
timeout,
timespan,
});
const data = getRangeVectorStats(utilization, description, null);
const data = getRangeVectorStats(utilization, description);
const ChartComponent = data?.length === 1 ? AreaChart : StackChart;
return <ChartComponent data={data} loading={loading} query={query} {...rest} />;
};
Expand Down

0 comments on commit 7d4e944

Please sign in to comment.