mapbox-gl
Version:
A WebGL interactive maps library
184 lines (163 loc) • 7.38 kB
JavaScript
// @flow
import {version as sdkVersion} from '../../package.json';
import {
isMapboxHTTPStyleURL,
isMapboxHTTPTileJSONURL,
isMapboxHTTPSpriteURL,
isMapboxHTTPFontsURL,
isMapboxHTTPCDNURL
} from './mapbox.js';
type LivePerformanceMetrics = {
counters: Array<Object>,
metadata: Array<Object>,
attributes: Array<Object>
};
export type LivePerformanceData = {
interactionRange: [number, number],
visibilityHidden: number,
width: number,
height: number,
terrainEnabled: boolean,
fogEnabled: boolean,
projection: string,
zoom: number,
renderer: ?string,
vendor: ?string
};
export const PerformanceMarkers = {
create: 'create',
load: 'load',
fullLoad: 'fullLoad'
};
export const LivePerformanceUtils = {
mark(marker: $Keys<typeof PerformanceMarkers>) {
performance.mark(marker);
},
measure(name: string, begin?: string, end?: string) {
performance.measure(name, begin, end);
}
};
function categorize(arr: Array<PerformanceResourceTiming>, fn: (entry: PerformanceResourceTiming) => string): {[string]: Array<PerformanceResourceTiming>} {
const obj = {};
if (arr) {
for (const item of arr) {
const category = fn(item);
if (obj[category] === undefined) {
obj[category] = [];
}
obj[category].push(item);
}
}
return obj;
}
function getCountersPerResourceType(resourceTimers: { [string]: Array<PerformanceResourceTiming> }) {
const obj = {};
if (resourceTimers) {
for (const category in resourceTimers) {
if (category !== 'other') {
for (const timer of resourceTimers[category]) {
const min = `${category}ResolveRangeMin`;
const max = `${category}ResolveRangeMax`;
const reqCount = `${category}RequestCount`;
const reqCachedCount = `${category}RequestCachedCount`;
// Resource -TransferStart and -TransferEnd represent the wall time
// between the start of a request to when the data is available
obj[min] = Math.min(obj[min] || +Infinity, timer.startTime);
obj[max] = Math.max(obj[max] || -Infinity, timer.responseEnd);
const increment = (key: string) => {
if (obj[key] === undefined) {
obj[key] = 0;
}
++obj[key];
};
const transferSizeSupported = timer.transferSize !== undefined;
if (transferSizeSupported) {
const resourceFetchedFromCache = (timer.transferSize === 0);
if (resourceFetchedFromCache) {
increment(reqCachedCount);
}
}
increment(reqCount);
}
}
}
}
return obj;
}
function getResourceCategory(entry: PerformanceResourceTiming): string {
const url = entry.name.split('?')[0];
if (isMapboxHTTPCDNURL(url) && url.includes('mapbox-gl.js')) return 'javascript';
if (isMapboxHTTPCDNURL(url) && url.includes('mapbox-gl.css')) return 'css';
if (isMapboxHTTPFontsURL(url)) return 'fontRange';
if (isMapboxHTTPSpriteURL(url)) return 'sprite';
if (isMapboxHTTPStyleURL(url)) return 'style';
if (isMapboxHTTPTileJSONURL(url)) return 'tilejson';
return 'other';
}
function getStyle(resourceTimers: Array<PerformanceResourceTiming>): ?string {
if (resourceTimers) {
for (const timer of resourceTimers) {
const url = timer.name.split('?')[0];
if (isMapboxHTTPStyleURL(url)) {
const split = url.split('/').slice(-2);
if (split.length === 2) {
return `mapbox://styles/${split[0]}/${split[1]}`;
}
}
}
}
}
export function getLivePerformanceMetrics(data: LivePerformanceData): LivePerformanceMetrics {
const resourceTimers = ((performance.getEntriesByType('resource'): any): Array<PerformanceResourceTiming>);
const markerTimers = performance.getEntriesByType('mark');
const resourcesByType = categorize(resourceTimers, getResourceCategory);
const counters = getCountersPerResourceType(resourcesByType);
const devicePixelRatio = window.devicePixelRatio;
// $FlowFixMe[prop-missing] no connection types in Flow
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
const effectiveType = connection ? (connection: any).effectiveType : undefined;
const metrics: LivePerformanceMetrics = {counters: [], metadata: [], attributes: []};
// Please read carefully before adding or modifying the following metrics:
// https://github.com/mapbox/gl-js-team/blob/main/docs/live_performance_metrics.md
const addMetric = (arr: Array<{| name: string, value: string |}>, name: string, value: ?(number | string)) => {
if (value !== undefined && value !== null) {
arr.push({name, value: value.toString()});
}
};
for (const counter in counters) {
addMetric(metrics.counters, counter, counters[counter]);
}
if (data.interactionRange[0] !== +Infinity && data.interactionRange[1] !== -Infinity) {
addMetric(metrics.counters, "interactionRangeMin", data.interactionRange[0]);
addMetric(metrics.counters, "interactionRangeMax", data.interactionRange[1]);
}
if (markerTimers) {
for (const marker of Object.keys(PerformanceMarkers)) {
const markerName = PerformanceMarkers[marker];
const markerTimer = markerTimers.find((entry) => entry.name === markerName);
if (markerTimer) {
addMetric(metrics.counters, markerName, markerTimer.startTime);
}
}
}
addMetric(metrics.counters, "visibilityHidden", data.visibilityHidden);
addMetric(metrics.attributes, "style", getStyle(resourceTimers));
addMetric(metrics.attributes, "terrainEnabled", data.terrainEnabled ? "true" : "false");
addMetric(metrics.attributes, "fogEnabled", data.fogEnabled ? "true" : "false");
addMetric(metrics.attributes, "projection", data.projection);
addMetric(metrics.attributes, "zoom", data.zoom);
addMetric(metrics.metadata, "devicePixelRatio", devicePixelRatio);
addMetric(metrics.metadata, "connectionEffectiveType", effectiveType);
addMetric(metrics.metadata, "navigatorUserAgent", navigator.userAgent);
addMetric(metrics.metadata, "screenWidth", window.screen.width);
addMetric(metrics.metadata, "screenHeight", window.screen.height);
addMetric(metrics.metadata, "windowWidth", window.innerWidth);
addMetric(metrics.metadata, "windowHeight", window.innerHeight);
addMetric(metrics.metadata, "mapWidth", data.width / devicePixelRatio);
addMetric(metrics.metadata, "mapHeight", data.height / devicePixelRatio);
addMetric(metrics.metadata, "webglRenderer", data.renderer);
addMetric(metrics.metadata, "webglVendor", data.vendor);
addMetric(metrics.metadata, "sdkVersion", sdkVersion);
addMetric(metrics.metadata, "sdkIdentifier", "mapbox-gl-js");
return metrics;
}