diff --git a/docker/.dockerignore b/.dockerignore similarity index 100% rename from docker/.dockerignore rename to .dockerignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e69de29..86efe94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: build and test +on: + push: + branches: + - main + pull_request: + types: [opened, reopened, synchronize, ready_for_review] +jobs: + build_and_test: + runs-on: ubuntu-latest + steps: + - name: get code + uses: actions/checkout@v4 + - name: Setup Bazel environment + uses: bazel-contrib/setup-bazel@0.15.0 + with: + bazelisk-cache: true + disk-cache: ${{ github.workflow }} + repository-cache: true + - name: setup Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: build docker image + run: docker build -f Dockerfile -t cpp-analytics-devops:v1 . + - name: run docker container tests (docker image run in the background) + run: | + docker run -d -p 8080:8080 cpp-analytics-devops:v1 + sleep 5 + curl --fail --show-error --silent "http://127.0.0.1:8080/factorize?number=1000" || echo "API request failed." + sleep 5 + curl --fail --show-error --silent "http://127.0.0.1:8080/simulate/pi?iterations=1000000" || echo "API request failed." \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fdadf86 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +# run this file with: docker build -f Dockerfile -t rest-api-container:v1 . +# build +FROM ubuntu:22.04 as builder +RUN apt-get update && apt-get install -y curl gnupg apt-transport-https g++ clang +RUN apt-get update && apt-get install -y curl unzip \ + && ARCH=$(uname -m | sed 's/aarch64/arm64/') \ + && echo "Detected architecture: $ARCH" \ + && curl -fsSL https://releases.bazel.build/8.4.2/release/bazel-8.4.2-linux-$ARCH \ + -o /usr/local/bin/bazel \ + && chmod +x /usr/local/bin/bazel +WORKDIR /app +COPY . . +RUN bazel build //src:mainProg + +# runtime +FROM ubuntu:22.04 +WORKDIR /app +COPY --from=builder /app/bazel-bin/src/mainProg /app/server +RUN chmod +x /app/server +EXPOSE 8080 +CMD ["./server"] diff --git a/MODULE.bazel b/MODULE.bazel index e69de29..ecf015f 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -0,0 +1,7 @@ +module( + name = "cpp-analytics-docker", + version = "1.0.0", +) + +bazel_dep(name = "googletest", version = "1.17.0") +bazel_dep(name = "cpp-httplib", version = "0.22.0") \ No newline at end of file diff --git a/README.md b/README.md index 2ba9672..a344247 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,141 @@ # cpp-analytics-devops -Project demonstrating C++ algorithms (factorization, Monte Carlo ฯ€) with Bazel builds, Docker images, Kubernetes deployment, autoscaling, monitoring, and CI/CD pipelines + +A modern **C++ microservice project** demonstrating mathematical analytics, and DevOps practices. This project exposes a lightweight REST API for computational mathematics (prime factorization and Monte Carlo ฯ€ estimation) and showcases full integration with **Bazel**, **Docker**, **Kubernetes**, and **GitHub Actions**. + +--- + +## ๐Ÿš€ Features + +### ๐Ÿงฎ Math & Analytics API + +* **Prime Factorization** using Pollardโ€™s Rho and Millerโ€“Rabin primality test. +* **Monte Carlo Simulation** for estimating the value of ฯ€. +* Implemented fully in **C++17**, designed for high performance and modularity. + +### ๐ŸŒ RESTful API + +* Built with [`cpp-httplib`](https://github.com/yhirose/cpp-httplib) (header-only HTTP/HTTPS library). +* Exposes endpoints: + + * `GET /factorize?number=` โ†’ returns factors of the input number. + * `GET /simulate/pi?iterations=` โ†’ estimates ฯ€ via Monte Carlo. + +### ๐Ÿ—๏ธ Modern Build System + +* **Bazel** is used for building, testing, and dependency management. +* Reproducible, cross-platform builds compatible with macOS and Ubuntu. + +### ๐Ÿณ Containerization + +* Multi-stage **Dockerfile**: + + * **Builder stage:** compiles with Bazel. + * **Runtime stage:** runs the compiled binary only (minimal footprint). +* Example build and run: + + ```bash + docker build -t cpp-analytics-devops . + docker run -p 8080:8080 cpp-analytics-devops + ``` + +### โš™๏ธ CI/CD Integration + +* **GitHub Actions** automates: + + * Build and test pipelines. + * Docker image creation and publishing. + * Optional K8s deployment hooks. + +--- + +## ๐Ÿ”ง Endpoints Summary + +| Method | Endpoint | Description | +| ------ | ------------------------------- | ----------------------------------------- | +| GET | `/factorize?number=` | Factorizes the given number. | +| GET | `/simulate/pi?iterations=` | Estimates ฯ€ using Monte Carlo simulation. | + +Example usage: + +```bash +curl "http://localhost:8080/factorize?number=1024" +curl "http://localhost:8080/simulate/pi?iterations=1000000" +``` + +--- + +## ๐Ÿงฑ Project Structure + +``` +cpp-analytics-devops/ +โ”œโ”€โ”€ BUILD # Root Bazel build file +โ”œโ”€โ”€ MODULE.bazel # Bazel module configuration +โ”œโ”€โ”€ src/ # Core source code +โ”‚ โ”œโ”€โ”€ api/ # REST server implementation +โ”‚ โ”œโ”€โ”€ factor.cpp/.h # Pollard's Rho + Millerโ€“Rabin +โ”‚ โ”œโ”€โ”€ montecarlo.cpp/.h # Monte Carlo ฯ€ estimator +โ”‚ โ””โ”€โ”€ main.cpp # Entry point +โ”œโ”€โ”€ tests/ # Unit tests +โ”œโ”€โ”€ docker/ # Dockerfile and related assets +โ”œโ”€โ”€ k8s/ # Kubernetes manifests +โ”œโ”€โ”€ docs/ # Documentation +โ””โ”€โ”€ .github/workflows/ # CI/CD pipelines +``` + +--- + +## โšก Quick Start + +### 1๏ธโƒฃ Build Locally + +```bash +bazel build //src:mainProg +``` + +### 2๏ธโƒฃ Run Locally + +```bash +bazel run //src:mainProg +``` + +Server starts on `http://0.0.0.0:8080`. + +### 3๏ธโƒฃ Docker + +```bash +docker build -t cpp-analytics-devops . +docker run -p 8080:8080 cpp-analytics-devops +``` + +--- + +## ๐Ÿงฉ Tech Stack + +| Category | Tool | +| ----------------------------- | ------------------------------ | +| Language | C++17 | +| Build System | Bazel | +| Web Framework | cpp-httplib | +| CI/CD | GitHub Actions | +| Containerization | Docker | +| Orchestration(future work) | Kubernetes | +| DB (future work) | PostgreSQL integration planned | + +--- + +## ๐Ÿง  Future Work + +* Add Kubernetes for orchestration +* Integrate PostgreSQL for result persistence. +* Add caching layer for factorization results. + +--- + +## ๐Ÿ“„ License + +Licensed under the [MIT License](./LICENSE). + +--- + +**Author:** RaphaelRevivor +**Repository:** [cpp-analytics-devops](https://github.com/RaphaelRevivor/cpp-analytics-devops) diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index e69de29..0000000 diff --git a/src/BUILD b/src/BUILD index b268e1c..f2db14b 100644 --- a/src/BUILD +++ b/src/BUILD @@ -6,6 +6,9 @@ cc_library( hdrs = [ "factor.h" ], + visibility = [ + "//:__subpackages__", + ], ) cc_library( @@ -16,6 +19,9 @@ cc_library( hdrs = [ "montecarlo.h" ], + visibility = [ + "//:__subpackages__", + ], ) cc_binary( @@ -27,5 +33,6 @@ cc_binary( deps = [ ":factorLib", ":montecarloLib", + "//src/api:restapiLib", ], ) \ No newline at end of file diff --git a/src/api/BUILD b/src/api/BUILD index e69de29..bd53c22 100644 --- a/src/api/BUILD +++ b/src/api/BUILD @@ -0,0 +1,17 @@ +cc_library( + name = "restapiLib", + srcs = [ + "rest.cpp" + ], + hdrs = [ + "rest.h", + ], + deps = [ + "@cpp-httplib//:httplib", + "//src:montecarloLib", + "//src:factorLib", + ], + visibility = [ + "//:__subpackages__", + ], +) \ No newline at end of file diff --git a/src/api/rest.cpp b/src/api/rest.cpp index e69de29..212d1dc 100644 --- a/src/api/rest.cpp +++ b/src/api/rest.cpp @@ -0,0 +1,36 @@ +#include "rest.h" + +int RestApi::runServer() +{ + svr.Get("/factorize", [&] (const Request& req, Response& res) { + auto nStr = req.get_param_value("number"); + int64_t n = stoll(nStr); + auto factorization = Factorization(n); + auto factorsStr = factorization.getAllFactorsInString(); + + // cout << "Server is trying to reply: " << factorsStr << endl; + + res.status = 200; + res.set_content(factorsStr, "text/plain"); + }); + + svr.Get("/simulate/pi", [&] (const Request& req, Response& res) { + auto nStr = req.get_param_value("iterations"); + int64_t n = stoll(nStr); + auto montecarloPi = MonteCarloPi(n); + auto piStr = to_string(montecarloPi.estimatePi()); + + // cout << "Server is trying to reply: " << piStr << endl; + + res.status = 200; + res.set_content(piStr, "text/plain"); + }); + + if (!svr.listen("0.0.0.0", 8080)) { + std::cerr << "Error starting server\n"; + return 1; + } +} + + + diff --git a/src/api/rest.h b/src/api/rest.h index e69de29..bcddee0 100644 --- a/src/api/rest.h +++ b/src/api/rest.h @@ -0,0 +1,28 @@ +#ifndef REST_H +#define REST_H + +// #define CPPHTTPLIB_OPENSSL_SUPPORT + +#ifdef _MSC_VER +#define WIN32_LEAN_AND_MEAN +#include +#endif +#include + +#include "src/factor.h" +#include "src/montecarlo.h" + +using namespace httplib; + +class RestApi +{ +public: + RestApi() = default; + ~RestApi() = default; + int runServer(); + +private: + Server svr; +}; + +#endif \ No newline at end of file diff --git a/src/factor.cpp b/src/factor.cpp index e3db641..05f023f 100644 --- a/src/factor.cpp +++ b/src/factor.cpp @@ -10,6 +10,20 @@ Factorization::Factorization(int64_t n) : n(n) srand(time(0)); } +string Factorization::getAllFactorsInString() +{ + string output = ""; + auto factors = getAllFactors(); + for(auto iter = factors.begin(); iter != factors.end(); iter++) + { + output += to_string(*iter); + if (iter != prev(factors.end())) + output += ","; + } + output += "\n"; + return output; +} + vector Factorization::getAllFactors() { vector output = {}; @@ -92,7 +106,11 @@ int64_t Factorization::selectX0(const int64_t& input) /// @return f(x) int64_t Factorization::squareMod(const int64_t& x, const int64_t& c, const int64_t& input) { +#ifdef _MSC_VER + int64_t output = (x * x + c) % input; +#else __int128_t output = (x * x + c) % input; +#endif return output; } @@ -147,9 +165,13 @@ bool Factorization::millerRabin(const int64_t& input) if (base >= input) continue; - // calculate x = a^d % n +#ifdef _MSC_VER + int64_t x = mod_pow(base, d, input); +#else __int128_t x = mod_pow(base, d, input); +#endif + // calculate x = a^d % n if (x == 1 || x == input - 1) continue; @@ -175,7 +197,12 @@ bool Factorization::millerRabin(const int64_t& input) /// @return mod power result int64_t Factorization::mod_pow(const int64_t& b, const int64_t& d, const int64_t& input) { + +#ifdef _MSC_VER + int64_t result = 1; +#else __int128_t result = 1; +#endif int64_t base = b; int64_t dd = d; base = base % input; diff --git a/src/factor.h b/src/factor.h index d1a1497..e4d0240 100644 --- a/src/factor.h +++ b/src/factor.h @@ -8,6 +8,7 @@ #include #include #include +#include using namespace std; @@ -17,6 +18,7 @@ class Factorization Factorization(int64_t n); ~Factorization() = default; vector getAllFactors(); + string getAllFactorsInString(); int64_t getFactor(const int64_t& input); private: diff --git a/src/main.cpp b/src/main.cpp index 22870bd..1fc7cef 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,29 +4,32 @@ using namespace std; int main(int argc, char** argv) { - string input = ""; - if (argc > 1) - input = argv[1]; - else - { - throw invalid_argument("No argument is passed!"); - } + // string input = ""; + // if (argc > 1) + // input = argv[1]; + // else + // { + // throw invalid_argument("No argument is passed!"); + // } - // might throw std::invalid_argument or std::out_of_range in case not able to parse - int64_t n = stoll(input); + // // might throw std::invalid_argument or std::out_of_range in case not able to parse + // int64_t n = stoll(input); - auto factorization = Factorization(n); + // auto factorization = Factorization(n); - cout << "Factors of " << n << " are: " << endl; - auto results = factorization.getAllFactors(); + // cout << "Factors of " << n << " are: " << endl; + // auto results = factorization.getAllFactors(); - for(const auto& result : results) - { - cout << result << ","; - } - cout << endl; + // for(const auto& result : results) + // { + // cout << result << ","; + // } + // cout << endl; - auto monteCarloPi = MonteCarloPi(); - double pi = monteCarloPi.estimatePi(); - cout << "The estimated pi is: " << pi << endl; + // auto monteCarloPi = MonteCarloPi(); + // double pi = monteCarloPi.estimatePi(); + // cout << "The estimated pi is: " << pi << endl; + + auto restApi = RestApi(); + (void)restApi.runServer(); } \ No newline at end of file diff --git a/src/main.h b/src/main.h index 1342c80..197d1ed 100644 --- a/src/main.h +++ b/src/main.h @@ -5,5 +5,6 @@ #include #include "factor.h" #include "montecarlo.h" +#include "api/rest.h" #endif \ No newline at end of file diff --git a/src/montecarlo.cpp b/src/montecarlo.cpp index 6b796dd..8ee221c 100644 --- a/src/montecarlo.cpp +++ b/src/montecarlo.cpp @@ -3,9 +3,8 @@ using namespace std; const int NUM = 8; -const int NUM_EACH_THREAD = 10000000; -MonteCarloPi::MonteCarloPi() +MonteCarloPi::MonteCarloPi(int n) : n(n) { inCircleVec.resize(NUM, 0); } @@ -16,7 +15,9 @@ void MonteCarloPi::estimatePiSingleThread(int threadId) // mersenne_twister_engine seeded with rd() mt19937 gen(rd()); uniform_real_distribution distrib(0.0, 1.0); - for(int i = 0; i < NUM_EACH_THREAD; i++) + // for last thread, run a bit more + int limit = threadId == NUM - 1 ? n / NUM + (n - NUM * (n / NUM)) : n / NUM; + for(int i = 0; i < limit; i++) { double x = distrib(gen); double y = distrib(gen); @@ -38,5 +39,5 @@ double MonteCarloPi::estimatePi() } // return pi here - return (4.0 * accumulate(inCircleVec.begin(), inCircleVec.end(), 0) / (NUM * NUM_EACH_THREAD)); + return (4.0 * accumulate(inCircleVec.begin(), inCircleVec.end(), 0) / n); } \ No newline at end of file diff --git a/src/montecarlo.h b/src/montecarlo.h index 9e7743e..5c33d00 100644 --- a/src/montecarlo.h +++ b/src/montecarlo.h @@ -6,6 +6,7 @@ #include #include #include +#include using namespace std; @@ -14,13 +15,14 @@ class MonteCarloPi public: MonteCarloPi& operator=(const MonteCarloPi&) = delete; - MonteCarloPi(); + MonteCarloPi(int n); ~MonteCarloPi() = default; void estimatePiSingleThread(int threadId); double estimatePi(); private: + int n = 0; vector threads = {}; vector inCircleVec = {}; };