Skip to content

Commit d6cd341

Browse files
authored
Sandbox/grpc (#103)
* Add gRPC server/client sample and automated build in sandbox/grpc_server - Add EchoService sample using echo.proto - CMake now auto-generates C++/gRPC code from .proto into build dir - Add sample server (grpc_server) and client (grpc_client, grpc_client_safe) implementations - Support for AddressSanitizer-disabled builds - Generated files are kept out of the source tree
1 parent b53ac25 commit d6cd341

27 files changed

Lines changed: 974 additions & 19 deletions

.clang-tidy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,5 @@ CheckOptions:
5454
value: 1
5555
- key: readability-function-cognitive-complexity.IgnoreMacros
5656
value: true
57-
HeaderFilterRegex: '.*/(include|src|examples)/.*\.h$'
57+
HeaderFilterRegex: '^(?!.*/build/).*/(include|src|examples)/.*\.h$'
5858
...

.github/workflows/ci-build.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ jobs:
3535
with:
3636
submodules: true
3737

38+
- name: Install gRPC
39+
run: |
40+
apt-get update
41+
apt-get install -y libabsl-dev
42+
3843
- name: CMake_Build
3944
run: |
4045
mkdir build

CMakeLists.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ option(ENABLE_ALTIMETER "enable altimeter logging" OFF)
4141
option(INSTALL_EXPERIMENTAL_TOOLS "Install experimental tools like tgreplica" OFF)
4242
option(BUILD_REPLICATION_TESTS "Build replication tests" OFF)
4343
option(BUILD_SANDBOX_TOOLS "build sandbox (temporary) tools" OFF)
44+
option(USE_GRPC_CONFIG "Use CMake Config mode for gRPC instead of pkg-config" OFF)
4445

4546
if (FORCE_INSTALL_RPATH)
4647
message(DEPRECATION "FORCE_INSTALL_RPATH is obsoleted")
@@ -77,6 +78,23 @@ if (ENABLE_ALTIMETER)
7778
find_package(fmt REQUIRED)
7879
endif()
7980

81+
82+
# gRPC/protobuf
83+
find_package(Protobuf REQUIRED)
84+
85+
86+
87+
if(USE_GRPC_CONFIG)
88+
message(STATUS "Using gRPC CMake Config mode")
89+
find_package(gRPC CONFIG REQUIRED)
90+
else()
91+
message(STATUS "Using gRPC pkg-config mode")
92+
find_package(PkgConfig REQUIRED)
93+
pkg_check_modules(GRPC REQUIRED grpc++)
94+
endif()
95+
96+
find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin REQUIRED)
97+
8098
add_subdirectory(third_party) # should be before enable_testing()
8199

82100
include(GNUInstallDirs)

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Limestone - a datastore engine
22

3+
34
## Requirements
45

56
* CMake `>= 3.16`
@@ -16,7 +17,7 @@ git submodule update --init --recursive
1617
```dockerfile
1718
FROM ubuntu:22.04
1819

19-
RUN apt update -y && apt install -y git build-essential cmake ninja-build libboost-filesystem-dev libboost-system-dev libboost-container-dev libboost-thread-dev libgoogle-glog-dev libgflags-dev doxygen libleveldb-dev librocksdb-dev pkg-config nlohmann-json3-dev
20+
RUN apt update -y && apt install -y git build-essential cmake ninja-build libboost-filesystem-dev libboost-system-dev libboost-container-dev libboost-thread-dev libgoogle-glog-dev libgflags-dev doxygen libleveldb-dev librocksdb-dev pkg-config nlohmann-json3-dev libgrpc-dev libgrpc++-dev protobuf-compiler-grpc libabsl-dev
2021
# libleveldb-dev is not required if -DRECOVERY_SORTER_KVSLIB=ROCKSDB
2122
# librocksdb-dev is not required if -DRECOVERY_SORTER_KVSLIB=LEVELDB
2223
```
@@ -47,6 +48,7 @@ available options:
4748
* `-DRECOVERY_SORTER_KVSLIB=<library>` - select the eKVS library using at recovery process. (`LEVELDB` or `ROCKSDB` (default), case-insensitive)
4849
* `-DRECOVERY_SORTER_PUT_ONLY=OFF` - don't use (faster) put-only method at recovery process
4950
* `-DBUILD_REPLICATION_TESTS=ON` - (temporary) enable experimental replication tests (excluded by default)
51+
* `-DUSE_GRPC_CONFIG=ON` - use CMake Config mode for gRPC instead of pkg-config (recommended on Ubuntu 24.04 or later for faster configuration)
5052

5153
* for debugging only
5254
* `-DENABLE_SANITIZER=OFF` - disable sanitizers (requires `-DCMAKE_BUILD_TYPE=Debug`)

sandbox/CMakeLists.txt

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
set(TOOLS
2-
startup_speed_benchmark
3-
wal_dump
2+
startup_speed_benchmark/startup_speed_benchmark.cpp
3+
wal_dump/wal_dump.cpp
4+
grpc/grpc_server.cpp
5+
grpc/grpc_client.cpp
6+
grpc/server_streaming_sample_client.cpp
7+
grpc/server_streaming_sample_server.cpp
48
)
59

