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

Configuration cache is unusable in large projects when running ./gradlew projectHealth #1098

Open
TimvdLippe opened this issue Jan 11, 2024 · 9 comments
Labels
blocked-by-gradle Blocked due to issue with Gradle itself performance

Comments

@TimvdLippe
Copy link

Build scan link
Internal scan unfortunately

Plugin version
Latest master (1.28.0)

Gradle version
8.6

JDK version
17

Describe the bug
When trying to integrate the latest version of the dependency analysis plugin into our codebase, we observed super slow configuration phases with Gradle. I ran ./gradlew projectHealth on my laptop and this is the result:

du -sh .gradle/configuration-cache/*
4.1G	.gradle/configuration-cache/3xf2m0hsy44d2s1o8ckwbboiy

Before, the largest configuration cache side I had on my machine was 24MB. Gradle spent about 2 minutes in the "Storing configuration cache state" and then proceeded to run for 20 minutes in "Loading configuration cache state" after which I killed the process as I didn't expect it to finish.

Unfortunately the caches themselves are .bin files, so I can't give you anymore information of which particular part of the cache is large. I also suspect the size of the configuration cache correlates with the number of Java files, as we configure them in file collections.

To Reproduce
Steps to reproduce the behavior:

  1. Have a lot of code in a lot of modules
  2. Enable Gradle configuration cache
  3. Run ./gradlew projectHealth

Expected behavior
The configuration cache for a run that includes projectHealth is within reasonable bounds compared to other tasks. In our case, we expect the cache to be smaller than 30 MB. It also should not cause the configuration cache storing and loading to take minutes for large builds. For a smaller build of ours, it still took 6 seconds to load the full configuration cache, where the entry is 211MB (about 1/4th of all modules included in this build, but typically excludes the largest ones we have).

CC @cobexer and @ljacomet who contributed the configuration cache fixes in #1039

@mlopatkin
Copy link

There's a viewer for the configuration cache data: https://github.com/gradle/gcc2speedscope, which may give some insights into what is being stored. @TimvdLippe could you please try that?

@autonomousapps autonomousapps added blocked-by-gradle Blocked due to issue with Gradle itself performance labels Jan 11, 2024
@TimvdLippe
Copy link
Author

I will need security approval to run such tooling, so it will take me a couple of days to do so, but will do 👍

Is there a workaround to disable the configuration cache for the task anyways? I think we can mark it as incompatible ourselves, as we are an outlier in terms of codebase size and then we can wait for a potential fix.

@mlopatkin
Copy link

Is there a workaround to disable the configuration cache for the task anyways?

There's Task.notCompatibleWithConfigurationCache, you can look how Gradle itself uses it for a similar purpose. This should be enough to at least prevent loading the cache. I'm not sure if it also disables storing the graph (Gradle still checks for CC errors coming from other tasks), but that's the best option I know of.

@TimvdLippe
Copy link
Author

I was able to clone the project and build it. Unfortunately I am running into runtime issues with a Suppressed: kotlinx.coroutines.channels.ClosedReceiveChannelException: Channel was closed and later a Caused by: java.lang.ClassNotFoundException: gcc2speedscope.EventStore$queryProfiles$1. I lack Kotlin knowledge and am not sure if these are pre-existing issues or related to our internal infrastructure. Will try to debug further with a colleague who has knowledge on Kotlin

@TimvdLippe
Copy link
Author

All right. The failures were related to some internal changes I had to make. Fortunately I was able to run with the assistance of a colleague.

Some data:

  1. The generated debug.log was 16GB
  2. The configuration cache itself is 203MB
  3. With minified output to strip out only tasks, the speedscope.json is 1.1MB
  4. I had to anonymize the data before I could upload it to speedscope.app. Therefore, I had to manually map back to our internal module names and tasks
  5. The number of modules in this particular build is about 1000

With an eyeball look (since we have loads and loads of modules), I was able to see that some tasks stood out:

  1. graphViewMain which is 115Kb for a relatively small module
  2. Same for graphViewTest which is 175KB for that same module
  3. We see the same graphView for each of our sourcesets
  4. For some of the largest sourcesets, I see that each graphViewX takes 1MB and we have about 5 of these, so 5MB per module

Other tasks are also large, but at least from a first glance graphViewX appears to be the culprit. When I omit the data for all these, we are still looking at a large number, but maybe the findings from this task can help us with the rest as well.

@TimvdLippe
Copy link
Author

Looking at the PR in question, I wonder why we annotated compileFiles and runtimeFiles as @InputFiles? In our internal plugins, we have some tasks that also operate on our classpaths, but they don't have such a large configuration input. Instead, they operate on a @Classpath and we retrieve the files during task execution. Since we already have the runtimeClassPath property, shouldn't we annotate that property with @Classpath and then retrieve the files?

Example from our internal plugin that afaik doesn't have the same configuration cache size hit:

    @Classpath
    public abstract ConfigurableFileCollection getSourcesClasspath();

@TimvdLippe
Copy link
Author

@mlopatkin Is that sufficient information for you or do you want me to run anything else?

@TimvdLippe
Copy link
Author

To unblock our upgrade, we will mark the graphView tasks as incompatible with the configuration cache. Hopefully in the future the situation improves and we can use the configuration cache again for these tasks.

@TimvdLippe
Copy link
Author

This is the workaround I used if anybody else runs into this issue:

import com.autonomousapps.tasks.GraphViewTask

project.getPluginManager().withPlugin('com.autonomousapps.dependency-analysis', { plugin ->
    project.getTasks().withType(GraphViewTask).configureEach {
        it.notCompatibleWithConfigurationCache("https://github.com/autonomousapps/dependency-analysis-gradle-plugin/issues/1098")
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blocked-by-gradle Blocked due to issue with Gradle itself performance
Projects
None yet
Development

No branches or pull requests

3 participants