Skip to content

Commit

Permalink
Merge branch '7.x' into ua/7.x/consistent-terms
Browse files Browse the repository at this point in the history
  • Loading branch information
kibanamachine committed Sep 7, 2021
2 parents a45d828 + d4d52b0 commit 3b98a4e
Show file tree
Hide file tree
Showing 473 changed files with 7,906 additions and 3,343 deletions.
106 changes: 106 additions & 0 deletions dev_docs/key_concepts/performance.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
id: kibDevPerformance
slug: /kibana-dev-docs/performance
title: Performance
summary: Performance tips for Kibana development.
date: 2021-09-02
tags: ['kibana', 'onboarding', 'dev', 'performance']
---

## Keep Kibana fast

*tl;dr*: Load as much code lazily as possible. Everyone loves snappy
applications with a responsive UI and hates spinners. Users deserve the
best experience whether they run Kibana locally or
in the cloud, regardless of their hardware and environment.

There are 2 main aspects of the perceived speed of an application: loading time
and responsiveness to user actions. Kibana loads and bootstraps *all*
the plugins whenever a user lands on any page. It means that
every new application affects the overall _loading performance_, as plugin code is
loaded _eagerly_ to initialize the plugin and provide plugin API to dependent
plugins.

However, it’s usually not necessary that the whole plugin code should be loaded
and initialized at once. The plugin could keep on loading code covering API functionality
on Kibana bootstrap, but load UI related code lazily on-demand, when an
application page or management section is mounted.
Always prefer to import UI root components lazily when possible (such as in `mount`
handlers). Even if their size may seem negligible, they are likely using
some heavy-weight libraries that will also be removed from the initial
plugin bundle, therefore, reducing its size by a significant amount.

```ts
import type { Plugin, CoreSetup, AppMountParameters } from 'kibana/public';
export class MyPlugin implements Plugin<MyPluginSetup> {
setup(core: CoreSetup, plugins: SetupDeps) {
core.application.register({
id: 'app',
title: 'My app',
async mount(params: AppMountParameters) {
const { mountApp } = await import('./app/mount_app');
return mountApp(await core.getStartServices(), params);
},
});
plugins.management.sections.section.kibana.registerApp({
id: 'app',
title: 'My app',
order: 1,
async mount(params) {
const { mountManagementSection } = await import('./app/mount_management_section');
return mountManagementSection(coreSetup, params);
},
});
return {
doSomething() {},
};
}
}
```

### Understanding plugin bundle size

Kibana Platform plugins are pre-built with `@kbn/optimizer`
and distributed as package artifacts. This means that it is no
longer necessary for us to include the `optimizer` in the
distributable version of Kibana Every plugin artifact contains all
plugin dependencies required to run the plugin, except some
stateful dependencies shared across plugin bundles via
`@kbn/ui-shared-deps`. This means that plugin artifacts _tend to
be larger_ than they were in the legacy platform. To understand the
current size of your plugin artifact, run `@kbn/optimizer` with:

```bash
node scripts/build_kibana_platform_plugins.js --dist --profile --focus=my_plugin
```

and check the output in the `target` sub-folder of your plugin folder:

```bash
ls -lh plugins/my_plugin/target/public/
# output
# an async chunk loaded on demand
... 262K 0.plugin.js
# eagerly loaded chunk
... 50K my_plugin.plugin.js
```

