diff --git a/tests/performance/.gitignore b/tests/performance/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..29dc14c7167ab69076775ca77834e8616f0bd1e8
--- /dev/null
+++ b/tests/performance/.gitignore
@@ -0,0 +1,2 @@
+reports
+node_modules
diff --git a/tests/performance/example_reports/1561342783121-BeforeSingleflight-ColdCache.json b/tests/performance/example_reports/1561342783121-BeforeSingleflight-ColdCache.json
new file mode 100644
index 0000000000000000000000000000000000000000..d8745ddd214274513fd00b572f2b97abcfc7e52d
--- /dev/null
+++ b/tests/performance/example_reports/1561342783121-BeforeSingleflight-ColdCache.json
@@ -0,0 +1,202 @@
+{
+  "failed": 0,
+  "corrupted": 0,
+  "requestTimes": [
+    10007,
+    10083,
+    10100,
+    10137,
+    10402,
+    10413,
+    10427,
+    10554,
+    10555,
+    10578,
+    10640,
+    10641,
+    10775,
+    10939,
+    11089,
+    11090,
+    11097,
+    11317,
+    11460,
+    11464,
+    11466,
+    11467,
+    11520,
+    11625,
+    11741,
+    11762,
+    11788,
+    11809,
+    11815,
+    11816,
+    11867,
+    11884,
+    11921,
+    11991,
+    11997,
+    12103,
+    12109,
+    12184,
+    12526,
+    12527,
+    13039,
+    13185,
+    13191,
+    13314,
+    13343,
+    13489,
+    13499,
+    13556,
+    13557,
+    4401,
+    4523,
+    4524,
+    4575,
+    4730,
+    4862,
+    4875,
+    5027,
+    5033,
+    5034,
+    5035,
+    5042,
+    5045,
+    5045,
+    5050,
+    5053,
+    5091,
+    5096,
+    5101,
+    5143,
+    5155,
+    5292,
+    5307,
+    5309,
+    5460,
+    5464,
+    5467,
+    5469,
+    5470,
+    5475,
+    5476,
+    5480,
+    5491,
+    5520,
+    5521,
+    5530,
+    5545,
+    5564,
+    5566,
+    5578,
+    5580,
+    5618,
+    5632,
+    5640,
+    5742,
+    5742,
+    5763,
+    5765,
+    5770,
+    5771,
+    5787,
+    5788,
+    5815,
+    5834,
+    5834,
+    5855,
+    5863,
+    5867,
+    5891,
+    5894,
+    5899,
+    5931,
+    5955,
+    5964,
+    6111,
+    6114,
+    6116,
+    6117,
+    6150,
+    6295,
+    6304,
+    6316,
+    6316,
+    6319,
+    6329,
+    6332,
+    6335,
+    6345,
+    6365,
+    6381,
+    6394,
+    6413,
+    6510,
+    6517,
+    6521,
+    6529,
+    6530,
+    6532,
+    6590,
+    6637,
+    6642,
+    6647,
+    6857,
+    6885,
+    6934,
+    6962,
+    6962,
+    6969,
+    6999,
+    7340,
+    7425,
+    7473,
+    7603,
+    7649,
+    7698,
+    7715,
+    7734,
+    7738,
+    7753,
+    7758,
+    7761,
+    7836,
+    7837,
+    7892,
+    7892,
+    7915,
+    7925,
+    8208,
+    8216,
+    8217,
+    8217,
+    8219,
+    8273,
+    8275,
+    8282,
+    8456,
+    8512,
+    8513,
+    8587,
+    8791,
+    8793,
+    8794,
+    8797,
+    8801,
+    8802,
+    9023,
+    9264,
+    9367,
+    9622,
+    9994,
+    9995,
+    9998
+  ],
+  "min": 4401,
+  "max": 13557,
+  "average": 7878.104712041885,
+  "median": 5763,
+  "count": 191
+}
\ No newline at end of file
diff --git a/tests/performance/example_reports/1561342955384-BeforeSingleflight-WarmCache.json b/tests/performance/example_reports/1561342955384-BeforeSingleflight-WarmCache.json
new file mode 100644
index 0000000000000000000000000000000000000000..683179973166abc61d7c9a832c4985c1030c3782
--- /dev/null
+++ b/tests/performance/example_reports/1561342955384-BeforeSingleflight-WarmCache.json
@@ -0,0 +1,202 @@
+{
+  "failed": 0,
+  "corrupted": 0,
+  "requestTimes": [
+    1054,
+    1055,
+    1064,
+    1071,
+    1076,
+    1077,
+    1087,
+    1091,
+    1096,
+    1108,
+    1112,
+    1133,
+    1135,
+    1141,
+    1161,
+    1165,
+    1171,
+    1191,
+    1193,
+    1208,
+    1261,
+    1290,
+    1308,
+    1315,
+    1324,
+    1333,
+    1350,
+    1372,
+    1386,
+    1390,
+    1400,
+    1406,
+    1425,
+    1436,
+    1455,
+    1460,
+    1471,
+    1502,
+    1511,
+    1513,
+    1532,
+    1546,
+    1555,
+    1562,
+    1572,
+    1577,
+    1592,
+    1597,
+    1608,
+    1618,
+    1626,
+    1627,
+    1631,
+    1638,
+    1660,
+    1670,
+    1682,
+    1689,
+    1695,
+    1703,
+    1709,
+    1717,
+    1726,
+    1727,
+    1730,
+    1751,
+    1763,
+    1764,
+    1779,
+    1779,
+    1785,
+    1795,
+    1808,
+    1818,
+    1821,
+    1827,
+    1829,
+    1830,
+    1842,
+    1850,
+    1860,
+    1875,
+    1883,
+    1892,
+    1893,
+    1905,
+    1907,
+    1917,
+    1921,
+    1932,
+    1935,
+    1949,
+    1949,
+    1952,
+    1956,
+    1957,
+    1967,
+    1994,
+    2002,
+    2004,
+    2007,
+    2011,
+    2018,
+    2037,
+    2038,
+    2041,
+    2041,
+    2061,
+    2073,
+    2093,
+    2097,
+    2105,
+    2118,
+    2129,
+    2138,
+    2146,
+    2160,
+    2163,
+    2174,
+    2175,
+    2176,
+    2184,
+    2210,
+    2213,
+    2225,
+    2232,
+    2233,
+    2246,
+    2250,
+    2253,
+    2262,
+    2265,
+    2276,
+    2288,
+    2300,
+    2318,
+    2321,
+    2324,
+    2338,
+    2356,
+    2358,
+    2365,
+    2370,
+    2373,
+    2387,
+    2387,
+    2388,
+    2389,
+    2407,
+    2423,
+    2431,
+    2441,
+    2441,
+    2442,
+    2459,
+    2469,
+    2474,
+    2492,
+    2493,
+    2495,
+    2503,
+    2513,
+    2514,
+    2514,
+    2515,
+    2515,
+    2520,
+    2531,
+    2538,
+    2541,
+    2543,
+    2550,
+    2571,
+    2574,
+    2625,
+    2650,
+    457,
+    564,
+    660,
+    693,
+    758,
+    774,
+    777,
+    796,
+    797,
+    838,
+    881,
+    902,
+    927,
+    957,
+    999
+  ],
+  "min": 457,
+  "max": 2650,
+  "average": 1806.5602094240837,
+  "median": 1957,
+  "count": 191
+}
\ No newline at end of file
diff --git a/tests/performance/example_reports/1561432114806-AfterSingleflight-ColdCache.json b/tests/performance/example_reports/1561432114806-AfterSingleflight-ColdCache.json
new file mode 100644
index 0000000000000000000000000000000000000000..cb1eb9ec233c732fae1bd59711d4a2f42b7b43d2
--- /dev/null
+++ b/tests/performance/example_reports/1561432114806-AfterSingleflight-ColdCache.json
@@ -0,0 +1,202 @@
+{
+  "failed": 0,
+  "corrupted": 0,
+  "requestTimes": [
+    10336,
+    10337,
+    10337,
+    10433,
+    10434,
+    10434,
+    10434,
+    10435,
+    10435,
+    10435,
+    10435,
+    10436,
+    10439,
+    10440,
+    10440,
+    10463,
+    10463,
+    10464,
+    10464,
+    10464,
+    10465,
+    11894,
+    11895,
+    11896,
+    12483,
+    12483,
+    12483,
+    12484,
+    12484,
+    12485,
+    3550,
+    3551,
+    3551,
+    3826,
+    3826,
+    3827,
+    3842,
+    3843,
+    3843,
+    3844,
+    3844,
+    3846,
+    3846,
+    3847,
+    3847,
+    3847,
+    3847,
+    3847,
+    3847,
+    3848,
+    3848,
+    3849,
+    3849,
+    3849,
+    3850,
+    3851,
+    3851,
+    3852,
+    3852,
+    3852,
+    3854,
+    3944,
+    3945,
+    3945,
+    3946,
+    3947,
+    3947,
+    3947,
+    3947,
+    3948,
+    3948,
+    3948,
+    3948,
+    3948,
+    3948,
+    3948,
+    3948,
+    3949,
+    3949,
+    3949,
+    3950,
+    3951,
+    3951,
+    3951,
+    3959,
+    3960,
+    3960,
+    3961,
+    5794,
+    5794,
+    5794,
+    5826,
+    5827,
+    5827,
+    5844,
+    5845,
+    5845,
+    5851,
+    5851,
+    5851,
+    6039,
+    6040,
+    6040,
+    6116,
+    6117,
+    6117,
+    6118,
+    6119,
+    6119,
+    6120,
+    6120,
+    6120,
+    6120,
+    6121,
+    6121,
+    6121,
+    6121,
+    6121,
+    6122,
+    6122,
+    6122,
+    6122,
+    6123,
+    6123,
+    6123,
+    6124,
+    6124,
+    6124,
+    6125,
+    6126,
+    6173,
+    6175,
+    6175,
+    6238,
+    6239,
+    6239,
+    6239,
+    6239,
+    6240,
+    6251,
+    6252,
+    6252,
+    6253,
+    6253,
+    6253,
+    6280,
+    6280,
+    6281,
+    6281,
+    6281,
+    6281,
+    6386,
+    6387,
+    6500,
+    6504,
+    6529,
+    6531,
+    6531,
+    6532,
+    6532,
+    6532,
+    6532,
+    6532,
+    6532,
+    6532,
+    6533,
+    6533,
+    8100,
+    8100,
+    8100,
+    8376,
+    8377,
+    8377,
+    8534,
+    8535,
+    8535,
+    8666,
+    8666,
+    8666,
+    8666,
+    8666,
+    8667,
+    8685,
+    8685,
+    8685,
+    8745,
+    8751,
+    8751,
+    8752,
+    8753,
+    8753
+  ],
+  "min": 3550,
+  "max": 12485,
+  "average": 6538.3298429319375,
+  "median": 5845,
+  "count": 191
+}
\ No newline at end of file
diff --git a/tests/performance/example_reports/1561432138475-AfterSingleflight-WarmCache.json b/tests/performance/example_reports/1561432138475-AfterSingleflight-WarmCache.json
new file mode 100644
index 0000000000000000000000000000000000000000..c3693ac692e09aea18f156b1c4014462905176f2
--- /dev/null
+++ b/tests/performance/example_reports/1561432138475-AfterSingleflight-WarmCache.json
@@ -0,0 +1,202 @@
+{
+  "failed": 0,
+  "corrupted": 0,
+  "requestTimes": [
+    178,
+    178,
+    179,
+    179,
+    179,
+    180,
+    193,
+    193,
+    193,
+    193,
+    193,
+    193,
+    193,
+    194,
+    194,
+    194,
+    194,
+    194,
+    194,
+    195,
+    195,
+    195,
+    195,
+    207,
+    209,
+    209,
+    209,
+    209,
+    209,
+    209,
+    209,
+    209,
+    210,
+    210,
+    210,
+    211,
+    211,
+    211,
+    211,
+    211,
+    211,
+    211,
+    212,
+    212,
+    212,
+    212,
+    212,
+    212,
+    213,
+    213,
+    213,
+    213,
+    213,
+    213,
+    214,
+    214,
+    214,
+    222,
+    222,
+    222,
+    222,
+    222,
+    222,
+    222,
+    224,
+    224,
+    227,
+    227,
+    228,
+    243,
+    244,
+    244,
+    244,
+    244,
+    244,
+    245,
+    245,
+    246,
+    247,
+    248,
+    249,
+    249,
+    249,
+    249,
+    249,
+    250,
+    250,
+    250,
+    251,
+    251,
+    251,
+    251,
+    251,
+    253,
+    253,
+    254,
+    255,
+    264,
+    264,
+    264,
+    264,
+    264,
+    265,
+    268,
+    268,
+    268,
+    270,
+    270,
+    270,
+    270,
+    270,
+    271,
+    308,
+    308,
+    309,
+    314,
+    315,
+    316,
+    322,
+    322,
+    323,
+    323,
+    324,
+    325,
+    328,
+    329,
+    330,
+    338,
+    338,
+    338,
+    339,
+    339,
+    339,
+    347,
+    348,
+    348,
+    348,
+    349,
+    349,
+    357,
+    357,
+    358,
+    358,
+    358,
+    358,
+    358,
+    358,
+    358,
+    358,
+    358,
+    359,
+    359,
+    359,
+    359,
+    359,
+    359,
+    359,
+    360,
+    360,
+    360,
+    360,
+    360,
+    360,
+    361,
+    361,
+    362,
+    362,
+    362,
+    363,
+    363,
+    363,
+    364,
+    364,
+    364,
+    364,
+    364,
+    370,
+    370,
+    370,
+    371,
+    372,
+    373,
+    374,
+    375,
+    375,
+    385,
+    386,
+    386,
+    386,
+    386,
+    386
+  ],
+  "min": 178,
+  "max": 386,
+  "average": 278.0418848167539,
+  "median": 254,
+  "count": 191
+}
\ No newline at end of file
diff --git a/tests/performance/expected_thumbnails/t2bot.io_00845bfa7e453d392f86b53eadbdbfd3d10d3fcd.png b/tests/performance/expected_thumbnails/t2bot.io_00845bfa7e453d392f86b53eadbdbfd3d10d3fcd.png
new file mode 100644
index 0000000000000000000000000000000000000000..e6868a16ed7b1bfe942881e522eb0114db656054
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_00845bfa7e453d392f86b53eadbdbfd3d10d3fcd.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_0850052a28cf72d91c77b52b74e67cc9fccd3607.png b/tests/performance/expected_thumbnails/t2bot.io_0850052a28cf72d91c77b52b74e67cc9fccd3607.png
new file mode 100644
index 0000000000000000000000000000000000000000..d0ba73e478d48784d8ec5787c36e99340f00352d
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_0850052a28cf72d91c77b52b74e67cc9fccd3607.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_102d17f15f25e697d857c5d5003efcf27c36886f.png b/tests/performance/expected_thumbnails/t2bot.io_102d17f15f25e697d857c5d5003efcf27c36886f.png
new file mode 100644
index 0000000000000000000000000000000000000000..66a4644bdfe6f95ce824d870eacc75a3c3095939
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_102d17f15f25e697d857c5d5003efcf27c36886f.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_13754a79523fb6c96abdb06a52fd2b54788d1a2a.png b/tests/performance/expected_thumbnails/t2bot.io_13754a79523fb6c96abdb06a52fd2b54788d1a2a.png
new file mode 100644
index 0000000000000000000000000000000000000000..102560f3fb4e93a654b7c93049d5fe30b884d53e
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_13754a79523fb6c96abdb06a52fd2b54788d1a2a.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_144c17562179ad788652961dd5238e8796f6a0a5.png b/tests/performance/expected_thumbnails/t2bot.io_144c17562179ad788652961dd5238e8796f6a0a5.png
new file mode 100644
index 0000000000000000000000000000000000000000..a79fbd4e813481553d0510163ceffd753a0b02c6
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_144c17562179ad788652961dd5238e8796f6a0a5.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_180a386b83717f75785c4f0501ae1551ff448a1b.png b/tests/performance/expected_thumbnails/t2bot.io_180a386b83717f75785c4f0501ae1551ff448a1b.png
new file mode 100644
index 0000000000000000000000000000000000000000..58fcf02aaf2b52a011de9b0d280a8e706586dc79
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_180a386b83717f75785c4f0501ae1551ff448a1b.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_1e4925f241581d27d5dfff7d7b0bfaf62714b4c3.png b/tests/performance/expected_thumbnails/t2bot.io_1e4925f241581d27d5dfff7d7b0bfaf62714b4c3.png
new file mode 100644
index 0000000000000000000000000000000000000000..429e292cda1eba7400d034b52cc3a418d8e78d04
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_1e4925f241581d27d5dfff7d7b0bfaf62714b4c3.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_20777c9ccca38567559ef08764602ee5c2ab6927.png b/tests/performance/expected_thumbnails/t2bot.io_20777c9ccca38567559ef08764602ee5c2ab6927.png
new file mode 100644
index 0000000000000000000000000000000000000000..2c545c16f2c2ef0ed5e8d8de00d7612b5d80543e
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_20777c9ccca38567559ef08764602ee5c2ab6927.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_3fab93bdf0643c191721426aa930aab9aa61fc47.png b/tests/performance/expected_thumbnails/t2bot.io_3fab93bdf0643c191721426aa930aab9aa61fc47.png
new file mode 100644
index 0000000000000000000000000000000000000000..f20dae3edf4a200b7721a861336a9422fb935be3
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_3fab93bdf0643c191721426aa930aab9aa61fc47.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_4b7b400b9863e4be09014b00cb430fb63b5b60ac.png b/tests/performance/expected_thumbnails/t2bot.io_4b7b400b9863e4be09014b00cb430fb63b5b60ac.png
new file mode 100644
index 0000000000000000000000000000000000000000..a459f3ce533608276206d29a62caae2b6f5f800e
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_4b7b400b9863e4be09014b00cb430fb63b5b60ac.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_560e8403349cfbbc977129fb58c7c12be667294e.png b/tests/performance/expected_thumbnails/t2bot.io_560e8403349cfbbc977129fb58c7c12be667294e.png
new file mode 100644
index 0000000000000000000000000000000000000000..627671d3fad6f7507d1e9cf783dd1371fd9c4148
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_560e8403349cfbbc977129fb58c7c12be667294e.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_7e449b5990a142803038d4639c646d7a826a776d.png b/tests/performance/expected_thumbnails/t2bot.io_7e449b5990a142803038d4639c646d7a826a776d.png
new file mode 100644
index 0000000000000000000000000000000000000000..d94bb684a014c1050625e1a0b34a1fd046f04a56
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_7e449b5990a142803038d4639c646d7a826a776d.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_8039dfbaeb0f2aa3e04d1275291d090206f84728.png b/tests/performance/expected_thumbnails/t2bot.io_8039dfbaeb0f2aa3e04d1275291d090206f84728.png
new file mode 100644
index 0000000000000000000000000000000000000000..eec5e9d7aed2b9bff7a3c6031c736502e13951b8
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_8039dfbaeb0f2aa3e04d1275291d090206f84728.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_84acd2d1464a8bbdabde408b5f962b59a7a9f8c7.png b/tests/performance/expected_thumbnails/t2bot.io_84acd2d1464a8bbdabde408b5f962b59a7a9f8c7.png
new file mode 100644
index 0000000000000000000000000000000000000000..9d24e347a01a717c29da7b35663600f493a503c7
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_84acd2d1464a8bbdabde408b5f962b59a7a9f8c7.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_89c4e87cd44a81ea20aca1b81e10a68fb295bd9c.png b/tests/performance/expected_thumbnails/t2bot.io_89c4e87cd44a81ea20aca1b81e10a68fb295bd9c.png
new file mode 100644
index 0000000000000000000000000000000000000000..68e641c1dd82138452bae2b7adbfa2c88a2930df
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_89c4e87cd44a81ea20aca1b81e10a68fb295bd9c.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_8ab3523920932d69e5f32303761f504a148ca66b.png b/tests/performance/expected_thumbnails/t2bot.io_8ab3523920932d69e5f32303761f504a148ca66b.png
new file mode 100644
index 0000000000000000000000000000000000000000..9a15302df1b0161d522c5d29fd8831c1a19d2e79
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_8ab3523920932d69e5f32303761f504a148ca66b.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_a1fda5b8102268243c5651a683a473d858841c64.png b/tests/performance/expected_thumbnails/t2bot.io_a1fda5b8102268243c5651a683a473d858841c64.png
new file mode 100644
index 0000000000000000000000000000000000000000..4528b91b7544b9e05bcc9e39138aa2df479a99a3
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_a1fda5b8102268243c5651a683a473d858841c64.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_a2cda3f751ef8261e959743c95ed6a36ad73fe4d.png b/tests/performance/expected_thumbnails/t2bot.io_a2cda3f751ef8261e959743c95ed6a36ad73fe4d.png
new file mode 100644
index 0000000000000000000000000000000000000000..bde48e110eb78612239d26c3d60688297c753b5c
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_a2cda3f751ef8261e959743c95ed6a36ad73fe4d.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_a479b93f20a6589b11449250cb7720f3582bc4ac.png b/tests/performance/expected_thumbnails/t2bot.io_a479b93f20a6589b11449250cb7720f3582bc4ac.png
new file mode 100644
index 0000000000000000000000000000000000000000..195cb44ca66e981fdeffc51ecff04a46b0eb2852
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_a479b93f20a6589b11449250cb7720f3582bc4ac.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_a4d233b40227e2eac716f797794c54303f067fad.png b/tests/performance/expected_thumbnails/t2bot.io_a4d233b40227e2eac716f797794c54303f067fad.png
new file mode 100644
index 0000000000000000000000000000000000000000..300f77ebdbb3470ce8b3fbbccaf3cb10b58c0dde
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_a4d233b40227e2eac716f797794c54303f067fad.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_af87cb7b79d2b099ba0379764aca7d2cf3d55ea2.png b/tests/performance/expected_thumbnails/t2bot.io_af87cb7b79d2b099ba0379764aca7d2cf3d55ea2.png
new file mode 100644
index 0000000000000000000000000000000000000000..ced7a5d4bf3e239a3b12c55b3fc338b2a68c39e5
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_af87cb7b79d2b099ba0379764aca7d2cf3d55ea2.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_b02309d6c0a63595e262b081222aec2d432fef9a.png b/tests/performance/expected_thumbnails/t2bot.io_b02309d6c0a63595e262b081222aec2d432fef9a.png
new file mode 100644
index 0000000000000000000000000000000000000000..6cfdd20be2d4bcf7156360dc72c2c22ba8710457
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_b02309d6c0a63595e262b081222aec2d432fef9a.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_c1a498a4a00e0932f55c0ed32305216b40d5f22a.png b/tests/performance/expected_thumbnails/t2bot.io_c1a498a4a00e0932f55c0ed32305216b40d5f22a.png
new file mode 100644
index 0000000000000000000000000000000000000000..6678e0a2f9ab4892025fe5e7251ec86f51962c3c
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_c1a498a4a00e0932f55c0ed32305216b40d5f22a.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_cd2fa571ec432e5bcbea0b2d56a87494590af01a.png b/tests/performance/expected_thumbnails/t2bot.io_cd2fa571ec432e5bcbea0b2d56a87494590af01a.png
new file mode 100644
index 0000000000000000000000000000000000000000..993738fb99ed66ef0a7c9e9dad1e5811a038a48e
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_cd2fa571ec432e5bcbea0b2d56a87494590af01a.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_d65b755940c9cc0d850ff42591268186fc7065f9.png b/tests/performance/expected_thumbnails/t2bot.io_d65b755940c9cc0d850ff42591268186fc7065f9.png
new file mode 100644
index 0000000000000000000000000000000000000000..0596bf2894c56ac44d6a54a4582570a1b2424feb
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_d65b755940c9cc0d850ff42591268186fc7065f9.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_dbbc7ea87cc44060147d641afd587b0efc9a5cd6.png b/tests/performance/expected_thumbnails/t2bot.io_dbbc7ea87cc44060147d641afd587b0efc9a5cd6.png
new file mode 100644
index 0000000000000000000000000000000000000000..bba0d065c183d45f6b3b66af1e7d7c2e0d3a6617
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_dbbc7ea87cc44060147d641afd587b0efc9a5cd6.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_e31525ee69b9aa3cfa34e09317eb4c9d354fa6e5.png b/tests/performance/expected_thumbnails/t2bot.io_e31525ee69b9aa3cfa34e09317eb4c9d354fa6e5.png
new file mode 100644
index 0000000000000000000000000000000000000000..f0281361c378e60ce087b322b548c70efa5f7b17
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_e31525ee69b9aa3cfa34e09317eb4c9d354fa6e5.png differ
diff --git a/tests/performance/expected_thumbnails/t2bot.io_f645a8bd38d26e622492ec36b98f28332c1a60d1.png b/tests/performance/expected_thumbnails/t2bot.io_f645a8bd38d26e622492ec36b98f28332c1a60d1.png
new file mode 100644
index 0000000000000000000000000000000000000000..ec55ec1bc116403640c2d74daa798503273b4048
Binary files /dev/null and b/tests/performance/expected_thumbnails/t2bot.io_f645a8bd38d26e622492ec36b98f28332c1a60d1.png differ
diff --git a/tests/performance/generate-expectations.js b/tests/performance/generate-expectations.js
new file mode 100644
index 0000000000000000000000000000000000000000..ac28ba4ad36bb8ea7b3aa3e9c6f79c6328fb362d
--- /dev/null
+++ b/tests/performance/generate-expectations.js
@@ -0,0 +1,19 @@
+const request = require("request-promise");
+const thumbnails = require("./thumbnails.json");
+const fs = require("fs");
+
+const target = "http://localhost:8001";
+
+async function download(mxc) {
+    console.log("Downloading " + mxc);
+    const result = await request(`${target}/_matrix/media/r0/thumbnail/${mxc.substring("mxc://".length)}?width=800&height=600&method=crop&animated=false`, {encoding: null});
+    fs.writeFileSync("expected_thumbnails/" + mxc.substring("mxc://".length).replace(/\//g, '_') + ".png", result);
+}
+
+console.log("Downloading thumbnails...");
+const promises = [];
+for (const thumbnailMxc of Object.keys(thumbnails)) {
+    promises.push(download(thumbnailMxc));
+}
+
+Promise.all(promises).then(() => console.log("Done!"));
diff --git a/tests/performance/index.js b/tests/performance/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..5fd964d43c59b1bd41b2e4383f55c802c93f12ef
--- /dev/null
+++ b/tests/performance/index.js
@@ -0,0 +1,64 @@
+const request = require("request-promise");
+const thumbnails = require("./thumbnails.json");
+const fs = require("fs");
+const mkdirp = require("mkdirp");
+
+const target = "http://localhost:8001";
+
+const requestTimes = [];
+let failed = 0;
+let corrupted = 0;
+
+async function thumbnailDefaults(mxc) {
+    const start = (new Date()).getTime();
+
+    const result = Buffer.from(await request(`${target}/_matrix/media/r0/thumbnail/${mxc.substring("mxc://".length)}?width=800&height=600&method=crop&animated=false`, {encoding: null}));
+    const expectedResult = fs.readFileSync("expected_thumbnails/" + mxc.substring("mxc://".length).replace(/\//, '_') + ".png");
+
+    if (Buffer.compare(result, expectedResult) !== 0) {
+        console.warn("Unexpected corruption!");
+        corrupted++;
+    }
+
+    return (new Date()).getTime() - start;
+}
+
+function handleThumbnail(mxc) {
+    console.log("Starting download of " + mxc);
+    return thumbnailDefaults(mxc).then(t => requestTimes.push(t)).catch(() => failed++);
+}
+
+const promises = [];
+for (const thumbnailMxc of Object.keys(thumbnails)) {
+    for (let i = 0; i < thumbnails[thumbnailMxc]; i++) {
+        promises.push(handleThumbnail(thumbnailMxc));
+    }
+}
+
+console.log("Waiting for results...");
+Promise.all(promises).then(() => {
+    requestTimes.sort();
+
+    let average = 0;
+    for (const i of requestTimes) average += i;
+    average = average / requestTimes.length;
+
+    const obj = {
+        failed,
+        corrupted,
+        requestTimes,
+        min: Math.min(...requestTimes),
+        max: Math.max(...requestTimes),
+        average,
+        median: requestTimes[Math.floor(requestTimes.length / 2)],
+        count: requestTimes.length,
+    };
+
+    console.log("Saving report...");
+    mkdirp.sync("reports");
+    fs.writeFileSync(`reports/${(new Date()).getTime()}.json`, JSON.stringify(obj, null, 2));
+
+    delete obj.requestTimes;
+    console.log(obj);
+    console.log("Done!");
+});
diff --git a/tests/performance/package-lock.json b/tests/performance/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..99e0fc92311fab1ee0b1cfc5ec7b30cc4c05e1e7
--- /dev/null
+++ b/tests/performance/package-lock.json
@@ -0,0 +1,393 @@
+{
+  "name": "performance",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "ajv": {
+      "version": "6.10.0",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
+      "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
+      "requires": {
+        "fast-deep-equal": "^2.0.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "asn1": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+      "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+      "requires": {
+        "safer-buffer": "~2.1.0"
+      }
+    },
+    "assert-plus": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+    },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+    },
+    "aws-sign2": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
+    },
+    "aws4": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
+      "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
+    },
+    "bcrypt-pbkdf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+      "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+      "requires": {
+        "tweetnacl": "^0.14.3"
+      }
+    },
+    "bluebird": {
+      "version": "3.5.5",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz",
+      "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w=="
+    },
+    "caseless": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+    },
+    "combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "requires": {
+        "delayed-stream": "~1.0.0"
+      }
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+    },
+    "dashdash": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+    },
+    "ecc-jsbn": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+      "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+      "requires": {
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.1.0"
+      }
+    },
+    "extend": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+    },
+    "extsprintf": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+    },
+    "fast-deep-equal": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+      "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+      "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
+    },
+    "forever-agent": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+    },
+    "form-data": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+      "requires": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.6",
+        "mime-types": "^2.1.12"
+      }
+    },
+    "getpass": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "har-schema": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
+    },
+    "har-validator": {
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+      "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+      "requires": {
+        "ajv": "^6.5.5",
+        "har-schema": "^2.0.0"
+      }
+    },
+    "http-signature": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "jsprim": "^1.2.2",
+        "sshpk": "^1.7.0"
+      }
+    },
+    "is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+    },
+    "isstream": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+    },
+    "jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+    },
+    "json-schema": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+    },
+    "json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+    },
+    "jsprim": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+      "requires": {
+        "assert-plus": "1.0.0",
+        "extsprintf": "1.3.0",
+        "json-schema": "0.2.3",
+        "verror": "1.10.0"
+      }
+    },
+    "lodash": {
+      "version": "4.17.11",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
+      "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
+    },
+    "mime-db": {
+      "version": "1.40.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
+      "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
+    },
+    "mime-types": {
+      "version": "2.1.24",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
+      "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
+      "requires": {
+        "mime-db": "1.40.0"
+      }
+    },
+    "minimist": {
+      "version": "0.0.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+      "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+    },
+    "mkdirp": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+      "requires": {
+        "minimist": "0.0.8"
+      }
+    },
+    "oauth-sign": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+      "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
+    },
+    "performance-now": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+    },
+    "psl": {
+      "version": "1.1.33",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.33.tgz",
+      "integrity": "sha512-LTDP2uSrsc7XCb5lO7A8BI1qYxRe/8EqlRvMeEl6rsnYAqDOl8xHR+8lSAIVfrNaSAlTPTNOCgNjWcoUL3AZsw=="
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+    },
+    "qs": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
+    },
+    "request": {
+      "version": "2.88.0",
+      "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
+      "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
+      "requires": {
+        "aws-sign2": "~0.7.0",
+        "aws4": "^1.8.0",
+        "caseless": "~0.12.0",
+        "combined-stream": "~1.0.6",
+        "extend": "~3.0.2",
+        "forever-agent": "~0.6.1",
+        "form-data": "~2.3.2",
+        "har-validator": "~5.1.0",
+        "http-signature": "~1.2.0",
+        "is-typedarray": "~1.0.0",
+        "isstream": "~0.1.2",
+        "json-stringify-safe": "~5.0.1",
+        "mime-types": "~2.1.19",
+        "oauth-sign": "~0.9.0",
+        "performance-now": "^2.1.0",
+        "qs": "~6.5.2",
+        "safe-buffer": "^5.1.2",
+        "tough-cookie": "~2.4.3",
+        "tunnel-agent": "^0.6.0",
+        "uuid": "^3.3.2"
+      }
+    },
+    "request-promise": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.4.tgz",
+      "integrity": "sha512-8wgMrvE546PzbR5WbYxUQogUnUDfM0S7QIFZMID+J73vdFARkFy+HElj4T+MWYhpXwlLp0EQ8Zoj8xUA0he4Vg==",
+      "requires": {
+        "bluebird": "^3.5.0",
+        "request-promise-core": "1.1.2",
+        "stealthy-require": "^1.1.1",
+        "tough-cookie": "^2.3.3"
+      }
+    },
+    "request-promise-core": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz",
+      "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==",
+      "requires": {
+        "lodash": "^4.17.11"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "sshpk": {
+      "version": "1.16.1",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+      "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+      "requires": {
+        "asn1": "~0.2.3",
+        "assert-plus": "^1.0.0",
+        "bcrypt-pbkdf": "^1.0.0",
+        "dashdash": "^1.12.0",
+        "ecc-jsbn": "~0.1.1",
+        "getpass": "^0.1.1",
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.0.2",
+        "tweetnacl": "~0.14.0"
+      }
+    },
+    "stealthy-require": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
+      "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
+    },
+    "tough-cookie": {
+      "version": "2.4.3",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
+      "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
+      "requires": {
+        "psl": "^1.1.24",
+        "punycode": "^1.4.1"
+      },
+      "dependencies": {
+        "punycode": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
+        }
+      }
+    },
+    "tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "tweetnacl": {
+      "version": "0.14.5",
+      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+    },
+    "uri-js": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "uuid": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
+      "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
+    },
+    "verror": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "core-util-is": "1.0.2",
+        "extsprintf": "^1.2.0"
+      }
+    }
+  }
+}
diff --git a/tests/performance/package.json b/tests/performance/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..c9ffeab4717c2bcd95c56e2eaea6d7d8d71419fb
--- /dev/null
+++ b/tests/performance/package.json
@@ -0,0 +1,16 @@
+{
+  "name": "performance",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "MIT",
+  "dependencies": {
+    "mkdirp": "^0.5.1",
+    "request": "^2.88.0",
+    "request-promise": "^4.2.4"
+  }
+}
diff --git a/tests/performance/thumbnails.json b/tests/performance/thumbnails.json
new file mode 100644
index 0000000000000000000000000000000000000000..1ed8135a433cce33253111eb960c2e7809ba1b81
--- /dev/null
+++ b/tests/performance/thumbnails.json
@@ -0,0 +1,30 @@
+{
+    "mxc://t2bot.io/00845bfa7e453d392f86b53eadbdbfd3d10d3fcd": 4,
+    "mxc://t2bot.io/a2cda3f751ef8261e959743c95ed6a36ad73fe4d": 25,
+    "mxc://t2bot.io/0850052a28cf72d91c77b52b74e67cc9fccd3607": 27,
+    "mxc://t2bot.io/a1fda5b8102268243c5651a683a473d858841c64": 27,
+    "mxc://t2bot.io/dbbc7ea87cc44060147d641afd587b0efc9a5cd6": 12,
+    "mxc://t2bot.io/1e4925f241581d27d5dfff7d7b0bfaf62714b4c3": 3,
+    "mxc://t2bot.io/180a386b83717f75785c4f0501ae1551ff448a1b": 3,
+    "mxc://t2bot.io/4b7b400b9863e4be09014b00cb430fb63b5b60ac": 6,
+    "mxc://t2bot.io/c1a498a4a00e0932f55c0ed32305216b40d5f22a": 6,
+    "mxc://t2bot.io/d65b755940c9cc0d850ff42591268186fc7065f9": 3,
+    "mxc://t2bot.io/a479b93f20a6589b11449250cb7720f3582bc4ac": 3,
+    "mxc://t2bot.io/144c17562179ad788652961dd5238e8796f6a0a5": 3,
+    "mxc://t2bot.io/8ab3523920932d69e5f32303761f504a148ca66b": 3,
+    "mxc://t2bot.io/84acd2d1464a8bbdabde408b5f962b59a7a9f8c7": 3,
+    "mxc://t2bot.io/e31525ee69b9aa3cfa34e09317eb4c9d354fa6e5": 3,
+    "mxc://t2bot.io/560e8403349cfbbc977129fb58c7c12be667294e": 6,
+    "mxc://t2bot.io/cd2fa571ec432e5bcbea0b2d56a87494590af01a": 6,
+    "mxc://t2bot.io/b02309d6c0a63595e262b081222aec2d432fef9a": 3,
+    "mxc://t2bot.io/a4d233b40227e2eac716f797794c54303f067fad": 6,
+    "mxc://t2bot.io/7e449b5990a142803038d4639c646d7a826a776d": 6,
+    "mxc://t2bot.io/af87cb7b79d2b099ba0379764aca7d2cf3d55ea2": 3,
+    "mxc://t2bot.io/20777c9ccca38567559ef08764602ee5c2ab6927": 3,
+    "mxc://t2bot.io/f645a8bd38d26e622492ec36b98f28332c1a60d1": 3,
+    "mxc://t2bot.io/8039dfbaeb0f2aa3e04d1275291d090206f84728": 3,
+    "mxc://t2bot.io/89c4e87cd44a81ea20aca1b81e10a68fb295bd9c": 6,
+    "mxc://t2bot.io/13754a79523fb6c96abdb06a52fd2b54788d1a2a": 3,
+    "mxc://t2bot.io/102d17f15f25e697d857c5d5003efcf27c36886f": 6,
+    "mxc://t2bot.io/3fab93bdf0643c191721426aa930aab9aa61fc47": 6
+}