Skip to content
Draft
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
1 change: 1 addition & 0 deletions tcmalloc/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ cc_library(
"@com_google_absl//absl/base",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/base:nullability",
"@com_google_absl//absl/crc:crc32c",
"@com_google_absl//absl/functional:function_ref",
"@com_google_absl//absl/hash",
"@com_google_absl//absl/strings",
Expand Down
45 changes: 41 additions & 4 deletions tcmalloc/experiment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

#include "tcmalloc/experiment.h"

#include <string.h>
#include <sys/utsname.h>

#include <algorithm>
#include <cassert>
Expand All @@ -27,10 +27,10 @@
#include "absl/base/attributes.h"
#include "absl/base/call_once.h"
#include "absl/base/internal/cycleclock.h"
#include "absl/crc/crc32c.h"
#include "absl/functional/function_ref.h"
#include "absl/hash/hash.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "tcmalloc/experiment_config.h"
#include "tcmalloc/internal/config.h"
Expand Down Expand Up @@ -75,11 +75,18 @@ const bool* GetSelectedExperiments() {
const char* test_target = thread_safe_getenv("TEST_TARGET");
const char* active_experiments = thread_safe_getenv(kExperiments);
const char* disabled_experiments = thread_safe_getenv(kDisableExperiments);
struct utsname uts;
absl::string_view hostname;
if (const char* env = thread_safe_getenv("HOSTNAME")) {
hostname = env;
}

SelectExperiments(
by_id, test_target ? test_target : "",
active_experiments ? active_experiments : "",
disabled_experiments ? disabled_experiments : "",
active_experiments == nullptr && disabled_experiments == nullptr);
active_experiments == nullptr && disabled_experiments == nullptr,
hostname);
});
return by_id;
}
Expand All @@ -104,16 +111,46 @@ void ParseExperiments(absl::string_view labels, F f) {

} // namespace

uint16_t CalculateRolloutBucket(absl::string_view hostname,
absl::string_view salt) {
uint32_t hash = static_cast<uint32_t>(
absl::ExtendCrc32c(absl::ComputeCrc32c(hostname), salt));
hash = hash ^ (hash >> 16);
return hash % 1024;
}

bool IsExperimentRolloutEnabled(const ExperimentConfig& config,
absl::string_view hostname) {
if (hostname.empty()) {
return false;
}

// Ensure experiments are i.i.d. from one another by using their own names as
// a salt, unless explicitly requested.
absl::string_view salt =
config.rollout_salt.empty() ? config.name : config.rollout_salt;
uint16_t val = CalculateRolloutBucket(hostname, actual_salt);
return val >= config.rollout_lower_bound && val < config.rollout_upper_bound;
}

const bool* SelectExperiments(bool* buffer, absl::string_view test_target,
absl::string_view active,
absl::string_view disabled,
bool unset) {
bool unset, absl::string_view hostname) {
memset(buffer, 0, sizeof(*buffer) * kNumExperiments);

if (active == kEnableAll) {
std::fill(buffer, buffer + kNumExperiments, true);
}

for (const auto& config : experiments) {
if (config.rollout_upper_bound > 0) {
if (IsExperimentRolloutEnabled(config, hostname)) {
buffer[static_cast<int>(config.id)] = true;
}
}
}

ParseExperiments(active, [buffer](absl::string_view token) {
Experiment id;
if (LookupExperimentID(token, &id)) {
Expand Down
8 changes: 7 additions & 1 deletion tcmalloc/experiment.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,13 @@ constexpr size_t kNumExperiments =
const bool* absl_nonnull SelectExperiments(
bool* absl_nonnull buffer, absl::string_view test_target,
absl::string_view active, absl::string_view disabled,
bool unset);
bool unset, absl::string_view hostname);

uint16_t CalculateRolloutBucket(absl::string_view hostname,
absl::string_view salt);

bool IsExperimentRolloutEnabled(const ExperimentConfig& config,
absl::string_view hostname);

} // namespace tcmalloc_internal

Expand Down
5 changes: 5 additions & 0 deletions tcmalloc/experiment_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#ifndef TCMALLOC_EXPERIMENT_CONFIG_H_
#define TCMALLOC_EXPERIMENT_CONFIG_H_

#include <cstdint>

#include "absl/strings/string_view.h"

// Autogenerated by experiments_proto_test --experiments_generate_config=true
Expand Down Expand Up @@ -45,6 +47,9 @@ struct ExperimentConfig {
absl::string_view name;
bool brittle = false;
bool force_disable = false;
uint16_t rollout_lower_bound = 0;
uint16_t rollout_upper_bound = 0;
absl::string_view rollout_salt;
};

// clang-format off
Expand Down
14 changes: 11 additions & 3 deletions tcmalloc/experiment_fuzz.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,39 @@
#include "fuzztest/fuzztest.h"
#include "absl/strings/string_view.h"
#include "tcmalloc/experiment.h"
#include "tcmalloc/experiment_config.h"

namespace tcmalloc::tcmalloc_internal {
namespace {

void FuzzSelectExperiments(absl::string_view test_target,
absl::string_view active, absl::string_view disabled,
bool unset) {
bool unset, absl::string_view hostname) {
if (unset && !test_target.empty() && (!active.empty() || !disabled.empty())) {
return;
}

bool buffer[tcmalloc::tcmalloc_internal::kNumExperiments];

SelectExperiments(buffer, test_target, active, disabled,
unset);
unset, hostname);
}

FUZZ_TEST(ExperimentTest, FuzzSelectExperiments);

void FuzzRolloutEnabled(const ExperimentConfig& config,
absl::string_view hostname) {
IsExperimentRolloutEnabled(config, hostname);
}

FUZZ_TEST(ExperimentTest, FuzzRolloutEnabled);

TEST(ExperimentTest, FuzzSelectExperiments_b395212979) {
FuzzSelectExperiments(
"t_fuenchmark&"
"vvvvvvvvvvvvvvvvvvVvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv",
"", "",
true);
true, "some_hostname");
}

} // namespace
Expand Down
Loading