Compare commits

...

5 Commits

Author SHA1 Message Date
José Henrique 6d8ec28dbb save image 2023-09-28 22:53:09 -03:00
José Henrique 4535ed5cf6 fix actix docker 2023-09-28 22:35:06 -03:00
José Henrique 4fa2ae956f curl 2023-09-28 21:03:32 -03:00
José Henrique 2e3398f683 average runs 2023-09-28 21:00:55 -03:00
José Henrique 2f91c3636f add actix 2023-09-28 20:40:45 -03:00
10 changed files with 133 additions and 72 deletions

2
ActixAPI/.dockerignore Normal file
View File

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

18
ActixAPI/Cargo.lock generated
View File

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

View File

@ -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 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 apt update && apt install wget -y && \ RUN cargo build --release && \
wget https://files.ivanch.me/api/public/dl/iFuXSNhw/small-image.png && \ cp ./target/release/ActixAPI . && \
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
RUN cargo build --release ENV LD_LIBRARY_PATH=/usr/local/lib
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,22 +1,13 @@
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, ToMagick}; use magick_rust::MagickWand;
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..];
@ -24,19 +15,50 @@ async fn static_serve(req: HttpRequest) -> Result<NamedFile> {
Ok(NamedFile::open(real_path)?) Ok(NamedFile::open(real_path)?)
} }
#[post("/image/blur")] #[get("/image/load-small-image")]
async fn blur_image(image_data: web::Bytes) -> Result<HttpResponse> { async fn load_small_image() -> Result<NamedFile> {
// Load the image from the incoming bytes let real_path = "static/small-image.png";
let image = ToMagick::<magick_rust::MagickWand>::to_magick(image_data).unwrap();
// Apply a blur filter to the image Ok(NamedFile::open(real_path)?)
let blurred_image = image.blur(FilterType::Gaussian, 0.0).unwrap(); }
// Convert the blurred image back to bytes #[get("/image/load-big-image")]
let blurred_image_bytes = blurred_image.to_bytes().unwrap(); 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() Ok(HttpResponse::Ok()
.content_type("image/jpeg") .content_type("application/json")
.body("{\"status\": 200}"))
}
#[post("/image/blur")]
async fn blur_image(image_data: web::Bytes, req: HttpRequest) -> Result<HttpResponse> {
// 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::<f64>().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)) .body(blurred_image_bytes))
} }
@ -48,11 +70,13 @@ 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(echo) .service(load_small_image)
.service(load_big_image)
.service(save_big_image)
.service(blur_image) .service(blur_image)
.route("/hey", web::get().to(manual_hello)) .app_data(web::PayloadConfig::new(1024 * 1024 * 1024))
}) })
.bind(("0.0.0.0", 9090))? .bind(("0.0.0.0", 5000))?
.run() .run()
.await .await
} }

View File

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

View File

@ -13,6 +13,8 @@ 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,6 +1,5 @@
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,11 +2,10 @@ 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 from common import FRAMEWORKS, ENDPOINTS, API_REQUESTS, AVG_RUNS
init() init()
@ -62,43 +61,57 @@ 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
ok_responses = 0 total_cpu, total_ram = 0, 0
bad_responses = 0 total_time = 0
server_errors = 0
cpu, ram = 0, 0
with concurrent.futures.ThreadPoolExecutor(max_workers=THREADS) as executor: for run in range(AVG_RUNS):
url = f'{URL_BASE}{endpoint}' 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 = [] start_time = time.time()
#with requests.Session() as session:
# futures = [executor.submit(send_request, session, url) for _ in range(num_request)]
half = floor(num_request/2) futures = []
for i in range(num_request): #with requests.Session() as session:
futures.append(executor.submit(send_request, url, method, metadata)) # futures = [executor.submit(send_request, session, url) for _ in range(num_request)]
if i == half: half = floor(num_request/2)
cpu, ram = get_resource_usage() 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: elapsed_time = time.time() - start_time
responses = future.result() total_time += elapsed_time
ok_responses += responses[2]
bad_responses += responses[4] for future in futures:
server_errors += responses[5] 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(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
tcc-express Submodule

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