Skip to content

Commit

Permalink
Merge branch 'main' into issue-145097-extend-session
Browse files Browse the repository at this point in the history
  • Loading branch information
azasypkin committed Nov 24, 2022
2 parents b49e182 + 2a69707 commit 80e1cd6
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 84 deletions.
3 changes: 3 additions & 0 deletions src/plugins/data/common/search/aggs/buckets/histogram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ export const getHistogramBucketAgg = ({
})
.catch((e: Error) => {
if (e.name === 'AbortError') return;
if (e.name === 'KQLSyntaxError') {
throw e;
}
throw new Error(
i18n.translate('data.search.aggs.histogram.missingMaxMinValuesWarning', {
defaultMessage:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from '@elastic/eui';

import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';

import { AnalyticsCollection } from '../../../../../common/types/analytics';
import { getEnterpriseSearchUrl } from '../../../shared/enterprise_search_url';
Expand Down Expand Up @@ -99,15 +100,13 @@ export const AnalyticsCollectionIntegrate: React.FC<AnalyticsCollectionIntegrate

<EuiSpacer size="l" />
<EuiText size="s">
<p>
{i18n.translate(
'xpack.enterpriseSearch.analytics.collections.collectionsView.integrateTab.scriptDescription',
{
defaultMessage:
'Track individual events, like clicks, by calling the <strong>trackEvent</strong> method.',
}
)}
</p>
<FormattedMessage
id="xpack.enterpriseSearch.analytics.collections.collectionsView.integrateTab.scriptDescription"
defaultMessage="Track individual events, like clicks, by calling the {trackEvent} method."
values={{
trackEvent: <strong>trackEvent</strong>,
}}
/>
</EuiText>
<EuiSpacer size="s" />
<EuiCodeBlock language="js" isCopyable>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import { computeErrorBudget } from './compute_error_budget';
import { toDateRange } from './date_range';

describe('computeErrorBudget', () => {
describe('for occurrences based SLO', () => {
describe('with rolling time window', () => {
describe('for rolling time window', () => {
describe('for occurrences budgeting method', () => {
it('computes the error budget', () => {
const slo = createSLO({
budgeting_method: 'occurrences',
Expand All @@ -32,58 +32,61 @@ describe('computeErrorBudget', () => {
initial: 0.05,
consumed: 0.6,
remaining: 0.4,
is_estimated: false,
});
});
});

describe('with calendar aligned time window', () => {
describe('for timeslices budgeting method', () => {
it('computes the error budget', () => {
const slo = createSLO({
budgeting_method: 'occurrences',
time_window: weeklyCalendarAligned(twoDaysAgo()),
objective: { target: 0.95 },
budgeting_method: 'timeslices',
time_window: sevenDaysRolling(),
objective: { target: 0.95, timeslice_target: 0.95, timeslice_window: oneMinute() },
});
const dateRange = toDateRange(slo.time_window);
// 7 days sliced in 1m buckets = 10,080 slices
const errorBudget = computeErrorBudget(slo, {
good: 97,
total: 100,
good: 9987,
total: 10080,
date_range: dateRange,
});

expect(errorBudget).toEqual({
initial: 0.05,
consumed: 0.6,
remaining: 0.4,
consumed: 0.184524,
remaining: 0.815476,
is_estimated: false,
});
});
});
});

describe('for timeslices based SLO', () => {
describe('with rolling time window', () => {
it('computes the error budget', () => {
describe('for calendar aligned time window', () => {
describe('for occurrences budgeting method', () => {
it('computes the error budget with an estimation of total events', () => {
const slo = createSLO({
budgeting_method: 'timeslices',
time_window: sevenDaysRolling(),
objective: { target: 0.95, timeslice_target: 0.95, timeslice_window: oneMinute() },
budgeting_method: 'occurrences',
time_window: weeklyCalendarAligned(twoDaysAgo()),
objective: { target: 0.95 },
});
const dateRange = toDateRange(slo.time_window);
// 7 days sliced in 1m buckets = 10,080 slices
const errorBudget = computeErrorBudget(slo, {
good: 9987,
total: 10080,
good: 97,
total: 100,
date_range: dateRange,
});

expect(errorBudget).toEqual({
initial: 0.05,
consumed: 0.184524,
remaining: 0.815476,
consumed: 0.171429,
remaining: 0.828571,
is_estimated: true,
});
});
});

describe('with calendar aligned time window', () => {
describe('for timeslices budgeting method', () => {
it('computes the error budget', () => {
const slo = createSLO({
budgeting_method: 'timeslices',
Expand All @@ -105,6 +108,7 @@ describe('computeErrorBudget', () => {
initial: 0.05,
consumed: 0.113095,
remaining: 0.886905,
is_estimated: false,
});
});
});
Expand All @@ -119,10 +123,11 @@ describe('computeErrorBudget', () => {
initial: 0.001, // 0.1%
consumed: 0, // 0% consumed
remaining: 1, // 100% remaining
is_estimated: false,
});
});

it("computes the error budget when 'good > total' events", () => {
it("returns default values when 'good >= total' events", () => {
const slo = createSLO();
const dateRange = toDateRange(slo.time_window);
const errorBudget = computeErrorBudget(slo, { good: 9999, total: 9, date_range: dateRange });
Expand All @@ -131,6 +136,7 @@ describe('computeErrorBudget', () => {
initial: 0.001,
consumed: 0,
remaining: 1,
is_estimated: false,
});
});

Expand All @@ -143,6 +149,7 @@ describe('computeErrorBudget', () => {
initial: 0.001,
consumed: 0,
remaining: 1,
is_estimated: false,
});
});

Expand All @@ -155,6 +162,7 @@ describe('computeErrorBudget', () => {
initial: 0.001,
consumed: 1,
remaining: 0,
is_estimated: false,
});
});

Expand All @@ -167,6 +175,7 @@ describe('computeErrorBudget', () => {
initial: 0.001,
consumed: 571.428571, // i.e. 57,142% consumed
remaining: 0,
is_estimated: false,
});
});

Expand All @@ -179,6 +188,7 @@ describe('computeErrorBudget', () => {
initial: 0.001,
consumed: 1000, // i.e. 100,000% consumed
remaining: 0,
is_estimated: false,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,86 @@ import moment from 'moment';
import { ErrorBudget, IndicatorData, SLO, toMomentUnitOfTime } from '../models';
import {
calendarAlignedTimeWindowSchema,
occurrencesBudgetingMethodSchema,
rollingTimeWindowSchema,
timeslicesBudgetingMethodSchema,
} from '../../types/schema';
import { toHighPrecision } from '../../utils/number';

// More details about calculus: https://github.com/elastic/kibana/issues/143980
export function computeErrorBudget(slo: SLO, sliData: IndicatorData): ErrorBudget {
const { good, total, date_range: dateRange } = sliData;
const initialErrorBudget = toHighPrecision(1 - slo.objective.target);
const { good, total } = sliData;
if (total === 0 || good >= total) {
return {
initial: initialErrorBudget,
consumed: 0,
remaining: 1,
};
const initialErrorBudget = 1 - slo.objective.target;
return toErrorBudget(initialErrorBudget, 0);
}

if (
timeslicesBudgetingMethodSchema.is(slo.budgeting_method) &&
calendarAlignedTimeWindowSchema.is(slo.time_window)
) {
const dateRangeDurationInUnit = moment(dateRange.to).diff(
dateRange.from,
toMomentUnitOfTime(slo.objective.timeslice_window!.unit)
);
const totalSlices = Math.ceil(dateRangeDurationInUnit / slo.objective.timeslice_window!.value);

const consumedErrorBudget = toHighPrecision(
(total - good) / (totalSlices * initialErrorBudget)
);
const remainingErrorBudget = Math.max(toHighPrecision(1 - consumedErrorBudget), 0);
return {
initial: initialErrorBudget,
consumed: consumedErrorBudget,
remaining: remainingErrorBudget,
};
if (rollingTimeWindowSchema.is(slo.time_window)) {
return computeForRolling(slo, sliData);
}

const consumedErrorBudget = toHighPrecision((total - good) / (total * initialErrorBudget));
const remainingErrorBudget = Math.max(toHighPrecision(1 - consumedErrorBudget), 0);
if (calendarAlignedTimeWindowSchema.is(slo.time_window)) {
if (timeslicesBudgetingMethodSchema.is(slo.budgeting_method)) {
return computeForCalendarAlignedWithTimeslices(slo, sliData);
}

if (occurrencesBudgetingMethodSchema.is(slo.budgeting_method)) {
return computeForCalendarAlignedWithOccurrences(slo, sliData);
}
}

throw new Error('Invalid slo time window');
}

function computeForRolling(slo: SLO, sliData: IndicatorData) {
const { good, total } = sliData;
const initialErrorBudget = 1 - slo.objective.target;
const consumedErrorBudget = (total - good) / (total * initialErrorBudget);
return toErrorBudget(initialErrorBudget, consumedErrorBudget);
}

function computeForCalendarAlignedWithOccurrences(slo: SLO, sliData: IndicatorData) {
const { good, total, date_range: dateRange } = sliData;
const initialErrorBudget = 1 - slo.objective.target;
const now = moment();

const durationCalendarPeriod = moment(dateRange.to).diff(dateRange.from, 'minutes');
const durationSinceBeginning = now.isAfter(dateRange.to)
? durationCalendarPeriod
: moment(now).diff(dateRange.from, 'minutes');

const totalEventsEstimatedAtPeriodEnd = Math.round(
(total / durationSinceBeginning) * durationCalendarPeriod
);

const consumedErrorBudget =
(total - good) / (totalEventsEstimatedAtPeriodEnd * initialErrorBudget);
return toErrorBudget(initialErrorBudget, consumedErrorBudget, true);
}

function computeForCalendarAlignedWithTimeslices(slo: SLO, sliData: IndicatorData) {
const { good, total, date_range: dateRange } = sliData;
const initialErrorBudget = 1 - slo.objective.target;

const dateRangeDurationInUnit = moment(dateRange.to).diff(
dateRange.from,
toMomentUnitOfTime(slo.objective.timeslice_window!.unit)
);
const totalSlices = Math.ceil(dateRangeDurationInUnit / slo.objective.timeslice_window!.value);
const consumedErrorBudget = (total - good) / (totalSlices * initialErrorBudget);

return toErrorBudget(initialErrorBudget, consumedErrorBudget);
}

function toErrorBudget(
initial: number,
consumed: number,
isEstimated: boolean = false
): ErrorBudget {
return {
initial: initialErrorBudget,
consumed: consumedErrorBudget,
remaining: remainingErrorBudget,
initial: toHighPrecision(initial),
consumed: toHighPrecision(consumed),
remaining: Math.max(toHighPrecision(1 - consumed), 0),
is_estimated: isEstimated,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ describe('GetSLO', () => {
initial: 0.001,
consumed: 0.1,
remaining: 0.9,
is_estimated: false,
},
},
created_at: slo.created_at.toISOString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { toDateRange } from '../../domain/services/date_range';
import { InternalQueryError } from '../../errors';
import { DateRange, Duration, IndicatorData, SLO } from '../../domain/models';
import {
occurencesBudgetingMethodSchema,
occurrencesBudgetingMethodSchema,
timeslicesBudgetingMethodSchema,
} from '../../types/schema';

Expand All @@ -44,7 +44,7 @@ export class DefaultSLIClient implements SLIClient {

async fetchCurrentSLIData(slo: SLO): Promise<IndicatorData> {
const dateRange = toDateRange(slo.time_window);
if (occurencesBudgetingMethodSchema.is(slo.budgeting_method)) {
if (occurrencesBudgetingMethodSchema.is(slo.budgeting_method)) {
const result = await this.esClient.search<unknown, Record<AggKey, AggregationsSumAggregate>>({
...commonQuery(slo, dateRange),
aggs: {
Expand Down Expand Up @@ -117,7 +117,7 @@ export class DefaultSLIClient implements SLIClient {
is_rolling: true,
});

if (occurencesBudgetingMethodSchema.is(slo.budgeting_method)) {
if (occurrencesBudgetingMethodSchema.is(slo.budgeting_method)) {
const result = await this.esClient.search<unknown, EsAggregations>({
...commonQuery(slo, longestDateRange),
aggs: toLookbackWindowsAggregationsQuery(sortedLookbackWindows),
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/observability/server/types/schema/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const errorBudgetSchema = t.type({
initial: t.number,
consumed: t.number,
remaining: t.number,
is_estimated: t.boolean,
});

const dateRangeSchema = t.type({ from: dateType, to: dateType });
Expand Down
6 changes: 3 additions & 3 deletions x-pack/plugins/observability/server/types/schema/slo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import { durationType } from './duration';
import { indicatorSchema } from './indicators';
import { timeWindowSchema } from './time_window';

const occurencesBudgetingMethodSchema = t.literal<string>('occurrences');
const occurrencesBudgetingMethodSchema = t.literal<string>('occurrences');
const timeslicesBudgetingMethodSchema = t.literal<string>('timeslices');

const budgetingMethodSchema = t.union([
occurencesBudgetingMethodSchema,
occurrencesBudgetingMethodSchema,
timeslicesBudgetingMethodSchema,
]);

Expand All @@ -40,7 +40,7 @@ const sloSchema = t.type({
export {
budgetingMethodSchema,
objectiveSchema,
occurencesBudgetingMethodSchema,
occurrencesBudgetingMethodSchema,
sloSchema,
timeslicesBudgetingMethodSchema,
};
Loading

0 comments on commit 80e1cd6

Please sign in to comment.