Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions tcmalloc/internal/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,51 @@ cc_proto_library(
deps = [":profile_proto"],
)

cc_library(
name = "compressibility",
srcs = ["compressibility.cc"],
hdrs = ["compressibility.h"],
copts = TCMALLOC_DEFAULT_COPTS,
visibility = [
"//tcmalloc:__subpackages__",
],
deps = [
":util",
"@com_google_absl//absl/container:fixed_array",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/types:span",
],
)

cc_test(
name = "compressibility_test",
srcs = ["compressibility_test.cc"],
copts = TCMALLOC_DEFAULT_COPTS,
tags = ["nompu64"], # tcmalloc:google3-only
deps = [
":compressibility",
"@com_google_absl//absl/status:status_matchers",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/types:span",
"@com_google_googletest//:gtest_main",
],
)

cc_test(
name = "compressibility_fuzz",
srcs = ["compressibility_fuzz.cc"],
copts = TCMALLOC_DEFAULT_COPTS + TCMALLOC_DEFAULT_CXXOPTS,
tags = ["nompu64"], # tcmalloc:google3-only
deps = [
":compressibility",
"@com_google_absl//absl/types:span",
"@com_google_fuzztest//fuzztest",
"@com_google_fuzztest//fuzztest:fuzztest_gtest_main",
"@com_google_googletest//:gtest",
],
)

