diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7895687 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +CC=g++ +CFLAGS=-Werror -Wall -Wextra -Ofast -pedantic + +all: build + +build: + $(CC) main.cpp -o httpsbench -lssl -lcrypto -lcurl -pthread $(CFLAGS) + +run: build + @echo "Usage: ./httpsbench -c <ThreadCount> -n <RequestCount> <URL>" + @echo "Example: ./httpsbench -c 10 -n 1000 http://example.com" + ./httpsbench $(ARGS) + +.PHONY: all build run \ No newline at end of file diff --git a/README.md b/README.md index 5c7fe3d..3b5148d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,45 @@ -# httpsbench +# HTTPS Benchmark Tool -Application for benchmarking https servers \ No newline at end of file +This tool is designed to benchmark HTTP requests using a thread pool for concurrent execution. + +## Usage + +To use the tool, compile and run it with the following commands: + +```bash +make build +./httpsbench -c <ThreadCount> -n <RequestCount> <URL> +``` + +### Example + +```bash +./httpsbench -c 10 -n 1000 http://example.com +``` + +## Parameters + +- `-c <ThreadCount>`: Number of threads to use in the thread pool. +- `-n <RequestCount>`: Number of HTTP requests to make. +- `<URL>`: The URL to benchmark. + +## Output + +The tool will output the elapsed time in seconds and milliseconds, as well as the requests per second. + +## Dependencies + +- libcurl +- libssl +- libcrypto + +## Files + +- `main.cpp`: Main program file. +- `threadpool.hpp`: Thread pool implementation. +- `http.hpp`: HTTP request function declaration. +- `Makefile`: Build script. + +## License + +This project is licensed under the MIT License - see the LICENSE.md file for details. diff --git a/http.hpp b/http.hpp new file mode 100644 index 0000000..8d8e2bc --- /dev/null +++ b/http.hpp @@ -0,0 +1,50 @@ +#include <curl/curl.h> +#include <stdio.h> + + +// Callback function for handling data received from the server +size_t write_callback(void *buffer, size_t size, size_t nmemb, void *userp) { + size_t total_size = size * nmemb; + fwrite(buffer, size, nmemb, (FILE *)userp); + return total_size; +} + +void http_get(const char * url){ + CURL *curl; + CURLcode res; + + curl = curl_easy_init(); + + if (curl) { + // Set the URL for the request + curl_easy_setopt(curl, CURLOPT_URL, url); + + // Use HTTPS + curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL); + + // Verify the server's SSL certificate + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); // Enable SSL certificate verification + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); // Verify that the certificate matches the hostname + + // Optional: Specify CA certificate if the default is not sufficient + // curl_easy_setopt(curl, CURLOPT_CAINFO, "/path/to/cacert.pem"); + + // Set the write callback function to handle the response + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, stdout); + + // Perform the request + res = curl_easy_perform(curl); + + // Check for errors + if (res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); + } + + // Cleanup + curl_easy_cleanup(curl); + } + +} + + diff --git a/httpsbench b/httpsbench new file mode 100755 index 0000000..28cddaa Binary files /dev/null and b/httpsbench differ diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..76a307b --- /dev/null +++ b/main.cpp @@ -0,0 +1,82 @@ +#include <iostream> +#include <vector> +#include <thread> +#include <queue> +#include <functional> +#include <condition_variable> +#include "threadpool.hpp" +#include "http.hpp" +#include <chrono> + +std::mutex mtx; // Mutex to protect shared data +int threads_working = 0; + +// Function to simulate work that requires thread synchronization +void incThreadsWorking() { + mtx.lock(); // Lock the mutex to enter the critical section + threads_working++; + mtx.unlock(); // Unlock the mutex to allow other threads to enter the critical section +} +void decThreadsWorking(){ + mtx.lock(); + threads_working--; + mtx.unlock(); +} + +int main(int argc, char* argv[]) { + const char* url = nullptr; + int threadCount = 10; // Default value + int requestCount = 1000; // Default value + + // Parse command-line arguments + for (int i = 1; i < argc; ++i) { + if (std::string(argv[i]) == "-c" && i + 1 < argc) { + threadCount = std::stoi(argv[++i]); + } else if (std::string(argv[i]) == "-n" && i + 1 < argc) { + requestCount = std::stoi(argv[++i]); + } else { + url = argv[i]; // Assume the last argument is the URL + } + } + + if (url == nullptr) { + std::cerr << "Usage: " << argv[0] << " -c <ThreadCount> -n <RequestCount> <URL>" << std::endl; + return 1; + } + + // Initialize libcurl + curl_global_init(CURL_GLOBAL_DEFAULT); + + auto start = std::chrono::steady_clock::now(); + + ThreadPool pool(threadCount); // Create a pool with specified number of threads + + // Submit tasks to the thread pool + for (int i = 0; i < requestCount; ++i) { + pool.enqueue([url, i] { + incThreadsWorking(); + http_get(url); + decThreadsWorking(); + std::cout << "Threads working: " << threads_working << std::endl; + }); + } + + while(threads_working){ + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + auto end = std::chrono::steady_clock::now(); + + auto duration = std::chrono::duration_cast<std::chrono::seconds>(end - start); + std::cout << "Elapsed time in seconds: " << duration.count() << " seconds." << std::endl; + + auto duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); + std::cout << "Elapsed time in milliseconds: " << duration_ms.count() << " ms." << std::endl; + + std::cout << "Requests per second: " << requestCount / duration.count() << "." << std::endl; + + // Cleanup global libcurl state + curl_global_cleanup(); + + return 0; +} \ No newline at end of file diff --git a/threadpool.hpp b/threadpool.hpp new file mode 100644 index 0000000..5c290a0 --- /dev/null +++ b/threadpool.hpp @@ -0,0 +1,78 @@ +#include <iostream> +#include <vector> +#include <thread> +#include <queue> +#include <functional> +#include <condition_variable> +#include <atomic> + +class ThreadPool { +public: + ThreadPool(size_t numThreads) : stop(false) { + for (size_t i = 0; i < numThreads; ++i) { + workers.emplace_back([this] { + while (true) { + std::function<void()> task; + + // Lock the queue to retrieve the next task + { + std::unique_lock<std::mutex> lock(queueMutex); + condition.wait(lock, [this] { + return stop || !tasks.empty(); + }); + + if (stop && tasks.empty()) { + return; + } + + // Get the next task from the queue + task = std::move(tasks.front()); + tasks.pop(); + } + + // Execute the task + task(); + } + }); + } + } + + // Submit a task to the thread pool + template <typename F> + void enqueue(F&& f) { + { + std::unique_lock<std::mutex> lock(queueMutex); + if (stop) { + throw std::runtime_error("ThreadPool is stopped"); + } + tasks.push(std::function<void()>(std::forward<F>(f))); + } + condition.notify_one(); + } + + // Wait for all threads to finish executing their tasks + void wait() { + // Block until all tasks have been completed + std::unique_lock<std::mutex> lock(queueMutex); + condition.wait(lock, [this] { return tasks.empty(); }); + } + + // Destructor: join all threads + ~ThreadPool() { + { + std::unique_lock<std::mutex> lock(queueMutex); + stop = true; + } + condition.notify_all(); + for (std::thread& worker : workers) { + worker.join(); // Ensure each thread finishes before destruction + } + } + +private: + std::vector<std::thread> workers; + std::queue<std::function<void()>> tasks; + std::mutex queueMutex; + std::condition_variable condition; + bool stop; +};