Added mobile synthtrace scenario (#146985)

Extended APM synthtrace to support mobile use cases and added a mobile
scenario that generates trace data.

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Kate Patticha <aikaterini.patticha@elastic.co>
This commit is contained in:
Alexander Wert 2022-12-07 15:28:56 +01:00 committed by GitHub
parent bc9ee32e3d
commit 6872f82a52
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 768 additions and 23 deletions

View file

@ -41,14 +41,37 @@ export interface Observer {
version_major: number;
}
export interface GeoLocation {
coordinates: number[];
type: string;
}
export type ApmFields = Fields &
Partial<{
'timestamp.us'?: number;
'agent.name': string;
'agent.version': string;
'client.geo.city_name': string;
'client.geo.continent_name': string;
'client.geo.country_iso_code': string;
'client.geo.country_name': string;
'client.geo.region_iso_code': string;
'client.geo.region_name': string;
'client.geo.location': GeoLocation;
'client.ip': string;
'cloud.provider': string;
'cloud.project.name': string;
'cloud.service.name': string;
'cloud.availability_zone': string;
'cloud.machine.type': string;
'cloud.region': string;
'container.id': string;
'destination.address': string;
'destination.port': number;
'device.id': string;
'device.model.identifier': string;
'device.model.name': string;
'device.manufacturer': string;
'ecs.version': string;
'event.outcome': string;
'event.ingested': number;
@ -56,18 +79,36 @@ export type ApmFields = Fields &
'error.exception': ApmException[];
'error.grouping_name': string;
'error.grouping_key': string;
'faas.id': string;
'faas.name': string;
'faas.coldstart': boolean;
'faas.execution': string;
'faas.trigger.type': string;
'faas.trigger.request_id': string;
'host.name': string;
'host.architecture': string;
'host.hostname': string;
'host.os.full': string;
'host.os.name': string;
'host.os.platform': string;
'host.os.type': string;
'host.os.version': string;
'http.request.method': string;
'http.response.status_code': number;
'kubernetes.pod.uid': string;
'kubernetes.pod.name': string;
'metricset.name': string;
observer: Observer;
'network.connection.type': string;
'network.connection.subtype': string;
'network.carrier.name': string;
'network.carrier.mcc': string;
'network.carrier.mnc': string;
'network.carrier.icc': string;
'parent.id': string;
'processor.event': string;
'processor.name': string;
'session.id': string;
'trace.id': string;
'transaction.name': string;
'transaction.type': string;
@ -86,6 +127,7 @@ export type ApmFields = Fields &
'service.runtime.name': string;
'service.runtime.version': string;
'service.framework.name': string;
'service.framework.version': string;
'service.target.name': string;
'service.target.type': string;
'span.action': string;
@ -103,18 +145,12 @@ export type ApmFields = Fields &
trace: { id: string };
span: { id: string };
}>;
'cloud.provider': string;
'cloud.project.name': string;
'cloud.service.name': string;
'cloud.availability_zone': string;
'cloud.machine.type': string;
'cloud.region': string;
'host.os.platform': string;
'faas.id': string;
'faas.name': string;
'faas.coldstart': boolean;
'faas.execution': string;
'faas.trigger.type': string;
'faas.trigger.request_id': string;
'url.original': string;
}> &
ApmApplicationMetricFields;
export type SpanParams = {
spanName: string;
spanType: string;
spanSubtype?: string;
} & ApmFields;

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
import { service } from './service';
import { mobileApp } from './mobile_app';
import { browser } from './browser';
import { serverlessFunction } from './serverless_function';
import { getTransactionMetrics } from './processors/get_transaction_metrics';
@ -20,6 +21,7 @@ import type { ApmException } from './apm_fields';
export const apm = {
service,
mobileApp,
browser,
getTransactionMetrics,
getSpanDestinationMetrics,

View file

@ -11,13 +11,7 @@ import { Entity } from '../entity';
import { Metricset } from './metricset';
import { Span } from './span';
import { Transaction } from './transaction';
import { ApmApplicationMetricFields, ApmFields } from './apm_fields';
export type SpanParams = {
spanName: string;
spanType: string;
spanSubtype?: string;
} & ApmFields;
import { ApmApplicationMetricFields, ApmFields, SpanParams } from './apm_fields';
export class Instance extends Entity<ApmFields> {
transaction(

View file

@ -0,0 +1,48 @@
/*
* 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 { Entity } from '../entity';
import { ApmFields } from './apm_fields';
import { MobileDevice } from './mobile_device';
import { generateLongId } from '../utils/generate_id';
type MobileAgentName = 'android/java' | 'iOS/swift';
export class MobileApp extends Entity<ApmFields> {
mobileDevice(deviceId?: string) {
return new MobileDevice({
...this.fields,
'device.id': deviceId ? deviceId : generateLongId(),
'service.language.name': this.fields['agent.name'] === 'iOS' ? 'swift' : 'java',
'service.version': this.fields['service.version'] ?? '1.0',
}).startNewSession();
}
}
export function mobileApp(name: string, environment: string, agentName: MobileAgentName): MobileApp;
export function mobileApp(options: {
name: string;
environment: string;
agentName: MobileAgentName;
}): MobileApp;
export function mobileApp(
...args:
| [{ name: string; environment: string; agentName: MobileAgentName }]
| [string, string, MobileAgentName]
) {
const [name, environment, agentName] =
args.length === 1 ? [args[0].name, args[0].environment, args[0].agentName] : args;
return new MobileApp({
'service.name': name,
'service.environment': environment,
'agent.name': agentName,
});
}

View file

@ -0,0 +1,240 @@
/*
* 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 { Entity } from '../entity';
import { Span } from './span';
import { Transaction } from './transaction';
import { ApmFields, SpanParams, GeoLocation } from './apm_fields';
import { generateLongId } from '../utils/generate_id';
export interface DeviceInfo {
manufacturer: string;
modelIdentifier: string;
modelName?: string;
}
export interface OSInfo {
osType: 'ios' | 'android';
osVersion: string;
osFull?: string;
runtimeVersion?: string;
}
export interface NetworkConnectionInfo {
type: 'unavailable' | 'wifi' | 'wired' | 'cell' | 'unknown';
subType?: string;
carrierName?: string;
carrierMCC?: string;
carrierMNC?: string;
carrierICC?: string;
}
export interface GeoInfo {
clientIp: string;
cityName?: string;
continentName?: string;
countryIsoCode?: string;
countryName?: string;
regionName?: string;
regionIsoCode?: string;
location?: GeoLocation;
}
export class MobileDevice extends Entity<ApmFields> {
networkConnection: NetworkConnectionInfo;
constructor(public readonly fields: ApmFields) {
super(fields);
this.networkConnection = { type: 'unavailable' };
}
deviceInfo(...options: [DeviceInfo] | [string, string] | [string, string, string]) {
let manufacturer: string;
let modelIdentifier: string;
let modelName: string | undefined;
if (options.length === 3) {
manufacturer = options[0];
modelIdentifier = options[1];
modelName = options[2];
} else if (options.length === 2) {
manufacturer = options[0];
modelIdentifier = options[1];
} else {
manufacturer = options[0].manufacturer;
modelIdentifier = options[0].modelIdentifier;
modelName = options[0].modelName;
}
this.fields['device.manufacturer'] = manufacturer;
this.fields['device.model.identifier'] = modelIdentifier;
this.fields['device.model.name'] = modelName;
return this;
}
osInfo(
...options:
| [OSInfo]
| [string, string]
| [string, string, string]
| [string, string, string, string]
) {
let osType: string;
let osVersion: string;
let osFull: string | undefined;
let runtimeVersion: string | undefined;
if (options.length === 4) {
osType = options[0];
osVersion = options[1];
osFull = options[2];
runtimeVersion = options[3];
} else if (options.length === 3) {
osType = options[0];
osVersion = options[1];
osFull = options[2];
} else if (options.length === 2) {
osType = options[0];
osVersion = options[1];
} else {
osType = options[0].osType;
osVersion = options[0].osVersion;
osFull = options[0].osFull;
runtimeVersion = options[0].runtimeVersion;
}
this.fields['host.os.type'] = osType;
this.fields['host.os.name'] = osType === 'ios' ? 'iOS' : 'Android';
this.fields['host.os.version'] = osVersion;
this.fields['host.os.full'] = osFull;
this.fields['service.runtime.name'] = osType === 'ios' ? 'iOS' : 'Android Runtime';
this.fields['service.runtime.version'] = runtimeVersion ?? osVersion;
return this;
}
startNewSession() {
this.fields['session.id'] = generateLongId();
return this;
}
setNetworkConnection(networkInfo: NetworkConnectionInfo) {
this.networkConnection = networkInfo;
return this;
}
setGeoInfo(geoInfo: GeoInfo) {
if (geoInfo) {
this.fields['client.ip'] = geoInfo.clientIp;
this.fields['client.geo.city_name'] = geoInfo.cityName;
this.fields['client.geo.country_name'] = geoInfo.countryName;
this.fields['client.geo.country_iso_code'] = geoInfo.countryIsoCode;
this.fields['client.geo.continent_name'] = geoInfo.continentName;
this.fields['client.geo.region_name'] = geoInfo.regionName;
this.fields['client.geo.region_iso_code'] = geoInfo.regionIsoCode;
this.fields['client.geo.location'] = geoInfo.location;
}
return this;
}
transaction(
...options:
| [{ transactionName: string; frameworkName?: string; frameworkVersion?: string }]
| [string]
| [string, string]
| [string, string, string]
) {
let transactionName: string;
let frameworkName: string | undefined;
let frameworkVersion: string | undefined;
if (options.length === 3) {
transactionName = options[0];
frameworkName = options[1];
frameworkVersion = options[2];
} else if (options.length === 2) {
transactionName = options[0];
frameworkName = options[1];
} else if (typeof options[0] === 'string') {
transactionName = options[0];
} else {
transactionName = options[0].transactionName;
frameworkName = options[0].frameworkName;
frameworkVersion = options[0].frameworkVersion;
}
return new Transaction({
...this.fields,
'transaction.name': transactionName,
'transaction.type': 'mobile',
'service.framework.name': frameworkName,
'service.framework.version': frameworkVersion,
});
}
span(...options: [string, string] | [string, string, string] | [SpanParams]) {
let spanName: string;
let spanType: string;
let spanSubtype: string;
let fields: ApmFields;
if (options.length === 3 || options.length === 2) {
spanName = options[0];
spanType = options[1];
spanSubtype = options[2] || 'unknown';
fields = {};
} else {
({ spanName, spanType, spanSubtype = 'unknown', ...fields } = options[0]);
}
return new Span({
...this.fields,
...fields,
'span.name': spanName,
'span.type': spanType,
'span.subtype': spanSubtype,
});
}
httpSpan(
...options:
| [{ spanName: string; httpMethod: string; httpUrl: string }]
| [string, string, string]
) {
let spanName: string;
let httpMethod: string;
let httpUrl: string;
if (options.length === 3) {
spanName = options[0];
httpMethod = options[1];
httpUrl = options[2];
} else {
spanName = options[0].spanName;
httpMethod = options[0].httpMethod;
httpUrl = options[0].httpUrl;
}
let spanParameters: SpanParams = {
spanName,
spanType: 'external',
spanSubtype: 'http',
'http.request.method': httpMethod,
'url.original': httpUrl,
};
if (this.networkConnection) {
spanParameters = {
...spanParameters,
'network.connection.type': this.networkConnection.type,
'network.connection.subtype': this.networkConnection.subType,
'network.carrier.name': this.networkConnection.carrierName,
'network.carrier.mcc': this.networkConnection.carrierMCC,
'network.carrier.mnc': this.networkConnection.carrierMNC,
'network.carrier.icc': this.networkConnection.carrierICC,
};
}
return this.span(spanParameters);
}
}

View file

@ -9,8 +9,7 @@
import url from 'url';
import { BaseSpan } from './base_span';
import { generateShortId } from '../utils/generate_id';
import { ApmFields } from './apm_fields';
import { SpanParams } from './instance';
import { ApmFields, SpanParams } from './apm_fields';
export class Span extends BaseSpan {
constructor(fields: ApmFields) {

View file

@ -9,8 +9,9 @@
import { times } from 'lodash';
import { elasticsearchSpan, httpExitSpan, HttpMethod, redisSpan, sqliteSpan } from '../apm/span';
import { BaseSpan } from '../apm/base_span';
import { Instance, SpanParams } from '../apm/instance';
import { Instance } from '../apm/instance';
import { Transaction } from '../apm/transaction';
import { SpanParams } from '../apm/apm_fields';
export class DistributedTrace {
timestamp: number;

View file

@ -0,0 +1,425 @@
/*
* 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 { apm, timerange } from '../..';
import {
DeviceInfo,
MobileDevice,
OSInfo,
GeoInfo,
NetworkConnectionInfo,
} from '../lib/apm/mobile_device';
import { ApmFields } from '../lib/apm/apm_fields';
import { Scenario } from '../cli/scenario';
import { getLogger } from '../cli/utils/get_common_services';
import { RunOptions } from '../cli/utils/parse_run_cli_flags';
import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment';
const ENVIRONMENT = getSynthtraceEnvironment(__filename);
type DeviceMetadata = DeviceInfo & OSInfo;
const ANDROID_DEVICES: DeviceMetadata[] = [
{
manufacturer: 'Samsung',
modelIdentifier: 'SM-G930F',
modelName: 'Galaxy S7',
osType: 'android',
osVersion: '10',
osFull: 'Android 10, API level 29, BUILD A022MUBU2AUD1',
runtimeVersion: '2.1.0',
},
{
manufacturer: 'Samsung',
modelIdentifier: 'SM-G973F',
modelName: 'Galaxy S10',
osType: 'android',
osVersion: '13',
osFull: 'Android 13, API level 3, BUILD X0ETMUBU2AER2',
runtimeVersion: '2.0.0',
},
{
manufacturer: 'Samsung',
modelIdentifier: 'SM-N950F',
modelName: 'Galaxy Note8',
osType: 'android',
osVersion: '10',
osFull: 'Android 10, API level 29, BUILD A022MUBU2AUD1',
runtimeVersion: '2.1.0',
},
{
manufacturer: 'Huawei',
modelIdentifier: 'HUAWEI P2-0000',
osType: 'android',
osVersion: '10',
osFull: 'Android 10, API level 29, BUILD A022MUBU2AUD1',
runtimeVersion: '2.1.0',
},
{
manufacturer: 'Huawei',
modelIdentifier: 'HUAWEI NXT-CL00',
modelName: 'Mate8',
osType: 'android',
osVersion: '10',
osFull: 'Android 10, API level 29, BUILD A022MUBU2AUD1',
runtimeVersion: '2.1.0',
},
{
manufacturer: 'Huawei',
modelIdentifier: 'T1-701u',
modelName: 'MediaPad',
osType: 'android',
osVersion: '10',
osFull: 'Android 13, API level 29, BUILD X0ETMUBU2AER2',
runtimeVersion: '2.0.0',
},
{
manufacturer: 'Google',
modelIdentifier: 'Pixel 3a',
modelName: 'Pixel 3a',
osType: 'android',
osVersion: '10',
osFull: 'Android 10, API level 29, BUILD A022MUBU2AUD1',
runtimeVersion: '2.1.0',
},
{
manufacturer: 'Google',
modelIdentifier: 'Pixel 7 Pro',
modelName: 'Pixel 7 Pro',
osType: 'android',
osVersion: '10',
osFull: 'Android 10, API level 29, BUILD A022MUBU2AUD1',
runtimeVersion: '2.1.0',
},
{
manufacturer: 'LGE',
modelIdentifier: 'LG G6',
modelName: 'LG-LS993',
osType: 'android',
osVersion: '10',
osFull: 'Android 12, API level 31, BUILD KJSJADSlKSDAA',
runtimeVersion: '1.9.0',
},
{
manufacturer: 'LGE',
modelIdentifier: 'LG K10',
modelName: 'LG-K425',
osType: 'android',
osVersion: '10',
osFull: 'Android 12, API level 32, BUILD KJSJA342SlKSD',
runtimeVersion: '1.9.0',
},
];
const APPLE_DEVICES: DeviceMetadata[] = [
{
manufacturer: 'Apple',
modelIdentifier: 'iPhone15,2',
modelName: 'iPhone 14 Pro',
osType: 'ios',
osVersion: '16',
osFull: 'iOS 16',
},
{
manufacturer: 'Apple',
modelIdentifier: 'iPhone14,5',
modelName: 'iPhone 13',
osType: 'ios',
osVersion: '15',
osFull: 'iOS 15',
},
{
manufacturer: 'Apple',
modelIdentifier: 'iPhone11,8',
modelName: 'iPhone XR',
osType: 'ios',
osVersion: '11',
osFull: 'iOS 11',
},
{
manufacturer: 'Apple',
modelIdentifier: 'iPhone9,1',
modelName: 'iPhone 7',
osType: 'ios',
osVersion: '9',
osFull: 'iOS 9',
},
{
manufacturer: 'Apple',
modelIdentifier: 'iPhone13,2',
modelName: 'iPhone 12',
osType: 'ios',
osVersion: '13',
osFull: 'iOS 13',
},
{
manufacturer: 'Apple',
modelIdentifier: 'iPad12,2',
modelName: 'iPad 9th Gen',
osType: 'ios',
osVersion: '12',
osFull: 'iOS 12',
},
{
manufacturer: 'Apple',
modelIdentifier: 'iPad13,7',
modelName: 'iPad Pro 11 inch 5th Gen',
osType: 'ios',
osVersion: '13',
osFull: 'iPadOS 13',
},
{
manufacturer: 'Apple',
modelIdentifier: 'iPad13,11',
modelName: 'iPad Pro 12.9 inch 5th Gen',
osType: 'ios',
osVersion: '13',
osFull: 'iPadOS 13',
},
{
manufacturer: 'Apple',
modelIdentifier: 'iPad13,19',
modelName: 'iPad 10th Gen',
osType: 'ios',
osVersion: '13',
osFull: 'iPadOS 13',
},
{
manufacturer: 'Apple',
modelIdentifier: 'Watch6,8',
modelName: 'Apple Watch Series 7 41mm case',
osType: 'ios',
osVersion: '6',
osFull: 'WatchOS 6',
},
];
type GeoAndNetwork = GeoInfo & NetworkConnectionInfo;
const GEO_AND_NETWORK: GeoAndNetwork[] = [
{
clientIp: '223.72.43.22',
cityName: 'Beijing',
continentName: 'Asia',
countryIsoCode: 'CN',
countryName: 'China',
regionIsoCode: 'CN-BJ',
regionName: 'Beijing',
location: { coordinates: [116.3861, 39.9143], type: 'Point' },
type: 'wifi',
},
{
clientIp: '20.24.184.101',
cityName: 'Singapore',
continentName: 'Asia',
countryIsoCode: 'SG',
countryName: 'Singapore',
location: { coordinates: [103.8554, 1.3036], type: 'Point' },
type: 'cell',
subType: 'edge',
carrierName: 'M1 Limited',
carrierMNC: '03',
carrierICC: 'SG',
carrierMCC: '525',
},
{
clientIp: '178.173.228.103',
cityName: 'Tokyo',
continentName: 'Asia',
countryIsoCode: 'JP',
countryName: 'Japan',
regionIsoCode: 'JP-13',
regionName: 'Tokyo',
location: { coordinates: [139.7425, 35.6164], type: 'Point' },
type: 'cell',
subType: 'edge',
carrierName: 'Osaka Gas Business Create Co., Ltd.',
carrierMNC: '17',
carrierICC: 'JP',
carrierMCC: '440',
},
{
clientIp: '147.161.184.179',
cityName: 'Paris',
continentName: 'Europe',
countryIsoCode: 'FR',
countryName: 'France',
regionIsoCode: 'FR-75',
regionName: 'Paris',
location: { coordinates: [2.4075, 48.8323], type: 'Point' },
type: 'cell',
subType: 'hspa',
carrierName: 'Altice',
carrierMNC: '09',
carrierICC: 'FR',
carrierMCC: '208',
},
{
clientIp: '34.136.92.88',
cityName: 'Council Bluffs',
continentName: 'North America',
countryIsoCode: 'US',
countryName: 'United States',
regionIsoCode: 'US-IA',
regionName: 'Iowa',
location: { coordinates: [-95.8517, 41.2591], type: 'Point' },
type: 'cell',
subType: 'lte',
carrierName: 'Midwest Network Solutions Hub LLC',
carrierMNC: '070',
carrierICC: 'US',
carrierMCC: '313',
},
{
clientIp: '163.116.135.123',
cityName: 'New York',
continentName: 'North America',
countryIsoCode: 'US',
countryName: 'United States',
regionIsoCode: 'US-NY',
regionName: 'New York',
location: { coordinates: [-73.9877, 40.7425], type: 'Point' },
type: 'cell',
subType: 'lte',
carrierName: 'Texas A&M University',
carrierMNC: '080',
carrierICC: 'US',
carrierMCC: '314',
},
{
clientIp: '163.116.178.27',
cityName: 'Frankfurt am Main',
continentName: 'Europe',
countryIsoCode: 'DE',
countryName: 'Germany',
regionIsoCode: 'DE-HE',
regionName: 'Hesse',
location: { coordinates: [8.6843, 50.1188], type: 'Point' },
type: 'cell',
subType: 'lte',
carrierName: 'Telekom Deutschland GmbH',
carrierMNC: '06',
carrierICC: 'DE',
carrierMCC: '262',
},
];
function randomInt(max: number) {
return Math.floor(Math.random() * max);
}
const scenario: Scenario<ApmFields> = async (runOptions: RunOptions) => {
const logger = getLogger(runOptions);
const { numDevices = 10 } = runOptions.scenarioOpts || {};
return {
generate: ({ from, to }) => {
const range = timerange(from, to);
const androidDevices = [...Array(numDevices).keys()].map((index) => {
const deviceMetadata = ANDROID_DEVICES[randomInt(ANDROID_DEVICES.length)];
const geoNetwork = GEO_AND_NETWORK[randomInt(GEO_AND_NETWORK.length)];
return apm
.mobileApp({ name: 'synth-android', environment: ENVIRONMENT, agentName: 'android/java' })
.mobileDevice()
.deviceInfo(deviceMetadata)
.osInfo(deviceMetadata)
.setGeoInfo(geoNetwork)
.setNetworkConnection(geoNetwork);
});
const iOSDevices = [...Array(numDevices).keys()].map((index) => {
const deviceMetadata = APPLE_DEVICES[randomInt(APPLE_DEVICES.length)];
const geoNetwork = GEO_AND_NETWORK[randomInt(GEO_AND_NETWORK.length)];
return apm
.mobileApp({ name: 'synth-ios', environment: ENVIRONMENT, agentName: 'iOS/swift' })
.mobileDevice()
.deviceInfo(deviceMetadata)
.osInfo(deviceMetadata)
.setGeoInfo(geoNetwork)
.setNetworkConnection(geoNetwork);
});
const clickRate = range.ratePerMinute(2);
const sessionTransactions = (device: MobileDevice) => {
return clickRate.generator((timestamp, index) => {
device.startNewSession();
const framework =
device.fields['device.manufacturer'] === 'Apple' ? 'iOS' : 'Android Activity';
return [
device
.transaction('Start View - View Appearing', framework)
.timestamp(timestamp)
.duration(500)
.success()
.children(
device
.span({
spanName: 'onCreate',
spanType: 'app',
spanSubtype: 'internal',
})
.duration(50)
.success()
.timestamp(timestamp + 20),
device
.httpSpan({
spanName: 'GET backend:1234',
httpMethod: 'GET',
httpUrl: 'https://backend:1234/api/start',
})
.duration(800)
.success()
.timestamp(timestamp + 400)
),
device
.transaction('Second View - View Appearing', framework)
.timestamp(10000 + timestamp)
.duration(300)
.success()
.children(
device
.httpSpan({
spanName: 'GET backend:1234',
httpMethod: 'GET',
httpUrl: 'https://backend:1234/api/second',
})
.duration(400)
.success()
.timestamp(10000 + timestamp + 250)
),
device
.transaction('Third View - View Appearing', framework)
.timestamp(20000 + timestamp)
.duration(300)
.success()
.children(
device
.span({
spanName: 'onCreate',
spanType: 'app',
spanSubtype: 'internal',
})
.duration(50)
.success()
.timestamp(20000 + timestamp + 20)
),
];
});
};
return [...androidDevices, ...iOSDevices]
.map((device) => logger.perf('generating_apm_events', () => sessionTransactions(device)))
.reduce((p, c) => p.merge(c));
},
};
};
export default scenario;