Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref: Convert useUploads to TS + performance improvements #3156

Merged
merged 3 commits into from
Sep 4, 2024

Conversation

ajay-sentry
Copy link
Contributor

Description

This PR aims to fix some of the performance issues that were being faced for users with a lot of carry forward flags when looking at a commit from the commits page. I'll detail on the PR itself where a lot of the optimization were coming from since there's quite a few changes, but the "main" meat and potatoes is honestly converting the object we're appending all the flags from an array to a set. This allows us to get reduce our quadratic time complexity to linear, ideally helping quite a bit (in the cases where users may have 1000+ CFF)

Outside of the optimizations, we did TS conversions of a few files, and renaming some of the variables coming from the hook to aid readability, as well as removing the lodash calls/imports since we're kinda using them for things where just doing the "thing" natively is going to give us a more performant and just as readable version of the code.

Some references that helped along the way:

Countby performance
benchmark for set vs. includes

I think I can look at this page on sentry to compare performance pre/post, but if anyone has a better idea for that would appreciate as well: https://codecov.sentry.io/performance/summary/vitals/?project=5514400&query=&statsPeriod=30d&transaction=%2F%3Aprovider%2F%3Aowner%2F%3Arepo%2Fcommit%2F%3Acommit

Related to codecov/engineering-team#1921 and codecov/engineering-team#1880

Screenshots

Link to Sample Entry

Legal Boilerplate

Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. In 2022 this entity acquired Codecov and as result Sentry is going to need some rights from me in order to utilize my contributions in this PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms.

@codecov-staging
Copy link

codecov-staging bot commented Sep 3, 2024

Bundle Report

Changes will increase total bundle size by 274.76kB ⬆️

Bundle name Size Change
gazebo-staging-array-push 5.98MB 274.76kB ⬆️

Copy link

codecov bot commented Sep 3, 2024

Bundle Report

Changes will increase total bundle size by 274.76kB ⬆️

Bundle name Size Change
gazebo-production-array-push 5.98MB 274.76kB ⬆️

@codecov-releaser
Copy link
Contributor

codecov-releaser commented Sep 3, 2024

✅ Deploy preview for gazebo ready!

Previews expire after 1 month automatically.

Storybook

Commit Created Cloud Enterprise
5410452 Tue, 03 Sep 2024 20:16:22 GMT Expired Expired
b1eecfe Wed, 04 Sep 2024 16:11:44 GMT Cloud Enterprise

@@ -7,11 +7,11 @@ import { useUploads } from './useUploads'

import YamlModal from '../YamlModal'

const NULL = 'null'
const NONE = 'none'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't use null as a constant value, just removing this code smell

} from 'services/commit'
import { UploadStateEnum, UploadTypeEnum } from 'shared/utils/commit'

interface ErrorObject {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all of this stuff at the top is just TS interface boilerplate stuff

}

function deleteDuplicateCFFUploads({ uploads }) {
const nonCFFFlags = []
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CarryForwardFlagsFlags -> CarryForwardFlags


import { UploadStateEnum, UploadTypeEnum } from 'shared/utils/commit'

function humanReadableOverview(state, count) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

count in this function is unused


function deleteDuplicateCFFUploads({ uploads }) {
const nonCFFFlags = []
const duplicateUploads = uploads && [...uploads]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this copy was unneeded

upload?.uploadType !== UploadTypeEnum.CARRIED_FORWARD &&
upload?.flags
) {
nonCFFFlags.push(...upload.flags)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

converted this to a set from an array so we can use .has() instead of .includes()


const {
uploadsOverview,
providerGroups: groupedUploads,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

groupedUploads is a better name than "sorted" uploads, because that's what we're doing!

}

const uploads = deleteDuplicateCFFUploads({ uploads: unfilteredUploads })
const hasNoUploads = !uploads || uploads.length === 0
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debated doing an early return for this case, but figured it's a minor optimization at best

errorProviderGroups: erroredUploads,
} = createUploadGroups({ uploads })

const uploadsProviderList = Object.keys(groupedUploads)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could also do this in the createUploadGroups function, but the O(k) where k = number of keys in groupedUploads is bounded by the number of providers, which is very small in the grand scheme

// We're looping in reverse as splicing reindexes the array, making us
// skip the index that's removed. Indices are preserved when you
// loop backwards.
for (let index = duplicateUploads.length - 1; index >= 0; index--) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we're going through all uploads AND all CFF for each upload with a "break" if we find one early. In the worst case this is a quadratic function n*m where n is the number of uploads and m is the number of CFF we have

splice is also an incredibly expensive operation because we are creating a new list each time we call that function.

if (state === UploadStateEnum.started) return 'started'
}

