diff --git a/WORKSPACE b/WORKSPACE index 11d1a097d2..50c7a7a65b 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -61,6 +61,16 @@ http_archive( ], ) +# GoogleBenchmark +# https://github.com/google/benchmark +http_archive( + name = "com_github_google_benchmark", + strip_prefix = "benchmark-1.9.1", + urls = [ + "https://github.com/google/benchmark/archive/refs/tags/v1.9.1.zip", + ], +) + # C++ gRPC support. # https://github.com/grpc/grpc http_archive( diff --git a/cc/containers/hello_world_enclave_app/BUILD b/cc/containers/hello_world_enclave_app/BUILD index 591067b3bc..c643e6b046 100644 --- a/cc/containers/hello_world_enclave_app/BUILD +++ b/cc/containers/hello_world_enclave_app/BUILD @@ -80,6 +80,27 @@ cc_test( ], ) +cc_binary( + name = "standalone_benchmark", + testonly = True, + srcs = ["standalone_benchmark.cc"], + deps = [ + ":app_service", + "//cc/attestation/verification:insecure_attestation_verifier", + "//cc/client", + "//cc/client:session_client", + "//cc/containers/sdk/standalone:oak_standalone", + "//cc/oak_session:client_session", + "//cc/transport:grpc_streaming_transport", + "//cc/transport:grpc_sync_session_client_transport", + "//oak_containers/examples/hello_world/proto:hello_world_cc_grpc", + "@com_github_google_benchmark//:benchmark", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:status_matchers", + ], +) + pkg_tar( name = "tar", srcs = [":main"], diff --git a/cc/containers/hello_world_enclave_app/README.md b/cc/containers/hello_world_enclave_app/README.md index 681e050cd6..8d0659a58d 100644 --- a/cc/containers/hello_world_enclave_app/README.md +++ b/cc/containers/hello_world_enclave_app/README.md @@ -10,3 +10,9 @@ C++ implementation of the enclave app part (inside the TEE) of the Oak Containers Hello World example application. This implementation parallels the Rust implementation in [`/oak_containers/examples/hello_world/enclave_app`](../../../oak_containers/examples/hello_world/enclave_app). + +## Benchmarks + +To get some basic benchmark statistics: + +bazel run cc/containers/hello_world_enclave_app:standalone_benchmark diff --git a/cc/containers/hello_world_enclave_app/app_service.cc b/cc/containers/hello_world_enclave_app/app_service.cc index 472067820e..8ca94a8520 100644 --- a/cc/containers/hello_world_enclave_app/app_service.cc +++ b/cc/containers/hello_world_enclave_app/app_service.cc @@ -33,6 +33,7 @@ namespace oak::containers::hello_world_enclave_app { +using ::oak::session::v1::PlaintextMessage; using ::oak::session::v1::RequestWrapper; using ::oak::session::v1::ResponseWrapper; using ::oak::session::v1::SessionRequest; @@ -85,4 +86,17 @@ grpc::Status EnclaveApplicationImpl::OakSession( return grpc::Status(); } +grpc::Status EnclaveApplicationImpl::PlaintextSession( + grpc::ServerContext* context, + grpc::ServerReaderWriter<PlaintextMessage, PlaintextMessage>* stream) { + PlaintextMessage request; + while (stream->Read(&request)) { + std::string response_text = HandleRequest(request.plaintext()); + PlaintextMessage response; + *(response.mutable_plaintext()) = response_text; + stream->Write(response); + } + return grpc::Status(); +} + } // namespace oak::containers::hello_world_enclave_app diff --git a/cc/containers/hello_world_enclave_app/app_service.h b/cc/containers/hello_world_enclave_app/app_service.h index 7fb53f1ab8..309c4c4b06 100644 --- a/cc/containers/hello_world_enclave_app/app_service.h +++ b/cc/containers/hello_world_enclave_app/app_service.h @@ -53,6 +53,12 @@ class EnclaveApplicationImpl oak::session::v1::SessionRequest>* stream) override; + grpc::Status PlaintextSession( + grpc::ServerContext* context, + grpc::ServerReaderWriter<oak::session::v1::PlaintextMessage, + oak::session::v1::PlaintextMessage>* stream) + override; + private: server::OakSessionServer session_server_; std::string HandleRequest(absl::string_view request); diff --git a/cc/containers/hello_world_enclave_app/standalone_benchmark.cc b/cc/containers/hello_world_enclave_app/standalone_benchmark.cc new file mode 100644 index 0000000000..558d164f34 --- /dev/null +++ b/cc/containers/hello_world_enclave_app/standalone_benchmark.cc @@ -0,0 +1,182 @@ +/* + * Copyright 2025 The Project Oak 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 <cstdint> + +#include "absl/log/check.h" +#include "absl/log/log.h" +#include "absl/status/status_matchers.h" +#include "benchmark/benchmark.h" +#include "cc/attestation/verification/insecure_attestation_verifier.h" +#include "cc/client/client.h" +#include "cc/client/session_client.h" +#include "cc/containers/hello_world_enclave_app/app_service.h" +#include "cc/containers/sdk/standalone/oak_standalone.h" +#include "cc/oak_session/channel/oak_session_channel.h" +#include "cc/oak_session/client_session.h" +#include "cc/transport/grpc_streaming_transport.h" +#include "cc/transport/grpc_sync_session_client_transport.h" +#include "grpcpp/server.h" +#include "grpcpp/server_builder.h" +#include "oak_containers/examples/hello_world/proto/hello_world.grpc.pb.h" + +using ::oak::attestation::v1::AttestationResults; +using ::oak::attestation::verification::InsecureAttestationVerifier; +using ::oak::client::OakClient; +using ::oak::containers::example::EnclaveApplication; +using ::oak::containers::hello_world_enclave_app::EnclaveApplicationImpl; +using ::oak::crypto::EncryptionKeyProvider; +using ::oak::crypto::KeyPair; +using ::oak::session::v1::EndorsedEvidence; +using ::oak::session::v1::PlaintextMessage; +using ::oak::session::v1::SessionResponse; +using ::oak::transport::GrpcStreamingTransport; + +namespace oak::containers::sdk::standalone { + +namespace { + +std::string TestMessage(int size) { + std::string message; + message.reserve(size); + for (int i = 0; i < size; i++) { + message += i % 255; + } + return message; +} + +constexpr absl::string_view kApplicationConfig = "{}"; + +class HelloWorldStandaloneBench : public benchmark::Fixture { + public: + void SetUp(benchmark::State& state) override { + // Set up our new Keypair and get an EndorsedEvidence from Rust. + absl::StatusOr<KeyPair> key_pair = KeyPair::Generate(); + QCHECK_OK(key_pair); + absl::StatusOr<EndorsedEvidence> endorsed_evidence = + GetEndorsedEvidence(*key_pair); + QCHECK_OK(endorsed_evidence); + + // Verify that the endorsed evidence is valid. + InsecureAttestationVerifier verifier; + absl::StatusOr<AttestationResults> attestation_results = verifier.Verify( + std::chrono::system_clock::now(), endorsed_evidence->evidence(), + endorsed_evidence->endorsements()); + QCHECK_OK(attestation_results); + + service_ = std::make_unique<EnclaveApplicationImpl>( + OakSessionContext(std::move(*endorsed_evidence), + std::make_unique<EncryptionKeyProvider>(*key_pair)), + kApplicationConfig); + + grpc::ServerBuilder builder; + builder.AddListeningPort("[::]:8080", grpc::InsecureServerCredentials()); + builder.RegisterService(service_.get()); + server_ = builder.BuildAndStart(); + + auto channel = grpc::CreateChannel("localhost:8080", + grpc::InsecureChannelCredentials()); + stub_ = EnclaveApplication::NewStub(channel); + } + + protected: + std::unique_ptr<EnclaveApplicationImpl> service_; + std::unique_ptr<grpc::Server> server_; + std::unique_ptr<EnclaveApplication::Stub> stub_; +}; + +BENCHMARK_DEFINE_F(HelloWorldStandaloneBench, HPKEInvocation) +(benchmark::State& state) { + std::string test_message = TestMessage(state.range(0)); + grpc::ClientContext context; + auto transport = + std::make_unique<GrpcStreamingTransport>(stub_->LegacySession(&context)); + InsecureAttestationVerifier verifier; + auto client = OakClient::Create(std::move(transport), verifier); + QCHECK_OK(client); + + for (auto iter : state) { + auto result = (*client)->Invoke(test_message); + QCHECK_OK(result); + QCHECK(*result == + absl::Substitute("Hello from the enclave, $1! Btw, the app has a " + "config with a length of $0 bytes.", + kApplicationConfig.size(), test_message)); + } + state.SetBytesProcessed(int64_t(state.iterations()) * + int64_t(state.range(0))); +} + +BENCHMARK_DEFINE_F(HelloWorldStandaloneBench, NoiseInvocation) +(benchmark::State& state) { + std::string test_message = TestMessage(state.range(0)); + client::OakSessionClient session_client; + grpc::ClientContext context; + auto channel = session_client.NewChannel( + std::make_unique<transport::GrpcSyncSessionClientTransport>( + stub_->OakSession(&context))); + QCHECK_OK(channel); + + for (auto i : state) { + auto result = (*channel)->Send(test_message); + QCHECK_OK(result); + auto response = (*channel)->Receive(); + QCHECK_OK(response); + QCHECK(*response == + absl::Substitute("Hello from the enclave, $1! Btw, the app has a " + "config with a length of $0 bytes.", + kApplicationConfig.size(), test_message)); + } + state.SetBytesProcessed(int64_t(state.iterations()) * + int64_t(state.range(0))); +} + +BENCHMARK_DEFINE_F(HelloWorldStandaloneBench, PlaintextInvocation) +(benchmark::State& state) { + std::string test_message = TestMessage(state.range(0)); + grpc::ClientContext context; + auto reader_writer = stub_->PlaintextSession(&context); + + for (auto _ : state) { + PlaintextMessage request; + request.set_plaintext(test_message); + bool result = reader_writer->Write(request); + QCHECK(result); + PlaintextMessage response; + bool response_result = reader_writer->Read(&response); + QCHECK(response_result); + QCHECK(response.plaintext() == + absl::Substitute("Hello from the enclave, $1! Btw, the app has a " + "config with a length of $0 bytes.", + kApplicationConfig.size(), test_message)); + } + state.SetBytesProcessed(int64_t(state.iterations()) * + int64_t(state.range(0))); +} + +BENCHMARK_REGISTER_F(HelloWorldStandaloneBench, HPKEInvocation) + ->Range(2, 1 << 21); +BENCHMARK_REGISTER_F(HelloWorldStandaloneBench, NoiseInvocation) + ->Range(2, 1 << 21); +BENCHMARK_REGISTER_F(HelloWorldStandaloneBench, PlaintextInvocation) + ->Range(2, 1 << 21); + +} // namespace + +} // namespace oak::containers::sdk::standalone + +// Run the benchmark +BENCHMARK_MAIN(); diff --git a/cc/oak_session/BUILD b/cc/oak_session/BUILD index 54bbf23015..7f6ae699bc 100644 --- a/cc/oak_session/BUILD +++ b/cc/oak_session/BUILD @@ -90,6 +90,23 @@ cc_test( ], ) +cc_binary( + name = "client_server_session_benchmark", + testonly = True, + srcs = ["client_server_session_benchmark.cc"], + tags = ["asan"], + deps = [ + ":client_session", + ":server_session", + "//proto/session:session_cc_proto", + "@com_github_google_benchmark//:benchmark", + "@com_github_grpc_grpc//:grpc++", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/status:status_matchers", + ], +) + cc_library( name = "config", srcs = ["config.cc"], diff --git a/cc/oak_session/client_server_session_benchmark.cc b/cc/oak_session/client_server_session_benchmark.cc new file mode 100644 index 0000000000..caffc9b33e --- /dev/null +++ b/cc/oak_session/client_server_session_benchmark.cc @@ -0,0 +1,106 @@ +/* + * Copyright 2025 The Project Oak 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 <string> + +#include "absl/log/check.h" +#include "absl/status/status_matchers.h" +#include "benchmark/benchmark.h" +#include "cc/oak_session/client_session.h" +#include "cc/oak_session/server_session.h" +#include "proto/session/session.pb.h" + +namespace oak::session { +namespace { + +using ::oak::session::v1::SessionRequest; +using ::oak::session::v1::SessionResponse; + +class ClientServerSessionBench : public benchmark::Fixture {}; + +SessionConfig* TestConfig() { + return SessionConfigBuilder(AttestationType::kUnattested, + HandshakeType::kNoiseNN) + .Build(); +} + +std::string TestMessage(int size) { + std::string message; + message.reserve(size); + for (int i = 0; i < size; i++) { + message += i % 255; + } + return message; +} + +void DoHandshake(ClientSession& client_session, ServerSession& server_session) { + absl::StatusOr<std::optional<SessionRequest>> init = + client_session.GetOutgoingMessage(); + QCHECK_OK(init); + QCHECK(*init != std::nullopt); + QCHECK_OK(server_session.PutIncomingMessage(**init)); + + absl::StatusOr<std::optional<SessionResponse>> init_resp = + server_session.GetOutgoingMessage(); + QCHECK_OK(init_resp); + QCHECK(*init_resp != std::nullopt); + QCHECK_OK(client_session.PutIncomingMessage(**init_resp)); + + QCHECK(client_session.IsOpen()); + QCHECK(server_session.IsOpen()); +} + +BENCHMARK_DEFINE_F(ClientServerSessionBench, ClientEncryptServerDecrypt) +(benchmark::State& state) { + auto client_session = ClientSession::Create(TestConfig()); + auto server_session = ServerSession::Create(TestConfig()); + + DoHandshake(**client_session, **server_session); + + std::string test_message = TestMessage(state.range(0)); + for (auto iter : state) { + v1::PlaintextMessage plaintext_message_request; + plaintext_message_request.set_plaintext(test_message); + + QCHECK_OK((*client_session)->Write(plaintext_message_request)); + absl::StatusOr<std::optional<SessionRequest>> request = + (*client_session)->GetOutgoingMessage(); + QCHECK_OK(request); + QCHECK(*request != std::nullopt); + + QCHECK_OK((*server_session)->PutIncomingMessage(**request)); + absl::StatusOr<std::optional<v1::PlaintextMessage>> received_request = + (*server_session)->Read(); + QCHECK_OK(received_request); + QCHECK(*received_request != std::nullopt); + + QCHECK((**received_request).plaintext() == + plaintext_message_request.plaintext()); + } + + state.SetBytesProcessed(int64_t(state.iterations()) * + int64_t(state.range(0))); +} + +BENCHMARK_REGISTER_F(ClientServerSessionBench, ClientEncryptServerDecrypt) + ->Range(2, 2 << 21); + +} // namespace + +} // namespace oak::session + +// Run the benchmark +BENCHMARK_MAIN(); diff --git a/oak_containers/examples/hello_world/enclave_app/src/app_service.rs b/oak_containers/examples/hello_world/enclave_app/src/app_service.rs index f602bbd7da..2bf48476d2 100644 --- a/oak_containers/examples/hello_world/enclave_app/src/app_service.rs +++ b/oak_containers/examples/hello_world/enclave_app/src/app_service.rs @@ -106,6 +106,8 @@ impl EnclaveApplication for EnclaveApplicationImplementation { type LegacySessionStream = oak_containers_sdk::tonic::OakSessionStream; type OakSessionStream = Pin<Box<dyn Stream<Item = Result<SessionResponse, tonic::Status>> + Send + 'static>>; + type PlaintextSessionStream = + Pin<Box<dyn Stream<Item = Result<PlaintextMessage, tonic::Status>> + Send + 'static>>; async fn legacy_session( &self, @@ -145,6 +147,28 @@ impl EnclaveApplication for EnclaveApplicationImplementation { }; Ok(tonic::Response::new(Box::pin(response_stream) as Self::OakSessionStream)) } + + async fn plaintext_session( + &self, + request: tonic::Request<tonic::Streaming<PlaintextMessage>>, + ) -> Result<tonic::Response<Self::PlaintextSessionStream>, tonic::Status> { + let mut request_stream = request.into_inner(); + let application_handler = self.application_handler.clone(); + + let response_stream = async_stream::try_stream! { + while let Some(request) = request_stream.next().await { + let plaintext_message = request?; + yield PlaintextMessage { + plaintext: application_handler + .handle(&plaintext_message.plaintext) + .await + .into_tonic_result("application failed")? + }; + } + }; + + Ok(tonic::Response::new(Box::pin(response_stream) as Self::PlaintextSessionStream)) + } } pub async fn create( diff --git a/oak_containers/examples/hello_world/proto/hello_world.proto b/oak_containers/examples/hello_world/proto/hello_world.proto index c6d7ad81ef..648d3ac3f7 100644 --- a/oak_containers/examples/hello_world/proto/hello_world.proto +++ b/oak_containers/examples/hello_world/proto/hello_world.proto @@ -27,6 +27,9 @@ service EnclaveApplication { rpc OakSession(stream oak.session.v1.SessionRequest) returns (stream oak.session.v1.SessionResponse) {} + + rpc PlaintextSession(stream oak.session.v1.PlaintextMessage) + returns (stream oak.session.v1.PlaintextMessage) {} } service HostApplication {