6-
foreach(tool IN LISTS TOOLS)
7-
add_executable(${tool} ${tool}/${tool}.cpp)
8-
9-
target_include_directories(${tool}
10-
PRIVATE ${CMAKE_SOURCE_DIR}/src/limestone
11-
PRIVATE ${CMAKE_SOURCE_DIR}/include
12-
)
13-
14-
target_link_libraries(${tool}
15-
PRIVATE limestone-impl
16-
PRIVATE glog::glog
10+
foreach(tool_src IN LISTS TOOLS)
11+
get_filename_component(tool_name ${tool_src} NAME_WE)
12+
add_executable(${tool_name} ${tool_src})
13+
target_include_directories(${tool_name} PRIVATE
14+
${PROJECT_SOURCE_DIR}/src
15+
${PROJECT_SOURCE_DIR}/src/limestone
16+
${PROJECT_SOURCE_DIR}/include
17+
${CMAKE_BINARY_DIR}/src # for generated protobuf headers (safe to include always)
1718
)
19+
target_link_libraries(${tool_name} PRIVATE limestone-impl)
1820
endforeach()

sandbox/grpc/grpc_client.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#include <iostream>
2+
#include <memory>
3+
#include <string>
4+
5+
#include <grpcpp/grpcpp.h>
6+
#include <glog/logging.h>
7+
#include "limestone/grpc/client/echo_client.h"
8+
9+
int main(int argc, char** argv) {
10+
// Initialize glog
11+
google::InitGoogleLogging(argv[0]);
12+
FLAGS_logtostderr = 1; // Log to stderr instead of file
13+
14+
std::string target_str = "localhost:50051";
15+
std::string message = "Hello, gRPC!";
16+
17+
if (argc > 1) {
18+
message = argv[1];
19+
}
20+
21+
// Message size check
22+
if (message.size() > 100) {
23+
std::cerr << "Error: Message too long (max 100 chars)" << std::endl;
24+
return 1;
25+
}
26+
27+
LOG(INFO) << "Starting limestone gRPC echo client";
28+
LOG(INFO) << "Connecting to: " << target_str;
29+
30+
try {
31+
// Create echo client
32+
limestone::grpc::client::echo_client client(target_str);
33+
34+
// Send echo request
35+
std::string response;
36+
::grpc::Status status = client.echo(message, response);
37+
38+
if (status.ok()) {
39+
std::cout << "Server replied: " << response << std::endl;
40+
LOG(INFO) << "Echo successful: " << response;
41+
} else {
42+
std::cout << "RPC failed: " << status.error_message() << std::endl;
43+
LOG(ERROR) << "RPC failed: " << status.error_code()
44+
<< ": " << status.error_message();
45+
return 1;
46+
}
47+
} catch (const std::exception& e) {
48+
std::cerr << "Exception: " << e.what() << std::endl;
49+
LOG(ERROR) << "Exception occurred: " << e.what();
50+
return 1;
51+
}
52+
53+
return 0;
54+
}

sandbox/grpc/grpc_server.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#include <iostream>
2+
#include <memory>
3+
#include <string>
4+
5+
#include <grpcpp/grpcpp.h>
6+
#include <glog/logging.h>
7+
#include "limestone/grpc/service/echo_service_impl.h"
8+
9+
using grpc::Server;
10+
using grpc::ServerBuilder;
11+
12+
void RunServer() {
13+
std::string server_address("0.0.0.0:50051");
14+
limestone::grpc::service::echo_service_impl service;
15+
16+
ServerBuilder builder;
17+
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
18+
builder.RegisterService(&service);
19+
std::unique_ptr<Server> server(builder.BuildAndStart());
20+
21+
LOG(INFO) << "Echo server listening on " << server_address;
22+
std::cout << "Server listening on " << server_address << std::endl;
23+
server->Wait();
24+
}
25+
26+
int main(int argc, char** argv) {
27+
// Initialize glog
28+
google::InitGoogleLogging(argv[0]);
29+
FLAGS_logtostderr = 1; // Log to stderr instead of file
30+
31+
LOG(INFO) << "Starting limestone gRPC echo server";
32+
RunServer();
33+
return 0;
34+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Command-line client for server-streaming sample services
2+
#include <grpcpp/grpcpp.h>
3+
#include <iostream>
4+
#include <glog/logging.h>
5+
#include <fstream>
6+
#include <vector>
7+
#include <string>
8+
#include "server_streaming_sample.pb.h"
9+
#include "server_streaming_sample.grpc.pb.h"
10+
11+
void print_usage(const char* prog_name) {
12+
std::cout << "Usage: " << prog_name << " <service> <server_address> [options]\n"
13+
<< " service: file_size | random_bytes\n"
14+
<< " server_address: host:port\n"
15+
<< " file_size options: <file_path>\n"
16+
<< " random_bytes options: <size>\n";
17+
}
18+
19+
int main(int argc, char* argv[]) {
20+
if (argc < 3) {
21+
print_usage(argv[0]);
22+
return 1;
23+
}
24+
std::string service = argv[1];
25+
std::string server_address = argv[2];
26+
grpc::ChannelArguments args;
27+
args.SetMaxSendMessageSize(64 * 1024 * 1024); // 64MB
28+
args.SetMaxReceiveMessageSize(64 * 1024 * 1024); // 64MB
29+
auto channel = grpc::CreateCustomChannel(server_address, grpc::InsecureChannelCredentials(), args);
30+
31+
if (service == "file_size") {
32+
using namespace std::chrono;
33+
LOG(INFO) << "[file_size] start";
34+
auto t0 = steady_clock::now();
35+
if (argc < 4) {
36+
std::cerr << "Missing file_path argument for file_size service\n";
37+
return 1;
38+
}
39+
std::string file_path = argv[3];
40+
std::ifstream file(file_path, std::ios::binary);
41+
if (!file) {
42+
std::cerr << "Failed to open file: " << file_path << "\n";
43+
return 1;
44+
}
45+
limestone::grpc::FileSizeService::Stub stub(channel);
46+
grpc::ClientContext context;
47+
limestone::grpc::FileSizeResponse response;
48+
std::unique_ptr<grpc::ClientWriter<limestone::grpc::FileChunk>> writer(
49+
stub.GetFileSize(&context, &response));
50+
constexpr size_t buffer_size = 32 * 1024 * 1024; // 32MB
51+
std::vector<char> buffer(buffer_size);
52+
while (file.read(buffer.data(), buffer.size()) || file.gcount() > 0) {
53+
limestone::grpc::FileChunk chunk;
54+
chunk.set_data(std::string(buffer.data(), file.gcount()));
55+
if (!writer->Write(chunk)) {
56+
std::cerr << "Failed to write chunk to server\n";
57+
break;
58+
}
59+
}
60+
writer->WritesDone();
61+
grpc::Status status = writer->Finish();
62+
auto t1 = steady_clock::now();
63+
auto ms = duration_cast<milliseconds>(t1 - t0).count();
64+
if (status.ok()) {
65+
std::cout << "File size: " << response.size() << " bytes\n";
66+
} else {
67+
std::cerr << "RPC failed: " << status.error_message() << "\n";
68+
}
69+
LOG(INFO) << "[file_size] end: elapsed " << ms << " ms";
70+
} else if (service == "random_bytes") {
71+
using namespace std::chrono;
72+
LOG(INFO) << "[random_bytes] start";
73+
auto t0 = steady_clock::now();
74+
if (argc < 4) {
75+
std::cerr << "Missing size argument for random_bytes service\n";
76+
return 1;
77+
}
78+
int64_t size = std::stoll(argv[3]);
79+
limestone::grpc::RandomBytesService::Stub stub(channel);
80+
grpc::ClientContext context;
81+
limestone::grpc::RandomBytesRequest request;
82+
request.set_size(size);
83+
std::unique_ptr<grpc::ClientReader<limestone::grpc::RandomBytesChunk>> reader(
84+
stub.GenerateRandomBytes(&context, request));
85+
int64_t received = 0;
86+
limestone::grpc::RandomBytesChunk chunk;
87+
while (reader->Read(&chunk)) {
88+
received += chunk.data().size();
89+
// For demonstration, do not print the data
90+
}
91+
grpc::Status status = reader->Finish();
92+
auto t1 = steady_clock::now();
93+
auto ms = duration_cast<milliseconds>(t1 - t0).count();
94+
if (status.ok()) {
95+
std::cout << "Received " << received << " bytes of random data\n";
96+
} else {
97+
std::cerr << "RPC failed: " << status.error_message() << "\n";
98+
}
99+
LOG(INFO) << "[random_bytes] end: elapsed " << ms << " ms";
100+
} else {
101+
std::cerr << "Unknown service: " << service << "\n";
102+
print_usage(argv[0]);
103+
return 1;
104+
}
105+
return 0;
106+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Command-line server for server-streaming sample services
2+
#include <grpcpp/grpcpp.h>
3+
#include "grpc/service/server_streaming_sample_service.h"
4+
#include <iostream>
5+
6+
int main(int argc, char* argv[]) {
7+
std::string server_address = "0.0.0.0:50051";
8+
if (argc > 1) {
9+
server_address = argv[1];
10+
}
11+
grpc::ServerBuilder builder;
12+
builder.SetMaxReceiveMessageSize(64 * 1024 * 1024); // 64MB
13+
builder.SetMaxSendMessageSize(64 * 1024 * 1024); // 64MB
14+
builder.SetMaxMessageSize(64 * 1024 * 1024); // 64MB
15+
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
16+
limestone::grpc::service::FileSizeServiceImpl file_size_service;
17+
limestone::grpc::service::RandomBytesServiceImpl random_bytes_service;
18+
builder.RegisterService(&file_size_service);
19+
builder.RegisterService(&random_bytes_service);
20+
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
21+
std::cout << "Server listening on " << server_address << std::endl;
22+
server->Wait();
23+
return 0;
24+
}

0 commit comments

Comments
 (0)