function deleteDuplicateCFFUploads({ uploads }: { uploads: Upload[] }) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the new implementation, we have a set() for all the nonCFFlags that gets populated on a first pass

Then we do a filter for each upload on a second pass, exiting early if any of the flags exist in our set (where the "some" call really shines, though its a micro optimization compared to the set vs. array)

}

const createUploadOverview = ({ uploads }) =>
Object.entries(countBy(uploads, (upload) => upload.state))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we're doing another full pass through all the uploads to generate the "counts" for each state

Then another pass to do the groupBy and filter

Then another pass to do another group by on providers

effectively 3 passes through the uploads to do a couple minor operations. In the new implementation we combine it all into a single pass

)
}

const createUploadGroups = ({ uploads }: { uploads: Upload[] }) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we can see the single pass approach, which IMO is just as readable, if not more readable where we have separate "top level" variables for the state counts, provider groups, and errorProviderGroups that all get returned at the the end of the loop

Copy link

codecov-public-qa bot commented Sep 3, 2024

Codecov Report

Attention: Patch coverage is 94.11765% with 3 lines in your changes missing coverage. Please review.

Project coverage is 98.24%. Comparing base (8e7daaa) to head (b1eecfe).
Report is 4 commits behind head on main.

✅ All tests successful. No failed tests found.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3156      +/-   ##
==========================================
- Coverage   98.29%   98.24%   -0.05%     
==========================================
  Files         924      932       +8     
  Lines       14357    14455      +98     
  Branches     3831     3944     +113     
==========================================
+ Hits        14112    14202      +90     
- Misses        240      248       +8     
  Partials        5        5              
Files Coverage Δ
...ailPage/CommitCoverage/UploadsCard/UploadsCard.jsx 100.00% <ø> (ø)
...ommitCoverage/UploadsCard/useUploads/useUploads.ts 100.00% <100.00%> (ø)
src/services/commit/useCommit.tsx 100.00% <100.00%> (ø)
src/shared/utils/extractUploads.ts 93.61% <93.61%> (ø)

... and 26 files with indirect coverage changes

Components Coverage Δ
Assets 53.48% <ø> (-0.57%) ⬇️
Layouts 98.87% <ø> (+0.06%) ⬆️
Pages 99.03% <100.00%> (+<0.01%) ⬆️
Services 99.48% <100.00%> (+0.03%) ⬆️
Shared 99.51% <93.61%> (-0.21%) ⬇️
UI 94.60% <ø> (+0.09%) ⬆️

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8e7daaa...b1eecfe. Read the comment docs.

@codecov-qa
Copy link

codecov-qa bot commented Sep 3, 2024

Codecov Report

Attention: Patch coverage is 94.11765% with 3 lines in your changes missing coverage. Please review.

Project coverage is 98.24%. Comparing base (8e7daaa) to head (b1eecfe).
Report is 5 commits behind head on main.

✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/shared/utils/extractUploads.ts 93.61% 3 Missing ⚠️

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3156      +/-   ##
==========================================
- Coverage   98.29%   98.24%   -0.05%     
==========================================
  Files         924      932       +8     
  Lines       14357    14455      +98     
  Branches     3917     3939      +22     
==========================================
+ Hits        14112    14202      +90     
- Misses        240      248       +8     
  Partials        5        5              
Files with missing lines Coverage Δ
...ailPage/CommitCoverage/UploadsCard/UploadsCard.jsx 100.00% <ø> (ø)
...ommitCoverage/UploadsCard/useUploads/useUploads.ts 100.00% <100.00%> (ø)
src/services/commit/useCommit.tsx 100.00% <100.00%> (ø)
src/shared/utils/extractUploads.ts 93.61% <93.61%> (ø)

... and 26 files with indirect coverage changes

