Compare commits

..

No commits in common. "6d8ec28dbb8628d1ec96d3791203d42be8d32626" and "aa4b1e9709ef74e88d091e40336e16886bcdf414" have entirely different histories.

10 changed files with 70 additions and 131 deletions

View File

@ -1,2 +0,0 @@
Dockerfile
target/

18
ActixAPI/Cargo.lock generated
View File

@ -9,7 +9,6 @@ dependencies = [
"actix-files", "actix-files",
"actix-web", "actix-web",
"magick_rust", "magick_rust",
"qstring",
] ]
[[package]] [[package]]
@ -300,9 +299,9 @@ checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
[[package]] [[package]]
name = "bindgen" name = "bindgen"
version = "0.68.1" version = "0.66.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7"
dependencies = [ dependencies = [
"bitflags 2.4.0", "bitflags 2.4.0",
"cexpr", "cexpr",
@ -786,9 +785,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]] [[package]]
name = "magick_rust" name = "magick_rust"
version = "0.19.1" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c912b69250bcd5d024852a1a75c567d3b5d881871a55b741018741632a921bf8" checksum = "ce666df6ace809001c625ad383f4727657c7c7881df43b3b71486e8e9f07d017"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"libc", "libc",
@ -953,15 +952,6 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "quote" name = "quote"
version = "1.0.33" version = "1.0.33"

View File

@ -8,5 +8,4 @@ edition = "2021"
[dependencies] [dependencies]
actix-web = "4" actix-web = "4"
actix-files = "0.6.2" actix-files = "0.6.2"
magick_rust = "0.19.1" magick_rust = "0.19.0"
qstring = "0.7.2"

View File

@ -1,34 +1,25 @@
FROM rust:latest FROM rust:slim-bullseye AS build-env
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 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 . . COPY . .
RUN cargo build --release && \ RUN apt update && apt install wget -y && \
cp ./target/release/ActixAPI . && \ 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 && \
mv small-image.png ./static && \ mv small-image.png ./static && \
mv big-image.png ./static && \ mv big-image.png ./static && \
mv video.mp4 ./static && \ mv video.mp4 ./static
ldconfig /usr/local/lib
ENV LD_LIBRARY_PATH=/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
ENTRYPOINT ["./ActixAPI"] ENTRYPOINT ["./ActixAPI"]

View File

@ -1,13 +1,22 @@
use qstring::QString;
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, HttpRequest, Result}; use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, HttpRequest, Result};
use actix_files::NamedFile; use actix_files::NamedFile;
use magick_rust::MagickWand; use magick_rust::{MagickWand, ToMagick};
use magick_rust::{self, FilterType};
#[get("/status/ok")] #[get("/status/ok")]
async fn hello() -> impl Responder { async fn hello() -> impl Responder {
HttpResponse::Ok().body("{\"status\": 200}") 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<NamedFile> { async fn static_serve(req: HttpRequest) -> Result<NamedFile> {
let path: &str = req.path(); let path: &str = req.path();
let real_path = &path[1..]; let real_path = &path[1..];
@ -15,50 +24,19 @@ async fn static_serve(req: HttpRequest) -> Result<NamedFile> {
Ok(NamedFile::open(real_path)?) Ok(NamedFile::open(real_path)?)
} }
#[get("/image/load-small-image")]
async fn load_small_image() -> Result<NamedFile> {
let real_path = "static/small-image.png";
Ok(NamedFile::open(real_path)?)
}
#[get("/image/load-big-image")]
async fn load_big_image() -> Result<NamedFile> {
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<HttpResponse> {
// 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")] #[post("/image/blur")]
async fn blur_image(image_data: web::Bytes, req: HttpRequest) -> Result<HttpResponse> { async fn blur_image(image_data: web::Bytes) -> Result<HttpResponse> {
// Load the image from the incoming bytes // Load the image from the incoming bytes
let mut wand = MagickWand::new(); let image = ToMagick::<magick_rust::MagickWand>::to_magick(image_data).unwrap();
wand.read_image_blob(&image_data).unwrap();
let query_str = req.query_string(); // Apply a blur filter to the image
let qs = QString::from(query_str); let blurred_image = image.blur(FilterType::Gaussian, 0.0).unwrap();
let radius = qs.get("radius").unwrap_or("5").parse::<f64>().unwrap_or(5.0);
// Blur the image // Convert the blurred image back to bytes
wand.blur_image(radius, radius).unwrap(); let blurred_image_bytes = blurred_image.to_bytes().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() Ok(HttpResponse::Ok()
.content_type("image/png") .content_type("image/jpeg")
.body(blurred_image_bytes)) .body(blurred_image_bytes))
} }
@ -70,13 +48,11 @@ async fn main() -> std::io::Result<()> {
App::new() App::new()
.route("/static/{filename:.*}", web::get().to(static_serve)) .route("/static/{filename:.*}", web::get().to(static_serve))
.service(hello) .service(hello)
.service(load_small_image) .service(echo)
.service(load_big_image)
.service(save_big_image)
.service(blur_image) .service(blur_image)
.app_data(web::PayloadConfig::new(1024 * 1024 * 1024)) .route("/hey", web::get().to(manual_hello))
}) })
.bind(("0.0.0.0", 5000))? .bind(("0.0.0.0", 9090))?
.run() .run()
.await .await
} }

