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

Add Windows native method to retrieve the number of allocated bytes on disk for file #79698

Merged
merged 24 commits into from
Nov 5, 2021

Conversation

tlrx
Copy link
Member

@tlrx tlrx commented Oct 25, 2021

In #79371 we fixed a bug where cache files were not created as sparse files on Windows platforms because the wrong options were used when creating the files for the first time. This bug got unnoticed as we were lacking a way to retrieve the exact number of bytes allocated for a given file on disk.

This pull request adds a new Natives.allocatedSizeInBytes(Path) method for that exact purpose (only implemented for Windows for now) and a test in CacheFileTests that would fail on Windows if the cache file is not sparse.

As a next step I'd like to add support for stat() on Linux x86 64bits platforms, and then try to craft an appropriate assertion that would verify that files are allocated on disk according to the ranges that are written in cache (this may need some non trivial computation with block sizes etc)

@tlrx tlrx added >test Issues or PRs that are addressing/adding tests :Distributed Coordination/Snapshot/Restore Anything directly related to the `_snapshot/*` APIs v8.0.0 test-windows Trigger CI checks on Windows v7.16.1 labels Oct 25, 2021
@elasticmachine elasticmachine added the Team:Distributed (Obsolete) Meta label for distributed team (obsolete). Replaced by Distributed Indexing/Coordination. label Oct 25, 2021
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-distributed (Team:Distributed)

@tlrx tlrx force-pushed the windows-native-method-allocated-file-size branch from 972f252 to d13a95b Compare October 25, 2021 09:32
@tlrx
Copy link
Member Author

tlrx commented Oct 25, 2021

@elasticmachine run elasticsearch-ci/part-2-windows (unrelated failure)

@tlrx
Copy link
Member Author

tlrx commented Oct 25, 2021

@tlrx
Copy link
Member Author

tlrx commented Oct 25, 2021

Test failed as expected:
https://gradle-enterprise.elastic.co/s/stxcoxtiq6hr2/console-log/raw

org.elasticsearch.xpack.searchablesnapshots.cache.common.CacheFileTests > testCacheFileCreatedAsSparseFile FAILED
    java.lang.AssertionError: 
    Expected: not <1048576L>
         but: was <1048576L>
        at __randomizedtesting.SeedInfo.seed([B63C9F8865D2540F:33B26286A2CDA87E]:0)
        at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:18)
        at org.junit.Assert.assertThat(Assert.java:956)
        at org.junit.Assert.assertThat(Assert.java:923)
        at org.elasticsearch.xpack.searchablesnapshots.cache.common.CacheFileTests.testCacheFileCreatedAsSparseFile(CacheFileTests.java:411)

Now reverting the create/create_new option and test should succeed again.

@tlrx tlrx changed the title [Draft] Add Windows native method to retrieve the number of allocated bytes on disk for file Add Windows native method to retrieve the number of allocated bytes on disk for file Oct 26, 2021
Copy link
Contributor

@DaveCTurner DaveCTurner left a comment

Choose a reason for hiding this comment

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

Looks great :) I left a couple of small comments.

fill(cacheFile.getChannel(), Math.toIntExact(cacheFile.getLength() - 1L), Math.toIntExact(cacheFile.getLength()));

sizeOnDisk = Natives.allocatedSizeInBytes(file);
assertThat(sizeOnDisk, not(equalTo(1048576L)));
assertThat("Cache file should be sparse and not fully allocated on disk", sizeOnDisk, not(equalTo(1048576L)));
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we be asserting its size is <1MiB rather than ≠1MiB? I worry that some funny rounding (e.g. counting the size of the directory entry) could pass this assertion even for non-sparse files.

After we make this assertion could we fill the file with genuine data and assert that its size on disk is now ≥1MiB?

Copy link
Member Author

Choose a reason for hiding this comment

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

Should we be asserting its size is <1MiB rather than ≠1MiB?

Yes it is more appropriate (I think I did it and then reverted it somehow 🤔 )

After we make this assertion could we fill the file with genuine data and assert that its size on disk is now ≥1MiB?

Yes too.

I pushed f109191

* @param lpFileSizeHigh pointer to high-order DWORD for compressed file size (or null if not needed)
* @return the low-order DWORD for compressed file siz
*/
native int GetCompressedFileSizeW(WString lpFileName, IntByReference lpFileSizeHigh);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we define this in test/framework rather than server since it's not used in prod code today? Or are we planning to use it in prod later on (e.g. expose sparse sizes in stats)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Personally I'd like this to be returned as part of searchable snapshot's Cache Stats API, so that we have information about the shared cache and cold cache that sit altogether. What do you think?

