mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add basic microbenchmarks.
PiperOrigin-RevId: 296104390
This commit is contained in:
committed by
Copybara-Service
parent
2daa21e4d7
commit
30794512d3
@@ -330,3 +330,13 @@ http_archive(
|
||||
"https://github.com/google/googletest/archive/565f1b848215b77c3732bca345fe76a0431d8b34.tar.gz",
|
||||
],
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "com_google_benchmark",
|
||||
sha256 = "3c6a165b6ecc948967a1ead710d4a181d7b0fbcaa183ef7ea84604994966221a",
|
||||
strip_prefix = "benchmark-1.5.0",
|
||||
urls = [
|
||||
"https://mirror.bazel.build/github.com/google/benchmark/archive/v1.5.0.tar.gz",
|
||||
"https://github.com/google/benchmark/archive/v1.5.0.tar.gz",
|
||||
],
|
||||
)
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
load("//test/runner:defs.bzl", "syscall_test")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
syscall_test(
|
||||
test = "//test/perf/linux:clock_getres_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
test = "//test/perf/linux:clock_gettime_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
test = "//test/perf/linux:death_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
test = "//test/perf/linux:epoll_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
test = "//test/perf/linux:fork_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
test = "//test/perf/linux:futex_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
test = "//test/perf/linux:getdents_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
test = "//test/perf/linux:getpid_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
test = "//test/perf/linux:gettid_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
test = "//test/perf/linux:mapping_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
add_overlay = True,
|
||||
test = "//test/perf/linux:open_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
test = "//test/perf/linux:pipe_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
add_overlay = True,
|
||||
test = "//test/perf/linux:randread_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
add_overlay = True,
|
||||
test = "//test/perf/linux:read_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
test = "//test/perf/linux:sched_yield_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
test = "//test/perf/linux:send_recv_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
add_overlay = True,
|
||||
test = "//test/perf/linux:seqwrite_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
test = "//test/perf/linux:signal_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
test = "//test/perf/linux:sleep_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
add_overlay = True,
|
||||
test = "//test/perf/linux:stat_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
add_overlay = True,
|
||||
test = "//test/perf/linux:unlink_benchmark",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
add_overlay = True,
|
||||
test = "//test/perf/linux:write_benchmark",
|
||||
)
|
||||
@@ -0,0 +1,356 @@
|
||||
load("//tools:defs.bzl", "cc_binary", "gbenchmark", "gtest")
|
||||
|
||||
package(
|
||||
default_visibility = ["//:sandbox"],
|
||||
licenses = ["notice"],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "getpid_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"getpid_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:test_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "send_recv_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"send_recv_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/syscalls/linux:socket_test_util",
|
||||
"//test/util:file_descriptor",
|
||||
"//test/util:logging",
|
||||
"//test/util:posix_error",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
"//test/util:thread_util",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "gettid_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"gettid_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:test_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "sched_yield_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"sched_yield_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "clock_getres_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"clock_getres_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:test_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "clock_gettime_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"clock_gettime_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:test_main",
|
||||
"@com_google_absl//absl/time",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "open_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"open_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:fs_util",
|
||||
"//test/util:logging",
|
||||
"//test/util:temp_path",
|
||||
"//test/util:test_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "read_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"read_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:fs_util",
|
||||
"//test/util:logging",
|
||||
"//test/util:temp_path",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "randread_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"randread_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:file_descriptor",
|
||||
"//test/util:logging",
|
||||
"//test/util:temp_path",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
"@com_google_absl//absl/random",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "write_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"write_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:logging",
|
||||
"//test/util:temp_path",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "seqwrite_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"seqwrite_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:logging",
|
||||
"//test/util:temp_path",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
"@com_google_absl//absl/random",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "pipe_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"pipe_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:logging",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
"//test/util:thread_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "fork_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"fork_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:cleanup",
|
||||
"//test/util:file_descriptor",
|
||||
"//test/util:logging",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
"//test/util:thread_util",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "futex_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"futex_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:logging",
|
||||
"//test/util:test_main",
|
||||
"//test/util:thread_util",
|
||||
"@com_google_absl//absl/time",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "epoll_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"epoll_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:epoll_util",
|
||||
"//test/util:file_descriptor",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
"//test/util:thread_util",
|
||||
"@com_google_absl//absl/time",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "death_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"death_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:logging",
|
||||
"//test/util:test_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "mapping_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"mapping_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:logging",
|
||||
"//test/util:memory_util",
|
||||
"//test/util:posix_error",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "signal_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"signal_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:logging",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "getdents_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"getdents_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:file_descriptor",
|
||||
"//test/util:fs_util",
|
||||
"//test/util:temp_path",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "sleep_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"sleep_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:logging",
|
||||
"//test/util:test_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "stat_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"stat_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:fs_util",
|
||||
"//test/util:temp_path",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "unlink_benchmark",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"unlink_benchmark.cc",
|
||||
],
|
||||
deps = [
|
||||
gbenchmark,
|
||||
gtest,
|
||||
"//test/util:fs_util",
|
||||
"//test/util:temp_path",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <time.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
// clock_getres(1) is very nearly a no-op syscall, but it does require copying
|
||||
// out to a userspace struct. It thus provides a nice small copy-out benchmark.
|
||||
void BM_ClockGetRes(benchmark::State& state) {
|
||||
struct timespec ts;
|
||||
for (auto _ : state) {
|
||||
clock_getres(CLOCK_MONOTONIC, &ts);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_ClockGetRes);
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <pthread.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
void BM_ClockGettimeThreadCPUTime(benchmark::State& state) {
|
||||
clockid_t clockid;
|
||||
ASSERT_EQ(0, pthread_getcpuclockid(pthread_self(), &clockid));
|
||||
struct timespec tp;
|
||||
|
||||
for (auto _ : state) {
|
||||
clock_gettime(clockid, &tp);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_ClockGettimeThreadCPUTime);
|
||||
|
||||
void BM_VDSOClockGettime(benchmark::State& state) {
|
||||
const clockid_t clock = state.range(0);
|
||||
struct timespec tp;
|
||||
absl::Time start = absl::Now();
|
||||
|
||||
// Don't benchmark the calibration phase.
|
||||
while (absl::Now() < start + absl::Milliseconds(2100)) {
|
||||
clock_gettime(clock, &tp);
|
||||
}
|
||||
|
||||
for (auto _ : state) {
|
||||
clock_gettime(clock, &tp);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_VDSOClockGettime)->Arg(CLOCK_MONOTONIC)->Arg(CLOCK_REALTIME);
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <signal.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "test/util/logging.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
// DeathTest is not so much a microbenchmark as a macrobenchmark. It is testing
|
||||
// the ability of gVisor (on whatever platform) to execute all the related
|
||||
// stack-dumping routines associated with EXPECT_EXIT / EXPECT_DEATH.
|
||||
TEST(DeathTest, ZeroEqualsOne) {
|
||||
EXPECT_EXIT({ TEST_CHECK(0 == 1); }, ::testing::KilledBySignal(SIGABRT), "");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <sys/epoll.h>
|
||||
#include <sys/eventfd.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <memory>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "test/util/epoll_util.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/test_util.h"
|
||||
#include "test/util/thread_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns a new eventfd.
|
||||
PosixErrorOr<FileDescriptor> NewEventFD() {
|
||||
int fd = eventfd(0, /* flags = */ 0);
|
||||
MaybeSave();
|
||||
if (fd < 0) {
|
||||
return PosixError(errno, "eventfd");
|
||||
}
|
||||
return FileDescriptor(fd);
|
||||
}
|
||||
|
||||
// Also stolen from epoll.cc unit tests.
|
||||
void BM_EpollTimeout(benchmark::State& state) {
|
||||
constexpr int kFDsPerEpoll = 3;
|
||||
auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
|
||||
|
||||
std::vector<FileDescriptor> eventfds;
|
||||
for (int i = 0; i < kFDsPerEpoll; i++) {
|
||||
eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()));
|
||||
ASSERT_NO_ERRNO(
|
||||
RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN, 0));
|
||||
}
|
||||
|
||||
struct epoll_event result[kFDsPerEpoll];
|
||||
int timeout_ms = state.range(0);
|
||||
|
||||
for (auto _ : state) {
|
||||
EXPECT_EQ(0, epoll_wait(epollfd.get(), result, kFDsPerEpoll, timeout_ms));
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_EpollTimeout)->Range(0, 8);
|
||||
|
||||
// Also stolen from epoll.cc unit tests.
|
||||
void BM_EpollAllEvents(benchmark::State& state) {
|
||||
auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
|
||||
const int fds_per_epoll = state.range(0);
|
||||
constexpr uint64_t kEventVal = 5;
|
||||
|
||||
std::vector<FileDescriptor> eventfds;
|
||||
for (int i = 0; i < fds_per_epoll; i++) {
|
||||
eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()));
|
||||
ASSERT_NO_ERRNO(
|
||||
RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN, 0));
|
||||
|
||||
ASSERT_THAT(WriteFd(eventfds[i].get(), &kEventVal, sizeof(kEventVal)),
|
||||
SyscallSucceedsWithValue(sizeof(kEventVal)));
|
||||
}
|
||||
|
||||
std::vector<struct epoll_event> result(fds_per_epoll);
|
||||
|
||||
for (auto _ : state) {
|
||||
EXPECT_EQ(fds_per_epoll,
|
||||
epoll_wait(epollfd.get(), result.data(), fds_per_epoll, 0));
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_EpollAllEvents)->Range(2, 1024);
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,350 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <unistd.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/synchronization/barrier.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "test/util/cleanup.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/logging.h"
|
||||
#include "test/util/test_util.h"
|
||||
#include "test/util/thread_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kBusyMax = 250;
|
||||
|
||||
// Do some CPU-bound busy-work.
|
||||
int busy(int max) {
|
||||
// Prevent the compiler from optimizing this work away,
|
||||
volatile int count = 0;
|
||||
|
||||
for (int i = 1; i < max; i++) {
|
||||
for (int j = 2; j < i / 2; j++) {
|
||||
if (i % j == 0) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void BM_CPUBoundUniprocess(benchmark::State& state) {
|
||||
for (auto _ : state) {
|
||||
busy(kBusyMax);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_CPUBoundUniprocess);
|
||||
|
||||
void BM_CPUBoundAsymmetric(benchmark::State& state) {
|
||||
const size_t max = state.max_iterations;
|
||||
pid_t child = fork();
|
||||
if (child == 0) {
|
||||
for (int i = 0; i < max; i++) {
|
||||
busy(kBusyMax);
|
||||
}
|
||||
_exit(0);
|
||||
}
|
||||
ASSERT_THAT(child, SyscallSucceeds());
|
||||
ASSERT_TRUE(state.KeepRunningBatch(max));
|
||||
|
||||
int status;
|
||||
EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds());
|
||||
EXPECT_TRUE(WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
ASSERT_FALSE(state.KeepRunning());
|
||||
}
|
||||
|
||||
BENCHMARK(BM_CPUBoundAsymmetric)->UseRealTime();
|
||||
|
||||
void BM_CPUBoundSymmetric(benchmark::State& state) {
|
||||
std::vector<pid_t> children;
|
||||
auto child_cleanup = Cleanup([&] {
|
||||
for (const pid_t child : children) {
|
||||
int status;
|
||||
EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds());
|
||||
EXPECT_TRUE(WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
}
|
||||
ASSERT_FALSE(state.KeepRunning());
|
||||
});
|
||||
|
||||
const int processes = state.range(0);
|
||||
for (int i = 0; i < processes; i++) {
|
||||
size_t cur = (state.max_iterations + (processes - 1)) / processes;
|
||||
if ((state.iterations() + cur) >= state.max_iterations) {
|
||||
cur = state.max_iterations - state.iterations();
|
||||
}
|
||||
pid_t child = fork();
|
||||
if (child == 0) {
|
||||
for (int i = 0; i < cur; i++) {
|
||||
busy(kBusyMax);
|
||||
}
|
||||
_exit(0);
|
||||
}
|
||||
ASSERT_THAT(child, SyscallSucceeds());
|
||||
if (cur > 0) {
|
||||
// We can have a zero cur here, depending.
|
||||
ASSERT_TRUE(state.KeepRunningBatch(cur));
|
||||
}
|
||||
children.push_back(child);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_CPUBoundSymmetric)->Range(2, 16)->UseRealTime();
|
||||
|
||||
// Child routine for ProcessSwitch/ThreadSwitch.
|
||||
// Reads from readfd and writes the result to writefd.
|
||||
void SwitchChild(int readfd, int writefd) {
|
||||
while (1) {
|
||||
char buf;
|
||||
int ret = ReadFd(readfd, &buf, 1);
|
||||
if (ret == 0) {
|
||||
break;
|
||||
}
|
||||
TEST_CHECK_MSG(ret == 1, "read failed");
|
||||
|
||||
ret = WriteFd(writefd, &buf, 1);
|
||||
if (ret == -1) {
|
||||
TEST_CHECK_MSG(errno == EPIPE, "unexpected write failure");
|
||||
break;
|
||||
}
|
||||
TEST_CHECK_MSG(ret == 1, "write failed");
|
||||
}
|
||||
}
|
||||
|
||||
// Send bytes in a loop through a series of pipes, each passing through a
|
||||
// different process.
|
||||
//
|
||||
// Proc 0 Proc 1
|
||||
// * ----------> *
|
||||
// ^ Pipe 1 |
|
||||
// | |
|
||||
// | Pipe 0 | Pipe 2
|
||||
// | |
|
||||
// | |
|
||||
// | Pipe 3 v
|
||||
// * <---------- *
|
||||
// Proc 3 Proc 2
|
||||
//
|
||||
// This exercises context switching through multiple processes.
|
||||
void BM_ProcessSwitch(benchmark::State& state) {
|
||||
// Code below assumes there are at least two processes.
|
||||
const int num_processes = state.range(0);
|
||||
ASSERT_GE(num_processes, 2);
|
||||
|
||||
std::vector<pid_t> children;
|
||||
auto child_cleanup = Cleanup([&] {
|
||||
for (const pid_t child : children) {
|
||||
int status;
|
||||
EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds());
|
||||
EXPECT_TRUE(WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
}
|
||||
});
|
||||
|
||||
// Must come after children, as the FDs must be closed before the children
|
||||
// will exit.
|
||||
std::vector<FileDescriptor> read_fds;
|
||||
std::vector<FileDescriptor> write_fds;
|
||||
|
||||
for (int i = 0; i < num_processes; i++) {
|
||||
int fds[2];
|
||||
ASSERT_THAT(pipe(fds), SyscallSucceeds());
|
||||
read_fds.emplace_back(fds[0]);
|
||||
write_fds.emplace_back(fds[1]);
|
||||
}
|
||||
|
||||
// This process is one of the processes in the loop. It will be considered
|
||||
// index 0.
|
||||
for (int i = 1; i < num_processes; i++) {
|
||||
// Read from current pipe index, write to next.
|
||||
const int read_index = i;
|
||||
const int read_fd = read_fds[read_index].get();
|
||||
|
||||
const int write_index = (i + 1) % num_processes;
|
||||
const int write_fd = write_fds[write_index].get();
|
||||
|
||||
// std::vector isn't safe to use from the fork child.
|
||||
FileDescriptor* read_array = read_fds.data();
|
||||
FileDescriptor* write_array = write_fds.data();
|
||||
|
||||
pid_t child = fork();
|
||||
if (!child) {
|
||||
// Close all other FDs.
|
||||
for (int j = 0; j < num_processes; j++) {
|
||||
if (j != read_index) {
|
||||
read_array[j].reset();
|
||||
}
|
||||
if (j != write_index) {
|
||||
write_array[j].reset();
|
||||
}
|
||||
}
|
||||
|
||||
SwitchChild(read_fd, write_fd);
|
||||
_exit(0);
|
||||
}
|
||||
ASSERT_THAT(child, SyscallSucceeds());
|
||||
children.push_back(child);
|
||||
}
|
||||
|
||||
// Read from current pipe index (0), write to next (1).
|
||||
const int read_index = 0;
|
||||
const int read_fd = read_fds[read_index].get();
|
||||
|
||||
const int write_index = 1;
|
||||
const int write_fd = write_fds[write_index].get();
|
||||
|
||||
// Kick start the loop.
|
||||
char buf = 'a';
|
||||
ASSERT_THAT(WriteFd(write_fd, &buf, 1), SyscallSucceedsWithValue(1));
|
||||
|
||||
for (auto _ : state) {
|
||||
ASSERT_THAT(ReadFd(read_fd, &buf, 1), SyscallSucceedsWithValue(1));
|
||||
ASSERT_THAT(WriteFd(write_fd, &buf, 1), SyscallSucceedsWithValue(1));
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_ProcessSwitch)->Range(2, 16)->UseRealTime();
|
||||
|
||||
// Equivalent to BM_ThreadSwitch using threads instead of processes.
|
||||
void BM_ThreadSwitch(benchmark::State& state) {
|
||||
// Code below assumes there are at least two threads.
|
||||
const int num_threads = state.range(0);
|
||||
ASSERT_GE(num_threads, 2);
|
||||
|
||||
// Must come after threads, as the FDs must be closed before the children
|
||||
// will exit.
|
||||
std::vector<std::unique_ptr<ScopedThread>> threads;
|
||||
std::vector<FileDescriptor> read_fds;
|
||||
std::vector<FileDescriptor> write_fds;
|
||||
|
||||
for (int i = 0; i < num_threads; i++) {
|
||||
int fds[2];
|
||||
ASSERT_THAT(pipe(fds), SyscallSucceeds());
|
||||
read_fds.emplace_back(fds[0]);
|
||||
write_fds.emplace_back(fds[1]);
|
||||
}
|
||||
|
||||
// This thread is one of the threads in the loop. It will be considered
|
||||
// index 0.
|
||||
for (int i = 1; i < num_threads; i++) {
|
||||
// Read from current pipe index, write to next.
|
||||
//
|
||||
// Transfer ownership of the FDs to the thread.
|
||||
const int read_index = i;
|
||||
const int read_fd = read_fds[read_index].release();
|
||||
|
||||
const int write_index = (i + 1) % num_threads;
|
||||
const int write_fd = write_fds[write_index].release();
|
||||
|
||||
threads.emplace_back(std::make_unique<ScopedThread>([read_fd, write_fd] {
|
||||
FileDescriptor read(read_fd);
|
||||
FileDescriptor write(write_fd);
|
||||
SwitchChild(read.get(), write.get());
|
||||
}));
|
||||
}
|
||||
|
||||
// Read from current pipe index (0), write to next (1).
|
||||
const int read_index = 0;
|
||||
const int read_fd = read_fds[read_index].get();
|
||||
|
||||
const int write_index = 1;
|
||||
const int write_fd = write_fds[write_index].get();
|
||||
|
||||
// Kick start the loop.
|
||||
char buf = 'a';
|
||||
ASSERT_THAT(WriteFd(write_fd, &buf, 1), SyscallSucceedsWithValue(1));
|
||||
|
||||
for (auto _ : state) {
|
||||
ASSERT_THAT(ReadFd(read_fd, &buf, 1), SyscallSucceedsWithValue(1));
|
||||
ASSERT_THAT(WriteFd(write_fd, &buf, 1), SyscallSucceedsWithValue(1));
|
||||
}
|
||||
|
||||
// The two FDs still owned by this thread are closed, causing the next thread
|
||||
// to exit its loop and close its FDs, and so on until all threads exit.
|
||||
}
|
||||
|
||||
BENCHMARK(BM_ThreadSwitch)->Range(2, 16)->UseRealTime();
|
||||
|
||||
void BM_ThreadStart(benchmark::State& state) {
|
||||
const int num_threads = state.range(0);
|
||||
|
||||
for (auto _ : state) {
|
||||
state.PauseTiming();
|
||||
|
||||
auto barrier = new absl::Barrier(num_threads + 1);
|
||||
std::vector<std::unique_ptr<ScopedThread>> threads;
|
||||
|
||||
state.ResumeTiming();
|
||||
|
||||
for (size_t i = 0; i < num_threads; ++i) {
|
||||
threads.emplace_back(std::make_unique<ScopedThread>([barrier] {
|
||||
if (barrier->Block()) {
|
||||
delete barrier;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
if (barrier->Block()) {
|
||||
delete barrier;
|
||||
}
|
||||
|
||||
state.PauseTiming();
|
||||
|
||||
for (const auto& thread : threads) {
|
||||
thread->Join();
|
||||
}
|
||||
|
||||
state.ResumeTiming();
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_ThreadStart)->Range(1, 2048)->UseRealTime();
|
||||
|
||||
// Benchmark the complete fork + exit + wait.
|
||||
void BM_ProcessLifecycle(benchmark::State& state) {
|
||||
const int num_procs = state.range(0);
|
||||
|
||||
std::vector<pid_t> pids(num_procs);
|
||||
for (auto _ : state) {
|
||||
for (size_t i = 0; i < num_procs; ++i) {
|
||||
int pid = fork();
|
||||
if (pid == 0) {
|
||||
_exit(0);
|
||||
}
|
||||
ASSERT_THAT(pid, SyscallSucceeds());
|
||||
pids[i] = pid;
|
||||
}
|
||||
|
||||
for (const int pid : pids) {
|
||||
ASSERT_THAT(RetryEINTR(waitpid)(pid, nullptr, 0),
|
||||
SyscallSucceedsWithValue(pid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_ProcessLifecycle)->Range(1, 512)->UseRealTime();
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,248 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <linux/futex.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "test/util/logging.h"
|
||||
#include "test/util/thread_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
inline int FutexWait(std::atomic<int32_t>* v, int32_t val) {
|
||||
return syscall(SYS_futex, v, FUTEX_BITSET_MATCH_ANY, nullptr);
|
||||
}
|
||||
|
||||
inline int FutexWaitRelativeTimeout(std::atomic<int32_t>* v, int32_t val,
|
||||
const struct timespec* reltime) {
|
||||
return syscall(SYS_futex, v, FUTEX_WAIT_PRIVATE, reltime);
|
||||
}
|
||||
|
||||
inline int FutexWaitAbsoluteTimeout(std::atomic<int32_t>* v, int32_t val,
|
||||
const struct timespec* abstime) {
|
||||
return syscall(SYS_futex, v, FUTEX_BITSET_MATCH_ANY, abstime);
|
||||
}
|
||||
|
||||
inline int FutexWaitBitsetAbsoluteTimeout(std::atomic<int32_t>* v, int32_t val,
|
||||
int32_t bits,
|
||||
const struct timespec* abstime) {
|
||||
return syscall(SYS_futex, v, FUTEX_WAIT_BITSET_PRIVATE | FUTEX_CLOCK_REALTIME,
|
||||
val, abstime, nullptr, bits);
|
||||
}
|
||||
|
||||
inline int FutexWake(std::atomic<int32_t>* v, int32_t count) {
|
||||
return syscall(SYS_futex, v, FUTEX_WAKE_PRIVATE, count);
|
||||
}
|
||||
|
||||
// This just uses FUTEX_WAKE on an address with nothing waiting, very simple.
|
||||
void BM_FutexWakeNop(benchmark::State& state) {
|
||||
std::atomic<int32_t> v(0);
|
||||
|
||||
for (auto _ : state) {
|
||||
EXPECT_EQ(0, FutexWake(&v, 1));
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_FutexWakeNop);
|
||||
|
||||
// This just uses FUTEX_WAIT on an address whose value has changed, i.e., the
|
||||
// syscall won't wait.
|
||||
void BM_FutexWaitNop(benchmark::State& state) {
|
||||
std::atomic<int32_t> v(0);
|
||||
|
||||
for (auto _ : state) {
|
||||
EXPECT_EQ(-EAGAIN, FutexWait(&v, 1));
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_FutexWaitNop);
|
||||
|
||||
// This uses FUTEX_WAIT with a timeout on an address whose value never
|
||||
// changes, such that it always times out. Timeout overhead can be estimated by
|
||||
// timer overruns for short timeouts.
|
||||
void BM_FutexWaitTimeout(benchmark::State& state) {
|
||||
const int timeout_ns = state.range(0);
|
||||
std::atomic<int32_t> v(0);
|
||||
auto ts = absl::ToTimespec(absl::Nanoseconds(timeout_ns));
|
||||
|
||||
for (auto _ : state) {
|
||||
EXPECT_EQ(-ETIMEDOUT, FutexWaitRelativeTimeout(&v, 0, &ts));
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_FutexWaitTimeout)
|
||||
->Arg(1)
|
||||
->Arg(10)
|
||||
->Arg(100)
|
||||
->Arg(1000)
|
||||
->Arg(10000);
|
||||
|
||||
// This calls FUTEX_WAIT_BITSET with CLOCK_REALTIME.
|
||||
void BM_FutexWaitBitset(benchmark::State& state) {
|
||||
std::atomic<int32_t> v(0);
|
||||
int timeout_ns = state.range(0);
|
||||
auto ts = absl::ToTimespec(absl::Nanoseconds(timeout_ns));
|
||||
for (auto _ : state) {
|
||||
EXPECT_EQ(-ETIMEDOUT, FutexWaitBitsetAbsoluteTimeout(&v, 0, 1, &ts));
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_FutexWaitBitset)->Range(0, 100000);
|
||||
|
||||
int64_t GetCurrentMonotonicTimeNanos() {
|
||||
struct timespec ts;
|
||||
TEST_CHECK(clock_gettime(CLOCK_MONOTONIC, &ts) != -1);
|
||||
return ts.tv_sec * 1000000000ULL + ts.tv_nsec;
|
||||
}
|
||||
|
||||
void SpinNanos(int64_t delay_ns) {
|
||||
if (delay_ns <= 0) {
|
||||
return;
|
||||
}
|
||||
const int64_t end = GetCurrentMonotonicTimeNanos() + delay_ns;
|
||||
while (GetCurrentMonotonicTimeNanos() < end) {
|
||||
// spin
|
||||
}
|
||||
}
|
||||
|
||||
// Each iteration of FutexRoundtripDelayed involves a thread sending a futex
|
||||
// wakeup to another thread, which spins for delay_us and then sends a futex
|
||||
// wakeup back. The time per iteration is 2* (delay_us + kBeforeWakeDelayNs +
|
||||
// futex/scheduling overhead).
|
||||
void BM_FutexRoundtripDelayed(benchmark::State& state) {
|
||||
const int delay_us = state.range(0);
|
||||
|
||||
const int64_t delay_ns = delay_us * 1000;
|
||||
// Spin for an extra kBeforeWakeDelayNs before invoking FUTEX_WAKE to reduce
|
||||
// the probability that the wakeup comes before the wait, preventing the wait
|
||||
// from ever taking effect and causing the benchmark to underestimate the
|
||||
// actual wakeup time.
|
||||
constexpr int64_t kBeforeWakeDelayNs = 500;
|
||||
std::atomic<int32_t> v(0);
|
||||
ScopedThread t([&] {
|
||||
for (int i = 0; i < state.max_iterations; i++) {
|
||||
SpinNanos(delay_ns);
|
||||
while (v.load(std::memory_order_acquire) == 0) {
|
||||
FutexWait(&v, 0);
|
||||
}
|
||||
SpinNanos(kBeforeWakeDelayNs + delay_ns);
|
||||
v.store(0, std::memory_order_release);
|
||||
FutexWake(&v, 1);
|
||||
}
|
||||
});
|
||||
for (auto _ : state) {
|
||||
SpinNanos(kBeforeWakeDelayNs + delay_ns);
|
||||
v.store(1, std::memory_order_release);
|
||||
FutexWake(&v, 1);
|
||||
SpinNanos(delay_ns);
|
||||
while (v.load(std::memory_order_acquire) == 1) {
|
||||
FutexWait(&v, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_FutexRoundtripDelayed)
|
||||
->Arg(0)
|
||||
->Arg(10)
|
||||
->Arg(20)
|
||||
->Arg(50)
|
||||
->Arg(100);
|
||||
|
||||
// FutexLock is a simple, dumb futex based lock implementation.
|
||||
// It will try to acquire the lock by atomically incrementing the
|
||||
// lock word. If it did not increment the lock from 0 to 1, someone
|
||||
// else has the lock, so it will FUTEX_WAIT until it is woken in
|
||||
// the unlock path.
|
||||
class FutexLock {
|
||||
public:
|
||||
FutexLock() : lock_word_(0) {}
|
||||
|
||||
void lock(struct timespec* deadline) {
|
||||
int32_t val;
|
||||
while ((val = lock_word_.fetch_add(1, std::memory_order_acquire) + 1) !=
|
||||
1) {
|
||||
// If we didn't get the lock by incrementing from 0 to 1,
|
||||
// do a FUTEX_WAIT with the desired current value set to
|
||||
// val. If val is no longer what the atomic increment returned,
|
||||
// someone might have set it to 0 so we can try to acquire
|
||||
// again.
|
||||
int ret = FutexWaitAbsoluteTimeout(&lock_word_, val, deadline);
|
||||
if (ret == 0 || ret == -EWOULDBLOCK || ret == -EINTR) {
|
||||
continue;
|
||||
} else {
|
||||
FAIL() << "unexpected FUTEX_WAIT return: " << ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
// Store 0 into the lock word and wake one waiter. We intentionally
|
||||
// ignore the return value of the FUTEX_WAKE here, since there may be
|
||||
// no waiters to wake anyway.
|
||||
lock_word_.store(0, std::memory_order_release);
|
||||
(void)FutexWake(&lock_word_, 1);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<int32_t> lock_word_;
|
||||
};
|
||||
|
||||
FutexLock* test_lock; // Used below.
|
||||
|
||||
void FutexContend(benchmark::State& state, int thread_index,
|
||||
struct timespec* deadline) {
|
||||
int counter = 0;
|
||||
if (thread_index == 0) {
|
||||
test_lock = new FutexLock();
|
||||
}
|
||||
for (auto _ : state) {
|
||||
test_lock->lock(deadline);
|
||||
counter++;
|
||||
test_lock->unlock();
|
||||
}
|
||||
if (thread_index == 0) {
|
||||
delete test_lock;
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations());
|
||||
}
|
||||
|
||||
void BM_FutexContend(benchmark::State& state) {
|
||||
FutexContend(state, state.thread_index, nullptr);
|
||||
}
|
||||
|
||||
BENCHMARK(BM_FutexContend)->ThreadRange(1, 1024)->UseRealTime();
|
||||
|
||||
void BM_FutexDeadlineContend(benchmark::State& state) {
|
||||
auto deadline = absl::ToTimespec(absl::Now() + absl::Minutes(10));
|
||||
FutexContend(state, state.thread_index, &deadline);
|
||||
}
|
||||
|
||||
BENCHMARK(BM_FutexDeadlineContend)->ThreadRange(1, 1024)->UseRealTime();
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/fs_util.h"
|
||||
#include "test/util/temp_path.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
#ifndef SYS_getdents64
|
||||
#if defined(__x86_64__)
|
||||
#define SYS_getdents64 217
|
||||
#elif defined(__aarch64__)
|
||||
#define SYS_getdents64 217
|
||||
#else
|
||||
#error "Unknown architecture"
|
||||
#endif
|
||||
#endif // SYS_getdents64
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kBufferSize = 16384;
|
||||
|
||||
PosixErrorOr<TempPath> CreateDirectory(int count,
|
||||
std::vector<std::string>* files) {
|
||||
ASSIGN_OR_RETURN_ERRNO(TempPath dir, TempPath::CreateDir());
|
||||
|
||||
ASSIGN_OR_RETURN_ERRNO(FileDescriptor dfd,
|
||||
Open(dir.path(), O_RDONLY | O_DIRECTORY));
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
auto file = NewTempRelPath();
|
||||
auto res = MknodAt(dfd, file, S_IFREG | 0644, 0);
|
||||
RETURN_IF_ERRNO(res);
|
||||
files->push_back(file);
|
||||
}
|
||||
|
||||
return std::move(dir);
|
||||
}
|
||||
|
||||
PosixError CleanupDirectory(const TempPath& dir,
|
||||
std::vector<std::string>* files) {
|
||||
ASSIGN_OR_RETURN_ERRNO(FileDescriptor dfd,
|
||||
Open(dir.path(), O_RDONLY | O_DIRECTORY));
|
||||
|
||||
for (auto it = files->begin(); it != files->end(); ++it) {
|
||||
auto res = UnlinkAt(dfd, *it, 0);
|
||||
RETURN_IF_ERRNO(res);
|
||||
}
|
||||
return NoError();
|
||||
}
|
||||
|
||||
// Creates a directory containing `files` files, and reads all the directory
|
||||
// entries from the directory using a single FD.
|
||||
void BM_GetdentsSameFD(benchmark::State& state) {
|
||||
// Create directory with given files.
|
||||
const int count = state.range(0);
|
||||
|
||||
// Keep a vector of all of the file TempPaths that is destroyed before dir.
|
||||
//
|
||||
// Normally, we'd simply allow dir to recursively clean up the contained
|
||||
// files, but that recursive cleanup uses getdents, which may be very slow in
|
||||
// extreme benchmarks.
|
||||
TempPath dir;
|
||||
std::vector<std::string> files;
|
||||
dir = ASSERT_NO_ERRNO_AND_VALUE(CreateDirectory(count, &files));
|
||||
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY | O_DIRECTORY));
|
||||
char buffer[kBufferSize];
|
||||
|
||||
// We read all directory entries on each iteration, but report this as a
|
||||
// "batch" iteration so that reported times are per file.
|
||||
while (state.KeepRunningBatch(count)) {
|
||||
ASSERT_THAT(lseek(fd.get(), 0, SEEK_SET), SyscallSucceeds());
|
||||
|
||||
int ret;
|
||||
do {
|
||||
ASSERT_THAT(ret = syscall(SYS_getdents64, fd.get(), buffer, kBufferSize),
|
||||
SyscallSucceeds());
|
||||
} while (ret > 0);
|
||||
}
|
||||
|
||||
ASSERT_NO_ERRNO(CleanupDirectory(dir, &files));
|
||||
|
||||
state.SetItemsProcessed(state.iterations());
|
||||
}
|
||||
|
||||
BENCHMARK(BM_GetdentsSameFD)->Range(1, 1 << 16)->UseRealTime();
|
||||
|
||||
// Creates a directory containing `files` files, and reads all the directory
|
||||
// entries from the directory using a new FD each time.
|
||||
void BM_GetdentsNewFD(benchmark::State& state) {
|
||||
// Create directory with given files.
|
||||
const int count = state.range(0);
|
||||
|
||||
// Keep a vector of all of the file TempPaths that is destroyed before dir.
|
||||
//
|
||||
// Normally, we'd simply allow dir to recursively clean up the contained
|
||||
// files, but that recursive cleanup uses getdents, which may be very slow in
|
||||
// extreme benchmarks.
|
||||
TempPath dir;
|
||||
std::vector<std::string> files;
|
||||
dir = ASSERT_NO_ERRNO_AND_VALUE(CreateDirectory(count, &files));
|
||||
char buffer[kBufferSize];
|
||||
|
||||
// We read all directory entries on each iteration, but report this as a
|
||||
// "batch" iteration so that reported times are per file.
|
||||
while (state.KeepRunningBatch(count)) {
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY | O_DIRECTORY));
|
||||
|
||||
int ret;
|
||||
do {
|
||||
ASSERT_THAT(ret = syscall(SYS_getdents64, fd.get(), buffer, kBufferSize),
|
||||
SyscallSucceeds());
|
||||
} while (ret > 0);
|
||||
}
|
||||
|
||||
ASSERT_NO_ERRNO(CleanupDirectory(dir, &files));
|
||||
|
||||
state.SetItemsProcessed(state.iterations());
|
||||
}
|
||||
|
||||
BENCHMARK(BM_GetdentsNewFD)->Range(1, 1 << 16)->UseRealTime();
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
void BM_Getpid(benchmark::State& state) {
|
||||
for (auto _ : state) {
|
||||
syscall(SYS_getpid);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_Getpid);
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
void BM_Gettid(benchmark::State& state) {
|
||||
for (auto _ : state) {
|
||||
syscall(SYS_gettid);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_Gettid)->ThreadRange(1, 4000)->UseRealTime();
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,163 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "test/util/logging.h"
|
||||
#include "test/util/memory_util.h"
|
||||
#include "test/util/posix_error.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
// Conservative value for /proc/sys/vm/max_map_count, which limits the number of
|
||||
// VMAs, minus a safety margin for VMAs that already exist for the test binary.
|
||||
// The default value for max_map_count is
|
||||
// include/linux/mm.h:DEFAULT_MAX_MAP_COUNT = 65530.
|
||||
constexpr size_t kMaxVMAs = 64001;
|
||||
|
||||
// Map then unmap pages without touching them.
|
||||
void BM_MapUnmap(benchmark::State& state) {
|
||||
// Number of pages to map.
|
||||
const int pages = state.range(0);
|
||||
|
||||
while (state.KeepRunning()) {
|
||||
void* addr = mmap(0, pages * kPageSize, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
TEST_CHECK_MSG(addr != MAP_FAILED, "mmap failed");
|
||||
|
||||
int ret = munmap(addr, pages * kPageSize);
|
||||
TEST_CHECK_MSG(ret == 0, "munmap failed");
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_MapUnmap)->Range(1, 1 << 17)->UseRealTime();
|
||||
|
||||
// Map, touch, then unmap pages.
|
||||
void BM_MapTouchUnmap(benchmark::State& state) {
|
||||
// Number of pages to map.
|
||||
const int pages = state.range(0);
|
||||
|
||||
while (state.KeepRunning()) {
|
||||
void* addr = mmap(0, pages * kPageSize, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
TEST_CHECK_MSG(addr != MAP_FAILED, "mmap failed");
|
||||
|
||||
char* c = reinterpret_cast<char*>(addr);
|
||||
char* end = c + pages * kPageSize;
|
||||
while (c < end) {
|
||||
*c = 42;
|
||||
c += kPageSize;
|
||||
}
|
||||
|
||||
int ret = munmap(addr, pages * kPageSize);
|
||||
TEST_CHECK_MSG(ret == 0, "munmap failed");
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_MapTouchUnmap)->Range(1, 1 << 17)->UseRealTime();
|
||||
|
||||
// Map and touch many pages, unmapping all at once.
|
||||
//
|
||||
// NOTE(b/111429208): This is a regression test to ensure performant mapping and
|
||||
// allocation even with tons of mappings.
|
||||
void BM_MapTouchMany(benchmark::State& state) {
|
||||
// Number of pages to map.
|
||||
const int page_count = state.range(0);
|
||||
|
||||
while (state.KeepRunning()) {
|
||||
std::vector<void*> pages;
|
||||
|
||||
for (int i = 0; i < page_count; i++) {
|
||||
void* addr = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
TEST_CHECK_MSG(addr != MAP_FAILED, "mmap failed");
|
||||
|
||||
char* c = reinterpret_cast<char*>(addr);
|
||||
*c = 42;
|
||||
|
||||
pages.push_back(addr);
|
||||
}
|
||||
|
||||
for (void* addr : pages) {
|
||||
int ret = munmap(addr, kPageSize);
|
||||
TEST_CHECK_MSG(ret == 0, "munmap failed");
|
||||
}
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(kPageSize * page_count * state.iterations());
|
||||
}
|
||||
|
||||
BENCHMARK(BM_MapTouchMany)->Range(1, 1 << 12)->UseRealTime();
|
||||
|
||||
void BM_PageFault(benchmark::State& state) {
|
||||
// Map the region in which we will take page faults. To ensure that each page
|
||||
// fault maps only a single page, each page we touch must correspond to a
|
||||
// distinct VMA. Thus we need a 1-page gap between each 1-page VMA. However,
|
||||
// each gap consists of a PROT_NONE VMA, instead of an unmapped hole, so that
|
||||
// if there are background threads running, they can't inadvertently creating
|
||||
// mappings in our gaps that are unmapped when the test ends.
|
||||
size_t test_pages = kMaxVMAs;
|
||||
// Ensure that test_pages is odd, since we want the test region to both
|
||||
// begin and end with a mapped page.
|
||||
if (test_pages % 2 == 0) {
|
||||
test_pages--;
|
||||
}
|
||||
const size_t test_region_bytes = test_pages * kPageSize;
|
||||
// Use MAP_SHARED here because madvise(MADV_DONTNEED) on private mappings on
|
||||
// gVisor won't force future sentry page faults (by design). Use MAP_POPULATE
|
||||
// so that Linux pre-allocates the shmem file used to back the mapping.
|
||||
Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
MmapAnon(test_region_bytes, PROT_READ, MAP_SHARED | MAP_POPULATE));
|
||||
for (size_t i = 0; i < test_pages / 2; i++) {
|
||||
ASSERT_THAT(
|
||||
mprotect(reinterpret_cast<void*>(m.addr() + ((2 * i + 1) * kPageSize)),
|
||||
kPageSize, PROT_NONE),
|
||||
SyscallSucceeds());
|
||||
}
|
||||
|
||||
const size_t mapped_pages = test_pages / 2 + 1;
|
||||
// "Start" at the end of the mapped region to force the mapped region to be
|
||||
// reset, since we mapped it with MAP_POPULATE.
|
||||
size_t cur_page = mapped_pages;
|
||||
for (auto _ : state) {
|
||||
if (cur_page >= mapped_pages) {
|
||||
// We've reached the end of our mapped region and have to reset it to
|
||||
// incur page faults again.
|
||||
state.PauseTiming();
|
||||
ASSERT_THAT(madvise(m.ptr(), test_region_bytes, MADV_DONTNEED),
|
||||
SyscallSucceeds());
|
||||
cur_page = 0;
|
||||
state.ResumeTiming();
|
||||
}
|
||||
const uintptr_t addr = m.addr() + (2 * cur_page * kPageSize);
|
||||
const char c = *reinterpret_cast<volatile char*>(addr);
|
||||
benchmark::DoNotOptimize(c);
|
||||
cur_page++;
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_PageFault)->UseRealTime();
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "test/util/fs_util.h"
|
||||
#include "test/util/logging.h"
|
||||
#include "test/util/temp_path.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
void BM_Open(benchmark::State& state) {
|
||||
const int size = state.range(0);
|
||||
std::vector<TempPath> cache;
|
||||
for (int i = 0; i < size; i++) {
|
||||
auto path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
|
||||
cache.emplace_back(std::move(path));
|
||||
}
|
||||
|
||||
unsigned int seed = 1;
|
||||
for (auto _ : state) {
|
||||
const int chosen = rand_r(&seed) % size;
|
||||
int fd = open(cache[chosen].path().c_str(), O_RDONLY);
|
||||
TEST_CHECK(fd != -1);
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_Open)->Range(1, 128)->UseRealTime();
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "test/util/logging.h"
|
||||
#include "test/util/test_util.h"
|
||||
#include "test/util/thread_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
void BM_Pipe(benchmark::State& state) {
|
||||
int fds[2];
|
||||
TEST_CHECK(pipe(fds) == 0);
|
||||
|
||||
const int size = state.range(0);
|
||||
std::vector<char> wbuf(size);
|
||||
std::vector<char> rbuf(size);
|
||||
RandomizeBuffer(wbuf.data(), size);
|
||||
|
||||
ScopedThread t([&] {
|
||||
auto const fd = fds[1];
|
||||
for (int i = 0; i < state.max_iterations; i++) {
|
||||
TEST_CHECK(WriteFd(fd, wbuf.data(), wbuf.size()) == size);
|
||||
}
|
||||
});
|
||||
|
||||
for (auto _ : state) {
|
||||
TEST_CHECK(ReadFd(fds[0], rbuf.data(), rbuf.size()) == size);
|
||||
}
|
||||
|
||||
t.Join();
|
||||
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
|
||||
state.SetBytesProcessed(static_cast<int64_t>(size) *
|
||||
static_cast<int64_t>(state.iterations()));
|
||||
}
|
||||
|
||||
BENCHMARK(BM_Pipe)->Range(1, 1 << 20)->UseRealTime();
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/logging.h"
|
||||
#include "test/util/temp_path.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
// Create a 1GB file that will be read from at random positions. This should
|
||||
// invalid any performance gains from caching.
|
||||
const uint64_t kFileSize = 1ULL << 30;
|
||||
|
||||
// How many bytes to write at once to initialize the file used to read from.
|
||||
const uint32_t kWriteSize = 65536;
|
||||
|
||||
// Largest benchmarked read unit.
|
||||
const uint32_t kMaxRead = 1UL << 26;
|
||||
|
||||
TempPath CreateFile(uint64_t file_size) {
|
||||
auto path = TempPath::CreateFile().ValueOrDie();
|
||||
FileDescriptor fd = Open(path.path(), O_WRONLY).ValueOrDie();
|
||||
|
||||
// Try to minimize syscalls by using maximum size writev() requests.
|
||||
std::vector<char> buffer(kWriteSize);
|
||||
RandomizeBuffer(buffer.data(), buffer.size());
|
||||
const std::vector<std::vector<struct iovec>> iovecs_list =
|
||||
GenerateIovecs(file_size, buffer.data(), buffer.size());
|
||||
for (const auto& iovecs : iovecs_list) {
|
||||
TEST_CHECK(writev(fd.get(), iovecs.data(), iovecs.size()) >= 0);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
// Global test state, initialized once per process lifetime.
|
||||
struct GlobalState {
|
||||
const TempPath tmpfile;
|
||||
explicit GlobalState(TempPath tfile) : tmpfile(std::move(tfile)) {}
|
||||
};
|
||||
|
||||
GlobalState& GetGlobalState() {
|
||||
// This gets created only once throughout the lifetime of the process.
|
||||
// Use a dynamically allocated object (that is never deleted) to avoid order
|
||||
// of destruction of static storage variables issues.
|
||||
static GlobalState* const state =
|
||||
// The actual file size is the maximum random seek range (kFileSize) + the
|
||||
// maximum read size so we can read that number of bytes at the end of the
|
||||
// file.
|
||||
new GlobalState(CreateFile(kFileSize + kMaxRead));
|
||||
return *state;
|
||||
}
|
||||
|
||||
void BM_RandRead(benchmark::State& state) {
|
||||
const int size = state.range(0);
|
||||
|
||||
GlobalState& global_state = GetGlobalState();
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open(global_state.tmpfile.path(), O_RDONLY));
|
||||
std::vector<char> buf(size);
|
||||
|
||||
unsigned int seed = 1;
|
||||
for (auto _ : state) {
|
||||
TEST_CHECK(PreadFd(fd.get(), buf.data(), buf.size(),
|
||||
rand_r(&seed) % kFileSize) == size);
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(static_cast<int64_t>(size) *
|
||||
static_cast<int64_t>(state.iterations()));
|
||||
}
|
||||
|
||||
BENCHMARK(BM_RandRead)->Range(1, kMaxRead)->UseRealTime();
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "test/util/fs_util.h"
|
||||
#include "test/util/logging.h"
|
||||
#include "test/util/temp_path.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
void BM_Read(benchmark::State& state) {
|
||||
const int size = state.range(0);
|
||||
const std::string contents(size, 0);
|
||||
auto path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
|
||||
GetAbsoluteTestTmpdir(), contents, TempPath::kDefaultFileMode));
|
||||
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_RDONLY));
|
||||
|
||||
std::vector<char> buf(size);
|
||||
for (auto _ : state) {
|
||||
TEST_CHECK(PreadFd(fd.get(), buf.data(), buf.size(), 0) == size);
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(static_cast<int64_t>(size) *
|
||||
static_cast<int64_t>(state.iterations()));
|
||||
}
|
||||
|
||||
BENCHMARK(BM_Read)->Range(1, 1 << 26)->UseRealTime();
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <sched.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
void BM_Sched_yield(benchmark::State& state) {
|
||||
for (auto ignored : state) {
|
||||
TEST_CHECK(sched_yield() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_Sched_yield)->ThreadRange(1, 2000)->UseRealTime();
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,372 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <poll.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/synchronization/notification.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "test/syscalls/linux/socket_test_util.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/logging.h"
|
||||
#include "test/util/posix_error.h"
|
||||
#include "test/util/test_util.h"
|
||||
#include "test/util/thread_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr ssize_t kMessageSize = 1024;
|
||||
|
||||
class Message {
|
||||
public:
|
||||
explicit Message(int byte = 0) : Message(byte, kMessageSize, 0) {}
|
||||
|
||||
explicit Message(int byte, int sz) : Message(byte, sz, 0) {}
|
||||
|
||||
explicit Message(int byte, int sz, int cmsg_sz)
|
||||
: buffer_(sz, byte), cmsg_buffer_(cmsg_sz, 0) {
|
||||
iov_.iov_base = buffer_.data();
|
||||
iov_.iov_len = sz;
|
||||
hdr_.msg_iov = &iov_;
|
||||
hdr_.msg_iovlen = 1;
|
||||
hdr_.msg_control = cmsg_buffer_.data();
|
||||
hdr_.msg_controllen = cmsg_sz;
|
||||
}
|
||||
|
||||
struct msghdr* header() {
|
||||
return &hdr_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<char> buffer_;
|
||||
std::vector<char> cmsg_buffer_;
|
||||
struct iovec iov_ = {};
|
||||
struct msghdr hdr_ = {};
|
||||
};
|
||||
|
||||
void BM_Recvmsg(benchmark::State& state) {
|
||||
int sockets[2];
|
||||
TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0);
|
||||
FileDescriptor send_socket(sockets[0]), recv_socket(sockets[1]);
|
||||
absl::Notification notification;
|
||||
Message send_msg('a'), recv_msg;
|
||||
|
||||
ScopedThread t([&send_msg, &send_socket, ¬ification] {
|
||||
while (!notification.HasBeenNotified()) {
|
||||
sendmsg(send_socket.get(), send_msg.header(), 0);
|
||||
}
|
||||
});
|
||||
|
||||
int64_t bytes_received = 0;
|
||||
for (auto ignored : state) {
|
||||
int n = recvmsg(recv_socket.get(), recv_msg.header(), 0);
|
||||
TEST_CHECK(n > 0);
|
||||
bytes_received += n;
|
||||
}
|
||||
|
||||
notification.Notify();
|
||||
recv_socket.reset();
|
||||
|
||||
state.SetBytesProcessed(bytes_received);
|
||||
}
|
||||
|
||||
BENCHMARK(BM_Recvmsg)->UseRealTime();
|
||||
|
||||
void BM_Sendmsg(benchmark::State& state) {
|
||||
int sockets[2];
|
||||
TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0);
|
||||
FileDescriptor send_socket(sockets[0]), recv_socket(sockets[1]);
|
||||
absl::Notification notification;
|
||||
Message send_msg('a'), recv_msg;
|
||||
|
||||
ScopedThread t([&recv_msg, &recv_socket, ¬ification] {
|
||||
while (!notification.HasBeenNotified()) {
|
||||
recvmsg(recv_socket.get(), recv_msg.header(), 0);
|
||||
}
|
||||
});
|
||||
|
||||
int64_t bytes_sent = 0;
|
||||
for (auto ignored : state) {
|
||||
int n = sendmsg(send_socket.get(), send_msg.header(), 0);
|
||||
TEST_CHECK(n > 0);
|
||||
bytes_sent += n;
|
||||
}
|
||||
|
||||
notification.Notify();
|
||||
send_socket.reset();
|
||||
|
||||
state.SetBytesProcessed(bytes_sent);
|
||||
}
|
||||
|
||||
BENCHMARK(BM_Sendmsg)->UseRealTime();
|
||||
|
||||
void BM_Recvfrom(benchmark::State& state) {
|
||||
int sockets[2];
|
||||
TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0);
|
||||
FileDescriptor send_socket(sockets[0]), recv_socket(sockets[1]);
|
||||
absl::Notification notification;
|
||||
char send_buffer[kMessageSize], recv_buffer[kMessageSize];
|
||||
|
||||
ScopedThread t([&send_socket, &send_buffer, ¬ification] {
|
||||
while (!notification.HasBeenNotified()) {
|
||||
sendto(send_socket.get(), send_buffer, kMessageSize, 0, nullptr, 0);
|
||||
}
|
||||
});
|
||||
|
||||
int bytes_received = 0;
|
||||
for (auto ignored : state) {
|
||||
int n = recvfrom(recv_socket.get(), recv_buffer, kMessageSize, 0, nullptr,
|
||||
nullptr);
|
||||
TEST_CHECK(n > 0);
|
||||
bytes_received += n;
|
||||
}
|
||||
|
||||
notification.Notify();
|
||||
recv_socket.reset();
|
||||
|
||||
state.SetBytesProcessed(bytes_received);
|
||||
}
|
||||
|
||||
BENCHMARK(BM_Recvfrom)->UseRealTime();
|
||||
|
||||
void BM_Sendto(benchmark::State& state) {
|
||||
int sockets[2];
|
||||
TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0);
|
||||
FileDescriptor send_socket(sockets[0]), recv_socket(sockets[1]);
|
||||
absl::Notification notification;
|
||||
char send_buffer[kMessageSize], recv_buffer[kMessageSize];
|
||||
|
||||
ScopedThread t([&recv_socket, &recv_buffer, ¬ification] {
|
||||
while (!notification.HasBeenNotified()) {
|
||||
recvfrom(recv_socket.get(), recv_buffer, kMessageSize, 0, nullptr,
|
||||
nullptr);
|
||||
}
|
||||
});
|
||||
|
||||
int64_t bytes_sent = 0;
|
||||
for (auto ignored : state) {
|
||||
int n = sendto(send_socket.get(), send_buffer, kMessageSize, 0, nullptr, 0);
|
||||
TEST_CHECK(n > 0);
|
||||
bytes_sent += n;
|
||||
}
|
||||
|
||||
notification.Notify();
|
||||
send_socket.reset();
|
||||
|
||||
state.SetBytesProcessed(bytes_sent);
|
||||
}
|
||||
|
||||
BENCHMARK(BM_Sendto)->UseRealTime();
|
||||
|
||||
PosixErrorOr<sockaddr_storage> InetLoopbackAddr(int family) {
|
||||
struct sockaddr_storage addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.ss_family = family;
|
||||
switch (family) {
|
||||
case AF_INET:
|
||||
reinterpret_cast<struct sockaddr_in*>(&addr)->sin_addr.s_addr =
|
||||
htonl(INADDR_LOOPBACK);
|
||||
break;
|
||||
case AF_INET6:
|
||||
reinterpret_cast<struct sockaddr_in6*>(&addr)->sin6_addr =
|
||||
in6addr_loopback;
|
||||
break;
|
||||
default:
|
||||
return PosixError(EINVAL,
|
||||
absl::StrCat("unknown socket family: ", family));
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
// BM_RecvmsgWithControlBuf measures the performance of recvmsg when we allocate
|
||||
// space for control messages. Note that we do not expect to receive any.
|
||||
void BM_RecvmsgWithControlBuf(benchmark::State& state) {
|
||||
auto listen_socket =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP));
|
||||
|
||||
// Initialize address to the loopback one.
|
||||
sockaddr_storage addr = ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(AF_INET6));
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
|
||||
// Bind to some port then start listening.
|
||||
ASSERT_THAT(bind(listen_socket.get(),
|
||||
reinterpret_cast<struct sockaddr*>(&addr), addrlen),
|
||||
SyscallSucceeds());
|
||||
|
||||
ASSERT_THAT(listen(listen_socket.get(), SOMAXCONN), SyscallSucceeds());
|
||||
|
||||
// Get the address we're listening on, then connect to it. We need to do this
|
||||
// because we're allowing the stack to pick a port for us.
|
||||
ASSERT_THAT(getsockname(listen_socket.get(),
|
||||
reinterpret_cast<struct sockaddr*>(&addr), &addrlen),
|
||||
SyscallSucceeds());
|
||||
|
||||
auto send_socket =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP));
|
||||
|
||||
ASSERT_THAT(
|
||||
RetryEINTR(connect)(send_socket.get(),
|
||||
reinterpret_cast<struct sockaddr*>(&addr), addrlen),
|
||||
SyscallSucceeds());
|
||||
|
||||
// Accept the connection.
|
||||
auto recv_socket =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_socket.get(), nullptr, nullptr));
|
||||
|
||||
absl::Notification notification;
|
||||
Message send_msg('a');
|
||||
// Create a msghdr with a buffer allocated for control messages.
|
||||
Message recv_msg(0, kMessageSize, /*cmsg_sz=*/24);
|
||||
|
||||
ScopedThread t([&send_msg, &send_socket, ¬ification] {
|
||||
while (!notification.HasBeenNotified()) {
|
||||
sendmsg(send_socket.get(), send_msg.header(), 0);
|
||||
}
|
||||
});
|
||||
|
||||
int64_t bytes_received = 0;
|
||||
for (auto ignored : state) {
|
||||
int n = recvmsg(recv_socket.get(), recv_msg.header(), 0);
|
||||
TEST_CHECK(n > 0);
|
||||
bytes_received += n;
|
||||
}
|
||||
|
||||
notification.Notify();
|
||||
recv_socket.reset();
|
||||
|
||||
state.SetBytesProcessed(bytes_received);
|
||||
}
|
||||
|
||||
BENCHMARK(BM_RecvmsgWithControlBuf)->UseRealTime();
|
||||
|
||||
// BM_SendmsgTCP measures the sendmsg throughput with varying payload sizes.
|
||||
//
|
||||
// state.Args[0] indicates whether the underlying socket should be blocking or
|
||||
// non-blocking w/ 0 indicating non-blocking and 1 to indicate blocking.
|
||||
// state.Args[1] is the size of the payload to be used per sendmsg call.
|
||||
void BM_SendmsgTCP(benchmark::State& state) {
|
||||
auto listen_socket =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
|
||||
|
||||
// Initialize address to the loopback one.
|
||||
sockaddr_storage addr = ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(AF_INET));
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
|
||||
// Bind to some port then start listening.
|
||||
ASSERT_THAT(bind(listen_socket.get(),
|
||||
reinterpret_cast<struct sockaddr*>(&addr), addrlen),
|
||||
SyscallSucceeds());
|
||||
|
||||
ASSERT_THAT(listen(listen_socket.get(), SOMAXCONN), SyscallSucceeds());
|
||||
|
||||
// Get the address we're listening on, then connect to it. We need to do this
|
||||
// because we're allowing the stack to pick a port for us.
|
||||
ASSERT_THAT(getsockname(listen_socket.get(),
|
||||
reinterpret_cast<struct sockaddr*>(&addr), &addrlen),
|
||||
SyscallSucceeds());
|
||||
|
||||
auto send_socket =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
|
||||
|
||||
ASSERT_THAT(
|
||||
RetryEINTR(connect)(send_socket.get(),
|
||||
reinterpret_cast<struct sockaddr*>(&addr), addrlen),
|
||||
SyscallSucceeds());
|
||||
|
||||
// Accept the connection.
|
||||
auto recv_socket =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_socket.get(), nullptr, nullptr));
|
||||
|
||||
// Check if we want to run the test w/ a blocking send socket
|
||||
// or non-blocking.
|
||||
const int blocking = state.range(0);
|
||||
if (!blocking) {
|
||||
// Set the send FD to O_NONBLOCK.
|
||||
int opts;
|
||||
ASSERT_THAT(opts = fcntl(send_socket.get(), F_GETFL), SyscallSucceeds());
|
||||
opts |= O_NONBLOCK;
|
||||
ASSERT_THAT(fcntl(send_socket.get(), F_SETFL, opts), SyscallSucceeds());
|
||||
}
|
||||
|
||||
absl::Notification notification;
|
||||
|
||||
// Get the buffer size we should use for this iteration of the test.
|
||||
const int buf_size = state.range(1);
|
||||
Message send_msg('a', buf_size), recv_msg(0, buf_size);
|
||||
|
||||
ScopedThread t([&recv_msg, &recv_socket, ¬ification] {
|
||||
while (!notification.HasBeenNotified()) {
|
||||
TEST_CHECK(recvmsg(recv_socket.get(), recv_msg.header(), 0) >= 0);
|
||||
}
|
||||
});
|
||||
|
||||
int64_t bytes_sent = 0;
|
||||
int ncalls = 0;
|
||||
for (auto ignored : state) {
|
||||
int sent = 0;
|
||||
while (true) {
|
||||
struct msghdr hdr = {};
|
||||
struct iovec iov = {};
|
||||
struct msghdr* snd_header = send_msg.header();
|
||||
iov.iov_base = static_cast<char*>(snd_header->msg_iov->iov_base) + sent;
|
||||
iov.iov_len = snd_header->msg_iov->iov_len - sent;
|
||||
hdr.msg_iov = &iov;
|
||||
hdr.msg_iovlen = 1;
|
||||
int n = RetryEINTR(sendmsg)(send_socket.get(), &hdr, 0);
|
||||
ncalls++;
|
||||
if (n > 0) {
|
||||
sent += n;
|
||||
if (sent == buf_size) {
|
||||
break;
|
||||
}
|
||||
// n can be > 0 but less than requested size. In which case we don't
|
||||
// poll.
|
||||
continue;
|
||||
}
|
||||
// Poll the fd for it to become writable.
|
||||
struct pollfd poll_fd = {send_socket.get(), POLL_OUT, 0};
|
||||
EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10),
|
||||
SyscallSucceedsWithValue(0));
|
||||
}
|
||||
bytes_sent += static_cast<int64_t>(sent);
|
||||
}
|
||||
|
||||
notification.Notify();
|
||||
send_socket.reset();
|
||||
state.SetBytesProcessed(bytes_sent);
|
||||
}
|
||||
|
||||
void Args(benchmark::internal::Benchmark* benchmark) {
|
||||
for (int blocking = 0; blocking < 2; blocking++) {
|
||||
for (int buf_size = 1024; buf_size <= 256 << 20; buf_size *= 2) {
|
||||
benchmark->Args({blocking, buf_size});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_SendmsgTCP)->Apply(&Args)->UseRealTime();
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2020 The gVisor 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
|
||||
//
|
||||
// http://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 <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "test/util/logging.h"
|
||||
#include "test/util/temp_path.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
// The maximum file size of the test file, when writes get beyond this point
|
||||
// they wrap around. This should be large enough to blow away caches.
|
||||
const uint64_t kMaxFile = 1 << 30;
|
||||
|
||||
// Perform writes of various sizes sequentially to one file. Wraps around if it
|
||||
// goes above a certain maximum file size.
|
||||
void BM_SeqWrite(benchmark::State& state) {
|
||||
auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
|
||||
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_WRONLY));
|
||||
|
||||
const int size = state.range(0);
|
||||
std::vector<char> buf(size);
|
||||
RandomizeBuffer(buf.data(), buf.size());
|
||||
|
||||
// Start writes at offset 0.
|
||||
uint64_t offset = 0;
|
||||
for (auto _ : state) {
|
||||
TEST_CHECK(PwriteFd(fd.get(), buf.data(), buf.size(), offset) ==
|
||||
buf.size());
|
||||
offset += buf.size();
|
||||
// Wrap around if going above the maximum file size.
|
||||
if (offset >= kMaxFile) {
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(static_cast<int64_t>(size) *
|
||||
static_cast<int64_t>(state.iterations()));
|
||||
}
|
||||
|
||||
BENCHMARK(BM_SeqWrite)->Range(1, 1 << 26)->UseRealTime();
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user