Skip to content

Commit

Permalink
Merge branch 'master' into graph/savedObjectClientResolve
Browse files Browse the repository at this point in the history
  • Loading branch information
kibanamachine authored Sep 8, 2021
2 parents 60fba05 + 77e25be commit 66bb543
Show file tree
Hide file tree
Showing 225 changed files with 3,624 additions and 3,278 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

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: 2 additions & 0 deletions src/core/public/chrome/chrome_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const createStartContractMock = () => {
getCustomNavLink$: jest.fn(),
setCustomNavLink: jest.fn(),
setHeaderBanner: jest.fn(),
hasHeaderBanner$: jest.fn(),
getBodyClasses$: jest.fn(),
};
startContract.navLinks.getAll.mockReturnValue([]);
Expand All @@ -65,6 +66,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
7 changes: 7 additions & 0 deletions src/core/public/chrome/chrome_service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,13 @@ export class ChromeService {
headerBanner$.next(headerBanner);
},

hasHeaderBanner$: () => {
return headerBanner$.pipe(
takeUntil(this.stop$),
map((banner) => Boolean(banner))
);
},

getBodyClasses$: () => bodyClasses$.pipe(takeUntil(this.stop$)),
};
}
Expand Down
5 changes: 5 additions & 0 deletions src/core/public/chrome/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ export interface ChromeStart {
* @remarks Using `undefined` when invoking this API will remove the banner.
*/
setHeaderBanner(headerBanner?: ChromeUserBanner): void;

/**
* Get an observable of the current header banner presence state.
*/
hasHeaderBanner$(): Observable<boolean>;
}

/** @internal */
Expand Down
1 change: 1 addition & 0 deletions src/core/public/public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ export interface ChromeStart {
getHelpExtension$(): Observable<ChromeHelpExtension | undefined>;
getIsNavDrawerLocked$(): Observable<boolean>;
getIsVisible$(): Observable<boolean>;
hasHeaderBanner$(): Observable<boolean>;
navControls: ChromeNavControls;
navLinks: ChromeNavLinks;
recentlyAccessed: ChromeRecentlyAccessed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ describe('migration v2', () => {
await root.setup();
await expect(root.start()).resolves.toBeTruthy();

await new Promise((resolve) => setTimeout(resolve, 1000));
// After plugins start, some saved objects are deleted/recreated, so we
// wait a bit for the count to settle.
await new Promise((resolve) => setTimeout(resolve, 5000));

const esClient: ElasticsearchClient = esServer.es.getClient();
const migratedIndexResponse = await esClient.count({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@

import { retryAsync } from './retry_async';

// FLAKY: https://github.com/elastic/kibana/issues/110970
describe.skip('retry', () => {
describe('retry', () => {
it('retries throwing functions until they succeed', async () => {
let i = 0;
await expect(
Expand Down Expand Up @@ -53,6 +52,8 @@ describe.skip('retry', () => {
},
{ retryAttempts: 3, retryDelayMs: 100 }
);
expect(Date.now() - now).toBeGreaterThanOrEqual(200);
// Would expect it to take 200ms but seems like timing inaccuracies
// sometimes causes the duration to be measured as 199ms
expect(Date.now() - now).toBeGreaterThanOrEqual(199);
});
});
2 changes: 0 additions & 2 deletions src/core/server/saved_objects/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { registerUpdateRoute } from './update';
import { registerBulkGetRoute } from './bulk_get';
import { registerBulkCreateRoute } from './bulk_create';
import { registerBulkUpdateRoute } from './bulk_update';
import { registerLogLegacyImportRoute } from './log_legacy_import';
import { registerExportRoute } from './export';
import { registerImportRoute } from './import';
import { registerResolveImportErrorsRoute } from './resolve_import_errors';
Expand Down Expand Up @@ -50,7 +49,6 @@ export function registerRoutes({
registerBulkGetRoute(router, { coreUsageData });
registerBulkCreateRoute(router, { coreUsageData });
registerBulkUpdateRoute(router, { coreUsageData });
registerLogLegacyImportRoute(router, logger);
registerExportRoute(router, { config, coreUsageData });
registerImportRoute(router, { config, coreUsageData });
registerResolveImportErrorsRoute(router, { config, coreUsageData });
Expand Down

This file was deleted.

23 changes: 0 additions & 23 deletions src/core/server/saved_objects/routes/log_legacy_import.ts

This file was deleted.

5 changes: 1 addition & 4 deletions src/dev/build/tasks/create_empty_dirs_and_files_task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ export const CreateEmptyDirsAndFiles: Task = {
description: 'Creating some empty directories and files to prevent file-permission issues',

async run(config, log, build) {
await Promise.all([
mkdirp(build.resolvePath('plugins')),
mkdirp(build.resolvePath('data/optimize')),
]);
await Promise.all([mkdirp(build.resolvePath('plugins')), mkdirp(build.resolvePath('data'))]);
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export const useDashboardAppState = ({
savedDashboards,
kbnUrlStateStorage,
initializerContext,
savedObjectsTagging,
isEmbeddedExternally,
dashboardCapabilities,
dispatchDashboardStateChange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ describe('getStateDefaults', () => {
"default_column",
],
"filters": undefined,
"hideChart": undefined,
"index": "index-pattern-with-timefield-id",
"interval": "auto",
"query": undefined,
Expand Down Expand Up @@ -54,6 +55,7 @@ describe('getStateDefaults', () => {
"default_column",
],
"filters": undefined,
"hideChart": undefined,
"index": "the-index-pattern-id",
"interval": "auto",
"query": undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export function getStateDefaults({
index: indexPattern!.id,
interval: 'auto',
filters: cloneDeep(searchSource.getOwnField('filter')),
hideChart: undefined,
} as AppState;
if (savedSearch.grid) {
defaultState.grid = savedSearch.grid;
Expand Down
1 change: 1 addition & 0 deletions src/plugins/embeddable/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"name": "App Services",
"githubTeam": "kibana-app-services"
},
"description": "Adds embeddables service to Kibana",
"requiredPlugins": ["inspector", "uiActions"],
"extraPublicDirs": ["public/lib/test_samples", "common"],
"requiredBundles": ["savedObjects", "kibanaReact", "kibanaUtils"]
Expand Down
Loading

0 comments on commit 66bb543

Please sign in to comment.