View File

@ -31,7 +31,7 @@ services:
build: ./ActixAPI build: ./ActixAPI
restart: always restart: always
ports: ports:
- "9083:5000" - "9083:9090"
deploy: deploy:
resources: resources:
limits: limits:

View File

@ -13,8 +13,6 @@ ENDPOINTS = {
} }
BLUR_RADIUS = 5 BLUR_RADIUS = 5
AVG_RUNS = 3
API_REQUESTS = [ API_REQUESTS = [
('/status/ok', 'GET', range(0, 30_000, 5000), None), ('/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()), ('/image/save-big-image', 'POST', range(0, 10_000, 1_000), open('big-image.png', 'rb').read()),

View File

@ -1,5 +1,6 @@
import numpy as np import numpy as np
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import os
from common import API_REQUESTS, FRAMEWORKS from common import API_REQUESTS, FRAMEWORKS
FRAMEWORKS = [f for f, _ in FRAMEWORKS] FRAMEWORKS = [f for f, _ in FRAMEWORKS]

View File

@ -2,10 +2,11 @@ import requests
import docker import docker
import concurrent.futures import concurrent.futures
import time import time
import sys
import os import os
from math import floor from math import floor
from init import init from init import init
from common import FRAMEWORKS, ENDPOINTS, API_REQUESTS, AVG_RUNS from common import FRAMEWORKS, ENDPOINTS, API_REQUESTS
init() init()
@ -61,13 +62,10 @@ def run_tests(endpoint, method, num_requests, metadata):
for num_request in num_requests: for num_request in num_requests:
if num_request <= 0: continue if num_request <= 0: continue
total_cpu, total_ram = 0, 0
total_time = 0
for run in range(AVG_RUNS):
ok_responses = 0 ok_responses = 0
bad_responses = 0 bad_responses = 0
server_errors = 0 server_errors = 0
cpu, ram = 0, 0
with concurrent.futures.ThreadPoolExecutor(max_workers=THREADS) as executor: with concurrent.futures.ThreadPoolExecutor(max_workers=THREADS) as executor:
url = f'{URL_BASE}{endpoint}' url = f'{URL_BASE}{endpoint}'
@ -84,13 +82,10 @@ def run_tests(endpoint, method, num_requests, metadata):
if i == half: if i == half:
cpu, ram = get_resource_usage() cpu, ram = get_resource_usage()
total_cpu += float(cpu)
total_ram += float(ram)
concurrent.futures.wait(futures) concurrent.futures.wait(futures)
elapsed_time = time.time() - start_time elapsed_time = time.time() - start_time
total_time += elapsed_time
for future in futures: for future in futures:
responses = future.result() responses = future.result()
@ -98,20 +93,12 @@ def run_tests(endpoint, method, num_requests, metadata):
bad_responses += responses[4] bad_responses += responses[4]
server_errors += responses[5] 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}]]") 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}]]")
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
record(files[0], num_request, f"{num_request/elapsed_time:.2f}") record(files[0], num_request, f"{num_request/elapsed_time:.2f}")
record_resource(files[1], num_request, cpu, ram) record_resource(files[1], num_request, cpu, ram)
time.sleep(3)
def get_resource_usage(): def get_resource_usage():
if CONTAINER_NAME == "": return 0, 0 if CONTAINER_NAME == "": return 0, 0

@ -1 +0,0 @@
Subproject commit 504b59278f6024219814649f2715b70561251c65