You might see at least one js bundle - `my_plugin.plugin.js`. This is
the _only_ artifact loaded by Kibana during bootstrap in the
browser. The rule of thumb is to keep its size as small as possible.
Other lazily loaded parts of your plugin will be present in the same folder as
separate chunks under `{number}.myplugin.js` names. If you want to
investigate what your plugin bundle consists of, you need to run
`@kbn/optimizer` with `--profile` flag to generate a
[webpack stats file](https://webpack.js.org/api/stats/).

```bash
node scripts/build_kibana_platform_plugins.js --dist --no-examples --profile
```

Many OSS tools allow you to analyze the generated stats file:

* [An official tool](https://webpack.github.io/analyse/#modules) from
Webpack authors
* [webpack-visualizer](https://chrisbateman.github.io/webpack-visualizer/)
* [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-core-public](./kibana-plugin-core-public.md) &gt; [ChromeStart](./kibana-plugin-core-public.chromestart.md) &gt; [hasHeaderBanner$](./kibana-plugin-core-public.chromestart.hasheaderbanner_.md)

## ChromeStart.hasHeaderBanner$() method

Get an observable of the current header banner presence state.

<b>Signature:</b>

```typescript
hasHeaderBanner$(): Observable<boolean>;
```
<b>Returns:</b>

`Observable<boolean>`

Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ core.chrome.setHelpExtension(elem => {
| [getHelpExtension$()](./kibana-plugin-core-public.chromestart.gethelpextension_.md) | Get an observable of the current custom help conttent |
| [getIsNavDrawerLocked$()](./kibana-plugin-core-public.chromestart.getisnavdrawerlocked_.md) | Get an observable of the current locked state of the nav drawer. |
| [getIsVisible$()](./kibana-plugin-core-public.chromestart.getisvisible_.md) | Get an observable of the current visibility state of the chrome. |
| [hasHeaderBanner$()](./kibana-plugin-core-public.chromestart.hasheaderbanner_.md) | Get an observable of the current header banner presence state. |
| [setBadge(badge)](./kibana-plugin-core-public.chromestart.setbadge.md) | Override the current badge |
| [setBreadcrumbs(newBreadcrumbs)](./kibana-plugin-core-public.chromestart.setbreadcrumbs.md) | Override the current set of breadcrumbs |
| [setBreadcrumbsAppendExtension(breadcrumbsAppendExtension)](./kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md) | Mount an element next to the last breadcrumb |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ readonly links: {
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly customLinks: string;
readonly droppedTransactionSpans: string;
readonly upgrading: string;
readonly metaData: string;
};
readonly canvas: {
readonly guide: string;
Expand Down

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/maps/maps-getting-started.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ and lighter shades will symbolize countries with less traffic.

. In **Statistics source**, set:
** **Index pattern** to **kibana_sample_data_logs**
** **Join field** to **geo.src**
** **Join field** to **geo.dest**

. Click **Add layer**.

. In **Layer settings**, set:

** **Name** to `Total Requests by Country`
** **Name** to `Total Requests by Destination`
** **Opacity** to 50%

. Add a Tooltip field:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@
"react-moment-proptypes": "^1.7.0",
"react-monaco-editor": "^0.41.2",
"react-popper-tooltip": "^2.10.1",
"react-query": "^3.21.0",
"react-query": "^3.21.1",
"react-redux": "^7.2.0",
"react-resizable": "^1.7.5",
"react-resize-detector": "^4.2.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pageLoadAssetSize:
indexPatternManagement: 28222
indexPatternEditor: 40000
infra: 184320
fleet: 465774
fleet: 250000
ingestPipelines: 58003
inputControlVis: 172675
inspector: 148711
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface UseExceptionListsProps {
http: HttpStart;
namespaceTypes: NamespaceType[];
notifications: NotificationsStart;
pagination?: Pagination;
initialPagination?: Pagination;
showTrustedApps: boolean;
showEventFilters: boolean;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { useEffect, useMemo, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type {
ExceptionListSchema,
UseExceptionListsProps,
Expand All @@ -17,7 +17,19 @@ import { fetchExceptionLists } from '@kbn/securitysolution-list-api';
import { getFilters } from '@kbn/securitysolution-list-utils';

export type Func = () => void;
export type ReturnExceptionLists = [boolean, ExceptionListSchema[], Pagination, Func | null];
export type ReturnExceptionLists = [
loading: boolean,
exceptionLists: ExceptionListSchema[],
pagination: Pagination,
setPagination: React.Dispatch<React.SetStateAction<Pagination>>,
fetchLists: Func | null
];

const DEFAULT_PAGINATION = {
page: 1,
perPage: 20,
total: 0,
};

/**
* Hook for fetching ExceptionLists
Expand All @@ -29,27 +41,23 @@ export type ReturnExceptionLists = [boolean, ExceptionListSchema[], Pagination,
* @param notifications kibana service for displaying toasters
* @param showTrustedApps boolean - include/exclude trusted app lists
* @param showEventFilters boolean - include/exclude event filters lists
* @param pagination
* @param initialPagination
*
*/
export const useExceptionLists = ({
errorMessage,
http,
pagination = {
page: 1,
perPage: 20,
total: 0,
},
initialPagination = DEFAULT_PAGINATION,
filterOptions = {},
namespaceTypes,
notifications,
showTrustedApps = false,
showEventFilters = false,
}: UseExceptionListsProps): ReturnExceptionLists => {
const [exceptionLists, setExceptionLists] = useState<ExceptionListSchema[]>([]);
const [paginationInfo, setPagination] = useState<Pagination>(pagination);
const [pagination, setPagination] = useState<Pagination>(initialPagination);
const [loading, setLoading] = useState(true);
const fetchExceptionListsRef = useRef<Func | null>(null);
const abortCtrlRef = useRef<AbortController>();

const namespaceTypesAsString = useMemo(() => namespaceTypes.join(','), [namespaceTypes]);
const filters = useMemo(
Expand All @@ -58,66 +66,57 @@ export const useExceptionLists = ({
[namespaceTypes, filterOptions, showTrustedApps, showEventFilters]
);

useEffect(() => {
let isSubscribed = true;
const abortCtrl = new AbortController();
const fetchData = useCallback(async (): Promise<void> => {
try {
setLoading(true);

const fetchData = async (): Promise<void> => {
try {
setLoading(true);
abortCtrlRef.current = new AbortController();

const { page, per_page: perPage, total, data } = await fetchExceptionLists({
filters,
http,
namespaceTypes: namespaceTypesAsString,
pagination: {
page: pagination.page,
perPage: pagination.perPage,
},
signal: abortCtrl.signal,
});
const { page, per_page: perPage, total, data } = await fetchExceptionLists({
filters,
http,
namespaceTypes: namespaceTypesAsString,
pagination: {
page: pagination.page,
perPage: pagination.perPage,
},
signal: abortCtrlRef.current.signal,
});

if (isSubscribed) {
setPagination({
page,
perPage,
total,
});
setExceptionLists(data);
setLoading(false);
}
} catch (error) {
if (isSubscribed) {
notifications.toasts.addError(error, {
title: errorMessage,
});
setExceptionLists([]);
setPagination({
page: 1,
perPage: 20,
total: 0,
});
setLoading(false);
}
setPagination({
page,
perPage,
total,
});
setExceptionLists(data);
setLoading(false);
} catch (error) {
if (error.name !== 'AbortError') {
notifications.toasts.addError(error, {
title: errorMessage,
});
setExceptionLists([]);
setPagination(DEFAULT_PAGINATION);
setLoading(false);
}
};

fetchData();

fetchExceptionListsRef.current = fetchData;
return (): void => {
isSubscribed = false;
abortCtrl.abort();
};
}
}, [
errorMessage,
notifications,
pagination.page,
pagination.perPage,
filters,
namespaceTypesAsString,
http,
namespaceTypesAsString,
notifications.toasts,
pagination.page,
pagination.perPage,
]);

return [loading, exceptionLists, paginationInfo, fetchExceptionListsRef.current];
useEffect(() => {
fetchData();

return (): void => {
abortCtrlRef.current?.abort();
};
}, [fetchData]);

return [loading, exceptionLists, pagination, setPagination, fetchData];
};
2 changes: 2 additions & 0 deletions src/core/public/chrome/chrome_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const createStartContractMock = () => {
getCustomNavLink$: jest.fn(),
setCustomNavLink: jest.fn(),
setHeaderBanner: jest.fn(),
hasHeaderBanner$: jest.fn(),
getBodyClasses$: jest.fn(),
};
startContract.navLinks.getAll.mockReturnValue([]);
Expand All @@ -66,6 +67,7 @@ const createStartContractMock = () => {
startContract.getHelpExtension$.mockReturnValue(new BehaviorSubject(undefined));
startContract.getIsNavDrawerLocked$.mockReturnValue(new BehaviorSubject(false));
startContract.getBodyClasses$.mockReturnValue(new BehaviorSubject([]));
startContract.hasHeaderBanner$.mockReturnValue(new BehaviorSubject(false));
return startContract;
};

Expand Down
13 changes: 13 additions & 0 deletions src/core/public/chrome/chrome_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,19 @@ describe('start', () => {
});
});

describe('header banner', () => {
it('updates/emits the state of the header banner', async () => {
const { chrome, service } = await start();
const promise = chrome.hasHeaderBanner$().pipe(toArray()).toPromise();

chrome.setHeaderBanner({ content: () => () => undefined });
chrome.setHeaderBanner(undefined);
service.stop();

await expect(promise).resolves.toEqual([false, true, false]);
});
});

describe('erase chrome fields', () => {
it('while switching an app', async () => {
const startDeps = defaultStartDeps([new FakeApp('alpha')]);
Expand Down
Loading

0 comments on commit 3b98a4e

Please sign in to comment.