I can try to make it work in the test framework; it should be OK as long as the static instance is created before the security manager is installed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Also: I'd like to have assertion in CacheFile itself that would retrieve the allocated size on disk and compares it with the completed ranges of a cache file, and verify if size vs ranges is not completly out of bounds.

That would also require the method to be in server. I tried to make it work in the plugin itself but for JNA kernel32 library it's very difficult given all the permissions it requires (createClassLoader, setSecurityManager etc). Linux is OK though.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 having these stats in future seems like a good plan, so I'm ok to leave the method where it is.

@tlrx tlrx requested a review from DaveCTurner October 26, 2021 08:55
Copy link
Contributor

@DaveCTurner DaveCTurner left a comment

Choose a reason for hiding this comment

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

LGTM (one nit)

@@ -411,7 +412,16 @@ public void testCacheFileCreatedAsSparseFile() throws Exception {
fill(cacheFile.getChannel(), Math.toIntExact(cacheFile.getLength() - 1L), Math.toIntExact(cacheFile.getLength()));

sizeOnDisk = Natives.allocatedSizeInBytes(file);
assertThat("Cache file should be sparse and not fully allocated on disk", sizeOnDisk, not(equalTo(1048576L)));
assertThat("Cache file should be sparse and not fully allocated on disk", sizeOnDisk, lessThan(1048576L));
Copy link
Contributor

Choose a reason for hiding this comment

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

I have a slight preference for extracting the 1048576L as a constant (it appears in 3 places now and anyway I don't immediately recognise that this is a nice power of two).

* @param lpFileSizeHigh pointer to high-order DWORD for compressed file size (or null if not needed)
* @return the low-order DWORD for compressed file siz
*/
native int GetCompressedFileSizeW(WString lpFileName, IntByReference lpFileSizeHigh);
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 having these stats in future seems like a good plan, so I'm ok to leave the method where it is.

@tlrx
Copy link
Member Author

tlrx commented Oct 26, 2021

I'm holding merging this: there is a test failure and the method does not return the expected result. I'm investigating.

@tlrx tlrx requested a review from arteam October 26, 2021 14:45
@tlrx
Copy link
Member Author

tlrx commented Oct 26, 2021

All good, the test failure was caused by DisableFsyncFS which is now suppressed.

Copy link
Contributor

@DaveCTurner DaveCTurner left a comment

Choose a reason for hiding this comment

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

I left some suggestions, also I think there's a (super-rare) case in which we incorrectly return empty().

return OptionalLong.empty();
}

final long allocatedSize = (((long) lpFileSizeHigh.getValue()) << 32) | (lpFileSizeLow & 0xffffffffL);
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 to a comment here, although it's pretty much the standard idiom for doing this computation.

Suggested change
final long allocatedSize = (((long) lpFileSizeHigh.getValue()) << 32) | (lpFileSizeLow & 0xffffffffL);
// convert lpFileSizeLow to unsigned long and combine with signed/shifted lpFileSizeHigh
final long allocatedSize = (((long) lpFileSizeHigh.getValue()) << Integer.SIZE) | (lpFileSizeLow & 0xffffffffL);

final IntByReference lpFileSizeHigh = new IntByReference();

final int lpFileSizeLow = GetCompressedFileSizeW(fileName, lpFileSizeHigh);
if (lpFileSizeLow == 0xffffffff) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It's still an int here so I think we can just say -1, but also the API docs call this constant INVALID_FILE_SIZE. Could we use the same name here?

Suggested change
if (lpFileSizeLow == 0xffffffff) {
if (lpFileSizeLow == INVALID_FILE_SIZE) {

Copy link
Member Author

Choose a reason for hiding this comment

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

I pushed 5928b39

final int lpFileSizeLow = GetCompressedFileSizeW(fileName, lpFileSizeHigh);
if (lpFileSizeLow == 0xffffffff) {
final int err = Native.getLastError();
logger.warn("error [{}] when executing native method GetCompressedFileSizeW for file [{}]", err, path);
Copy link
Contributor

Choose a reason for hiding this comment

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

lpFileSizeLow can legitimately be INVALID_FILE_SIZE (== -1) if the file size is one less than a multiple of 2^32; in this case getLastError() returns NO_ERROR and we should carry on with the computation.

Copy link
Member Author

Choose a reason for hiding this comment

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

Wow thanks for catching this David! I remember I left this aside while I was fighting to make this work with the security manager but I forgot to come back to it.

I pushed 3357e8f

@tlrx tlrx requested review from arteam and DaveCTurner November 5, 2021 11:26
Copy link
Contributor

@DaveCTurner DaveCTurner left a comment

Choose a reason for hiding this comment

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

LGTM

@tlrx tlrx merged commit 4cf8e4b into elastic:master Nov 5, 2021
@tlrx tlrx deleted the windows-native-method-allocated-file-size branch November 5, 2021 15:22
@tlrx
Copy link
Member Author

tlrx commented Nov 5, 2021

Thanks everybody!

tlrx added a commit to tlrx/elasticsearch that referenced this pull request Nov 5, 2021
…n disk for file (elastic#79698)

In elastic#79371 we fixed a bug where cache files were not created 
as sparse files on Windows platforms because the wrong 
options were used when creating the files for the first time. 
This bug got unnoticed as we were lacking a way to retrieve 
the exact number of bytes allocated for a given file on disk.

This commit adds a FileSystemNatives.allocatedSizeInBytes(Path) 
method for that exact purpose (only implemented for Windows 
for now) and a test in CacheFileTests that would fail on 
Windows if the cache file is not sparse.

Relates elastic#79371
tlrx added a commit to tlrx/elasticsearch that referenced this pull request Nov 5, 2021
…n disk for file (elastic#79698)

In elastic#79371 we fixed a bug where cache files were not created 
as sparse files on Windows platforms because the wrong 
options were used when creating the files for the first time. 
This bug got unnoticed as we were lacking a way to retrieve 
the exact number of bytes allocated for a given file on disk.

This commit adds a FileSystemNatives.allocatedSizeInBytes(Path) 
method for that exact purpose (only implemented for Windows 
for now) and a test in CacheFileTests that would fail on 
Windows if the cache file is not sparse.

Relates elastic#79371
@elasticsearchmachine
Copy link
Collaborator

💚 Backport successful

Status Branch Result
8.0
7.16

tlrx added a commit to tlrx/elasticsearch that referenced this pull request Nov 5, 2021
elasticsearchmachine pushed a commit that referenced this pull request Nov 5, 2021
…n disk for file (#79698) (#80426)

In #79371 we fixed a bug where cache files were not created 
as sparse files on Windows platforms because the wrong 
options were used when creating the files for the first time. 
This bug got unnoticed as we were lacking a way to retrieve 
the exact number of bytes allocated for a given file on disk.

This commit adds a FileSystemNatives.allocatedSizeInBytes(Path) 
method for that exact purpose (only implemented for Windows 
for now) and a test in CacheFileTests that would fail on 
Windows if the cache file is not sparse.

Relates #79371

Co-authored-by: Elastic Machine <[email protected]>
elasticsearchmachine pushed a commit that referenced this pull request Nov 5, 2021
…n disk for file (#79698) (#80427)

In #79371 we fixed a bug where cache files were not created 
as sparse files on Windows platforms because the wrong 
options were used when creating the files for the first time. 
This bug got unnoticed as we were lacking a way to retrieve 
the exact number of bytes allocated for a given file on disk.

This commit adds a FileSystemNatives.allocatedSizeInBytes(Path) 
method for that exact purpose (only implemented for Windows 
for now) and a test in CacheFileTests that would fail on 
Windows if the cache file is not sparse.

Relates #79371

Co-authored-by: Elastic Machine <[email protected]>
tlrx added a commit that referenced this pull request Dec 6, 2021
…d bytes on disk for a file (#80437)

This commit introduces a new native method for Linux 64bits 
platforms to retrieve the number of bytes allocated on disk for 
a given sparse file. It calls the native glibc method __xstat.

Follow up #79698
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
:Distributed Coordination/Snapshot/Restore Anything directly related to the `_snapshot/*` APIs Team:Distributed (Obsolete) Meta label for distributed team (obsolete). Replaced by Distributed Indexing/Coordination. >test Issues or PRs that are addressing/adding tests test-windows Trigger CI checks on Windows v7.16.0 v8.0.0-rc1 v8.1.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants