From acd1442ceac23f82911d6b3bff8337cd6106347d Mon Sep 17 00:00:00 2001
From: Christoph Herzog <chris@theduke.at>
Date: Wed, 21 Aug 2019 12:07:30 +0200
Subject: [PATCH] Experimental benchmarks

---
 Cargo.toml                              |   1 +
 benches/bench.rs                        | 127 ------------------------
 juniper/src/http/mod.rs                 |   8 +-
 juniper/src/lib.rs                      |   1 -
 juniper_benchmarks/.gitignore           |   3 +
 juniper_benchmarks/Cargo.toml           |  19 ++++
 juniper_benchmarks/benches/benchmark.rs |  74 ++++++++++++++
 juniper_benchmarks/src/lib.rs           | 116 ++++++++++++++++++++++
 juniper_warp/src/lib.rs                 |   3 +-
 9 files changed, 215 insertions(+), 137 deletions(-)
 delete mode 100644 benches/bench.rs
 create mode 100644 juniper_benchmarks/.gitignore
 create mode 100644 juniper_benchmarks/Cargo.toml
 create mode 100644 juniper_benchmarks/benches/benchmark.rs
 create mode 100644 juniper_benchmarks/src/lib.rs

diff --git a/Cargo.toml b/Cargo.toml
index 6f61e9f8..8a2553e3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,7 @@
 [workspace]
 # Order is important as this is the order the crates will be released.
 members = [
+  "juniper_benchmarks",
   "juniper_codegen",
   "juniper",
   "integration_tests/juniper_tests",
diff --git a/benches/bench.rs b/benches/bench.rs
deleted file mode 100644
index 5d707527..00000000
--- a/benches/bench.rs
+++ /dev/null
@@ -1,127 +0,0 @@
-#[macro_use] extern crate bencher;
-extern crate juniper;
-
-use bencher::Bencher;
-
-use juniper::{execute, RootNode, EmptyMutation, Variables};
-use juniper::tests::model::Database;
-
-fn query_type_name(b: &mut Bencher) {
-    let database = Database::new();
-    let schema = RootNode::new(&database, EmptyMutation::<Database>::new());
-
-    let doc = r#"
-        query IntrospectionQueryTypeQuery {
-          __schema {
-            queryType {
-              name
-            }
-          }
-        }"#;
-
-    b.iter(|| execute(doc, None, &schema, &Variables::new(), &database));
-}
-
-fn introspection_query(b: &mut Bencher) {
-    let database = Database::new();
-    let schema = RootNode::new(&database, EmptyMutation::<Database>::new());
-
-    let doc = r#"
-  query IntrospectionQuery {
-    __schema {
-      queryType { name }
-      mutationType { name }
-      subscriptionType { name }
-      types {
-        ...FullType
-      }
-      directives {
-        name
-        description
-        locations
-        args {
-          ...InputValue
-        }
-      }
-    }
-  }
-
-  fragment FullType on __Type {
-    kind
-    name
-    description
-    fields(includeDeprecated: true) {
-      name
-      description
-      args {
-        ...InputValue
-      }
-      type {
-        ...TypeRef
-      }
-      isDeprecated
-      deprecationReason
-    }
-    inputFields {
-      ...InputValue
-    }
-    interfaces {
-      ...TypeRef
-    }
-    enumValues(includeDeprecated: true) {
-      name
-      description
-      isDeprecated
-      deprecationReason
-    }
-    possibleTypes {
-      ...TypeRef
-    }
-  }
-
-  fragment InputValue on __InputValue {
-    name
-    description
-    type { ...TypeRef }
-    defaultValue
-  }
-
-  fragment TypeRef on __Type {
-    kind
-    name
-    ofType {
-      kind
-      name
-      ofType {
-        kind
-        name
-        ofType {
-          kind
-          name
-          ofType {
-            kind
-            name
-            ofType {
-              kind
-              name
-              ofType {
-                kind
-                name
-                ofType {
-                  kind
-                  name
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-"#;
-
-    b.iter(|| execute(doc, None, &schema, &Variables::new(), &database));
-}
-
-benchmark_group!(queries, query_type_name, introspection_query);
-benchmark_main!(queries);
diff --git a/juniper/src/http/mod.rs b/juniper/src/http/mod.rs
index e6fbf21e..2d4e62bd 100644
--- a/juniper/src/http/mod.rs
+++ b/juniper/src/http/mod.rs
@@ -111,13 +111,7 @@ where
     {
         let op = self.operation_name();
         let vars = &self.variables();
-        let res = crate::execute_async(
-            &self.query,
-            op,
-            root_node,
-            vars,
-            context,
-        ).await;
+        let res = crate::execute_async(&self.query, op, root_node, vars, context).await;
         GraphQLResponse(res)
     }
 }
diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs
index 3557560e..0d014ab2 100644
--- a/juniper/src/lib.rs
+++ b/juniper/src/lib.rs
@@ -91,7 +91,6 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected.
 
 #![doc(html_root_url = "https://docs.rs/juniper/0.13.1")]
 #![warn(missing_docs)]
-
 #![cfg_attr(feature = "async", feature(async_await, async_closure))]
 
 #[doc(hidden)]
diff --git a/juniper_benchmarks/.gitignore b/juniper_benchmarks/.gitignore
new file mode 100644
index 00000000..69369904
--- /dev/null
+++ b/juniper_benchmarks/.gitignore
@@ -0,0 +1,3 @@
+/target
+**/*.rs.bk
+Cargo.lock
diff --git a/juniper_benchmarks/Cargo.toml b/juniper_benchmarks/Cargo.toml
new file mode 100644
index 00000000..e4da5478
--- /dev/null
+++ b/juniper_benchmarks/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "juniper_benchmarks"
+version = "0.1.0"
+authors = ["Christoph Herzog <chris@theduke.at>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[[bench]]
+name = "benchmark"
+harness = false
+
+[dependencies]
+juniper = { path = "../juniper", features = ["async"] }
+futures-preview = "0.3.0-alpha.18"
+
+[dev-dependencies]
+criterion = "0.2.11"
+tokio = "0.2.0-alpha.2"
diff --git a/juniper_benchmarks/benches/benchmark.rs b/juniper_benchmarks/benches/benchmark.rs
new file mode 100644
index 00000000..fb0646ac
--- /dev/null
+++ b/juniper_benchmarks/benches/benchmark.rs
@@ -0,0 +1,74 @@
+extern crate juniper_benchmarks;
+
+use criterion::{black_box, criterion_group, criterion_main, Criterion, ParameterizedBenchmark};
+
+use juniper::{graphql_value, InputValue, ToInputValue, Value};
+use juniper_benchmarks as j;
+
+fn bench_sync_vs_async_single_user_flat_instant(c: &mut Criterion) {
+    const QUERY: &'static str = r#"
+        query Query($id: Int) {
+            user(id: $id) {
+                id
+                kind
+                username
+                email
+            }
+        }
+    "#;
+
+    c.bench(
+        "Sync vs Async - Single User Flat - Instant",
+        ParameterizedBenchmark::new(
+            "Sync",
+            |b, count| {
+                let ids = (0..*count)
+                    .map(|x| InputValue::scalar(x as i32))
+                    .collect::<Vec<_>>();
+                let ids = InputValue::list(ids);
+                b.iter(|| {
+                    j::execute(
+                        QUERY,
+                        vec![("ids".to_string(), ids.clone())].into_iter().collect(),
+                    )
+                })
+            },
+            vec![1, 10],
+        )
+        .with_function("Async - Single Thread", |b, count| {
+            let mut rt = tokio::runtime::current_thread::Runtime::new().unwrap();
+
+            let ids = (0..*count)
+                .map(|x| InputValue::scalar(x as i32))
+                .collect::<Vec<_>>();
+            let ids = InputValue::list(ids);
+
+            b.iter(|| {
+                let f = j::execute_async(
+                    QUERY,
+                    vec![("ids".to_string(), ids.clone())].into_iter().collect(),
+                );
+                rt.block_on(f)
+            })
+        })
+        .with_function("Async - Threadpool", |b, count| {
+            let rt = tokio::runtime::Runtime::new().unwrap();
+
+            let ids = (0..*count)
+                .map(|x| InputValue::scalar(x as i32))
+                .collect::<Vec<_>>();
+            let ids = InputValue::list(ids);
+
+            b.iter(|| {
+                let f = j::execute_async(
+                    QUERY,
+                    vec![("ids".to_string(), ids.clone())].into_iter().collect(),
+                );
+                rt.block_on(f)
+            })
+        }),
+    );
+}
+
+criterion_group!(benches, bench_sync_vs_async_single_user_flat_instant);
+criterion_main!(benches);
diff --git a/juniper_benchmarks/src/lib.rs b/juniper_benchmarks/src/lib.rs
new file mode 100644
index 00000000..a9f2aa4b
--- /dev/null
+++ b/juniper_benchmarks/src/lib.rs
@@ -0,0 +1,116 @@
+#![feature(async_await, async_closure)]
+
+use juniper::{
+    object, DefaultScalarValue, ExecutionError, FieldError, GraphQLEnum, Value, Variables,
+};
+
+pub type QueryResult = Result<
+    (
+        Value<DefaultScalarValue>,
+        Vec<ExecutionError<DefaultScalarValue>>,
+    ),
+    String,
+>;
+
+pub struct Context {}
+
+impl Context {
+    fn new() -> Self {
+        Self {}
+    }
+}
+
+impl juniper::Context for Context {}
+
+#[derive(GraphQLEnum)]
+pub enum Gender {
+    Male,
+    Female,
+    Other,
+}
+
+#[derive(GraphQLEnum)]
+pub enum UserKind {
+    SuperAdmin,
+    Admin,
+    Moderator,
+    User,
+    Guest,
+}
+
+pub struct User {
+    pub id: i32,
+    pub kind: UserKind,
+    pub username: String,
+    pub email: String,
+    pub gender: Option<Gender>,
+}
+
+impl User {
+    fn new(id: i32) -> Self {
+        Self {
+            id,
+            kind: UserKind::Admin,
+            username: "userx".to_string(),
+            email: "userx@domain.com".to_string(),
+            gender: Some(Gender::Female),
+        }
+    }
+}
+
+#[object(Context = Context)]
+impl User {}
+
+pub struct Query;
+
+#[object(Context = Context)]
+impl Query {
+    fn user_sync_instant(id: i32) -> Result<User, FieldError> {
+        Ok(User::new(id))
+    }
+
+    fn users_sync_instant(ids: Option<Vec<i32>>) -> Result<Vec<User>, FieldError> {
+        if let Some(ids) = ids {
+            let users = ids.into_iter().map(User::new).collect();
+            Ok(users)
+        } else {
+            Ok(vec![])
+        }
+    }
+
+    async fn user_async_instant(id: i32) -> Result<User, FieldError> {
+        Ok(User::new(id))
+    }
+
+    async fn users_async_instant(ids: Option<Vec<i32>>) -> Result<Vec<User>, FieldError> {
+        if let Some(ids) = ids {
+            let users = ids.into_iter().map(User::new).collect();
+            Ok(users)
+        } else {
+            Ok(vec![])
+        }
+    }
+}
+
+pub struct Mutation;
+
+#[object(Context = Context)]
+impl Mutation {}
+
+pub fn new_schema() -> juniper::RootNode<'static, Query, Mutation> {
+    juniper::RootNode::new(Query, Mutation)
+}
+
+pub fn execute(query: &str, vars: Variables) -> QueryResult {
+    let root = new_schema();
+    let ctx = Context::new();
+    juniper::execute(query, None, &root, &vars, &ctx).map_err(|e| format!("{:?}", e))
+}
+
+pub async fn execute_async(query: &str, vars: Variables) -> QueryResult {
+    let root = new_schema();
+    let ctx = Context::new();
+    juniper::execute_async(query, None, &root, &vars, &ctx)
+        .await
+        .map_err(|e| format!("{:?}", e))
+}
diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs
index 6f6b7711..a8725b29 100644
--- a/juniper_warp/src/lib.rs
+++ b/juniper_warp/src/lib.rs
@@ -39,7 +39,6 @@ Check the LICENSE file for details.
 #![deny(missing_docs)]
 #![deny(warnings)]
 #![doc(html_root_url = "https://docs.rs/juniper_warp/0.2.0")]
-
 #![cfg_attr(feature = "async", feature(async_await, async_closure))]
 
 use futures::{future::poll_fn, Future};
@@ -114,7 +113,7 @@ where
                     .iter()
                     .map(|request| request.execute_async(root_node, context))
                     .collect::<Vec<_>>();
-                let responses =  futures03::future::join_all(futures).await;
+                let responses = futures03::future::join_all(futures).await;
 
                 GraphQLBatchResponse::Batch(responses)
             }