Skip to content

Commit

Permalink
[APM] Add transaction name filter in latency threshold rule (#154241)
Browse files Browse the repository at this point in the history
Part of the #152329

1. Adds a synthrace scenario that generates many transactions per
service
2. Fixes the duration chart preview when selecting All option -
#152195
3. Introduces the `Transaction name` filter in the rule type. 


### Pages loading the rule type
1. APM
2. Alert
3. Management rule 


### Consider 
- [ ] Update/Adding documentation example
https://www.elastic.co/guide/en/kibana/master/apm-alerts.html#apm-create-transaction-alert
## Creating a rule

https://user-images.githubusercontent.com/3369346/231740745-425c8eb8-6798-4ce4-b375-4ef96afdb334.mov

## Updating a rule


https://user-images.githubusercontent.com/3369346/231742035-28f50dfd-64bb-475d-b037-331eea4188d7.mov


### Before



https://user-images.githubusercontent.com/3369346/232799038-2edaa199-b970-48f2-b3ca-728273e4bf44.mov



### Notes

#### Feedback
The default action messages don't include any links. I will create a
follow-up ticket to improve the messages with actionable links.

Related bugs but out of the scope of the PR

- #154818
- #154704

---------

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
kpatticha and kibanamachine authored Apr 20, 2023
1 parent 79969fb commit 7c70508
Show file tree
Hide file tree
Showing 17 changed files with 276 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { ApmFields, apm, Instance } from '@kbn/apm-synthtrace-client';
import { Scenario } from '../cli/scenario';
import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment';

const ENVIRONMENT = getSynthtraceEnvironment(__filename);

const scenario: Scenario<ApmFields> = async (runOptions) => {
const { logger } = runOptions;
const { numServices = 3 } = runOptions.scenarioOpts || {};
const numTransactions = 100;

return {
generate: ({ range }) => {
const urls = ['GET /order', 'POST /basket', 'DELETE /basket', 'GET /products'];

const successfulTimestamps = range.ratePerMinute(180);
const failedTimestamps = range.interval('1m').rate(180);

const instances = [...Array(numServices).keys()].map((index) =>
apm
.service({ name: `synth-go-${index}`, environment: ENVIRONMENT, agentName: 'go' })
.instance(`instance-${index}`)
);

const transactionNames = [...Array(numTransactions).keys()].map(
(index) => `${urls[index % urls.length]}/${index}`
);

const instanceSpans = (instance: Instance, transactionName: string) => {
const successfulTraceEvents = successfulTimestamps.generator((timestamp) =>
instance.transaction({ transactionName }).timestamp(timestamp).duration(1000).success()
);

const failedTraceEvents = failedTimestamps.generator((timestamp) =>
instance
.transaction({ transactionName })
.timestamp(timestamp)
.duration(1000)
.failure()
.errors(
instance
.error({ message: '[ResponseError] index_not_found_exception' })
.timestamp(timestamp + 50)
)
);

const metricsets = range
.interval('30s')
.rate(1)
.generator((timestamp) =>
instance
.appMetrics({
'system.memory.actual.free': 800,
'system.memory.total': 1000,
'system.cpu.total.norm.pct': 0.6,
'system.process.cpu.total.norm.pct': 0.7,
})
.timestamp(timestamp)
);

return [successfulTraceEvents, failedTraceEvents, metricsets];
};

return logger.perf('generating_apm_events', () =>
instances
.flatMap((instance) =>
transactionNames.map((transactionName) => ({ instance, transactionName }))
)
.flatMap(({ instance, transactionName }, index) =>
instanceSpans(instance, transactionName)
)
);
},
};
};

export default scenario;
3 changes: 2 additions & 1 deletion x-pack/plugins/apm/common/rules/default_action_message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export const transactionDurationMessage = i18n.translate(
defaultMessage: `\\{\\{alertName\\}\\} alert is firing because of the following conditions:
- Service name: \\{\\{context.serviceName\\}\\}
- Type: \\{\\{context.transactionType\\}\\}
- Transaction type: \\{\\{context.transactionType\\}\\}
- Transaction name: \\{\\{context.transactionName\\}\\}
- Environment: \\{\\{context.environment\\}\\}
- Latency threshold: \\{\\{context.threshold\\}\\}ms
- Latency observed: \\{\\{context.triggerValue\\}\\} over the last \\{\\{context.interval\\}\\}`,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/apm/common/rules/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const errorCountParamsSchema = schema.object({
export const transactionDurationParamsSchema = schema.object({
serviceName: schema.maybe(schema.string()),
transactionType: schema.maybe(schema.string()),
transactionName: schema.maybe(schema.string()),
windowSize: schema.number(),
windowUnit: schema.string(),
threshold: schema.number(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,18 @@ import { CoreStart } from '@kbn/core/public';
import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
import { RuleParams, TransactionDurationRuleType } from '.';
import { AggregationType } from '../../../../../common/rules/apm_rule_types';
import { AlertMetadata } from '../../utils/helper';
import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values';

const KibanaReactContext = createKibanaReactContext({
notifications: { toasts: { add: () => {} } },
} as unknown as Partial<CoreStart>);

interface Args {
ruleParams: RuleParams;
metadata?: AlertMetadata;
}

export default {
title: 'alerting/TransactionDurationRuleType',
component: TransactionDurationRuleType,
Expand All @@ -32,16 +39,48 @@ export default {
],
};

export const Example: Story = () => {
const [params, setParams] = useState<RuleParams>({
export const CreatingInApmServiceOverview: Story<Args> = ({
ruleParams,
metadata,
}) => {
const [params, setParams] = useState<RuleParams>(ruleParams);

function setRuleParams(property: string, value: any) {
setParams({ ...params, [property]: value });
}

return (
<TransactionDurationRuleType
ruleParams={params}
metadata={metadata}
setRuleParams={setRuleParams}
setRuleProperty={() => {}}
/>
);
};

CreatingInApmServiceOverview.args = {
ruleParams: {
aggregationType: AggregationType.Avg,
environment: 'testEnvironment',
serviceName: 'testServiceName',
threshold: 1500,
transactionType: 'testTransactionType',
transactionName: 'GET /api/customer/:id',
windowSize: 5,
windowUnit: 'm',
});
},
metadata: {
environment: ENVIRONMENT_ALL.value,
serviceName: undefined,
},
};

export const CreatingInStackManagement: Story<Args> = ({
ruleParams,
metadata,
}) => {
const [params, setParams] = useState<RuleParams>(ruleParams);

function setRuleParams(property: string, value: any) {
setParams({ ...params, [property]: value });
Expand All @@ -50,8 +89,20 @@ export const Example: Story = () => {
return (
<TransactionDurationRuleType
ruleParams={params}
metadata={metadata}
setRuleParams={setRuleParams}
setRuleProperty={() => {}}
/>
);
};

CreatingInStackManagement.args = {
ruleParams: {
aggregationType: AggregationType.Avg,
environment: 'testEnvironment',
threshold: 1500,
windowSize: 5,
windowUnit: 'm',
},
metadata: undefined,
};
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
IsAboveField,
ServiceField,
TransactionTypeField,
TransactionNameField,
} from '../../utils/fields';
import { AlertMetadata, getIntervalAndTimeRange } from '../../utils/helper';
import { ApmRuleParamsContainer } from '../../ui_components/apm_rule_params_container';
Expand All @@ -38,9 +39,10 @@ import { PopoverExpression } from '../../ui_components/popover_expression';
export interface RuleParams {
aggregationType: AggregationType;
environment: string;
serviceName: string;
threshold: number;
transactionType: string;
transactionType?: string;
transactionName?: string;
serviceName?: string;
windowSize: number;
windowUnit: string;
}
Expand Down Expand Up @@ -105,6 +107,7 @@ export function TransactionDurationRuleType(props: Props) {
environment: params.environment,
serviceName: params.serviceName,
transactionType: params.transactionType,
transactionName: params.transactionName,
interval,
start,
end,
Expand All @@ -119,6 +122,7 @@ export function TransactionDurationRuleType(props: Props) {
params.environment,
params.serviceName,
params.transactionType,
params.transactionName,
params.windowSize,
params.windowUnit,
]
Expand Down Expand Up @@ -149,7 +153,8 @@ export function TransactionDurationRuleType(props: Props) {
onChange={(value) => {
if (value !== params.serviceName) {
setRuleParams('serviceName', value);
setRuleParams('transactionType', '');
setRuleParams('transactionType', undefined);
setRuleParams('transactionName', undefined);
setRuleParams('environment', ENVIRONMENT_ALL.value);
}
}}
Expand All @@ -164,6 +169,11 @@ export function TransactionDurationRuleType(props: Props) {
onChange={(value) => setRuleParams('environment', value)}
serviceName={params.serviceName}
/>,
<TransactionNameField
currentValue={params.transactionName}
onChange={(value) => setRuleParams('transactionName', value)}
serviceName={params.serviceName}
/>,
<PopoverExpression
value={params.aggregationType}
title={i18n.translate('xpack.apm.transactionDurationRuleType.when', {
Expand Down
45 changes: 43 additions & 2 deletions x-pack/plugins/apm/public/components/alerting/utils/fields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
SERVICE_ENVIRONMENT,
SERVICE_NAME,
TRANSACTION_TYPE,
TRANSACTION_NAME,
} from '../../../../common/es_fields/apm';
import {
ENVIRONMENT_ALL,
Expand Down Expand Up @@ -39,7 +40,7 @@ export function ServiceField({
>
<SuggestionsSelect
customOptions={
allowAll ? [{ label: allOptionText, value: '' }] : undefined
allowAll ? [{ label: allOptionText, value: undefined }] : undefined
}
customOptionText={i18n.translate(
'xpack.apm.serviceNamesSelectCustomOptionText',
Expand Down Expand Up @@ -98,6 +99,46 @@ export function EnvironmentField({
);
}

export function TransactionNameField({
currentValue,
onChange,
serviceName,
}: {
currentValue?: string;
onChange: (value?: string) => void;
serviceName?: string;
}) {
const label = i18n.translate('xpack.apm.alerting.fields.transaction.name', {
defaultMessage: 'Name',
});

return (
<PopoverExpression value={currentValue || allOptionText} title={label}>
<SuggestionsSelect
customOptions={[{ label: allOptionText, value: undefined }]}
customOptionText={i18n.translate(
'xpack.apm.alerting.transaction.name.custom.text',
{
defaultMessage: 'Add \\{searchValue\\} as a new transaction name',
}
)}
defaultValue={currentValue}
fieldName={TRANSACTION_NAME}
onChange={onChange}
placeholder={i18n.translate(
'xpack.apm.transactionNamesSelectPlaceholder',
{
defaultMessage: 'Select transaction name',
}
)}
start={moment().subtract(24, 'h').toISOString()}
end={moment().toISOString()}
serviceName={serviceName}
/>
</PopoverExpression>
);
}

export function TransactionTypeField({
currentValue,
onChange,
Expand All @@ -113,7 +154,7 @@ export function TransactionTypeField({
return (
<PopoverExpression value={currentValue || allOptionText} title={label}>
<SuggestionsSelect
customOptions={[{ label: allOptionText, value: '' }]}
customOptions={[{ label: allOptionText, value: undefined }]}
customOptionText={i18n.translate(
'xpack.apm.transactionTypesSelectCustomOptionText',
{
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/apm/server/routes/alerts/action_variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ export const apmActionVariables = {
),
name: 'transactionType' as const,
},
transactionName: {
description: i18n.translate(
'xpack.apm.alerts.action_variables.transactionName',
{ defaultMessage: 'The transaction name the alert is created for' }
),
name: 'transactionName' as const,
},
triggerValue: {
description: i18n.translate(
'xpack.apm.alerts.action_variables.triggerValue',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
SERVICE_ENVIRONMENT,
SERVICE_LANGUAGE_NAME,
SERVICE_NAME,
TRANSACTION_NAME,
TRANSACTION_TYPE,
} from '../../../common/es_fields/apm';
import { registerTransactionDurationRuleType } from './rule_types/transaction_duration/register_transaction_duration_rule_type';
Expand All @@ -45,6 +46,10 @@ export const apmRuleTypeAlertFieldMap = {
type: 'keyword',
required: false,
},
[TRANSACTION_NAME]: {
type: 'keyword',
required: false,
},
[PROCESSOR_EVENT]: {
type: 'keyword',
required: false,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/apm/server/routes/alerts/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const alertParamsRt = t.intersection([
]),
serviceName: t.string,
transactionType: t.string,
transactionName: t.string,
}),
environmentRt,
rangeRt,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,7 @@ export function registerErrorCountRuleType({
},
},
{ term: { [PROCESSOR_EVENT]: ProcessorEvent.error } },
...termQuery(SERVICE_NAME, ruleParams.serviceName, {
queryEmptyString: false,
}),
...termQuery(SERVICE_NAME, ruleParams.serviceName),
...environmentQuery(ruleParams.environment),
],
},
Expand Down
Loading

0 comments on commit 7c70508

Please sign in to comment.