diff --git a/.buildkite/pipelines/performance/nightly.yml b/.buildkite/pipelines/performance/nightly.yml new file mode 100644 index 0000000000000..aa52fb7a9335c --- /dev/null +++ b/.buildkite/pipelines/performance/nightly.yml @@ -0,0 +1,35 @@ +steps: + - block: ":gear: Performance Tests Configuration" + prompt: "Fill out the details for performance test" + fields: + - text: ":arrows_counterclockwise: Iterations" + key: "performance-test-iteration-count" + hint: "How many times you want to run tests? " + required: true + if: build.env('ITERATION_COUNT_ENV') == null + + - label: ":male-mechanic::skin-tone-2: Pre-Build" + command: .buildkite/scripts/lifecycle/pre_build.sh + + - wait + + - label: ":factory_worker: Build Kibana Distribution and Plugins" + command: .buildkite/scripts/steps/build_kibana.sh + agents: + queue: c2-16 + key: build + + - label: ":muscle: Performance Tests" + command: .buildkite/scripts/steps/functional/performance.sh + agents: + queue: ci-group-6 + depends_on: build + concurrency: 50 + concurrency_group: 'performance-test-group' + + - wait: ~ + continue_on_failure: true + + - label: ":male_superhero::skin-tone-2: Post-Build" + command: .buildkite/scripts/lifecycle/post_build.sh + diff --git a/.buildkite/scripts/pipelines/pull_request/pipeline.js b/.buildkite/scripts/pipelines/pull_request/pipeline.js index 78dc6e1b29b6d..028c90020a0b8 100644 --- a/.buildkite/scripts/pipelines/pull_request/pipeline.js +++ b/.buildkite/scripts/pipelines/pull_request/pipeline.js @@ -55,21 +55,20 @@ const uploadPipeline = (pipelineContent) => { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/base.yml', false)); if ( - await doAnyChangesMatch([ + (await doAnyChangesMatch([ /^x-pack\/plugins\/security_solution/, /^x-pack\/test\/security_solution_cypress/, /^x-pack\/plugins\/triggers_actions_ui\/public\/application\/sections\/action_connector_form/, /^x-pack\/plugins\/triggers_actions_ui\/public\/application\/context\/actions_connectors_context\.tsx/, - ]) || process.env.GITHUB_PR_LABELS.includes('ci:all-cypress-suites') + ])) || + process.env.GITHUB_PR_LABELS.includes('ci:all-cypress-suites') ) { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/security_solution.yml')); } - // Disabled for now, these are failing/disabled in Jenkins currently as well // if ( - // await doAnyChangesMatch([ - // /^x-pack\/plugins\/apm/, - // ]) || process.env.GITHUB_PR_LABELS.includes('ci:all-cypress-suites') + // (await doAnyChangesMatch([/^x-pack\/plugins\/apm/])) || + // process.env.GITHUB_PR_LABELS.includes('ci:all-cypress-suites') // ) { // pipeline.push(getPipeline('.buildkite/pipelines/pull_request/apm_cypress.yml')); // } diff --git a/.buildkite/scripts/steps/functional/performance.sh b/.buildkite/scripts/steps/functional/performance.sh new file mode 100644 index 0000000000000..2f1a77690d153 --- /dev/null +++ b/.buildkite/scripts/steps/functional/performance.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -uo pipefail + +if [ -z "${ITERATION_COUNT_ENV+x}" ]; then + ITERATION_COUNT="$(buildkite-agent meta-data get performance-test-iteration-count)" +else + ITERATION_COUNT=$ITERATION_COUNT_ENV +fi + +tput setab 2; tput setaf 0; echo "Performance test will be run at ${BUILDKITE_BRANCH} ${ITERATION_COUNT} times" + +cat << EOF | buildkite-agent pipeline upload +steps: + - command: .buildkite/scripts/steps/functional/performance_sub.sh + parallelism: "$ITERATION_COUNT" +EOF + + + diff --git a/.buildkite/scripts/steps/functional/performance_sub.sh b/.buildkite/scripts/steps/functional/performance_sub.sh new file mode 100644 index 0000000000000..d3e6c0ba7304e --- /dev/null +++ b/.buildkite/scripts/steps/functional/performance_sub.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/common/util.sh + +.buildkite/scripts/bootstrap.sh +.buildkite/scripts/download_build_artifacts.sh + +cd "$XPACK_DIR" + +echo --- Run Performance Tests +checks-reporter-with-killswitch "Run Performance Tests" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ + --config test/performance/config.ts; diff --git a/.ci/Dockerfile b/.ci/Dockerfile index d3ea74ca38969..29ed08c84b23e 100644 --- a/.ci/Dockerfile +++ b/.ci/Dockerfile @@ -1,7 +1,7 @@ # NOTE: This Dockerfile is ONLY used to run certain tasks in CI. It is not used to run Kibana or as a distributable. # If you're looking for the Kibana Docker image distributable, please see: src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts -ARG NODE_VERSION=14.17.6 +ARG NODE_VERSION=16.11.1 FROM node:${NODE_VERSION} AS base @@ -10,7 +10,7 @@ RUN apt-get update && \ libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 \ libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 \ libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 \ - libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget openjdk-8-jre && \ + libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget openjdk-11-jre && \ rm -rf /var/lib/apt/lists/* RUN curl -sSL https://dl.google.com/linux/linux_signing_key.pub | apt-key add - \ diff --git a/.eslintrc.js b/.eslintrc.js index 6f62f953c9a4c..c6aeb6f94255f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -156,6 +156,78 @@ const DEV_PATTERNS = [ 'x-pack/plugins/*/server/scripts/**/*', ]; +/** Restricted imports with suggested alternatives */ +const RESTRICTED_IMPORTS = [ + { + name: 'lodash', + importNames: ['set', 'setWith'], + message: 'Please use @elastic/safer-lodash-set instead', + }, + { + name: 'lodash.set', + message: 'Please use @elastic/safer-lodash-set instead', + }, + { + name: 'lodash.setwith', + message: 'Please use @elastic/safer-lodash-set instead', + }, + { + name: 'lodash/set', + message: 'Please use @elastic/safer-lodash-set instead', + }, + { + name: 'lodash/setWith', + message: 'Please use @elastic/safer-lodash-set instead', + }, + { + name: 'lodash/fp', + importNames: ['set', 'setWith', 'assoc', 'assocPath'], + message: 'Please use @elastic/safer-lodash-set instead', + }, + { + name: 'lodash/fp/set', + message: 'Please use @elastic/safer-lodash-set instead', + }, + { + name: 'lodash/fp/setWith', + message: 'Please use @elastic/safer-lodash-set instead', + }, + { + name: 'lodash/fp/assoc', + message: 'Please use @elastic/safer-lodash-set instead', + }, + { + name: 'lodash/fp/assocPath', + message: 'Please use @elastic/safer-lodash-set instead', + }, + { + name: 'lodash', + importNames: ['template'], + message: 'lodash.template is unsafe, and not compatible with our content security policy.', + }, + { + name: 'lodash.template', + message: 'lodash.template is unsafe, and not compatible with our content security policy.', + }, + { + name: 'lodash/template', + message: 'lodash.template is unsafe, and not compatible with our content security policy.', + }, + { + name: 'lodash/fp', + importNames: ['template'], + message: 'lodash.template is unsafe, and not compatible with our content security policy.', + }, + { + name: 'lodash/fp/template', + message: 'lodash.template is unsafe, and not compatible with our content security policy.', + }, + { + name: 'react-use', + message: 'Please use react-use/lib/{method} instead.', + }, +]; + module.exports = { root: true, @@ -671,81 +743,7 @@ module.exports = { 'no-restricted-imports': [ 2, { - paths: [ - { - name: 'lodash', - importNames: ['set', 'setWith'], - message: 'Please use @elastic/safer-lodash-set instead', - }, - { - name: 'lodash.set', - message: 'Please use @elastic/safer-lodash-set instead', - }, - { - name: 'lodash.setwith', - message: 'Please use @elastic/safer-lodash-set instead', - }, - { - name: 'lodash/set', - message: 'Please use @elastic/safer-lodash-set instead', - }, - { - name: 'lodash/setWith', - message: 'Please use @elastic/safer-lodash-set instead', - }, - { - name: 'lodash/fp', - importNames: ['set', 'setWith', 'assoc', 'assocPath'], - message: 'Please use @elastic/safer-lodash-set instead', - }, - { - name: 'lodash/fp/set', - message: 'Please use @elastic/safer-lodash-set instead', - }, - { - name: 'lodash/fp/setWith', - message: 'Please use @elastic/safer-lodash-set instead', - }, - { - name: 'lodash/fp/assoc', - message: 'Please use @elastic/safer-lodash-set instead', - }, - { - name: 'lodash/fp/assocPath', - message: 'Please use @elastic/safer-lodash-set instead', - }, - { - name: 'lodash', - importNames: ['template'], - message: - 'lodash.template is unsafe, and not compatible with our content security policy.', - }, - { - name: 'lodash.template', - message: - 'lodash.template is unsafe, and not compatible with our content security policy.', - }, - { - name: 'lodash/template', - message: - 'lodash.template is unsafe, and not compatible with our content security policy.', - }, - { - name: 'lodash/fp', - importNames: ['template'], - message: - 'lodash.template is unsafe, and not compatible with our content security policy.', - }, - { - name: 'lodash/fp/template', - message: - 'lodash.template is unsafe, and not compatible with our content security policy.', - }, - { - name: 'react-use', - message: 'Please use react-use/lib/{method} instead.', - }, - ], + paths: RESTRICTED_IMPORTS, }, ], 'no-restricted-modules': [ @@ -838,6 +836,23 @@ module.exports = { ], }, }, + { + files: ['**/common/**/*.{js,mjs,ts,tsx}', '**/public/**/*.{js,mjs,ts,tsx}'], + rules: { + 'no-restricted-imports': [ + 2, + { + paths: [ + ...RESTRICTED_IMPORTS, + { + name: 'semver', + message: 'Please use "semver/*/{function}" instead', + }, + ], + }, + ], + }, + }, /** * APM and Observability overrides @@ -1585,8 +1600,8 @@ module.exports = { plugins: ['react', '@typescript-eslint'], files: ['x-pack/plugins/osquery/**/*.{js,mjs,ts,tsx}'], rules: { - // 'arrow-body-style': ['error', 'as-needed'], - // 'prefer-arrow-callback': 'error', + 'arrow-body-style': ['error', 'as-needed'], + 'prefer-arrow-callback': 'error', 'no-unused-vars': 'off', 'react/prop-types': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', diff --git a/.node-version b/.node-version index 5595ae1aa9e4c..141e9a2a2cef0 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -14.17.6 +16.11.1 diff --git a/.nvmrc b/.nvmrc index 5595ae1aa9e4c..141e9a2a2cef0 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -14.17.6 +16.11.1 diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index 3ae3f202a3bfd..287b376037abe 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -27,13 +27,13 @@ check_rules_nodejs_version(minimum_version_string = "3.8.0") # we can update that rule. node_repositories( node_repositories = { - "14.17.6-darwin_amd64": ("node-v14.17.6-darwin-x64.tar.gz", "node-v14.17.6-darwin-x64", "e3e4c02240d74fb1dc8a514daa62e5de04f7eaee0bcbca06a366ece73a52ad88"), - "14.17.6-linux_arm64": ("node-v14.17.6-linux-arm64.tar.xz", "node-v14.17.6-linux-arm64", "9c4f3a651e03cd9b5bddd33a80e8be6a6eb15e518513e410bb0852a658699156"), - "14.17.6-linux_s390x": ("node-v14.17.6-linux-s390x.tar.xz", "node-v14.17.6-linux-s390x", "3677f35b97608056013b5368f86eecdb044bdccc1b3976c1d4448736c37b6a0c"), - "14.17.6-linux_amd64": ("node-v14.17.6-linux-x64.tar.xz", "node-v14.17.6-linux-x64", "3bbe4faf356738d88b45be222bf5e858330541ff16bd0d4cfad36540c331461b"), - "14.17.6-windows_amd64": ("node-v14.17.6-win-x64.zip", "node-v14.17.6-win-x64", "b83e9ce542fda7fc519cec6eb24a2575a84862ea4227dedc171a8e0b5b614ac0"), + "16.11.1-darwin_amd64": ("node-v16.11.1-darwin-x64.tar.gz", "node-v16.11.1-darwin-x64", "ba54b8ed504bd934d03eb860fefe991419b4209824280d4274f6a911588b5e45"), + "16.11.1-linux_arm64": ("node-v16.11.1-linux-arm64.tar.xz", "node-v16.11.1-linux-arm64", "083fc51f0ea26de9041aaf9821874651a9fd3b20d1cf57071ce6b523a0436f17"), + "16.11.1-linux_s390x": ("node-v16.11.1-linux-s390x.tar.xz", "node-v16.11.1-linux-s390x", "855b5c83c2ccb05273d50bb04376335c68d47df57f3187cdebe1f22b972d2825"), + "16.11.1-linux_amd64": ("node-v16.11.1-linux-x64.tar.xz", "node-v16.11.1-linux-x64", "493bcc9b660eff983a6de65a0f032eb2717f57207edf74c745bcb86e360310b3"), + "16.11.1-windows_amd64": ("node-v16.11.1-win-x64.zip", "node-v16.11.1-win-x64", "4d3c179b82d42e66e321c3948a4e332ed78592917a69d38b86e3a242d7e62fb7"), }, - node_version = "14.17.6", + node_version = "16.11.1", node_urls = [ "https://nodejs.org/dist/v{version}/{filename}", ], diff --git a/config/node.options b/config/node.options index 2927d1b576716..2585745249706 100644 --- a/config/node.options +++ b/config/node.options @@ -4,3 +4,6 @@ ## max size of old space in megabytes #--max-old-space-size=4096 + +## do not terminate process on unhandled promise rejection + --unhandled-rejections=warn diff --git a/docs/developer/plugin/external-plugin-localization.asciidoc b/docs/developer/plugin/external-plugin-localization.asciidoc index d30dec1a8f46b..656dff90fe0de 100644 --- a/docs/developer/plugin/external-plugin-localization.asciidoc +++ b/docs/developer/plugin/external-plugin-localization.asciidoc @@ -135,27 +135,6 @@ export const Component = () => { Full details are {kib-repo}tree/master/packages/kbn-i18n#react[here]. - - -[discrete] -==== i18n for Angular - -You are encouraged to use `i18n.translate()` by statically importing `i18n` from `@kbn/i18n` wherever possible in your Angular code. Angular wrappers use the translation `service` with the i18n engine under the hood. - -The translation directive has the following syntax: -["source","js"] ------------ - ------------ - -Full details are {kib-repo}tree/master/packages/kbn-i18n#angularjs[here]. - - [discrete] === Resources diff --git a/docs/management/connectors/action-types/pagerduty.asciidoc b/docs/management/connectors/action-types/pagerduty.asciidoc index db1c4e3932d14..5e12eddaa5c77 100644 --- a/docs/management/connectors/action-types/pagerduty.asciidoc +++ b/docs/management/connectors/action-types/pagerduty.asciidoc @@ -68,7 +68,7 @@ PagerDuty actions have the following properties. Severity:: The perceived severity of on the affected system. This can be one of `Critical`, `Error`, `Warning` or `Info`(default). Event action:: One of `Trigger` (default), `Resolve`, or `Acknowledge`. See https://v2.developer.pagerduty.com/docs/events-api-v2#event-action[event action] for more details. -Dedup Key:: All actions sharing this key will be associated with the same PagerDuty alert. This value is used to correlate trigger and resolution. This value is *optional*, and if not set, defaults to `:`. The maximum length is *255* characters. See https://v2.developer.pagerduty.com/docs/events-api-v2#alert-de-duplication[alert deduplication] for details. +Dedup Key:: All actions sharing this key will be associated with the same PagerDuty alert. This value is used to correlate trigger and resolution. This value is *optional*, and if not set, defaults to `:`. The maximum length is *255* characters. See https://v2.developer.pagerduty.com/docs/events-api-v2#alert-de-duplication[alert deduplication] for details. Timestamp:: An *optional* https://v2.developer.pagerduty.com/v2/docs/types#datetime[ISO-8601 format date-time], indicating the time the event was detected or generated. Component:: An *optional* value indicating the component of the source machine that is responsible for the event, for example `mysql` or `eth0`. Group:: An *optional* value indicating the logical grouping of components of a service, for example `app-stack`. diff --git a/package.json b/package.json index 60c1fee7bf192..bafa13d83dd4a 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "url": "https://github.com/elastic/kibana.git" }, "resolutions": { - "**/@types/node": "14.14.44", + "**/@types/node": "16.10.2", "**/chokidar": "^3.4.3", "**/deepmerge": "^4.2.2", "**/fast-deep-equal": "^3.1.1", @@ -84,7 +84,7 @@ "**/underscore": "^1.13.1" }, "engines": { - "node": "14.17.6", + "node": "16.11.1", "yarn": "^1.21.1" }, "dependencies": { @@ -341,7 +341,7 @@ "react-moment-proptypes": "^1.7.0", "react-monaco-editor": "^0.41.2", "react-popper-tooltip": "^2.10.1", - "react-query": "^3.21.1", + "react-query": "^3.27.0", "react-redux": "^7.2.0", "react-resizable": "^1.7.5", "react-resize-detector": "^4.2.0", @@ -496,7 +496,6 @@ "@testing-library/react-hooks": "^5.1.1", "@testing-library/user-event": "^13.1.1", "@types/angular": "^1.6.56", - "@types/angular-mocks": "^1.7.0", "@types/apidoc": "^0.22.3", "@types/archiver": "^5.1.0", "@types/babel__core": "^7.1.16", @@ -573,12 +572,12 @@ "@types/minimatch": "^2.0.29", "@types/minimist": "^1.2.1", "@types/mocha": "^8.2.0", - "@types/mock-fs": "^4.10.0", + "@types/mock-fs": "^4.13.1", "@types/moment-timezone": "^0.5.12", "@types/mustache": "^0.8.31", "@types/ncp": "^2.0.1", "@types/nock": "^10.0.3", - "@types/node": "14.14.44", + "@types/node": "16.10.2", "@types/node-fetch": "^2.5.7", "@types/node-forge": "^0.10.5", "@types/nodemailer": "^6.4.0", @@ -651,7 +650,6 @@ "@yarnpkg/lockfile": "^1.1.0", "abab": "^2.0.4", "aggregate-error": "^3.1.0", - "angular-mocks": "^1.7.9", "antlr4ts-cli": "^0.5.0-alpha.3", "apidoc": "^0.29.0", "apidoc-markdown": "^6.0.0", @@ -767,7 +765,7 @@ "mocha-junit-reporter": "^2.0.0", "mochawesome": "^6.2.1", "mochawesome-merge": "^4.2.0", - "mock-fs": "^4.12.0", + "mock-fs": "^5.1.1", "mock-http-server": "1.3.0", "ms-chromium-edge-driver": "^0.4.2", "multimatch": "^4.0.0", diff --git a/packages/elastic-apm-generator/src/lib/apm_error.ts b/packages/elastic-apm-generator/src/lib/apm_error.ts new file mode 100644 index 0000000000000..5a48093a26db2 --- /dev/null +++ b/packages/elastic-apm-generator/src/lib/apm_error.ts @@ -0,0 +1,30 @@ +/* + * 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 { Fields } from './entity'; +import { Serializable } from './serializable'; +import { generateLongId, generateShortId } from './utils/generate_id'; + +export class ApmError extends Serializable { + constructor(fields: Fields) { + super({ + ...fields, + 'processor.event': 'error', + 'processor.name': 'error', + 'error.id': generateShortId(), + }); + } + + serialize() { + const [data] = super.serialize(); + data['error.grouping_key'] = generateLongId( + this.fields['error.grouping_name'] || this.fields['error.exception']?.[0]?.message + ); + return [data]; + } +} diff --git a/packages/elastic-apm-generator/src/lib/base_span.ts b/packages/elastic-apm-generator/src/lib/base_span.ts index 6288c16d339b6..f762bf730a717 100644 --- a/packages/elastic-apm-generator/src/lib/base_span.ts +++ b/packages/elastic-apm-generator/src/lib/base_span.ts @@ -10,7 +10,7 @@ import { Fields } from './entity'; import { Serializable } from './serializable'; import { Span } from './span'; import { Transaction } from './transaction'; -import { generateTraceId } from './utils/generate_id'; +import { generateLongId } from './utils/generate_id'; export class BaseSpan extends Serializable { private readonly _children: BaseSpan[] = []; @@ -19,7 +19,7 @@ export class BaseSpan extends Serializable { super({ ...fields, 'event.outcome': 'unknown', - 'trace.id': generateTraceId(), + 'trace.id': generateLongId(), 'processor.name': 'transaction', }); } diff --git a/packages/elastic-apm-generator/src/lib/entity.ts b/packages/elastic-apm-generator/src/lib/entity.ts index 2a4beee652cf7..bf8fc10efd3a7 100644 --- a/packages/elastic-apm-generator/src/lib/entity.ts +++ b/packages/elastic-apm-generator/src/lib/entity.ts @@ -6,6 +6,19 @@ * Side Public License, v 1. */ +export type ApplicationMetricFields = Partial<{ + 'system.process.memory.size': number; + 'system.memory.actual.free': number; + 'system.memory.total': number; + 'system.cpu.total.norm.pct': number; + 'system.process.memory.rss.bytes': number; + 'system.process.cpu.total.norm.pct': number; +}>; + +export interface Exception { + message: string; +} + export type Fields = Partial<{ '@timestamp': number; 'agent.name': string; @@ -14,6 +27,10 @@ export type Fields = Partial<{ 'ecs.version': string; 'event.outcome': string; 'event.ingested': number; + 'error.id': string; + 'error.exception': Exception[]; + 'error.grouping_name': string; + 'error.grouping_key': string; 'host.name': string; 'metricset.name': string; 'observer.version': string; @@ -46,7 +63,8 @@ export type Fields = Partial<{ 'span.destination.service.response_time.count': number; 'span.self_time.count': number; 'span.self_time.sum.us': number; -}>; +}> & + ApplicationMetricFields; export class Entity { constructor(public readonly fields: Fields) { diff --git a/packages/elastic-apm-generator/src/lib/instance.ts b/packages/elastic-apm-generator/src/lib/instance.ts index 4218a9e23f4b4..3570f497c9055 100644 --- a/packages/elastic-apm-generator/src/lib/instance.ts +++ b/packages/elastic-apm-generator/src/lib/instance.ts @@ -6,7 +6,9 @@ * Side Public License, v 1. */ -import { Entity } from './entity'; +import { ApmError } from './apm_error'; +import { ApplicationMetricFields, Entity } from './entity'; +import { Metricset } from './metricset'; import { Span } from './span'; import { Transaction } from './transaction'; @@ -27,4 +29,20 @@ export class Instance extends Entity { 'span.subtype': spanSubtype, }); } + + error(message: string, type?: string, groupingName?: string) { + return new ApmError({ + ...this.fields, + 'error.exception': [{ message, ...(type ? { type } : {}) }], + 'error.grouping_name': groupingName || message, + }); + } + + appMetrics(metrics: ApplicationMetricFields) { + return new Metricset({ + ...this.fields, + 'metricset.name': 'app', + ...metrics, + }); + } } diff --git a/packages/elastic-apm-generator/src/lib/metricset.ts b/packages/elastic-apm-generator/src/lib/metricset.ts index f7abec6fde958..c1ebbea313123 100644 --- a/packages/elastic-apm-generator/src/lib/metricset.ts +++ b/packages/elastic-apm-generator/src/lib/metricset.ts @@ -6,12 +6,15 @@ * Side Public License, v 1. */ +import { Fields } from './entity'; import { Serializable } from './serializable'; -export class Metricset extends Serializable {} - -export function metricset(name: string) { - return new Metricset({ - 'metricset.name': name, - }); +export class Metricset extends Serializable { + constructor(fields: Fields) { + super({ + 'processor.event': 'metric', + 'processor.name': 'metric', + ...fields, + }); + } } diff --git a/packages/elastic-apm-generator/src/lib/output/to_elasticsearch_output.ts b/packages/elastic-apm-generator/src/lib/output/to_elasticsearch_output.ts index b4cae1b41b9a6..d90ce8e01f83d 100644 --- a/packages/elastic-apm-generator/src/lib/output/to_elasticsearch_output.ts +++ b/packages/elastic-apm-generator/src/lib/output/to_elasticsearch_output.ts @@ -25,7 +25,8 @@ export function toElasticsearchOutput(events: Fields[], versionOverride?: string const document = {}; // eslint-disable-next-line guard-for-in for (const key in values) { - set(document, key, values[key as keyof typeof values]); + const val = values[key as keyof typeof values]; + set(document, key, val); } return { _index: `apm-${versionOverride || values['observer.version']}-${values['processor.event']}`, diff --git a/packages/elastic-apm-generator/src/lib/span.ts b/packages/elastic-apm-generator/src/lib/span.ts index 36f7f44816d01..3c8d90f56b78e 100644 --- a/packages/elastic-apm-generator/src/lib/span.ts +++ b/packages/elastic-apm-generator/src/lib/span.ts @@ -8,14 +8,14 @@ import { BaseSpan } from './base_span'; import { Fields } from './entity'; -import { generateEventId } from './utils/generate_id'; +import { generateShortId } from './utils/generate_id'; export class Span extends BaseSpan { constructor(fields: Fields) { super({ ...fields, 'processor.event': 'span', - 'span.id': generateEventId(), + 'span.id': generateShortId(), }); } diff --git a/packages/elastic-apm-generator/src/lib/transaction.ts b/packages/elastic-apm-generator/src/lib/transaction.ts index f615f46710996..3a8d32e1843f8 100644 --- a/packages/elastic-apm-generator/src/lib/transaction.ts +++ b/packages/elastic-apm-generator/src/lib/transaction.ts @@ -6,22 +6,48 @@ * Side Public License, v 1. */ +import { ApmError } from './apm_error'; import { BaseSpan } from './base_span'; import { Fields } from './entity'; -import { generateEventId } from './utils/generate_id'; +import { generateShortId } from './utils/generate_id'; export class Transaction extends BaseSpan { private _sampled: boolean = true; + private readonly _errors: ApmError[] = []; constructor(fields: Fields) { super({ ...fields, 'processor.event': 'transaction', - 'transaction.id': generateEventId(), + 'transaction.id': generateShortId(), 'transaction.sampled': true, }); } + parent(span: BaseSpan) { + super.parent(span); + + this._errors.forEach((error) => { + error.fields['trace.id'] = this.fields['trace.id']; + error.fields['transaction.id'] = this.fields['transaction.id']; + error.fields['transaction.type'] = this.fields['transaction.type']; + }); + + return this; + } + + errors(...errors: ApmError[]) { + errors.forEach((error) => { + error.fields['trace.id'] = this.fields['trace.id']; + error.fields['transaction.id'] = this.fields['transaction.id']; + error.fields['transaction.type'] = this.fields['transaction.type']; + }); + + this._errors.push(...errors); + + return this; + } + duration(duration: number) { this.fields['transaction.duration.us'] = duration * 1000; return this; @@ -35,11 +61,13 @@ export class Transaction extends BaseSpan { serialize() { const [transaction, ...spans] = super.serialize(); + const errors = this._errors.flatMap((error) => error.serialize()); + const events = [transaction]; if (this._sampled) { events.push(...spans); } - return events; + return events.concat(errors); } } diff --git a/packages/elastic-apm-generator/src/lib/utils/generate_id.ts b/packages/elastic-apm-generator/src/lib/utils/generate_id.ts index 6c8b33fc19077..cc372a56209aa 100644 --- a/packages/elastic-apm-generator/src/lib/utils/generate_id.ts +++ b/packages/elastic-apm-generator/src/lib/utils/generate_id.ts @@ -12,14 +12,14 @@ let seq = 0; const namespace = 'f38d5b83-8eee-4f5b-9aa6-2107e15a71e3'; -function generateId() { - return uuidv5(String(seq++), namespace).replace(/-/g, ''); +function generateId(seed?: string) { + return uuidv5(seed ?? String(seq++), namespace).replace(/-/g, ''); } -export function generateEventId() { - return generateId().substr(0, 16); +export function generateShortId(seed?: string) { + return generateId(seed).substr(0, 16); } -export function generateTraceId() { - return generateId().substr(0, 32); +export function generateLongId(seed?: string) { + return generateId(seed).substr(0, 32); } diff --git a/packages/elastic-apm-generator/src/scripts/examples/01_simple_trace.ts b/packages/elastic-apm-generator/src/scripts/examples/01_simple_trace.ts index 7aae2986919c8..f6aad154532c2 100644 --- a/packages/elastic-apm-generator/src/scripts/examples/01_simple_trace.ts +++ b/packages/elastic-apm-generator/src/scripts/examples/01_simple_trace.ts @@ -14,11 +14,11 @@ export function simpleTrace(from: number, to: number) { const range = timerange(from, to); - const transactionName = '100rpm (80% success) failed 1000ms'; + const transactionName = '240rpm/60% 1000ms'; const successfulTraceEvents = range - .interval('30s') - .rate(40) + .interval('1s') + .rate(3) .flatMap((timestamp) => instance .transaction(transactionName) @@ -38,21 +38,39 @@ export function simpleTrace(from: number, to: number) { ); const failedTraceEvents = range - .interval('30s') - .rate(10) + .interval('1s') + .rate(1) .flatMap((timestamp) => instance .transaction(transactionName) .timestamp(timestamp) .duration(1000) .failure() + .errors( + instance.error('[ResponseError] index_not_found_exception').timestamp(timestamp + 50) + ) .serialize() ); + const metricsets = range + .interval('30s') + .rate(1) + .flatMap((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) + .serialize() + ); const events = successfulTraceEvents.concat(failedTraceEvents); return [ ...events, + ...metricsets, ...getTransactionMetrics(events), ...getSpanDestinationMetrics(events), ...getBreakdownMetrics(events), diff --git a/packages/elastic-apm-generator/src/test/scenarios/05_transactions_with_errors.test.ts b/packages/elastic-apm-generator/src/test/scenarios/05_transactions_with_errors.test.ts new file mode 100644 index 0000000000000..289fdfa6cf565 --- /dev/null +++ b/packages/elastic-apm-generator/src/test/scenarios/05_transactions_with_errors.test.ts @@ -0,0 +1,66 @@ +/* + * 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 { pick } from 'lodash'; +import { service } from '../../index'; +import { Instance } from '../../lib/instance'; + +describe('transactions with errors', () => { + let instance: Instance; + const timestamp = new Date('2021-01-01T00:00:00.000Z').getTime(); + + beforeEach(() => { + instance = service('opbeans-java', 'production', 'java').instance('instance'); + }); + it('generates error events', () => { + const events = instance + .transaction('GET /api') + .timestamp(timestamp) + .errors(instance.error('test error').timestamp(timestamp)) + .serialize(); + + const errorEvents = events.filter((event) => event['processor.event'] === 'error'); + + expect(errorEvents.length).toEqual(1); + + expect( + pick(errorEvents[0], 'processor.event', 'processor.name', 'error.exception', '@timestamp') + ).toEqual({ + 'processor.event': 'error', + 'processor.name': 'error', + '@timestamp': timestamp, + 'error.exception': [{ message: 'test error' }], + }); + }); + + it('sets the transaction and trace id', () => { + const [transaction, error] = instance + .transaction('GET /api') + .timestamp(timestamp) + .errors(instance.error('test error').timestamp(timestamp)) + .serialize(); + + const keys = ['transaction.id', 'trace.id', 'transaction.type']; + + expect(pick(error, keys)).toEqual({ + 'transaction.id': transaction['transaction.id'], + 'trace.id': transaction['trace.id'], + 'transaction.type': 'request', + }); + }); + + it('sets the error grouping key', () => { + const [, error] = instance + .transaction('GET /api') + .timestamp(timestamp) + .errors(instance.error('test error').timestamp(timestamp)) + .serialize(); + + expect(error['error.grouping_name']).toEqual('test error'); + expect(error['error.grouping_key']).toMatchInlineSnapshot(`"8b96fa10a7f85a5d960198627bf50840"`); + }); +}); diff --git a/packages/elastic-apm-generator/src/test/scenarios/06_application_metrics.test.ts b/packages/elastic-apm-generator/src/test/scenarios/06_application_metrics.test.ts new file mode 100644 index 0000000000000..59ca8f0edbe88 --- /dev/null +++ b/packages/elastic-apm-generator/src/test/scenarios/06_application_metrics.test.ts @@ -0,0 +1,64 @@ +/* + * 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 { pick } from 'lodash'; +import { service } from '../../index'; +import { Instance } from '../../lib/instance'; + +describe('application metrics', () => { + let instance: Instance; + const timestamp = new Date('2021-01-01T00:00:00.000Z').getTime(); + + beforeEach(() => { + instance = service('opbeans-java', 'production', 'java').instance('instance'); + }); + it('generates application metricsets', () => { + const events = instance + .appMetrics({ + 'system.memory.actual.free': 80, + 'system.memory.total': 100, + }) + .timestamp(timestamp) + .serialize(); + + const appMetrics = events.filter((event) => event['processor.event'] === 'metric'); + + expect(appMetrics.length).toEqual(1); + + expect( + pick( + appMetrics[0], + '@timestamp', + 'agent.name', + 'container.id', + 'metricset.name', + 'processor.event', + 'processor.name', + 'service.environment', + 'service.name', + 'service.node.name', + 'system.memory.actual.free', + 'system.memory.total' + ) + ).toEqual({ + '@timestamp': timestamp, + 'metricset.name': 'app', + 'processor.event': 'metric', + 'processor.name': 'metric', + 'system.memory.actual.free': 80, + 'system.memory.total': 100, + ...pick( + instance.fields, + 'agent.name', + 'container.id', + 'service.environment', + 'service.name', + 'service.node.name' + ), + }); + }); +}); diff --git a/packages/kbn-dev-utils/src/proc_runner/proc.ts b/packages/kbn-dev-utils/src/proc_runner/proc.ts index e04a189baf5cd..c9a520de6eb4d 100644 --- a/packages/kbn-dev-utils/src/proc_runner/proc.ts +++ b/packages/kbn-dev-utils/src/proc_runner/proc.ts @@ -131,7 +131,7 @@ export function startProc(name: string, options: ProcOptions, log: ToolingLog) { await withTimeout( async () => { log.debug(`Sending "${signal}" to proc "${name}"`); - await treeKillAsync(childProcess.pid, signal); + await treeKillAsync(childProcess.pid!, signal); await outcomePromise; }, STOP_TIMEOUT, @@ -139,7 +139,7 @@ export function startProc(name: string, options: ProcOptions, log: ToolingLog) { log.warning( `Proc "${name}" was sent "${signal}" didn't emit the "exit" or "error" events after ${STOP_TIMEOUT} ms, sending SIGKILL` ); - await treeKillAsync(childProcess.pid, 'SIGKILL'); + await treeKillAsync(childProcess.pid!, 'SIGKILL'); } ); diff --git a/packages/kbn-i18n/BUILD.bazel b/packages/kbn-i18n/BUILD.bazel index 49d5603b2c516..8ea6c3dd192f4 100644 --- a/packages/kbn-i18n/BUILD.bazel +++ b/packages/kbn-i18n/BUILD.bazel @@ -27,7 +27,6 @@ filegroup( ) NPM_MODULE_EXTRA_FILES = [ - "angular/package.json", "react/package.json", "package.json", "GUIDELINE.md", @@ -47,9 +46,9 @@ TYPES_DEPS = [ "//packages/kbn-babel-preset", "@npm//intl-messageformat", "@npm//tslib", - "@npm//@types/angular", "@npm//@types/intl-relativeformat", "@npm//@types/jest", + "@npm//@types/node", "@npm//@types/prop-types", "@npm//@types/react", "@npm//@types/react-intl", diff --git a/packages/kbn-i18n/GUIDELINE.md b/packages/kbn-i18n/GUIDELINE.md index fdb082bb1a067..21dd720f9d1f6 100644 --- a/packages/kbn-i18n/GUIDELINE.md +++ b/packages/kbn-i18n/GUIDELINE.md @@ -93,17 +93,6 @@ The long term plan is to rely on using `FormattedMessage` and `i18n.translate()` Currently, we support the following ReactJS `i18n` tools, but they will be removed in future releases: - Usage of `props.intl.formatmessage()` (where `intl` is passed to `props` by `injectI18n` HOC). -#### In AngularJS - -The long term plan is to rely on using `i18n.translate()` by statically importing `i18n` from the `@kbn/i18n` package. **Avoid using the `i18n` filter and the `i18n` service injected in controllers, directives, services.** - -- Call JS function `i18n.translate()` from the `@kbn/i18n` package. -- Use `i18nId` directive in template. - -Currently, we support the following AngluarJS `i18n` tools, but they will be removed in future releases: -- Usage of `i18n` service in controllers, directives, services by injecting it. -- Usage of `i18n` filter in template for attribute translation. Note: Use one-time binding ("{{:: ... }}") in filters wherever it's possible to prevent unnecessary expression re-evaluation. - #### In JavaScript - Use `i18n.translate()` in NodeJS or any other framework agnostic code, where `i18n` is the I18n engine from `@kbn/i18n` package. @@ -223,7 +212,6 @@ For example: - for button: ```js - @@ -232,11 +220,11 @@ For example: - for dropDown: ```js - +