diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-21 02:50:22 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-21 02:50:22 +0300 |
commit | 9dc93a4519d9d5d7be48ff274127136236a3adb3 (patch) | |
tree | 70467ae3692a0e35e5ea56bcb803eb512a10bedb /app/assets/javascripts/performance_bar | |
parent | 4b0f34b6d759d6299322b3a54453e930c6121ff0 (diff) |
Add latest changes from gitlab-org/gitlab@13-11-stable-eev13.11.0-rc43
Diffstat (limited to 'app/assets/javascripts/performance_bar')
7 files changed, 146 insertions, 41 deletions
diff --git a/app/assets/javascripts/performance_bar/components/detailed_metric.vue b/app/assets/javascripts/performance_bar/components/detailed_metric.vue index 9bf77239a6b..e5b26a00c4c 100644 --- a/app/assets/javascripts/performance_bar/components/detailed_metric.vue +++ b/app/assets/javascripts/performance_bar/components/detailed_metric.vue @@ -1,5 +1,8 @@ <script> -import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui'; +import { GlButton, GlModal, GlModalDirective, GlSegmentedControl } from '@gitlab/ui'; + +import { s__ } from '~/locale'; +import { sortOrders, sortOrderOptions } from '../constants'; import RequestWarning from './request_warning.vue'; export default { @@ -7,6 +10,7 @@ export default { RequestWarning, GlButton, GlModal, + GlSegmentedControl, }, directives: { 'gl-modal': GlModalDirective, @@ -39,6 +43,7 @@ export default { data() { return { openedBacktraces: [], + sortOrder: sortOrders.DURATION, }; }, computed: { @@ -48,13 +53,43 @@ export default { metricDetails() { return this.currentRequest.details[this.metric]; }, + metricDetailsSummary() { + const summary = {}; + + if (!this.metricDetails.summaryOptions?.hideTotal) { + summary[s__('Total')] = this.metricDetails.calls; + } + + if (!this.metricDetails.summaryOptions?.hideDuration) { + summary[s__('PerformanceBar|Total duration')] = this.metricDetails.duration; + } + + return { ...summary, ...(this.metricDetails.summary || {}) }; + }, metricDetailsLabel() { - return this.metricDetails.duration - ? `${this.metricDetails.duration} / ${this.metricDetails.calls}` - : this.metricDetails.calls; + if (this.metricDetails.duration && this.metricDetails.calls) { + return `${this.metricDetails.duration} / ${this.metricDetails.calls}`; + } else if (this.metricDetails.calls) { + return this.metricDetails.calls; + } + + return '0'; + }, + displaySortOrder() { + return ( + this.metricDetails.details.length !== 0 && + this.metricDetails.details.every((item) => item.start) + ); }, detailsList() { - return this.metricDetails.details; + return this.metricDetails.details.map((item, index) => ({ ...item, id: index })); + }, + sortedList() { + if (this.sortOrder === sortOrders.CHRONOLOGICAL) { + return this.detailsList.slice().sort(this.sortDetailChronologically); + } + + return this.detailsList.slice().sort(this.sortDetailByDuration); }, warnings() { return this.metricDetails.warnings || []; @@ -82,7 +117,17 @@ export default { itemHasOpenedBacktrace(toggledIndex) { return this.openedBacktraces.find((openedIndex) => openedIndex === toggledIndex) >= 0; }, + changeSortOrder(order) { + this.sortOrder = order; + }, + sortDetailByDuration(a, b) { + return a.duration < b.duration ? 1 : -1; + }, + sortDetailChronologically(a, b) { + return a.start < b.start ? -1 : 1; + }, }, + sortOrderOptions, }; </script> <template> @@ -93,18 +138,41 @@ export default { data-qa-selector="detailed_metric_content" > <gl-button v-gl-modal="modalId" class="gl-mr-2" type="button" variant="link"> - <span class="gl-text-blue-300 gl-font-weight-bold">{{ metricDetailsLabel }}</span> + <span + class="gl-text-blue-200 gl-font-weight-bold" + data-testid="performance-bar-details-label" + > + {{ metricDetailsLabel }} + </span> </gl-button> <gl-modal :modal-id="modalId" :title="header" size="lg" footer-class="d-none" scrollable> + <div class="gl-display-flex gl-align-items-center gl-justify-content-space-between"> + <div class="gl-display-flex gl-align-items-center" data-testid="performance-bar-summary"> + <div v-for="(value, name) in metricDetailsSummary" :key="name" class="gl-pr-8"> + <div v-if="value" data-testid="performance-bar-summary-item"> + <div>{{ name }}</div> + <div class="gl-font-size-h1 gl-font-weight-bold">{{ value }}</div> + </div> + </div> + </div> + <gl-segmented-control + v-if="displaySortOrder" + data-testid="performance-bar-sort-order" + :options="$options.sortOrderOptions" + :checked="sortOrder" + @input="changeSortOrder" + /> + </div> + <hr /> <table class="table gl-table"> - <template v-if="detailsList.length"> - <tr v-for="(item, index) in detailsList" :key="index"> - <td> + <template v-if="sortedList.length"> + <tr v-for="item in sortedList" :key="item.id"> + <td data-testid="performance-item-duration"> <span v-if="item.duration">{{ sprintf(__('%{duration}ms'), { duration: item.duration }) }}</span> </td> - <td> + <td data-testid="performance-item-content"> <div> <div v-for="(key, keyIndex) in keys" @@ -121,12 +189,12 @@ export default { variant="default" icon="ellipsis_h" size="small" - :selected="itemHasOpenedBacktrace(index)" + :selected="itemHasOpenedBacktrace(item.id)" :aria-label="__('Toggle backtrace')" - @click="toggleBacktrace(index)" + @click="toggleBacktrace(item.id)" /> </div> - <pre v-if="itemHasOpenedBacktrace(index)" class="backtrace-row mt-2">{{ + <pre v-if="itemHasOpenedBacktrace(item.id)" class="backtrace-row gl-mt-3">{{ item.backtrace }}</pre> </div> @@ -135,7 +203,7 @@ export default { </template> <template v-else> <tr> - <td> + <td data-testid="performance-bar-empty-detail-notice"> {{ sprintf(__('No %{header} for this request.'), { header: header.toLowerCase() }) }} </td> </tr> @@ -146,7 +214,7 @@ export default { <div></div> </template> </gl-modal> - {{ title }} + <span class="gl-text-white">{{ title }}</span> <request-warning :html-id="htmlId" :warnings="warnings" /> </div> </template> diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue index 6b446eb6073..ebe9c4eee2f 100644 --- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue +++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue @@ -40,7 +40,7 @@ export default { metric: 'active-record', title: 'pg', header: s__('PerformanceBar|SQL queries'), - keys: ['sql', 'cached', 'db_role'], + keys: ['sql', 'cached', 'transaction', 'db_role'], }, { metric: 'bullet', @@ -69,6 +69,7 @@ export default { }, { metric: 'external-http', + title: 'external', header: s__('PerformanceBar|External Http calls'), keys: ['label', 'code', 'proxy', 'error'], }, @@ -135,7 +136,7 @@ export default { <div id="peek-view-host" class="view"> <span v-if="hasHost" - class="current-host" + class="current-host gl-text-white" :class="{ canary: currentRequest.details.host.canary }" > <span v-html="birdEmoji"></span> @@ -156,16 +157,18 @@ export default { id="peek-view-trace" class="view" > - <a class="gl-text-blue-300" :href="currentRequest.details.tracing.tracing_url">{{ - s__('PerformanceBar|trace') + <a class="gl-text-blue-200" :href="currentRequest.details.tracing.tracing_url">{{ + s__('PerformanceBar|Trace') }}</a> </div> - <add-request v-on="$listeners" /> <div v-if="currentRequest.details" id="peek-download" class="view"> - <a class="gl-text-blue-300" :download="downloadName" :href="downloadPath">{{ + <a class="gl-text-blue-200" :download="downloadName" :href="downloadPath">{{ s__('PerformanceBar|Download') }}</a> </div> + <a v-if="statsUrl" class="gl-text-blue-200 view" :href="statsUrl">{{ + s__('PerformanceBar|Stats') + }}</a> <request-selector v-if="currentRequest" :current-request="currentRequest" @@ -173,9 +176,7 @@ export default { class="ml-auto" @change-current-request="changeCurrentRequest" /> - <div v-if="statsUrl" id="peek-stats" class="view"> - <a class="gl-text-blue-300" :href="statsUrl">{{ s__('PerformanceBar|Stats') }}</a> - </div> + <add-request v-on="$listeners" /> </div> </div> </template> diff --git a/app/assets/javascripts/performance_bar/components/request_selector.vue b/app/assets/javascripts/performance_bar/components/request_selector.vue index 5666e038f02..75fb7bbc5c5 100644 --- a/app/assets/javascripts/performance_bar/components/request_selector.vue +++ b/app/assets/javascripts/performance_bar/components/request_selector.vue @@ -58,12 +58,12 @@ export default { <span v-if="request.hasWarnings">(!)</span> </option> </select> - <span v-if="requestsWithWarnings.length"> + <span v-if="requestsWithWarnings.length" class="gl-cursor-default"> <span id="performance-bar-request-selector-warning" v-html="glEmojiTag('warning')"></span> <gl-popover + placement="bottom" target="performance-bar-request-selector-warning" :content="warningMessage" - triggers="hover focus" /> </span> </div> diff --git a/app/assets/javascripts/performance_bar/components/request_warning.vue b/app/assets/javascripts/performance_bar/components/request_warning.vue index b61e1e5b7a9..7fe6b088ebb 100644 --- a/app/assets/javascripts/performance_bar/components/request_warning.vue +++ b/app/assets/javascripts/performance_bar/components/request_warning.vue @@ -35,8 +35,8 @@ export default { }; </script> <template> - <span v-if="hasWarnings"> + <span v-if="hasWarnings" class="gl-cursor-default"> <span :id="htmlId" v-html="glEmojiTag('warning')"></span> - <gl-popover :target="htmlId" :content="warningMessage" triggers="hover focus" /> + <gl-popover placement="bottom" :target="htmlId" :content="warningMessage" /> </span> </template> diff --git a/app/assets/javascripts/performance_bar/constants.js b/app/assets/javascripts/performance_bar/constants.js new file mode 100644 index 00000000000..9659383edd9 --- /dev/null +++ b/app/assets/javascripts/performance_bar/constants.js @@ -0,0 +1,17 @@ +import { s__ } from '~/locale'; + +export const sortOrders = { + DURATION: 'duration', + CHRONOLOGICAL: 'chronological', +}; + +export const sortOrderOptions = [ + { + value: sortOrders.DURATION, + text: s__('PerformanceBar|Sort by duration'), + }, + { + value: sortOrders.CHRONOLOGICAL, + text: s__('PerformanceBar|Sort chronologically'), + }, +]; diff --git a/app/assets/javascripts/performance_bar/index.js b/app/assets/javascripts/performance_bar/index.js index 51b6108868f..d8aab25a6a8 100644 --- a/app/assets/javascripts/performance_bar/index.js +++ b/app/assets/javascripts/performance_bar/index.js @@ -1,6 +1,7 @@ -/* eslint-disable @gitlab/require-i18n-strings */ import Vue from 'vue'; import axios from '~/lib/utils/axios_utils'; +import { numberToHumanSize } from '~/lib/utils/number_utils'; +import { s__ } from '~/locale'; import Translate from '~/vue_shared/translate'; import initPerformanceBarLog from './performance_bar_log'; @@ -75,40 +76,53 @@ const initPerformanceBar = (el) => { const resourceEntries = performance.getEntriesByType('resource'); let durationString = ''; + let summary = {}; if (navigationEntries.length > 0) { - durationString = `${Math.round(navigationEntries[0].responseEnd)} | `; - durationString += `${Math.round(paintEntries[1].startTime)} | `; - durationString += ` ${Math.round(navigationEntries[0].domContentLoadedEventEnd)}`; + const backend = Math.round(navigationEntries[0].responseEnd); + const firstContentfulPaint = Math.round(paintEntries[1].startTime); + const domContentLoaded = Math.round(navigationEntries[0].domContentLoadedEventEnd); + + summary = { + [s__('PerformanceBar|Backend')]: backend, + [s__('PerformanceBar|First Contentful Paint')]: firstContentfulPaint, + [s__('PerformanceBar|DOM Content Loaded')]: domContentLoaded, + }; + + durationString = `${backend} | ${firstContentfulPaint} | ${domContentLoaded}`; } let newEntries = resourceEntries.map(this.transformResourceEntry); - this.updateFrontendPerformanceMetrics(durationString, newEntries); + this.updateFrontendPerformanceMetrics(durationString, summary, newEntries); if ('PerformanceObserver' in window) { // We start observing for more incoming timings const observer = new PerformanceObserver((list) => { newEntries = newEntries.concat(list.getEntries().map(this.transformResourceEntry)); - this.updateFrontendPerformanceMetrics(durationString, newEntries); + this.updateFrontendPerformanceMetrics(durationString, summary, newEntries); }); observer.observe({ entryTypes: ['resource'] }); } } }, - updateFrontendPerformanceMetrics(durationString, requestEntries) { + updateFrontendPerformanceMetrics(durationString, summary, requestEntries) { this.store.setRequestDetailsData(this.requestId, 'total', { duration: durationString, calls: requestEntries.length, details: requestEntries, + summaryOptions: { + hideDuration: true, + }, + summary, }); }, transformResourceEntry(entry) { - const nf = new Intl.NumberFormat(); return { + start: entry.startTime, name: entry.name.replace(document.location.origin, ''), duration: Math.round(entry.duration), - size: entry.transferSize ? `${nf.format(entry.transferSize)} bytes` : 'cached', + size: entry.transferSize ? numberToHumanSize(entry.transferSize) : 'cached', }; }, }, diff --git a/app/assets/javascripts/performance_bar/stores/performance_bar_store.js b/app/assets/javascripts/performance_bar/stores/performance_bar_store.js index 9d12d228d35..51a8eb5ca69 100644 --- a/app/assets/javascripts/performance_bar/stores/performance_bar_store.js +++ b/app/assets/javascripts/performance_bar/stores/performance_bar_store.js @@ -47,10 +47,15 @@ export default class PerformanceBarStore { } canTrackRequest(requestUrl) { - return ( - requestUrl.endsWith('/api/graphql') || - this.requests.filter((request) => request.url === requestUrl).length < 2 - ); + // We want to store at most 2 unique requests per URL, as additional + // requests to the same URL probably aren't very interesting. + // + // GraphQL requests are the exception: because all GraphQL requests + // go to the same URL, we set a higher limit of 10 to allow + // capturing different queries a page may make. + const requestsLimit = requestUrl.endsWith('/api/graphql') ? 10 : 2; + + return this.requests.filter((request) => request.url === requestUrl).length < requestsLimit; } static truncateUrl(requestUrl) { |