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

Allow inspecting the sizes of configuration cache entries #28327

Open
mgroth0 opened this issue Mar 2, 2024 · 8 comments
Open

Allow inspecting the sizes of configuration cache entries #28327

mgroth0 opened this issue Mar 2, 2024 · 8 comments
Labels
a:feature A new functionality in:configuration-cache Configuration Caching

Comments

@mgroth0
Copy link

mgroth0 commented Mar 2, 2024

Expected Behavior

The title says it all.

Current Behavior (optional)

There is no way to inspect the size of configuration cache interies.

The configuration cache is stored in .bin files (binary, with a custom, internal encoding?) inside .gradle/confgiuration-cache. I'm sure that the encoding is designed and optimized for performance, but currently there is no direct way for a developer to answer the question "what entries in my configuration cache is making it so large"?

The workarounds I can think of are:

  • None

Context

This is the third in my series of issues requesting features to allow developers to debug and better understand their own configuration cache. The previous issues (which as of now are still open):

  1. Programatically access Configuration Cache Report #24757
  2. There is no reasonable way to see all invalidated configuration cache inputs #26440

I have a very large build, and the time it takes to read and write the configuration cache is enormous (in the minutes sometimes) and growing. Now, for the first time, I got an OOM error while gradle was doing some work related to the configuration cache:

OutOfMemoryError Stack Trace

java.lang.OutOfMemoryError: GC overhead limit exceeded
	at org.gradle.configurationcache.serialization.codecs.BeanCodec.decode(BeanCodec.kt)
	at org.gradle.configurationcache.serialization.CombinatorsKt$reentrant$1$decodeLoop$1.invokeSuspend(Combinators.kt:166)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:115)
	at org.gradle.configurationcache.serialization.CombinatorsKt$reentrant$1.decodeLoop(Combinators.kt:167)
	at org.gradle.configurationcache.serialization.CombinatorsKt$reentrant$1.decode(Combinators.kt:131)
	at org.gradle.configurationcache.serialization.codecs.BindingsBackedCodec.decode(BindingsBackedCodec.kt:59)
	at org.gradle.configurationcache.serialization.DefaultReadContext.read(Contexts.kt:269)
	at org.gradle.configurationcache.serialization.beans.BeanPropertyReaderKt.readPropertyValue(BeanPropertyReader.kt:106)
	at org.gradle.configurationcache.serialization.beans.BeanPropertyReader.readStateOf(BeanPropertyReader.kt:68)
	at org.gradle.configurationcache.serialization.codecs.TaskNodeCodec$readTask$2.invokeSuspend(TaskNodeCodec.kt:135)
	at org.gradle.configurationcache.serialization.codecs.TaskNodeCodec$readTask$2.invoke(TaskNodeCodec.kt)
	at org.gradle.configurationcache.serialization.codecs.TaskNodeCodec$readTask$2.invoke(TaskNodeCodec.kt)
	at org.gradle.configurationcache.serialization.codecs.TaskNodeCodecKt.withTaskOf(TaskNodeCodec.kt:237)
	at org.gradle.configurationcache.serialization.codecs.TaskNodeCodecKt.access$withTaskOf(TaskNodeCodec.kt:1)
	at org.gradle.configurationcache.serialization.codecs.TaskNodeCodec.readTask(TaskNodeCodec.kt:129)
	at org.gradle.configurationcache.serialization.codecs.TaskNodeCodec.decode(TaskNodeCodec.kt:78)
	at org.gradle.configurationcache.serialization.codecs.BindingsBackedCodec.decode(BindingsBackedCodec.kt:59)
	at org.gradle.configurationcache.serialization.DefaultReadContext.read(Contexts.kt:269)
	at org.gradle.configurationcache.serialization.CodecKt.readNonNull(Codec.kt:96)
	at org.gradle.configurationcache.serialization.codecs.WorkNodeCodec.readNode(WorkNodeCodec.kt:126)
	at org.gradle.configurationcache.serialization.codecs.WorkNodeCodec.doRead(WorkNodeCodec.kt:104)
	at org.gradle.configurationcache.serialization.codecs.WorkNodeCodec.readWork(WorkNodeCodec.kt:62)
	at org.gradle.configurationcache.ConfigurationCacheState.readWorkGraph(ConfigurationCacheState.kt:493)
	at org.gradle.configurationcache.ConfigurationCacheState.readBuildContent$configuration_cache(ConfigurationCacheState.kt:474)
	at org.gradle.configurationcache.ConfigurationCacheState.readBuildState(ConfigurationCacheState.kt:326)
	at org.gradle.configurationcache.ConfigurationCacheState.readBuildsInTree(ConfigurationCacheState.kt:288)
	at org.gradle.configurationcache.ConfigurationCacheState.readRootBuild(ConfigurationCacheState.kt:259)
	at org.gradle.configurationcache.ConfigurationCacheState.readRootBuildState(ConfigurationCacheState.kt:160)
	at org.gradle.configurationcache.ConfigurationCacheIO$readRootBuildStateFrom$1.invokeSuspend(ConfigurationCacheIO.kt:160)
	at org.gradle.configurationcache.ConfigurationCacheIO$readRootBuildStateFrom$1.invoke(ConfigurationCacheIO.kt)
	at org.gradle.configurationcache.ConfigurationCacheIO$readRootBuildStateFrom$1.invoke(ConfigurationCacheIO.kt)


Currently, there is no reasonable way for me to fix my error. My only options are to basically to delete the cache and try again. My build is large and complex and I have no idea what subprojects/tasks/plugins are making the cache so large. My machine has more than enough memory, and this should not be an issue. Even if I didn't get an OOM error, there is still the issue of configuration-cache work taking minutes at a time.

