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

Coverage Converter update #25

Merged
merged 2 commits into from
May 19, 2024

Conversation

maxwell-legrand
Copy link
Collaborator

This change was brought about by a necessity to optimize how the coverage for each file was generated. On my machine, running the tool for a large xcresult file would take a long time to complete (roughly 2-2.5 minutes) and would cause a significant draw of CPU as multiple instances of xccov were being run.

I've attempted to improve the performance by instead making one call to xccov and generating a JSON report for the whole xcresult file and then iterate over the keys in parallel. This brings the time for running the tool against the same file from 2 minutes to 4 seconds.

This is my first attempt at writing real Swift, so feel free to tear this apart with feedback! Appreciate all the amazing work that has gone into this project! Thanks in advance!

Copy link
Collaborator

@hisaac hisaac 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 thoughts for cleaning things up, but this is looking great to me. I'll approve, but let's wait for @a7ex's feedback as well. 💯

}

// Subrange information struct
struct Subrange: Codable {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since all we're doing here is decoding JSON, you could change this (and the ones below) to use Decodable instead of Codable.

Suggested change
struct Subrange: Codable {
struct Subrange: Decodable {

Comment on lines 70 to 79
// Custom encoding method to handle the top-level dictionary
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
for (key, value) in files {
guard let codingKey = CodingKeys(stringValue: key) else {
continue
}
try container.encode(value, forKey: codingKey)
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you switch to only using Decodable, you can then remove this custom encoding method.

Suggested change
// Custom encoding method to handle the top-level dictionary
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
for (key, value) in files {
guard let codingKey = CodingKeys(stringValue: key) else {
continue
}
try container.encode(value, forKey: codingKey)
}
}

Comment on lines 153 to 155
let lineNum = lineData.line
var covered = lineData.executionCount
if covered != nil {
Copy link
Collaborator

@hisaac hisaac May 14, 2024

Choose a reason for hiding this comment

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

Since we're not doing anything if covered == nil, could we switch this to a guard statement? It'd eliminate the need for force unwrapping covered below as well.

Suggested change
let lineNum = lineData.line
var covered = lineData.executionCount
if covered != nil {
let lineNum = lineData.line
guard var covered = lineData.executionCount else {
...

@a7ex
Copy link
Owner

a7ex commented May 17, 2024

That looks very good to me as well. A change I should have done in the first place. Please give me one more day to review. I am pretty loaded with work "at work" at the moment. Will come back to you on this tomorrow

@a7ex
Copy link
Owner

a7ex commented May 18, 2024

Hi @maxwell-legrand could you fix the tests on your branch, so the action runs green? I can not commit the fixed tests to your repo.
To fix the tests, just use the cobertura.xml file, which @MatthewTHFisher updated in his PR. I already merged the PR, so you could just use the file from this repos master branch: https://github.com/a7ex/xcresultparser/blob/main/Tests/XcresultparserTests/TestAssets/cobertura.xml

@a7ex a7ex merged commit 9797bbb into a7ex:main May 19, 2024
1 check failed
Copy link
Owner

@a7ex a7ex left a comment

Choose a reason for hiding this comment

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

Thanks again for the great work, I was postponing that work. which you now did for quite some weeks, since I learned, that xcccv tool now can give us the coverage with only one call.

arguments.append("--json")
arguments.append(resultFile.url.path)
let coverageData = try Shell.execute(program: "/usr/bin/xcrun", with: arguments)
let resultsString = String(decoding: coverageData, as: UTF8.self)
Copy link
Owner

Choose a reason for hiding this comment

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

No need to convert coverageData to String and then back to Data.
Instead we can use

guard let coverageJson = try? JSONDecoder().decode(FileCoverage.self, from: coverageData) else
...

right away. Right?
Skip line 122 to 127

let fileInfoSemaphore = DispatchSemaphore(value: 1)
var fileInfo: [FileInfo] = []

// since we need to invoke xccov for each file, it takes pretty much time
// so we invoke it in parallel on 8 threads, that speeds up things considerably
let queue = OperationQueue()
Copy link
Owner

Choose a reason for hiding this comment

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

Now that xcccv tool doesn't need to get called repeatedly we do not need to spread the for loop over different threads. In fact I testest and without spawning to threads it ran slightly faster.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hey there, sorry for the delay here, haven't been at my computer much this weekend. Is there a way to toggle this to be a CLI flag? I imagine that for a very large results file it might be faster to work serially on a machine with not a lot of CPUs but on a machine with 8+ cpus I image it might eek out better performance? I can try and work on a PR to add that functionality if you'd be alright with that

Copy link
Owner

Choose a reason for hiding this comment

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

I was just saving the CPU for other parallel tasks. I will try some larger xcresult bundles and measure the difference with the PR I just created and with your version. Let's see, it it is worth it to block more than one core for this. And let's take this discussion to the new PR. I invited you as collaborator and once you accept I can add you as reviewer to the PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sounds good to me, I'm not all too familiar with Swift's parallelism and my best frame of reference is Go's goroutines (greenthreads)

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