Browse Source

Updated ohkami benchmark implementation with ohkami v0.15 (#8763)

kanarus 1 year ago
parent
commit
c47af754b2

+ 1 - 1
frameworks/Rust/ohkami/.gitignore

@@ -1 +1 @@
-/target
+/target

File diff suppressed because it is too large
+ 235 - 415
frameworks/Rust/ohkami/Cargo.lock


+ 9 - 7
frameworks/Rust/ohkami/Cargo.toml

@@ -1,11 +1,13 @@
 [package]
-name = "ohkami"
-version = "0.1.0"
+name    = "ohkami_framework_benchmarks"
+version = "0.15.0"
 edition = "2021"
+authors = ["kanarus <[email protected]>"]
 
 [dependencies]
-ohkami = { version = "0.3.3", features = ["sqlx", "postgres"] }
-serde = { version = "1.0", features = ["derive"] }
-sqlx = "0.6"
-rand = "0.8.5"
-yarte = "0.15"
+ohkami       = { version = "=0.15.0", features = ["rt_tokio"] }
+tokio        = { version = "1.36.0" , features = ["full"] }
+rand         = { version = "0.8.5"  , features = ["small_rng"] }
+sqlx         = { version = "0.7.3"  , features = ["postgres", "macros", "runtime-tokio-native-tls"] }
+yarte        = { version = "0.15.7" }
+futures-util = { version = "0.3.30" }

+ 35 - 9
frameworks/Rust/ohkami/README.md

@@ -1,15 +1,41 @@
-# [ohkami](https://github.com/kana-rus/ohkami) web framework
+# [ohkami](https://github.com/kana-rus/ohkami) - Intuitive and Declarative Web Framework for Rust
 
 ## Description
-ohkami is **simple** and **macro free** wen framework.
+
+> Build web app in intuitive and declarative code
+> - *macro-less and type-safe* APIs for intuitive and declarative code
+> - *multi runtime* support:`tokio`, `async-std`
+
+- [User Guide](https://docs.rs/ohkami/latest/ohkami/)
+- [API Documentation](https://docs.rs/ohkami/latest/ohkami/)
+- Cargo package: [ohkami](https://crates.io/crates/ohkami)
 
 ## Database
-- PostgreSQL
+
+PostgreSQL with [sqlx](https://github.com/launchbadge/sqlx)
 
 ## Test URLs
-- JSON Encoding: [http://localhost:8080/json](http://localhost:8080/json)
-- Single Row Query: [http://localhost:8080/db](http://localhost:8080/db)
-- Multi Row Query: [http://localhost:8080/queries](http://localhost:8080/queries)
-- Fortunes: [http://localhost:8080/fortunes](http://localhost:8080/fortunes)
-- Update Query: [http://localhost:8080/updates](http://localhost:8080/updates)
-- Plaintext: [http://localhost:8080/plaintext](http://localhost:8080/plaintext)
+
+### 1. JSON Serialization
+
+    http://localhost:8000/json
+
+### 2. Single Database Query
+
+    http://localhost:8000/db
+
+### 3. Multiple Database Queries
+
+    http://localhost:8000/queries?q={count}
+
+### 4. Fortunes
+
+    http://localhost:8000/fortunes
+
+### 5. Database Updates
+
+    http://localhost:8000/updates?q={count}
+
+### 6. Plaintext
+
+    http://localhost:8000/plaintext

+ 19 - 17
frameworks/Rust/ohkami/benchmark_config.json

@@ -3,24 +3,26 @@
     "tests": [
         {
             "default": {
-                "json_url": "/json",
-                "plaintext_url": "/plaintext",
-                "fortune_url": "/fortunes",
-                "db_url": "/db",
-                "query_url": "/queries?q=",
-                "update_url": "/updates?q=",
-                "port": 8080,
-                "approach": "Realistic",
+                "json_url":       "/json",
+                "db_url":         "/db",
+                "query_url":      "/queries?q=",
+                "fortune_url":    "/fortunes",
+                "update_url":     "/updates?q=",
+                "plaintext_url":  "/plaintext",
+                "port":           8000,
+                "approach":       "Realistic",
                 "classification": "Micro",
-                "database": "Postgres",
-                "framework": "ohkami",
-                "language": "Rust",
-                "orm": "Raw",
-                "platform": "Rust",
-                "webserver": "ohkami",
-                "os": "Linux",
-                "database_os": "Linux",
-                "display_name": "ohkami"
+                "database":       "Postgres",
+                "framework":      "ohkami",
+                "language":       "Rust",
+                "orm":            "Raw",
+                "platform":       "None",
+                "webserver":      "ohkami",
+                "os":             "Linux",
+                "database_os":    "Linux",
+                "display_name":   "ohkami",
+                "notes":          "",
+                "versus":         "None"
             }
         }
     ]

+ 19 - 0
frameworks/Rust/ohkami/config.toml

@@ -0,0 +1,19 @@
+[framework]
+name = "ohkami"
+
+[main]
+urls.json      = "/json"
+urls.db        = "/db"
+urls.query     = "/queries?q="
+urls.fortune   = "/fortunes"
+urls.update    = "/updates?q="
+urls.plaintext = "/plaintext"
+approach       = "Realistic"
+classification = "Micro"
+database       = "Postgres"
+database_os    = "Linux"
+os             = "Linux"
+orm            = "Raw"
+platform       = "None"
+webserver      = "ohkami"
+versus         = "None"

+ 15 - 9
frameworks/Rust/ohkami/ohkami.dockerfile

@@ -1,13 +1,19 @@
-FROM rust:1.65
+FROM rust:1.76-slim-buster
+WORKDIR /ohkami_framework_benchmarks
 
-RUN apt update -yqq \
- && apt install -yqq cmake g++
+ENV DATABASE_URL=postgres://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world
+ENV MAX_CONNECTIONS=56
+ENV MIN_CONNECTIONS=56
 
-ADD ./ /ohkami
-WORKDIR /ohkami
+COPY ./src        ./src
+COPY ./templates  ./templates
+COPY ./Cargo.toml ./Cargo.toml
+COPY ./Cargo.lock ./Cargo.lock
 
-RUN cargo clean \
- && RUSTFLAGS="-C target-cpu=native" cargo build --release
+RUN apt update && apt install -y --no-install-recommends \
+    libpq-dev pkg-config libssl-dev && \
+    rm -rf /var/lib/apt/lists/* 
 
-EXPOSE 8080
-CMD ./target/release/ohkami
+RUN RUSTFLAGS="-C target-cpu=native" cargo build --release
+EXPOSE 8000
+CMD ./target/release/ohkami_framework_benchmarks

+ 0 - 60
frameworks/Rust/ohkami/src/components.rs

@@ -1,60 +0,0 @@
-pub(crate) mod consts {
-    use std::ops::RangeInclusive;
-
-    pub const RAND_RANGE: RangeInclusive<usize>  = 1..=10000;
-    pub const DB_URL:              &'static str  = "postgres://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world?sslmode=disable";
-    pub const MAX_CONNECTIONS:     u32           = 10000;
-}
-
-pub(crate) mod models {
-    use serde::Serialize;
-    use sqlx::FromRow;
-    use yarte::Template;
-
-    #[derive(FromRow, Serialize)]
-    pub struct World {
-        id:           i32,
-        randomnumber: i32,
-    } impl World {
-        pub fn set_randomnumber(&mut self, new_randomnumber: i32) {
-            self.randomnumber = new_randomnumber
-        }
-    }
-
-    #[derive(FromRow, Serialize)]
-    pub struct Fortune {
-        pub id:      i32,
-        pub message: String,
-    }
-    #[derive(Template)]
-    #[template(path = "fortunes.hbs")]
-    pub(crate) struct FortunesTemplate {
-        pub(crate) fortunes: Vec<Fortune>
-    }
-}
-
-pub(crate) mod functions {
-    use ohkami::{prelude::Body, result::{Result, ElseResponseWithErr}, response::Response};
-    use rand::Rng;
-    use yarte::Template;
-    use super::{models::{Fortune, FortunesTemplate}, consts::RAND_RANGE};
-
-    pub fn random_i32() -> i32 {
-        rand::thread_rng().gen_range(RAND_RANGE) as i32
-    }
-    pub fn random_i32s(n: usize) -> std::vec::IntoIter<i32> {
-        let mut generator = rand::thread_rng();
-        let mut i32s = Vec::with_capacity(n);
-        for _ in 0..n {
-            i32s.push(generator.gen_range(RAND_RANGE) as i32)
-        }
-        i32s.into_iter()
-    }
-    pub fn render_html(fortunes: Vec<Fortune>) -> Result<Response> {
-        Response::OK(Body::html(
-            FortunesTemplate {fortunes}
-                .call()
-                ._else(|_| Response::InternalServerError("failed to render template"))?
-        ))
-    }
-}

+ 62 - 85
frameworks/Rust/ohkami/src/main.rs

@@ -1,97 +1,74 @@
-use ohkami::{prelude::*, json};
-use sqlx::postgres::PgPoolOptions;
-mod components; use components::{
-    consts::{DB_URL, MAX_CONNECTIONS},
-    models::{World, Fortune},
-    functions::{random_i32, random_i32s, render_html},
-};
-
-fn main() -> Result<()> {
-    let config = Config {
-        db_profile: DBprofile {
-            pool_options: PgPoolOptions::new().max_connections(MAX_CONNECTIONS),
-            url:          DB_URL,
-        },
-        log_subscribe: None,
-        ..Default::default()
-    };
-
-    Server::setup_with(config)
-        .GET("/json",      || async {Response::OK(json!("message": "Hello, World!"))})
-        .GET("/plaintext", || async {Response::OK("Hello, World!")})
-        .GET("/db",        handle_db)
-        .GET("/fortunes",  handle_fortunes)
-        .GET("/queries",   handle_queries)
-        .GET("/updates",   handle_updates)
-        .serve_on(":8080")
+mod models;
+pub use models::{Fortune, Message, World, WorldsQuery};
+
+mod postgres;
+pub use postgres::Postgres;
+
+mod templates;
+pub use templates::FortunesTemplate;
+
+use ohkami::{Ohkami, Route, Memory};
+
+
+#[tokio::main]
+async fn main() {
+    struct SetServer;
+    impl ohkami::BackFang for SetServer {
+        type Error = std::convert::Infallible;
+        #[inline(always)]
+        async fn bite(&self, res: &mut ohkami::Response, _req: &ohkami::Request) -> Result<(), Self::Error> {
+            res.headers.set().Server("ohkami");
+            Ok(())
+        }
+    }
+
+    Ohkami::with((SetServer, Postgres::init().await), (
+        "/json"     .GET(json_serialization),
+        "/db"       .GET(single_database_query),
+        "/queries"  .GET(multiple_database_query),
+        "/fortunes" .GET(fortunes),
+        "/updates"  .GET(database_updates),
+        "/plaintext".GET(plaintext),
+    )).howl("0.0.0.0:8000").await
+}
+
+async fn json_serialization() -> Message {
+    Message {
+        message: "Hello, World!"
+    }
 }
 
-async fn handle_db(ctx: Context) -> Result<Response> {
-    let id = random_i32();
-    let world = sqlx::query_as::<_, World>(
-        "SELECT id, randomnumber FROM world WHERE id = $1"
-    ).bind(id)
-        .fetch_one(ctx.pool())
-        .await?;
-    Response::OK(json(&world)?)
+async fn single_database_query(p: Memory<'_, Postgres>) -> World {
+    p.select_random_world().await
 }
 
-async fn handle_fortunes(ctx: Context) -> Result<Response> {
-    let mut fortunes = sqlx::query_as::<_, Fortune>(
-        "SELECT id, message FROM fortune"
-    )
-        .fetch_all(ctx.pool())
-        .await?;
+async fn multiple_database_query(q: WorldsQuery<'_>, p: Memory<'_, Postgres>) -> Vec<World> {
+    let n = q.parse();
+    p.select_n_random_worlds(n).await
+}
+
+async fn fortunes(p: Memory<'_, Postgres>) -> FortunesTemplate {
+    let mut fortunes = p.select_all_fortunes().await;
+
     fortunes.push(Fortune {
         id:      0,
-        message: "Additional fortune added at request time.".into(),
+        message: String::from("Additional fortune added at request time."),
     });
-    fortunes.sort_unstable_by(|it, next| it.message.cmp(&next.message));
-    render_html(fortunes)
+
+    fortunes.sort_unstable_by(|a, b| str::cmp(&a.message, &b.message));
+
+    FortunesTemplate { fortunes }
 }
 
-async fn handle_queries(ctx: Context) -> Result<Response> {
-    let count = {
-        let queries = ctx.query::<&str>("q").unwrap_or("1").parse::<usize>().unwrap_or(1);
-        if queries < 1 {1} else if 500 < queries {500} else {queries}
-    };
-    let mut worlds = Vec::with_capacity(count);
-    for id in random_i32s(count) {
-        worlds.push(
-            sqlx::query_as::<_, World>(
-                "SELECT id, randomnumber FROM world WHERE id = $1"
-            ).bind(id)
-                .fetch_one(ctx.pool())
-                .await?
-        )
-    }
-    Response::OK(json(&worlds)?)
+async fn database_updates(q: WorldsQuery<'_>, p: Memory<'_, Postgres>) -> Vec<World> {
+    let n = q.parse();
+    let mut worlds = p.select_n_random_worlds(n).await;
+
+    p.update_random_ids_of_worlds(&mut worlds).await;
+
+    worlds
 }
 
-async fn handle_updates(ctx: Context) -> Result<Response> {
-    let count = {
-        let queries = ctx.query::<&str>("q").unwrap_or("1").parse::<usize>().unwrap_or(1);
-        if queries < 1 {1} else if 500 < queries {500} else {queries}
-    };
-    let mut worlds = Vec::with_capacity(count);
-    let mut new_randomnumbers = random_i32s(count);
-    for id in random_i32s(count) {
-        let mut world = sqlx::query_as::<_, World>(
-            "SELECT id, randomnumber FROM world WHERE id = $1"
-        ).bind(id)
-            .fetch_one(ctx.pool())
-            .await?;
-
-        let new_randomnumber = new_randomnumbers.next().unwrap();
-        world.set_randomnumber(new_randomnumber);
-
-        sqlx::query("UPDATE world SET randomnumber = $1 WHERE id = $2")
-            .bind(new_randomnumber)
-            .bind(id)
-            .execute(ctx.pool())
-            .await?;
-
-        worlds.push(world)
-    }
-    Response::OK(json(&worlds)?)
+async fn plaintext() -> &'static str {
+    "Hello, World!"
 }

+ 36 - 0
frameworks/Rust/ohkami/src/models.rs

@@ -0,0 +1,36 @@
+use ohkami::typed::{ResponseBody, Query};
+
+
+#[ResponseBody(JSONS)]
+pub struct Message {
+    pub message: &'static str,
+}
+
+#[derive(sqlx::FromRow)]
+pub struct Fortune {
+    pub id:      i32,
+    pub message: String,
+}
+
+#[derive(sqlx::FromRow)]
+#[ResponseBody(JSONS)]
+pub struct World {
+    pub id:           i32,
+    #[serde(rename="randomNumber")]
+    pub randomnumber: i32,
+}
+
+#[Query]
+pub struct WorldsQuery<'q> {
+    q: Option<&'q str>,
+}
+impl WorldsQuery<'_> {
+    #[inline(always)]
+    pub fn parse(self) -> usize {
+        match self.q.unwrap_or("1").parse::<usize>().unwrap_or(1) {
+            n @ 1..=500 => n,
+            0           => 1,
+            501..       => 500,
+        }
+    }
+}

+ 102 - 0
frameworks/Rust/ohkami/src/postgres.rs

@@ -0,0 +1,102 @@
+use futures_util::{stream::FuturesUnordered, TryStreamExt};
+use rand::{rngs::SmallRng, SeedableRng, Rng, thread_rng};
+use crate::models::{World, Fortune};
+
+
+#[derive(Clone)]
+pub struct Postgres(sqlx::PgPool);
+
+impl Postgres {
+    pub async fn init() -> impl ohkami::FrontFang {
+        pub struct UsePostgres(Postgres);
+
+        impl ohkami::FrontFang for UsePostgres {
+            type Error = std::convert::Infallible;
+            #[inline(always)]
+            async fn bite(&self, req: &mut ohkami::Request) -> Result<(), Self::Error> {
+                req.memorize(self.0.clone());
+                Ok(())
+            }
+        }
+
+        macro_rules! load_env {
+            ($($name:ident as $t:ty)*) => {
+                $(
+                    #[allow(non_snake_case)]
+                    let $name = ::std::env::var(stringify!($name))
+                        .expect(concat!(
+                            "Failed to load environment variable ",
+                            "`", stringify!($name), "`"
+                        ))
+                        .parse::<$t>()
+                        .unwrap();
+                )*
+            };
+        } load_env! {
+            MAX_CONNECTIONS as u32
+            MIN_CONNECTIONS as u32
+            DATABASE_URL    as String
+        }
+
+        UsePostgres(Self(
+            sqlx::postgres::PgPoolOptions::new()
+                .max_connections(MAX_CONNECTIONS)
+                .min_connections(MIN_CONNECTIONS)
+                .connect(&DATABASE_URL).await
+                .unwrap()
+        ))
+    }
+}
+
+impl Postgres {
+    pub async fn select_random_world(&self) -> World {
+        let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
+    
+        sqlx::query_as(
+            "SELECT id, randomnumber FROM World WHERE id = $1")
+            .bind((rng.gen::<u32>() % 10_000 + 1) as i32)
+            .fetch_one(&self.0).await
+            .expect("Failed to fetch a world")
+    }
+    
+    pub async fn select_all_fortunes(&self) -> Vec<Fortune> {
+        sqlx::query_as(
+            "SELECT id, message FROM Fortune")
+            .fetch_all(&self.0).await
+            .expect("Failed to fetch fortunes")
+    }
+    
+    pub async fn select_n_random_worlds(&self, n: usize) -> Vec<World> {
+        let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
+    
+        let selects = FuturesUnordered::new();
+        for _ in 0..n {
+            selects.push(
+                sqlx::query_as(
+                    "SELECT id, randomnumber FROM World WHERE id = $1")
+                    .bind((rng.gen::<u32>() % 10_000 + 1) as i32)
+                    .fetch_one(&self.0)
+            )
+        }
+    
+        selects.try_collect().await.expect("Failed to fetch worlds")
+    }
+    
+    pub async fn update_random_ids_of_worlds(&self, worlds: &mut Vec<World>) {
+        let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
+    
+        let updates = FuturesUnordered::new();
+        for w in worlds {
+            w.randomnumber = (rng.gen::<u32>() % 10_000 + 1) as i32;
+            updates.push(
+                sqlx::query(
+                    "UPDATE World SET randomnumber = $1 WHERE id = $2")
+                    .bind(w.randomnumber)
+                    .bind(w.id)
+                    .execute(&self.0)
+            )
+        }
+    
+        let _: sqlx::postgres::PgQueryResult = updates.try_collect().await.expect("Failed to fetch worlds");
+    }
+}

+ 18 - 0
frameworks/Rust/ohkami/src/templates.rs

@@ -0,0 +1,18 @@
+use ohkami::{Response, IntoResponse};
+use crate::models::Fortune;
+
+
+#[derive(yarte::Template)]
+#[template(path="fortunes")]
+pub struct FortunesTemplate {
+    pub fortunes: Vec<Fortune>,
+}
+impl IntoResponse for FortunesTemplate {
+    #[inline(always)]
+    fn into_response(self) -> Response {
+        ohkami::utils::HTML(
+            <Self as yarte::Template>::call(&self)
+                .expect("Failed to render fortunes template")
+        ).into_response()
+    }
+}

Some files were not shown because too many files changed in this diff