diff --git a/.babelrc b/.babelrc index 703832f257..d98f1d0f82 100644 --- a/.babelrc +++ b/.babelrc @@ -61,17 +61,6 @@ "loose": true } ], - [ - // This plugin is configured by preset-env when needed... however because - // Webpack v4 doesn't understand this new syntaxe we need to - // unconditionally transpile it even for platforms that don't support it. - "@babel/plugin-proposal-nullish-coalescing-operator", - { - // The loose operation is when assuming that we don't use document.all. - // We should probably use the "assumptions" object in preset-env. - "loose": true - } - ], [ "module-resolver", { diff --git a/.browserslistrc b/.browserslistrc index 7a90e53304..2011bc6298 100644 --- a/.browserslistrc +++ b/.browserslistrc @@ -1,4 +1,12 @@ -current node +[development] last 2 chrome versions last 2 firefox versions last 2 safari versions + +[production] +last 2 chrome versions +last 2 firefox versions +last 2 safari versions + +[test] +current node diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0b1addd24d..7ebeef2f11 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,16 +31,9 @@ We're friendly and we're on the [Mozilla Matrix instance](https://chat.mozilla.o [profiler.firefox.com](https://profiler.firefox.com) is a web application that loads in performance profiles for analysis. The profiles are loaded in from a variety of sources including directly imported from Firefox, online storage, and from local files. -You will need a recent enough version of [Yarn](http://yarnpkg.com/), -version 1.0.1 is known to work correctly. -You can install it into your home directory on Linux and probably OS X with: - -```bash -cd /tmp -wget https://yarnpkg.com/install.sh -chmod a+x install.sh -./install.sh -``` +You will need a recent enough version of [Yarn 1 (Classic)](https://classic.yarnpkg.com/), +version 1.10 is known to work correctly. +You can install it using `npm install -g yarn`. Please refer to [its documentation](https://classic.yarnpkg.com/en/docs/install) for other possible install procedures. To get started clone the repo and get the web application started. diff --git a/README.md b/README.md index c73d847e81..df2ebbd1cf 100644 --- a/README.md +++ b/README.md @@ -27,16 +27,9 @@ If you would like to help us test on other assistive technologies or improve the ### Development -You will need a recent enough version of [Yarn](http://yarnpkg.com/), +You will need a recent enough version of [Yarn 1 (Classic)](https://classic.yarnpkg.com/), version 1.10 is known to work correctly. -You can install it into your home directory on Linux and probably OS X with: - -```bash -cd /tmp -wget https://yarnpkg.com/install.sh -chmod a+x install.sh -./install.sh -``` +You can install it using `npm install -g yarn`. Please refer to [its documentation](https://classic.yarnpkg.com/en/docs/install) for other possible install procedures. To download and build the Firefox Profiler web app run: diff --git a/locales/de/app.ftl b/locales/de/app.ftl index b1d33db182..2e3dbb2296 100644 --- a/locales/de/app.ftl +++ b/locales/de/app.ftl @@ -248,7 +248,7 @@ Home--profiler-motto = Zeichnen Sie ein Leistungsprofil auf. Analysieren Sie es. Home--additional-content-title = Bestehende Profile laden Home--additional-content-content = Sie können eine Profildatei per Ziehen und Ablegen hierher bewegen, um sie zu laden, oder: Home--compare-recordings-info = Sie können auch Aufnahmen vergleichen. Öffnen Sie die Vergleichsschnittstelle. -Home--recent-uploaded-recordings-title = Kürzlich hochgeladene Aufzeichnungen +Home--your-recent-uploaded-recordings-title = Ihre kürzlich hochgeladenen Aufzeichnungen ## IdleSearchField ## The component that is used for all the search inputs in the application. @@ -274,9 +274,9 @@ ListOfPublishedProfiles--published-profiles-link = ListOfPublishedProfiles--published-profiles-delete-button-disabled = Löschen .title = Dieses Profil kann nicht gelöscht werden, weil die Berechtigung fehlt. ListOfPublishedProfiles--uploaded-profile-information-list-empty = Es wurde noch kein Profil hochgeladen! -# This string is used below the 'Recent uploaded recordings' list section. +# This string is used below the 'Your recent uploaded recordings' list section. # Variables: -# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Recent uploaded recordings'. +# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Your recent uploaded recordings'. ListOfPublishedProfiles--uploaded-profile-information-label = Sehen und verwalten Sie alle Ihre Aufzeichnungen ({ $profilesRestCount } weitere) # Depending on the number of uploaded profiles, the message is different. # Variables: @@ -367,6 +367,7 @@ MenuButtons--metaInfo--symbolicate-profile = Profil symbolisieren MenuButtons--metaInfo--attempting-resymbolicate = Versuch, das Profil erneut zu symbolisieren MenuButtons--metaInfo--currently-symbolicating = Profil wird aktuell symbolisiert MenuButtons--metaInfo--cpu = CPU: +MenuButtons--metaInfo--main-memory = Hauptspeicher: # This string is used when we have the information about both physical and # logical CPU cores. # Variable: @@ -398,6 +399,7 @@ MenuButtons--metaInfo--logical-cpu = *[other] { $logicalCPUs } logische Kerne } MenuButtons--metaInfo--main-process-started = Hauptprozess gestartet: +MenuButtons--metaInfo--main-process-ended = Hauptprozess beendet: MenuButtons--metaInfo--interval = Intervall: MenuButtons--metaInfo--buffer-capacity = Pufferkapazität: MenuButtons--metaInfo--buffer-duration = Pufferdauer: @@ -416,6 +418,7 @@ MenuButtons--metaInfo--name-and-version = Name und Version: MenuButtons--metaInfo--update-channel = Update-Kanal: MenuButtons--metaInfo--build-id = Build-ID: MenuButtons--metaInfo--build-type = Build-Typ: +MenuButtons--metaInfo--arguments = Argumente: ## Strings refer to specific types of builds, and should be kept in English. @@ -652,6 +655,12 @@ TrackContextMenu--hide-all-matching-tracks = Alle passenden Tracks ausblenden # Variables: # $searchFilter (String) - The search filter string that user enters. TrackContextMenu--no-results-found = Kein Ergebnis für „{ $searchFilter }“ gefunden +# This button appears when hovering a track name and is displayed as an X icon. +TrackNameButton--hide-track = + .title = Track ausblenden +# This button appears when hovering a global track name and is displayed as an X icon. +TrackNameButton--hide-process = + .title = Prozess ausblenden ## TrackMemoryGraph ## This is used to show the memory graph of that process in the timeline part of @@ -662,20 +671,46 @@ TrackMemoryGraph--relative-memory-at-this-time = Relativer Speicherverbrauch zu TrackMemoryGraph--memory-range-in-graph = Speicherbereich im Diagramm TrackMemoryGraph--operations-since-the-previous-sample = Operationen seit der vorherigen Stichprobe -## TrackPowerGraph +## TrackPower ## This is used to show the power used by the CPU and other chips in a computer, ## graphed over time. ## It's not displayed by default in the UI, but an example can be found at ## https://share.firefox.dev/3a1fiT7. -# This is used in the tooltip when the power value uses the Watt unit. +# This is used in the tooltip when the power value uses the watt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-watt = Leistung: { $value } W -# This is used in the tooltip when the power value uses the Milliwatt unit. +TrackPower--tooltip-power-watt = { $value } W + .label = Leistung +# This is used in the tooltip when the instant power value uses the milliwatt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-milliwatt = Leistung: { $value } mW +TrackPower--tooltip-power-milliwatt = { $value } mW + .label = Leistung +# This is used in the tooltip when the energy used in the current range uses the +# watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh + .label = Im sichtbaren Bereich verbrauchte Energie +# This is used in the tooltip when the energy used in the current range uses the +# milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh + .label = Im sichtbaren Bereich verbrauchte Energie +# This is used in the tooltip when the energy used in the current preview +# selection uses the watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh + .label = In der aktuellen Auswahl verbrauchte Energie +# This is used in the tooltip when the energy used in the current preview +# selection uses the milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh + .label = In der aktuellen Auswahl verbrauchte Energie ## TrackSearchField ## The component that is used for the search input in the track context menu. diff --git a/locales/el/app.ftl b/locales/el/app.ftl index 31ff2f4f61..66a24a483b 100644 --- a/locales/el/app.ftl +++ b/locales/el/app.ftl @@ -259,7 +259,7 @@ Home--profiler-motto = Καταγράψτε ένα προφίλ επιδόσεω Home--additional-content-title = Φόρτωση υπαρχόντων προφίλ Home--additional-content-content = Μπορείτε να σύρετε και να εναποθέσετε ένα αρχείο προφίλ εδώ για φόρτωση, ή: Home--compare-recordings-info = Μπορείτε επίσης να συγκρίνετε καταγραφές. Άνοιγμα περιβάλλοντος σύγκρισης. -Home--recent-uploaded-recordings-title = Πρόσφατα μεταφορτωμένες καταγραφές +Home--your-recent-uploaded-recordings-title = Πρόσφατα μεταφορτωμένες καταγραφές ## IdleSearchField ## The component that is used for all the search inputs in the application. @@ -285,9 +285,9 @@ ListOfPublishedProfiles--published-profiles-link = ListOfPublishedProfiles--published-profiles-delete-button-disabled = Διαγραφή .title = Δεν είναι δυνατή η διαγραφή αυτού του προφίλ επειδή μας λείπουν πληροφορίες εξουσιοδότησης. ListOfPublishedProfiles--uploaded-profile-information-list-empty = Δεν έχει μεταφορτωθεί ακόμη κανένα προφίλ! -# This string is used below the 'Recent uploaded recordings' list section. +# This string is used below the 'Your recent uploaded recordings' list section. # Variables: -# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Recent uploaded recordings'. +# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Your recent uploaded recordings'. ListOfPublishedProfiles--uploaded-profile-information-label = Προβολή και διαχείριση όλων των καταγραφών σας ({ $profilesRestCount } ακόμη) # Depending on the number of uploaded profiles, the message is different. # Variables: @@ -378,6 +378,7 @@ MenuButtons--metaInfo--symbolicate-profile = Συμβολισμός προφίλ MenuButtons--metaInfo--attempting-resymbolicate = Απόπειρα επανασυμβολισμού προφίλ MenuButtons--metaInfo--currently-symbolicating = Γίνεται συμβολισμός προφίλ αυτή τη στιγμή MenuButtons--metaInfo--cpu = CPU: +MenuButtons--metaInfo--main-memory = Κύρια μνήμη: # This string is used when we have the information about both physical and # logical CPU cores. # Variable: @@ -409,6 +410,7 @@ MenuButtons--metaInfo--logical-cpu = *[other] { $logicalCPUs } λογικοί πυρήνες } MenuButtons--metaInfo--main-process-started = Έναρξη κύριας διεργασίας: +MenuButtons--metaInfo--main-process-ended = Τέλος κύριας διεργασίας: MenuButtons--metaInfo--interval = Διάστημα: MenuButtons--metaInfo--buffer-capacity = Χωρητικότητα buffer: MenuButtons--metaInfo--buffer-duration = Διάρκεια buffer: @@ -427,6 +429,7 @@ MenuButtons--metaInfo--name-and-version = Όνομα και έκδοση: MenuButtons--metaInfo--update-channel = Κανάλι ενημερώσεων: MenuButtons--metaInfo--build-id = ID δομής: MenuButtons--metaInfo--build-type = Τύπος δομής: +MenuButtons--metaInfo--arguments = Ορίσματα: ## Strings refer to specific types of builds, and should be kept in English. @@ -663,6 +666,12 @@ TrackContextMenu--hide-all-matching-tracks = Απόκρυψη όλων των α # Variables: # $searchFilter (String) - The search filter string that user enters. TrackContextMenu--no-results-found = Δεν βρέθηκαν αποτελέσματα για «{ $searchFilter }» +# This button appears when hovering a track name and is displayed as an X icon. +TrackNameButton--hide-track = + .title = Απόκρυψη κομματιού +# This button appears when hovering a global track name and is displayed as an X icon. +TrackNameButton--hide-process = + .title = Απόκρυψη διεργασίας ## TrackMemoryGraph ## This is used to show the memory graph of that process in the timeline part of @@ -673,20 +682,46 @@ TrackMemoryGraph--relative-memory-at-this-time = σχετική μνήμη αυ TrackMemoryGraph--memory-range-in-graph = εύρος μνήμης στο γράφημα TrackMemoryGraph--operations-since-the-previous-sample = λειτουργίες από το προηγούμενο δείγμα -## TrackPowerGraph +## TrackPower ## This is used to show the power used by the CPU and other chips in a computer, ## graphed over time. ## It's not displayed by default in the UI, but an example can be found at ## https://share.firefox.dev/3a1fiT7. -# This is used in the tooltip when the power value uses the Watt unit. +# This is used in the tooltip when the power value uses the watt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-watt = Ισχύς: { $value } W -# This is used in the tooltip when the power value uses the Milliwatt unit. +TrackPower--tooltip-power-watt = { $value } W + .label = Ισχύς +# This is used in the tooltip when the instant power value uses the milliwatt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-milliwatt = Ισχύς: { $value } mW +TrackPower--tooltip-power-milliwatt = { $value } mW + .label = Ισχύς +# This is used in the tooltip when the energy used in the current range uses the +# watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh + .label = Ενέργεια που χρησιμοποιείται στο ορατό εύρος +# This is used in the tooltip when the energy used in the current range uses the +# milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh + .label = Ενέργεια που χρησιμοποιείται στο ορατό εύρος +# This is used in the tooltip when the energy used in the current preview +# selection uses the watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh + .label = Ενέργεια που χρησιμοποιείται στην τρέχουσα επιλογή +# This is used in the tooltip when the energy used in the current preview +# selection uses the milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh + .label = Ενέργεια που χρησιμοποιείται στην τρέχουσα επιλογή ## TrackSearchField ## The component that is used for the search input in the track context menu. diff --git a/locales/en-GB/app.ftl b/locales/en-GB/app.ftl index a9641522a3..4c83b0285e 100644 --- a/locales/en-GB/app.ftl +++ b/locales/en-GB/app.ftl @@ -263,7 +263,7 @@ Home--profiler-motto = Capture a performance profile. Analyse it. Share it. Make Home--additional-content-title = Load existing profiles Home--additional-content-content = You can drag and drop a profile file here to load it, or: Home--compare-recordings-info = You can also compare recordings. Open the comparing interface. -Home--recent-uploaded-recordings-title = Recent uploaded recordings +Home--your-recent-uploaded-recordings-title = Your recent uploaded recordings ## IdleSearchField ## The component that is used for all the search inputs in the application. @@ -289,9 +289,9 @@ ListOfPublishedProfiles--published-profiles-link = ListOfPublishedProfiles--published-profiles-delete-button-disabled = Delete .title = This profile cannot be deleted because we lack the authorisation information. ListOfPublishedProfiles--uploaded-profile-information-list-empty = No profile has been uploaded yet! -# This string is used below the 'Recent uploaded recordings' list section. +# This string is used below the 'Your recent uploaded recordings' list section. # Variables: -# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Recent uploaded recordings'. +# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Your recent uploaded recordings'. ListOfPublishedProfiles--uploaded-profile-information-label = See and manage all your recordings ({ $profilesRestCount } more) # Depending on the number of uploaded profiles, the message is different. # Variables: @@ -382,6 +382,7 @@ MenuButtons--metaInfo--symbolicate-profile = Symbolicate profile MenuButtons--metaInfo--attempting-resymbolicate = Attempting to re-symbolicate profile MenuButtons--metaInfo--currently-symbolicating = Currently symbolicating profile MenuButtons--metaInfo--cpu = CPU: +MenuButtons--metaInfo--main-memory = Main memory: # This string is used when we have the information about both physical and # logical CPU cores. # Variable: @@ -413,6 +414,7 @@ MenuButtons--metaInfo--logical-cpu = *[other] { $logicalCPUs } logical cores } MenuButtons--metaInfo--main-process-started = Main process started: +MenuButtons--metaInfo--main-process-ended = Main process ended: MenuButtons--metaInfo--interval = Interval: MenuButtons--metaInfo--buffer-capacity = Buffer Capacity: MenuButtons--metaInfo--buffer-duration = Buffer Duration: @@ -431,6 +433,7 @@ MenuButtons--metaInfo--name-and-version = Name and version: MenuButtons--metaInfo--update-channel = Update Channel: MenuButtons--metaInfo--build-id = Build ID: MenuButtons--metaInfo--build-type = Build Type: +MenuButtons--metaInfo--arguments = Arguments: ## Strings refer to specific types of builds, and should be kept in English. @@ -667,6 +670,12 @@ TrackContextMenu--hide-all-matching-tracks = Hide all matching tracks # Variables: # $searchFilter (String) - The search filter string that user enters. TrackContextMenu--no-results-found = No results found for “{ $searchFilter }” +# This button appears when hovering a track name and is displayed as an X icon. +TrackNameButton--hide-track = + .title = Hide track +# This button appears when hovering a global track name and is displayed as an X icon. +TrackNameButton--hide-process = + .title = Hide process ## TrackMemoryGraph ## This is used to show the memory graph of that process in the timeline part of @@ -677,20 +686,46 @@ TrackMemoryGraph--relative-memory-at-this-time = relative memory at this time TrackMemoryGraph--memory-range-in-graph = memory range in graph TrackMemoryGraph--operations-since-the-previous-sample = operations since the previous sample -## TrackPowerGraph +## TrackPower ## This is used to show the power used by the CPU and other chips in a computer, ## graphed over time. ## It's not displayed by default in the UI, but an example can be found at ## https://share.firefox.dev/3a1fiT7. -# This is used in the tooltip when the power value uses the Watt unit. +# This is used in the tooltip when the power value uses the watt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-watt = Power: { $value } W -# This is used in the tooltip when the power value uses the Milliwatt unit. +TrackPower--tooltip-power-watt = { $value } W + .label = Power +# This is used in the tooltip when the instant power value uses the milliwatt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-milliwatt = Power: { $value } mW +TrackPower--tooltip-power-milliwatt = { $value } mW + .label = Power +# This is used in the tooltip when the energy used in the current range uses the +# watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh + .label = Energy used in the visible range +# This is used in the tooltip when the energy used in the current range uses the +# milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh + .label = Energy used in the visible range +# This is used in the tooltip when the energy used in the current preview +# selection uses the watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh + .label = Energy used in the current selection +# This is used in the tooltip when the energy used in the current preview +# selection uses the milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh + .label = Energy used in the current selection ## TrackSearchField ## The component that is used for the search input in the track context menu. diff --git a/locales/en-US/app.ftl b/locales/en-US/app.ftl index 01db4891eb..5437beb387 100644 --- a/locales/en-US/app.ftl +++ b/locales/en-US/app.ftl @@ -738,21 +738,65 @@ TrackMemoryGraph--relative-memory-at-this-time = relative memory at this time TrackMemoryGraph--memory-range-in-graph = memory range in graph TrackMemoryGraph--operations-since-the-previous-sample = operations since the previous sample -## TrackPowerGraph +## TrackPower ## This is used to show the power used by the CPU and other chips in a computer, ## graphed over time. ## It's not displayed by default in the UI, but an example can be found at ## https://share.firefox.dev/3a1fiT7. -# This is used in the tooltip when the power value uses the Watt unit. +# This is used in the tooltip when the power value uses the watt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-watt = Power: { $value } W +TrackPower--tooltip-power-watt = { $value } W + .label = Power -# This is used in the tooltip when the power value uses the Milliwatt unit. +# This is used in the tooltip when the instant power value uses the milliwatt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-milliwatt = Power: { $value } mW +TrackPower--tooltip-power-milliwatt = { $value } mW + .label = Power + +# This is used in the tooltip when the energy used in the current range uses the +# watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh + .label = Energy used in the visible range + +# This is used in the tooltip when the energy used in the current range uses the +# milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh + .label = Energy used in the visible range + +# This is used in the tooltip when the energy used in the current range uses the +# microwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-microwatthour = { $value } µWh + .label = Energy used in the visible range + +# This is used in the tooltip when the energy used in the current preview +# selection uses the watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh + .label = Energy used in the current selection + +# This is used in the tooltip when the energy used in the current preview +# selection uses the milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh + .label = Energy used in the current selection + +# This is used in the tooltip when the energy used in the current preview +# selection uses the microwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-microwatthour = { $value } µWh + .label = Energy used in the current selection ## TrackSearchField ## The component that is used for the search input in the track context menu. diff --git a/locales/es-CL/app.ftl b/locales/es-CL/app.ftl index 959437bd39..816e7f88cf 100644 --- a/locales/es-CL/app.ftl +++ b/locales/es-CL/app.ftl @@ -202,7 +202,7 @@ Home--profiler-motto = Captura un perfil de rendimiento. Analízalo. Compártelo Home--additional-content-title = Cargar perfiles existentes Home--additional-content-content = Puedes arrastrar y soltar un archivo de perfil aquí para cargarlo, o: Home--compare-recordings-info = También puedes comparar los registros. Abre la interfaz de comparación. -Home--recent-uploaded-recordings-title = Registros subidos recientemente +Home--your-recent-uploaded-recordings-title = Tus registros subidos recientemente ## IdleSearchField ## The component that is used for all the search inputs in the application. @@ -228,9 +228,9 @@ ListOfPublishedProfiles--published-profiles-link = ListOfPublishedProfiles--published-profiles-delete-button-disabled = Borrar .title = Este perfil no puede ser eliminado porque no tenemos la información de autorización. ListOfPublishedProfiles--uploaded-profile-information-list-empty = ¡Aún no se ha subido ningún perfil! -# This string is used below the 'Recent uploaded recordings' list section. +# This string is used below the 'Your recent uploaded recordings' list section. # Variables: -# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Recent uploaded recordings'. +# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Your recent uploaded recordings'. ListOfPublishedProfiles--uploaded-profile-information-label = Revisa y gestiona todos tus registros ({ $profilesRestCount } más) # Depending on the number of uploaded profiles, the message is different. # Variables: @@ -321,6 +321,7 @@ MenuButtons--metaInfo--symbolicate-profile = Simbolizar perfil MenuButtons--metaInfo--attempting-resymbolicate = Intentando volver a simbolizar el perfil MenuButtons--metaInfo--currently-symbolicating = Perfil actualmente simbolizado MenuButtons--metaInfo--cpu = CPU: +MenuButtons--metaInfo--main-memory = Memoria principal: # This string is used when we have the information about both physical and # logical CPU cores. # Variable: @@ -352,6 +353,7 @@ MenuButtons--metaInfo--logical-cpu = *[other] { $logicalCPUs } núcleos lógicos } MenuButtons--metaInfo--main-process-started = Proceso principal iniciado: +MenuButtons--metaInfo--main-process-ended = Proceso principal terminado: MenuButtons--metaInfo--interval = Intervalo: MenuButtons--metaInfo--buffer-capacity = Capacidad del búfer: MenuButtons--metaInfo--buffer-duration = Duración del búfer: @@ -370,6 +372,7 @@ MenuButtons--metaInfo--name-and-version = Nombre y versión: MenuButtons--metaInfo--update-channel = Canal de actualización: MenuButtons--metaInfo--build-id = ID de compilación: MenuButtons--metaInfo--build-type = Tipo de compilación: +MenuButtons--metaInfo--arguments = Argumentos: ## Strings refer to specific types of builds, and should be kept in English. @@ -602,6 +605,12 @@ TrackContextMenu--hide-all-matching-tracks = Ocultar todas las pistas coincident # Variables: # $searchFilter (String) - The search filter string that user enters. TrackContextMenu--no-results-found = No se encontraron resultados para "{ $searchFilter }" +# This button appears when hovering a track name and is displayed as an X icon. +TrackNameButton--hide-track = + .title = Ocultar pista +# This button appears when hovering a global track name and is displayed as an X icon. +TrackNameButton--hide-process = + .title = Ocultar proceso ## TrackMemoryGraph ## This is used to show the memory graph of that process in the timeline part of @@ -612,20 +621,46 @@ TrackMemoryGraph--relative-memory-at-this-time = memoria relativa en este moment TrackMemoryGraph--memory-range-in-graph = rango de memoria en un gráfico TrackMemoryGraph--operations-since-the-previous-sample = operaciones desde la muestra anterior -## TrackPowerGraph +## TrackPower ## This is used to show the power used by the CPU and other chips in a computer, ## graphed over time. ## It's not displayed by default in the UI, but an example can be found at ## https://share.firefox.dev/3a1fiT7. -# This is used in the tooltip when the power value uses the Watt unit. +# This is used in the tooltip when the power value uses the watt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-watt = Potencia: { $value } W -# This is used in the tooltip when the power value uses the Milliwatt unit. +TrackPower--tooltip-power-watt = { $value } W + .label = Potencia +# This is used in the tooltip when the instant power value uses the milliwatt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-milliwatt = Potencia: { $value } mW +TrackPower--tooltip-power-milliwatt = { $value } mW + .label = Potencia +# This is used in the tooltip when the energy used in the current range uses the +# watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh + .label = Energía usada en el rango visible +# This is used in the tooltip when the energy used in the current range uses the +# milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh + .label = Energía usada en el rango visible +# This is used in the tooltip when the energy used in the current preview +# selection uses the watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh + .label = Energía usada en la selección actual +# This is used in the tooltip when the energy used in the current preview +# selection uses the milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh + .label = Energía usada en la selección actual ## TrackSearchField ## The component that is used for the search input in the track context menu. diff --git a/locales/fr/app.ftl b/locales/fr/app.ftl index a545a0be89..4834368bdc 100644 --- a/locales/fr/app.ftl +++ b/locales/fr/app.ftl @@ -198,7 +198,7 @@ Home--profiler-motto = Capturez un profil de performances. Analysez-le. Partagez Home--additional-content-title = Charger des profils existants Home--additional-content-content = Vous pouvez glisser-déposer un fichier de profil ici pour le charger, ou : Home--compare-recordings-info = Vous pouvez également comparer des enregistrements. Ouvrir l’interface de comparaison. -Home--recent-uploaded-recordings-title = Enregistrements récemment envoyés +Home--your-recent-uploaded-recordings-title = Vos enregistrements récemment envoyés ## IdleSearchField ## The component that is used for all the search inputs in the application. @@ -224,9 +224,9 @@ ListOfPublishedProfiles--published-profiles-link = ListOfPublishedProfiles--published-profiles-delete-button-disabled = Supprimer .title = Ce profil ne peut pas être supprimé, car les informations d’autorisation sont manquantes. ListOfPublishedProfiles--uploaded-profile-information-list-empty = Aucun profil n’a encore été envoyé. -# This string is used below the 'Recent uploaded recordings' list section. +# This string is used below the 'Your recent uploaded recordings' list section. # Variables: -# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Recent uploaded recordings'. +# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Your recent uploaded recordings'. ListOfPublishedProfiles--uploaded-profile-information-label = Voir et gérer tous vos enregistrements ({ $profilesRestCount } de plus) # Depending on the number of uploaded profiles, the message is different. # Variables: @@ -317,6 +317,7 @@ MenuButtons--metaInfo--symbolicate-profile = Profil symbolique MenuButtons--metaInfo--attempting-resymbolicate = Tenter de re-symboliser le profil MenuButtons--metaInfo--currently-symbolicating = Re-symbolisation du profil en cours MenuButtons--metaInfo--cpu = Processeur : +MenuButtons--metaInfo--main-memory = Mémoire principale : # This string is used when we have the information about both physical and # logical CPU cores. # Variable: @@ -348,6 +349,7 @@ MenuButtons--metaInfo--logical-cpu = *[other] { $logicalCPUs } cœurs logiques } MenuButtons--metaInfo--main-process-started = Processus principal démarré : +MenuButtons--metaInfo--main-process-ended = Processus principal terminé : MenuButtons--metaInfo--interval = Intervalle : MenuButtons--metaInfo--buffer-capacity = Capacité de la mémoire tampon : MenuButtons--metaInfo--buffer-duration = Durée de la mémoire tampon : @@ -366,6 +368,7 @@ MenuButtons--metaInfo--name-and-version = Nom et version : MenuButtons--metaInfo--update-channel = Canal de mise à jour : MenuButtons--metaInfo--build-id = Identifiant de compilation : MenuButtons--metaInfo--build-type = Type de compilation : +MenuButtons--metaInfo--arguments = Arguments : ## Strings refer to specific types of builds, and should be kept in English. @@ -598,6 +601,12 @@ TrackContextMenu--hide-all-matching-tracks = Masquer toutes les pistes correspon # Variables: # $searchFilter (String) - The search filter string that user enters. TrackContextMenu--no-results-found = Aucun résultat pour « { $searchFilter } » +# This button appears when hovering a track name and is displayed as an X icon. +TrackNameButton--hide-track = + .title = Masquer la piste +# This button appears when hovering a global track name and is displayed as an X icon. +TrackNameButton--hide-process = + .title = Masquer le processus ## TrackMemoryGraph ## This is used to show the memory graph of that process in the timeline part of diff --git a/locales/fy-NL/app.ftl b/locales/fy-NL/app.ftl index 98b2a65d51..7936beec0a 100644 --- a/locales/fy-NL/app.ftl +++ b/locales/fy-NL/app.ftl @@ -217,10 +217,6 @@ FooterLinks--hide-button = ## The timeline component of the full view in the analysis UI at the top of the ## page. -FullTimeline--graph-type = Grafyktype: -FullTimeline--categories-with-cpu = Kategoryen mei CPU -FullTimeline--categories = Kategoryen -FullTimeline--stack-height = Stackhichte # This string is used as the text of the track selection button. # Displays the ratio of visible tracks count to total tracks count in the timeline. # We have spans here to make the numbers bold. @@ -267,7 +263,7 @@ Home--profiler-motto = Lis in prestaasjeprofyl fêst. Analysearje it. Diel it. M Home--additional-content-title = Besteande profilen lade Home--additional-content-content = Jo kinne in profylbestân hjirhinne fersleepje om it te laden, of: Home--compare-recordings-info = Jo kinne ek opnamen fergelykje. De fergelikingsinterface iepenje. -Home--recent-uploaded-recordings-title = Resint opladen opnamen +Home--your-recent-uploaded-recordings-title = Jo resint opladen opnamen ## IdleSearchField ## The component that is used for all the search inputs in the application. @@ -293,9 +289,9 @@ ListOfPublishedProfiles--published-profiles-link = ListOfPublishedProfiles--published-profiles-delete-button-disabled = Fuortsmite .title = Dit profyl kin net fuortsmiten wurde, omdat wy gjin autorisaasjegegevens hawwen. ListOfPublishedProfiles--uploaded-profile-information-list-empty = Der is noch gjin profyl opladen! -# This string is used below the 'Recent uploaded recordings' list section. +# This string is used below the 'Your recent uploaded recordings' list section. # Variables: -# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Recent uploaded recordings'. +# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Your recent uploaded recordings'. ListOfPublishedProfiles--uploaded-profile-information-label = Al jo opnamen besjen en beheare (noch { $profilesRestCount }) # Depending on the number of uploaded profiles, the message is different. # Variables: @@ -386,6 +382,7 @@ MenuButtons--metaInfo--symbolicate-profile = Profyl symbolisearje MenuButtons--metaInfo--attempting-resymbolicate = Besykjen ta opnij symbolisearjen profyl MenuButtons--metaInfo--currently-symbolicating = Profyl wurdt symbolisearre MenuButtons--metaInfo--cpu = CPU: +MenuButtons--metaInfo--main-memory = Haadûnthâld: # This string is used when we have the information about both physical and # logical CPU cores. # Variable: @@ -417,6 +414,7 @@ MenuButtons--metaInfo--logical-cpu = *[other] { $logicalCPUs } logyske kearnen } MenuButtons--metaInfo--main-process-started = Haadproses start: +MenuButtons--metaInfo--main-process-ended = Haadproses stoppe: MenuButtons--metaInfo--interval = Ynterfal: MenuButtons--metaInfo--buffer-capacity = Bufferkapasiteit: MenuButtons--metaInfo--buffer-duration = Bufferdoer: @@ -435,6 +433,7 @@ MenuButtons--metaInfo--name-and-version = Namme en ferzje: MenuButtons--metaInfo--update-channel = Fernijkanaal: MenuButtons--metaInfo--build-id = Build-ID: MenuButtons--metaInfo--build-type = Buildtype: +MenuButtons--metaInfo--arguments = Arguminten: ## Strings refer to specific types of builds, and should be kept in English. @@ -597,7 +596,7 @@ ProfileRootMessage--additional = Tebek nei startside ## This is the component responsible for handling the service worker installation ## and update. It appears at the top of the UI. -ServiceWorkerManager--installing-button = Ynstallearje… +ServiceWorkerManager--applying-button = Tapasse… ServiceWorkerManager--pending-button = Tapasse en opnij lade ServiceWorkerManager--installed-button = De tapassing opnij lade ServiceWorkerManager--updated-while-not-ready = @@ -671,6 +670,12 @@ TrackContextMenu--hide-all-matching-tracks = Alle oerienkommende tracks ferstopj # Variables: # $searchFilter (String) - The search filter string that user enters. TrackContextMenu--no-results-found = Gjin resultaten fûn foar ‘{ $searchFilter }’ +# This button appears when hovering a track name and is displayed as an X icon. +TrackNameButton--hide-track = + .title = Track ferstopje +# This button appears when hovering a global track name and is displayed as an X icon. +TrackNameButton--hide-process = + .title = Proses ferstopje ## TrackMemoryGraph ## This is used to show the memory graph of that process in the timeline part of @@ -681,20 +686,46 @@ TrackMemoryGraph--relative-memory-at-this-time = relatyf ûnthâld op dit stuit TrackMemoryGraph--memory-range-in-graph = ûnthâldberik yn grafyk TrackMemoryGraph--operations-since-the-previous-sample = bewurkingen sûnt de foarige werjefte -## TrackPowerGraph +## TrackPower ## This is used to show the power used by the CPU and other chips in a computer, ## graphed over time. ## It's not displayed by default in the UI, but an example can be found at ## https://share.firefox.dev/3a1fiT7. -# This is used in the tooltip when the power value uses the Watt unit. +# This is used in the tooltip when the power value uses the watt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-watt = Fermogen: { $value } W -# This is used in the tooltip when the power value uses the Milliwatt unit. +TrackPower--tooltip-power-watt = { $value } W + .label = Fermogen +# This is used in the tooltip when the instant power value uses the milliwatt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-milliwatt = Fermogen: { $value } mW +TrackPower--tooltip-power-milliwatt = { $value } mW + .label = Fermogen +# This is used in the tooltip when the energy used in the current range uses the +# watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh + .label = Ferbrûkte enerzjy yn it sichtbere berik +# This is used in the tooltip when the energy used in the current range uses the +# milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh + .label = Ferbrûkte enerzjy yn it sichtbere berik +# This is used in the tooltip when the energy used in the current preview +# selection uses the watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh + .label = Ferbrûkte enerzjy yn de aktuele seleksje +# This is used in the tooltip when the energy used in the current preview +# selection uses the milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh + .label = Ferbrûkte enerzjy yn de aktuele seleksje ## TrackSearchField ## The component that is used for the search input in the track context menu. diff --git a/locales/ia/app.ftl b/locales/ia/app.ftl index 7f4fdc1fa0..5fdf484d9b 100644 --- a/locales/ia/app.ftl +++ b/locales/ia/app.ftl @@ -256,7 +256,7 @@ Home--profiler-motto = Capturar un profilo de prestation. Analysar lo. Compartir Home--additional-content-title = Cargar profilos existente Home--additional-content-content = Tu pote traher e deponer hic un file profilo pro cargar lo, o: Home--compare-recordings-info = Tu pote alsi comparar registrationes. Aperir le interfacie de comparation. -Home--recent-uploaded-recordings-title = Registrationes cargate recentemente +Home--your-recent-uploaded-recordings-title = Tu registrationes cargate recentemente ## IdleSearchField ## The component that is used for all the search inputs in the application. @@ -282,9 +282,9 @@ ListOfPublishedProfiles--published-profiles-link = ListOfPublishedProfiles--published-profiles-delete-button-disabled = Deler .title = Iste profilo non pote esser delite perque nos care de informationes de autorisation. ListOfPublishedProfiles--uploaded-profile-information-list-empty = Nulle profilo ha essite cargate ancora! -# This string is used below the 'Recent uploaded recordings' list section. +# This string is used below the 'Your recent uploaded recordings' list section. # Variables: -# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Recent uploaded recordings'. +# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Your recent uploaded recordings'. ListOfPublishedProfiles--uploaded-profile-information-label = Vide e gere tote tu ({ $profilesRestCount } restante registrationes) # Depending on the number of uploaded profiles, the message is different. # Variables: @@ -375,6 +375,7 @@ MenuButtons--metaInfo--symbolicate-profile = { $logicalCPUs } nucleo logic MenuButtons--metaInfo--attempting-resymbolicate = { $logicalCPUs } nucleos logic MenuButtons--metaInfo--currently-symbolicating = Actualmente symbolisante le profilo MenuButtons--metaInfo--cpu = CPU: +MenuButtons--metaInfo--main-memory = Memoria principal: # This string is used when we have the information about both physical and # logical CPU cores. # Variable: @@ -406,6 +407,7 @@ MenuButtons--metaInfo--logical-cpu = *[other] { $logicalCPUs } nucleos logic } MenuButtons--metaInfo--main-process-started = Processo principal initiate: +MenuButtons--metaInfo--main-process-ended = Processo principal finite: MenuButtons--metaInfo--interval = Intervallo: MenuButtons--metaInfo--buffer-capacity = Capacitate de buffer: MenuButtons--metaInfo--buffer-duration = Capacitate de buffer: @@ -424,6 +426,7 @@ MenuButtons--metaInfo--name-and-version = Nomine e version: MenuButtons--metaInfo--update-channel = Canal de actualisation: MenuButtons--metaInfo--build-id = ID de version: MenuButtons--metaInfo--build-type = Typo de compilation: +MenuButtons--metaInfo--arguments = Argumentos: ## Strings refer to specific types of builds, and should be kept in English. @@ -658,6 +661,12 @@ TrackContextMenu--hide-all-matching-tracks = Celar tote le tracias concordante # Variables: # $searchFilter (String) - The search filter string that user enters. TrackContextMenu--no-results-found = Nulle resultatos trovate pro “{ $searchFilter }” +# This button appears when hovering a track name and is displayed as an X icon. +TrackNameButton--hide-track = + .title = Celar tracia +# This button appears when hovering a global track name and is displayed as an X icon. +TrackNameButton--hide-process = + .title = Celar processo ## TrackMemoryGraph ## This is used to show the memory graph of that process in the timeline part of @@ -668,20 +677,46 @@ TrackMemoryGraph--relative-memory-at-this-time = relative memoria al momento TrackMemoryGraph--memory-range-in-graph = intervallo de memoria in graphico TrackMemoryGraph--operations-since-the-previous-sample = operationes depost le previe specimen -## TrackPowerGraph +## TrackPower ## This is used to show the power used by the CPU and other chips in a computer, ## graphed over time. ## It's not displayed by default in the UI, but an example can be found at ## https://share.firefox.dev/3a1fiT7. -# This is used in the tooltip when the power value uses the Watt unit. +# This is used in the tooltip when the power value uses the watt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-watt = Consumo: { $value } W -# This is used in the tooltip when the power value uses the Milliwatt unit. +TrackPower--tooltip-power-watt = { $value } W + .label = Potentia +# This is used in the tooltip when the instant power value uses the milliwatt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-milliwatt = Consumo: { $value } mW +TrackPower--tooltip-power-milliwatt = { $value } mW + .label = Potentia +# This is used in the tooltip when the energy used in the current range uses the +# watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh + .label = Energia usate in le campo visibile +# This is used in the tooltip when the energy used in the current range uses the +# milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh + .label = Energia usate in le campo visibile +# This is used in the tooltip when the energy used in the current preview +# selection uses the watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh + .label = Energia usate in le campo visibile +# This is used in the tooltip when the energy used in the current preview +# selection uses the milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh + .label = Energia usate in le campo visibile ## TrackSearchField ## The component that is used for the search input in the track context menu. diff --git a/locales/it/app.ftl b/locales/it/app.ftl index fa0e597003..a52bfeb1df 100644 --- a/locales/it/app.ftl +++ b/locales/it/app.ftl @@ -194,7 +194,7 @@ Home--profiler-motto = Cattura un profilo delle prestazioni. Analizzalo. Condivi Home--additional-content-title = Carica profili esistenti Home--additional-content-content = È possibile trascinare e rilasciare qui un profilo per caricarlo, oppure: Home--compare-recordings-info = È anche possibile confrontare diverse registrazioni. Apri l’interfaccia per il confronto. -Home--recent-uploaded-recordings-title = Registrazioni caricate di recente +Home--your-recent-uploaded-recordings-title = Le tue registrazioni caricate di recente ## IdleSearchField ## The component that is used for all the search inputs in the application. @@ -220,9 +220,9 @@ ListOfPublishedProfiles--published-profiles-link = ListOfPublishedProfiles--published-profiles-delete-button-disabled = Elimina .title = Non è possibile eliminare questo profilo in quanto mancano le informazioni di autorizzazione. ListOfPublishedProfiles--uploaded-profile-information-list-empty = Non è stato ancora caricato alcun profilo. -# This string is used below the 'Recent uploaded recordings' list section. +# This string is used below the 'Your recent uploaded recordings' list section. # Variables: -# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Recent uploaded recordings'. +# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Your recent uploaded recordings'. ListOfPublishedProfiles--uploaded-profile-information-label = { $profilesRestCount -> [one] Visualizza e gestisci tutte le tue registrazioni ({ $profilesRestCount } altra) @@ -317,6 +317,7 @@ MenuButtons--metaInfo--symbolicate-profile = Simbolizza il profilo MenuButtons--metaInfo--attempting-resymbolicate = Tentativo di risimbolizzare il profilo MenuButtons--metaInfo--currently-symbolicating = Profilo attualmente in fase di simbolizzazione MenuButtons--metaInfo--cpu = CPU: +MenuButtons--metaInfo--main-memory = Memoria principale: # This string is used when we have the information about both physical and # logical CPU cores. # Variable: @@ -348,6 +349,7 @@ MenuButtons--metaInfo--logical-cpu = *[other] { $logicalCPUs } core logici } MenuButtons--metaInfo--main-process-started = Processo principale avviato: +MenuButtons--metaInfo--main-process-ended = Processo principale completato: MenuButtons--metaInfo--interval = Intervallo: MenuButtons--metaInfo--buffer-capacity = Capacità buffer: MenuButtons--metaInfo--buffer-duration = Durata buffer: @@ -366,6 +368,7 @@ MenuButtons--metaInfo--name-and-version = Nome e versione: MenuButtons--metaInfo--update-channel = Canale di aggiornamento: MenuButtons--metaInfo--build-id = ID build: MenuButtons--metaInfo--build-type = Tipo di build: +MenuButtons--metaInfo--arguments = Argomenti: ## Strings refer to specific types of builds, and should be kept in English. @@ -597,6 +600,12 @@ TrackContextMenu--hide-all-matching-tracks = Nascondi tutte le tracce corrispond # Variables: # $searchFilter (String) - The search filter string that user enters. TrackContextMenu--no-results-found = Nessun risultato trovato per “{ $searchFilter }” +# This button appears when hovering a track name and is displayed as an X icon. +TrackNameButton--hide-track = + .title = Nascondi traccia +# This button appears when hovering a global track name and is displayed as an X icon. +TrackNameButton--hide-process = + .title = Nascondi processo ## TrackMemoryGraph ## This is used to show the memory graph of that process in the timeline part of @@ -607,20 +616,46 @@ TrackMemoryGraph--relative-memory-at-this-time = memoria relativa al momento TrackMemoryGraph--memory-range-in-graph = intervallo di memoria nel grafico TrackMemoryGraph--operations-since-the-previous-sample = operazioni dal campione precedente -## TrackPowerGraph +## TrackPower ## This is used to show the power used by the CPU and other chips in a computer, ## graphed over time. ## It's not displayed by default in the UI, but an example can be found at ## https://share.firefox.dev/3a1fiT7. -# This is used in the tooltip when the power value uses the Watt unit. +# This is used in the tooltip when the power value uses the watt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-watt = Consumo: { $value } W -# This is used in the tooltip when the power value uses the Milliwatt unit. +TrackPower--tooltip-power-watt = { $value } W + .label = Consumo +# This is used in the tooltip when the instant power value uses the milliwatt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-milliwatt = Consumo: { $value } mW +TrackPower--tooltip-power-milliwatt = { $value } mW + .label = Consumo +# This is used in the tooltip when the energy used in the current range uses the +# watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh + .label = Energia utilizzata nell’intervallo visualizzato +# This is used in the tooltip when the energy used in the current range uses the +# milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh + .label = Energia utilizzata nell’intervallo visualizzato +# This is used in the tooltip when the energy used in the current preview +# selection uses the watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh + .label = Energia utilizzata nella selezione corrente +# This is used in the tooltip when the energy used in the current preview +# selection uses the milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh + .label = Energia utilizzata nella selezione corrente ## TrackSearchField ## The component that is used for the search input in the track context menu. diff --git a/locales/kab/app.ftl b/locales/kab/app.ftl index 868c6ad554..e33701e6aa 100644 --- a/locales/kab/app.ftl +++ b/locales/kab/app.ftl @@ -113,10 +113,6 @@ FooterLinks--hide-button = ## The timeline component of the full view in the analysis UI at the top of the ## page. -FullTimeline--graph-type = Anaw n udfil: -FullTimeline--categories-with-cpu = taggayin s CPU -FullTimeline--categories = Taggayin -FullTimeline--stack-height = Teɣzi n tbursa # This string is used as the text of the track selection button. # Displays the ratio of visible tracks count to total tracks count in the timeline. # We have spans here to make the numbers bold. @@ -140,7 +136,6 @@ Home--profiler-motto = Ṭṭef amaɣnu n temlellit. Sleḍ-it. Bḍu-t. Err web Home--additional-content-title = Sali imuɣna yellan Home--additional-content-content = Tzemreḍ ad tzuɣreḍ syen sers afaylu n umaɣnu da i usali-ines, neɣ: Home--compare-recordings-info = Tzemreḍ daɣen ad tsenmehleḍ iseklasen. Ldi agrudem n usnemhel. -Home--recent-uploaded-recordings-title = Iseklasen i d-ulin melmi kan ## IdleSearchField ## The component that is used for all the search inputs in the application. @@ -164,9 +159,9 @@ ListOfPublishedProfiles--published-profiles-link = ListOfPublishedProfiles--published-profiles-delete-button-disabled = Kkes .title = Amaɣnu-a ur yezmir ara ad yettwakkes acku ur nesɛi ara talɣut n usireg. ListOfPublishedProfiles--uploaded-profile-information-list-empty = Ulac ameɣnu i d-yettwasulin akka ar tura! -# This string is used below the 'Recent uploaded recordings' list section. +# This string is used below the 'Your recent uploaded recordings' list section. # Variables: -# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Recent uploaded recordings'. +# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Your recent uploaded recordings'. ListOfPublishedProfiles--uploaded-profile-information-label = Wali syen sefrek meṛṛa iseklasen-ik·im ({ $profilesRestCount } d wugar) # Depending on the number of uploaded profiles, the message is different. # Variables: @@ -232,7 +227,8 @@ MenuButtons--index--profile-info-uploaded-actions = Kkes MenuButtons--index--metaInfo-subtitle = Talɣut n umaɣnu MenuButtons--metaInfo--symbols = Izamulen: MenuButtons--metaInfo--cpu = CPU: -MenuButtons--metaInfo--recording-started = Asekles yebda: +MenuButtons--metaInfo--main-process-started = Asesfer agejdan yebda: +MenuButtons--metaInfo--main-process-ended = Asesfer agejdan yekfa: MenuButtons--metaInfo--interval = Azilal: MenuButtons--metaInfo--buffer-capacity = Tazmert n uḥraz: MenuButtons--metaInfo--buffer-duration = Tanzgat n uḥraz: @@ -251,6 +247,7 @@ MenuButtons--metaInfo--name-and-version = Isem akked lqem: MenuButtons--metaInfo--update-channel = Leqqem abadu: MenuButtons--metaInfo--build-id = Asulay n lebni: MenuButtons--metaInfo--build-type = Anaw n lebni: +MenuButtons--metaInfo--arguments = Ifakulen: ## Strings refer to specific types of builds, and should be kept in English. @@ -267,6 +264,7 @@ MenuButtons--metaInfo--os = Anagraw n wammud: MenuButtons--metaInfo--abi = ABI: MenuButtons--metaInfo--speed-index = Amatar arurad: MenuButtons--metaInfo-renderRowOfList-label-features = Timahilin: +MenuButtons--metaInfo-renderRowOfList-label-threads-filter = Imsizdeg n usqerdec: MenuButtons--metaInfo-renderRowOfList-label-extensions = Isiɣzaf: ## Overhead refers to the additional resources used to run the profiler. @@ -285,9 +283,15 @@ MenuButtons--metaOverheadStatistics-statkeys-interval = Azilal ## Publish panel ## These strings are used in the publishing panel. +MenuButtons--publish--renderCheckbox-label-include-other-tabs = Seddu isefka seg waccaren-nniḍen MenuButtons--publish--renderCheckbox-label-include-screenshots = Seddu inegzumen +MenuButtons--publish--renderCheckbox-label-resource = Seddu URLs d yiberdan n tiɣbula MenuButtons--publish--renderCheckbox-label-extension = Seddu talɣut n usiɣzef MenuButtons--publish--renderCheckbox-label-preference = Seddu azalen n usmenyif +MenuButtons--publish--renderCheckbox-label-private-browsing = Seddu isefka seg yisfuyla n tunigin tusligt +MenuButtons--publish--renderCheckbox-label-private-browsing-warning-image = + .title = Amaɣnu-a yegber isefka n tunigin tusligt +MenuButtons--publish--reupload-performance-profile = Ales asali n umaɣnu n temlellit MenuButtons--publish--share-performance-profile = Bḍu amaɣnu n usmenyif MenuButtons--publish--info-description-default = S wudem amezwer, isefka-ik·im udmawanen ttwakksen. MenuButtons--publish--button-upload = Sali @@ -321,6 +325,34 @@ NumberFormat--short-date = { SHORTDATE($date) } ## Profile Delete Button +# This string is used on the tooltip of the published profile links delete button in uploaded recordings page. +# Variables: +# $smallProfileName (String) - Shortened name for the published Profile. +ProfileDeleteButton--delete-button = + .label = Kkes + .title = Sit dagi i tukksa n umaɣnu { $smallProfileName } + +## Profile Delete Panel +## This panel is displayed when the user clicks on the Profile Delete Button, +## it's a confirmation dialog. + +# This string is used when there's an error while deleting a profile. The link +# will show the error message when hovering. +ProfileDeletePanel--delete-error = Tella-d tuccḍa lawan n tukksa n umaɣna-a Ɛeddi ɣef useɣwen-a i wakken ad teẓreḍ ugar +# This is the title of the dialog +# Variables: +# $profileName (string) - Some string that identifies the profile +ProfileDeletePanel--dialog-title = Kkes { $profileName } +ProfileDeletePanel--dialog-cancel-button = + .value = Sefsex +ProfileDeletePanel--dialog-delete-button = + .value = Kkes +# This is used inside the Delete button after the user has clicked it, as a cheap +# progress indicator. +ProfileDeletePanel--dialog-deleting-button = + .value = Tukksa… +# This message is displayed when a profile has been successfully deleted. +ProfileDeletePanel--message-success = Isefka i d-yulin ttwakksen akken iwata ## ProfileFilterNavigator ## This is used at the top of the profile analysis UI. @@ -328,6 +360,13 @@ NumberFormat--short-date = { SHORTDATE($date) } ## Profile Loader Animation +ProfileLoaderAnimation--loading-unpublished = Aktar n umaɣnu srid seg { -firefox-brand-name }… +ProfileLoaderAnimation--loading-from-file = Taɣuri n ufaylu d usesfer n umaɣnu… +ProfileLoaderAnimation--loading-local = Ur yettwasebded ara yakan. +ProfileLoaderAnimation--loading-public = Asader d usesfer n umaɣnu… +ProfileLoaderAnimation--loading-from-url = Asader d usesfer n umaɣnu… +ProfileLoaderAnimation--loading-compare = Taquri d usesfer n yimuɣna… +ProfileLoaderAnimation--loading-view-not-found = Ur tettwaf ara teskant ## ProfileRootMessage @@ -338,9 +377,10 @@ ProfileRootMessage--additional = Uɣal ɣer ugejdan ## This is the component responsible for handling the service worker installation ## and update. It appears at the top of the UI. -ServiceWorkerManager--installing-button = Asebded… +ServiceWorkerManager--applying-button = Asnas iteddu… ServiceWorkerManager--pending-button = Snes syen ales asali ServiceWorkerManager--installed-button = Ales asali n usnas +ServiceWorkerManager--new-version-is-ready = Lqem amaynut n usnas yettwasader, yewjed i useqqdec ServiceWorkerManager--hide-notice-button = .title = Ffer alɣu-a d-yulin i tikkelt-nniḍen .aria-label = Ffer alɣu-a d-yulin i tikkelt-nniḍen @@ -364,6 +404,7 @@ TabBar--network-tab = Aẓeṭṭa ## This is used as a context menu for timeline to organize the tracks in the ## analysis UI. +TrackContextMenu--only-show-this-process = Sken kan asesfer-a # This is used as the context menu item to show only the given track. # Variables: # $trackName (String) - Name of the selected track to isolate. @@ -372,6 +413,14 @@ TrackContextMenu--only-show-track = Sken kan “{ $trackName }” # Variables: # $trackName (String) - Name of the selected track to hide. TrackContextMenu--hide-track = Ffer “{ $trackName }” +# This is used in the tracks context menu when the search filter doesn't match +# any track. +# Variables: +# $searchFilter (String) - The search filter string that user enters. +TrackContextMenu--no-results-found = Ulac igmaḍ yettwafen i “{ $searchFilter }” +# This button appears when hovering a global track name and is displayed as an X icon. +TrackNameButton--hide-process = + .title = Ffer asesfer ## TrackMemoryGraph ## This is used to show the memory graph of that process in the timeline part of @@ -380,6 +429,13 @@ TrackContextMenu--hide-track = Ffer “{ $trackName }” TrackMemoryGraph--relative-memory-at-this-time = takatut tamassaɣt deg wakud-a +## TrackPowerGraph +## This is used to show the power used by the CPU and other chips in a computer, +## graphed over time. +## It's not displayed by default in the UI, but an example can be found at +## https://share.firefox.dev/3a1fiT7. + + ## TrackSearchField ## The component that is used for the search input in the track context menu. diff --git a/locales/nl/app.ftl b/locales/nl/app.ftl index e9acf19350..690f05a7f9 100644 --- a/locales/nl/app.ftl +++ b/locales/nl/app.ftl @@ -217,10 +217,6 @@ FooterLinks--hide-button = ## The timeline component of the full view in the analysis UI at the top of the ## page. -FullTimeline--graph-type = Grafiektype: -FullTimeline--categories-with-cpu = Categorieën met CPU -FullTimeline--categories = Categorieën -FullTimeline--stack-height = Stackhoogte # This string is used as the text of the track selection button. # Displays the ratio of visible tracks count to total tracks count in the timeline. # We have spans here to make the numbers bold. @@ -267,7 +263,7 @@ Home--profiler-motto = Leg een prestatieprofiel vast. Analyseer het. Deel het. M Home--additional-content-title = Bestaande profielen laden Home--additional-content-content = U kunt een profielbestand hierheen verslepen om het te laden, of: Home--compare-recordings-info = U kunt ook opnamen vergelijken. De vergelijkingsinterface openen. -Home--recent-uploaded-recordings-title = Onlangs geüploade opnamen +Home--your-recent-uploaded-recordings-title = Uw onlangs geüploade opnamen ## IdleSearchField ## The component that is used for all the search inputs in the application. @@ -293,9 +289,9 @@ ListOfPublishedProfiles--published-profiles-link = ListOfPublishedProfiles--published-profiles-delete-button-disabled = Verwijderen .title = Dit profiel kan niet worden verwijderd, omdat we geen autorisatiegegevens hebben. ListOfPublishedProfiles--uploaded-profile-information-list-empty = Er is nog geen profiel geüpload! -# This string is used below the 'Recent uploaded recordings' list section. +# This string is used below the 'Your recent uploaded recordings' list section. # Variables: -# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Recent uploaded recordings'. +# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Your recent uploaded recordings'. ListOfPublishedProfiles--uploaded-profile-information-label = Al uw opnamen bekijken en beheren (nog { $profilesRestCount }) # Depending on the number of uploaded profiles, the message is different. # Variables: @@ -386,6 +382,7 @@ MenuButtons--metaInfo--symbolicate-profile = Profiel symboliseren MenuButtons--metaInfo--attempting-resymbolicate = Poging tot hersymboliseren profiel MenuButtons--metaInfo--currently-symbolicating = Profiel wordt gesymboliseerd MenuButtons--metaInfo--cpu = CPU: +MenuButtons--metaInfo--main-memory = Hoofdgeheugen: # This string is used when we have the information about both physical and # logical CPU cores. # Variable: @@ -417,6 +414,7 @@ MenuButtons--metaInfo--logical-cpu = *[other] { $logicalCPUs } logische kernen } MenuButtons--metaInfo--main-process-started = Hoofdproces gestart: +MenuButtons--metaInfo--main-process-ended = Hoofdproces beëindigd: MenuButtons--metaInfo--interval = Interval: MenuButtons--metaInfo--buffer-capacity = Buffercapaciteit: MenuButtons--metaInfo--buffer-duration = Bufferduur: @@ -435,6 +433,7 @@ MenuButtons--metaInfo--name-and-version = Naam en versie: MenuButtons--metaInfo--update-channel = Updatekanaal: MenuButtons--metaInfo--build-id = Build-ID: MenuButtons--metaInfo--build-type = Buildtype: +MenuButtons--metaInfo--arguments = Argumenten: ## Strings refer to specific types of builds, and should be kept in English. @@ -597,7 +596,7 @@ ProfileRootMessage--additional = Terug naar startpagina ## This is the component responsible for handling the service worker installation ## and update. It appears at the top of the UI. -ServiceWorkerManager--installing-button = Installeren… +ServiceWorkerManager--applying-button = Toepassen… ServiceWorkerManager--pending-button = Toepassen en opnieuw laden ServiceWorkerManager--installed-button = De toepassing opnieuw laden ServiceWorkerManager--updated-while-not-ready = @@ -671,6 +670,12 @@ TrackContextMenu--hide-all-matching-tracks = Alle overeenkomende tracks verberge # Variables: # $searchFilter (String) - The search filter string that user enters. TrackContextMenu--no-results-found = Geen resultaten gevonden voor ‘{ $searchFilter }’ +# This button appears when hovering a track name and is displayed as an X icon. +TrackNameButton--hide-track = + .title = Track verbergen +# This button appears when hovering a global track name and is displayed as an X icon. +TrackNameButton--hide-process = + .title = Proces verbergen ## TrackMemoryGraph ## This is used to show the memory graph of that process in the timeline part of @@ -681,20 +686,46 @@ TrackMemoryGraph--relative-memory-at-this-time = relatief geheugen op dit moment TrackMemoryGraph--memory-range-in-graph = geheugenbereik in grafiek TrackMemoryGraph--operations-since-the-previous-sample = bewerkingen sinds de vorige weergave -## TrackPowerGraph +## TrackPower ## This is used to show the power used by the CPU and other chips in a computer, ## graphed over time. ## It's not displayed by default in the UI, but an example can be found at ## https://share.firefox.dev/3a1fiT7. -# This is used in the tooltip when the power value uses the Watt unit. +# This is used in the tooltip when the power value uses the watt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-watt = Vermogen: { $value } W -# This is used in the tooltip when the power value uses the Milliwatt unit. +TrackPower--tooltip-power-watt = { $value } W + .label = Vermogen +# This is used in the tooltip when the instant power value uses the milliwatt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-milliwatt = Vermogen: { $value } mW +TrackPower--tooltip-power-milliwatt = { $value } mW + .label = Vermogen +# This is used in the tooltip when the energy used in the current range uses the +# watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh + .label = Verbruikte energie in het zichtbare bereik +# This is used in the tooltip when the energy used in the current range uses the +# milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh + .label = Verbruikte energie in het zichtbare bereik +# This is used in the tooltip when the energy used in the current preview +# selection uses the watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh + .label = Verbruikte energie in de huidige selectie +# This is used in the tooltip when the energy used in the current preview +# selection uses the milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh + .label = Verbruikte energie in de huidige selectie ## TrackSearchField ## The component that is used for the search input in the track context menu. diff --git a/locales/pt-BR/app.ftl b/locales/pt-BR/app.ftl index 1884db664c..91ff40fd98 100644 --- a/locales/pt-BR/app.ftl +++ b/locales/pt-BR/app.ftl @@ -200,7 +200,7 @@ Home--profiler-motto = Capture um profile de desempenho. Analise. Compartilhe. T Home--additional-content-title = Carregar profiles existentes Home--additional-content-content = Você pode arrastar e soltar aqui um arquivo de profile para carregar, ou: Home--compare-recordings-info = Você também pode comparar gravações. Abra a interface de comparação. -Home--recent-uploaded-recordings-title = Gravações enviadas recentemente +Home--your-recent-uploaded-recordings-title = Suas gravações enviadas recentemente ## IdleSearchField ## The component that is used for all the search inputs in the application. @@ -226,9 +226,9 @@ ListOfPublishedProfiles--published-profiles-link = ListOfPublishedProfiles--published-profiles-delete-button-disabled = Excluir .title = Este profile não pode ser excluído por falta de informações de autorização. ListOfPublishedProfiles--uploaded-profile-information-list-empty = Nenhum profile foi carregado ainda! -# This string is used below the 'Recent uploaded recordings' list section. +# This string is used below the 'Your recent uploaded recordings' list section. # Variables: -# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Recent uploaded recordings'. +# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Your recent uploaded recordings'. ListOfPublishedProfiles--uploaded-profile-information-label = Veja e gerencie todas as suas gravações (mais { $profilesRestCount }) # Depending on the number of uploaded profiles, the message is different. # Variables: @@ -319,6 +319,7 @@ MenuButtons--metaInfo--symbolicate-profile = Criar simbólicos no profile MenuButtons--metaInfo--attempting-resymbolicate = Tentando recriar simbólicos no profile MenuButtons--metaInfo--currently-symbolicating = Criando simbólicos no profile MenuButtons--metaInfo--cpu = CPU: +MenuButtons--metaInfo--main-memory = Memória principal: # This string is used when we have the information about both physical and # logical CPU cores. # Variable: @@ -350,6 +351,7 @@ MenuButtons--metaInfo--logical-cpu = *[other] { $logicalCPUs } cores lógicos } MenuButtons--metaInfo--main-process-started = Processo principal iniciado: +MenuButtons--metaInfo--main-process-ended = Processo principal finalizado: MenuButtons--metaInfo--interval = Intervalo: MenuButtons--metaInfo--buffer-capacity = Capacidade do buffer: MenuButtons--metaInfo--buffer-duration = Duração do buffer: @@ -368,6 +370,7 @@ MenuButtons--metaInfo--name-and-version = Nome e versão: MenuButtons--metaInfo--update-channel = Canal de atualização: MenuButtons--metaInfo--build-id = ID da compilação: MenuButtons--metaInfo--build-type = Tipo de compilação: +MenuButtons--metaInfo--arguments = Argumentos: ## Strings refer to specific types of builds, and should be kept in English. @@ -604,6 +607,12 @@ TrackContextMenu--hide-all-matching-tracks = Ocultar todas as faixas corresponde # Variables: # $searchFilter (String) - The search filter string that user enters. TrackContextMenu--no-results-found = Nenhum resultado encontrado de “{ $searchFilter }” +# This button appears when hovering a track name and is displayed as an X icon. +TrackNameButton--hide-track = + .title = Ocultar faixa +# This button appears when hovering a global track name and is displayed as an X icon. +TrackNameButton--hide-process = + .title = Ocultar processo ## TrackMemoryGraph ## This is used to show the memory graph of that process in the timeline part of @@ -614,20 +623,46 @@ TrackMemoryGraph--relative-memory-at-this-time = memória relativa neste momento TrackMemoryGraph--memory-range-in-graph = intervalo de memória no gráfico TrackMemoryGraph--operations-since-the-previous-sample = operações desde a amostra anterior -## TrackPowerGraph +## TrackPower ## This is used to show the power used by the CPU and other chips in a computer, ## graphed over time. ## It's not displayed by default in the UI, but an example can be found at ## https://share.firefox.dev/3a1fiT7. -# This is used in the tooltip when the power value uses the Watt unit. +# This is used in the tooltip when the power value uses the watt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-watt = Potência: { $value } W -# This is used in the tooltip when the power value uses the Milliwatt unit. +TrackPower--tooltip-power-watt = { $value } W + .label = Potência +# This is used in the tooltip when the instant power value uses the milliwatt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-milliwatt = Potência: { $value } mW +TrackPower--tooltip-power-milliwatt = { $value } mW + .label = Potência +# This is used in the tooltip when the energy used in the current range uses the +# watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh + .label = Energia usada na escala visível +# This is used in the tooltip when the energy used in the current range uses the +# milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh + .label = Energia usada na escala visível +# This is used in the tooltip when the energy used in the current preview +# selection uses the watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh + .label = Energia usada na seleção atual +# This is used in the tooltip when the energy used in the current preview +# selection uses the milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh + .label = Energia usada na seleção atual ## TrackSearchField ## The component that is used for the search input in the track context menu. diff --git a/locales/sv-SE/app.ftl b/locales/sv-SE/app.ftl index dd13958a32..93b54cc8a8 100644 --- a/locales/sv-SE/app.ftl +++ b/locales/sv-SE/app.ftl @@ -256,7 +256,7 @@ Home--profiler-motto = Spela in en prestandaprofil. Analysera den. Dela den. Gö Home--additional-content-title = Ladda befintliga profiler Home--additional-content-content = Du kan dra och släppa en profilfil här för att ladda den, eller: Home--compare-recordings-info = Du kan också jämföra inspelningar.Öppna gränssnitt för att jämföra. -Home--recent-uploaded-recordings-title = Senast uppladdade inspelningar +Home--your-recent-uploaded-recordings-title = Dina senaste uppladdade inspelningar ## IdleSearchField ## The component that is used for all the search inputs in the application. @@ -282,9 +282,9 @@ ListOfPublishedProfiles--published-profiles-link = ListOfPublishedProfiles--published-profiles-delete-button-disabled = Ta bort .title = Den här profilen kan inte tas bort eftersom vi saknar behörighetsinformation. ListOfPublishedProfiles--uploaded-profile-information-list-empty = Ingen profil har laddats upp än! -# This string is used below the 'Recent uploaded recordings' list section. +# This string is used below the 'Your recent uploaded recordings' list section. # Variables: -# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Recent uploaded recordings'. +# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Your recent uploaded recordings'. ListOfPublishedProfiles--uploaded-profile-information-label = Se och hantera alla dina inspelningar ({ $profilesRestCount } till) # Depending on the number of uploaded profiles, the message is different. # Variables: @@ -375,6 +375,7 @@ MenuButtons--metaInfo--symbolicate-profile = Symbolisera profil MenuButtons--metaInfo--attempting-resymbolicate = Försöker att symbolisera profilen på nytt MenuButtons--metaInfo--currently-symbolicating = Profilen symboliseras för närvarande MenuButtons--metaInfo--cpu = CPU: +MenuButtons--metaInfo--main-memory = Huvudminne: # This string is used when we have the information about both physical and # logical CPU cores. # Variable: @@ -406,6 +407,7 @@ MenuButtons--metaInfo--logical-cpu = *[other] { $logicalCPUs } logiska kärnor } MenuButtons--metaInfo--main-process-started = Huvudprocessen startade: +MenuButtons--metaInfo--main-process-ended = Huvudprocessen avslutad: MenuButtons--metaInfo--interval = Intervall: MenuButtons--metaInfo--buffer-capacity = Buffertkapacitet: MenuButtons--metaInfo--buffer-duration = Buffertlängd: @@ -424,6 +426,7 @@ MenuButtons--metaInfo--name-and-version = Namn och version: MenuButtons--metaInfo--update-channel = Uppdateringskanal: MenuButtons--metaInfo--build-id = Bygg-ID: MenuButtons--metaInfo--build-type = Byggtyp: +MenuButtons--metaInfo--arguments = Argument: ## Strings refer to specific types of builds, and should be kept in English. @@ -660,6 +663,12 @@ TrackContextMenu--hide-all-matching-tracks = Dölj alla matchande spår # Variables: # $searchFilter (String) - The search filter string that user enters. TrackContextMenu--no-results-found = Inga resultat hittades för “{ $searchFilter }” +# This button appears when hovering a track name and is displayed as an X icon. +TrackNameButton--hide-track = + .title = Dölj spår +# This button appears when hovering a global track name and is displayed as an X icon. +TrackNameButton--hide-process = + .title = Dölj process ## TrackMemoryGraph ## This is used to show the memory graph of that process in the timeline part of @@ -670,20 +679,46 @@ TrackMemoryGraph--relative-memory-at-this-time = relativa minnet vid denna tidpu TrackMemoryGraph--memory-range-in-graph = minnesintervall i grafen TrackMemoryGraph--operations-since-the-previous-sample = operationer sedan föregående prov -## TrackPowerGraph +## TrackPower ## This is used to show the power used by the CPU and other chips in a computer, ## graphed over time. ## It's not displayed by default in the UI, but an example can be found at ## https://share.firefox.dev/3a1fiT7. -# This is used in the tooltip when the power value uses the Watt unit. +# This is used in the tooltip when the power value uses the watt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-watt = Effekt: { $value } W -# This is used in the tooltip when the power value uses the Milliwatt unit. +TrackPower--tooltip-power-watt = { $value } W + .label = Effekt +# This is used in the tooltip when the instant power value uses the milliwatt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-milliwatt = Effekt: { $value } mW +TrackPower--tooltip-power-milliwatt = { $value } mW + .label = Effekt +# This is used in the tooltip when the energy used in the current range uses the +# watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh + .label = Energi som används i det synliga området +# This is used in the tooltip when the energy used in the current range uses the +# milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh + .label = Energi som används i det synliga området +# This is used in the tooltip when the energy used in the current preview +# selection uses the watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh + .label = Energi som används i det aktuella urvalet +# This is used in the tooltip when the energy used in the current preview +# selection uses the milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh + .label = Energi som används i det aktuella urvalet ## TrackSearchField ## The component that is used for the search input in the track context menu. diff --git a/locales/uk/app.ftl b/locales/uk/app.ftl index 25e1f12fee..fc825b90f4 100644 --- a/locales/uk/app.ftl +++ b/locales/uk/app.ftl @@ -259,7 +259,7 @@ Home--profiler-motto = Отримайте профіль швидкодії. П Home--additional-content-title = Завантажити наявні профілі Home--additional-content-content = Ви можете перетягнути файл профілю сюди, щоб завантажити його, або: Home--compare-recordings-info = Ви також можете порівняти записи. Відкрити інтерфейс порівняння. -Home--recent-uploaded-recordings-title = Останні завантаження +Home--your-recent-uploaded-recordings-title = Ваші недавно вивантажені записи ## IdleSearchField ## The component that is used for all the search inputs in the application. @@ -285,9 +285,9 @@ ListOfPublishedProfiles--published-profiles-link = ListOfPublishedProfiles--published-profiles-delete-button-disabled = Видалити .title = Цей профіль не можна видалити оскільки ми не маємо інформації про авторизацію. ListOfPublishedProfiles--uploaded-profile-information-list-empty = Жодного профілю ще не завантажено! -# This string is used below the 'Recent uploaded recordings' list section. +# This string is used below the 'Your recent uploaded recordings' list section. # Variables: -# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Recent uploaded recordings'. +# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Your recent uploaded recordings'. ListOfPublishedProfiles--uploaded-profile-information-label = Переглянути всі свої записи та керувати ними (ще { $profilesRestCount }) # Depending on the number of uploaded profiles, the message is different. # Variables: @@ -379,6 +379,7 @@ MenuButtons--metaInfo--symbolicate-profile = Символізувати проф MenuButtons--metaInfo--attempting-resymbolicate = Спроба повторно символізувати профіль MenuButtons--metaInfo--currently-symbolicating = Наразі профіль символізується MenuButtons--metaInfo--cpu = ЦП: +MenuButtons--metaInfo--main-memory = Основна пам'ять: # This string is used when we have the information about both physical and # logical CPU cores. # Variable: @@ -414,6 +415,7 @@ MenuButtons--metaInfo--logical-cpu = *[many] { $logicalCPUs } логічних ядер } MenuButtons--metaInfo--main-process-started = Основний процес розпочато: +MenuButtons--metaInfo--main-process-ended = Основний процес завершено: MenuButtons--metaInfo--interval = Інтервал: MenuButtons--metaInfo--buffer-capacity = Обсяг буфера: MenuButtons--metaInfo--buffer-duration = Тривалість буфера: @@ -433,6 +435,7 @@ MenuButtons--metaInfo--name-and-version = Назва та версія: MenuButtons--metaInfo--update-channel = Канал оновлень: MenuButtons--metaInfo--build-id = ID збірки: MenuButtons--metaInfo--build-type = Тип збірки: +MenuButtons--metaInfo--arguments = Аргументи: ## Strings refer to specific types of builds, and should be kept in English. @@ -669,6 +672,12 @@ TrackContextMenu--hide-all-matching-tracks = Сховати всі відпов # Variables: # $searchFilter (String) - The search filter string that user enters. TrackContextMenu--no-results-found = Не знайдено результатів за запитом “{ $searchFilter }” +# This button appears when hovering a track name and is displayed as an X icon. +TrackNameButton--hide-track = + .title = Сховати доріжку +# This button appears when hovering a global track name and is displayed as an X icon. +TrackNameButton--hide-process = + .title = Сховати процес ## TrackMemoryGraph ## This is used to show the memory graph of that process in the timeline part of @@ -679,20 +688,46 @@ TrackMemoryGraph--relative-memory-at-this-time = відносна пам'ять TrackMemoryGraph--memory-range-in-graph = діапазон пам'яті в графіку TrackMemoryGraph--operations-since-the-previous-sample = операції, починаючи з попереднього зразка -## TrackPowerGraph +## TrackPower ## This is used to show the power used by the CPU and other chips in a computer, ## graphed over time. ## It's not displayed by default in the UI, but an example can be found at ## https://share.firefox.dev/3a1fiT7. -# This is used in the tooltip when the power value uses the Watt unit. +# This is used in the tooltip when the power value uses the watt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-watt = Потужність: { $value } Вт -# This is used in the tooltip when the power value uses the Milliwatt unit. +TrackPower--tooltip-power-watt = { $value } Вт + .label = Потужність +# This is used in the tooltip when the instant power value uses the milliwatt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-milliwatt = Потужність: { $value } мВт +TrackPower--tooltip-power-milliwatt = { $value } мВт + .label = Потужність +# This is used in the tooltip when the energy used in the current range uses the +# watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-watthour = { $value } Вт·год + .label = Використовувана у видимому діапазоні енергія +# This is used in the tooltip when the energy used in the current range uses the +# milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } мВт·год + .label = Використовувана у видимому діапазоні енергія +# This is used in the tooltip when the energy used in the current preview +# selection uses the watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Вт·год + .label = Використовувана в поточному виборі енергія +# This is used in the tooltip when the energy used in the current preview +# selection uses the milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } Вт·год + .label = Використовувана в поточному виборі енергія ## TrackSearchField ## The component that is used for the search input in the track context menu. diff --git a/locales/zh-CN/app.ftl b/locales/zh-CN/app.ftl index 4382a11373..3259a17c83 100644 --- a/locales/zh-CN/app.ftl +++ b/locales/zh-CN/app.ftl @@ -160,10 +160,6 @@ FooterLinks--hide-button = ## The timeline component of the full view in the analysis UI at the top of the ## page. -FullTimeline--graph-type = 图标类型: -FullTimeline--categories-with-cpu = 含 CPU 的分类 -FullTimeline--categories = 分类 -FullTimeline--stack-height = 栈深度 # This string is used as the text of the track selection button. # Displays the ratio of visible tracks count to total tracks count in the timeline. # We have spans here to make the numbers bold. @@ -198,7 +194,7 @@ Home--profiler-motto = 捕捉性能分析记录。剖析、分享、让网站速 Home--additional-content-title = 加载现有分析记录 Home--additional-content-content = 您可以将分析记录拖放至此处,或: Home--compare-recordings-info = 您也可以比较记录内容。打开比较界面。 -Home--recent-uploaded-recordings-title = 近期上传的记录 +Home--your-recent-uploaded-recordings-title = 您最近上传的记录 ## IdleSearchField ## The component that is used for all the search inputs in the application. @@ -224,9 +220,9 @@ ListOfPublishedProfiles--published-profiles-link = ListOfPublishedProfiles--published-profiles-delete-button-disabled = 删除 .title = 由于缺少授权信息,无法删除此 Profile。 ListOfPublishedProfiles--uploaded-profile-information-list-empty = 还未上传任何分析记录! -# This string is used below the 'Recent uploaded recordings' list section. +# This string is used below the 'Your recent uploaded recordings' list section. # Variables: -# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Recent uploaded recordings'. +# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Your recent uploaded recordings'. ListOfPublishedProfiles--uploaded-profile-information-label = 查看并管理您的所有记录(还有 { $profilesRestCount } 条) # Depending on the number of uploaded profiles, the message is different. # Variables: @@ -317,6 +313,7 @@ MenuButtons--metaInfo--symbolicate-profile = 符号化分析记录 MenuButtons--metaInfo--attempting-resymbolicate = 正在尝试重新符号化分析记录 MenuButtons--metaInfo--currently-symbolicating = 当前符号化的分析记录 MenuButtons--metaInfo--cpu = CPU: +MenuButtons--metaInfo--main-memory = 主内存: # This string is used when we have the information about both physical and # logical CPU cores. # Variable: @@ -344,6 +341,7 @@ MenuButtons--metaInfo--logical-cpu = *[other] 逻辑核心 × { $logicalCPUs } } MenuButtons--metaInfo--main-process-started = 主进程开始: +MenuButtons--metaInfo--main-process-ended = 主进程结束: MenuButtons--metaInfo--interval = 间隔: MenuButtons--metaInfo--buffer-capacity = 缓冲容量: MenuButtons--metaInfo--buffer-duration = 缓冲间隔: @@ -361,6 +359,7 @@ MenuButtons--metaInfo--name-and-version = 名称和版本: MenuButtons--metaInfo--update-channel = 更新通道: MenuButtons--metaInfo--build-id = 构建 ID: MenuButtons--metaInfo--build-type = 构建类型: +MenuButtons--metaInfo--arguments = 参数: ## Strings refer to specific types of builds, and should be kept in English. @@ -521,7 +520,7 @@ ProfileRootMessage--additional = 返回主页 ## This is the component responsible for handling the service worker installation ## and update. It appears at the top of the UI. -ServiceWorkerManager--installing-button = 正在安装… +ServiceWorkerManager--applying-button = 正在应用… ServiceWorkerManager--pending-button = 应用并重新加载 ServiceWorkerManager--installed-button = 重新加载应用程序 ServiceWorkerManager--updated-while-not-ready = @@ -596,6 +595,12 @@ TrackContextMenu--hide-all-matching-tracks = 隐藏所有匹配的轨道 # Variables: # $searchFilter (String) - The search filter string that user enters. TrackContextMenu--no-results-found = 找不到“{ $searchFilter }”的结果 +# This button appears when hovering a track name and is displayed as an X icon. +TrackNameButton--hide-track = + .title = 隐藏轨道 +# This button appears when hovering a global track name and is displayed as an X icon. +TrackNameButton--hide-process = + .title = 隐藏进程 ## TrackMemoryGraph ## This is used to show the memory graph of that process in the timeline part of @@ -606,20 +611,46 @@ TrackMemoryGraph--relative-memory-at-this-time = 此时的相对内存用量 TrackMemoryGraph--memory-range-in-graph = 图表里的内存范围 TrackMemoryGraph--operations-since-the-previous-sample = 自前一次采样以来的操作次数 -## TrackPowerGraph +## TrackPower ## This is used to show the power used by the CPU and other chips in a computer, ## graphed over time. ## It's not displayed by default in the UI, but an example can be found at ## https://share.firefox.dev/3a1fiT7. -# This is used in the tooltip when the power value uses the Watt unit. +# This is used in the tooltip when the power value uses the watt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-watt = 功率:{ $value } W -# This is used in the tooltip when the power value uses the Milliwatt unit. +TrackPower--tooltip-power-watt = { $value } W + .label = 功率 +# This is used in the tooltip when the instant power value uses the milliwatt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-milliwatt = 功率:{ $value } mW +TrackPower--tooltip-power-milliwatt = { $value } mW + .label = 功率 +# This is used in the tooltip when the energy used in the current range uses the +# watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh + .label = 可见范围内的功耗 +# This is used in the tooltip when the energy used in the current range uses the +# milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh + .label = 可见范围内的功耗 +# This is used in the tooltip when the energy used in the current preview +# selection uses the watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh + .label = 当前选择范围内的功耗 +# This is used in the tooltip when the energy used in the current preview +# selection uses the milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh + .label = 当前选择范围内的功耗 ## TrackSearchField ## The component that is used for the search input in the track context menu. diff --git a/locales/zh-TW/app.ftl b/locales/zh-TW/app.ftl index 3629a1f1c1..0d5c5a7c3c 100644 --- a/locales/zh-TW/app.ftl +++ b/locales/zh-TW/app.ftl @@ -194,7 +194,7 @@ Home--profiler-motto = 捕捉效能檢測檔。分析、分享、讓網站運作 Home--additional-content-title = 載入現有檢測檔 Home--additional-content-content = 您可以將效能檢測檔拖曳到此處,或: Home--compare-recordings-info = 您也可以比較紀錄內容。開啟比較介面。 -Home--recent-uploaded-recordings-title = 近期上傳的紀錄 +Home--your-recent-uploaded-recordings-title = 您近期上傳的紀錄 ## IdleSearchField ## The component that is used for all the search inputs in the application. @@ -220,9 +220,9 @@ ListOfPublishedProfiles--published-profiles-link = ListOfPublishedProfiles--published-profiles-delete-button-disabled = 刪除 .title = 由於缺少授權資訊,無法刪除此效能檢測檔。 ListOfPublishedProfiles--uploaded-profile-information-list-empty = 還沒有上傳任何檢測檔! -# This string is used below the 'Recent uploaded recordings' list section. +# This string is used below the 'Your recent uploaded recordings' list section. # Variables: -# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Recent uploaded recordings'. +# $profilesRestCount (Number) - Remaining numbers of the uploaded profiles which are not listed under 'Your recent uploaded recordings'. ListOfPublishedProfiles--uploaded-profile-information-label = 檢視並管理您的所有紀錄檔(還有 { $profilesRestCount } 筆) # Depending on the number of uploaded profiles, the message is different. # Variables: @@ -313,6 +313,7 @@ MenuButtons--metaInfo--symbolicate-profile = 符號化檢測檔 MenuButtons--metaInfo--attempting-resymbolicate = 正在嘗試重新符號化檢測檔 MenuButtons--metaInfo--currently-symbolicating = 目前符號化的檢測檔 MenuButtons--metaInfo--cpu = CPU: +MenuButtons--metaInfo--main-memory = 主要記憶體: # This string is used when we have the information about both physical and # logical CPU cores. # Variable: @@ -340,6 +341,7 @@ MenuButtons--metaInfo--logical-cpu = *[other] { $logicalCPUs } 顆邏輯核心 } MenuButtons--metaInfo--main-process-started = 主處理程序開始: +MenuButtons--metaInfo--main-process-ended = 主要處理程序結束於: MenuButtons--metaInfo--interval = 間隔: MenuButtons--metaInfo--buffer-capacity = 緩衝容量: MenuButtons--metaInfo--buffer-duration = 緩衝間隔: @@ -357,6 +359,7 @@ MenuButtons--metaInfo--name-and-version = 名稱與版本: MenuButtons--metaInfo--update-channel = 更新頻道: MenuButtons--metaInfo--build-id = Build ID: MenuButtons--metaInfo--build-type = Build Type: +MenuButtons--metaInfo--arguments = 參數: ## Strings refer to specific types of builds, and should be kept in English. @@ -592,6 +595,12 @@ TrackContextMenu--hide-all-matching-tracks = 隱藏所有符合的軌道 # Variables: # $searchFilter (String) - The search filter string that user enters. TrackContextMenu--no-results-found = 找不到「{ $searchFilter }」的結果 +# This button appears when hovering a track name and is displayed as an X icon. +TrackNameButton--hide-track = + .title = 隱藏軌道 +# This button appears when hovering a global track name and is displayed as an X icon. +TrackNameButton--hide-process = + .title = 隱藏處理程序 ## TrackMemoryGraph ## This is used to show the memory graph of that process in the timeline part of @@ -602,20 +611,46 @@ TrackMemoryGraph--relative-memory-at-this-time = 此時的相對記憶體用量 TrackMemoryGraph--memory-range-in-graph = 圖表中的記憶體範圍 TrackMemoryGraph--operations-since-the-previous-sample = 自前一次取樣以來的操作次數 -## TrackPowerGraph +## TrackPower ## This is used to show the power used by the CPU and other chips in a computer, ## graphed over time. ## It's not displayed by default in the UI, but an example can be found at ## https://share.firefox.dev/3a1fiT7. -# This is used in the tooltip when the power value uses the Watt unit. +# This is used in the tooltip when the power value uses the watt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-watt = 電源: { $value } W -# This is used in the tooltip when the power value uses the Milliwatt unit. +TrackPower--tooltip-power-watt = { $value } W + .label = 功率 +# This is used in the tooltip when the instant power value uses the milliwatt unit. # Variables: # $value (String) - the power value at this location -TrackPowerGraph--tooltip-power-milliwatt = 電源: { $value } mW +TrackPower--tooltip-power-milliwatt = { $value } mW + .label = 功率 +# This is used in the tooltip when the energy used in the current range uses the +# watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-watthour = { $value } Wh + .label = 可見範圍內消耗的能源 +# This is used in the tooltip when the energy used in the current range uses the +# milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-range-milliwatthour = { $value } mWh + .label = 可見範圍內消耗的能源 +# This is used in the tooltip when the energy used in the current preview +# selection uses the watt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-watthour = { $value } Wh + .label = 目前選擇範圍內消耗的能源 +# This is used in the tooltip when the energy used in the current preview +# selection uses the milliwatt-hour unit. +# Variables: +# $value (String) - the energy value for this range +TrackPower--tooltip-energy-used-in-preview-milliwatthour = { $value } mWh + .label = 目前選擇範圍內消耗的能源 ## TrackSearchField ## The component that is used for the search input in the track context menu. diff --git a/package.json b/package.json index 1292158955..bdef3eedd9 100644 --- a/package.json +++ b/package.json @@ -49,26 +49,26 @@ }, "dependencies": { "@codemirror/lang-cpp": "^6.0.1", - "@codemirror/lang-javascript": "^6.0.2", + "@codemirror/lang-javascript": "^6.1.0", "@codemirror/lang-rust": "^6.0.0", "@codemirror/language": "^6.2.1", - "@codemirror/state": "^6.1.1", - "@codemirror/view": "^6.1.2", + "@codemirror/state": "^6.1.2", + "@codemirror/view": "^6.2.2", "@firefox-devtools/react-contextmenu": "^5.0.0", "@fluent/bundle": "^0.17.1", "@fluent/langneg": "^0.6.2", "@fluent/react": "^0.14.1", - "@lezer/highlight": "^1.0.0", + "@lezer/highlight": "^1.1.1", "array-move": "^3.0.1", "array-range": "^1.0.1", "clamp": "^1.0.1", - "classnames": "^2.3.1", + "classnames": "^2.3.2", "common-tags": "^1.8.2", "copy-to-clipboard": "^3.3.2", - "core-js": "^3.24.1", + "core-js": "^3.25.2", "escape-string-regexp": "^4.0.0", "gecko-profiler-demangle": "^0.3.3", - "idb": "^7.0.2", + "idb": "^7.1.0", "jszip": "^3.10.1", "memoize-immutable": "^3.0.0", "memoize-one": "^6.0.0", @@ -79,7 +79,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-intersection-observer": "^9.4.0", - "react-redux": "^8.0.2", + "react-redux": "^8.0.4", "react-splitter-layout": "^4.0.0", "react-transition-group": "^4.4.5", "redux": "^4.2.0", @@ -92,42 +92,41 @@ }, "devDependencies": { "@babel/cli": "^7.18.10", - "@babel/core": "^7.18.10", - "@babel/eslint-parser": "^7.18.9", - "@babel/eslint-plugin": "^7.18.10", + "@babel/core": "^7.19.1", + "@babel/eslint-parser": "^7.19.1", + "@babel/eslint-plugin": "^7.19.1", "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/preset-env": "^7.18.10", + "@babel/preset-env": "^7.19.0", "@babel/preset-flow": "^7.18.6", "@babel/preset-react": "^7.18.6", - "@testing-library/dom": "^8.17.1", + "@testing-library/dom": "^8.18.1", "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^13.3.0", + "@testing-library/react": "^13.4.0", "alex": "^10.0.0", - "autoprefixer": "^10.4.8", - "babel-jest": "^28.1.3", + "autoprefixer": "^10.4.11", + "babel-jest": "^29.0.1", "babel-loader": "^8.2.5", "babel-plugin-module-resolver": "^4.1.0", - "browserslist": "^4.21.2", + "browserslist": "^4.21.4", "caniuse-lite": "^1.0.30001359", "circular-dependency-plugin": "^5.2.1", "codecov": "^3.8.3", - "copy-webpack-plugin": "^6.3.2", + "copy-webpack-plugin": "^11.0.0", "cross-env": "^7.0.3", - "css-loader": "^5.2.7", + "css-loader": "^6.7.1", "cssnano": "^5.0.2", "devtools-license-check": "^0.9.0", - "eslint": "^8.21.0", + "eslint": "^8.23.1", "eslint-config-prettier": "^8.5.0", "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-flowtype": "^8.0.3", "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jest": "^26.8.3", + "eslint-plugin-jest": "^26.9.0", "eslint-plugin-jest-dom": "^4.0.2", "eslint-plugin-jest-formatting": "^3.1.0", "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-react": "^7.30.1", - "eslint-plugin-testing-library": "^5.6.0", + "eslint-plugin-react": "^7.31.1", + "eslint-plugin-testing-library": "^5.6.4", "fake-indexeddb": "^3.1.8", "fetch-mock-jest": "^1.5.1", "file-loader": "^6.2.0", @@ -135,31 +134,31 @@ "flow-coverage-report": "^0.8.0", "flow-typed": "^3.8.0", "glob": "^8.0.3", - "html-webpack-plugin": "^4.5.1", + "html-webpack-plugin": "^5.5.0", "husky": "^4.3.8", - "jest": "^28.1.3", - "jest-environment-jsdom": "^28.1.3", - "jest-extended": "^3.0.2", + "jest": "^29.0.1", + "jest-environment-jsdom": "^29.0.1", + "jest-extended": "^3.1.0", "json-loader": "^0.5.7", - "local-web-server": "^4.2.1", + "local-web-server": "^5.2.1", "lockfile-lint": "^4.8.0", "mkdirp": "^1.0.4", "node-fetch": "^2.6.7", "npm-run-all": "^4.1.5", "postcss": "^8.4.16", - "postcss-loader": "^4.2.0", + "postcss-loader": "^7.0.1", "prettier": "^2.7.1", "raw-loader": "^4.0.2", "rimraf": "^3.0.2", - "style-loader": "^2.0.0", - "stylelint": "^14.10.0", - "stylelint-config-idiomatic-order": "^8.1.0", + "style-loader": "^3.3.1", + "stylelint": "^14.12.1", + "stylelint-config-idiomatic-order": "^9.0.0", "stylelint-config-prettier": "^9.0.3", - "stylelint-config-standard": "^27.0.0", + "stylelint-config-standard": "^28.0.0", "stylelint-prettier": "^2.0.0", - "webpack": "^4.44.2", - "webpack-cli": "^3.3.12", - "webpack-dev-server": "^4.10.0", + "webpack": "^5.74.0", + "webpack-cli": "^4.10.0", + "webpack-dev-server": "^4.11.1", "workbox-webpack-plugin": "^6.5.4" }, "jest": { @@ -186,6 +185,10 @@ "globals": { "AVAILABLE_STAGING_LOCALES": null }, + "snapshotFormat": { + "escapeString": true, + "printBasicPrototype": true + }, "testEnvironment": "jsdom", "verbose": false }, diff --git a/res/_headers b/res/_headers index e1a377dcde..346c5bf8cc 100644 --- a/res/_headers +++ b/res/_headers @@ -14,14 +14,8 @@ Referrer-Policy: same-origin # 1. script-src - # a. The first hash is for the addon's content.js file being injected - # with WebExt's tabs.executeScript API. - # b. The second hash is for the addon's content-home.js being injected from - # the addon's manifest.json. - # You can generate these hashes using the website - # https://report-uri.io/home/hash after grabbing the script from the Inspector. - # NOTE: you can't use the script file directly as the API modifies it before injecting. - # c. We use Google Analytics to track the usage of the application. + # a. 'wasm-unsafe-eval' allows to execute wasm scripts without compromising the javascript CSP. + # b. We use Google Analytics to track the usage of the application. # 2. style-src # a. `unsafe-inline` is necessary to support favicons. # b. Google Fonts are used in the docs. @@ -32,7 +26,7 @@ # 7. `frame-ancestors` is the same purpose as `X-Frame-Options` above. # 8. `form-action`prevents forms, we don't need this.` # 9. `frame-src` allows the embedding of YouTube videos in the docs. - Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-eRTCQnd2fhPykpATDzCv4gdVk/EOdDq+6yzFXaWgGEw=' 'sha256-vY1KJ1dyP9vvnuERKMiQAcoKKtMUXZUEWJ/dT1XqpKM=' https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src http: https: data:; object-src 'none'; connect-src *; frame-ancestors 'self'; form-action 'none'; frame-src www.youtube-nocookie.com + Content-Security-Policy: default-src 'self'; script-src 'self' 'wasm-unsafe-eval' https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src http: https: data:; object-src 'none'; connect-src *; frame-ancestors 'self'; form-action 'none'; frame-src www.youtube-nocookie.com # Set the correct MIME type for WebAssembly modules. /*.wasm diff --git a/res/css/photon/button.css b/res/css/photon/button.css index e81d53ba75..21ae58569b 100644 --- a/res/css/photon/button.css +++ b/res/css/photon/button.css @@ -6,6 +6,7 @@ .photon-button { padding: 0 8px; border: none; + border-radius: 2px; margin: 0; /* reset default styles */ @@ -13,7 +14,6 @@ /* photon styles */ background-color: var(--grey-90-a10); - border-radius: 2px; color: var(--grey-90); font: inherit; } diff --git a/res/css/photon/checkbox.css b/res/css/photon/checkbox.css index 4c9524afa2..5db0c2b9b4 100644 --- a/res/css/photon/checkbox.css +++ b/res/css/photon/checkbox.css @@ -6,9 +6,9 @@ position: relative; padding: 0; border: 1px solid var(--grey-90-a30); + border-radius: 2px; appearance: none; background-color: var(--grey-90-a10); - border-radius: 2px; } /* Remove the border, as it compets with box-shadow styles applied with focus.css */ diff --git a/res/css/photon/message-bar.css b/res/css/photon/message-bar.css index f2a67d6ce7..181a4f8b9d 100644 --- a/res/css/photon/message-bar.css +++ b/res/css/photon/message-bar.css @@ -13,13 +13,13 @@ * for the icon) + 16px (icon width) + 4px (right padding for the icon) + 4px * (space between icon and text) */ padding: 4px 4px 4px 32px; + border-radius: 4px; /* Note: 8px is: 4px (left padding for the message bar) + 4px (left padding * for the icon). And same for the top positioning, because we want the icon * to stick at the top when the text is multiline */ background: url(../../img/svg/info-icon.svg) no-repeat 8px 8px / 16px 16px var(--grey-20); - border-radius: 4px; color: var(--grey-90); font-size: 13px; font-weight: 400; diff --git a/res/css/photon/radio-button.css b/res/css/photon/radio-button.css index 214bd50bb9..5fc657c366 100644 --- a/res/css/photon/radio-button.css +++ b/res/css/photon/radio-button.css @@ -4,10 +4,10 @@ .photon-radio { border: 1px solid var(--grey-90-a30); + border-radius: 8px; appearance: none; background: none; background-color: var(--grey-90-a10); - border-radius: 8px; } /* Use this class with the photon-radio for the canonical photon styling. */ diff --git a/res/css/style.css b/res/css/style.css index 8f0e933afc..03e8dea49e 100644 --- a/res/css/style.css +++ b/res/css/style.css @@ -22,6 +22,9 @@ body { 'Helvetica Neue', sans-serif; font-size: 11px; + /* equal size all numbers, so that longer numbers are larger */ + font-variant-numeric: tabular-nums; + /* Disable the "rubberband" overscrolling effect in Chrome. This happens when * using the charts and the scroll wheel. */ overscroll-behavior: none; diff --git a/res/photon/webpack.config.js b/res/photon/webpack.config.js index 5055b77d1e..4af73d9501 100644 --- a/res/photon/webpack.config.js +++ b/res/photon/webpack.config.js @@ -10,7 +10,7 @@ module.exports = { rules: [ { test: /\.css?$/, - loaders: ['style-loader', 'css-loader'], + use: ['style-loader', 'css-loader'], include: [ path.join(projectRoot, 'src'), path.join(projectRoot, 'res'), @@ -19,7 +19,7 @@ module.exports = { }, { test: /\.(svg|png|jpg)$/, - loader: 'file-loader', + type: 'asset/resource', }, ], }, @@ -35,8 +35,7 @@ module.exports = { entry: './res/photon/index.js', output: { path: path.join(projectRoot, 'dist/photon'), - filename: '[hash].bundle.js', - chunkFilename: '[id].[hash].bundle.js', + filename: '[name].[contenthash].bundle.js', publicPath: '/photon/', }, }; diff --git a/server.js b/server.js index 0d5e8de2fa..bd3a9b092d 100644 --- a/server.js +++ b/server.js @@ -38,8 +38,7 @@ const serverConfig = { default-src 'self'; script-src 'self' - 'sha256-eRTCQnd2fhPykpATDzCv4gdVk/EOdDq+6yzFXaWgGEw=' - 'sha256-vY1KJ1dyP9vvnuERKMiQAcoKKtMUXZUEWJ/dT1XqpKM=' + 'wasm-unsafe-eval' https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; diff --git a/src/actions/app.js b/src/actions/app.js index 44f9b4e57e..2ba3166a7f 100644 --- a/src/actions/app.js +++ b/src/actions/app.js @@ -49,6 +49,7 @@ import type { ThunkAction, UrlState, UploadedProfileInformation, + IndexIntoCategoryList, } from 'firefox-profiler/types'; import type { TabSlug } from 'firefox-profiler/app-logic/tabs-handling'; import type { @@ -416,3 +417,14 @@ export function updateBrowserConnectionStatus( ): Action { return { type: 'UPDATE_BROWSER_CONNECTION_STATUS', browserConnectionStatus }; } + +export function toggleOpenCategoryInSidebar( + kind: string, + category: IndexIntoCategoryList +): Action { + return { + type: 'TOGGLE_SIDEBAR_OPEN_CATEGORY', + kind, + category, + }; +} diff --git a/src/app-logic/constants.js b/src/app-logic/constants.js index a792e7dbbf..924538302a 100644 --- a/src/app-logic/constants.js +++ b/src/app-logic/constants.js @@ -10,7 +10,7 @@ import type { MarkerPhase } from 'firefox-profiler/types'; export const GECKO_PROFILE_VERSION = 25; // The current version of the "processed" profile format. -export const PROCESSED_PROFILE_VERSION = 41; +export const PROCESSED_PROFILE_VERSION = 42; // The following are the margin sizes for the left and right of the timeline. Independent // components need to share these values. diff --git a/src/components/app/BottomBox.css b/src/components/app/BottomBox.css index b77da63251..15caca382f 100644 --- a/src/components/app/BottomBox.css +++ b/src/components/app/BottomBox.css @@ -62,9 +62,9 @@ flex-flow: row; align-items: center; padding: 15px; + border-radius: 10px; margin: auto; background-color: rgb(240 240 240 / 0.8); - border-radius: 10px; gap: 15px; word-break: break-word; } diff --git a/src/components/app/CompareHome.css b/src/components/app/CompareHome.css index 1be44c5118..2b9d036069 100644 --- a/src/components/app/CompareHome.css +++ b/src/components/app/CompareHome.css @@ -5,11 +5,11 @@ /* Box */ padding: 8em; border: 1px solid #ccc; + border-radius: 3px; margin: auto; /* Other */ background: #fff; - border-radius: 3px; box-shadow: 0 5px 25px #0b1f50; font-size: 130%; } diff --git a/src/components/app/ErrorBoundary.css b/src/components/app/ErrorBoundary.css index 29dac7c6d0..decb5e59bd 100644 --- a/src/components/app/ErrorBoundary.css +++ b/src/components/app/ErrorBoundary.css @@ -25,9 +25,9 @@ .appErrorBoundaryDetails { padding: 25px; + border-radius: 4px; margin: 12px 0; background: #fff; - border-radius: 4px; box-shadow: 0 0 10px rgb(0 0 0 / 0.1); color: var(--grey-70); font-family: monospace; diff --git a/src/components/app/Home.css b/src/components/app/Home.css index 89056df043..3b113d7885 100644 --- a/src/components/app/Home.css +++ b/src/components/app/Home.css @@ -15,9 +15,9 @@ .homeSpecialMessage { padding: 8px 16px; border: 1px solid #000; + border-radius: 3px; margin: 17px 0; background-color: #798fc8; - border-radius: 3px; color: #fff; } @@ -29,8 +29,8 @@ box-sizing: border-box; padding: 4em 8em; border: 1px solid #ccc; - background-color: #fff; border-radius: 3px; + background-color: #fff; box-shadow: 0 5px 25px #0b1f50; font-size: 130%; } @@ -39,9 +39,9 @@ display: inline-block; padding: 0 0.5em; border: 1px solid #ccc; + border-radius: 0.2em; margin: 0 0.2em; background-color: #f6f6f6; - border-radius: 0.2em; box-shadow: 0.1em 0.1em 0 #bbb; } diff --git a/src/components/app/KeyboardShortcut.css b/src/components/app/KeyboardShortcut.css index 21e8db4600..cb2772af4b 100644 --- a/src/components/app/KeyboardShortcut.css +++ b/src/components/app/KeyboardShortcut.css @@ -26,10 +26,10 @@ /* appKeyboardShortcuts margin top + bottom = 40px */ max-height: calc(100% - 40px); + border-radius: 5px; margin: 0 auto; animation: arrowPanelAppear 0.2s cubic-bezier(0.07, 0.95, 0, 1); background: #fff; - border-radius: 5px; filter: drop-shadow(0 0 0.5px rgb(0 0 0 / 0.4)) drop-shadow(0 4px 5px rgb(0 0 0 / 0.4)); } @@ -54,8 +54,8 @@ display: inline-flex; align-items: center; padding: 0 5px; - background: rgb(222 222 227 / 0.79); border-radius: 3px; + background: rgb(222 222 227 / 0.79); box-shadow: 1px 1px rgb(0 0 0 / 0.27); color: #000; margin-inline-start: 6px; @@ -91,12 +91,12 @@ .appKeyboardShortcutsHeaderClose { padding: 6px 6px 6px 30px; border: 0; + border-radius: 3px; /* Allow for the photon focus ring to fit in the space by using a 4px margin. */ margin: 4px; background: url(../../../res/img/svg/searchfield-cancel.svg) 10px center no-repeat; - border-radius: 3px; cursor: pointer; font-size: inherit; } diff --git a/src/components/app/MenuButtons/Publish.css b/src/components/app/MenuButtons/Publish.css index ddfa09113c..0f9fa96f90 100644 --- a/src/components/app/MenuButtons/Publish.css +++ b/src/components/app/MenuButtons/Publish.css @@ -141,14 +141,15 @@ .menuButtonsPublishUploadBar { overflow: hidden; height: 5px; - background-color: var(--grey-40); border-radius: 2px; + background-color: var(--grey-40); } .menuButtonsPublishUploadBarInner { position: absolute; top: 0; height: 3px; + border-radius: 0 2px 2px 0; animation: animate-stripes 1s linear infinite; background-color: var(--blue-50); background-image: linear-gradient( @@ -163,7 +164,6 @@ transparent 80% ); background-size: 21px 20px, 100% 100%, 100% 100%; - border-radius: 0 2px 2px 0; } @keyframes animate-stripes { diff --git a/src/components/app/MenuButtons/index.js b/src/components/app/MenuButtons/index.js index b3bb5c445b..6e4ae23462 100644 --- a/src/components/app/MenuButtons/index.js +++ b/src/components/app/MenuButtons/index.js @@ -109,7 +109,10 @@ class MenuButtonsImpl extends React.PureComponent { case 'local': return 'local'; case 'from-url': - return profileUrl.startsWith('http://127.0.0.1') ? 'local' : 'uploaded'; + return profileUrl.startsWith('http://127.0.0.1') || + profileUrl.startsWith('http://localhost') + ? 'local' + : 'uploaded'; case 'none': case 'uploaded-recordings': throw new Error(`The datasource ${dataSource} shouldn't happen here.`); diff --git a/src/components/app/ProfileName.css b/src/components/app/ProfileName.css index 5839e91cdb..d8c0944069 100644 --- a/src/components/app/ProfileName.css +++ b/src/components/app/ProfileName.css @@ -6,8 +6,8 @@ height: 17px; padding: 0 6px; border: none; - margin: 4px; border-radius: 1px; + margin: 4px; color: var(--grey-60); font: inherit; font-weight: 700; diff --git a/src/components/app/ProfileRootMessage.css b/src/components/app/ProfileRootMessage.css index fde52de4e9..cd3314ab61 100644 --- a/src/components/app/ProfileRootMessage.css +++ b/src/components/app/ProfileRootMessage.css @@ -20,8 +20,8 @@ box-sizing: border-box; padding: 3em; border: 1px solid #ccc; - background-color: #fff; border-radius: 3px; + background-color: #fff; box-shadow: 0 5px 25px #0b1f50; font-size: 130%; } @@ -52,10 +52,10 @@ .loading-div { position: absolute; height: 8px; + border-radius: 2px; animation-duration: 4000ms; animation-iteration-count: infinite; animation-name: loadingdiv; - border-radius: 2px; } .loading-row-1 { diff --git a/src/components/app/ProfileViewer.css b/src/components/app/ProfileViewer.css index 8e650a18b5..e59a7ccdca 100644 --- a/src/components/app/ProfileViewer.css +++ b/src/components/app/ProfileViewer.css @@ -76,12 +76,12 @@ flex: none; padding: 0; border: 1px solid var(--green-60); + border-radius: 3px; margin: 3px 0 3px 3px; /* Other */ background: var(--green-50) url(../../../res/img/svg/back-arrow.svg) center center no-repeat; - border-radius: 3px; color: #000; } diff --git a/src/components/app/SymbolicationStatusOverlay.css b/src/components/app/SymbolicationStatusOverlay.css index 1dc3dc29a4..1fbc0b1adc 100644 --- a/src/components/app/SymbolicationStatusOverlay.css +++ b/src/components/app/SymbolicationStatusOverlay.css @@ -12,8 +12,8 @@ padding-top: 8px; padding-right: 10px; padding-left: 10px; - background: var(--grey-20); border-radius: 0 0 5px 5px; + background: var(--grey-20); box-shadow: 0 0 0 0.5px rgb(0 0 0 / 0.1), 0 2px 4px rgb(0 0 0 / 0.1); line-height: 20px; text-align: center; diff --git a/src/components/app/UploadedRecordingsHome.css b/src/components/app/UploadedRecordingsHome.css index a1bb9cb5d4..9d9f48f335 100644 --- a/src/components/app/UploadedRecordingsHome.css +++ b/src/components/app/UploadedRecordingsHome.css @@ -9,11 +9,11 @@ box-sizing: border-box; padding: 3em 1em; border: 1px solid #ccc; + border-radius: 3px; margin: auto; /* Other */ background: #fff; - border-radius: 3px; box-shadow: 0 5px 25px #0b1f50; font-size: 13px; line-height: 1.5; diff --git a/src/components/app/ZipFileViewer.css b/src/components/app/ZipFileViewer.css index 953f7da48c..1e4a1b0e9d 100644 --- a/src/components/app/ZipFileViewer.css +++ b/src/components/app/ZipFileViewer.css @@ -29,10 +29,10 @@ box-sizing: border-box; flex-direction: column; border: 1px solid #ccc; + border-radius: 3px; /* Other */ background-color: #fff; - border-radius: 3px; box-shadow: 0 5px 25px #0b1f50; } @@ -72,8 +72,8 @@ */ max-width: 70%; padding: 0 5px; - background-color: var(--grey-30); border-radius: 3px; + background-color: var(--grey-30); font-family: monospace; text-overflow: ellipsis; } diff --git a/src/components/calltree/CallTree.css b/src/components/calltree/CallTree.css new file mode 100644 index 0000000000..7134a4daa8 --- /dev/null +++ b/src/components/calltree/CallTree.css @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +.treeViewHeaderColumn.total, +.treeViewHeaderColumn.self { + text-align: right; +} + +.treeViewFixedColumn.total { + width: 70px; +} + +/* The header for the total column spans both totalPercent and total columns */ +.treeViewHeaderColumn.total { + width: 120px; +} + +.treeViewFixedColumn.totalPercent { + width: 50px; + border-right: none; +} + +/* The header for the totalPercent column is not visible */ +.treeViewHeaderColumn.totalPercent { + display: none; +} + +.treeViewFixedColumn.self { + width: 70px; +} + +.treeViewFixedColumn.icon { + display: flex; + width: 19px; + flex-flow: column nowrap; + align-items: center; +} + +.treeViewRowColumn.total, +.treeViewRowColumn.totalPercent, +.treeViewRowColumn.self, +.treeViewRowColumn.timestamp { + text-align: right; +} + +.treeBadge.inlined, +.treeBadge.divergent-inlining { + background: url(../../../res/img/svg/inlined-icon.svg); +} diff --git a/src/components/calltree/CallTree.js b/src/components/calltree/CallTree.js index a90ca3a250..c787e8612a 100644 --- a/src/components/calltree/CallTree.js +++ b/src/components/calltree/CallTree.js @@ -6,7 +6,10 @@ import React, { PureComponent } from 'react'; import memoize from 'memoize-immutable'; import explicitConnect from 'firefox-profiler/utils/connect'; -import { TreeView } from 'firefox-profiler/components/shared/TreeView'; +import { + TreeView, + ColumnSortState, +} from 'firefox-profiler/components/shared/TreeView'; import { CallTreeEmptyReasons } from './CallTreeEmptyReasons'; import { Icon } from 'firefox-profiler/components/shared/Icon'; import { getCallNodePathFromIndex } from 'firefox-profiler/profile-logic/profile-data'; @@ -46,6 +49,8 @@ import type { CallTree as CallTreeType } from 'firefox-profiler/profile-logic/ca import type { Column } from 'firefox-profiler/components/shared/TreeView'; import type { ConnectedProps } from 'firefox-profiler/utils/connect'; +import './CallTree.css'; + type StateProps = {| +threadsKey: ThreadsKey, +scrollToSelectionGeneration: number, @@ -85,6 +90,23 @@ class CallTreeImpl extends PureComponent { }; _treeView: TreeView | null = null; _takeTreeViewRef = (treeView) => (this._treeView = treeView); + _sortableColumns = new Set(['self', 'total']); + _sortedColumns = new ColumnSortState([]); + + _compareColumn = ( + first: CallNodeDisplayData, + second: CallNodeDisplayData, + column: string + ) => { + switch (column) { + case 'total': + return second.rawTotal - first.rawTotal; + case 'self': + return second.rawSelf - first.rawSelf; + default: + throw new Error('Invalid column ' + column); + } + }; /** * Call Trees can have different types of "weights" for the data. Choose the @@ -258,6 +280,10 @@ class CallTreeImpl extends PureComponent { } } + _onSort = (sortedColumns: ColumnSortState) => { + this._sortedColumns = sortedColumns; + }; + render() { const { tree, @@ -294,6 +320,10 @@ class CallTreeImpl extends PureComponent { onKeyDown={this._onKeyDown} onEnterKey={this._onEnterOrDoubleClick} onDoubleClick={this._onEnterOrDoubleClick} + initialSortedColumns={this._sortedColumns} + onSort={this._onSort} + compareColumn={this._compareColumn} + sortableColumns={this._sortableColumns} /> ); } diff --git a/src/components/marker-table/index.css b/src/components/marker-table/index.css index 99e620f024..b2c2eab337 100644 --- a/src/components/marker-table/index.css +++ b/src/components/marker-table/index.css @@ -11,25 +11,23 @@ } .treeViewFixedColumn.start { - left: 0; width: 80px; - padding-right: 5px; text-align: right; } .treeViewFixedColumn.duration { - left: 80px; width: 80px; - padding-right: 5px; - border-right: none; text-align: right; } +.treeViewHeaderColumn.name { + padding: 0 20px; +} + .treeViewFixedColumn.type { - left: 160px; - width: 80px; + width: 100px; } -.treeViewMainColumn.name { - left: 255px; +.treeViewRowColumn.type { + text-align: left; } diff --git a/src/components/marker-table/index.js b/src/components/marker-table/index.js index 6928167fd2..895f7f3ad4 100644 --- a/src/components/marker-table/index.js +++ b/src/components/marker-table/index.js @@ -8,7 +8,7 @@ import React, { PureComponent } from 'react'; import memoize from 'memoize-immutable'; import explicitConnect from '../../utils/connect'; -import { TreeView } from '../shared/TreeView'; +import { TreeView, ColumnSortState } from '../shared/TreeView'; import { MarkerTableEmptyReasons } from './MarkerTableEmptyReasons'; import { getZeroAt, @@ -42,7 +42,9 @@ const MAX_DESCRIPTION_CHARACTERS = 500; type MarkerDisplayData = {| start: string, + rawStart: Milliseconds, duration: string | null, + rawDuration: Milliseconds | null, name: string, type: string, |}; @@ -113,10 +115,13 @@ class MarkerTree { } let duration = null; + let rawDuration: number | null = null; + const markerEnd = marker.end; if (marker.incomplete) { duration = 'unknown'; - } else if (marker.end !== null) { - duration = formatTimestamp(marker.end - marker.start); + } else if (markerEnd !== null) { + duration = formatTimestamp(markerEnd - marker.start); + rawDuration = markerEnd - marker.start; } displayData = { @@ -124,6 +129,8 @@ class MarkerTree { duration, name, type: getMarkerSchemaName(this._markerSchemaByName, marker), + rawDuration: rawDuration, + rawStart: marker.start, }; this._displayDataByIndex.set(markerIndex, displayData); } @@ -160,11 +167,13 @@ class MarkerTableImpl extends PureComponent { { propName: 'duration', titleL10nId: 'MarkerTable--duration' }, { propName: 'type', titleL10nId: 'MarkerTable--type' }, ]; + _sortableColumns = new Set(['start', 'duration', 'type', 'name']); _mainColumn = { propName: 'name', titleL10nId: 'MarkerTable--description' }; _expandedNodeIds: Array = []; _onExpandedNodeIdsChange = () => {}; _treeView: ?TreeView; _takeTreeViewRef = (treeView) => (this._treeView = treeView); + _sortedColumns = new ColumnSortState([]); getMarkerTree = memoize((...args) => new MarkerTree(...args), { limit: 1 }); @@ -200,6 +209,33 @@ class MarkerTableImpl extends PureComponent { changeRightClickedMarker(threadsKey, selectedMarker); }; + _compareColumn = ( + first: MarkerDisplayData, + second: MarkerDisplayData, + column: string + ) => { + switch (column) { + case 'start': + return second.rawStart - first.rawStart; + case 'duration': + if (first.rawDuration === null) { + return -1; + } + if (second.rawDuration === null) { + return 1; + } + return second.rawDuration - first.rawDuration; + case 'type': + return first.type.localeCompare(second.type); + default: + throw new Error('Invalid column ' + column); + } + }; + + _onSort = (sortedColumns: ColumnSortState) => { + this._sortedColumns = sortedColumns; + }; + render() { const { getMarker, @@ -210,7 +246,7 @@ class MarkerTableImpl extends PureComponent { markerSchemaByName, getMarkerLabel, } = this.props; - const tree = this.getMarkerTree( + const tree: MarkerTree = this.getMarkerTree( getMarker, markerIndexes, zeroAt, @@ -243,6 +279,10 @@ class MarkerTableImpl extends PureComponent { contextMenuId="MarkerContextMenu" rowHeight={16} indentWidth={10} + initialSortedColumns={this._sortedColumns} + onSort={this._onSort} + compareColumn={this._compareColumn} + sortableColumns={this._sortableColumns} /> )} diff --git a/src/components/network-chart/index.css b/src/components/network-chart/index.css index 5ceb016cc4..79e96c5c98 100644 --- a/src/components/network-chart/index.css +++ b/src/components/network-chart/index.css @@ -54,11 +54,11 @@ display: inline-block; overflow: hidden; /* This clips this element's children using its border-radius */ height: 14px; + border-radius: 2px; margin: 1px 0; /* Because the line's height is 16px, this margin vertically centers the bar */ /* styling properties */ background-color: var(--grey-20); - border-radius: 2px; box-shadow: 0 0 0 1px inset var(--marker-color); opacity: 0.7; } diff --git a/src/components/shared/ButtonWithPanel/ArrowPanel.css b/src/components/shared/ButtonWithPanel/ArrowPanel.css index ef5544961f..995a927ccc 100644 --- a/src/components/shared/ButtonWithPanel/ArrowPanel.css +++ b/src/components/shared/ButtonWithPanel/ArrowPanel.css @@ -19,8 +19,8 @@ top: var(--internal-offset-from-top); right: calc(var(--internal-offset-from-right) * -1); min-width: var(--internal-width); - background: hsl(0deg 0% 97% / 0.95); border-radius: 5px; + background: hsl(0deg 0% 97% / 0.95); color: black; filter: drop-shadow(0 0 0.5px rgb(0 0 0 / 0.4)) drop-shadow(0 4px 5px rgb(0 0 0 / 0.4)); diff --git a/src/components/shared/CallNodeContextMenu.css b/src/components/shared/CallNodeContextMenu.css index 94f3682b63..3698c40b3a 100644 --- a/src/components/shared/CallNodeContextMenu.css +++ b/src/components/shared/CallNodeContextMenu.css @@ -33,11 +33,11 @@ .callNodeContextMenuShortcut { padding: 0 5px; + border-radius: 3px; /* This color is based off of photon grey, but adjusted to have a nice visual look when hovering. */ background: #dedee3c9; - border-radius: 3px; box-shadow: 1px 1px #0004; /* Override the hover color. */ diff --git a/src/components/shared/ContextMenu.css b/src/components/shared/ContextMenu.css index cf56fa883f..130b8a1986 100644 --- a/src/components/shared/ContextMenu.css +++ b/src/components/shared/ContextMenu.css @@ -8,9 +8,9 @@ min-width: 160px; max-width: 600px; padding: 5px 0; + border-radius: 3px; margin: 2px 0 0; background-color: #fff; - border-radius: 3px; box-shadow: 0 0 0 0.5px rgb(0 0 0 / 0.1), 0 10px 12px rgb(0 0 0 / 0.3); color: #000; font-size: 12px; diff --git a/src/components/shared/SourceView-codemirror.js b/src/components/shared/SourceView-codemirror.js index e5e862675a..a3ac66a09b 100644 --- a/src/components/shared/SourceView-codemirror.js +++ b/src/components/shared/SourceView-codemirror.js @@ -63,6 +63,7 @@ function _languageExtForPath( path.endsWith('.js') || path.endsWith('.jsm') || path.endsWith('.jsx') || + path.endsWith('.mjs') || path.endsWith('.ts') || path.endsWith('.tsx') ) { diff --git a/src/components/shared/TreeView.css b/src/components/shared/TreeView.css index 19f621c8a7..373ef83b1d 100644 --- a/src/components/shared/TreeView.css +++ b/src/components/shared/TreeView.css @@ -11,10 +11,21 @@ user-select: none; } +.treeViewRow, .treeViewHeader { - position: relative; - height: 16px; - padding: 1px 0; + display: flex; + flex-flow: row nowrap; + justify-content: flex-start; + white-space: nowrap; +} + +.treeViewRowScrolledColumns { + align-items: center; +} + +.treeViewHeader { + height: 15px; + padding: 4px 0; border-bottom: 1px solid var(--grey-30); background: white; } @@ -64,85 +75,44 @@ background: white; } -.treeViewRow { - display: flex; - flex-flow: row nowrap; - align-items: center; - justify-content: flex-start; - white-space: nowrap; -} - .treeViewHeaderColumn { - position: absolute; - top: 0; - bottom: 0; + position: relative; box-sizing: border-box; - padding: 1px 5px; + padding: 0 5px; line-height: 15px; white-space: nowrap; } -.treeViewHeaderColumn.treeViewFixedColumn::after { - position: absolute; - top: 3px; - right: 0; - bottom: 3px; - width: 1px; - background: #e5e5e5; - content: ''; +.treeViewFixedColumn { + overflow: hidden; + padding: 0 5px; + text-overflow: ellipsis; } -.treeViewHeaderColumn.total, -.treeViewHeaderColumn.self { - text-align: right; +.treeViewHeaderColumn.treeViewFixedColumn { + /* The fixed columns in the row don't shrink because they're positioning using + * position: sticky, therefore we prevent shrinking for the columns in the + * header too. */ + flex: none; + border-right: 1px solid #e2e2e2; } .treeViewRowColumn.treeViewFixedColumn { - overflow: hidden; box-sizing: border-box; border-right: 1px solid var(--grey-30); - text-overflow: ellipsis; } -.treeViewFixedColumn.total { - left: 0; - width: 70px; +.treeViewHeaderColumn.sortInactive::after { + content: " ▲"; + opacity: 0; } -.treeViewFixedColumn.totalPercent { - left: 70px; - width: 50px; - border-right: none; +.treeViewHeaderColumn.sortAscending::after { + content: ' ▲'; } -.treeViewFixedColumn.self { - left: 120px; - width: 70px; -} - -.treeViewHeaderColumn.total { - width: 120px; -} - -.treeViewFixedColumn.icon { - left: 190px; - display: flex; - width: 19px; - flex-flow: column nowrap; - align-items: center; -} - -.treeViewRowColumn.total, -.treeViewRowColumn.totalPercent, -.treeViewRowColumn.self, -.treeViewRowColumn.timestamp { - padding-right: 5px; - text-align: right; -} - -.treeViewRowColumn.type { - padding-left: 5px; - text-align: left; +.treeViewHeaderColumn.sortDescending::after { + content: ' ▼ '; } .treeBadge { @@ -155,11 +125,6 @@ vertical-align: -2px; } -.treeBadge.inlined, -.treeBadge.divergent-inlining { - background: url(../../../res/img/svg/inlined-icon.svg); -} - .treeRowIndentSpacer { flex-shrink: 0; } @@ -213,13 +178,13 @@ .treeViewHighlighting { padding: 1px 0; + border-radius: 2px; /* This negative margin enlarges the background to the top, so that it fully * covers the underlying background. There's an underlying background when the * line is selected. */ margin: -1px 0; background: #cbe8fe; - border-radius: 2px; box-shadow: 0 0 0 0.5px rgb(0 0 0 / 0.05), 0 1px 1px rgb(0 0 0 / 0.3); color: #000; } diff --git a/src/components/shared/TreeView.js b/src/components/shared/TreeView.js index e8e01d8021..114df69ebd 100644 --- a/src/components/shared/TreeView.js +++ b/src/components/shared/TreeView.js @@ -53,40 +53,119 @@ export type Column = {| |}>, |}; +type SingleColumnSortState = {| + column: string, + ascending: boolean, +|}; + type TreeViewHeaderProps = {| +fixedColumns: Column[], +mainColumn: Column, + +onSort: (string) => void, + +currentSortedColumn: SingleColumnSortState | null, |}; -const TreeViewHeader = ({ - fixedColumns, - mainColumn, -}: TreeViewHeaderProps) => { - if (fixedColumns.length === 0 && !mainColumn.titleL10nId) { - // If there is nothing to display in the header, do not render it. - return null; +export class ColumnSortState { + sortedColumns: SingleColumnSortState[]; + + // start by sorting last column + constructor(sortedColumns: SingleColumnSortState[]) { + this.sortedColumns = sortedColumns; + } + + sortColumn(column: string, ascending: boolean | null = null) { + const current = this.getStateForColumn(column); + const sortedColumns = this.sortedColumns.filter((c) => c.column !== column); + if (ascending === true || current === null) { + sortedColumns.push({ column, ascending: true }); + } else { + sortedColumns.push(this._invertSortState(current)); + } + return new ColumnSortState(sortedColumns); + } + + _invertSortState(state: SingleColumnSortState): SingleColumnSortState { + return { column: state.column, ascending: !state.ascending }; } - return ( -
- {fixedColumns.map((col) => ( + + getStateForColumn(column: string): SingleColumnSortState | null { + return this.sortedColumns + .filter((c) => c.column === column) + .concat(null)[0]; + } + + getStateForColumnOrDefault(column: string): SingleColumnSortState { + return this.sortedColumns + .filter((c) => c.column === column) + .concat({ column: column, ascending: true })[0]; + } + + current(): SingleColumnSortState | null { + return this.sortedColumns.length > 0 + ? this.sortedColumns[this.sortedColumns.length - 1] + : null; + } +} + +class TreeViewHeader extends React.PureComponent< + TreeViewHeaderProps +> { + _onSort = (e: MouseEvent) => { + const { onSort } = this.props; + const target = e.target; + if (target instanceof HTMLElement) { + onSort(String(target.getAttribute('data-column'))); + } + }; + + render() { + const { fixedColumns, mainColumn, currentSortedColumn } = this.props; + if (fixedColumns.length === 0 && !mainColumn.titleL10nId) { + // If there is nothing to display in the header, do not render it. + return null; + } + return ( +
+ {fixedColumns.map((col) => { + let sortClass = ''; + if ( + currentSortedColumn && + currentSortedColumn.column === col.propName + ) { + if (currentSortedColumn.ascending) { + sortClass = 'sortDescending'; + } else { + sortClass = 'sortAscending'; + } + } else { + sortClass = 'sortInactive'; + } + return ( + + + + ); + })} - ))} - - - -
- ); -}; +
+ ); + } +} function reactStringWithHighlightedSubstrings( string: string, @@ -393,13 +472,31 @@ type TreeViewProps = {| +rowHeight: CssPixels, +indentWidth: CssPixels, +onKeyDown?: (SyntheticKeyboardEvent<>) => void, + // number: column number, -1: first larger, 0: same, 1: first smaller + +compareColumn?: (DisplayData, DisplayData, string) => number, + +initialSortedColumns?: ColumnSortState, + +onSort?: (ColumnSortState) => void, + +sortableColumns?: Set, +|}; + +type TreeViewState = {| + sortedColumns: ColumnSortState, |}; export class TreeView extends React.PureComponent< - TreeViewProps + TreeViewProps, + TreeViewState > { _list: VirtualList | null = null; _takeListRef = (list: VirtualList | null) => (this._list = list); + state = { sortedColumns: new ColumnSortState([]) }; + + constructor(props: TreeViewProps) { + super(props); + if (props.initialSortedColumns) { + this.state.sortedColumns = props.initialSortedColumns; + } + } // The tuple `specialItems` always contains 2 elements: the first element is // the selected node id (if any), and the second element is the right clicked @@ -422,7 +519,37 @@ export class TreeView extends React.PureComponent< ); _computeAllVisibleRowsMemoized = memoize( - (tree: Tree, expandedNodes: Set) => { + ( + tree: Tree, + expandedNodes: Set, + sortedColumns: ColumnSortState, + compareColumn: ?(DisplayData, DisplayData, string) => number + ) => { + function sortNodes(nodeIds: Array): Array { + if (!compareColumn) { + return nodeIds; + } + let sortedNodeIds = nodeIds; + for (const { column, ascending } of sortedColumns.sortedColumns) { + const sign = ascending ? 1 : -1; + sortedNodeIds = sortedNodeIds.sort((a, b) => { + return ( + sign * + ((compareColumn: any): ( + DisplayData, + DisplayData, + string + ) => number)( + tree.getDisplayData(a), + tree.getDisplayData(b), + column + ) + ); + }); + } + return sortedNodeIds; + } + function _addVisibleRowsFromNode(tree, expandedNodes, arr, nodeId) { arr.push(nodeId); if (!expandedNodes.has(nodeId)) { @@ -433,8 +560,8 @@ export class TreeView extends React.PureComponent< _addVisibleRowsFromNode(tree, expandedNodes, arr, children[i]); } } - - const roots = tree.getRoots(); + // we only sort the root nodes for now + const roots = sortNodes(tree.getRoots()); const allRows = []; for (let i = 0; i < roots.length; i++) { _addVisibleRowsFromNode(tree, expandedNodes, allRows, roots[i]); @@ -458,7 +585,6 @@ export class TreeView extends React.PureComponent< _renderRow = (nodeId: NodeIndex, index: number, columnIndex: number) => { const { - tree, fixedColumns, mainColumn, appendageColumn, @@ -468,6 +594,7 @@ export class TreeView extends React.PureComponent< rowHeight, indentWidth, } = this.props; + const { tree } = this.props; const displayData = tree.getDisplayData(nodeId); // React converts height into 'px' values, while lineHeight is valid in // non-'px' units. @@ -518,8 +645,13 @@ export class TreeView extends React.PureComponent< } _getAllVisibleRows(): NodeIndex[] { - const { tree } = this.props; - return this._computeAllVisibleRowsMemoized(tree, this._getExpandedNodes()); + const { tree, compareColumn } = this.props; + return this._computeAllVisibleRowsMemoized( + tree, + this._getExpandedNodes(), + this.state.sortedColumns, + compareColumn + ); } _getSpecialItems(): [NodeIndex | void, NodeIndex | void] { @@ -591,7 +723,8 @@ export class TreeView extends React.PureComponent< _onCopy = (event: ClipboardEvent) => { event.preventDefault(); - const { tree, selectedNodeId, mainColumn } = this.props; + const { selectedNodeId, mainColumn } = this.props; + const { tree } = this.props; if (selectedNodeId) { const displayData = tree.getDisplayData(selectedNodeId); const clipboardData: DataTransfer = (event: any).clipboardData; @@ -732,6 +865,25 @@ export class TreeView extends React.PureComponent< } } + _onSort = (column: string) => { + if ( + !this.props.sortableColumns || + !this.props.sortableColumns.has(column) + ) { + return; + } + + this.setState((x) => { + const newSortedColumns = x.sortedColumns.sortColumn(column); + if (this.props.onSort) { + this.props.onSort(newSortedColumns); + } + return { + sortedColumns: newSortedColumns, + }; + }); + }; + render() { const { fixedColumns, @@ -743,9 +895,16 @@ export class TreeView extends React.PureComponent< rowHeight, selectedNodeId, } = this.props; + const { sortedColumns } = this.state; + const currentSortedColumn = sortedColumns.current(); return (
- + string, |}; -type CategoryBreakdownState = {| - +openCategories: Set, +type CategoryBreakdownStateProps = {| + +sidebarOpenCategories: Map>, |}; -class CategoryBreakdown extends React.PureComponent< - CategoryBreakdownProps, - CategoryBreakdownState -> { - state = { - openCategories: new Set(), - }; +type CategoryBreakdownDispatchProps = {| + +toggleOpenCategoryInSidebar: typeof toggleOpenCategoryInSidebar, +|}; + +type CategoryBreakdownAllProps = ConnectedProps< + CategoryBreakdownOwnProps, + CategoryBreakdownStateProps, + CategoryBreakdownDispatchProps +>; +class CategoryBreakdownImpl extends React.PureComponent { _toggleCategory = (event: SyntheticInputEvent<>) => { - const { category } = event.target.dataset; - if (typeof category !== 'string') { - throw new Error('Expected to find a category on the clicked element.'); - } - this.setState(({ openCategories }) => { - const newCategories = new Set(openCategories); - if (openCategories.has(category)) { - newCategories.delete(category); - } else { - newCategories.add(category); - } - return { openCategories: newCategories }; - }); + const { toggleOpenCategoryInSidebar, kind } = this.props; + const { categoryIndex } = event.target.dataset; + toggleOpenCategoryInSidebar(kind, parseInt(categoryIndex, 10)); }; render() { - const { breakdown, categoryList, number } = this.props; + const { breakdown, categoryList, number, sidebarOpenCategories, kind } = + this.props; const data = breakdown .map((oneCategoryBreakdown, categoryIndex) => { const category = categoryList[categoryIndex]; return { + categoryIndex, category, value: oneCategoryBreakdown.entireCategoryValue || 0, subcategories: category.subcategories @@ -210,14 +210,14 @@ class CategoryBreakdown extends React.PureComponent< 0 ); - const { openCategories } = this.state; - return ( <> - {data.map(({ category, value, subcategories }) => { + {data.map(({ category, value, subcategories, categoryIndex }) => { const hasSubcategory = shouldDisplaySubcategoryInfoForCategory(category); - const expanded = openCategories.has(category.name); + const openCats = sidebarOpenCategories.get(kind); + const expanded = + openCats !== undefined && openCats.has(categoryIndex); return ( ({ + mapStateToProps: (state) => { + return { + sidebarOpenCategories: getSidebarOpenCategories(state), + }; + }, + mapDispatchToProps: { toggleOpenCategoryInSidebar }, + component: CategoryBreakdownImpl, +}); + type StateProps = {| +selectedNodeIndex: IndexIntoCallNodeTable | null, +callNodeTable: CallNodeTable, @@ -363,6 +377,7 @@ class CallTreeSidebarImpl extends React.PureComponent { const totalTimePercent = Math.round((totalTime.value / rootTime) * 100); const selfTimePercent = Math.round((selfTime.value / rootTime) * 100); const totalTimeBreakdownByCategory = totalTime.breakdownByCategory; + const selfTimeBreakdownByCategory = selfTime.breakdownByCategory; const totalTimeBreakdownByImplementation = totalTime.breakdownByImplementation; const selfTimeBreakdownByImplementation = @@ -431,12 +446,29 @@ class CallTreeSidebarImpl extends React.PureComponent {
) : null} + {selfTimeBreakdownByCategory ? ( + <> +

+
Categories
+
+ Self {getWeightTypeLabel(weightType)} +
+

+ + + ) : null} {totalTimeBreakdownByImplementation && totalTime.value ? (

diff --git a/src/components/timeline/Selection.css b/src/components/timeline/Selection.css index 5601efb6a5..0be94c0c6f 100644 --- a/src/components/timeline/Selection.css +++ b/src/components/timeline/Selection.css @@ -78,9 +78,9 @@ width: 0; padding: 3px; border: 1px solid white; + border-radius: 5px; margin: 0 -4px; background: #aaa; - border-radius: 5px; cursor: ew-resize; } @@ -113,8 +113,8 @@ position: absolute; top: 20px; padding: 4px 8px; - background-color: var(--blue-50); border-radius: 0 0 4px 4px; + background-color: var(--blue-50); box-shadow: 0 2px 2px rgb(0 0 0 / 0.2); color: #fff; opacity: 1; @@ -132,10 +132,10 @@ height: 30px; box-sizing: border-box; border: 1px solid rgb(0 0 0 / 0.2); + border-radius: 100%; margin: -15px; background: url(../../../res/img/svg/zoom-icon.svg) center center no-repeat rgb(255 255 255 / 0.6); - border-radius: 100%; opacity: 0.5; pointer-events: auto; transition: opacity 200ms ease-in-out; diff --git a/src/components/timeline/Track.css b/src/components/timeline/Track.css index f9f43a4a58..d1640fff99 100644 --- a/src/components/timeline/Track.css +++ b/src/components/timeline/Track.css @@ -69,10 +69,10 @@ flex-shrink: 0; padding: 1px; border: 0; + border-radius: 2px; background: url(../../../res/img/svg/close-dark.svg) no-repeat center; background-origin: content-box; background-size: contain; - border-radius: 2px; color: transparent; margin-inline-end: 2px; -moz-user-focus: ignore; diff --git a/src/components/timeline/TrackEventDelay.css b/src/components/timeline/TrackEventDelay.css index 858ebd0fb1..498da075a8 100644 --- a/src/components/timeline/TrackEventDelay.css +++ b/src/components/timeline/TrackEventDelay.css @@ -18,10 +18,10 @@ position: absolute; width: 6px; height: 6px; + border-radius: 3px; margin-top: -3px; margin-left: -3px; background-color: var(--orange-60); - border-radius: 3px; pointer-events: none; } diff --git a/src/components/timeline/TrackMemory.css b/src/components/timeline/TrackMemory.css index 46ce68c7d4..b51b182c9d 100644 --- a/src/components/timeline/TrackMemory.css +++ b/src/components/timeline/TrackMemory.css @@ -18,10 +18,10 @@ position: absolute; width: 6px; height: 6px; + border-radius: 3px; margin-top: -3px; margin-left: -3px; background-color: var(--orange-60); - border-radius: 3px; pointer-events: none; } diff --git a/src/components/timeline/TrackPower.css b/src/components/timeline/TrackPower.css index 276b72ba82..7ecd677585 100644 --- a/src/components/timeline/TrackPower.css +++ b/src/components/timeline/TrackPower.css @@ -18,20 +18,9 @@ position: absolute; width: 6px; height: 6px; + border-radius: 3px; margin-top: -3px; margin-left: -3px; background-color: var(--grey-50); - border-radius: 3px; pointer-events: none; } - -.timelineTrackPowerTooltipLine { - white-space: nowrap; -} - -.timelineTrackPowerTooltipNumber { - display: inline-block; - min-width: 60px; - color: var(--grey-60); - font-weight: bold; -} diff --git a/src/components/timeline/TrackPowerGraph.js b/src/components/timeline/TrackPowerGraph.js index 8d740143fe..fb37584271 100644 --- a/src/components/timeline/TrackPowerGraph.js +++ b/src/components/timeline/TrackPowerGraph.js @@ -5,10 +5,8 @@ // @flow import * as React from 'react'; -import { Localized } from '@fluent/react'; import { withSize } from 'firefox-profiler/components/shared/WithSize'; import explicitConnect from 'firefox-profiler/utils/connect'; -import { formatNumber } from 'firefox-profiler/utils/format-numbers'; import { bisectionRight } from 'firefox-profiler/utils/bisect'; import { getCommittedRange, @@ -18,6 +16,7 @@ import { import { getThreadSelectors } from 'firefox-profiler/selectors/per-thread'; import { GREY_50 } from 'photon-colors'; import { Tooltip } from 'firefox-profiler/components/tooltip/Tooltip'; +import { TooltipTrackPower } from 'firefox-profiler/components/tooltip/TrackPower'; import { EmptyThreadIndicator } from './EmptyThreadIndicator'; import type { @@ -314,9 +313,10 @@ class TrackPowerGraphImpl extends React.PureComponent { } }; - _renderTooltip(counterIndex: number): React.Node { - const { counter, interval, rangeStart, rangeEnd } = this.props; + _renderTooltip(counterSampleIndex: number): React.Node { + const { counter, rangeStart, rangeEnd } = this.props; const { mouseX, mouseY } = this.state; + const samples = counter.sampleGroups[0].samples; if (samples.length === 0) { // Gecko failed to capture samples for some reason and it shouldn't happen for @@ -324,7 +324,7 @@ class TrackPowerGraphImpl extends React.PureComponent { throw new Error('No sample found for power counter'); } - const sampleTime = samples.time[counterIndex]; + const sampleTime = samples.time[counterSampleIndex]; if (sampleTime < rangeStart || sampleTime > rangeEnd) { // Do not draw the tooltip if it will be rendered outside of the timeline. // This could happen when a sample time is outside of the time range. @@ -333,42 +333,12 @@ class TrackPowerGraphImpl extends React.PureComponent { return null; } - const powerUsageInPwh = samples.count[counterIndex]; // picowatt-hour - const sampleTimeDeltaInMs = - counterIndex === 0 - ? interval - : samples.time[counterIndex] - samples.time[counterIndex - 1]; - const power = - ((powerUsageInPwh * 1e-12) /* pWh->Wh */ / sampleTimeDeltaInMs) * - 1000 * // ms->s - 3600; // s->h - let value; - let l10nId; - if (power > 1) { - value = formatNumber(power, 3); - l10nId = 'TrackPowerGraph--tooltip-power-watt'; - } else if (power === 0) { - value = 0; - l10nId = 'TrackPowerGraph--tooltip-power-watt'; - } else { - value = formatNumber(power * 1000); - l10nId = 'TrackPowerGraph--tooltip-power-milliwatt'; - } return ( -
- , - }} - > -
- Power: {value} -
-
-
+
); } diff --git a/src/components/timeline/TrackProcessCPU.css b/src/components/timeline/TrackProcessCPU.css index 233e4b19bb..f834f41728 100644 --- a/src/components/timeline/TrackProcessCPU.css +++ b/src/components/timeline/TrackProcessCPU.css @@ -18,10 +18,10 @@ position: absolute; width: 6px; height: 6px; + border-radius: 3px; margin-top: -3px; margin-left: -3px; background-color: var(--grey-50); - border-radius: 3px; pointer-events: none; } diff --git a/src/components/timeline/TrackVisualProgress.css b/src/components/timeline/TrackVisualProgress.css index 85deec266b..a666104e06 100644 --- a/src/components/timeline/TrackVisualProgress.css +++ b/src/components/timeline/TrackVisualProgress.css @@ -18,10 +18,10 @@ position: absolute; width: 6px; height: 6px; + border-radius: 3px; margin-top: -3px; margin-left: -3px; background-color: var(--dot-color); - border-radius: 3px; pointer-events: none; } diff --git a/src/components/tooltip/CallNode.css b/src/components/tooltip/CallNode.css index 2b05e780f6..5d7fde20d3 100644 --- a/src/components/tooltip/CallNode.css +++ b/src/components/tooltip/CallNode.css @@ -26,15 +26,15 @@ position: relative; width: var(--graph-width); height: var(--graph-height); + border-radius: 2px; margin-top: 2px; background-color: var(--grey-30); - border-radius: 2px; } .tooltipCallNodeImplementationGraphRunning { height: var(--graph-height); - background-color: var(--blue-40); border-radius: 2px; + background-color: var(--blue-40); } .tooltipCallNodeImplementationGraphSelf { @@ -42,8 +42,8 @@ top: 0; left: 0; height: var(--graph-height); - background-color: var(--blue-60); border-radius: 2px; + background-color: var(--blue-60); } .tooltipCallNodeImplementationTiming { diff --git a/src/components/tooltip/Marker.css b/src/components/tooltip/Marker.css new file mode 100644 index 0000000000..ccbcfad6ad --- /dev/null +++ b/src/components/tooltip/Marker.css @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +.marker-list-value { + margin: 0; + padding-inline-start: 25px; +} + +/* These styles are useful to make the table well-aligned with its labels in tooltips. */ +.marker-table-value { + border-collapse: collapse; + margin-block-start: -1px; +} + +.marker-table-value th { + text-align: start; +} diff --git a/src/components/tooltip/Marker.js b/src/components/tooltip/Marker.js index f7b49ce383..5a769a7a97 100644 --- a/src/components/tooltip/Marker.js +++ b/src/components/tooltip/Marker.js @@ -35,7 +35,7 @@ import { import { Backtrace } from 'firefox-profiler/components/shared/Backtrace'; import { - formatFromMarkerSchema, + formatMarkupFromMarkerSchema, getSchemaFromMarker, } from 'firefox-profiler/profile-logic/marker-schema'; import { computeScreenshotSize } from 'firefox-profiler/profile-logic/marker-data'; @@ -62,6 +62,8 @@ import { getGCSliceDetails, } from './GCMarker'; +import './Marker.css'; + function _maybeFormatDuration( start: number | void, end: number | void @@ -245,7 +247,7 @@ class MarkerTooltipContents extends React.PureComponent { key={schema.name + '-' + key} label={label || key} > - {formatFromMarkerSchema(schema.name, format, value)} + {formatMarkupFromMarkerSchema(schema.name, format, value)} ); } diff --git a/src/components/tooltip/TrackPower.js b/src/components/tooltip/TrackPower.js new file mode 100644 index 0000000000..cf7ee8fe71 --- /dev/null +++ b/src/components/tooltip/TrackPower.js @@ -0,0 +1,166 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// @flow + +import * as React from 'react'; +import { Localized } from '@fluent/react'; +import memoize from 'memoize-one'; + +import explicitConnect from 'firefox-profiler/utils/connect'; +import { formatNumber } from 'firefox-profiler/utils/format-numbers'; +import { + getCommittedRange, + getPreviewSelection, + getProfileInterval, +} from 'firefox-profiler/selectors/profile'; +import { getSampleIndexRangeForSelection } from 'firefox-profiler/profile-logic/profile-data'; + +import { TooltipDetails, TooltipDetail } from './TooltipDetails'; + +import type { + Counter, + Milliseconds, + PreviewSelection, + StartEndRange, +} from 'firefox-profiler/types'; + +import type { ConnectedProps } from 'firefox-profiler/utils/connect'; + +type OwnProps = {| + counter: Counter, + counterSampleIndex: number, +|}; + +type StateProps = {| + interval: Milliseconds, + committedRange: StartEndRange, + previewSelection: PreviewSelection, +|}; + +type Props = ConnectedProps; + +class TooltipTrackPowerImpl extends React.PureComponent { + // This compute the sum of the power in the range. This returns a value in Wh. + _computePowerSumForRange(start: Milliseconds, end: Milliseconds): number { + const { counter } = this.props; + const samples = counter.sampleGroups[0].samples; + const [beginIndex, endIndex] = getSampleIndexRangeForSelection( + samples, + start, + end + ); + + let sum = 0; + for ( + let counterSampleIndex = beginIndex; + counterSampleIndex < endIndex; + counterSampleIndex++ + ) { + sum += samples.count[counterSampleIndex]; // picowatt-hour; + } + return sum * 1e-12; + } + + _computePowerSumForCommittedRange = memoize( + ({ start, end }: StartEndRange): number => + this._computePowerSumForRange(start, end) + ); + + _computePowerSumForPreviewRange = memoize( + ({ + selectionStart, + selectionEnd, + }: { + +hasSelection: true, + +selectionStart: number, + +selectionEnd: number, + }): number => this._computePowerSumForRange(selectionStart, selectionEnd) + ); + + _formatPowerValue( + power: number, + l10nIdUnit, + l10nIdMilliUnit, + l10nIdMicroUnit + ): Localized { + let value, l10nId; + if (power > 1) { + value = formatNumber(power, 3); + l10nId = l10nIdUnit; + } else if (power === 0) { + value = 0; + l10nId = l10nIdUnit; + } else if (power < 0.001 && l10nIdMicroUnit) { + value = formatNumber(power * 1000000); + l10nId = l10nIdMicroUnit; + } else { + value = formatNumber(power * 1000); + l10nId = l10nIdMilliUnit; + } + + return ( + + {value} + + ); + } + + render() { + const { + counter, + counterSampleIndex, + interval, + committedRange, + previewSelection, + } = this.props; + const samples = counter.sampleGroups[0].samples; + + const powerUsageInPwh = samples.count[counterSampleIndex]; // picowatt-hour + const sampleTimeDeltaInMs = + counterSampleIndex === 0 + ? interval + : samples.time[counterSampleIndex] - + samples.time[counterSampleIndex - 1]; + const power = + ((powerUsageInPwh * 1e-12) /* pWh->Wh */ / sampleTimeDeltaInMs) * + 1000 * // ms->s + 3600; // s->h + + return ( +
+ + {this._formatPowerValue( + power, + 'TrackPower--tooltip-power-watt', + 'TrackPower--tooltip-power-milliwatt' + )} + {previewSelection.hasSelection + ? this._formatPowerValue( + this._computePowerSumForPreviewRange(previewSelection), + 'TrackPower--tooltip-energy-used-in-preview-watthour', + 'TrackPower--tooltip-energy-used-in-preview-milliwatthour', + 'TrackPower--tooltip-energy-used-in-preview-microwatthour' + ) + : null} + {this._formatPowerValue( + this._computePowerSumForCommittedRange(committedRange), + 'TrackPower--tooltip-energy-used-in-range-watthour', + 'TrackPower--tooltip-energy-used-in-range-milliwatthour', + 'TrackPower--tooltip-energy-used-in-range-microwatthour' + )} + +
+ ); + } +} + +export const TooltipTrackPower = explicitConnect({ + mapStateToProps: (state) => ({ + interval: getProfileInterval(state), + committedRange: getCommittedRange(state), + previewSelection: getPreviewSelection(state), + }), + component: TooltipTrackPowerImpl, +}); diff --git a/src/profile-logic/address-timings.js b/src/profile-logic/address-timings.js new file mode 100644 index 0000000000..f456d771ce --- /dev/null +++ b/src/profile-logic/address-timings.js @@ -0,0 +1,646 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// @flow + +/** + * In this file, "address" always means "instruction address", expressed as a + * byte offset into a given library ("relative address"). + * + * The functions in this file (address-timings.js) behave very similarly to the + * ones in line-timings.js. + * line-timings.js is for the source view, and address-timings.js is for the + * assembly view. + * + * The assembly view displays the instructions for one "native symbol", i.e. for + * a function that the compiler created, and which the compiler didn't inline + * away entirely. Every such function has a start address (the symbol address) + * and a size in bytes. This defines an address range. + * + * Since the assembly view only displays the assembly code for a single function + * at a time, address-timings.js always computes information only for a single + * native symbol. It would also be reasonable to compute information for a + * single library; we'll see over time what makes more sense. The computed + * result for a single native symbol is small, but needs to be recomputed any + * time a different native symbol is selected. The computed result for an entire + * library would be quite large (e.g. all address hits for libxul.so), but it + * would not need to be recomputed when a different function is selected. + */ + +/** + * Quick recap of the relationship between addresses, frames, funcs, and native + * symbols for native code: + * - There is one native symbol per "outer" (i.e. non-inlined) function. + * - There is one func per function name + file name pair. Funcs are used for + * both inlined and non-inlined calls. + * - Each frame has at least the following properties: + * address, nativeSymbol, func, inlineDepth + * - As a result, there is a different frame for each sampled instruction address. + * - Multiple frames can share the same func. + * - Multiple frames can share the same native symbol. + * + * When there's inlining at a given address, then we create a multiple frames + * for that address with different func and inlineDepth values, and all these + * frames share the same address and native symbol. + * + * Here's an example "stack" tree. + * + * Before symbolication: + * + * - address:0x123 + * - address:0x250 + * - address:0x307 + * - address:0x427 + * - address:0x129 + * - address:0x435 + * + * After symbolication: + * + * - func:A nativeSymbol:A address:0x123 + * - func:B nativeSymbol:B address:0x250 + * - func:C nativeSymbol:B address:0x250 (inlineDepth:1) + * - func:D nativeSymbol:B address:0x250 (inlineDepth:2) + * - func:E nativeSymbol:E address:0x307 + * - func:F nativeSymbol:F address:0x427 + * - func:A nativeSymbol:A address:0x129 + * - func:G nativeSymbol:A address:0x129 (inlineDepth:1) + * - func:F nativeSymbol:F address:0x435 + * - func:D nativeSymbol:F address:0x435 (inlineDepth:1) + */ + +import type { + FrameTable, + FuncTable, + StackTable, + SamplesLikeTable, + CallNodeInfo, + IndexIntoCallNodeTable, + IndexIntoNativeSymbolTable, + StackAddressInfo, + AddressTimings, + Address, +} from 'firefox-profiler/types'; + +/** + * For each stack in `stackTable`, and one specific native symbol, compute the + * sets of addresses for frames belonging to that native symbol that are hit by + * the stack. + * + * For each stack we answer the following question: + * - "Does this stack contribute to address X's self time?" + * Answer: result.selfAddress[stack] === X + * - "Does this stack contribute to address X's total time?" + * Answer: result.stackAddresses[stack].has(X) + */ +export function getStackAddressInfo( + stackTable: StackTable, + frameTable: FrameTable, + funcTable: FuncTable, + nativeSymbol: IndexIntoNativeSymbolTable, + isInvertedTree: boolean +): StackAddressInfo { + return isInvertedTree + ? getStackAddressInfoInverted( + stackTable, + frameTable, + funcTable, + nativeSymbol + ) + : getStackAddressInfoNonInverted( + stackTable, + frameTable, + funcTable, + nativeSymbol + ); +} + +/** + * This function handles the non-inverted case of getStackAddressInfo. + * + * Compute the sets of instruction addresses for the given native symbol that + * are hit by each stack. + * For each stack in the stack table and each address for the native symbol, we + * answer the questions "Does this stack contribute to address X's self time? + * Does it contribute to address X's total time?" + * Each stack can only contribute to one address's self time: the address of the + * stack's own frame. + * But each stack can contribute to the total time of multiple addresses for a + * single native symbol, if there's recursion and the same native symbol (outer + * function) is present in multiple places in the stack. + * E.g if function A calls into B which calls into A, the call path [A, B, A] + * will contribute to the total time of 2 addresses: + * 1. The address in function A which has the call instruction to B, + * 2. The address in function A that is being executed at that stack (stack.frame.address). + * And if the call to B has been inlined into A, then it'll still just be two + * addresses, because the inlined frame has the same address as its parent frame. + * But with more complicated recursion you could have more than two addresses + * from the same native symbol in the same stack. + * + * This last address in a stack is the stack's "self address". + * If there is recursion, and the same address is present in multiple frames in + * the same stack, the address is only counted once - the addresses are stored + * in a set. + * + * The returned StackAddressInfo is computed as follows: + * selfAddress[stack]: + * For stacks whose stack.frame.nativeSymbol is the given native symbol, + * this is stack.frame.address. + * For all other stacks this is null. + * stackAddresses[stack]: + * For stacks whose stack.frame.nativeSymbol is the given native symbol, + * this is the stackAddresses of its prefix stack, plus stack.frame.address + * added to the set. + * For all other stacks this is the same as the stackAddresses set of the + * stack's prefix. + */ +export function getStackAddressInfoNonInverted( + stackTable: StackTable, + frameTable: FrameTable, + funcTable: FuncTable, + nativeSymbol: IndexIntoNativeSymbolTable +): StackAddressInfo { + // "self address" == "the address which a stack's self time is contributed to" + const selfAddressForAllStacks = []; + // "total addresses" == "the set of addresses whose total time this stack contributes to" + const totalAddressesForAllStacks = []; + + // This loop takes advantage of the fact that the stack table is topologically ordered: + // Prefix stacks are always visited before their descendants. + // Each stack inherits the "total" addresses from its parent stack, and then adds its + // self address to that set. If the stack doesn't have a self address in the library, we just + // re-use the prefix's set object without copying it. + for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) { + const frame = stackTable.frame[stackIndex]; + const prefixStack = stackTable.prefix[stackIndex]; + const nativeSymbolOfThisStack = frameTable.nativeSymbol[frame]; + + let selfAddress: Address | null = null; + let totalAddresses: Set
| null = + prefixStack !== null ? totalAddressesForAllStacks[prefixStack] : null; + + if (nativeSymbolOfThisStack === nativeSymbol) { + selfAddress = frameTable.address[frame]; + if (selfAddress !== -1) { + // Add this stack's address to this stack's totalAddresses. The rest of this stack's + // totalAddresses is the same as for the parent stack. + // We avoid creating new Set objects unless the new set is actually + // different. + if (totalAddresses === null) { + // None of the ancestor stack nodes have hit a address in the given library. + totalAddresses = new Set([selfAddress]); + } else if (!totalAddresses.has(selfAddress)) { + totalAddresses = new Set(totalAddresses); + totalAddresses.add(selfAddress); + } + } + } + + selfAddressForAllStacks.push(selfAddress); + totalAddressesForAllStacks.push(totalAddresses); + } + return { + selfAddress: selfAddressForAllStacks, + stackAddresses: totalAddressesForAllStacks, + }; +} + +/** + * This function handles the inverted case of getStackAddressInfo. + * + * The return value should exactly match what you'd get if you called + * `getStackAddressInfo` on the corresponding non-inverted thread. + * This function can probably be removed once we handle call tree inversion + * differently. + * + * Reminder about inverted threads: The self time is in the *root* nodes. Example: + * + * Stack node A, address 0x20 + * (called by) Stack node B, address 0x30 + * + * The inverted stack [A, B] contributes to the self time of address 0x20. + * + * The returned StackAddressInfo is computed as follows: + * selfAddress[stack]: + * For (inverted thread) root stack nodes whose stack.frame.nativeSymbol is + * the given native symbol, this is stack.frame.address. + * For (inverted thread) root stack nodes whose frame is in a different + * native symbol, this is null. + * For (inverted thread) *non-root* stack nodes, this is the same as the + * selfAddress of the stack's prefix. This way, the selfAddress is always + * inherited from the subtree root. + * stackAddresses[stack]: + * For stacks whose stack.frame.nativeSymbol is the given native symbol, + * this is the stackAddresses of its (inverted thread) prefix stack, plus + * stack.frame.address added to the set. + * For all other stacks this is the same as the stackAddresses set of the + * stack's prefix. + */ +export function getStackAddressInfoInverted( + stackTable: StackTable, + frameTable: FrameTable, + funcTable: FuncTable, + nativeSymbol: IndexIntoNativeSymbolTable +): StackAddressInfo { + // "self address" == "the address which a stack's self time is contributed to" + const selfAddressForAllStacks = []; + // "total addresses" == "the set of addresses whose total time this stack contributes to" + const totalAddressesForAllStacks = []; + + // This loop takes advantage of the fact that the stack table is topologically ordered: + // Prefix stacks are always visited before their descendants. + for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) { + const frame = stackTable.frame[stackIndex]; + const prefixStack = stackTable.prefix[stackIndex]; + const nativeSymbolOfThisStack = frameTable.nativeSymbol[frame]; + + let selfAddress: Address | null = null; + let totalAddresses: Set
| null = null; + + if (prefixStack === null) { + // This stack node is a root of the inverted tree. That means that this stack's + // frame's address is where the self time is assigned, for the entire subtree of + // the inverted stack tree at this root. + if (nativeSymbolOfThisStack === nativeSymbol) { + selfAddress = frameTable.address[frame]; + if (selfAddress !== -1) { + totalAddresses = new Set([selfAddress]); + } + } + } else { + // This stack node has a prefix, which, in inverted mode, means that *this node + // calls someone else, and that's where the time is spent*. The prefix is the callee. + // So this stack node contributes its time to its root node's address. + // We inherit the prefix's self address. + selfAddress = selfAddressForAllStacks[prefixStack]; + + // Add this stack's address to the totalAddresses set. + totalAddresses = totalAddressesForAllStacks[prefixStack]; + if (nativeSymbolOfThisStack === nativeSymbol) { + const thisStackAddress = frameTable.address[frame]; + if (thisStackAddress !== -1) { + if (totalAddresses === null) { + totalAddresses = new Set([thisStackAddress]); + } else if (!totalAddresses.has(thisStackAddress)) { + totalAddresses = new Set(totalAddresses); + totalAddresses.add(thisStackAddress); + } + } + } + } + + selfAddressForAllStacks.push(selfAddress); + totalAddressesForAllStacks.push(totalAddresses); + } + return { + selfAddress: selfAddressForAllStacks, + stackAddresses: totalAddressesForAllStacks, + }; +} + +/** + * Gathers the addresses which are hit by a given call node. + * This is different from `getStackAddressInfo`: `getStackAddressInfo` counts + * address hits anywhere in the stack, and this function only counts hits *in + * the given call node*. + * + * This is useful when opening the assembly view from a call node: We can + * directly jump to the place in the assembly where *this particular call node* + * spends its time. + * + * Returns a StackAddressInfo object for the given stackTable and for the library + * which contains the call node's func. + */ +export function getStackAddressInfoForCallNode( + stackTable: StackTable, + frameTable: FrameTable, + callNodeIndex: IndexIntoCallNodeTable, + callNodeInfo: CallNodeInfo, + nativeSymbol: IndexIntoNativeSymbolTable, + isInvertedTree: boolean +): StackAddressInfo { + return isInvertedTree + ? getStackAddressInfoForCallNodeInverted( + stackTable, + frameTable, + callNodeIndex, + callNodeInfo, + nativeSymbol + ) + : getStackAddressInfoForCallNodeNonInverted( + stackTable, + frameTable, + callNodeIndex, + callNodeInfo, + nativeSymbol + ); +} + +/** + * This function handles the non-inverted case of getStackAddressInfoForCallNode. + * + * Gathers the addresses which are hit by a given call node in a given native + * symbol. + * + * This is best explained with an example. We first start with a case that does + * not have any inlining, because this is already complicated enough. + * + * Let the call node be the node for the call path [A, B, C]. + * Let the native symbol be C. + * Let every frame have inlineDepth:0. + * Let there be a native symbol for every func, with the same name as the func. + * Let this be the stack tree: + * + * - stack 1, func A + * - stack 2, func B + * - stack 3, func C, address 0x30 + * - stack 4, func C, address 0x40 + * - stack 5, func B + * - stack 6, func C, address 0x60 + * - stack 7, func C, address 0x70 + * - stack 8, func D + * - stack 9, func E + * - stack 10, func F + * + * This maps to the following call tree: + * + * - call node 1, func A + * - call node 2, func B + * - call node 3, func C + * - call node 4, func D + * - call node 5, func E + * - call node 6, func F + * + * The call path [A, B, C] uniquely identifies call node 3. + * The following stacks all "collapse into" ("map to") call node 3: + * stack 3, 4, 6 and 7. + * Stack 8 maps to call node 4, which is a child of call node 3. + * Stacks 1, 2, 5, 9 and 10 are outside the call path [A, B, C]. + * + * In this function, we only compute "address hits" that are contributed to + * the given call node. + * Stacks 3, 4, 6 and 7 all contribute their time both as "self time" + * and as "total time" to call node 3, at the addresses 0x30, 0x40, 0x60, + * and 0x70, respectively. + * Stack 8 also hits call node 3 at address 0x70, but does not contribute to + * call node 3's "self time", it only contributes to its "total time". + * Stacks 1, 2, 5, 9 and 10 don't contribute to call node 3's self or total time. + * + * Now here's an example *with* inlining. + * + * Let the call node be the node for the call path [A, B, C]. + * Let the native symbol be B. + * Let this be the stack tree: + * + * - stack 1, func A, nativeSymbol A + * - stack 2, func B, nativeSymbol B, address 0x40 + * - stack 3, func C, nativeSymbol B, address 0x40, inlineDepth 1 + * - stack 4, func B, nativeSymbol B, address 0x45 + * - stack 5, func C, nativeSymbol B, address 0x45, inlineDepth 1 + * - stack 6, func D, nativeSymbol D + * - stack 7, func E, nativeSymbol E + * - stack 8, func A, nativeSymbol A, address 0x30 + * - stack 9, func B, nativeSymbol A, address 0x30, inlineDepth 1 + * - stack 10, func C, nativeSymbol A, address 0x30, inlineDepth 2 + * + * This maps to the following call tree: + * + * - call node 1, func A + * - call node 2, func B + * - call node 3, func C + * - call node 4, func D + * - call node 5, func E + * + * The funky part here is that call node 3 has frames from two different native + * symbols: Two from native symbol B, and one from native symbol A. That's + * because B is present both as its own native symbol (separate outer function) + * and as an inlined call from A. In other words, C has been inlined both into + * a standalone B and also into another copy of B which was inlined into A. + * + * This means that, if the user double clicks call node 3, there are two + * different symbols for which we may want to display the assembly code. And + * depending on whether the assembly for A or for B is displayed, we want to + * call this function for a different native symbol. + * + * In this example, we call this function for native symbol B. + * + * The call path [A, B, C] uniquely identifies call node 3. + * The following stacks all "collapse into" ("map to") call node 3: + * stack 3, 5 and 10. However, only stacks 3 and 5 belong to native symbol B; + * stack 10 belongs to native symbol A. + * Stack 6 maps to call node 4, which is a child of call node 3. + * Stacks 1, 2, 4, 7, 8 and 9 are outside the call path [A, B, C]. + * + * Stacks 3 and 5 both contribute their time both as "self time" and as "total + * time" to call node 3 and native symbol B, at the addresses 0x40 and 0x45, + * respectively. Stack 10 has the right call node but the wrong native symbol, + * so it contributes to neither self nor total time. + * Stack 6 also hits call node 3 at address 0x45, but does not contribute to + * call node 3's "self time", it only contributes to its "total time". + * Stacks 1, 2, 4, 7, 8 and 9 don't contribute to call node 3's self or total time. + * + * --- + * + * All stacks can contribute no more than one address in the given call node. + * This is different from the getStackAddressInfo function above, where each + * stack can hit many addreses in the same library, because all of the ancestor + * stacks are taken into account, rather than just one of them. Concretely, + * this means that in the returned StackAddressInfo, each stackAddresses[stack] + * set will only contain at most one element. + * + * The returned StackAddressInfo is computed as follows: + * selfAddress[stack]: + * For stacks that map to the given call node and whose nativeSymbol is the + * given native symbol, this is stack.frame.address. + * For all other stacks this is null. + * stackAddresses[stack]: + * For stacks that map to the given call node or one of its descendant + * call nodes, and whose nativeSymbol is the given native symbol, this is a + * set containing one element, which is ancestorStack.frame.address, where + * ancestorStack maps to the given call node. + * For all other stacks, this is null. + */ +export function getStackAddressInfoForCallNodeNonInverted( + stackTable: StackTable, + frameTable: FrameTable, + callNodeIndex: IndexIntoCallNodeTable, + { stackIndexToCallNodeIndex }: CallNodeInfo, + nativeSymbol: IndexIntoNativeSymbolTable +): StackAddressInfo { + // "self address" == "the address which a stack's self time is contributed to" + const callNodeSelfAddressForAllStacks = []; + // "total addresses" == "the set of addresses whose total time this stack contributes to" + // Either null or a single-element set. + const callNodeTotalAddressesForAllStacks = []; + + // This loop takes advantage of the fact that the stack table is topologically ordered: + // Prefix stacks are always visited before their descendants. + for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) { + let selfAddress: Address | null = null; + let totalAddresses: Set
| null = null; + const frame = stackTable.frame[stackIndex]; + + if ( + stackIndexToCallNodeIndex[stackIndex] === callNodeIndex && + frameTable.nativeSymbol[frame] === nativeSymbol + ) { + // This stack contributes to the call node's self time for the right + // native symbol. We needed to check both, because multiple stacks for the + // same call node can have different native symbols. + selfAddress = frameTable.address[frame]; + if (selfAddress !== -1) { + totalAddresses = new Set([selfAddress]); + } + } else { + // This stack does not map to the given call node or has the wrong native + // symbol. So this stack contributes no self time to the call node for the + // requested native symbol, and we leave selfAddress at null. + // As for totalTime, this stack contributes to the same address's totalTime + // as its parent stack: If it is a descendant of a stack X which maps to + // the given call node, then it contributes to stack X's address's totalTime, + // otherwise it contributes to no address's totalTime. + // In the example above, this is how stack 8 contributes to call node 3's + // totalTime. + const prefixStack = stackTable.prefix[stackIndex]; + totalAddresses = + prefixStack !== null + ? callNodeTotalAddressesForAllStacks[prefixStack] + : null; + } + + callNodeSelfAddressForAllStacks.push(selfAddress); + callNodeTotalAddressesForAllStacks.push(totalAddresses); + } + return { + selfAddress: callNodeSelfAddressForAllStacks, + stackAddresses: callNodeTotalAddressesForAllStacks, + }; +} + +/** + * This handles the inverted case of getStackAddressInfoForCallNode. + * + * The returned StackAddressInfo is computed as follows: + * selfAddress[stack]: + * For (inverted thread) root stack nodes that map to the given call node + * and whose stack.frame.nativeSymbol is the given library, this is stack.frame.address. + * For (inverted thread) root stack nodes whose frame is in a different library, + * or which don't map to the given call node, this is null. + * For (inverted thread) *non-root* stack nodes, this is the same as the selfAddress + * of the stack's prefix. This way, the selfAddress is always inherited from the + * subtree root. + * stackAddresses[stack]: + * For stacks that map to the given call node or one of its (inverted tree) + * descendant call nodes, this is a set containing one element, which is + * ancestorStack.frame.address, where ancestorStack maps to the given call + * node. + * For all other stacks, this is null. + */ +export function getStackAddressInfoForCallNodeInverted( + stackTable: StackTable, + frameTable: FrameTable, + callNodeIndex: IndexIntoCallNodeTable, + { stackIndexToCallNodeIndex }: CallNodeInfo, + nativeSymbol: IndexIntoNativeSymbolTable +): StackAddressInfo { + // "self address" == "the address which a stack's self time is contributed to" + const callNodeSelfAddressForAllStacks = []; + // "total addresses" == "the set of addresses whose total time this stack contributes to" + // Either null or a single-element set. + const callNodeTotalAddressesForAllStacks = []; + + // This loop takes advantage of the fact that the stack table is topologically ordered: + // Prefix stacks are always visited before their descendants. + for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) { + let selfAddress: Address | null = null; + let totalAddresses: Set
| null = null; + + const prefixStack = stackTable.prefix[stackIndex]; + if ( + stackIndexToCallNodeIndex[stackIndex] === callNodeIndex && + frameTable.nativeSymbol[stackTable.frame[stackIndex]] === nativeSymbol + ) { + // This stack contributes to the call node's self time for the right + // native symbol. We needed to check both, because multiple stacks for the + // same call node can have different native symbols. + const frame = stackTable.frame[stackIndex]; + const address = frameTable.address[frame]; + if (address !== -1) { + totalAddresses = new Set([address]); + if (prefixStack === null) { + // This is a root of the inverted tree, and it is the given + // call node. That means that we have a self address. + selfAddress = address; + } else { + // This is not a root stack node, so no self time is spent + // in the given call node for this stack node. + } + } + } else { + if (prefixStack === null) { + // This is a root of the inverted tree, but it doesn't map + // to the given call node. It doesn't contribute to the call node's + // self time or total time. + } else { + // This is not a root stack node. Samples that hit this stack node + // spend their time in the root node of our subtree. If that root + // maps to the given call node, we may have self time. + // Inherit both self and total time contribution from the parent stack. + selfAddress = callNodeSelfAddressForAllStacks[prefixStack]; + totalAddresses = callNodeTotalAddressesForAllStacks[prefixStack]; + } + } + + callNodeSelfAddressForAllStacks.push(selfAddress); + callNodeTotalAddressesForAllStacks.push(totalAddresses); + } + return { + selfAddress: callNodeSelfAddressForAllStacks, + stackAddresses: callNodeTotalAddressesForAllStacks, + }; +} + +// An AddressTimings instance without any hits. +export const emptyAddressTimings: AddressTimings = { + totalAddressHits: new Map(), + selfAddressHits: new Map(), +}; + +// Compute the AddressTimings for the supplied samples with the help of StackAddressInfo. +// This is fast and can be done whenever the preview selection changes. +// The slow part was the computation of the StackAddressInfo, which is already done. +export function getAddressTimings( + stackAddressInfo: StackAddressInfo | null, + samples: SamplesLikeTable +): AddressTimings { + if (stackAddressInfo === null) { + return emptyAddressTimings; + } + const { selfAddress, stackAddresses } = stackAddressInfo; + const totalAddressHits: Map = new Map(); + const selfAddressHits: Map = new Map(); + + // Iterate over all the samples, and aggregate the sample's weight into the + // addresses which are hit by the sample's stack. + // TODO: Maybe aggregate sample count per stack first, and then visit each stack only once? + for (let sampleIndex = 0; sampleIndex < samples.length; sampleIndex++) { + const stackIndex = samples.stack[sampleIndex]; + if (stackIndex === null) { + continue; + } + const weight = samples.weight ? samples.weight[sampleIndex] : 1; + const setOfHitAddresses = stackAddresses[stackIndex]; + if (setOfHitAddresses !== null) { + for (const address of setOfHitAddresses) { + const oldHitCount = totalAddressHits.get(address) ?? 0; + totalAddressHits.set(address, oldHitCount + weight); + } + } + const address = selfAddress[stackIndex]; + if (address !== null) { + const oldHitCount = selfAddressHits.get(address) ?? 0; + selfAddressHits.set(address, oldHitCount + weight); + } + } + return { totalAddressHits, selfAddressHits }; +} diff --git a/src/profile-logic/call-tree.js b/src/profile-logic/call-tree.js index 89fd02beb1..bdd31091dc 100644 --- a/src/profile-logic/call-tree.js +++ b/src/profile-logic/call-tree.js @@ -353,6 +353,8 @@ export class CallTree { iconSrc, icon, ariaLabel, + rawTotal: total, + rawSelf: self, }; this._displayDataByIndex.set(callNodeIndex, displayData); } diff --git a/src/profile-logic/data-structures.js b/src/profile-logic/data-structures.js index 8ac0b3dfe4..4ae4914e17 100644 --- a/src/profile-logic/data-structures.js +++ b/src/profile-logic/data-structures.js @@ -172,6 +172,7 @@ export function shallowCloneNativeSymbolTable( libIndex: nativeSymbols.libIndex.slice(), address: nativeSymbols.address.slice(), name: nativeSymbols.name.slice(), + functionSize: nativeSymbols.functionSize.slice(), length: nativeSymbols.length, }; } @@ -199,6 +200,7 @@ export function getEmptyNativeSymbolTable(): NativeSymbolTable { libIndex: [], address: [], name: [], + functionSize: [], length: 0, }; } diff --git a/src/profile-logic/import/chrome.js b/src/profile-logic/import/chrome.js index 22148e63d1..f9bcc6e735 100644 --- a/src/profile-logic/import/chrome.js +++ b/src/profile-logic/import/chrome.js @@ -424,13 +424,13 @@ type FunctionInfo = { function makeFunctionInfoFinder(categories) { const jsCat = categories.findIndex((c) => c.name === 'JavaScript'); const gcCat = categories.findIndex((c) => c.name === 'GC / CC'); - const domCat = categories.findIndex((c) => c.name === 'DOM'); + const nativeCat = categories.findIndex((c) => c.name === 'Native'); const otherCat = categories.findIndex((c) => c.name === 'Other'); const idleCat = categories.findIndex((c) => c.name === 'Idle'); if ( jsCat === -1 || gcCat === -1 || - domCat === -1 || + nativeCat === -1 || otherCat === -1 || idleCat === -1 ) { @@ -460,7 +460,7 @@ function makeFunctionInfoFinder(categories) { functionName !== '' && functionName !== '(unresolved function)' ) { - return { category: domCat, isJS: false, relevantForJS: true }; + return { category: nativeCat, isJS: false, relevantForJS: true }; } return { category: jsCat, isJS: true, relevantForJS: false }; } @@ -471,6 +471,14 @@ async function processTracingEvents( eventsByName: Map ): Promise { const profile = getEmptyProfile(); + profile.meta.categories = [ + { name: 'Other', color: 'grey', subcategories: ['Other'] }, + { name: 'Idle', color: 'transparent', subcategories: ['Other'] }, + { name: 'JavaScript', color: 'yellow', subcategories: ['Other'] }, + { name: 'GC / CC', color: 'orange', subcategories: ['Other'] }, + { name: 'Graphics', color: 'green', subcategories: ['Other'] }, + { name: 'Native', color: 'blue', subcategories: ['Other'] }, + ]; profile.meta.product = 'Chrome Trace'; profile.meta.importedFrom = 'Chrome Trace'; diff --git a/src/profile-logic/line-timings.js b/src/profile-logic/line-timings.js index ef9539f657..e412fbd838 100644 --- a/src/profile-logic/line-timings.js +++ b/src/profile-logic/line-timings.js @@ -289,7 +289,7 @@ export function getStackLineInfoForCallNode( * The following stacks all "collapse into" ("map to") call node 3: * stack 3, 4, 6 and 7. * Stack 8 maps to call node 4, which is a child of call node 3. - * All other stacks are outside the call path [A, B, C]. + * Stacks 1, 2, 5, 9 and 10 are outside the call path [A, B, C]. * * In this function, we only compute "line hits" that are contributed to * the given call node. @@ -298,7 +298,7 @@ export function getStackLineInfoForCallNode( * and 70, respectively. * Stack 8 also hits call node 3 at line 70, but does not contribute to * call node 3's "self time", it only contributes to its "total time". - * All other stacks don't contribute to call node 3's self or total time. + * Stacks 1, 2, 5, 9 and 10 don't contribute to call node 3's self or total time. * * All stacks can contribute no more than one line in the given call node. * This is different from the getStackLineInfo function above, where each diff --git a/src/profile-logic/marker-schema.js b/src/profile-logic/marker-schema.js index 3c28eaa4d0..9235ee5d3a 100644 --- a/src/profile-logic/marker-schema.js +++ b/src/profile-logic/marker-schema.js @@ -2,6 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // @flow +import * as React from 'react'; import { oneLine } from 'common-tags'; import { formatNumber, @@ -359,19 +360,64 @@ export function getLabelGetter( }; } +/** + * This function formats a string from a marker type and a value. + * If you wish to get markup instead, have a look at + * formatMarkupFromMarkerSchema below. + */ export function formatFromMarkerSchema( markerType: string, format: MarkerFormatType, value: any ): string { + if (value === undefined || value === null) { + console.warn( + `Formatting ${value} for ${markerType} with format ${JSON.stringify( + format + )}` + ); + return '(empty)'; + } + if (typeof format === 'object') { + switch (format.type) { + case 'table': { + const { columns } = format; + if (!(value instanceof Array)) { + throw new Error('Expected an array for table type'); + } + const hasHeader = columns.some((column) => column.label); + const rows = hasHeader + ? [columns.map((x) => x.label || '(empty)')] + : []; + const cellRows = value.map((row, i) => { + if (!(row instanceof Array)) { + throw new Error('Expected an array for table row'); + } + + if (row.length !== columns.length) { + throw new Error( + `Row ${i} length doesn't match column count (row: ${row.length}, cols: ${columns.length})` + ); + } + return row.map((cell, j) => { + const { format } = columns[j]; + return formatFromMarkerSchema(markerType, format || 'string', cell); + }); + }); + rows.push(...cellRows); + return rows.map((row) => `(${row.join(', ')})`).join(','); + } + default: + throw new Error( + `Unknown format type ${JSON.stringify((format.type: empty))}` + ); + } + } switch (format) { case 'url': case 'file-path': case 'string': // Make sure a non-empty string is returned here. - if (value === undefined || value === null) { - return '(empty)'; - } return String(value) || '(empty)'; case 'duration': case 'time': @@ -392,10 +438,129 @@ export function formatFromMarkerSchema( return formatNumber(value); case 'percentage': return formatPercent(value); + case 'list': + if (!(value instanceof Array)) { + throw new Error('Expected an array for list format'); + } + return value + .map((v) => formatFromMarkerSchema(markerType, 'string', v)) + .join(', '); default: - console.error( - `A marker schema of type "${markerType}" had an unknown format "${(format: empty)}"` + console.warn( + `A marker schema of type "${markerType}" had an unknown format ${JSON.stringify( + (format: empty) + )}` ); return value; } } + +// This regexp is used to test for URLs and remove their scheme for display. +const URL_SCHEME_REGEXP = /^http(s?):\/\//; + +/** + * This function may return structured markup for some types suchs as table, + * list, or urls. For other types this falls back to formatFromMarkerSchema + * above. + */ +export function formatMarkupFromMarkerSchema( + markerType: string, + format: MarkerFormatType, + value: any +): React.Element | string { + if (value === undefined || value === null) { + console.warn(`Formatting ${value} for ${JSON.stringify(markerType)}`); + return '(empty)'; + } + if (format !== 'url' && typeof format !== 'object' && format !== 'list') { + return formatFromMarkerSchema(markerType, format, value); + } + if (typeof format === 'object') { + switch (format.type) { + case 'table': { + const { columns } = format; + if (!(value instanceof Array)) { + throw new Error('Expected an array for table type'); + } + const hasHeader = columns.some((column) => column.label); + return ( + + {hasHeader ? ( + + + {columns.map((col, i) => ( + + ))} + + + ) : null} + + {value.map((row, i) => { + if (!(row instanceof Array)) { + throw new Error('Expected an array for table row'); + } + + if (row.length !== columns.length) { + throw new Error( + `Row ${i} length doesn't match column count (row: ${row.length}, cols: ${columns.length})` + ); + } + return ( + + {row.map((cell, i) => { + return ( + + ); + })} + + ); + })} + +
{col.label || ''}
+ {formatMarkupFromMarkerSchema( + markerType, + columns[i].type || 'string', + cell + )} +
+ ); + } + default: + throw new Error( + `Unknown format type ${JSON.stringify((format: empty))}` + ); + } + } + switch (format) { + case 'list': + if (!(value instanceof Array)) { + throw new Error('Expected an array for list format'); + } + return ( +
    + {value.map((entry, i) => ( +
  • + {formatFromMarkerSchema(markerType, 'string', value[i])} +
  • + ))} +
+ ); + case 'url': { + if (!URL_SCHEME_REGEXP.test(value)) { + return value; + } + return ( + + {value.replace(URL_SCHEME_REGEXP, '')} + + ); + } + default: + throw new Error(`Unknown format type ${JSON.stringify((format: empty))}`); + } +} diff --git a/src/profile-logic/merge-compare.js b/src/profile-logic/merge-compare.js index eeb0d0c720..b27f173638 100644 --- a/src/profile-logic/merge-compare.js +++ b/src/profile-logic/merge-compare.js @@ -561,6 +561,7 @@ function combineNativeSymbolTables( const nameIndex = nativeSymbols.name[i]; const newName = stringTable.getString(nameIndex); const address = nativeSymbols.address[i]; + const functionSize = nativeSymbols.functionSize[i]; // Duplicate search. const nativeSymbolKey = [newName, address].join('#'); @@ -577,6 +578,7 @@ function combineNativeSymbolTables( newNativeSymbols.libIndex.push(libIndex); newNativeSymbols.name.push(newStringTable.indexForString(newName)); newNativeSymbols.address.push(address); + newNativeSymbols.functionSize.push(functionSize); newNativeSymbols.length++; } diff --git a/src/profile-logic/mozilla-symbolication-api.js b/src/profile-logic/mozilla-symbolication-api.js index b9f64829b8..8ae096bfc1 100644 --- a/src/profile-logic/mozilla-symbolication-api.js +++ b/src/profile-logic/mozilla-symbolication-api.js @@ -61,6 +61,9 @@ type APIFrameInfoV5 = { // The hex offset between the requested address and the start of the function, // e.g. "0x3c". function_offset?: string, + // An optional size, in bytes, of the machine code of the outer function that + // this address belongs to, as a hex string, e.g. "0x270". + function_size?: string, // The path of the file that contains the function this frame was in, optional. // As of June 2021, this is only supported on the staging symbolication server // ("Eliot") but not on the implementation that's currently in production ("Tecken"). @@ -140,6 +143,22 @@ function _ensureIsAPIResultV5(result: MixedObject): APIResultV5 { if ('line' in frameInfo && typeof frameInfo.line !== 'number') { throw new Error('Expected frameInfo.line to be a number, if present'); } + if ( + 'function_offset' in frameInfo && + typeof frameInfo.function_offset !== 'string' + ) { + throw new Error( + 'Expected frameInfo.function_offset to be a string, if present' + ); + } + if ( + 'function_size' in frameInfo && + typeof frameInfo.function_size !== 'string' + ) { + throw new Error( + 'Expected frameInfo.function_size to be a string, if present' + ); + } if ('inlines' in frameInfo) { const inlines = frameInfo.inlines; if (!Array.isArray(inlines)) { @@ -206,12 +225,18 @@ function getV5ResultForLibRequest( }); } + let functionSize; + if (info.function_size !== undefined) { + functionSize = parseInt(info.function_size.substr(2), 16); + } + addressResult = { name, symbolAddress: address - functionOffset, file: info.file, line: info.line, inlines, + functionSize, }; } else { // This can happen if the address is between functions, or before the first diff --git a/src/profile-logic/processed-profile-versioning.js b/src/profile-logic/processed-profile-versioning.js index 594785604f..3e115d7491 100644 --- a/src/profile-logic/processed-profile-versioning.js +++ b/src/profile-logic/processed-profile-versioning.js @@ -2130,5 +2130,13 @@ const _upgraders = { } profile.libs = libs; }, + [42]: (profile) => { + // The nativeSymbols table now has a new column: functionSize. + // Its values can be null. + for (const thread of profile.threads) { + const { nativeSymbols } = thread; + nativeSymbols.functionSize = Array(nativeSymbols.length).fill(null); + } + }, }; /* eslint-enable no-useless-computed-key */ diff --git a/src/profile-logic/symbol-store.js b/src/profile-logic/symbol-store.js index 3607baeb1c..f7320fb29a 100644 --- a/src/profile-logic/symbol-store.js +++ b/src/profile-logic/symbol-store.js @@ -49,6 +49,9 @@ export type AddressResult = {| // addressResult.name calls addressResult.inlines[inlines.length - 1].function, which // calls addressResult.inlines[inlines.length - 2].function etc. inlines?: Array, + // An optional size, in bytes, of the machine code of the outer function that + // this address belongs to. + functionSize?: number, |}; export type AddressInlineFrame = {| @@ -106,6 +109,7 @@ export function readSymbolsFromSymbolTable( const results = new Map(); let currentSymbolIndex = undefined; let currentSymbol = ''; + let currentSymbolFunctionSize = undefined; for (let i = 0; i < addressArray.length; i++) { const address = addressArray[i]; @@ -131,11 +135,16 @@ export function readSymbolsFromSymbolTable( // C++ or rust symbols in the symbol table may have mangled names. // Demangle them here. currentSymbol = demangleCallback(decoder.decode(subarray)); + currentSymbolFunctionSize = + symbolIndex < symbolTableAddrs.length - 1 + ? symbolTableAddrs[symbolIndex + 1] - symbolTableAddrs[symbolIndex] + : undefined; currentSymbolIndex = symbolIndex; } results.set(address, { symbolAddress: symbolTableAddrs[symbolIndex], name: currentSymbol, + functionSize: currentSymbolFunctionSize, }); } else { results.set(address, { diff --git a/src/profile-logic/symbolication.js b/src/profile-logic/symbolication.js index ede47499f6..49583bd44a 100644 --- a/src/profile-logic/symbolication.js +++ b/src/profile-logic/symbolication.js @@ -546,7 +546,7 @@ export function applySymbolicationStep( allNativeSymbolsForThisLib ); const frameToSymbolAddressMap: Map = new Map(); - const symbolAddressToNameMap: Map = new Map(); + const symbolAddressToInfoMap: Map = new Map(); const symbolAddressToCanonicalSymbolIndexMap: Map< Address, IndexIntoNativeSymbolTable @@ -607,7 +607,7 @@ export function applySymbolicationStep( // offset. const symbolAddress = addressResult.symbolAddress; frameToSymbolAddressMap.set(frameIndex, symbolAddress); - symbolAddressToNameMap.set(symbolAddress, addressResult.name); + symbolAddressToInfoMap.set(symbolAddress, addressResult); if (oldFrameSymbol !== null) { // Opportunistically match up symbolAddress with oldFrameSymbol. @@ -653,8 +653,8 @@ export function applySymbolicationStep( // and give the canonical symbol the right address and symbol. const availableNativeSymbolIterator = availableNativeSymbols.values(); const nativeSymbols = shallowCloneNativeSymbolTable(oldNativeSymbols); - for (const [symbolAddress, symbolName] of symbolAddressToNameMap) { - const symbolStringIndex = stringTable.indexForString(symbolName); + for (const [symbolAddress, addressResult] of symbolAddressToInfoMap) { + const symbolStringIndex = stringTable.indexForString(addressResult.name); let symbolIndex = symbolAddressToCanonicalSymbolIndexMap.get(symbolAddress); if (symbolIndex === undefined) { // Repurpose a symbol from availableNativeSymbols as the canonical symbol for this @@ -672,6 +672,8 @@ export function applySymbolicationStep( // Update the symbol properties. nativeSymbols.address[symbolIndex] = symbolAddress; nativeSymbols.name[symbolIndex] = symbolStringIndex; + nativeSymbols.functionSize[symbolIndex] = + addressResult.functionSize ?? null; } // Now we have a canonical symbol for every symbolAddress. diff --git a/src/reducers/app.js b/src/reducers/app.js index 305f11e31a..5b1bc8a1ab 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.js @@ -327,6 +327,33 @@ const browserConnectionStatus: Reducer = ( } }; +/** + * Signals which categories are opened by default in the sidebar per type + */ +const sidebarOpenCategories: Reducer>> = ( + openCats: Map> = new Map(), + action +) => { + switch (action.type) { + case 'TOGGLE_SIDEBAR_OPEN_CATEGORY': { + const newOpenCats = new Map(openCats); + let openCatSet = newOpenCats.get(action.kind); + if (openCatSet === undefined) { + openCatSet = new Set(); + } + if (openCatSet.has(action.category)) { + openCatSet.delete(action.category); + } else { + openCatSet.add(action.category); + } + newOpenCats.set(action.kind, openCatSet); + return newOpenCats; + } + default: + return openCats; + } +}; + const appStateReducer: Reducer = combineReducers({ view, urlSetupPhase, @@ -341,6 +368,7 @@ const appStateReducer: Reducer = combineReducers({ experimental, currentProfileUploadedInformation, browserConnectionStatus, + sidebarOpenCategories, }); export default appStateReducer; diff --git a/src/selectors/app.js b/src/selectors/app.js index 823d244b99..d15d283904 100644 --- a/src/selectors/app.js +++ b/src/selectors/app.js @@ -356,3 +356,10 @@ export const getTimelineMarginLeft: Selector = createSelector( } } ); + +/** + * Returns the indexes of categories that are opened in the sidebar, + * for every category + */ +export const getSidebarOpenCategories: Selector>> = + createSelector(getApp, (app) => app.sidebarOpenCategories); diff --git a/src/test/components/CallTreeSidebar.test.js b/src/test/components/CallTreeSidebar.test.js index 1cbe6b1563..ce0c34341d 100644 --- a/src/test/components/CallTreeSidebar.test.js +++ b/src/test/components/CallTreeSidebar.test.js @@ -85,8 +85,17 @@ describe('CallTreeSidebar', function () { ); + + const rerenderContainer = () => + renderResult.rerender( + + + + ); + return { ...renderResult, + rerenderContainer, ...store, funcNamesDict: funcNamesDictPerThread[0], selectNode, @@ -177,17 +186,32 @@ describe('CallTreeSidebar', function () { selectNode, container, queryByText, - getByText, + getAllByText, funcNamesDict: { A, B, C }, + rerenderContainer, } = setup(getProfileWithSubCategories()); selectNode([A, B, C]); expect(queryByText('FakeSubCategoryC')).not.toBeInTheDocument(); + expect(container.firstChild).toMatchSnapshot(); + + const layoutCategory = getAllByText('Layout')[0]; - const layoutCategory = getByText('Layout'); fireFullClick(layoutCategory); + rerenderContainer(); - expect(getByText('FakeSubCategoryC')).toBeInTheDocument(); + expect(getAllByText('FakeSubCategoryC')[0]).toBeInTheDocument(); + // only the 'Layout' category for the total running samples is expanded, + // not the other one too + expect(getAllByText('FakeSubCategoryC').length).toBe(1); expect(container.firstChild).toMatchSnapshot(); + + const layoutCategory2 = getAllByText('Layout')[1]; + + fireFullClick(layoutCategory2); + rerenderContainer(); + + expect(getAllByText('FakeSubCategoryC')[0]).toBeInTheDocument(); + expect(getAllByText('FakeSubCategoryC')[1]).toBeInTheDocument(); }); }); diff --git a/src/test/components/MarkerTable.test.js b/src/test/components/MarkerTable.test.js index 58f6706602..efe8ae7b29 100644 --- a/src/test/components/MarkerTable.test.js +++ b/src/test/components/MarkerTable.test.js @@ -103,6 +103,17 @@ describe('MarkerTable', function () { expect(scrolledRows()).toHaveLength(2); }); + it('sorts when the start header and duration header is clicked', () => { + const { container, getByText } = setup(); + + fireFullClick(getByText('Start')); + expect(container).toMatchSnapshot(); + fireFullClick(getByText('Start')); + expect(container).toMatchSnapshot(); + fireFullClick(getByText('Duration')); + expect(container).toMatchSnapshot(); + }); + it('selects a row when left clicking', () => { const { getByText, getRowElement } = setup(); diff --git a/src/test/components/ProfileCallTreeView.test.js b/src/test/components/ProfileCallTreeView.test.js index 9e9ed6cf3a..7637d2dd45 100644 --- a/src/test/components/ProfileCallTreeView.test.js +++ b/src/test/components/ProfileCallTreeView.test.js @@ -292,6 +292,17 @@ describe('calltree/ProfileCallTreeView', function () { const { container } = setup(profile); expect(container.querySelector('.treeViewRow.isSelected')).toBeFalsy(); }); + + it('sorts when the total column header is clicked', () => { + const { container, getByText } = setup(); + + expect(container.firstChild).toMatchSnapshot(); + + fireFullClick(getByText('Total (samples)')); + expect(container.firstChild).toMatchSnapshot(); + fireFullClick(getByText('Total (samples)')); + expect(container.firstChild).toMatchSnapshot(); + }); }); describe('calltree/ProfileCallTreeView EmptyReasons', function () { diff --git a/src/test/components/TrackPower.test.js b/src/test/components/TrackPower.test.js index 1278aa4e22..afa609f3b5 100644 --- a/src/test/components/TrackPower.test.js +++ b/src/test/components/TrackPower.test.js @@ -11,6 +11,8 @@ import { Provider } from 'react-redux'; import { fireEvent } from '@testing-library/react'; import { render, screen } from 'firefox-profiler/test/fixtures/testing-library'; + +import { updatePreviewSelection } from 'firefox-profiler/actions/profile-view'; import { TrackPower } from '../../components/timeline/TrackPower'; import { ensureExists } from '../../utils/flow'; @@ -64,8 +66,8 @@ describe('TrackPower', function () { threadIndex, { time: thread.samples.time.slice(), - // Power usage numbers. - count: [100, 400, 500, 1000, 200, 500, 300, 100], + // Power usage numbers. They are pWh so they are pretty big. + count: [10000, 40000, 50000, 100000, 2000000, 5000000, 30000, 10000], length: SAMPLE_COUNT, }, 'SystemPower', @@ -148,6 +150,11 @@ describe('TrackPower', function () { const { moveMouseAtCounter, getTooltipContents } = setup(); // We are hovering exactly between 4th and 5th counter. That's why it should // show the 5th counter. + // Here are the values we'll get in the tooltip: + // 5th counter value is 5000000 pWh, that is 5 µWh. Over 1ms, this means + // 18W (5 * 1000 * 3600 / 1000^2). + // Over the full range, we get 7.240 µWh, therefore we'll see in the tooltip + // 0.007 mWh. moveMouseAtCounter(4, 0.5); expect(getTooltipContents()).toMatchSnapshot(); }); @@ -175,10 +182,29 @@ describe('TrackPower', function () { }); it('shows a tooltip with a Power: line and a power unit', function () { - const { moveMouseAtCounter } = setup(); + const { dispatch, moveMouseAtCounter } = setup(); + dispatch( + updatePreviewSelection({ + hasSelection: true, + isModifying: false, + selectionStart: 5, + selectionEnd: 6, + }) + ); moveMouseAtCounter(3, 0); - // 1000pWh spent over 1ms is 3.6mW + // 100000pWh spent over 1ms is 360mW // Note: Fluent adds isolation characters \u2068 and \u2069 around variables. - expect(screen.getByText(/Power:/)).toHaveTextContent('3.6\u2069 mW'); + expect(screen.getByText(/Power:/).nextSibling).toHaveTextContent( + '360\u2069 mW' + ); + // Over the full range, we get 7.240 µWh, therefore we'll see in the tooltip + // 7.2 µWh. + expect(screen.getByText(/visible range:/).nextSibling).toHaveTextContent( + '7.2\u2069 µWh' + ); + // Over the preview selection, we get 5 µWh which shows up as 5.0 µWh. + expect( + screen.getByText(/current selection:/).nextSibling + ).toHaveTextContent('5.0\u2069 µWh'); }); }); diff --git a/src/test/components/__snapshots__/CallTreeSidebar.test.js.snap b/src/test/components/__snapshots__/CallTreeSidebar.test.js.snap index 801964cf5b..8007dd0daa 100644 --- a/src/test/components/__snapshots__/CallTreeSidebar.test.js.snap +++ b/src/test/components/__snapshots__/CallTreeSidebar.test.js.snap @@ -13,6 +13,251 @@ exports[`CallTreeSidebar can expand subcategories 1`] = ` + + + +