[EBT] Enrich kibana loaded with timings (#134770)

* Add timings to kibana loaded event

* jest

* PR failures

* code review

* docs

* add first_app_nav and first_app

* tests

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

* Update src/core/public/core_system.ts

Co-authored-by: Alejandro Fernández Haro <afharo@gmail.com>

* Update src/core/public/core_system.ts

Co-authored-by: Alejandro Fernández Haro <afharo@gmail.com>

* review @afjaro

* typo

* KBN_LOAD_MARKS

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Alejandro Fernández Haro <afharo@gmail.com>
This commit is contained in:
Liza Katz 2022-06-23 12:46:07 +03:00 committed by GitHub
parent ca532310f2
commit 8133605b89
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 136 additions and 7 deletions

View file

@ -70,6 +70,19 @@ const defaultCoreSystemParams = {
beforeEach(() => {
jest.clearAllMocks();
MockPluginsService.getOpaqueIds.mockReturnValue(new Map());
window.performance.mark = jest.fn();
window.performance.clearMarks = jest.fn();
window.performance.getEntriesByName = jest.fn().mockReturnValue([
{
detail: 'load_started',
startTime: 456,
},
{
detail: 'bootstrap_started',
startTime: 123,
},
]);
});
function createCoreSystem(params = {}) {
@ -221,7 +234,9 @@ describe('#start()', () => {
});
await core.setup();
await core.start();
const services = await core.start();
await services?.application.navigateToApp('home');
}
it('clears the children of the rootDomElement and appends container for rendering service with #kibana-body, notifications, overlays', async () => {
@ -233,16 +248,22 @@ describe('#start()', () => {
);
});
it('reports the event Loaded Kibana', async () => {
it('reports the event Loaded Kibana and clears marks', async () => {
await startCore();
expect(analyticsServiceStartMock.reportEvent).toHaveBeenCalledTimes(1);
expect(analyticsServiceStartMock.reportEvent).toHaveBeenCalledWith('Loaded Kibana', {
kibana_version: '1.2.3',
load_started: 456,
bootstrap_started: 123,
});
expect(window.performance.clearMarks).toHaveBeenCalledTimes(1);
});
it('reports the event Loaded Kibana (with memory)', async () => {
fetchOptionalMemoryInfoMock.mockReturnValue({
load_started: 456,
bootstrap_started: 123,
memory_js_heap_size_limit: 3,
memory_js_heap_size_total: 2,
memory_js_heap_size_used: 1,
@ -251,6 +272,8 @@ describe('#start()', () => {
await startCore();
expect(analyticsServiceStartMock.reportEvent).toHaveBeenCalledTimes(1);
expect(analyticsServiceStartMock.reportEvent).toHaveBeenCalledWith('Loaded Kibana', {
load_started: 456,
bootstrap_started: 123,
kibana_version: '1.2.3',
memory_js_heap_size_limit: 3,
memory_js_heap_size_total: 2,

View file

@ -15,7 +15,7 @@ import {
} from '@kbn/core-injected-metadata-browser-internal';
import { DocLinksService } from '@kbn/core-doc-links-browser-internal';
import { ThemeService } from '@kbn/core-theme-browser-internal';
import type { AnalyticsServiceSetup } from '@kbn/core-analytics-browser';
import type { AnalyticsServiceSetup, AnalyticsServiceStart } from '@kbn/core-analytics-browser';
import { AnalyticsService } from '@kbn/core-analytics-browser-internal';
import { I18nService } from '@kbn/core-i18n-browser-internal';
import { CoreSetup, CoreStart } from '.';
@ -35,6 +35,7 @@ import { CoreApp } from './core_app';
import type { InternalApplicationSetup, InternalApplicationStart } from './application/types';
import { ExecutionContextService } from './execution_context';
import { fetchOptionalMemoryInfo } from './fetch_optional_memory_info';
import { KBN_LOAD_MARKS } from './utils';
interface Params {
rootDomElement: HTMLElement;
@ -124,6 +125,30 @@ export class CoreSystem {
this.plugins = new PluginsService(this.coreContext, injectedMetadata.uiPlugins);
this.coreApp = new CoreApp(this.coreContext);
performance.mark(KBN_LOAD_MARKS, {
detail: 'core_created',
});
}
private getLoadMarksInfo() {
if (!performance) return [];
const reportData: Record<string, number> = {};
const marks = performance.getEntriesByName(KBN_LOAD_MARKS);
for (const mark of marks) {
reportData[(mark as PerformanceMark).detail] = mark.startTime;
}
return reportData;
}
private reportKibanaLoadedEvent(analytics: AnalyticsServiceStart) {
analytics.reportEvent('Loaded Kibana', {
kibana_version: this.coreContext.env.packageInfo.version,
...fetchOptionalMemoryInfo(),
...this.getLoadMarksInfo(),
});
performance.clearMarks(KBN_LOAD_MARKS);
}
public async setup() {
@ -171,6 +196,10 @@ export class CoreSystem {
// Services that do not expose contracts at setup
await this.plugins.setup(core);
performance.mark(KBN_LOAD_MARKS, {
detail: 'setup_done',
});
return { fatalErrors: this.fatalErrorsSetup };
} catch (error) {
if (this.fatalErrorsSetup) {
@ -267,9 +296,19 @@ export class CoreSystem {
targetDomElement: coreUiTargetDomElement,
});
analytics.reportEvent('Loaded Kibana', {
kibana_version: this.coreContext.env.packageInfo.version,
...fetchOptionalMemoryInfo(),
performance.mark(KBN_LOAD_MARKS, {
detail: 'start_done',
});
// Wait for the first app navigation to report Kibana Loaded
const appSub = application.currentAppId$.subscribe((appId) => {
if (appId === undefined) return;
performance.mark(KBN_LOAD_MARKS, {
detail: 'first_app_nav',
});
this.reportKibanaLoadedEvent(analytics);
appSub.unsubscribe();
});
return {
@ -323,6 +362,33 @@ export class CoreSystem {
type: 'long',
_meta: { description: 'The used size of the heap', optional: true },
},
load_started: {
type: 'long',
_meta: { description: 'When the render template starts loading assets', optional: true },
},
bootstrap_started: {
type: 'long',
_meta: { description: 'When kbnBootstrap callback is called', optional: true },
},
core_created: {
type: 'long',
_meta: { description: 'When core system is created', optional: true },
},
setup_done: {
type: 'long',
_meta: { description: 'When core system setup is complete', optional: true },
},
start_done: {
type: 'long',
_meta: { description: 'When core system start is complete', optional: true },
},
first_app_nav: {
type: 'long',
_meta: {
description: 'When the application emits the first app navigation',
optional: true,
},
},
},
});
}

View file

@ -22,6 +22,7 @@ describe('kbn_bootstrap', () => {
beforeEach(() => {
jest.clearAllMocks();
window.performance.mark = jest.fn();
});
it('does not report a fatal error if apm load fails', async () => {

View file

@ -9,9 +9,14 @@
import { i18n } from '@kbn/i18n';
import { CoreSystem } from './core_system';
import { ApmSystem } from './apm_system';
import { KBN_LOAD_MARKS } from './utils';
/** @internal */
export async function __kbnBootstrap__() {
performance.mark(KBN_LOAD_MARKS, {
detail: 'bootstrap_started',
});
const injectedMetadata = JSON.parse(
document.querySelector('kbn-injected-metadata')!.getAttribute('data')!
);

View file

@ -1554,6 +1554,6 @@ export interface UserProvidedValues<T = any> {
// Warnings were encountered during analysis:
//
// src/core/public/core_system.ts:186:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts
// src/core/public/core_system.ts:202:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts
```

View file

@ -0,0 +1,10 @@
/*
* 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.
*/
/** @internal */
export const KBN_LOAD_MARKS = 'kbnLoad';

View file

@ -9,3 +9,4 @@
export { Sha256 } from './crypto';
export { MountWrapper, mountReactNode } from './mount';
export { CoreContextProvider } from './core_context_provider';
export { KBN_LOAD_MARKS } from './consts';

View file

@ -104,6 +104,10 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) {
});
}
performance.mark('kbnLoad', {
detail: 'load_started',
})
load([
'/js-1','/js-2'
], function () {

View file

@ -120,6 +120,10 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) {
});
}
performance.mark('kbnLoad', {
detail: 'load_started',
})
load([
${jsDependencyPaths.map((path) => `'${path}'`).join(',')}
], function () {

View file

@ -25,7 +25,22 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(event.properties).to.have.property('kibana_version');
expect(event.properties.kibana_version).to.be.a('string');
// Kibana Loaded timings
expect(event.properties).to.have.property('load_started');
expect(event.properties.load_started).to.be.a('number');
expect(event.properties).to.have.property('bootstrap_started');
expect(event.properties.bootstrap_started).to.be.a('number');
expect(event.properties).to.have.property('core_created');
expect(event.properties.core_created).to.be.a('number');
expect(event.properties).to.have.property('setup_done');
expect(event.properties.setup_done).to.be.a('number');
expect(event.properties).to.have.property('start_done');
expect(event.properties.start_done).to.be.a('number');
expect(event.properties).to.have.property('first_app_nav');
expect(event.properties.start_done).to.be.a('number');
if (browser.isChromium) {
// Kibana Loaded memory
expect(event.properties).to.have.property('memory_js_heap_size_limit');
expect(event.properties.memory_js_heap_size_limit).to.be.a('number');
expect(event.properties).to.have.property('memory_js_heap_size_total');