The whole Gradle community would benefit greatly if developers had the tools to start inspecting their own configuration cache.

  • It would help developers optimize their builds
  • It would help plugin authors optimize their plugins to provide a minimal footprint
  • It would allow new configuration cache issues to reported with greater detail

One way that could possibly be the easiest way to resolve this issue would be a simple command line tool or gradle task that reads a configuration cache folder (with the .bin files) and writes the entries to a json file. Each entry would just need a human-readable name and a byte size. This likely requires a solution to #24757 as well; we need a way to identify which build is associated with which configuration cache folder.

Here is a proposed json format:

[
	{
		"build": "<a build identifier>",
		"configuration-cache": {
		    "entries": [
		        {
		            "name": "FileContents[/some/jar]",
		            "size": 293555
		        },
                {
					"name": "Property[somePropertyName]",
		            "size": 53
		        }
		    ]
		}
	}
]
@mgroth0 mgroth0 added a:feature A new functionality to-triage labels Mar 2, 2024
@mgroth0 mgroth0 changed the title Allow inspecting the sizes of configuration cache entries. Allow inspecting the sizes of configuration cache entries Mar 2, 2024
@mgroth0
Copy link
Author

mgroth0 commented Mar 2, 2024

Using tree -h . in .gradle/configuration-cache I see:

❯ tree -h .
[ 224]  .
├── [ 192]  38c79z7i2qypbf5b7gp32v486
│   ├── [1.1M]  buildfingerprint.bin
│   ├── [  42]  entry.bin
│   ├── [ 83K]  projectfingerprint.bin
│   └── [1.1G]  work.bin
├── [ 192]  48kt9hrzgb8pg0jjaje343bw9
│   ├── [402K]  buildfingerprint.bin
│   ├── [  42]  entry.bin
│   ├── [ 83K]  projectfingerprint.bin
│   └── [498M]  work.bin
├── [ 192]  bepm718zty3c7g6wqah2mbl2e
│   ├── [384K]  buildfingerprint.bin
│   ├── [  42]  entry.bin
│   ├── [ 83K]  projectfingerprint.bin
│   └── [449K]  work.bin
├── [  39]  configuration-cache.lock
└── [   0]  gc.properties

3 directories, 14 files

Why is work.bin over a gigabyte? I would like to inspect it.

@ov7a ov7a added in:configuration-cache Configuration Caching and removed to-triage labels Mar 4, 2024
@ov7a
Copy link
Member

ov7a commented Mar 4, 2024

This feature request is in the backlog of the relevant team and is prioritized by them.

@bamboo
Copy link
Member

bamboo commented Mar 20, 2024

Why is work.bin over a gigabyte? I would like to inspect it.

This tool might help in the meantime: https://github.com/gradle/gcc2speedscope

@mgroth0
Copy link
Author

mgroth0 commented Mar 25, 2024

@bamboo while it doesn't seem like a perfect solution for inspecting the size of the configuration cache entries, the tool you shared still seems it will be very helpful. I just tried it, and the rendered graph contains a lot of useful-looking data for me to look through. It will definitely help in the meantime, thank you!

@mgroth0
Copy link
Author

mgroth0 commented Mar 25, 2024

I have made my first important discovery using speedscope. It seems kotlin BuildFusService was about 25% of my speedscope graph. So I created a YouTrack issue and PR requesting an ability to disable BuildFusService completely.

I will continue to update this issue if I find anything important such as this.

@mgroth0
Copy link
Author

mgroth0 commented Mar 26, 2024

@bamboo I think I have done all I can with this tool, and I would be curious to hear what advice you would have for what I should do next in my troubleshooting of my configuration cache size.

As of now, my "work.bin" file is above 1 gigabyte.

The Sppedscope seems promosing, but I think the main problem is that it seems to only provide the class name and not any details about the instances. For example, it will list many java.io.File entries but not the path of the file.

Here is a screenshot of my speedscope. This is just from running ./gradlew with no tasks.

Screenshot 2024-03-26 at 2 54 01 PM

As you can see there are 2 "sections":

  1. Gradle which is subdivided into a first part and and a second part for RegisteredBuildServiceProvider
  2. Cleanup registrations

Here is a zoomed in view of the first part of the Gradle section
Screenshot 2024-03-26 at 2 55 39 PM

It is very uniform. Most of this first part looks like this.

And here is a zoomed in view of the Cleanup registrations section.

Screenshot 2024-03-26 at 2 56 41 PM

This part is also very uniform.

Here is what I can gather from this analysis:

  • The large majority, like 99% of my configuration cache has to do with build services and/or files.
  • The main classes that are taking up space are FixedDirectory, File, RegisteredBuildServiceProvider, and org.gradle.util.Path

I am eager to break this down more to see how it ammounts to over 1 gigabyte. I am wondering if there might be some sort of bug or issue. For example, do you think it might be possible that something in my build logic is causing the same file path entry to be cached many times? If there was some sort of multiplicitive effect like this, where say duplicates of file path entries were being cached, that might start to explain the large size and some of the performance issues I am facing.

Seeing the actual file paths seems like it would be critical for this analysis.

Also, maybe breaking down the huge "RegisteredBuildServiceProivder" chunk into smaller more detailed entries would help as well.

@mgroth0
Copy link
Author

mgroth0 commented Mar 26, 2024

I realize there are two more views. Here they are for the same build

Screenshot 2024-03-26 at 3 09 03 PM Screenshot 2024-03-26 at 3 09 50 PM

@mgroth0
Copy link
Author

mgroth0 commented Mar 26, 2024

I also notice that in the charts, the numbers are going up to a few hundred kilobytes, which is nowhere near the over 1 gigabyte work.bin that is being produced.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a:feature A new functionality in:configuration-cache Configuration Caching
Projects
None yet
Development

No branches or pull requests

3 participants