Skip to content

Commit 2d76b88

Browse files
authored
Streamline some code related to profile publishing (#5608)
[Production](https://profiler.firefox.com/public/283zgs51v5kc3yxtjkm5w2x249468x99ydds610/flame-graph/?globalTrackOrder=1w46705&hiddenGlobalTracks=5&hiddenLocalTracksByPid=5472-0wAiAkwBhBjwE6E8wEhEjwFiFkwHv~5511-0wyp~5607-0wxhxjwCj~5696-0wxj~5855-0wCbCdwDi~6253-0whkwy4&localTrackOrderByPid=5472-HnwHpHbBaBcBnBpAnEhEfEgBlC8FeDqBkEqD7E5CjCkghy6z7zdA1EaA2A3E3EvF0FlF1FnF2FoG3G4FpG5GbGcG7GdGfGgGeGhwGoGuGpwGtGvH2H0H1H3H4HhwHmAuEuFawFcH7HvCtAgDcAoAsAtC7ClCsDsHrHsE0CuBgAbwAeAvB0B2wB4B6wB9BbBdBoBqwC2ErEtAiE9ElEmEoEjHfHgA7wAaF3F4jkA5A6DrDtDuAjAkF5wF9FvwG2AlAqB1G8EeHdC3FdC5DeDfEcE1E7ArBjEsHqHcAfCqCpCiDpC9DgCawChDhwDoBhAhilwy5y7wz6z8wzczewA0HaE2C4FkBeG9HtEdA4BfDdHeEpBmE6FfwFiFqwFtGaEkH9FmG6H8EbEnD8wDaC6CvwD6f0weBiFjFuApCrAmE8EiDvE4B5CmCnHuCoDbH5H6~6253-8mursewgy0wy2cdapx3x1n9txhwxm7x4hx5bx06oxewxgqx6wxd3w5021vliwkxnwxvy4x2y3~5855-D5DhCrC9Cagwimwox0wx8xbxcxexdxfwxhxjwxlxpwy1y4wybydwyuz0wz2z6wz8zcwzkzqwAnApwBoC6wC8CuD2CvD0DbwDdCqqCbD9Day2y3ycD1krx9xaxiz3wz5znwzpDfD7DiD6zlzmAoC2BpwC1yvjlpD4DeCsD8swvxmwxoz9wzbD3dwf0w4b5wacCtDgCpC5C3C4CdCgwCeChCjwClCiCmwCoCc&thread=Kv&v=11) | [Main branch](https://main--perf-html.netlify.app/public/283zgs51v5kc3yxtjkm5w2x249468x99ydds610/flame-graph/?globalTrackOrder=1w46705&hiddenGlobalTracks=5&hiddenLocalTracksByPid=5472-0wAiAkwBhBjwE6E8wEhEjwFiFkwHv~5511-0wyp~5607-0wxhxjwCj~5696-0wxj~5855-0wCbCdwDi~6253-0whkwy4&localTrackOrderByPid=5472-HnwHpHbBaBcBnBpAnEhEfEgBlC8FeDqBkEqD7E5CjCkghy6z7zdA1EaA2A3E3EvF0FlF1FnF2FoG3G4FpG5GbGcG7GdGfGgGeGhwGoGuGpwGtGvH2H0H1H3H4HhwHmAuEuFawFcH7HvCtAgDcAoAsAtC7ClCsDsHrHsE0CuBgAbwAeAvB0B2wB4B6wB9BbBdBoBqwC2ErEtAiE9ElEmEoEjHfHgA7wAaF3F4jkA5A6DrDtDuAjAkF5wF9FvwG2AlAqB1G8EeHdC3FdC5DeDfEcE1E7ArBjEsHqHcAfCqCpCiDpC9DgCawChDhwDoBhAhilwy5y7wz6z8wzczewA0HaE2C4FkBeG9HtEdA4BfDdHeEpBmE6FfwFiFqwFtGaEkH9FmG6H8EbEnD8wDaC6CvwD6f0weBiFjFuApCrAmE8EiDvE4B5CmCnHuCoDbH5H6~6253-8mursewgy0wy2cdapx3x1n9txhwxm7x4hx5bx06oxewxgqx6wxd3w5021vliwkxnwxvy4x2y3~5855-D5DhCrC9Cagwimwox0wx8xbxcxexdxfwxhxjwxlxpwy1y4wybydwyuz0wz2z6wz8zcwzkzqwAnApwBoC6wC8CuD2CvD0DbwDdCqqCbD9Day2y3ycD1krx9xaxiz3wz5znwzpDfD7DiD6zlzmAoC2BpwC1yvjlpD4DeCsD8swvxmwxoz9wzbD3dwf0w4b5wacCtDgCpC5C3C4CdCgwCeChCjwClCiCmwCoCc&thread=Kv&v=11) | [Deploy preview](https://deploy-preview-5608--perf-html.netlify.app/public/283zgs51v5kc3yxtjkm5w2x249468x99ydds610/flame-graph/?globalTrackOrder=1w46705&hiddenGlobalTracks=5&hiddenLocalTracksByPid=5472-0wAiAkwBhBjwE6E8wEhEjwFiFkwHv~5511-0wyp~5607-0wxhxjwCj~5696-0wxj~5855-0wCbCdwDi~6253-0whkwy4&localTrackOrderByPid=5472-HnwHpHbBaBcBnBpAnEhEfEgBlC8FeDqBkEqD7E5CjCkghy6z7zdA1EaA2A3E3EvF0FlF1FnF2FoG3G4FpG5GbGcG7GdGfGgGeGhwGoGuGpwGtGvH2H0H1H3H4HhwHmAuEuFawFcH7HvCtAgDcAoAsAtC7ClCsDsHrHsE0CuBgAbwAeAvB0B2wB4B6wB9BbBdBoBqwC2ErEtAiE9ElEmEoEjHfHgA7wAaF3F4jkA5A6DrDtDuAjAkF5wF9FvwG2AlAqB1G8EeHdC3FdC5DeDfEcE1E7ArBjEsHqHcAfCqCpCiDpC9DgCawChDhwDoBhAhilwy5y7wz6z8wzczewA0HaE2C4FkBeG9HtEdA4BfDdHeEpBmE6FfwFiFqwFtGaEkH9FmG6H8EbEnD8wDaC6CvwD6f0weBiFjFuApCrAmE8EiDvE4B5CmCnHuCoDbH5H6~6253-8mursewgy0wy2cdapx3x1n9txhwxm7x4hx5bx06oxewxgqx6wxd3w5021vliwkxnwxvy4x2y3~5855-D5DhCrC9Cagwimwox0wx8xbxcxexdxfwxhxjwxlxpwy1y4wybydwyuz0wz2z6wz8zcwzkzqwAnApwBoC6wC8CuD2CvD0DbwDdCqqCbD9Day2y3ycD1krx9xaxiz3wz5znwzpDfD7DiD6zlzmAoC2BpwC1yvjlpD4DeCsD8swvxmwxoz9wzbD3dwf0w4b5wacCtDgCpC5C3C4CdCgwCeChCjwClCiCmwCoCc&thread=Kv&v=11) I was looking at this code when I was doing the worker compression stuff and noticed some opportunities for improvement. Nothing urgent here, just some cleanup. The "Simplify profile publishing flow" commit fixes one bug: If you trigger a new compression while an existing compression is already going on, and the new compression finishes before the old compression, then the old compression's completion will no longer overwrite the size in the download button with an outdated value.
2 parents 8fabff3 + 2ac0748 commit 2d76b88

14 files changed

Lines changed: 432 additions & 373 deletions

File tree

src/actions/publish.ts

Lines changed: 139 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import type { SanitizeProfileResult } from 'firefox-profiler/profile-logic/sanit
99
import {
1010
getUploadGeneration,
1111
getSanitizedProfile,
12-
getSanitizedProfileData,
1312
getRemoveProfileInformation,
1413
getPrePublishedState,
14+
getSanitizedProfileEncodingState,
1515
} from 'firefox-profiler/selectors/publish';
1616
import {
1717
getDataSource,
@@ -36,14 +36,50 @@ import type {
3636
StartEndRange,
3737
ThreadIndex,
3838
State,
39+
Profile,
3940
} from 'firefox-profiler/types';
41+
import { compress } from 'firefox-profiler/utils/gz';
42+
import { serializeProfile } from 'firefox-profiler/profile-logic/process-profile';
4043

41-
export function toggleCheckedSharingOptions(
42-
slug: keyof CheckedSharingOptions
44+
export function updateSharingOption(
45+
slug: keyof CheckedSharingOptions,
46+
value: boolean
4347
): Action {
4448
return {
45-
type: 'TOGGLE_CHECKED_SHARING_OPTION',
49+
type: 'UPDATE_SHARING_OPTION',
4650
slug,
51+
value,
52+
};
53+
}
54+
55+
export function sanitizedProfileEncodingStarted(
56+
sanitizedProfile: Profile
57+
): Action {
58+
return {
59+
type: 'SANITIZED_PROFILE_ENCODING_STARTED',
60+
sanitizedProfile,
61+
};
62+
}
63+
64+
export function sanitizedProfileEncodingCompleted(
65+
sanitizedProfile: Profile,
66+
profileData: Blob
67+
): Action {
68+
return {
69+
type: 'SANITIZED_PROFILE_ENCODING_COMPLETED',
70+
sanitizedProfile,
71+
profileData,
72+
};
73+
}
74+
75+
export function sanitizedProfileEncodingFailed(
76+
sanitizedProfile: Profile,
77+
error: Error
78+
): Action {
79+
return {
80+
type: 'SANITIZED_PROFILE_ENCODING_FAILED',
81+
sanitizedProfile,
82+
error,
4783
};
4884
}
4985

@@ -196,6 +232,90 @@ async function persistJustUploadedProfileInformationToDb(
196232
}
197233
}
198234

235+
export type ProfileEncodingResult =
236+
| {
237+
type: 'SUCCESS';
238+
profileData: Blob;
239+
}
240+
| { type: 'ERROR'; error: Error };
241+
242+
function unwrapEncodedProfile(encodingResult: ProfileEncodingResult): Blob {
243+
if (encodingResult.type === 'ERROR') {
244+
throw encodingResult.error;
245+
}
246+
return encodingResult.profileData;
247+
}
248+
249+
export type InflightProfileEncoding = {
250+
sanitizedProfile: Profile;
251+
encodingPromise: Promise<ProfileEncodingResult>;
252+
};
253+
254+
/**
255+
* Kick off "encoding" of the sanitized profile. Specifically this means:
256+
* - Compute the sanitized profile
257+
* - Serialize the profile to a buffer
258+
* - Kick off the asynchronous compression of the buffer
259+
*
260+
* The asynchronous compression can take a few seconds, so we want to kick
261+
* it off immediately when the profile publishing panel is opened. We also
262+
* want to be able to make use of the current in-flight compression if the
263+
* user clicks the upload button before compression is done. This is why
264+
* we return an `InflightProfileEncoding` object from this action; it contains
265+
* a promise which lets other parts of the publishing pipeline wait on the
266+
* compressed results.
267+
*
268+
* This thunk action is synchronous.
269+
*/
270+
export function encodeSanitizedProfile(
271+
previousInflightEncoding?: InflightProfileEncoding
272+
): ThunkAction<InflightProfileEncoding> {
273+
return (dispatch, getState): InflightProfileEncoding => {
274+
const state = getState();
275+
const sanitizedProfile = getSanitizedProfile(state).profile;
276+
277+
if (previousInflightEncoding?.sanitizedProfile === sanitizedProfile) {
278+
// No need to kick of another compression. The current encoding may still
279+
// be in-flight, and returning the original promise allows the caller to
280+
// await it.
281+
return previousInflightEncoding;
282+
}
283+
284+
const encodingState = getSanitizedProfileEncodingState(state);
285+
if (
286+
encodingState.phase === 'DONE' &&
287+
encodingState.sanitizedProfile === sanitizedProfile
288+
) {
289+
// We already have an encoded version of this profile in our state! Use it.
290+
return {
291+
sanitizedProfile,
292+
encodingPromise: Promise.resolve({
293+
type: 'SUCCESS',
294+
profileData: encodingState.profileData,
295+
}),
296+
};
297+
}
298+
299+
// Kick off a new encoding for this profile. Don't await the promise,
300+
// just return it as part of the InflightProfileEncoding.
301+
const encodingPromise: Promise<ProfileEncodingResult> = (async function () {
302+
try {
303+
dispatch(sanitizedProfileEncodingStarted(sanitizedProfile));
304+
const gzipData = await compress(serializeProfile(sanitizedProfile));
305+
const blob = new Blob([gzipData], { type: 'application/octet-binary' });
306+
dispatch(sanitizedProfileEncodingCompleted(sanitizedProfile, blob));
307+
return { type: 'SUCCESS', profileData: blob };
308+
} catch (error) {
309+
dispatch(sanitizedProfileEncodingFailed(sanitizedProfile, error));
310+
console.error('Error while compressing the profile data', error);
311+
return { type: 'ERROR', error };
312+
}
313+
})();
314+
315+
return { sanitizedProfile, encodingPromise };
316+
};
317+
}
318+
199319
/**
200320
* This function starts the profile sharing process. Takes an optional argument that
201321
* indicates if the share attempt is being made for the second time. We have two share
@@ -208,7 +328,9 @@ async function persistJustUploadedProfileInformationToDb(
208328
* The return value is used for tests to determine if the request went all the way
209329
* through (true) or was quit early due to the generation value being invalidated (false).
210330
*/
211-
export function attemptToPublish(): ThunkAction<Promise<boolean>> {
331+
export function attemptToPublish(
332+
previousInflightEncoding?: InflightProfileEncoding
333+
): ThunkAction<Promise<boolean>> {
212334
return async (dispatch, getState) => {
213335
try {
214336
sendAnalytics({
@@ -242,21 +364,29 @@ export function attemptToPublish(): ThunkAction<Promise<boolean>> {
242364
dispatch(uploadCompressionStarted(abortfunction));
243365

244366
const sanitizedInformation = getSanitizedProfile(prePublishedState);
245-
const gzipData = await getSanitizedProfileData(prePublishedState);
367+
const profileEncoding = dispatch(
368+
encodeSanitizedProfile(previousInflightEncoding)
369+
);
370+
const encodingResult = await profileEncoding.encodingPromise;
246371

247372
// The previous line was async, check to make sure that this request is still valid.
248373
// The upload could have been aborted while we were compressing the data.
249374
if (uploadGeneration !== getUploadGeneration(getState())) {
250375
return false;
251376
}
252377

378+
const encodedProfile = unwrapEncodedProfile(encodingResult);
379+
253380
dispatch(uploadStarted());
254381

255382
// Upload the profile, and notify it with the amount of data that has been
256383
// uploaded.
257-
const hashOrToken = await startUpload(gzipData, (uploadProgress) => {
258-
dispatch(updateUploadProgress(uploadProgress));
259-
});
384+
const hashOrToken = await startUpload(
385+
encodedProfile,
386+
(uploadProgress) => {
387+
dispatch(updateUploadProgress(uploadProgress));
388+
}
389+
);
260390

261391
const hash = extractProfileTokenFromJwt(hashOrToken);
262392

src/components/app/MenuButtons/Publish.css

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,18 @@
3030
background-image: url(../../../../res/img/svg/error.svg);
3131
}
3232

33-
.menuButtonsPublishPanel {
33+
.publishPanelPanel {
3434
--width: 510px;
3535
}
3636

37-
.menuButtonsPublishContent {
37+
.publishPanelContent {
3838
position: relative;
3939

4040
/* This aligns all content, except the big icon. */
4141
padding-left: 70px;
4242
}
4343

44-
.menuButtonsPublishTitle {
44+
.publishPanelTitle {
4545
/* "60px" This is the value to put the background image at the right location.
4646
* This background image is 44x44, so this puts it 16px left of the text. */
4747
padding-left: 60px;
@@ -50,32 +50,32 @@
5050
line-height: 44px; /* This is the height of the background image */
5151
}
5252

53-
.menuButtonsPublishInfoDescription {
53+
.publishPanelInfoDescription {
5454
flex: 1;
5555
margin-bottom: 1em;
5656
line-height: 1.5;
5757
}
5858

59-
.menuButtonsPublishDataChoices {
59+
.publishPanelDataChoices {
6060
margin-left: 10px;
6161
}
6262

63-
.menuButtonsPublishDataChoicesLabel {
63+
.publishPanelDataChoicesLabel {
6464
display: flex;
6565
margin: 4px 0;
6666
}
6767

68-
.menuButtonsPublishDataChoicesIndicator {
68+
.publishPanelDataChoicesIndicator {
6969
margin-left: 8px;
7070
}
7171

72-
.menuButtonsPublishButtons {
72+
.publishPanelButtons {
7373
display: flex;
7474
justify-content: right;
7575
margin-top: 20px;
7676
}
7777

78-
.menuButtonsPublishButton {
78+
.publishPanelButton {
7979
display: inline-flex;
8080
min-width: 132px;
8181
box-sizing: content-box;
@@ -87,30 +87,30 @@
8787
text-decoration: none;
8888
}
8989

90-
.menuButtonsPublishButtonDisabled {
90+
.publishPanelButtonDisabled {
9191
opacity: 0.6;
9292
}
9393

94-
.menuButtonsPublishButtonDisabled:hover,
95-
.menuButtonsPublishButtonDisabled:active:hover {
94+
.publishPanelButtonDisabled:hover,
95+
.publishPanelButtonDisabled:active:hover {
9696
background-color: var(--grey-90-a10);
9797
}
9898

99-
.menuButtonsPublishButtonsSvg {
99+
.publishPanelButtonsSvg {
100100
position: relative;
101101
top: 4px;
102102
display: inline-block;
103103
margin-top: -6px;
104104
margin-right: 5px;
105105
}
106106

107-
.menuButtonsPublishButtonsSvgUpload {
107+
.publishPanelButtonsSvgUpload {
108108
width: 20px;
109109
height: 20px;
110110
background: url(../../../../res/img/svg/upload.svg) center center no-repeat;
111111
}
112112

113-
.menuButtonsPublishButtonsSvgDownload {
113+
.publishPanelButtonsSvgDownload {
114114
width: 20px;
115115
height: 20px;
116116
background: url(../../../../res/img/svg/download.svg) center center no-repeat;
@@ -122,28 +122,28 @@
122122
font-size: 11px;
123123
}
124124

125-
.menuButtonsPublishUpload {
125+
.publishPanelUpload {
126126
position: relative;
127127
padding: 10px 0;
128128
}
129129

130-
.menuButtonsPublishUploadTop {
130+
.publishPanelUploadTop {
131131
margin: 10px;
132132
}
133133

134-
.menuButtonsPublishUploadPercentage {
134+
.publishPanelUploadPercentage {
135135
margin: 10px 0;
136136
color: var(--blue-60);
137137
}
138138

139-
.menuButtonsPublishUploadBar {
139+
.publishPanelUploadBar {
140140
overflow: hidden;
141141
height: 5px;
142142
border-radius: 2px;
143143
background-color: var(--grey-40);
144144
}
145145

146-
.menuButtonsPublishUploadBarInner {
146+
.publishPanelUploadBarInner {
147147
position: absolute;
148148
top: 0;
149149
height: 3px;
@@ -173,13 +173,13 @@
173173
}
174174
}
175175

176-
.menuButtonsPublishError {
176+
.publishPanelError {
177177
margin: 10px 0;
178178
}
179179

180180
/* Do not animate the publish animation for stripes at the top loading bar. */
181181
@media (prefers-reduced-motion) {
182-
.menuButtonsPublishUploadBarInner {
182+
.publishPanelUploadBarInner {
183183
animation: none;
184184
}
185185
}

0 commit comments

Comments
 (0)