cc_library(
name = "profile_builder",
srcs = ["profile_builder.cc"],
Expand All @@ -992,14 +1037,18 @@ cc_library(
"//tcmalloc:__subpackages__",
],
deps = [
":compressibility",
":logging",
":pageflags",
":parameter_accessors",
":residency",
":util",
"//tcmalloc:malloc_extension",
"//tcmalloc/internal:profile_cc_proto",
"@com_google_absl//absl/base",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:btree",
"@com_google_absl//absl/container:fixed_array",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/hash",
"@com_google_absl//absl/status",
Expand Down
69 changes: 69 additions & 0 deletions tcmalloc/internal/compressibility.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2026 The TCMalloc Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "tcmalloc/internal/compressibility.h"

#include <algorithm>
#include <cstddef>
#include <cstdint>

#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/types/span.h"
#include "tcmalloc/internal/util.h"

namespace tcmalloc {
namespace tcmalloc_internal {

CompressionAnalyzer::CompressionAnalyzer(size_t max_local_copy_size)
: local_copy_(max_local_copy_size)
{}

absl::StatusOr<CompressionAnalyzer::Results> CompressionAnalyzer::Analyze(
absl::Span<const char> data) {
Results results;
bool still_in_trailing_zeroes = true;

// Walk backwards from the end of the data, copying chunks.
int64_t end_offset = data.size();
while (end_offset > 0) {
int64_t chunk_size =
std::min(end_offset, static_cast<int64_t>(local_copy_.size()));
if (!SafeCopyMemory(/*src=*/data.data() + end_offset - chunk_size,
/*dst=*/local_copy_.data(), /*size=*/chunk_size)) {
return absl::InternalError("SafeCopyMemory failed");
}
auto chunk = absl::MakeConstSpan(local_copy_.data(), chunk_size);

// Count zero bytes and trailing zero bytes in chunk.
for (size_t i = chunk.size(); i > 0; --i) {
char c = chunk[i - 1];
if (c == 0) {
results.zero_bytes++;
if (still_in_trailing_zeroes) {
results.trailing_zero_bytes++;
}
} else {
still_in_trailing_zeroes = false;
}
}

end_offset -= chunk_size;
}

return results;
}

} // namespace tcmalloc_internal
} // namespace tcmalloc
49 changes: 49 additions & 0 deletions tcmalloc/internal/compressibility.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2026 The TCMalloc Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef TCMALLOC_INTERNAL_COMPRESSIBILITY_H_
#define TCMALLOC_INTERNAL_COMPRESSIBILITY_H_

#include <cstddef>
#include <cstdint>

#include "absl/container/fixed_array.h"
#include "absl/status/statusor.h"
#include "absl/types/span.h"

namespace tcmalloc {
namespace tcmalloc_internal {

class CompressionAnalyzer {
public:
static constexpr size_t kDefaultMaxLocalCopySize = 2 * 1024 * 1024;

explicit CompressionAnalyzer(
size_t max_local_copy_size = kDefaultMaxLocalCopySize);

struct Results {
size_t zero_bytes = 0;
size_t trailing_zero_bytes = 0;
};

absl::StatusOr<Results> Analyze(absl::Span<const char> data);

private:
absl::FixedArray<char> local_copy_;
};

} // namespace tcmalloc_internal
} // namespace tcmalloc

#endif // TCMALLOC_INTERNAL_COMPRESSIBILITY_H_
42 changes: 42 additions & 0 deletions tcmalloc/internal/compressibility_fuzz.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2026 The TCMalloc Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cstddef>
#include <string>

#include "gtest/gtest.h"
#include "fuzztest/fuzztest.h"
#include "absl/types/span.h"
#include "tcmalloc/internal/compressibility.h"

namespace tcmalloc {
namespace tcmalloc_internal {
namespace {

void FuzzAnalyze(std::string data, size_t max_local_copy_size) {
CompressionAnalyzer analyzer(max_local_copy_size);
auto res = analyzer.Analyze(absl::MakeConstSpan(data));
if (res.ok()) {
EXPECT_LE(res->zero_bytes, data.size());
EXPECT_LE(res->trailing_zero_bytes, data.size());
EXPECT_LE(res->trailing_zero_bytes, res->zero_bytes);
}
}

FUZZ_TEST(CompressibilityFuzzTest, FuzzAnalyze)
.WithDomains(fuzztest::String(), fuzztest::InRange<size_t>(1, 1024));

} // namespace
} // namespace tcmalloc_internal
} // namespace tcmalloc
120 changes: 120 additions & 0 deletions tcmalloc/internal/compressibility_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright 2026 The TCMalloc Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "tcmalloc/internal/compressibility.h"

#include <sys/mman.h>
#include <unistd.h>

#include <algorithm>
#include <cstddef>
#include <cstring>
#include <random>
#include <vector>

#include "gtest/gtest.h"
#include "absl/status/status_matchers.h"
#include "absl/types/span.h"

namespace tcmalloc {
namespace tcmalloc_internal {
namespace {

TEST(CompressibilityTest, AllZeroes) {
CompressionAnalyzer analyzer;
std::vector<char> buf(8199, 0);
auto res = analyzer.Analyze(absl::MakeConstSpan(buf));
ABSL_ASSERT_OK(res);
EXPECT_EQ(res->zero_bytes, 8199);
EXPECT_EQ(res->trailing_zero_bytes, 8199);
}

TEST(CompressibilityTest, PartialZeroes) {
CompressionAnalyzer analyzer;
std::vector<char> buf(8199, 0);
std::memset(buf.data() + 100, 0x42, 100);
auto res = analyzer.Analyze(absl::MakeConstSpan(buf));
ABSL_ASSERT_OK(res);
EXPECT_EQ(res->zero_bytes, 8199 - 100);
EXPECT_EQ(res->trailing_zero_bytes, 8199 - 200);
}

TEST(CompressibilityTest, UniformNonZero) {
CompressionAnalyzer analyzer;
std::vector<char> buf(8199, 0x42);
auto res = analyzer.Analyze(absl::MakeConstSpan(buf));
ABSL_ASSERT_OK(res);
EXPECT_EQ(res->zero_bytes, 0);
EXPECT_EQ(res->trailing_zero_bytes, 0);
}

TEST(CompressibilityTest, PseudoRandomUncompressible) {
CompressionAnalyzer analyzer;
std::vector<char> buf(8199);
std::minstd_rand rng(12345);
std::generate(buf.begin(), buf.end(),
[&]() { return static_cast<char>(rng() | 1); });
auto res = analyzer.Analyze(absl::MakeConstSpan(buf));
ABSL_ASSERT_OK(res);
EXPECT_EQ(res->zero_bytes, 0);
EXPECT_EQ(res->trailing_zero_bytes, 0);
}

TEST(CompressibilityTest, MultiChunkScanning) {
CompressionAnalyzer analyzer;
// 5MB allocation to test multi-chunk scanning (>2MB)
size_t huge_size = 5 * 1024 * 1024 + 123;
std::vector<char> buf(huge_size);
std::memset(buf.data(), 0x42, 4 * 1024 * 1024);
std::memset(buf.data() + 4 * 1024 * 1024, 0, 1 * 1024 * 1024 + 123);

auto res = analyzer.Analyze(absl::MakeConstSpan(buf));
ABSL_ASSERT_OK(res);
EXPECT_EQ(res->zero_bytes, 1 * 1024 * 1024 + 123);
EXPECT_EQ(res->trailing_zero_bytes, 1 * 1024 * 1024 + 123);
}

TEST(CompressibilityTest, TrailingZeroesSkippedInCompression) {
CompressionAnalyzer analyzer;
size_t huge_size = 5 * 1024 * 1024 + 123;
std::vector<char> buf(huge_size);
std::minstd_rand rng(12345);
std::generate(buf.begin(), buf.begin() + 4 * 1024 * 1024,
[&]() { return static_cast<char>(rng() | 1); });
std::memset(buf.data() + 4 * 1024 * 1024, 0, 1 * 1024 * 1024 + 123);

auto res = analyzer.Analyze(absl::MakeConstSpan(buf));
ABSL_ASSERT_OK(res);
EXPECT_EQ(res->zero_bytes, 1 * 1024 * 1024 + 123);
EXPECT_EQ(res->trailing_zero_bytes, 1 * 1024 * 1024 + 123);
}

#if defined(__linux__)
TEST(CompressibilityTest, SafeMemoryCopyFailure) {
CompressionAnalyzer analyzer;
size_t page_size = sysconf(_SC_PAGESIZE);
void* prot_none =
mmap(nullptr, page_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (prot_none != MAP_FAILED) {
auto res = analyzer.Analyze(
absl::MakeConstSpan(static_cast<const char*>(prot_none), page_size));
EXPECT_FALSE(res.ok());
munmap(prot_none, page_size);
}
}
#endif // defined(__linux__)

} // namespace
} // namespace tcmalloc_internal
} // namespace tcmalloc
Loading
Loading