Components Coverage Δ
Assets 53.48% <ø> (-0.57%) ⬇️
Layouts 98.87% <ø> (+0.06%) ⬆️
Pages 99.03% <100.00%> (+<0.01%) ⬆️
Services 99.48% <100.00%> (+0.03%) ⬆️
Shared 99.51% <93.61%> (-0.21%) ⬇️
UI 94.60% <ø> (+0.09%) ⬆️

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8e7daaa...b1eecfe. Read the comment docs.

@codecov-notifications
Copy link

codecov-notifications bot commented Sep 3, 2024

Codecov Report

Attention: Patch coverage is 94.11765% with 3 lines in your changes missing coverage. Please review.

✅ All tests successful. No failed tests found.

Files Patch % Lines
src/shared/utils/extractUploads.ts 93.61% 3 Missing ⚠️

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3156      +/-   ##
==========================================
- Coverage   98.29%   98.24%   -0.05%     
==========================================
  Files         924      932       +8     
  Lines       14357    14455      +98     
  Branches     3886     3857      -29     
==========================================
+ Hits        14112    14202      +90     
- Misses        240      248       +8     
  Partials        5        5              
Files Coverage Δ
...ailPage/CommitCoverage/UploadsCard/UploadsCard.jsx 100.00% <ø> (ø)
...ommitCoverage/UploadsCard/useUploads/useUploads.ts 100.00% <100.00%> (ø)
src/services/commit/useCommit.tsx 100.00% <100.00%> (ø)
src/shared/utils/extractUploads.ts 93.61% <93.61%> (ø)

... and 26 files with indirect coverage changes

Components Coverage Δ
Assets 53.48% <ø> (-0.57%) ⬇️
Layouts 98.87% <ø> (+0.06%) ⬆️
Pages 99.03% <100.00%> (+<0.01%) ⬆️
Services 99.48% <100.00%> (+0.03%) ⬆️
Shared 99.51% <93.61%> (-0.21%) ⬇️
UI 94.60% <ø> (+0.09%) ⬆️

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8e7daaa...b1eecfe. Read the comment docs.

Copy link

codecov bot commented Sep 3, 2024

Codecov Report

Attention: Patch coverage is 94.11765% with 3 lines in your changes missing coverage. Please review.

Project coverage is 98.24%. Comparing base (8e7daaa) to head (b1eecfe).
Report is 5 commits behind head on main.

✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/shared/utils/extractUploads.ts 93.61% 3 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@               Coverage Diff                @@
##               main      #3156        +/-   ##
================================================
- Coverage   98.29000   98.24000   -0.05000     
================================================
  Files           924        932         +8     
  Lines         14357      14455        +98     
  Branches       3917       3912         -5     
================================================
+ Hits          14112      14202        +90     
- Misses          240        248         +8     
  Partials          5          5                
Files with missing lines Coverage Δ
...ailPage/CommitCoverage/UploadsCard/UploadsCard.jsx 100.00% <ø> (ø)
...ommitCoverage/UploadsCard/useUploads/useUploads.ts 100.00% <100.00%> (ø)
src/services/commit/useCommit.tsx 100.00% <100.00%> (ø)
src/shared/utils/extractUploads.ts 93.61% <93.61%> (ø)

... and 26 files with indirect coverage changes

Components Coverage Δ
Assets 53.48% <ø> (-0.57%) ⬇️
Layouts 98.87% <ø> (+0.06%) ⬆️
Pages 99.03% <100.00%> (+<0.01%) ⬆️
Services 99.48% <100.00%> (+0.03%) ⬆️
Shared 99.51% <93.61%> (-0.21%) ⬇️
UI 94.60% <ø> (+0.09%) ⬆️

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8e7daaa...b1eecfe. Read the comment docs.

errorProviderGroups[upload.provider] = [upload]
} else {
// @ts-ignore this key will always exist if we hit the else; just TS being weird
errorProviderGroups[upload.provider].push(upload)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we use the non-null assertion operator ! here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

definitely can! Will update

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

if (upload.provider === null || upload.provider === undefined) {
upload.provider = 'none'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we define const NONE = 'none' here and import from UploadsCard?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, will make the update

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@calvin-codecov calvin-codecov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple small comments/suggestions. Nice loop optimizations!

@ajay-sentry ajay-sentry added this pull request to the merge queue Sep 4, 2024
Merged via the queue into main with commit 53027a1 Sep 4, 2024
50 of 62 checks passed
@ajay-sentry ajay-sentry deleted the Ajay/uploads-enhancements-initial branch September 4, 2024 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants