Skip to content

Commit f495295

Browse files
mihaeljaicgitbuda
andauthored
Add e2e benchmarks (#24)
* Make MgError debuggable * Remove .vscode from exclude .vscode is now in .gitignore * Add connection benchmarks * Fix comments * Add new benchmarks * Reduce number of iterations * Make the benchmark a bit more robust * Format Co-authored-by: Marko Budiselic <[email protected]>
1 parent b5beb0c commit f495295

File tree

3 files changed

+236
-2
lines changed

3 files changed

+236
-2
lines changed

Cargo.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ edition = "2018"
1212
keywords = ["memgraph", "client", "driver", "database-adapter"]
1313
categories = ["database", "api-bindings"]
1414
exclude = [
15-
".vscode/*",
16-
".github/*",
15+
".github/*"
1716
]
1817

1918
[dependencies]
@@ -22,6 +21,7 @@ maplit = "1.0.2"
2221
[dev-dependencies]
2322
libc = "0.2"
2423
serial_test = "0.4.0"
24+
serde_json = "1.0.57"
2525

2626
[build-dependencies]
2727
bindgen = "0.58.1"
@@ -31,3 +31,8 @@ cmake = "0.1.45"
3131
version = "1"
3232
default-features = false
3333
features = ["user-hooks"]
34+
35+
[[bench]]
36+
harness = false
37+
name = "connection-benchmark"
38+
path = "benches/connection.rs"

benches/connection.rs

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
use maplit::hashmap;
2+
use rsmgclient::{ConnectParams, Connection, MgError, QueryParam};
3+
4+
use std::process::Command;
5+
use std::time::Instant;
6+
use std::{thread, time};
7+
8+
use serde_json::json;
9+
use std::collections::HashMap;
10+
use std::fs::{create_dir_all, OpenOptions};
11+
use std::io::prelude::*;
12+
use std::path::Path;
13+
14+
const NUMBER_OF_REPS: u32 = 100;
15+
const CONTAINER_NAME: &str = "memgraph-rsmgclient-benchmark";
16+
const FILE_PATH: &str = "./target/benchmark-summary.json";
17+
const MEMGRAPH_VERSION: &str = "memgraph:1.6.0-community";
18+
19+
fn main() {
20+
let insert_samples = insert_query_benchmark();
21+
let small_query_samples = small_query_with_query_params_benchmark();
22+
let small_query_2_samples = small_query_with_query_params_2_benchmark();
23+
let large_query_samples = large_query_benchmark();
24+
let large_query_2_samples = large_query_2_benchmark();
25+
26+
let summary = json!({
27+
"insert_query": {
28+
"samples": insert_samples,
29+
},
30+
"small_query": {
31+
"samples": small_query_samples,
32+
},
33+
"small_query_2": {
34+
"samples": small_query_2_samples,
35+
},
36+
"large_query": {
37+
"samples": large_query_samples,
38+
},
39+
"large_query_2": {
40+
"samples": large_query_2_samples,
41+
}
42+
});
43+
44+
write_to_file(FILE_PATH, summary.to_string().as_bytes());
45+
}
46+
47+
fn start_server() -> Connection {
48+
// Delete container from before if present.
49+
match Command::new("sh")
50+
.arg("-c")
51+
.arg(format!("docker rm {}", CONTAINER_NAME))
52+
.output()
53+
.expect("unable to delete container")
54+
.status
55+
.success()
56+
{
57+
true => {}
58+
false => println!("unable to delete container"),
59+
}
60+
61+
// Start the new server instance.
62+
match Command::new("sh")
63+
.arg("-c")
64+
.arg(format!(
65+
"docker run --rm -d -p 7687:7687 --name {} {} --telemetry-enabled=False",
66+
CONTAINER_NAME, MEMGRAPH_VERSION
67+
))
68+
.output()
69+
.expect("failed to start server")
70+
.status
71+
.success()
72+
{
73+
true => {}
74+
false => panic!("failed to start server"),
75+
}
76+
77+
// Wait until server has started.
78+
loop {
79+
match Connection::connect(&ConnectParams {
80+
host: Some(String::from("localhost")),
81+
autocommit: true,
82+
..Default::default()
83+
}) {
84+
Ok(connection) => {
85+
return connection;
86+
}
87+
Err(_) => {
88+
thread::sleep(time::Duration::from_millis(100));
89+
}
90+
}
91+
}
92+
}
93+
94+
fn stop_server() {
95+
let _ = Command::new("sh")
96+
.arg("-c")
97+
.arg(format!("docker stop {}", CONTAINER_NAME))
98+
.output()
99+
.expect("failed to stop server");
100+
}
101+
102+
fn benchmark_query(
103+
query: &str,
104+
query_params: Option<&HashMap<String, QueryParam>>,
105+
setup: &dyn Fn(&mut Connection) -> Result<(), MgError>,
106+
) -> Vec<f64> {
107+
let mut connection = start_server();
108+
if let Err(err) = setup(&mut connection) {
109+
panic!("{}", err)
110+
}
111+
112+
let mut samples = Vec::with_capacity(NUMBER_OF_REPS as usize);
113+
for _ in 0..NUMBER_OF_REPS {
114+
let start = Instant::now();
115+
let _ = match connection.execute(query, query_params) {
116+
Ok(cols) => cols,
117+
Err(err) => panic!("{}", err),
118+
};
119+
let _ = match connection.fetchall() {
120+
Ok(vals) => vals,
121+
Err(err) => panic!("{}", err),
122+
};
123+
// Convert to ms.
124+
samples.push(start.elapsed().as_nanos() as f64 / 1e6_f64);
125+
println!("Another benchmark rep DONE");
126+
}
127+
128+
stop_server();
129+
130+
samples
131+
}
132+
133+
fn write_to_file(file_name: &str, data: &[u8]) {
134+
let path = Path::new(file_name);
135+
if let Some(p) = path.parent() {
136+
create_dir_all(p).expect("Unable to create dirs")
137+
};
138+
let mut file = OpenOptions::new()
139+
.create(true)
140+
.write(true)
141+
.open(path)
142+
.expect("unable to write to file");
143+
file.write_all(data).expect("unable to write to file");
144+
}
145+
146+
fn insert_query_benchmark() -> Vec<f64> {
147+
let times = benchmark_query("CREATE (u:User)", None, &|_| Ok(()));
148+
println!("insert_query_benchmark DONE");
149+
times
150+
}
151+
152+
fn create_index(connection: &mut Connection, index: &str) -> Result<(), MgError> {
153+
connection.execute(format!("CREATE INDEX ON {}", index).as_str(), None)?;
154+
connection.fetchall()?;
155+
Ok(())
156+
}
157+
158+
fn small_query_with_query_params_benchmark() -> Vec<f64> {
159+
let times = benchmark_query(
160+
"MATCH (u:User) WHERE u.name = $name RETURN u",
161+
Some(&hashmap! {
162+
String::from("name") => QueryParam::String(String::from("u")),
163+
}),
164+
&|connection| {
165+
connection.execute("CREATE (u:User {name: 'u'})", None)?;
166+
connection.fetchall()?;
167+
168+
create_index(connection, ":User(name)")
169+
},
170+
);
171+
println!("small_query_with_query_params_benchmark DONE");
172+
times
173+
}
174+
175+
fn small_query_with_query_params_2_benchmark() -> Vec<f64> {
176+
let times = benchmark_query(
177+
"MATCH (u:User) WHERE u.id = $id RETURN u",
178+
Some(&hashmap! {
179+
String::from("id") => QueryParam::Int(1),
180+
}),
181+
&|connection| {
182+
let mut str = String::new();
183+
for _ in 0..100 {
184+
str.push('a');
185+
}
186+
connection.execute(
187+
format!("CREATE (u:User {{id: 1, name: '{}'}})", str).as_str(),
188+
None,
189+
)?;
190+
connection.fetchall()?;
191+
192+
create_index(connection, ":User(id)")
193+
},
194+
);
195+
println!("small_query_with_query_params_2_benchmark DONE");
196+
times
197+
}
198+
199+
fn large_query_benchmark() -> Vec<f64> {
200+
let times = benchmark_query("MATCH (u:User) RETURN u", None, &|connection| {
201+
for i in 0..1000 {
202+
connection.execute(format!("CREATE (u:User {{id: {}}})", i,).as_str(), None)?;
203+
connection.fetchall()?;
204+
}
205+
Ok(())
206+
});
207+
println!("large_query_benchmark DONE");
208+
times
209+
}
210+
211+
fn large_query_2_benchmark() -> Vec<f64> {
212+
let times = benchmark_query("MATCH (u:User) RETURN u", None, &|connection| {
213+
let mut name = String::new();
214+
for _ in 0..100 {
215+
name.push('a');
216+
}
217+
for i in 0..100 {
218+
connection.execute(
219+
format!("CREATE (u:User {{id: {}, name: '{}'}})", i, name).as_str(),
220+
None,
221+
)?;
222+
connection.fetchall()?;
223+
}
224+
Ok(())
225+
});
226+
println!("large_query_2_benchmark DONE");
227+
times
228+
}

src/error/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use std::fmt;
1616

1717
/// Error returned by using connection.
18+
#[derive(Debug)]
1819
pub struct MgError {
1920
message: String,
2021
}

0 commit comments

Comments
 (0)