Skip to content

Commit

Permalink
[APM][AWS] fix compute usage calc (#146328)
Browse files Browse the repository at this point in the history
closes #146206

**Before** we were averaging the memory and billed duration and then we
calculated the compute usage.
**Now** We first calculate the compute usage then get the average and
then convert to GB-Sec.

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
cauemarcondes and kibanamachine authored Nov 29, 2022
1 parent 6e4f22d commit 9cadd36
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ import { environmentQuery } from '../../../../common/utils/environment_query';
import { getMetricsDateHistogramParams } from '../../../lib/helpers/metrics';
import { GenericMetricsChart } from '../fetch_and_transform_metrics';
import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client';
import { calcComputeUsageGBSeconds } from './helper';
import { convertComputeUsageToGbSec } from './helper';

export const computeUsageAvgScript = {
avg: {
script: `return doc['${METRIC_SYSTEM_TOTAL_MEMORY}'].value * doc['${FAAS_BILLED_DURATION}'].value`,
},
};

export async function getComputeUsageChart({
environment,
Expand All @@ -47,9 +53,8 @@ export async function getComputeUsageChart({
serverlessId?: string;
}): Promise<GenericMetricsChart> {
const aggs = {
avgFaasBilledDuration: { avg: { field: FAAS_BILLED_DURATION } },
avgTotalMemory: { avg: { field: METRIC_SYSTEM_TOTAL_MEMORY } },
countInvocations: { value_count: { field: FAAS_BILLED_DURATION } },
avgComputeUsageBytesMs: computeUsageAvgScript,
};

const params = {
Expand Down Expand Up @@ -117,17 +122,16 @@ export async function getComputeUsageChart({
key: 'compute_usage',
type: 'bar',
overallValue:
calcComputeUsageGBSeconds({
billedDuration: aggregations?.avgFaasBilledDuration.value,
totalMemory: aggregations?.avgTotalMemory.value,
convertComputeUsageToGbSec({
computeUsageBytesMs:
aggregations?.avgComputeUsageBytesMs.value,
countInvocations: aggregations?.countInvocations.value,
}) ?? 0,
color: theme.euiColorVis0,
data: timeseriesData.buckets.map((bucket) => {
const computeUsage =
calcComputeUsageGBSeconds({
billedDuration: bucket.avgFaasBilledDuration.value,
totalMemory: bucket.avgTotalMemory.value,
convertComputeUsageToGbSec({
computeUsageBytesMs: bucket.avgComputeUsageBytesMs.value,
countInvocations: bucket.countInvocations.value,
}) ?? 0;
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ import {
} from '../../../../common/es_fields/apm';
import { environmentQuery } from '../../../../common/utils/environment_query';
import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client';
import { calcEstimatedCost, calcMemoryUsedRate } from './helper';
import { computeUsageAvgScript } from './get_compute_usage_chart';
import {
calcEstimatedCost,
calcMemoryUsedRate,
convertComputeUsageToGbSec,
} from './helper';

export type AwsLambdaArchitecture = 'arm' | 'x86_64';

Expand Down Expand Up @@ -121,6 +126,7 @@ export async function getServerlessSummary({
avgTotalMemory: { avg: { field: METRIC_SYSTEM_TOTAL_MEMORY } },
avgFreeMemory: { avg: { field: METRIC_SYSTEM_FREE_MEMORY } },
countInvocations: { value_count: { field: FAAS_BILLED_DURATION } },
avgComputeUsageBytesMs: computeUsageAvgScript,
sample: {
top_metrics: {
metrics: [{ field: HOST_ARCHITECTURE }],
Expand Down Expand Up @@ -159,9 +165,11 @@ export async function getServerlessSummary({
HOST_ARCHITECTURE
] as AwsLambdaArchitecture | undefined,
transactionThroughput,
billedDuration: response.aggregations?.faasBilledDurationAvg.value,
totalMemory: response.aggregations?.avgTotalMemory.value,
countInvocations: response.aggregations?.countInvocations.value,
computeUsageGbSec: convertComputeUsageToGbSec({
computeUsageBytesMs:
response.aggregations?.avgComputeUsageBytesMs.value,
countInvocations: response.aggregations?.countInvocations.value,
}),
}),
};
}
56 changes: 44 additions & 12 deletions x-pack/plugins/apm/server/routes/metrics/serverless/helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,32 @@ import {
calcMemoryUsed,
calcMemoryUsedRate,
calcEstimatedCost,
convertComputeUsageToGbSec,
} from './helper';

describe('convertComputeUsageToGbSec', () => {
it('returns undefined', () => {
[
{ computeUsageBytesMs: undefined, countInvocations: 1 },
{ computeUsageBytesMs: null, countInvocations: 1 },
{ computeUsageBytesMs: 1, countInvocations: undefined },
{ computeUsageBytesMs: 1, countInvocations: null },
].forEach(({ computeUsageBytesMs, countInvocations }) => {
expect(
convertComputeUsageToGbSec({ computeUsageBytesMs, countInvocations })
).toBeUndefined();
});
});

it('converts to gb sec', () => {
const totalMemory = 536870912; // 0.5gb
const billedDuration = 4000;
const computeUsageBytesMs = totalMemory * billedDuration;
expect(
convertComputeUsageToGbSec({ computeUsageBytesMs, countInvocations: 1 })
).toBe(computeUsageBytesMs / 1024 ** 3 / 1000);
});
});
describe('calcMemoryUsed', () => {
it('returns undefined when memory values are no a number', () => {
[
Expand Down Expand Up @@ -49,24 +74,29 @@ const AWS_LAMBDA_PRICE_FACTOR = {
};

describe('calcEstimatedCost', () => {
const totalMemory = 536870912; // 0.5gb
const billedDuration = 4000;
const computeUsageBytesMs = totalMemory * billedDuration;
const computeUsageGbSec = convertComputeUsageToGbSec({
computeUsageBytesMs,
countInvocations: 1,
});
it('returns undefined when price factor is not defined', () => {
expect(
calcEstimatedCost({
totalMemory: 1,
billedDuration: 1,
transactionThroughput: 1,
architecture: 'arm',
computeUsageGbSec,
})
).toBeUndefined();
});

it('returns undefined when architecture is not defined', () => {
expect(
calcEstimatedCost({
totalMemory: 1,
billedDuration: 1,
transactionThroughput: 1,
awsLambdaPriceFactor: AWS_LAMBDA_PRICE_FACTOR,
computeUsageGbSec,
})
).toBeUndefined();
});
Expand All @@ -84,11 +114,10 @@ describe('calcEstimatedCost', () => {
it('returns undefined when request cost per million is not defined', () => {
expect(
calcEstimatedCost({
totalMemory: 1,
billedDuration: 1,
transactionThroughput: 1,
awsLambdaPriceFactor: AWS_LAMBDA_PRICE_FACTOR,
architecture: 'arm',
computeUsageGbSec,
})
).toBeUndefined();
});
Expand All @@ -100,27 +129,30 @@ describe('calcEstimatedCost', () => {
calcEstimatedCost({
awsLambdaPriceFactor: AWS_LAMBDA_PRICE_FACTOR,
architecture,
billedDuration: 4000,
totalMemory: 536870912, // 0.5gb
transactionThroughput: 100000,
awsLambdaRequestCostPerMillion: 0.2,
countInvocations: 1,
computeUsageGbSec,
})
).toEqual(0.03);
});
});
describe('arm architecture', () => {
const architecture = 'arm';
it('returns correct cost', () => {
const _totalMemory = 536870912; // 0.5gb
const _billedDuration = 8000;
const _computeUsageBytesMs = _totalMemory * _billedDuration;
const _computeUsageGbSec = convertComputeUsageToGbSec({
computeUsageBytesMs: _computeUsageBytesMs,
countInvocations: 1,
});
expect(
calcEstimatedCost({
awsLambdaPriceFactor: AWS_LAMBDA_PRICE_FACTOR,
architecture,
billedDuration: 8000,
totalMemory: 536870912, // 0.5gb
transactionThroughput: 200000,
awsLambdaRequestCostPerMillion: 0.2,
countInvocations: 1,
computeUsageGbSec: _computeUsageGbSec,
})
).toEqual(0.05);
});
Expand Down
34 changes: 10 additions & 24 deletions x-pack/plugins/apm/server/routes/metrics/serverless/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,65 +44,51 @@ const GB = 1024 ** 3;
* But the result of this calculation is in Bytes-milliseconds, as the "system.memory.total" is stored in bytes and the "faas.billed_duration" is stored in milliseconds.
* But to calculate the overall cost AWS uses GB-second, so we need to convert the result to this unit.
*/
export function calcComputeUsageGBSeconds({
billedDuration,
totalMemory,
export function convertComputeUsageToGbSec({
computeUsageBytesMs,
countInvocations,
}: {
billedDuration?: number | null;
totalMemory?: number | null;
computeUsageBytesMs?: number | null;
countInvocations?: number | null;
}) {
if (
!isFiniteNumber(billedDuration) ||
!isFiniteNumber(totalMemory) ||
!isFiniteNumber(computeUsageBytesMs) ||
!isFiniteNumber(countInvocations)
) {
return undefined;
}

const totalMemoryGB = totalMemory / GB;
const faasBilledDurationSec = billedDuration / 1000;
return totalMemoryGB * faasBilledDurationSec * countInvocations;
const computeUsageGbSec = computeUsageBytesMs / GB / 1000;
return computeUsageGbSec * countInvocations;
}

export function calcEstimatedCost({
awsLambdaPriceFactor,
architecture,
transactionThroughput,
billedDuration,
totalMemory,
awsLambdaRequestCostPerMillion,
countInvocations,
computeUsageGbSec,
}: {
awsLambdaPriceFactor?: AWSLambdaPriceFactor;
architecture?: AwsLambdaArchitecture;
transactionThroughput: number;
billedDuration?: number | null;
totalMemory?: number | null;
awsLambdaRequestCostPerMillion?: number;
countInvocations?: number | null;
computeUsageGbSec?: number;
}) {
try {
const computeUsage = calcComputeUsageGBSeconds({
billedDuration,
totalMemory,
countInvocations,
});
if (
!awsLambdaPriceFactor ||
!architecture ||
!isFiniteNumber(awsLambdaRequestCostPerMillion) ||
!isFiniteNumber(awsLambdaPriceFactor?.[architecture]) ||
!isFiniteNumber(computeUsage)
!isFiniteNumber(computeUsageGbSec)
) {
return undefined;
}

const priceFactor = awsLambdaPriceFactor?.[architecture];

const estimatedCost =
computeUsage * priceFactor +
computeUsageGbSec * priceFactor +
transactionThroughput * (awsLambdaRequestCostPerMillion / 1000000);

// Rounds up the decimals
Expand Down

0 comments on commit 9cadd36

Please sign in to comment.