diff --git a/benchmarks/200.multimedia/210.thumbnailer/config.json b/benchmarks/200.multimedia/210.thumbnailer/config.json index 8edb99e5..f0c722d8 100644 --- a/benchmarks/200.multimedia/210.thumbnailer/config.json +++ b/benchmarks/200.multimedia/210.thumbnailer/config.json @@ -1,6 +1,7 @@ { "timeout": 60, "memory": 256, - "languages": ["python", "nodejs"], - "modules": ["storage"] + "languages": ["python", "nodejs", "cpp"], + "modules": ["storage"], + "cpp_dependencies": ["opencv", "boost"] } diff --git a/benchmarks/200.multimedia/210.thumbnailer/cpp/function.hpp b/benchmarks/200.multimedia/210.thumbnailer/cpp/function.hpp new file mode 100644 index 00000000..9b8b1773 --- /dev/null +++ b/benchmarks/200.multimedia/210.thumbnailer/cpp/function.hpp @@ -0,0 +1,27 @@ + +#include +#include + +void thumbnailer(cv::Mat &in, int64_t width, int64_t height, cv::Mat &out) +{ + try + { + // Calculate thumbnail size while maintaining aspect ratio + int orig_width = in.cols; + int orig_height = in.rows; + + double scale_w = static_cast(width) / orig_width; + double scale_h = static_cast(height) / orig_height; + double scale = std::min(scale_w, scale_h); // Use smaller scale to fit within bounds + + int new_width = static_cast(orig_width * scale); + int new_height = static_cast(orig_height * scale); + + // Resize image (equivalent to PIL's thumbnail method) + cv::resize(in, out, cv::Size(new_width, new_height), cv::INTER_LINEAR); + } + catch (const cv::Exception &e) + { + std::cerr << "OpenCV error: " << e.what() << std::endl; + } +} diff --git a/benchmarks/200.multimedia/210.thumbnailer/cpp/main.cpp b/benchmarks/200.multimedia/210.thumbnailer/cpp/main.cpp new file mode 100644 index 00000000..9f3ccebd --- /dev/null +++ b/benchmarks/200.multimedia/210.thumbnailer/cpp/main.cpp @@ -0,0 +1,117 @@ + +#include +#include + +#include +#include +#include + +#include +#include + +#include "function.hpp" +#include "storage.hpp" +#include "utils.hpp" + +Aws::Utils::Json::JsonValue function(Aws::Utils::Json::JsonView request) +{ + sebs::Storage client_ = sebs::Storage::get_client(); + + auto bucket_obj = request.GetObject("bucket"); + if (!bucket_obj.IsObject()) + { + Aws::Utils::Json::JsonValue error; + error.WithString("error", "Bucket object is not valid."); + return error; + } + auto bucket_name = bucket_obj.GetString("bucket"); + auto input_key_prefix = bucket_obj.GetString("input"); + auto output_key_prefix = bucket_obj.GetString("output"); + + auto image_name = request.GetObject("object").GetString("key"); + auto width = request.GetObject("object").GetInteger("width"); + auto height = request.GetObject("object").GetInteger("height"); + + std::string body_str; + uint64_t download_time; + { + std::string input_key = input_key_prefix + "/" + image_name; + auto ans = client_.download_file(bucket_name, input_key); + body_str = std::get<0>(ans); + download_time = std::get<1>(ans); + + if (body_str.empty()) + { + Aws::Utils::Json::JsonValue error; + error.WithString("error", "Failed to download object from S3: " + input_key); + return error; + } + } + + std::vector vectordata(body_str.begin(), body_str.end()); + cv::Mat image = imdecode(cv::Mat(vectordata), 1); + + // Apply the thumbnailer function and measure the computing time + cv::Mat image2; + uint64_t computing_time; + { + auto start_time = timeSinceEpochMicrosec(); + thumbnailer(image, width, height, image2); + computing_time = timeSinceEpochMicrosec() - start_time; + } + + std::vector out_buffer; + cv::imencode(".jpg", image2, out_buffer); + + // Create a unique key name for the output image + std::string key_name; + { + std::string output_key = output_key_prefix + "/" + image_name; + std::string name, extension; + if (output_key.find_last_of('.') != std::string::npos) + { + name = output_key.substr(0, output_key.find_last_of('.')); + extension = output_key.substr(output_key.find_last_of('.')); + } + else + { + name = output_key; + extension = ""; + } + key_name = name + "." + + boost::uuids::to_string(boost::uuids::random_generator()()) + + extension; + } + + // Upload the resulting image to S3 + // If the upload fails, return an error + Aws::String upload_data(out_buffer.begin(), out_buffer.end()); + + uint64_t upload_time = client_.upload_random_file( + bucket_name, key_name, true, + reinterpret_cast(out_buffer.data()), out_buffer.size()); + + if (upload_time == 0) + { + Aws::Utils::Json::JsonValue error; + error.WithString("error", "Failed to upload object to S3: " + key_name); + return error; + } + + + Aws::Utils::Json::JsonValue val; + Aws::Utils::Json::JsonValue result; + Aws::Utils::Json::JsonValue measurements; + + result.WithString("bucket", bucket_name); + result.WithString("key", key_name); + val.WithObject("result", result); + + measurements.WithInteger("download_time", download_time); + measurements.WithInteger("upload_time", upload_time); + measurements.WithInteger("compute_time", computing_time); + measurements.WithInteger("download_size", vectordata.size()); + measurements.WithInteger("upload_size", out_buffer.size()); + val.WithObject("measurements", measurements); + return val; +} \ No newline at end of file diff --git a/benchmarks/400.inference/411.image-recognition/config.json b/benchmarks/400.inference/411.image-recognition/config.json index 94ede792..56db1084 100644 --- a/benchmarks/400.inference/411.image-recognition/config.json +++ b/benchmarks/400.inference/411.image-recognition/config.json @@ -1,6 +1,7 @@ { "timeout": 60, "memory": 512, - "languages": ["python"], - "modules": ["storage"] + "languages": ["python", "cpp"], + "modules": ["storage"], + "cpp_dependencies": ["torch", "opencv"] } diff --git a/benchmarks/400.inference/411.image-recognition/cpp/main.cpp b/benchmarks/400.inference/411.image-recognition/cpp/main.cpp new file mode 100644 index 00000000..4e6afb9a --- /dev/null +++ b/benchmarks/400.inference/411.image-recognition/cpp/main.cpp @@ -0,0 +1,122 @@ +#include +#include + +#include +#include +#include + +#include +#include + +#include "utils.hpp" +#include "storage.hpp" + +#include +#include + +#include +#include +#include +#include + + +#define kIMAGE_SIZE 224 +#define kCHANNELS 3 +#define kTOP_K 3 + + +bool load_image(cv::Mat &image) +{ + + // image = cv::imread(file_name); // CV_8UC3 + if (image.empty() || !image.data) + { + return false; + } + cv::cvtColor(image, image, cv::COLOR_BGR2RGB); + + int w = image.size().width, h = image.size().height; + cv::Size scale((int)256 * ((float)w) / h, 256); + cv::resize(image, image, scale); + w = image.size().width, h = image.size().height; + image = image(cv::Range(16, 240), cv::Range(80, 304)); + image.convertTo(image, CV_32FC3, 1.0f / 255.0f); + + return true; +} + +int recognition(cv::Mat &image) +{ + + static bool initialized = false; + static torch::jit::script::Module module; + if (!initialized) + { + try + { + std::cout << "Initialize ResNet50 model" << std::endl; + module = torch::jit::load("./resnet50.pt"); + } + catch (const c10::Error &e) + { + std::cerr << "error loading the model\n"; + return -1; + } + initialized = true; + } + + if (load_image(image)) + { + + auto input_tensor = torch::from_blob( + image.data, {1, kIMAGE_SIZE, kIMAGE_SIZE, kCHANNELS}); + input_tensor = input_tensor.permute({0, 3, 1, 2}); + // input_tensor = input_tensor.permute({0, 1, 2, 3}); + input_tensor[0][0] = input_tensor[0][0].sub_(0.485).div_(0.229); + input_tensor[0][1] = input_tensor[0][1].sub_(0.456).div_(0.224); + input_tensor[0][2] = input_tensor[0][2].sub_(0.406).div_(0.225); + + torch::Tensor out_tensor = module.forward({input_tensor}).toTensor(); + auto results = out_tensor.sort(-1, true); + auto softmaxs = std::get<0>(results)[0].softmax(0); + auto indexs = std::get<1>(results)[0]; + + std::cout << indexs[0].item() << " " << softmaxs[0].item() << std::endl; + return indexs[0].item(); + // for(int i = 0; i < 100; ++i) + // std::cout << softmaxs[i].item() << ' '; + // std::cout << '\n'; + // for(int i = 0; i < 100; ++i) + // std::cout << indexs[i].item() << ' '; + // std::cout << '\n'; + } + + return -1; +} + +Aws::Utils::Json::JsonValue function(Aws::Utils::Json::JsonView request) +{ + sebs::Storage client_ = sebs::Storage::get_client(); + + auto bucket_obj = request.GetObject("bucket"); + if (!bucket_obj.IsObject()) + { + Aws::Utils::Json::JsonValue error; + error.WithString("error", "Bucket object is not valid."); + return error; + } + + auto bucket_name = bucket_obj.GetString("bucket"); + auto input_prefix = bucket_obj.GetString("input"); + auto model_prefix = bucket_obj.GetString("model"); + auto key = request.GetObject("object").GetString("input"); + auto model_key = request.GetObject("object").GetString("model"); + + + + Aws::Utils::Json::JsonValue val; + Aws::Utils::Json::JsonValue result; + Aws::Utils::Json::JsonValue measurements; + + return val; +} \ No newline at end of file diff --git a/benchmarks/500.scientific/501.graph-pagerank/config.json b/benchmarks/500.scientific/501.graph-pagerank/config.json index e80fb435..90e8c7f8 100644 --- a/benchmarks/500.scientific/501.graph-pagerank/config.json +++ b/benchmarks/500.scientific/501.graph-pagerank/config.json @@ -1,6 +1,7 @@ { "timeout": 120, "memory": 512, - "languages": ["python"], - "modules": [] + "languages": ["python", "cpp"], + "modules": [], + "cpp_dependencies": ["igraph"] } diff --git a/benchmarks/500.scientific/501.graph-pagerank/cpp/function.hpp b/benchmarks/500.scientific/501.graph-pagerank/cpp/function.hpp new file mode 100644 index 00000000..d04edd97 --- /dev/null +++ b/benchmarks/500.scientific/501.graph-pagerank/cpp/function.hpp @@ -0,0 +1,56 @@ + +#include + +#include + +#include "utils.hpp" + +igraph_real_t graph_pagerank +(int size, uint64_t seed, uint64_t &graph_generation_time_ms, uint64_t &compute_pr_time_ms) +{ + igraph_t graph; + igraph_vector_t pagerank; + igraph_real_t value; + + igraph_rng_seed(igraph_rng_default(), seed); + { + uint64_t start_time = timeSinceEpochMicrosec(); + igraph_barabasi_game( + /* graph= */ &graph, + /* n= */ size, + /* power= */ 1, + /* m= */ 10, + /* outseq= */ NULL, + /* outpref= */ 0, + /* A= */ 1.0, + /* directed= */ 0, + /* algo= */ IGRAPH_BARABASI_PSUMTREE_MULTIPLE, + /* start_from= */ 0 + ); + graph_generation_time_ms = (timeSinceEpochMicrosec() - start_time); + } + + igraph_vector_init(&pagerank, 0); + { + uint64_t start_time = timeSinceEpochMicrosec(); + igraph_pagerank(&graph, IGRAPH_PAGERANK_ALGO_PRPACK, + &pagerank, &value, + igraph_vss_all(), IGRAPH_DIRECTED, + /* damping */ 0.85, /* weights */ NULL, + NULL /* not needed with PRPACK method */); + compute_pr_time_ms = (timeSinceEpochMicrosec() - start_time); + } + /* Check that the eigenvalue is 1, as expected. */ + if (fabs(value - 1.0) > 32*DBL_EPSILON) { + fprintf(stderr, "PageRank failed to converge.\n"); + return 1; + } + + igraph_real_t result = VECTOR(pagerank)[0]; + + igraph_vector_destroy(&pagerank); + igraph_destroy(&graph); + + return result; +} + diff --git a/benchmarks/500.scientific/501.graph-pagerank/cpp/main.cpp b/benchmarks/500.scientific/501.graph-pagerank/cpp/main.cpp new file mode 100644 index 00000000..401f35b5 --- /dev/null +++ b/benchmarks/500.scientific/501.graph-pagerank/cpp/main.cpp @@ -0,0 +1,44 @@ + +#include +#include + +#include "function.hpp" +#include "storage.hpp" +#include "utils.hpp" + +#include +#include +#include +#include +#include // Required for ULLONG_MAX + +Aws::Utils::Json::JsonValue function(Aws::Utils::Json::JsonView request) +{ + sebs::Storage client_ = sebs::Storage::get_client(); + + auto size = request.GetInteger("size"); + + uint64_t seed; + if (request.ValueExists("seed")) { + seed = request.GetInteger("seed"); + } else { + double random_value = 0.0; + seed = static_cast(random_value * ULLONG_MAX); + } + + uint64_t graph_generation_time_ms; + uint64_t compute_pr_time_ms; + igraph_real_t value = graph_pagerank + (size, seed, graph_generation_time_ms, compute_pr_time_ms); + + Aws::Utils::Json::JsonValue val; + Aws::Utils::Json::JsonValue result; + Aws::Utils::Json::JsonValue measurements; + + measurements.WithInteger("graph_generating_time", graph_generation_time_ms); + measurements.WithInteger("compute_time", compute_pr_time_ms); + + val.WithDouble("value", static_cast(value)); + val.WithObject("measurements", std::move(measurements)); + return val; +} \ No newline at end of file diff --git a/benchmarks/500.scientific/503.graph-bfs/config.json b/benchmarks/500.scientific/503.graph-bfs/config.json index e80fb435..90e8c7f8 100644 --- a/benchmarks/500.scientific/503.graph-bfs/config.json +++ b/benchmarks/500.scientific/503.graph-bfs/config.json @@ -1,6 +1,7 @@ { "timeout": 120, "memory": 512, - "languages": ["python"], - "modules": [] + "languages": ["python", "cpp"], + "modules": [], + "cpp_dependencies": ["igraph"] } diff --git a/benchmarks/500.scientific/503.graph-bfs/cpp/main.cpp b/benchmarks/500.scientific/503.graph-bfs/cpp/main.cpp new file mode 100644 index 00000000..a160d2d0 --- /dev/null +++ b/benchmarks/500.scientific/503.graph-bfs/cpp/main.cpp @@ -0,0 +1,83 @@ +#include +#include + +#include +#include +#include +#include + +#include "utils.hpp" + +Aws::Utils::Json::JsonValue function(Aws::Utils::Json::JsonView request) +{ + int size = request.GetInteger("size"); + + uint64_t seed; + if (request.ValueExists("seed")) { + seed = request.GetInteger("seed"); + igraph_rng_seed(igraph_rng_default(), seed); + } else { + std::random_device rd; + seed = rd(); + } + igraph_rng_seed(igraph_rng_default(), seed); + + auto graph_gen_start = timeSinceEpochMicrosec(); + igraph_t graph; + igraph_barabasi_game( + &graph, + size, + 1, // power + 10, // m + nullptr,// outseq + 0, // outpref + 1.0, // A + 0, // directed + IGRAPH_BARABASI_PSUMTREE_MULTIPLE, + 0 // start_from + ); + auto graph_gen_end = timeSinceEpochMicrosec(); + + // Measure BFS time + auto bfs_start = timeSinceEpochMicrosec(); + igraph_vector_int_t order; + igraph_vector_int_init(&order, 0); + // Documentation: https://igraph.org/c/pdf/0.9.7/igraph-docs.pdf + igraph_bfs( + &graph, + 0, // root vertex + nullptr, // roots + IGRAPH_ALL, // neimode + true, // unreachable + nullptr, // restricted + &order, // order + nullptr, // rank + nullptr, // father + nullptr, // pred + nullptr, //succ, + nullptr, // dist + nullptr, // callback + nullptr // extra + ); + auto bfs_end = timeSinceEpochMicrosec(); + + // Calculate times in microseconds + auto graph_generating_time = graph_gen_end - graph_gen_start; + auto process_time =bfs_end - bfs_start; + + Aws::Utils::Json::JsonValue result; + + igraph_real_t bfs_result = VECTOR(order)[0]; + result.WithDouble("result", static_cast(bfs_result)); + + Aws::Utils::Json::JsonValue measurement; + measurement.WithInt64("graph_generating_time", graph_generating_time); + measurement.WithInt64("compute_time", process_time); + + result.WithObject("measurement", std::move(measurement)); + + igraph_vector_int_destroy(&order); + igraph_destroy(&graph); + + return result; +} \ No newline at end of file diff --git a/benchmarks/wrappers/aws/cpp/storage.cpp b/benchmarks/wrappers/aws/cpp/storage.cpp index 0ed61946..3926bd00 100644 --- a/benchmarks/wrappers/aws/cpp/storage.cpp +++ b/benchmarks/wrappers/aws/cpp/storage.cpp @@ -13,21 +13,20 @@ #include "storage.hpp" #include "utils.hpp" -Storage Storage::get_client() { +sebs::Storage sebs::Storage::get_client() { Aws::Client::ClientConfiguration config; - config.caFile = "/etc/pki/tls/certs/ca-bundle.crt"; char const TAG[] = "LAMBDA_ALLOC"; auto credentialsProvider = Aws::MakeShared(TAG); - Aws::S3::S3Client client(credentialsProvider, config); + Aws::S3::S3Client client(credentialsProvider, nullptr, config); return Storage(std::move(client)); } -uint64_t Storage::download_file(Aws::String const &bucket, +uint64_t sebs::Storage::download_file(Aws::String const &bucket, Aws::String const &key, int &required_retries, - bool report_dl_time) { - + bool report_dl_time, + Aws::IOStream &output_stream) { Aws::S3::Model::GetObjectRequest request; request.WithBucket(bucket).WithKey(key); auto bef = timeSinceEpochMicrosec(); @@ -40,11 +39,10 @@ uint64_t Storage::download_file(Aws::String const &bucket, if (outcome.IsSuccess()) { auto &s = outcome.GetResult().GetBody(); uint64_t finishedTime = timeSinceEpochMicrosec(); - // Perform NOP on result to prevent optimizations - std::stringstream ss; - ss << s.rdbuf(); - std::string first(" "); - ss.get(&first[0], 1); + + output_stream.clear(); + output_stream << s.rdbuf(); + required_retries = retries; if (report_dl_time) { return finishedTime - bef; @@ -63,9 +61,36 @@ uint64_t Storage::download_file(Aws::String const &bucket, return 0; } -uint64_t Storage::upload_random_file(Aws::String const &bucket, - Aws::String const &key, int size, - char *pBuf) { +std::tuple sebs::Storage::download_file( + Aws::String const &bucket, Aws::String const &key) { + Aws::S3::Model::GetObjectRequest request; + request.WithBucket(bucket).WithKey(key); + auto bef = timeSinceEpochMicrosec(); + + Aws::S3::Model::GetObjectOutcome outcome = this->_client.GetObject(request); + if (!outcome.IsSuccess()) { + std::cerr << "Error: GetObject: " << outcome.GetError().GetMessage() + << std::endl; + return {"", 0}; + } + auto &s = outcome.GetResult().GetBody(); + uint64_t finishedTime = timeSinceEpochMicrosec(); + + std::string content((std::istreambuf_iterator(s)), + std::istreambuf_iterator()); + return {content, finishedTime - bef}; +} + +uint64_t sebs::Storage::upload_random_file(Aws::String const &bucket, + Aws::String const &key, + bool report_dl_time, + char * data, + size_t data_size) { + if (data == nullptr || data_size == 0) { + std::cerr << "Error: upload_random_file called with null data or zero size." + << std::endl; + return 0; + } /** * We use Boost's bufferstream to wrap the array as an IOStream. Usign a * light-weight streambuf wrapper, as many solutions (e.g. @@ -74,16 +99,20 @@ uint64_t Storage::upload_random_file(Aws::String const &bucket, * functioning tellp(), etc... (for instance to get the body length). */ const std::shared_ptr input_data = - std::make_shared(pBuf, size); + std::make_shared( + data, data_size); Aws::S3::Model::PutObjectRequest request; request.WithBucket(bucket).WithKey(key); request.SetBody(input_data); uint64_t bef_upload = timeSinceEpochMicrosec(); Aws::S3::Model::PutObjectOutcome outcome = this->_client.PutObject(request); + int64_t finishedTime = timeSinceEpochMicrosec(); if (!outcome.IsSuccess()) { std::cerr << "Error: PutObject: " << outcome.GetError().GetMessage() << std::endl; + return 0; } - return bef_upload; + return report_dl_time ? finishedTime - bef_upload + : finishedTime; } diff --git a/benchmarks/wrappers/aws/cpp/storage.hpp b/benchmarks/wrappers/aws/cpp/storage.hpp index 2b548ef2..d0253fff 100644 --- a/benchmarks/wrappers/aws/cpp/storage.hpp +++ b/benchmarks/wrappers/aws/cpp/storage.hpp @@ -4,10 +4,12 @@ #include #include +namespace sebs { + class Storage { - Aws::S3::S3Client _client; public: + Aws::S3::S3Client _client; Storage(Aws::S3::S3Client && client): _client(client) @@ -18,11 +20,27 @@ class Storage uint64_t download_file(Aws::String const &bucket, Aws::String const &key, int &required_retries, - bool report_dl_time); + bool report_dl_time, + Aws::IOStream &output_stream); + + /* + * Downloads a file from S3 + * @param bucket The S3 bucket name + * @param key The S3 object key + * @return A tuple containing the file content as a string and the elapsed + * time in microseconds. + * If the download fails, an empty string and 0 are returned. + */ + std::tuple download_file(Aws::String const &bucket, + Aws::String const &key); uint64_t upload_random_file(Aws::String const &bucket, Aws::String const &key, - int size, char* pBuf); + bool report_dl_time, + char * data, + size_t data_size); + +}; }; diff --git a/config/systems.json b/config/systems.json index f646cec1..e75f4d52 100644 --- a/config/systems.json +++ b/config/systems.json @@ -124,10 +124,12 @@ }, "cpp": { "base_images": { - "all": "amazon/aws-lambda-provided:al2.2022.04.27.09" + "x64":{ + "all": "amazon/aws-lambda-provided:al2.2022.04.27.09" + } }, "dependencies": [ - "runtime", "sdk", "boost", "hiredis" + "runtime", "sdk", "boost", "hiredis", "opencv", "igraph", "torch" ], "versions": ["all"], "images": ["build"], diff --git a/dockerfiles/aws/cpp/Dockerfile.build b/dockerfiles/aws/cpp/Dockerfile.build index 2699806e..66079e52 100755 --- a/dockerfiles/aws/cpp/Dockerfile.build +++ b/dockerfiles/aws/cpp/Dockerfile.build @@ -1,10 +1,14 @@ ARG BASE_REPOSITORY ARG BASE_IMAGE + FROM ${BASE_REPOSITORY}:dependencies-sdk.aws.cpp.all as sdk FROM ${BASE_REPOSITORY}:dependencies-boost.aws.cpp.all as boost FROM ${BASE_REPOSITORY}:dependencies-hiredis.aws.cpp.all as hiredis FROM ${BASE_REPOSITORY}:dependencies-runtime.aws.cpp.all as runtime +FROM ${BASE_REPOSITORY}:dependencies-opencv.aws.cpp.all as opencv +FROM ${BASE_REPOSITORY}:dependencies-igraph.aws.cpp.all as igraph +FROM ${BASE_REPOSITORY}:dependencies-torch.aws.cpp.all as torch FROM ${BASE_IMAGE} as builder @@ -15,8 +19,13 @@ ENV GOSU_VERSION 1.14 RUN curl -o /usr/local/bin/gosu -SL "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-amd64" \ && chmod +x /usr/local/bin/gosu RUN mkdir -p /sebs/ -COPY docker/entrypoint.sh /sebs/entrypoint.sh -COPY docker/cpp_installer.sh /sebs/installer.sh +COPY dockerfiles/entrypoint.sh /sebs/entrypoint.sh +COPY dockerfiles/cpp_installer.sh /sebs/installer.sh + +# Set environment variables for LibTorch +ENV LD_LIBRARY_PATH=/opt/libtorch/lib:$LD_LIBRARY_PATH +ENV CMAKE_PREFIX_PATH=/opt/libtorch:$CMAKE_PREFIX_PATH + RUN chmod +x /sebs/entrypoint.sh RUN chmod +x /sebs/installer.sh @@ -24,6 +33,12 @@ COPY --from=sdk /opt /opt COPY --from=boost /opt /opt COPY --from=runtime /opt /opt COPY --from=hiredis /opt /opt +COPY --from=opencv /opt/opencv /opt/opencv +COPY --from=igraph /opt /opt +COPY --from=torch /opt /opt + +# Ensure libtorch.so symlink exists for runtime compatibility +RUN ln -sf /opt/libtorch/lib/libtorch_cpu.so /opt/libtorch/lib/libtorch.so # useradd and groupmod is installed in /usr/sbin which is not in PATH ENV PATH=/usr/sbin:$PATH diff --git a/dockerfiles/aws/cpp/Dockerfile.dependencies-igraph b/dockerfiles/aws/cpp/Dockerfile.dependencies-igraph new file mode 100644 index 00000000..9ec62def --- /dev/null +++ b/dockerfiles/aws/cpp/Dockerfile.dependencies-igraph @@ -0,0 +1,41 @@ +ARG BASE_IMAGE + +FROM ${BASE_IMAGE} as builder + +ARG WORKERS +ENV WORKERS=${WORKERS} + +WORKDIR /app/builder + +RUN yum update -y && \ + yum install -y cmake3 wget unzip curl git gcc gcc-c++ \ + make tar gzip libpng-devel zlib-devel \ + libjpeg-turbo-devel python3-devel python3-pip && \ + yum clean all && \ + rm -rf /var/cache/yum + +RUN wget https://github.com/Kitware/CMake/releases/download/v3.25.2/cmake-3.25.2-linux-x86_64.tar.gz && \ + tar -xzf cmake-3.25.2-linux-x86_64.tar.gz && \ + cp -r cmake-3.25.2-linux-x86_64/* /usr/local/ && \ + rm -rf cmake-3.25.2-linux-x86_64* + +# Verify CMake version +RUN cmake --version + +# Download and extract igraph +RUN wget https://github.com/igraph/igraph/releases/download/0.10.15/igraph-0.10.15.tar.gz && \ + tar -xzf igraph-0.10.15.tar.gz + +RUN mkdir build_igraph && \ + cd build_igraph && \ + cmake -DCMAKE_INSTALL_PREFIX=/app/builder/install \ + -DIGRAPH_OPENMP_SUPPORT=OFF \ + ../igraph-0.10.15 && \ + make -j${WORKERS} && \ + make install + +FROM ${BASE_IMAGE} + +WORKDIR /app + +COPY --from=builder /app/builder/install /opt \ No newline at end of file diff --git a/dockerfiles/aws/cpp/Dockerfile.dependencies-opencv b/dockerfiles/aws/cpp/Dockerfile.dependencies-opencv new file mode 100644 index 00000000..30efd16f --- /dev/null +++ b/dockerfiles/aws/cpp/Dockerfile.dependencies-opencv @@ -0,0 +1,15 @@ +ARG BASE_IMAGE +FROM ${BASE_IMAGE} as builder +ARG WORKERS +ENV WORKERS=${WORKERS} + + +RUN yum install -y unzip cmake3 curl libcurl libcurl-devel git gcc gcc-c++ make tar gzip zip zlib-devel openssl-devel openssl-static +RUN curl -LO https://github.com/opencv/opencv/archive/refs/tags/4.5.0.zip +RUN unzip 4.5.0.zip +RUN mkdir build && cd build && cmake3 -D CMAKE_BUILD_TYPE=RELEASE -D BUILD_EXAMPLES=Off -D OPENCV_GENERATE_PKGCONFIG=ON -DCMAKE_INSTALL_PREFIX=/opt/opencv -DBUILD_LIST=core,imgproc,imgcodecs ../opencv-4.5.0 +RUN cd build && make -j8 && make install + +FROM ${BASE_IMAGE} + +COPY --from=builder /opt /opt diff --git a/dockerfiles/aws/cpp/Dockerfile.dependencies-torch b/dockerfiles/aws/cpp/Dockerfile.dependencies-torch new file mode 100644 index 00000000..817cac1d --- /dev/null +++ b/dockerfiles/aws/cpp/Dockerfile.dependencies-torch @@ -0,0 +1,51 @@ +ARG BASE_IMAGE + +FROM ${BASE_IMAGE} as builder + +ARG WORKERS +ENV WORKERS=${WORKERS} + +WORKDIR /app/builder + +RUN yum update -y && \ + yum install -y cmake3 wget unzip curl git gcc gcc-c++ \ + make tar gzip libpng-devel zlib-devel \ + libjpeg-turbo-devel python3-devel python3-pip && \ + yum clean all && \ + rm -rf /var/cache/yum + +# Download and extract libtorch. +RUN curl -L -o torchlib.zip https://download.pytorch.org/libtorch/cpu/libtorch-cxx11-abi-shared-with-deps-1.7.0%2Bcpu.zip +RUN unzip -q torchlib.zip +RUN rm torchlib.zip + +# Move libtorch directly to /opt +RUN mv libtorch /opt/libtorch + + +# Set environment variables for LibTorch +ENV LD_LIBRARY_PATH=/opt/libtorch/lib:$LD_LIBRARY_PATH +ENV CMAKE_PREFIX_PATH=/opt/libtorch:$CMAKE_PREFIX_PATH + +ENV TORCHVISION_VERSION=0.8.0 + +# Download and extract torchvision source. +RUN curl -LO https://github.com/pytorch/vision/archive/refs/tags/v${TORCHVISION_VERSION}.zip && \ + unzip -q v${TORCHVISION_VERSION}.zip && \ + rm v${TORCHVISION_VERSION}.zip + +# Build and install torchvision directly to /opt +RUN mkdir build_torchvision && \ + cd build_torchvision && \ + cmake3 \ + -D CMAKE_BUILD_TYPE=RELEASE \ + -DCMAKE_INSTALL_PREFIX=/opt \ + -DCMAKE_PREFIX_PATH=/opt/libtorch/share/cmake \ + ../vision-${TORCHVISION_VERSION} && \ + make -j${WORKERS} && \ + make install + +FROM ${BASE_IMAGE} + +# Copy everything from /opt in the builder to /opt in the final image +COPY --from=builder /opt /opt \ No newline at end of file diff --git a/sebs/aws/aws.py b/sebs/aws/aws.py index a32c9830..8a7ff425 100644 --- a/sebs/aws/aws.py +++ b/sebs/aws/aws.py @@ -179,7 +179,7 @@ def package_code( raise NotImplementedError() return ( - os.path.join(directory, "{}.zip".format(benchmark)), + benchmark_archive, bytes_size, container_uri, ) diff --git a/sebs/benchmark.py b/sebs/benchmark.py index 5dd8e621..1d8781dd 100644 --- a/sebs/benchmark.py +++ b/sebs/benchmark.py @@ -10,6 +10,8 @@ import docker +from sebs.cpp_dependencies import CppDependencies + from sebs.config import SeBSConfig from sebs.cache import Cache from sebs.faas.config import Resources @@ -29,11 +31,13 @@ def __init__( memory: int, languages: List["Language"], modules: List[BenchmarkModule], + cpp_dependencies: Optional[List[CppDependencies]] = None, ): self._timeout = timeout self._memory = memory self._languages = languages self._modules = modules + self._cpp_dependencies = cpp_dependencies or [] @property def timeout(self) -> int: @@ -69,6 +73,9 @@ def deserialize(json_object: dict) -> "BenchmarkConfig": json_object["memory"], [Language.deserialize(x) for x in json_object["languages"]], [BenchmarkModule(x) for x in json_object["modules"]], + cpp_dependencies=[ + CppDependencies.deserialize(x) for x in json_object.get("cpp_dependencies", []) + ], ) @@ -415,9 +422,11 @@ def add_deployment_package_cpp(self, output_dir): # FIXME: Configure CMakeLists.txt dependencies # FIXME: Configure for AWS - this should be generic # FIXME: optional hiredis + cmake_script = """ cmake_minimum_required(VERSION 3.9) - set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD 14) + set(CMAKE_CXX_FLAGS "-Os") project(benchmark LANGUAGES CXX) add_executable( ${PROJECT_NAME} "handler.cpp" "key-value.cpp" @@ -425,16 +434,17 @@ def add_deployment_package_cpp(self, output_dir): ) target_include_directories(${PROJECT_NAME} PRIVATE ".") - target_compile_features(${PROJECT_NAME} PRIVATE "cxx_std_11") + target_compile_features(${PROJECT_NAME} PRIVATE "cxx_std_14") target_compile_options(${PROJECT_NAME} PRIVATE "-Wall" "-Wextra") find_package(aws-lambda-runtime) target_link_libraries(${PROJECT_NAME} PRIVATE AWS::aws-lambda-runtime) - - find_package(Boost REQUIRED) - target_include_directories(${PROJECT_NAME} PRIVATE ${Boost_INCLUDE_DIRS}) - target_link_libraries(${PROJECT_NAME} PRIVATE ${Boost_LIBRARIES}) - + """ + + for dependency in self._benchmark_config._cpp_dependencies: + cmake_script += CppDependencies.to_cmake_list(dependency) + + cmake_script += """ find_package(AWSSDK COMPONENTS s3 dynamodb core) target_link_libraries(${PROJECT_NAME} PUBLIC ${AWSSDK_LINK_LIBRARIES}) @@ -448,6 +458,12 @@ def add_deployment_package_cpp(self, output_dir): # this line creates a target that packages your binary and zips it up aws_lambda_package_target(${PROJECT_NAME}) """ + + self.logging.info( + "Dependencies for CPP benchmark {benchmark} are " + + str(len(self._benchmark_config._cpp_dependencies)) + " dependencies." + ) + build_script = os.path.join(output_dir, "CMakeLists.txt") with open(build_script, "w") as script_file: script_file.write(textwrap.dedent(cmake_script)) diff --git a/sebs/cpp_dependencies.py b/sebs/cpp_dependencies.py new file mode 100644 index 00000000..43b2a451 --- /dev/null +++ b/sebs/cpp_dependencies.py @@ -0,0 +1,86 @@ +from __future__ import annotations +from enum import Enum +from typing import Optional + +class CppDependencyConfig: + def __init__(self, docker_img: str, cmake_package: str, cmake_libs: str, cmake_dir: Optional[str] = None): + self.docker_img = docker_img + self.cmake_package = cmake_package + self.cmake_dir = cmake_dir + self.cmake_libs = cmake_libs + +class CppDependencies(str, Enum): + """ + Enum for C++ dependencies used in the benchmarks. + """ + TORCH = "torch" + OPENCV = "opencv" + IGRAPH = "igraph" + BOOST = "boost" + HIREDIS = "hiredis" + + @staticmethod + def _dependency_dictionary() -> dict[str, CppDependencyConfig]: + return { + CppDependencies.TORCH: CppDependencyConfig( + docker_img="dependencies-torch.aws.cpp.all", + cmake_package="Torch", + cmake_libs="${TORCH_LIBRARIES} -lm", + cmake_dir="${TORCH_INCLUDE_DIRS}" + ), + CppDependencies.OPENCV: CppDependencyConfig( + docker_img="dependencies-opencv.aws.cpp.all", + cmake_package="OpenCV", + cmake_libs="${OpenCV_LIBS}", + cmake_dir="${OpenCV_INCLUDE_DIRS}" + ), + CppDependencies.IGRAPH: CppDependencyConfig( + docker_img="dependencies-igraph.aws.cpp.all", + cmake_package="igraph", + cmake_libs="igraph::igraph" + ), + CppDependencies.BOOST: CppDependencyConfig( + docker_img="dependencies-boost.aws.cpp.all", + cmake_package="Boost", + cmake_libs="${Boost_LIBRARIES}", + cmake_dir="${Boost_INCLUDE_DIRS}" + ), + CppDependencies.HIREDIS: CppDependencyConfig( + docker_img="dependencies-hiredis.aws.cpp.all", + cmake_package="hiredis", + cmake_libs="hiredis::hiredis" + ), + + } + + @staticmethod + def deserialize(val: str) -> CppDependencies: + for member in CppDependencies: + if member.value == val: + return member + raise Exception(f"Unknown C++ dependency type {val}") + + @staticmethod + def to_cmake_list(dependency: CppDependencies) -> str: + """ + Returns the CMake target for the given C++ dependency. + """ + if dependency not in CppDependencies: + raise ValueError(f"Unknown C++ dependency {dependency}") + dependency_config = CppDependencies._dependency_dictionary()[dependency] + return \ + ''' + find_package({cmake_package} REQUIRED) + '''.format( + cmake_package=dependency_config.cmake_package, + ) + ("" if not dependency_config.cmake_dir else \ + ''' + target_include_directories(${{PROJECT_NAME}} PRIVATE {cmake_dir}) + '''.format( + cmake_dir=dependency_config.cmake_dir + )) + \ + ''' + target_link_libraries(${{PROJECT_NAME}} PRIVATE {cmake_libs}) + '''.format( + cmake_libs=dependency_config.cmake_libs + ) diff --git a/tools/build_docker_images.py b/tools/build_docker_images.py index e4db1508..65a67e59 100755 --- a/tools/build_docker_images.py +++ b/tools/build_docker_images.py @@ -45,18 +45,15 @@ def build(image_type, system, language=None, version=None, version_name=None): if version: target += "." + version sebs_version = config["general"].get("SeBS_version", "unknown") - target += "-" + sebs_version + if not (args.type == "dependencies"): + target += "-" + sebs_version # This should not be appended for dependencies' images # if we pass an integer, the build will fail with 'connection reset by peer' -<<<<<<< HEAD - buildargs = {"VERSION": version, "WORKERS": str(args.parallel)} -======= buildargs = { "VERSION": version, 'WORKERS': str(args.parallel), 'BASE_REPOSITORY': config["general"]["docker_repository"] } ->>>>>>> a9f3c27 ([aws][system] Add C++ dependencies images) if version: buildargs["BASE_IMAGE"] = version_name print( @@ -106,49 +103,18 @@ def build_systems(system, system_config): if args.language: if "dependencies" in system_config["languages"][args.language]: language_config = system_config["languages"][args.language] -<<<<<<< HEAD - # for all dependencies - if args.type_tag: - # for all image versions - for version, base_image in language_config["base_images"].items(): - build( - f"{args.type}.{args.type_tag}", - system, - args.language, - version, - base_image, - ) - else: - for dep in system_config["languages"][args.language][ - "dependencies" - ]: - # for all image versions - for version, base_image in language_config[ - "base_images" - ].items(): - build( - f"{args.type}.{dep}", - system, - args.language, - version, - base_image, - ) - else: - raise RuntimeError("Language must be specified for dependencies") -======= # for all dependencies if args.type_tag: # for all image versions - for version, base_image in language_config["base_images"].items(): + for version, base_image in language_config["base_images"]['x64'].items(): build(f"{args.type}-{args.type_tag}", system, args.language, version, base_image) else: for dep in system_config["languages"][args.language]["dependencies"]: # for all image versions - for version, base_image in language_config["base_images"].items(): + for version, base_image in language_config["base_images"]['x64'].items(): build(f"{args.type}-{dep}", system, args.language, version, base_image) else: raise RuntimeError('Language must be specified for dependencies') ->>>>>>> a9f3c27 ([aws][system] Add C++ dependencies images) else: if args.language: build_language(