diff --git a/ASP.NET/Services/ImageService.cs b/ASP.NET/Services/ImageService.cs index dfa6ce3..e66e2b5 100644 --- a/ASP.NET/Services/ImageService.cs +++ b/ASP.NET/Services/ImageService.cs @@ -9,11 +9,13 @@ namespace TCC.Services public MagickImage BoxBlurImage(Stream imageStream, int radius) { var image = new MagickImage(imageStream); - var blurredImage = new MagickImage(image); + image.GaussianBlur(radius, radius); + return image; + //var blurredImage = new MagickImage(image); - blurredImage = BoxBlurImageSeparable(image, blurredImage, radius, 0); - blurredImage = BoxBlurImageSeparable(blurredImage, blurredImage, 0, radius); - return blurredImage; + //blurredImage = BoxBlurImageSeparable(image, blurredImage, radius, 0); + //blurredImage = BoxBlurImageSeparable(blurredImage, blurredImage, 0, radius); + //return blurredImage; } private MagickImage BoxBlurImageSeparable(MagickImage image, MagickImage blurredImage, int radiusX, int radiusY) diff --git a/ActixAPI/.dockerignore b/ActixAPI/.dockerignore new file mode 100644 index 0000000..8487999 --- /dev/null +++ b/ActixAPI/.dockerignore @@ -0,0 +1,2 @@ +Dockerfile +target/ \ No newline at end of file diff --git a/ActixAPI/Cargo.lock b/ActixAPI/Cargo.lock index ab09c63..3784675 100644 --- a/ActixAPI/Cargo.lock +++ b/ActixAPI/Cargo.lock @@ -8,6 +8,8 @@ version = "0.1.0" dependencies = [ "actix-files", "actix-web", + "magick_rust", + "qstring", ] [[package]] @@ -296,6 +298,29 @@ version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +[[package]] +name = "bindgen" +version = "0.68.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" +dependencies = [ + "bitflags 2.4.0", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.32", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -363,12 +388,32 @@ dependencies = [ "libc", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -443,6 +488,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -452,6 +503,27 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "flate2" version = "1.0.27" @@ -534,6 +606,12 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "h2" version = "0.3.21" @@ -559,6 +637,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + [[package]] name = "http" version = "0.2.9" @@ -629,12 +716,40 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" + [[package]] name = "local-channel" version = "0.1.3" @@ -669,6 +784,17 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "magick_rust" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b69250bcd5d024852a1a75c567d3b5d881871a55b741018741632a921bf8" +dependencies = [ + "bindgen", + "libc", + "pkg-config", +] + [[package]] name = "memchr" version = "2.6.3" @@ -691,6 +817,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -712,6 +844,16 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "object" version = "0.32.1" @@ -756,6 +898,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.3.0" @@ -786,6 +934,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.32", +] + [[package]] name = "proc-macro2" version = "1.0.66" @@ -795,6 +953,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "qstring" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +dependencies = [ + "percent-encoding", +] + [[package]] name = "quote" version = "1.0.33" @@ -878,6 +1045,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -887,6 +1060,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.15" @@ -959,6 +1145,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1169,6 +1361,40 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/ActixAPI/Cargo.toml b/ActixAPI/Cargo.toml index 48d2712..776380e 100644 --- a/ActixAPI/Cargo.toml +++ b/ActixAPI/Cargo.toml @@ -7,4 +7,6 @@ edition = "2021" [dependencies] actix-web = "4" -actix-files = "0.6.2" \ No newline at end of file +actix-files = "0.6.2" +magick_rust = "0.19.1" +qstring = "0.7.2" diff --git a/ActixAPI/Dockerfile b/ActixAPI/Dockerfile index dce9b80..aa2f7da 100644 --- a/ActixAPI/Dockerfile +++ b/ActixAPI/Dockerfile @@ -1,25 +1,34 @@ -FROM rust:slim-bullseye AS build-env +FROM rust:latest + +ENV MAGICK_VERSION 7.1 + +RUN apt-get update \ + && apt-get -y install wget curl build-essential clang pkg-config libjpeg-turbo-progs libpng-dev + +RUN apt update && apt install curl -y \ + && curl https://imagemagick.org/archive/ImageMagick.tar.gz | tar xz \ + && cd ImageMagick-${MAGICK_VERSION}* \ + && ./configure --with-magick-plus-plus=no --with-perl=no \ + && make \ + && make install \ + && cd .. \ + && rm -r ImageMagick-${MAGICK_VERSION}* WORKDIR /app +RUN wget https://files.ivanch.me/api/public/dl/iFuXSNhw/small-image.png && \ + wget https://files.ivanch.me/api/public/dl/81Bkht5C/big-image.png && \ + wget https://files.ivanch.me/api/public/dl/nAndfAjK/video.mp4 + COPY . . -RUN apt update && apt install wget -y && \ - wget https://files.ivanch.me/api/public/dl/iFuXSNhw/small-image.png && \ - wget https://files.ivanch.me/api/public/dl/81Bkht5C/big-image.png && \ - wget https://files.ivanch.me/api/public/dl/nAndfAjK/video.mp4 && \ +RUN cargo build --release && \ + cp ./target/release/ActixAPI . && \ mv small-image.png ./static && \ mv big-image.png ./static && \ - mv video.mp4 ./static + mv video.mp4 ./static && \ + ldconfig /usr/local/lib -RUN cargo build --release - -FROM debian:bullseye-slim - -WORKDIR /app - -COPY --from=build-env /app/target/release . - -COPY --from=build-env /app/static ./static +ENV LD_LIBRARY_PATH=/usr/local/lib ENTRYPOINT ["./ActixAPI"] diff --git a/ActixAPI/src/main.rs b/ActixAPI/src/main.rs index 09c170f..878169d 100644 --- a/ActixAPI/src/main.rs +++ b/ActixAPI/src/main.rs @@ -1,21 +1,13 @@ +use qstring::QString; use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, HttpRequest, Result}; use actix_files::NamedFile; -use std::path::PathBuf; +use magick_rust::MagickWand; #[get("/status/ok")] async fn hello() -> impl Responder { HttpResponse::Ok().body("{\"status\": 200}") } -#[post("/echo")] -async fn echo(req_body: String) -> impl Responder { - HttpResponse::Ok().body(req_body) -} - -async fn manual_hello() -> impl Responder { - HttpResponse::Ok().body("Hey there!") -} - async fn static_serve(req: HttpRequest) -> Result { let path: &str = req.path(); let real_path = &path[1..]; @@ -23,6 +15,53 @@ async fn static_serve(req: HttpRequest) -> Result { Ok(NamedFile::open(real_path)?) } +#[get("/image/load-small-image")] +async fn load_small_image() -> Result { + let real_path = "static/small-image.png"; + + Ok(NamedFile::open(real_path)?) +} + +#[get("/image/load-big-image")] +async fn load_big_image() -> Result { + let real_path = "static/big-image.png"; + + Ok(NamedFile::open(real_path)?) +} + +#[post("/image/save-big-image")] +async fn save_big_image(image_data: web::Bytes) -> Result { + // Write bytes to file + std::fs::write("image.png", &image_data).unwrap(); + + // Return the blurred image bytes + Ok(HttpResponse::Ok() + .content_type("application/json") + .body("{\"status\": 200}")) +} + +#[post("/image/blur")] +async fn blur_image(image_data: web::Bytes, req: HttpRequest) -> Result { + // Load the image from the incoming bytes + let mut wand = MagickWand::new(); + wand.read_image_blob(&image_data).unwrap(); + + let query_str = req.query_string(); + let qs = QString::from(query_str); + let radius = qs.get("radius").unwrap_or("5").parse::().unwrap_or(5.0); + + // Blur the image + wand.blur_image(radius, radius).unwrap(); + + // Convert the image back to bytes + let blurred_image_bytes = wand.write_image_blob("png").unwrap(); + + // Return the blurred image bytes + Ok(HttpResponse::Ok() + .content_type("image/png") + .body(blurred_image_bytes)) +} + #[actix_web::main] async fn main() -> std::io::Result<()> { println!("Hello, world!"); @@ -31,10 +70,13 @@ async fn main() -> std::io::Result<()> { App::new() .route("/static/{filename:.*}", web::get().to(static_serve)) .service(hello) - .service(echo) - .route("/hey", web::get().to(manual_hello)) + .service(load_small_image) + .service(load_big_image) + .service(save_big_image) + .service(blur_image) + .app_data(web::PayloadConfig::new(1024 * 1024 * 1024)) }) - .bind(("0.0.0.0", 9090))? + .bind(("0.0.0.0", 5000))? .run() .await } \ No newline at end of file diff --git a/FlaskAPI/controllers/image.py b/FlaskAPI/controllers/image.py index aac4dbc..6fb10b9 100644 --- a/FlaskAPI/controllers/image.py +++ b/FlaskAPI/controllers/image.py @@ -11,10 +11,10 @@ def blur_image(): image = request.get_data() if radius and image: - return send_file(image_service.box_blur_image(image, radius), - mimetype='image/jpeg', + return send_file(io.BytesIO(image_service.box_blur_image(image, radius)), + mimetype='image/png', as_attachment=True, - download_name='blurred_image.jpeg') + download_name='blurred_image.png') return "Bad request", 400 diff --git a/FlaskAPI/services/image.py b/FlaskAPI/services/image.py index e4caeac..2ced7c5 100644 --- a/FlaskAPI/services/image.py +++ b/FlaskAPI/services/image.py @@ -1,29 +1,33 @@ -from wand.image import Image +from wand.image import Image, Color def box_blur_image_separable(image, radius_x, radius_y): - blurred_image = image.clone() width, height = image.width, image.height + kernel_x_size = 2 * radius_x + 1 + kernel_y_size = 2 * radius_y + 1 + kernel_area = kernel_x_size * kernel_y_size + + blurred_image = image.clone() for y in range(height): for x in range(width): - blurred_image[x, y] = image[x, y] - r_total, g_total, b_total = 0, 0, 0 - pixel_count = 0 + for offset_y in range(-radius_y, radius_y + 1): for offset_x in range(-radius_x, radius_x + 1): new_x = x + offset_x new_y = y + offset_y if 0 <= new_x < width and 0 <= new_y < height: - r_total += image[new_x, new_y].red_int8 - g_total += image[new_x, new_y].green_int8 - b_total += image[new_x, new_y].blue_int8 - pixel_count += 1 + pixel = image[new_x, new_y] + r_total += pixel.red_int8 + g_total += pixel.green_int8 + b_total += pixel.blue_int8 - blurred_image[x, y].red_int8 = int(r_total / pixel_count) - blurred_image[x, y].green_int8 = int(g_total / pixel_count) - blurred_image[x, y].blue_int8 = int(b_total / pixel_count) + r_avg = int(r_total / kernel_area) + g_avg = int(g_total / kernel_area) + b_avg = int(b_total / kernel_area) + + blurred_image[x, y] = Color(f'rgb({r_avg},{g_avg},{b_avg})') return blurred_image @@ -33,9 +37,12 @@ class ImageService: def box_blur_image(self, img, radius): with Image(blob=img) as image: - blurred_image = box_blur_image_separable(image, radius, 0) - blurred_image = box_blur_image_separable(blurred_image, 0, radius) - return blurred_image.make_blob() + image.gaussian_blur(radius, radius) + return image.make_blob() + + # blurred_image = box_blur_image_separable(image, radius, 0) + # blurred_image = box_blur_image_separable(blurred_image, 0, radius) + # return blurred_image.make_blob() def get_simple_image(self): with open("./static/small-image.png", "rb") as file: diff --git a/FlaskAPI/temp_image.jpeg b/FlaskAPI/temp_image.jpeg deleted file mode 100644 index 25783da..0000000 Binary files a/FlaskAPI/temp_image.jpeg and /dev/null differ diff --git a/docker-compose.yml b/docker-compose.yml index 6ec6e82..373409b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,10 +31,21 @@ services: build: ./ActixAPI restart: always ports: - - "9083:9090" + - "9083:5000" + deploy: + resources: + limits: + cpus: '1' + memory: 1GB + tcc-express: + image: tcc:express + container_name: tcc-express + build: ./tcc-express + restart: always + ports: + - "9084:5000" deploy: resources: limits: cpus: '1' memory: 1GB - diff --git a/scripts/common.py b/scripts/common.py new file mode 100644 index 0000000..0c04643 --- /dev/null +++ b/scripts/common.py @@ -0,0 +1,27 @@ +FRAMEWORKS = [ + ('Actix', 'tcc-actix'), + ('ASP.NET', 'tcc-aspnet'), + ('Flask', 'tcc-flask'), + ('Express', 'tcc-express'), +] + +ENDPOINTS = { + 'Actix': 'http://localhost:9083', + 'ASP.NET': 'http://localhost:9081', + 'Flask': 'http://localhost:9082', + 'Express': 'http://localhost:9084', +} + +BLUR_RADIUS = 5 +AVG_RUNS = 3 + +API_REQUESTS = [ + ('/status/ok', 'GET', range(0, 30_000, 5000), None), + ('/image/save-big-image', 'POST', range(0, 10_000, 1_000), open('big-image.png', 'rb').read()), + (f'/image/blur?radius={BLUR_RADIUS}', 'POST', range(0, 1_000, 50), open('small-image.png', 'rb').read()), + ('/image/load-small-image', 'GET', range(0, 30_000, 5000), None), + ('/static/small-image.png', 'GET', range(0, 30_000, 5000), None), + ('/image/load-big-image', 'GET', range(0, 500, 50), None), + ('/static/big-image.png', 'GET', range(0, 500, 50), None), + ('/static/video.mp4', 'GET', range(0, 10_000, 1_000), None), +] \ No newline at end of file diff --git a/scripts/graph.py b/scripts/graph.py index 6a82475..9e058a9 100644 --- a/scripts/graph.py +++ b/scripts/graph.py @@ -1,11 +1,17 @@ import numpy as np import matplotlib.pyplot as plt +from common import API_REQUESTS, FRAMEWORKS + +FRAMEWORKS = [f for f, _ in FRAMEWORKS] + +def plot_graph(x_data, y_data, title, x_label, y_label, filename): + for i, framework in enumerate(FRAMEWORKS): + plt.plot(x_data, y_data[i], markersize=1, linewidth=1, linestyle='solid', label=framework) -def plot_graph(x, y, title, x_label, y_label, filename): - plt.plot(x, y, 'ro', markersize=1, linewidth=0.5, linestyle='solid') plt.title(title) plt.xlabel(x_label) plt.ylabel(y_label) + plt.legend() plt.savefig(f'{filename}.png') plt.clf() @@ -13,18 +19,22 @@ def plot_graph(x, y, title, x_label, y_label, filename): def plot_resource_graph(x_data, y_data, title, x_label, y_label, filename): requests = x_data - resource = { - 'CPU': [p[0] for p in y_data], - 'RAM': [p[1] for p in y_data], - } + frameworks = {} + print(y_data) + for i, framework in enumerate(FRAMEWORKS): + frameworks[framework] = y_data[i] x = np.arange(len(requests)) - width = 0.25 + width = 0.2 multiplier = 0 - fig, ax = plt.subplots(layout='constrained') + fig, ax = plt.subplots(layout='constrained', figsize=(7.5, 5)) - for attribute, measurement in resource.items(): + print(x) + for framework, measurements in frameworks.items(): + print(framework, measurements) + + for attribute, measurement in frameworks.items(): offset = width * multiplier rects = ax.bar(x + offset, measurement, width, label=attribute) @@ -35,9 +45,9 @@ def plot_resource_graph(x_data, y_data, title, x_label, y_label, filename): ax.set_xlabel(x_label) ax.set_ylabel(y_label) ax.set_title(title) - ax.set_xticks(x + (width/2), requests) - ax.legend(loc='upper left', ncols=len(resource.items())) - ax.set_ylim(0, 100) + ax.set_xticks(x + (width*1.5), requests) + ax.legend(loc='upper left', ncols=len(frameworks.items())) + ax.set_ylim(0, 115) plt.savefig(f'{filename}.png') @@ -69,21 +79,54 @@ def get_resource_data(filename): for line in lines: line = line.strip().split(',') if line: + r = [round(float(line[1])*100), round(float(line[2])*100)] + if r[0] > 100: + r[0] = 100 + + if r[1] > 100: + r[1] = 100 + x.append(int(line[0])) # requests - y.append([float(v)*100 for v in line[1:]]) # cpu, ram + y.append(r) # cpu, ram return x, y def generate_req_graph(filename, framework_name, endpoint_name): - x, y = get_data(filename) + x, _ = get_data(filename) + y = [] - filename = filename.split('/')[-1] - new_filename = filename.replace('.csv', '') - plot_graph(x, y, f'{framework_name} - {endpoint_name}', 'Número de requisições', 'Requisições por segundo', new_filename) + for f in FRAMEWORKS: + newfile = filename.replace(framework_name, f) + _, y_data = get_data(newfile) + + y.append(y_data) + + graph_file = f'req_{endpoint_name.replace("/", "").replace("?", "")}' + plot_graph(x, y, f'Requisições atendidas por segundo - {endpoint_name}', 'Número de requisições', 'Requisições/segundo', graph_file) def generate_resource_graph(filename, framework_name, endpoint_name): - x, y = get_resource_data(filename) + x, _ = get_resource_data(filename) - filename = filename.split('/')[-1] - new_filename = filename.replace('.csv', '') - plot_resource_graph(x, y, f'{framework_name} - {endpoint_name}', 'Uso de recursos', 'Uso (%)', new_filename) \ No newline at end of file + for resource_index, resource in enumerate(['cpu', 'ram']): + y = [] + + for f in FRAMEWORKS: + newfile = filename.replace(framework_name, f) + _, y_data = get_resource_data(newfile) + y.append([data[resource_index] for data in y_data]) + + graph_file = f'{resource}_{endpoint_name.replace("/", "").replace("?", "")}' + plot_resource_graph(x, y, f'Uso de {resource.upper()} - {endpoint_name}', 'Número de requisições', f'Uso de {resource.upper()} (%)', graph_file) + +if __name__ == '__main__': + endpoints = [config[0] for config in API_REQUESTS] + + for endpoint_name in endpoints: + framework_name = 'ASP.NET' + endpoint_file = endpoint_name.replace('/', '') + + filename = f'data/resource_ASP.NET_{endpoint_file}.csv' + generate_resource_graph(filename, framework_name, endpoint_name) + + filename = f'data/req_ASP.NET_{endpoint_file}.csv' + generate_req_graph(filename, framework_name, endpoint_name) diff --git a/scripts/testes.py b/scripts/testes.py index 10eb847..93c7f5a 100644 --- a/scripts/testes.py +++ b/scripts/testes.py @@ -2,33 +2,17 @@ import requests import docker import concurrent.futures import time -import sys import os -from graph import generate_req_graph, generate_resource_graph from math import floor from init import init - -if len(sys.argv) < 2 or len(sys.argv) > 3 or sys.argv[1] == '-h' or sys.argv[1] == '--help': - print("Usage: python testes.py [container name]") - sys.exit(1) +from common import FRAMEWORKS, ENDPOINTS, API_REQUESTS, AVG_RUNS init() THREADS = 10 -FRAMEWORK_NAME = sys.argv[1] -CONTAINER_NAME = sys.argv[2] if len(sys.argv) > 2 else "" +FRAMEWORK_NAME = "" +CONTAINER_NAME = "" URL_BASE = 'http://localhost:9090' -BLUR_RADIUS = 5 -API_REQUESTS = [ - ('/image/save-big-image', 'POST', range(0, 10_000, 1_000), open('big-image.png', 'rb').read()), - (f'/image/blur?radius={BLUR_RADIUS}', 'POST', range(0, 1_000, 50), open('small-image.png', 'rb').read()), - ('/status/ok', 'GET', range(0, 30_000, 5000), None), - ('/image/load-small-image', 'GET', range(0, 30_000, 5000), None), - ('/static/small-image.png', 'GET', range(0, 30_000, 5000), None), - ('/image/load-big-image', 'GET', range(0, 500, 50), None), - ('/static/big-image.png', 'GET', range(0, 500, 50), None), - ('/static/video.mp4', 'GET', range(0, 10_000, 1_000), None), -] def send_request(url, method = 'GET', payload = None): success = False @@ -77,46 +61,57 @@ def run_tests(endpoint, method, num_requests, metadata): for num_request in num_requests: if num_request <= 0: continue - ok_responses = 0 - bad_responses = 0 - server_errors = 0 - cpu, ram = 0, 0 + total_cpu, total_ram = 0, 0 + total_time = 0 - with concurrent.futures.ThreadPoolExecutor(max_workers=THREADS) as executor: - url = f'{URL_BASE}{endpoint}' + for run in range(AVG_RUNS): + ok_responses = 0 + bad_responses = 0 + server_errors = 0 - start_time = time.time() + with concurrent.futures.ThreadPoolExecutor(max_workers=THREADS) as executor: + url = f'{URL_BASE}{endpoint}' - futures = [] - #with requests.Session() as session: - # futures = [executor.submit(send_request, session, url) for _ in range(num_request)] + start_time = time.time() - half = floor(num_request/2) - for i in range(num_request): - futures.append(executor.submit(send_request, url, method, metadata)) + futures = [] + #with requests.Session() as session: + # futures = [executor.submit(send_request, session, url) for _ in range(num_request)] - if i == half: - cpu, ram = get_resource_usage() + half = floor(num_request/2) + for i in range(num_request): + futures.append(executor.submit(send_request, url, method, metadata)) - concurrent.futures.wait(futures) + if i == half: + cpu, ram = get_resource_usage() + total_cpu += float(cpu) + total_ram += float(ram) - elapsed_time = time.time() - start_time + concurrent.futures.wait(futures) - for future in futures: - responses = future.result() - ok_responses += responses[2] - bad_responses += responses[4] - server_errors += responses[5] + elapsed_time = time.time() - start_time + total_time += elapsed_time + + for future in futures: + responses = future.result() + ok_responses += responses[2] + bad_responses += responses[4] + server_errors += responses[5] + + print(f"[#{run}] {num_request}: {elapsed_time:.2f} seconds. {elapsed_time/num_request:.4f} seconds per request. {num_request/elapsed_time:.2f} requests per second. [OK: {ok_responses}, Bad Request: {bad_responses}, Server Error: {server_errors}]]") + + client = docker.from_env() + client.containers.get(CONTAINER_NAME).restart() + + time.sleep(3) + + cpu = total_cpu / AVG_RUNS + ram = total_ram / AVG_RUNS + elapsed_time = total_time / AVG_RUNS - print(f"{num_request}: {elapsed_time:.2f} seconds. {elapsed_time/num_request:.4f} seconds per request. {num_request/elapsed_time:.2f} requests per second. [OK: {ok_responses}, Bad Request: {bad_responses}, Server Error: {server_errors}]]") record(files[0], num_request, f"{num_request/elapsed_time:.2f}") record_resource(files[1], num_request, cpu, ram) - generate_req_graph(files[0], FRAMEWORK_NAME, endpoint) - generate_resource_graph(files[1], FRAMEWORK_NAME, endpoint) - - time.sleep(3) - def get_resource_usage(): if CONTAINER_NAME == "": return 0, 0 @@ -147,9 +142,12 @@ def get_ram_usage(stats): if __name__ == "__main__": if not os.path.exists("data"): os.mkdir("data") - else: - os.system("rm -rf data/*") - for endpoint, method, num_requests, metadata in API_REQUESTS: - print(f"# {endpoint}") - run_tests(endpoint, method, num_requests, metadata) + for i in range(len(FRAMEWORKS)): + FRAMEWORK_NAME = FRAMEWORKS[i][0] + CONTAINER_NAME = FRAMEWORKS[i][1] + URL_BASE = ENDPOINTS[FRAMEWORK_NAME] + + for endpoint, method, num_requests, metadata in API_REQUESTS: + print(f"# {FRAMEWORK_NAME} - {endpoint}") + run_tests(endpoint, method, num_requests, metadata) diff --git a/tcc-express b/tcc-express new file mode 160000 index 0000000..504b592 --- /dev/null +++ b/tcc-express @@ -0,0 +1 @@ +Subproject commit 504b59278f6024219814649f2715b70561251c65