├── .github └── ISSUE_TEMPLATE │ ├── bug.md │ ├── epic.md │ ├── job-story.md │ └── task.md ├── .gitignore ├── LICENSE ├── README.md ├── api ├── .dockerignore ├── .eslintrc.js ├── .prettierrc ├── Dockerfile ├── package-lock.json ├── package.json ├── pm2.json ├── src │ ├── demo │ │ └── benchmark.ts │ ├── env.d.ts │ ├── main.ts │ ├── prediction │ │ ├── capacity.R │ │ ├── latency.R │ │ └── predict.R │ └── server │ │ ├── resolvers.ts │ │ └── schemas.ts ├── tsconfig.json ├── webpack.common.js ├── webpack.development.js └── webpack.production.js ├── client ├── .browserslistrc ├── .dockerignore ├── .env ├── .env.production ├── .env.staging ├── .eslintrc.js ├── .prettierrc ├── Dockerfile ├── babel.config.js ├── conf │ ├── nginx.conf │ └── nginx.conf_base ├── cypress.json ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── api │ │ ├── api.gql.ts │ │ └── client.ts │ ├── app.html │ ├── app.scss │ ├── app.ts │ ├── filters │ │ └── filters.ts │ ├── main.ts │ ├── router.ts │ ├── shims-tsx.d.ts │ ├── shims-vue.d.ts │ ├── store │ │ ├── index.ts │ │ ├── modules │ │ │ └── prediction │ │ │ │ ├── actions.ts │ │ │ │ ├── getters.ts │ │ │ │ ├── index.ts │ │ │ │ ├── mutations.ts │ │ │ │ ├── state.ts │ │ │ │ └── types.ts │ │ └── types.ts │ ├── style │ │ ├── bulma.custom.scss │ │ ├── bulma.variables.scss │ │ ├── global.scss │ │ ├── mixin.scss │ │ └── variables.scss │ ├── utils │ │ ├── SLOTypes.ts │ │ └── uuidGenerator.ts │ └── views │ │ └── home │ │ ├── Home.vue │ │ ├── chart.js │ │ ├── home.html │ │ ├── home.scss │ │ └── home.ts ├── tests │ ├── e2e │ │ ├── .eslintrc.js │ │ ├── plugins │ │ │ └── index.js │ │ ├── specs │ │ │ └── test.js │ │ └── support │ │ │ ├── commands.js │ │ │ └── index.js │ └── unit │ │ ├── .eslintrc.js │ │ └── example.spec.ts ├── tsconfig.json └── vue.config.js ├── k8s └── kubernetes.deployment.yaml ├── r-server ├── Dockerfile ├── Rserv.conf ├── install-packages.R ├── run-rserve.R └── supervisord.conf ├── resources ├── grant_w3f.png ├── research_m-schaffer_tuw.jpg ├── stacktical_logo_v2-dark.png ├── willitscale-api.png └── willitscale-client.png └── skaffold.yaml /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: BUG 3 | about: Use this template to streamline the logging of bugs. 4 | 5 | --- 6 | 7 | ## BUG 8 | 9 | #### EXPECTED BEHAVIOUR 10 | 11 | #### CURRENT BEHAVIOUR 12 | 13 | #### STEPS TO REPRODUCE 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/epic.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: EPIC 3 | about: Use this template to streamline the creation of epics. 4 | 5 | --- 6 | 7 | ## EPIC 8 | #### DESCRIPTION 9 | 10 | `` 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/job-story.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: JOB STORY 3 | about: Use this template to streamline the creation of job stories. 4 | 5 | --- 6 | 7 | ## JOB STORY 8 | #### DESCRIPTION 9 | 10 | When `` ``, `` wants to `` so that `` 11 | 12 | #### ACCEPTANCE CRITERIA 13 | 14 | * `` is able to `` 1 15 | * `` is able to `` 2 16 | ... 17 | * `` is able to `` n 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/task.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: TASK 3 | about: Use this template to streamline the creation of tasks and reference their job 4 | story. 5 | 6 | --- 7 | 8 | ## TASK 9 | #### DESCRIPTION 10 | 11 | `` 12 | 13 | #### JOB STORY 14 | 15 | `` 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | node_modules 4 | npm-debug.log* 5 | 6 | dist 7 | 8 | .env.local 9 | .env.*.local 10 | 11 | .idea 12 | .vscode 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | *.sw* 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![willitscale-polkadot](https://storage.googleapis.com/stacktical-public/willitscale-polkadot.jpg) 2 | 3 | # But will it scale ? 4 | 5 | `"Will it scale ?"` is an open source Predictive Analysis Platform that enables you to engineer **scalable** distributed applications, networks and other systems, **by applying mathematical models to bytesized performance measurements**. This initiative is the product of 3 years of R&D at [Stacktical (DSLA Protocol)](https://stacktical.com), and our hands-on experience with capacity planning in cloud computing environments. We are proud to make it public, for everyone to enjoy, direct our efforts towards the blockchain industry. 6 | 7 | ## Objective 8 | 9 | The initial objective of this project is to surface mathematical relations between the performance metrics of blockchain networks, with a focus on Polkadot, the driving force of interoperability in the blockchain industry (ergo the foundation of increasingly complex, end-to-end, cross-chain testing scenarios). 10 | 11 | When the value of two system metrics seem to vary in relation to each other, it becomes possible to use them as mathematical coordinates, and fit these coordinates into predictive mathematical models. 12 | 13 | In other words, using mathematical models that can predict the next values in a series, **gives us the ability to predict the next values in a series of blockchain performance metrics**, provided they are bound by maths. 14 | 15 | This scientific approach to capacity planning alleviates testing requirements (less tests, less time), a significant part of the guesswork involved in scalability-driven architectural, coding and configuration choices, and the overall quality of builds before they're deployed to production. 16 | 17 | Our initial research at [Stacktical](https://stacktical.com/) show that such relation seem to exist between the number of validating nodes in a blockchain network, and the throughput of the system, expressed in transactions per seconds (TPS). 18 | 19 | Instead of provisionning complex, costly testnets with hundreds of validating nodes, and running hundreds of throughput measurements, `"Will it scale ?"` enables you to chart, mathematically quantify and govern the scalability of your system **with only 10 performance measuremenents or so**. 20 | 21 | This also means that the `"Will it scale ?"` platform can serve as a tool to scientifically debunk false bockchain TPS claims. Is this is your goal, we'd be happy to hear from you at [contact@stacktical.com](mailto:contact@stacktical.com). 22 | 23 | ![grant_w3f](resources/grant_w3f.png) 24 | 25 | ## General Requirements 26 | 27 | `"Will it scale ?"` is meant to work on Linux and Mac OS machines with: 28 | 29 | * a [Node.js 10.0+](https://nodejs.org/) runtime environment to execute applications and manage dependencies 30 | * a [Docker 2.0+](https://docs.docker.com/) installation to build and manage application containers 31 | 32 | ## Platform Architecture 33 | 34 | The projects is comprised of three main components. 35 | 36 | ### `willitscale-r-server` 37 | 38 | A HTTP R server to make online predictive analysis using mathematical models. 39 | 40 | ### `willitscale-api` 41 | 42 | A Node.js GraphQL API server to query the `willitscale-r-server` and implements the business logic of predictions. 43 | 44 | ### `willitscale-client` 45 | 46 | A Vue.js GraphQL API client, to submit performance test results to the `willitiscale-api` from your browser. 47 | 48 | You will need to build and run these components to run your end-to-end predictions scenarios. 49 | 50 | # Local Deployment 51 | 52 | ## Automated Deployment (TL;DR) 53 | 54 | Install the following tools : 55 | 56 | - [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) 57 | - [kind](https://kind.sigs.k8s.io/docs/user/quick-start/) 58 | - [skaffold](https://skaffold.dev/docs/install/) 59 | 60 | Built the images and run against a local **kind** cluster using the following command : 61 | 62 | ```bash 63 | kind create cluster --name willitscale-polkadot 64 | skaffold run --port-forward 65 | ``` 66 | 67 | ## Manual Deployment 68 | 69 | ### Reachability 70 | 71 | Ensure that docker containers can communicate with each other by setting up the docker bridge network: 72 | 73 | `docker network create --driver bridge willitscale-polkadot` 74 | 75 | ### willitscale-r-server 76 | 77 | #### Building your R Server 78 | 79 | The `willitscale-r-server` is designed to run inside a Docker container. 80 | To build your container, use the following command in the root folder of your component. 81 | 82 | `docker build -t willitscale-r-server .` 83 | 84 | #### Running your R Server 85 | 86 | By default, the R server runs on the 6311 port, inside your Docker container. 87 | To run your container by mapping the 6311 port in Docker to the 10001 port in your machine, use the following command: 88 | 89 | `docker run -d -p 10001:6311 -t -P --network=willitscale-polkadot --name willitscale-r-server willitscale-r-server` 90 | 91 | Your server should be now running on the **10001 port.** 92 | 93 | ### willitscale-api 94 | 95 | #### Install dependencies 96 | 97 | We are using npm to manage dependencies for the `willitscale-api` API server. 98 | 99 | **If you plan on using the server locally,** simply use `npm install` from the `willitscale-api` folder. 100 | **If you plan to host it instead**, the Dockerfile will take care of installing dependencies for you, while building the container. 101 | 102 | #### Building your GraphQL API Server 103 | 104 | You can either build the server locally using npm, or build your server as a Docker container for further deployment (e.g. in Kubernetes). 105 | 106 | ##### Locally 107 | 108 | Set the environment variable and build the server using the following command: 109 | `NODE_ENV="development" npm run build` 110 | 111 | ##### In Docker 112 | 113 | Build the server using the following Docker command: 114 | `docker build -t willitscale-api . --build-arg NODE_ENV=development` 115 | 116 | #### Running your GraphQL API Server 117 | 118 | ##### Locally 119 | 120 | Set the environment variable and run the server using the following command: 121 | `NODE_ENV="development" npm run start` 122 | 123 | ##### In Docker 124 | 125 | Run the server using the following Docker command: 126 | `docker run -p 10000:10000 -v $(pwd)/dist/:/var/www/willitscale-api/public/dist/ --network=willitscale-polkadot -e SERVICE_R_HOST=willitscale-r-server -e SERVICE_R_PORT=6311 --name willitscale-api willitscale-api` 127 | 128 | 129 | Your server should be now running on the **10000 port.** 130 | 131 | ### willitscale-client (optional) 132 | 133 | `willitscale-client` provides a simple way to visualize and plot the predictions returned by the `willitscale-api`, in your browser. 134 | 135 | #### Install dependencies 136 | 137 | We are using npm to manage dependencies for the `willitscale-client` API client. 138 | Simply use `npm install` from the `willitscale-client` folder to install them. 139 | 140 | #### Serve the client locally 141 | 142 | Make sure `willitscale-r-server` and `willitscale-api` are running, then set the environment variable and run `willitscale-client` using the following command: 143 | 144 | `NODE_ENV="development" npm run serve` 145 | 146 | Your client should be now running at **[http://localhost:8080](http://localhost:8080)**. 147 | 148 | ![willitiscale-client.png](resources/willitscale-client.png) 149 | 150 | ## Verify installation 151 | 152 | Now that both our HTTP R server and GraphQL API are running, it is time to try running a prediction. 153 | 154 | GraphQL Playground is a graphical, interactive, in-browser GraphQL IDE, created by Prisma and based on GraphiQL (ndlr: the default playground for GraphQL). You can find more information about GraphQL Playgroun in the [Apollo documentation](https://www.apollographql.com/docs/apollo-server/testing/graphql-playground/). 155 | 156 | To get started, go to `http://localhost:10000`, or the address matching your deployment. 157 | 158 | Then use the following example to give the playground a try: 159 | 160 | ``` 161 | mutation { 162 | predictCapacity(points: 163 | [ 164 | { 165 | p: 1, 166 | Rt: 17.1, 167 | Xp: 37.9 168 | }, 169 | { 170 | p: 5, 171 | Rt: 7.2, 172 | Xp: 82.1 173 | }, 174 | { 175 | p: 10, 176 | Rt: 8.8, 177 | Xp: 76.3 178 | }, 179 | { 180 | p: 15, 181 | Rt: 10.3, 182 | Xp: 65.9 183 | }, 184 | { 185 | p: 20, 186 | Rt: 11.3, 187 | Xp: 53 188 | } 189 | ] 190 | ) 191 | } 192 | ``` 193 | 194 | Where: 195 | 196 | * p represents **concurrency** (e.g. validating nodes in the network) 197 | * Rt represents **latency** (e.g. transaction latency in seconds) 198 | * Xp represents **throughput** (e.g. transactions per second) 199 | 200 | The result pane should now be displaying a stringified JSON object comprised of `nodes vs throughput` points coordinates and other information, predicted from the specified payload. 201 | 202 | Here is what the console looks like when you run a prediction: 203 | 204 | ![willitiscale-api.png](resources/willitscale-api.png) 205 | 206 | # Remote Deployment 207 | 208 | ## Requirements 209 | 210 | A functional Kubernetes cluster (GKE, EKS, minikube, etc) accessible through kubectl, to orchestrate the platform containers. 211 | 212 | ## Create a new deployment 213 | 214 | To create a new deployment run the following command from the root folder of this repository: 215 | 216 | `kubectl create -f k8s/kubernetes.deployment.yaml` 217 | 218 | This will automatically pull the `willitscale-r-server` and `willitscale-api` from the Docker registry. 219 | 220 | ## Access the remote cluster locally 221 | 222 | If you still want the GraphQL Playground to be reachable locally at `http://localhost:10000`, use: 223 | 224 | ` kubectl port-forward svc/willitscale-api 10000:10000` 225 | 226 | The Playground is now reachable at [http://localhost:10000/](http://localhost:10000/), and forwarding your request to the remote `willitscale-api`. 227 | 228 | # API documentation 229 | 230 | Your server documentation is available in the GraphQL Playground. Two predictive queries are available at this stage: 231 | 232 | - A **`predictCapacity`** GraphQL mutation returning: 233 | 234 | - The network’s `nodes vs throughput` (scalability) chart points 235 | - The network’s `peak capacity` point 236 | - Quantified scalability bottlenecks (contention / coherency) 237 | 238 | - A **`predictLatency`** GraphQL mutation returning: 239 | - The network’s `nodes vs latency` chart points 240 | 241 | We originally planned on returning the network’s `latency at peak capacity` point, from the `predictLatency` mutation. 242 | 243 | Instead, we built `willitscale-client` so that this point is directly visible on a chart, and we started implementing a metric-agnostic `makePrediction` mutation in `willitscale-api` that will ultimately serve the same purpose in a wider variety of scenarios (e.g. predicting the network's throughput (Xp) for a given concurrency (p)). 244 | 245 | # About Predictions 246 | 247 | ## Measurements used in predictions 248 | 249 | To answer the `"Will it scale ?"` question, the Platform needs to be fed with performance measurements formatted as a JSON. 250 | 251 | All available predictions are currently using the demo dataset in Vienna's Technical University student, M. Schäffer's whitepaper, about the "Performance and Scalability of Private Ethereum Blockchains". Markus and his team ran thousands of measurements to surface the the insights below. 252 | 253 | ![research_m-schaffer_tuw.jpg](resources/research_m-schaffer_tuw.jpg) 254 | 255 | As this platform evolves with the feedback of the community, more performance measurements from different applications, networks and systems will be added to the list of available demonstration datasets (e.g. Substrate / Polkadot performance measurements). 256 | 257 | ## Dealing with prediction failures 258 | 259 | Nobel Prize recipient and quantum physicist Niels Bohr used to say that **"Prediction is very difficult, especially when it's about the future."** 260 | 261 | In the realm of Data Science, it's important to embrace that predictions can fail. In our experience, there are two main reasons for that : 262 | 263 | **1. Bad performance measurements** 264 | 265 | Predictions can fail if they detect uncommon patterns in the performance measurements they process. It is important to always chart your measurements once, and remove noisy coordinates from your mesurements, before submitting them to the predictive engine. 266 | 267 | Sometimes, what appears to be a clean set of measurements is in fact a perfectly wrong series of measurements. 268 | Rethinking your entire performance testing protocol might help in such case. 269 | 270 | **2. Wrong mathematical models** 271 | 272 | Predictions can fail if we try to fit performance measurements to the wrong mathematical models, or if we use the wrong mathematical functions to this model in the code (e.g. `nls` versus `nlxb` nonlinear regression functions in R). 273 | 274 | In the future, it would make sense to increase the number of mathematical models available in this repository. 275 | 276 | ## Contention & Coherency 277 | 278 | All systems experience contention and coherency penalties, undermining their ability to scale. The mathematical models we are using lets you quantify these penalties, to surface general areas of improvement of the system's capacity, latency and overall scalability. 279 | 280 | Contention is a state of conflict over access to a shared resource (e.g conflicting DApp transactions accessing the same data region). It forces transactions to be dealt with in a serialized way. 281 | 282 | Coherency is a state where the data in a cache is up to date with the system's memory. Ensuring it requires extra, costly synchronisation efforts from your system. 283 | 284 | We would suggest adding scalability bottlenecks checks to a CI/CD pipeline, to validate the scalability of builds before they're deployed to production. 285 | 286 | ## TODO 287 | 288 | Below are some of the things we thought of adding to the scope of the platform, as we were developing this first version. Provided they match what the community would like to do moving forward, they will be properly turn into issues in due time. 289 | 290 | ### Benchmarking 291 | 292 | - Properly thank Markus and his team for his great work with the thesis 293 | - Add more sample Substrate / Polkadot and other blockchain benchmark datasets 294 | 295 | ### Predictive analysis 296 | 297 | - Describe the mathematical models currently used in the platform 298 | - Finish implementing the `makePrediction` mutation 299 | 300 | ### Visualization 301 | 302 | - Enable user benchmark payload input on `willitscale-client` 303 | - Compare the scalability of different blockchain on `willitscale-client` 304 | 305 | ### Typings 306 | 307 | - Use `npm run codegen` to generate TypeScript code from the `willitscale-api` GraphQL schema 308 | - Implement type checks in the `willitscale-client` and `willitscale-api` 309 | 310 | ### Containerization 311 | 312 | - Add Dockerfile to `willitscale-client` 313 | 314 | ### Deployment 315 | 316 | - Add deployment information to the README 317 | 318 | 319 | ## About Stacktical (DSLA Protocol) 320 | 321 |

322 | 323 |

324 | 325 | Stacktical is a french fintech company specialized in IT Service Management (ITSM) and IT Service Governance. 326 | 327 | Their flagship product, [DSLA Protocol](https://stacktical.com), is an autonomous blockchain protocol to document, bargain and enforce service commitments between third party service providers and their customers, using peer-to-peer, electronic Service Level Agreement (SLA) contracts. 328 | 329 | As outsourcing application and network services increasingly expose individuals and corporations to service disruptions, DSLA Protocol enables outsourced third party service providers to offer verifiable, more transparent service level guarantees to their customers, to continuously adapt to changing service level needs, and to gracefully mitigate the economic impact of bad service levels using the DSLA cryptocurrency token. 330 | 331 | For more information about Stacktical and DSLA, please go to [stacktical.com](https://stacktical.com) 332 | 333 | 334 | -------------------------------------------------------------------------------- /api/.dockerignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.md 3 | !README.md 4 | node_modules 5 | public/dist 6 | -------------------------------------------------------------------------------- /api/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 3 | extends: [ 4 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 5 | 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 6 | 'plugin:prettier/recommended' // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 7 | ], 8 | parserOptions: { 9 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 10 | sourceType: 'module' // Allows for the use of imports 11 | }, 12 | rules: { 13 | '@typescript-eslint/no-var-requires': 'off', 14 | '@typescript-eslint/no-explicit-any': 'off', 15 | '@typescript-eslint/explicit-function-return-type': 'off' 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /api/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "parser": "typescript" 6 | } 7 | -------------------------------------------------------------------------------- /api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10.16.1-slim 2 | 3 | ENV WORKDIR=/var/www/willitscale-api 4 | 5 | WORKDIR $WORKDIR 6 | 7 | COPY package*.json ./ 8 | 9 | ENV HOST="0.0.0.0" \ 10 | PORT=10000 11 | 12 | RUN npm install pm2 -g \ 13 | && npm install --silent 14 | 15 | # Combining ARG end ENV to set the default NODE_ENV 16 | ARG NODE_ENV="development" 17 | 18 | ENV NODE_ENV=$NODE_ENV 19 | 20 | COPY . . 21 | 22 | RUN chown -R node:node $WORKDIR 23 | 24 | USER node 25 | 26 | RUN npm run build 27 | 28 | EXPOSE $PORT 29 | 30 | CMD pm2 start /var/www/willitscale-api/pm2.json --no-daemon --env $NODE_ENV -- \ 31 | --HOST $HOST \ 32 | --PORT $PORT 33 | -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "willitscale-api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "dependencies": { 7 | "@types/graphql": "^14.2.0", 8 | "apollo-server": "^2.4.8", 9 | "colors": "^1.4.0", 10 | "graphql": "^14.3.0", 11 | "rio": "^2.4.1" 12 | }, 13 | "devDependencies": { 14 | "@types/webpack-env": "^1.13.9", 15 | "@typescript-eslint/eslint-plugin": "^1.6.0", 16 | "@typescript-eslint/parser": "^1.6.0", 17 | "clean-webpack-plugin": "^3.0.0", 18 | "copy-webpack-plugin": "^5.0.4", 19 | "eslint": "^5.16.0", 20 | "eslint-config-prettier": "^4.1.0", 21 | "eslint-plugin-prettier": "^3.0.1", 22 | "husky": "^1.3.1", 23 | "lint-staged": "^8.1.5", 24 | "prettier": "^1.17.0", 25 | "ts-loader": "^5.3.3", 26 | "typescript": "^3.4.3", 27 | "webpack": "^4.30.0", 28 | "webpack-cli": "^3.3.0", 29 | "webpack-merge": "^4.2.1", 30 | "webpack-node-externals": "^1.7.2" 31 | }, 32 | "scripts": { 33 | "build": "webpack --config webpack.$NODE_ENV.js", 34 | "watch": "webpack --config webpack.$NODE_ENV.js --watch", 35 | "start": "node dist/main" 36 | }, 37 | "husky": { 38 | "hooks": { 39 | "pre-commit": "lint-staged" 40 | } 41 | }, 42 | "lint-staged": { 43 | "*.ts": [ 44 | "prettier --write", 45 | "git add" 46 | ] 47 | }, 48 | "keywords": [], 49 | "author": { 50 | "name": "Stacktical (DSLA Protocol)", 51 | "email": "contact@stacktical.com", 52 | "url": "https://stacktical.com" 53 | }, 54 | "license": "Apache-2.0", 55 | "licenses": [ 56 | { 57 | "type": "Apache-2.0", 58 | "url": "http://www.apache.org/licenses/LICENSE-2.0" 59 | } 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /api/pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps" : [ 3 | { 4 | "name" : "willitscale-api", 5 | "script" : "dist/main.js", 6 | "instances" : "1", 7 | "watch": false, 8 | "env": { 9 | "NODE_ENV": "default", 10 | "PORT": 10000 11 | }, 12 | "env_development": { 13 | "NODE_ENV": "development", 14 | "PORT": 10000 15 | }, 16 | "env_staging": { 17 | "NODE_ENV": "staging", 18 | "PORT": 10000 19 | }, 20 | "env_production" : { 21 | "NODE_ENV": "production", 22 | "PORT": 10000 23 | } 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /api/src/demo/benchmark.ts: -------------------------------------------------------------------------------- 1 | const points = [ 2 | { 3 | p: 1, 4 | Rt: 17.1, 5 | Xp: 37.9 6 | }, 7 | { 8 | p: 5, 9 | Rt: 7.2, 10 | Xp: 82.1 11 | }, 12 | { 13 | p: 10, 14 | Rt: 8.8, 15 | Xp: 76.3 16 | }, 17 | { 18 | p: 15, 19 | Rt: 10.3, 20 | Xp: 65.9 21 | }, 22 | { 23 | p: 20, 24 | Rt: 11.3, 25 | Xp: 53 26 | } 27 | ]; 28 | 29 | exports.points = points; 30 | -------------------------------------------------------------------------------- /api/src/env.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace NodeJS { 2 | interface ProcessEnv { 3 | NODE_ENV: 'development' | 'staging' | 'production'; 4 | HOST: string; 5 | PORT: number; 6 | } 7 | } 8 | 9 | declare module '*.R' { 10 | const content: any; 11 | export default content; 12 | } 13 | -------------------------------------------------------------------------------- /api/src/main.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from 'apollo-server'; 2 | 3 | import resolvers from './server/resolvers'; 4 | import typeDefs from './server/schemas'; 5 | 6 | const server = new ApolloServer({ 7 | resolvers, 8 | typeDefs, 9 | context: ({ req }: { req: Request }) => { 10 | // Evolution goes here. 11 | } 12 | }); 13 | 14 | const host = process.env.HOST || '0.0.0.0'; 15 | const port = process.env.PORT || 10000; 16 | 17 | server 18 | .listen({ 19 | hostname: host, 20 | port: port 21 | }) 22 | .then(({ url }: any) => 23 | console.log(`Scalability Prediction API ready at ${url}. `) 24 | ); 25 | 26 | if (module.hot) { 27 | module.hot.accept(); 28 | 29 | module.hot.dispose(() => server.stop()); 30 | } 31 | -------------------------------------------------------------------------------- /api/src/prediction/capacity.R: -------------------------------------------------------------------------------- 1 | predictCapacity <- function (jsonObj) { 2 | options(scipen = 999) 3 | library(jsonlite) 4 | library(nlmrt) 5 | library(nls2) 6 | 7 | campaign <- fromJSON(jsonObj) 8 | names(campaign) 9 | print(campaign, row.names = FALSE) 10 | cat("\n\n") 11 | 12 | # Extract p and Xp from R JSON object 13 | p <- campaign$points$p 14 | Xp <- campaign$points$Xp 15 | 16 | # Duplicate dataset 17 | model <- campaign$points 18 | 19 | # Calculate scale factor: get throughput for entry where load = 1 (lambda, in Little's law) 20 | scale.factor <- campaign$points$Xp[1] 21 | 22 | # Rename columns 23 | names(model) <- c("load", "throughput") 24 | 25 | # Normalize data (cf. GCaP chapter 5.4) / Compute deviations from linearity (cf. GCaP chapter 5.5.2) 26 | model$capacity <- model$throughput / scale.factor 27 | model$x <- model$load - 1 28 | model$y <- (model$load / model$capacity) - 1 29 | model.fit <- lm(y ~ I(x^2) + x - 1, data = model) 30 | 31 | # Calculate alpha and beta value from linear deviation 32 | alpha <- coef(model.fit)[[2]] - coef(model.fit)[[1]] 33 | beta <- ifelse(coef(model.fit)[[1]]<0, 0, coef(model.fit)[[1]]) 34 | 35 | # Set lm coefficients to 0 when they're negative 36 | if (alpha<0) { 37 | cat("alpha is negative, setting to 0... \n") 38 | cat(alpha) 39 | alpha <- 0 40 | cat("Done! \n\n") 41 | } 42 | 43 | if (beta<0) { 44 | cat("beta is negative, setting to 0... \n") 45 | cat(beta) 46 | beta <- 0 47 | cat("Done! \n\n") 48 | } 49 | 50 | cat("lm alpha:\n") 51 | cat(alpha) 52 | cat("\n\n") 53 | 54 | cat("lm beta:\n") 55 | cat(beta) 56 | cat("\n\n") 57 | 58 | # Apply non linear regression using computed alpha and beta value from linear deviation 59 | 60 | # Port implementation 61 | fit.nlsPo <- nls(Xp ~ scale.factor * p/(1 + A * (p-1) + B * p * (p-1)), data=campaign$points, start=c(A=alpha, B=beta), algorithm="port") 62 | 63 | sigma.nlsPo <- coef(fit.nlsPo)['A'] 64 | cat("Initial sigma (nlsPo): \n") 65 | cat(sigma.nlsPo) 66 | cat("\n\n") 67 | 68 | kappa.nlsPo <- coef(fit.nlsPo)['B'] 69 | 70 | cat("Initial kappa (nlsPo): \n") 71 | cat(kappa.nlsPo) 72 | cat("\n\n") 73 | 74 | peak.nlsPo <- sqrt((1 - sigma.nlsPo) / kappa.nlsPo) 75 | 76 | cat("Peak (nlsPo): \n") 77 | cat(peak.nlsPo) 78 | cat("\n\n") 79 | 80 | # Plinear implementation 81 | fit.nlsPl <- nls(Xp ~ scale.factor * p/(1 + A * (p-1) + B * p * (p-1)), data=campaign$points, start=c(A=alpha, B=beta), algorithm="plinear") 82 | 83 | sigma.nlsPl <- coef(fit.nlsPl)['A'] 84 | 85 | cat("Initial sigma (nlsPl): \n") 86 | cat(sigma.nlsPl) 87 | cat("\n\n") 88 | 89 | kappa.nlsPl <- coef(fit.nlsPl)['B'] 90 | 91 | cat("Initial kappa (nlsPl): \n") 92 | cat(kappa.nlsPl) 93 | cat("\n\n") 94 | 95 | peak.nlsPl <- sqrt((1 - sigma.nlsPl) / kappa.nlsPl) 96 | 97 | cat("Peak (nlsPl): \n") 98 | cat(peak.nlsPl) 99 | cat("\n\n") 100 | 101 | # nlxb + nls2 implementation 102 | fit.nlxb <- nlxb(Xp ~ scale.factor * p/(1 + A * (p-1) + B * p * (p-1)), 103 | data = campaign$points, 104 | start = c(scale.factor = 0, A = 0, B = 0), 105 | lower = c(scale.factor = 0, A = 0, B = 0), 106 | upper = c(scale.factor = Inf, A = 1, B = 1) 107 | ) 108 | 109 | sigma.nlxb <- fit.nlxb$coefficients['A'] 110 | kappa.nlxb <- fit.nlxb$coefficients['B'] 111 | 112 | # nlxb nls2 pass (necessary for predict()) 113 | fit.nls2 <- nls2(Xp ~ scale.factor * p/(1 + A * (p-1) + B * p * (p-1)), data = campaign$points, start = fit.nlxb$coefficients, algorithm = "brute-force") 114 | 115 | # Default USL is (more robust) nlxb + nls2 implementation 116 | usl <- fit.nls2 117 | implementation <- "nlxb" 118 | 119 | cat("Initial sigma (nlxb): \n") 120 | cat(sigma.nlxb) 121 | cat("\n\n") 122 | 123 | cat("Initial kappa (nlxb): \n") 124 | cat(kappa.nlxb) 125 | cat("\n\n") 126 | 127 | peak.nlxb <- sqrt((1 - sigma.nlxb) / kappa.nlxb) 128 | cat("Peak (nlsPl): \n") 129 | cat(peak.nlxb) 130 | cat("\n\n") 131 | 132 | # Switch to nlsPl implementation when we detect a negative nlxb coefficient 133 | if (kappa.nlxb<0 || sigma.nlxb<0) { 134 | cat("kappa is negative, moving to nlsPl implementation. \n") 135 | cat("Adjusted sigma (nlsPl): \n") 136 | cat(sigma.nlsPl) 137 | cat("\n\n") 138 | 139 | cat("Adjusted kappa (nlsPl): \n") 140 | cat(kappa.nlsPl) 141 | cat("\n\n") 142 | 143 | usl <- fit.nlsPl 144 | implementation <- "nlsPl" 145 | } 146 | 147 | # Switch to nlsPo implementation when we detect a negative nlsPl coefficient 148 | if (kappa.nlsPl<0 || sigma.nlsPl<0) { 149 | cat("kappa is negative, moving to nlsPo implementation. \n") 150 | cat("Adjusted sigma (nlsPo): \n") 151 | cat(sigma.nlsPo) 152 | cat("\n\n") 153 | 154 | cat("Adjusted kappa (nlsPo): \n") 155 | cat(kappa.nlsPo) 156 | cat("\n\n") 157 | 158 | usl <- fit.nlsPo 159 | implementation <- "nlsPo" 160 | } 161 | 162 | cat("Computed implementation: \n") 163 | cat(implementation) 164 | cat("\n\n") 165 | 166 | # Get sigma and kappa parameters from computed USL 167 | coefficients <- coef(usl) 168 | sigma <- coefficients['A'] 169 | kappa <- coefficients['B'] 170 | 171 | # Set coefficient to 0 when they're still negative 172 | if (sigma<0) { 173 | cat("sigma is still negative: \n") 174 | cat(sigma) 175 | cat("\n\n") 176 | } 177 | 178 | if (kappa<0) { 179 | cat("kappa is still negative: \n") 180 | cat(kappa) 181 | cat("\n\n") 182 | } 183 | 184 | cat("Final sigma:\n") 185 | cat(sigma) 186 | cat("\n\n") 187 | 188 | cat("Final kappa:\n") 189 | cat(kappa) 190 | cat("\n\n") 191 | 192 | # Calculate final peak scalability 193 | peak <- sqrt((1 - sigma) / kappa) 194 | 195 | cat("Final peak scalability:\n") 196 | cat(peak) 197 | cat("\n\n") 198 | 199 | # Calculate degree of freedom 200 | samples <- dim(campaign$points)[1] 201 | degfree <- samples - 1 202 | 203 | # Calculate confidence interval (error bands) 204 | y.usl <- predict(usl) 205 | y.mu <- mean(campaign$points$Xp) 206 | y.se <- sqrt(sum((campaign$points$Xp-y.usl)^2) / degfree) 207 | sse <- sum((campaign$points$Xp-y.usl)^2) 208 | sst <- sum((campaign$points$Xp-y.mu)^2) 209 | y.r2 <- 1 - sse / sst 210 | y.ci <- y.se * qt(0.95, degfree) 211 | 212 | cat("Fit accuracy:\n") 213 | cat(y.r2) 214 | cat("\n\n") 215 | 216 | cat("Confidence interval:\n") 217 | cat(y.ci) 218 | cat("\n\n") 219 | 220 | chartSize <- max(p)*3 221 | 222 | # Resize, fit and return scalability prediction 223 | chart <- with(campaign$points, expand.grid(p=seq(min(p), chartSize, length=chartSize))) 224 | chart$fit <- predict(usl, newdata=chart) 225 | chart$ucl <- chart$fit + y.ci 226 | chart$lcl <- chart$fit - y.ci 227 | 228 | result <- list( 229 | campaign = campaign$points, 230 | chart = chart, 231 | usl = chart$fit, 232 | ymax = chart$ucl, 233 | ymin = chart$lcl, 234 | sigma = sigma, 235 | kappa = kappa, 236 | peakP = peak, 237 | peakXp = max(chart$fit), 238 | rsquared = y.r2 239 | ) 240 | 241 | response <- serializeJSON(result) 242 | 243 | return(response) 244 | } -------------------------------------------------------------------------------- /api/src/prediction/latency.R: -------------------------------------------------------------------------------- 1 | predictLatency <- function (jsonObj) { 2 | options(scipen = 999) 3 | library(jsonlite) 4 | 5 | campaign <- fromJSON(jsonObj) 6 | names(campaign) 7 | print(campaign, row.names = FALSE) 8 | cat("\n\n") 9 | 10 | # Extract p and Rt from R JSON object 11 | p <- campaign$points$p 12 | Rt <- campaign$points$Rt 13 | 14 | # Extract first Rt value as lamba coefficient 15 | lambda <- campaign$points$Rt[1] 16 | 17 | # Set customer chart size 18 | chartSize <- max(p)*2 19 | 20 | # Solve USL for Rt as a function of p 21 | little <- nls(Rt ~ (1 + A * (p-1) + B * p * (p-1))/lambda, data=campaign$points, start=c(A=0.1, B=0.01), algorithm="plinear") 22 | 23 | # Resize, fit and return prediction 24 | chart <- with(campaign$points, expand.grid(p=seq(min(p), chartSize, length=chartSize))) 25 | chart$fit <- predict(little, newdata=chart) 26 | 27 | # Curate results into a list response body 28 | result <- list( 29 | campaign = campaign$points, 30 | chart = chart, 31 | little = chart$fit 32 | ) 33 | 34 | print(result) 35 | cat("\n\n") 36 | 37 | response <- serializeJSON(result) 38 | 39 | return(response) 40 | } 41 | -------------------------------------------------------------------------------- /api/src/prediction/predict.R: -------------------------------------------------------------------------------- 1 | makePrediction <- function (jsonObj) { 2 | options(scipen = 999) 3 | library(jsonlite) 4 | 5 | campaign <- fromJSON(jsonObj) 6 | names(campaign) 7 | print(campaign, row.names = FALSE) 8 | cat("\n\n") 9 | 10 | # Extract p from R JSON object 11 | p <- campaign$point 12 | little <- campaign$fit 13 | 14 | prediction <- predict(fit, newdata=data.frame(p=p)) 15 | 16 | result <- list( 17 | p = campaign$p, 18 | data = data, 19 | prediction = prediction 20 | ) 21 | 22 | response <- serializeJSON(result) 23 | 24 | return(response) 25 | } 26 | -------------------------------------------------------------------------------- /api/src/server/resolvers.ts: -------------------------------------------------------------------------------- 1 | const colors = require('colors'); 2 | const rio = require('rio'); 3 | const RHost = process.env.SERVICE_R_HOST || '0.0.0.0'; 4 | const RPort = process.env.SERVICE_R_PORT || 10001; 5 | const demoBenchmark = require('../demo/benchmark'); 6 | 7 | colors.setTheme({ 8 | prediction: ['white', 'bold', 'bgGreen'], 9 | bottleneck: ['white', 'bold', 'bgRed'], 10 | info: ['white', 'bold', 'bgBlue'] 11 | }); 12 | 13 | const demo = (): string => { 14 | return JSON.stringify(demoBenchmark); 15 | }; 16 | 17 | const makePrediction = async (args: any) => { 18 | return new Promise((resolve, reject) => { 19 | try { 20 | if (args == null) args = demoBenchmark; 21 | 22 | console.log( 23 | '\n__________________________________________________________\n\n', 24 | colors.white.bold('Predicting measurement from p...'), 25 | '\n\n', 26 | colors.green(JSON.stringify(args.points, null, 2)), 27 | '\n' 28 | ); 29 | 30 | const config = { 31 | filename: 'dist/predict.R', 32 | entrypoint: 'makePrediction', 33 | data: args, 34 | host: RHost, 35 | port: RPort, 36 | callback: function(err: any, res: any) { 37 | if (!err) { 38 | console.log( 39 | colors.white.bold('\n... Done! Your prediction is ready:\n\n') 40 | ); 41 | 42 | let prediction = JSON.parse(res); 43 | 44 | console.log( 45 | colors.green(JSON.stringify(prediction, null, 2)), 46 | '\n' 47 | ); 48 | 49 | resolve(JSON.stringify(prediction)); 50 | } else { 51 | reject(err); 52 | console.log('Unable to predict measurement.', err); 53 | } 54 | } 55 | }; 56 | 57 | rio.e(config); 58 | } catch (err) { 59 | reject(err); 60 | console.log('Unable to predict measurement.', err); 61 | } 62 | }); 63 | }; 64 | 65 | const predictCapacity = async (args: any): Promise => { 66 | return new Promise((resolve, reject) => { 67 | try { 68 | if (args == null) args = demoBenchmark; 69 | 70 | console.log( 71 | '\n__________________________________________________________\n\n', 72 | colors.white.bold('Predicting the capacity of the following system...'), 73 | '\n\n', 74 | colors.green(JSON.stringify(args.points, null, 2)), 75 | '\n' 76 | ); 77 | 78 | const config = { 79 | filename: 'dist/capacity.R', 80 | entrypoint: 'predictCapacity', 81 | data: args, 82 | host: RHost, 83 | port: RPort, 84 | callback: function(err: any, res: any) { 85 | if (!err) { 86 | console.log( 87 | colors.white.bold('\n... Done! Your prediction is ready:\n\n') 88 | ); 89 | 90 | let prediction = JSON.parse(res); 91 | 92 | let i = 0; 93 | let concurrency = prediction.value[1].value[0]; 94 | let throughput = prediction.value[2]; 95 | let high = prediction.value[3]; 96 | let low = prediction.value[4]; 97 | 98 | let formattedPrediction: object[] = []; 99 | 100 | for (i = 0; i < throughput.value.length; i++) { 101 | let responsePoint = { 102 | p: concurrency.value[i], 103 | ucl: high.value[i], 104 | Xp: throughput.value[i], 105 | lcl: low.value[i] 106 | }; 107 | 108 | formattedPrediction.push(responsePoint); 109 | } 110 | 111 | let systemContention = ( 112 | prediction.value[5].value[0] * 100 113 | ).toPrecision(); 114 | 115 | console.log( 116 | 'Contention Penalty (%) :', 117 | '\n\n', 118 | colors.bottleneck(systemContention), 119 | '\n' 120 | ); 121 | 122 | let systemCoherency = ( 123 | prediction.value[6].value[0] * 100 124 | ).toPrecision(); 125 | 126 | console.log( 127 | 'Coherency Penalty (%) :', 128 | '\n\n', 129 | colors.bottleneck(systemCoherency), 130 | '\n' 131 | ); 132 | 133 | let peakCapacityP = prediction.value[7].value[0]; 134 | 135 | console.log( 136 | 'Peak Capacity (p) :', 137 | '\n\n', 138 | colors.prediction(peakCapacityP), 139 | '\n' 140 | ); 141 | 142 | let peakCapacityXp = prediction.value[8].value[0]; 143 | 144 | console.log( 145 | 'Peak Capacity (Xp) :', 146 | '\n\n', 147 | colors.prediction(peakCapacityXp), 148 | '\n' 149 | ); 150 | 151 | let predictionAccuracy = ( 152 | prediction.value[9].value[0] * 100 153 | ).toPrecision(); 154 | 155 | console.log( 156 | 'Accuracy (%) :', 157 | '\n', 158 | colors.info(predictionAccuracy), 159 | '\n' 160 | ); 161 | 162 | console.log( 163 | colors.white.bold( 164 | '\nPlease check client or HTTP response for charts and more.' 165 | ), 166 | '\n__________________________________________________________\n\n' 167 | ); 168 | 169 | let formattedResponse = { 170 | benchmark: args.points, 171 | scalabilityChart: formattedPrediction, 172 | peakCapacityP: peakCapacityP, 173 | peakCapacityXp: peakCapacityXp, 174 | contentionBottleneck: systemContention, 175 | coherencyBottleneck: systemCoherency 176 | }; 177 | 178 | resolve(JSON.stringify(formattedResponse)); 179 | } else { 180 | reject(err); 181 | console.log('Unable to predict network capacity.', err); 182 | } 183 | } 184 | }; 185 | 186 | rio.e(config); 187 | } catch (err) { 188 | reject(err); 189 | console.log('Unable to predict network capacity.', err); 190 | } 191 | }); 192 | }; 193 | 194 | const predictLatency = async (args: any): Promise => { 195 | return new Promise((resolve, reject) => { 196 | try { 197 | if (args == null) args = demoBenchmark; 198 | 199 | console.log( 200 | '\n__________________________________________________________\n\n', 201 | colors.white.bold('Predicting the latency of the following system...'), 202 | '\n\n', 203 | colors.green(JSON.stringify(args.points, null, 2)), 204 | '\n' 205 | ); 206 | 207 | const config = { 208 | filename: 'dist/latency.R', 209 | entrypoint: 'predictLatency', 210 | data: args, 211 | host: RHost, 212 | port: RPort, 213 | callback: function(err: any, res: any) { 214 | if (!err) { 215 | console.log( 216 | colors.white.bold('\n... Done! Your prediction is ready:\n\n') 217 | ); 218 | 219 | let prediction = JSON.parse(res); 220 | 221 | let i = 0; 222 | let concurrency = prediction.value[1].value[0]; 223 | let responseTime = prediction.value[2]; 224 | 225 | let formattedPrediction: object[] = []; 226 | 227 | for (i = 0; i < responseTime.value.length; i++) { 228 | let responsePoint = { 229 | p: concurrency.value[i], 230 | Rt: responseTime.value[i] 231 | }; 232 | 233 | formattedPrediction.push(responsePoint); 234 | } 235 | 236 | console.log( 237 | colors.green(JSON.stringify(formattedPrediction, null, 2)), 238 | '\n' 239 | ); 240 | 241 | console.log( 242 | colors.white.bold( 243 | '\nPlease check client or HTTP response for charts and more.' 244 | ), 245 | '\n__________________________________________________________\n\n' 246 | ); 247 | 248 | resolve(JSON.stringify(formattedPrediction)); 249 | } else { 250 | reject(err); 251 | console.log('Unable to predict network latency.', err); 252 | } 253 | } 254 | }; 255 | 256 | rio.e(config); 257 | } catch (err) { 258 | reject(err); 259 | console.log('Unable to predict network latency.', err); 260 | } 261 | }); 262 | }; 263 | 264 | export default { 265 | Query: { 266 | demo 267 | }, 268 | Mutation: { 269 | predictLatency, 270 | predictCapacity, 271 | makePrediction 272 | } 273 | }; 274 | -------------------------------------------------------------------------------- /api/src/server/schemas.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | export default gql` 4 | type Query { 5 | """ 6 | Use this fuction to populate demo Concurrency (p) vs Throughput (Xp) vs Response Time (Rt) benchmark results. 7 | """ 8 | demo: String 9 | } 10 | 11 | type Mutation { 12 | """ 13 | Use this function to forecast the capacity of an application or a network, by fitting the Universal Scalability Law model to "Concurrency (p) vs Throughput (Xp)" benchmark results. 14 | """ 15 | predictCapacity(points: [Point]): String 16 | """ 17 | Use this function to forecast the latency of an application or a network, by solving the Universal Scalability Law for Response Time (Rt) as a function of Concurrency (p). 18 | """ 19 | predictLatency(points: [Point]): String 20 | """ 21 | Use this function to predict a measurement from a Concurrency (p) according the specified fit. 22 | """ 23 | makePrediction(p: Float, fit: String): String 24 | } 25 | 26 | input Point { 27 | p: Float! 28 | Xp: Float 29 | Rt: Float 30 | } 31 | `; 32 | -------------------------------------------------------------------------------- /api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | "lib": ["dom","es6"], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "dist", /* Redirect output structure to the directory. */ 15 | "rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "incremental": true, /* Enable incremental compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | 52 | /* Source Map Options */ 53 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 54 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 55 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 56 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 57 | 58 | /* Experimental Options */ 59 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 60 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 61 | }, 62 | "exclude": [ "node_modules" ], 63 | "include": [ "src/**/*.ts", "src/type/*.ts" ] 64 | } 65 | -------------------------------------------------------------------------------- /api/webpack.common.js: -------------------------------------------------------------------------------- 1 | /* eslint @typescript-eslint/no-var-requires: 0 */ 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | module: { 6 | rules: [ 7 | { 8 | exclude: [path.resolve(__dirname, 'node_modules')], 9 | test: /\.ts$/, 10 | use: 'ts-loader' 11 | } 12 | ] 13 | }, 14 | output: { 15 | filename: 'main.js', 16 | path: path.resolve(__dirname, 'dist') 17 | }, 18 | resolve: { 19 | extensions: ['.ts', '.js'] 20 | }, 21 | target: 'node' 22 | }; 23 | -------------------------------------------------------------------------------- /api/webpack.development.js: -------------------------------------------------------------------------------- 1 | /* eslint @typescript-eslint/no-var-requires: 0 */ 2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 3 | const CopyPlugin = require('copy-webpack-plugin'); 4 | 5 | const merge = require('webpack-merge'); 6 | const nodeExternals = require('webpack-node-externals'); 7 | const path = require('path'); 8 | const webpack = require('webpack'); 9 | 10 | const common = require('./webpack.common.js'); 11 | 12 | module.exports = merge.smart(common, { 13 | devtool: 'inline-source-map', 14 | entry: ['webpack/hot/poll?1000', path.join(__dirname, 'src/main.ts')], 15 | externals: [ 16 | nodeExternals({ 17 | whitelist: ['webpack/hot/poll?1000'] 18 | }) 19 | ], 20 | mode: 'development', 21 | plugins: [ 22 | new CleanWebpackPlugin(), 23 | new webpack.HotModuleReplacementPlugin(), 24 | new CopyPlugin([{ from: 'src/prediction', to: '.' }]) 25 | ] 26 | }); 27 | -------------------------------------------------------------------------------- /api/webpack.production.js: -------------------------------------------------------------------------------- 1 | /* eslint @typescript-eslint/no-var-requires: 0 */ 2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 3 | const CopyPlugin = require('copy-webpack-plugin'); 4 | 5 | const merge = require('webpack-merge'); 6 | const nodeExternals = require('webpack-node-externals'); 7 | const path = require('path'); 8 | 9 | const common = require('./webpack.common.js'); 10 | 11 | module.exports = merge(common, { 12 | devtool: 'source-map', 13 | entry: [path.join(__dirname, 'src/main.ts')], 14 | externals: [nodeExternals({})], 15 | mode: 'production', 16 | plugins: [ 17 | new CleanWebpackPlugin(), 18 | new CopyPlugin([{ from: 'src/prediction', to: '.' }]) 19 | ] 20 | }); 21 | -------------------------------------------------------------------------------- /client/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /client/.dockerignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.md 3 | !.eslintrc.js 4 | !.env* 5 | !.htpasswd 6 | !README.md 7 | node_modules 8 | public/dist 9 | -------------------------------------------------------------------------------- /client/.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | VUE_APP_API="http://localhost:10000/" -------------------------------------------------------------------------------- /client/.env.production: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_API="" -------------------------------------------------------------------------------- /client/.env.staging: -------------------------------------------------------------------------------- 1 | NODE_ENV=staging 2 | VUE_APP_API="" -------------------------------------------------------------------------------- /client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: ['plugin:vue/essential', '@vue/prettier', '@vue/typescript'], 7 | rules: { 8 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 9 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 10 | 'prettier/prettier': ['error', { singleQuote: true }] 11 | }, 12 | parserOptions: { 13 | parser: '@typescript-eslint/parser' 14 | }, 15 | plugins: ['html'], 16 | settings: { 17 | 'html/html-extensions': ['.vue'] 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /client/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "parser": "typescript" 6 | } 7 | -------------------------------------------------------------------------------- /client/Dockerfile: -------------------------------------------------------------------------------- 1 | # Build Stage 2 | FROM node:10.16.1 as build-stage 3 | 4 | ENV WORKDIR=/var/www/willitscale-client \ 5 | PORT=9999 6 | 7 | WORKDIR $WORKDIR 8 | 9 | COPY package*.json ./ 10 | 11 | # npm install must run before NODE_ENV deplaration 12 | RUN npm install --silent 13 | 14 | ARG NODE_ENV="development" 15 | ENV NODE_ENV=$NODE_ENV 16 | 17 | COPY . . 18 | 19 | RUN npm run build -- --mode $NODE_ENV 20 | 21 | # Production Stage 22 | FROM nginx:1.19.0 as production-stage 23 | 24 | ENV PORT=9999 25 | 26 | COPY --from=build-stage /var/www/willitscale-client/dist /var/www/willitscale-client/public/dist 27 | #COPY ops/nginx.conf_base /etc/nginx/nginx.conf 28 | ADD conf/nginx.conf /etc/nginx/conf.d/ 29 | 30 | RUN chown nginx:nginx /var/www/willitscale-client/public/dist 31 | 32 | EXPOSE $PORT 33 | 34 | CMD ["nginx", "-g", "daemon off;"] 35 | -------------------------------------------------------------------------------- /client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/app'], 3 | plugins: ['graphql-tag'] 4 | }; 5 | -------------------------------------------------------------------------------- /client/conf/nginx.conf: -------------------------------------------------------------------------------- 1 | ## Hide server verion 2 | server_tokens off; 3 | ## Compression 4 | gzip on; 5 | gzip_comp_level 5; 6 | gzip_vary on; 7 | gzip_min_length 1000; 8 | gzip_proxied any; 9 | gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; 10 | gzip_buffers 16 8k; 11 | ## Buffer overflow prevention 12 | client_max_body_size 100k; 13 | large_client_header_buffers 2 1k; 14 | 15 | server { 16 | listen 10001; 17 | charset utf8; 18 | location / { 19 | root /var/www/willitscale-polkadot/public/dist; 20 | index index.html; 21 | try_files $uri $uri/ /index.html; 22 | } 23 | location = /50x.html { 24 | root /usr/share/nginx/html; 25 | } 26 | error_page 500 502 503 504 /50x.html; 27 | } 28 | 29 | -------------------------------------------------------------------------------- /client/conf/nginx.conf_base: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes 1; 3 | 4 | error_log /var/log/nginx/error.log warn; 5 | pid /var/run/nginx.pid; 6 | 7 | 8 | events { 9 | worker_connections 1024; 10 | } 11 | 12 | 13 | http { 14 | include /etc/nginx/mime.types; 15 | default_type application/octet-stream; 16 | 17 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 18 | '$status $body_bytes_sent "$http_referer" ' 19 | '"$http_user_agent" "$http_x_forwarded_for"'; 20 | 21 | access_log /var/log/nginx/access.log main; 22 | 23 | sendfile off; 24 | #tcp_nopush on; 25 | 26 | keepalive_timeout 65; 27 | 28 | #gzip on; 29 | 30 | include /etc/nginx/conf.d/*.conf; 31 | } 32 | -------------------------------------------------------------------------------- /client/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "willitscale-client", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "@types/graphql": "^14.2.0", 6 | "apollo-cache-inmemory": "^1.6.1", 7 | "apollo-client": "^2.6.1", 8 | "apollo-link": "^1.2.11", 9 | "apollo-link-http": "^1.5.14", 10 | "bulma": "^0.7.4", 11 | "bulmaswatch": "^0.7.2", 12 | "chart.js": "^2.8.0", 13 | "graphql": "^14.3.1", 14 | "graphql-tag": "^2.10.1", 15 | "install": "^0.12.2", 16 | "moment": "^2.24.0", 17 | "numeral": "^2.0.6", 18 | "vue": "^2.6.6", 19 | "vue-chartjs": "^3.4.2", 20 | "vue-class-component": "^6.3.2", 21 | "vue-property-decorator": "^7.3.0", 22 | "vue-router": "^3.0.2", 23 | "vue-typer": "^1.2.0", 24 | "vuex": "^3.1.0", 25 | "vuex-class": "^0.3.1" 26 | }, 27 | "devDependencies": { 28 | "@babel/core": "^7.1.2", 29 | "@types/chai": "^4.1.7", 30 | "@types/mocha": "^5.2.6", 31 | "@vue/cli-plugin-babel": "^3.4.0", 32 | "@vue/cli-plugin-e2e-cypress": "^3.7.0", 33 | "@vue/cli-plugin-eslint": "^3.4.0", 34 | "@vue/cli-plugin-typescript": "^3.4.0", 35 | "@vue/cli-plugin-unit-mocha": "^3.4.0", 36 | "@vue/cli-service": "^3.4.0", 37 | "@vue/eslint-config-prettier": "^4.0.1", 38 | "@vue/eslint-config-typescript": "^4.0.0", 39 | "@vue/test-utils": "^1.0.0-beta.29", 40 | "babel-eslint": "^10.0.1", 41 | "babel-plugin-graphql-tag": "^2.4.0", 42 | "chai": "^4.2.0", 43 | "eslint": "^5.15.1", 44 | "eslint-plugin-html": "^5.0.3", 45 | "eslint-plugin-prettier": "^3.1.0", 46 | "eslint-plugin-vue": "^5.2.2", 47 | "lint-staged": "^8.1.4", 48 | "node-sass": "^4.13.1", 49 | "postcss-sorting": "^5.0.0", 50 | "prettier": "^1.18.2", 51 | "sass-loader": "^7.1.0", 52 | "typescript": "~3.3.3", 53 | "vue-template-compiler": "^2.6.6", 54 | "webpack": "^4.0.0" 55 | }, 56 | "scripts": { 57 | "serve": "vue-cli-service serve", 58 | "build": "vue-cli-service build", 59 | "lint": "vue-cli-service lint", 60 | "test:e2e": "vue-cli-service test:e2e", 61 | "test:unit": "vue-cli-service test:unit" 62 | }, 63 | "gitHooks": { 64 | "pre-commit": "lint-staged" 65 | }, 66 | "lint-staged": { 67 | "*.js": [ 68 | "vue-cli-service lint", 69 | "git add" 70 | ], 71 | "*.vue": [ 72 | "vue-cli-service lint", 73 | "git add" 74 | ] 75 | }, 76 | "author": { 77 | "name": "Stacktical (DSLA Protocol)", 78 | "email": "contact@stacktical.com", 79 | "url": "https://stacktical.com" 80 | }, 81 | "license": "Apache-2.0", 82 | "licenses": [ 83 | { 84 | "type": "Apache-2.0", 85 | "url": "http://www.apache.org/licenses/LICENSE-2.0" 86 | } 87 | ] 88 | } 89 | -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | cssnano: {}, 4 | autoprefixer: {}, 5 | 'postcss-sorting': { 6 | order: [ 7 | 'custom-properties', 8 | 'dollar-variables', 9 | 'declarations', 10 | 'at-rules', 11 | 'rules' 12 | ], 13 | 14 | 'properties-order': 'alphabetical', 15 | 16 | 'unspecified-properties-position': 'bottom' 17 | } 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacktical/willitscale-polkadot/1a052a093d4c23870e1822d1d9a3ac9e68cba835/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | willitscale-client by Stacktical 10 | 11 | 12 | 13 | 14 | 15 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /client/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/api/api.gql.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const QUERY_DEMO_BENCHMARK = gql` 4 | query demo { 5 | demo 6 | } 7 | `; 8 | 9 | export const MUTATION_PREDICT_CAPACITY = gql` 10 | mutation predictCapacity($points: [Point]) { 11 | predictCapacity(points: $points) 12 | } 13 | `; 14 | 15 | export const MUTATION_PREDICT_LATENCY = gql` 16 | mutation predictLatency($points: [Point]) { 17 | predictLatency(points: $points) 18 | } 19 | `; 20 | -------------------------------------------------------------------------------- /client/src/api/client.ts: -------------------------------------------------------------------------------- 1 | import { ApolloClient } from 'apollo-client'; 2 | import { HttpLink } from 'apollo-link-http'; 3 | import { InMemoryCache } from 'apollo-cache-inmemory'; 4 | 5 | const httpLink = new HttpLink({ 6 | uri: process.env.VUE_APP_API 7 | }); 8 | 9 | export const apolloClient = new ApolloClient({ 10 | link: httpLink, 11 | cache: new InMemoryCache(), 12 | connectToDevTools: true 13 | }); 14 | -------------------------------------------------------------------------------- /client/src/app.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 8 | 9 |
10 |
-------------------------------------------------------------------------------- /client/src/app.scss: -------------------------------------------------------------------------------- 1 | .navbar { 2 | box-shadow: none; 3 | 4 | .brand { 5 | margin-top: 1.25rem; 6 | padding: 0 1.5rem; 7 | display: inline-block; 8 | font-weight: 800; 9 | } 10 | 11 | &-end { 12 | label { 13 | margin-top: 15px; 14 | font-size: 0.8em; 15 | } 16 | } 17 | } 18 | 19 | .main-wrapper { 20 | position: relative; 21 | padding: 0 5% 0 5%; 22 | width: 100%; 23 | min-height: 100vh; 24 | transition: padding 0.3s ease-in-out; 25 | } 26 | -------------------------------------------------------------------------------- /client/src/app.ts: -------------------------------------------------------------------------------- 1 | import { Component, Vue, Watch } from 'vue-property-decorator'; 2 | import { State, Action, Getter } from 'vuex-class'; 3 | 4 | import { PredictionState } from '@/store/modules/prediction/types'; 5 | 6 | const namespace: string = 'web3'; 7 | 8 | @Component({}) 9 | export default class App extends Vue { 10 | @State('Prediction') SLA!: PredictionState; 11 | 12 | mounted() { 13 | if (process.env.NODE_ENV == 'development') { 14 | console.log('[DEBUG] Development Mode.'); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/src/filters/filters.ts: -------------------------------------------------------------------------------- 1 | import numeral from 'numeral'; 2 | 3 | const formatJSON = (value: number): any => { 4 | return JSON.stringify(value); 5 | }; 6 | 7 | const formatSLO = (slo: number): number => { 8 | return slo / 1000; 9 | }; 10 | 11 | const formatMetric = (metricValue: number, metricFormat: string): any => { 12 | return numeral(metricValue).format(metricFormat); 13 | }; 14 | 15 | const truncateText = (text: string, length: number, suffix: string): string => { 16 | if (text.length > length) { 17 | return text.substring(0, length) + suffix; 18 | } else { 19 | return text; 20 | } 21 | }; 22 | 23 | const roundNumber = (value: number, decimals: number): number => { 24 | if (!value) { 25 | value = 0; 26 | } 27 | 28 | if (!decimals) { 29 | decimals = 0; 30 | } 31 | 32 | value = Math.round(value * Math.pow(10, decimals)) / Math.pow(10, decimals); 33 | return value; 34 | }; 35 | 36 | export default { 37 | formatJSON, 38 | formatSLO, 39 | formatMetric, 40 | truncateText, 41 | roundNumber 42 | }; 43 | -------------------------------------------------------------------------------- /client/src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from '@/App.vue'; 3 | import router from '@/router'; 4 | import store from '@/store/index'; 5 | import filters from '@/filters/filters'; 6 | 7 | import '@/style/global.scss'; 8 | 9 | Vue.config.productionTip = false; 10 | Vue.filter('formatJSON', filters.formatJSON); 11 | Vue.filter('formatSLO', filters.formatSLO); 12 | Vue.filter('formatMetric', filters.formatMetric); 13 | Vue.filter('truncateText', filters.truncateText); 14 | Vue.filter('roundNumber', filters.roundNumber); 15 | 16 | new Vue({ 17 | router, 18 | store, 19 | render: h => h(App) 20 | }).$mount('#app'); 21 | -------------------------------------------------------------------------------- /client/src/router.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import Home from './views/home/Home.vue'; 4 | 5 | Vue.use(Router); 6 | 7 | export default new Router({ 8 | mode: 'history', 9 | base: process.env.BASE_URL, 10 | routes: [ 11 | { 12 | path: '/', 13 | name: 'home', 14 | component: Home 15 | } 16 | ] 17 | }); 18 | -------------------------------------------------------------------------------- /client/src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from "vue"; 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /client/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import Vue from "vue"; 3 | export default Vue; 4 | } 5 | -------------------------------------------------------------------------------- /client/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex, { StoreOptions } from 'vuex'; 3 | import { RootState } from './types'; 4 | import { Prediction } from './modules/prediction/index'; 5 | 6 | Vue.use(Vuex); 7 | 8 | const store: StoreOptions = { 9 | modules: { 10 | Prediction 11 | } 12 | }; 13 | 14 | export default new Vuex.Store(store); 15 | -------------------------------------------------------------------------------- /client/src/store/modules/prediction/actions.ts: -------------------------------------------------------------------------------- 1 | import { ActionTree, ActionContext } from 'vuex'; 2 | import { RootState } from '@/store/types'; 3 | import { PredictionState } from './types'; 4 | import { apolloClient } from '@/api/client'; 5 | import { 6 | MUTATION_PREDICT_CAPACITY, 7 | MUTATION_PREDICT_LATENCY 8 | } from '@/api/api.gql'; 9 | 10 | const predictCapacity = async ({ 11 | commit 12 | }: ActionContext): Promise => { 13 | const response = await apolloClient.mutate({ 14 | mutation: MUTATION_PREDICT_CAPACITY, 15 | variables: {} 16 | }); 17 | 18 | let predictiveAnalysis = JSON.parse(response.data.predictCapacity); 19 | console.log('Response:', predictiveAnalysis); 20 | 21 | let formattedBenchmark: object[] = []; 22 | 23 | let i: number; 24 | 25 | for (i = 0; i < predictiveAnalysis.benchmark.length; i++) { 26 | let responsePoint = { 27 | x: parseFloat(predictiveAnalysis.benchmark[i].p), 28 | y: parseFloat(predictiveAnalysis.benchmark[i].Xp) 29 | }; 30 | 31 | formattedBenchmark.push(responsePoint); 32 | } 33 | 34 | let formattedPrediction: object[] = []; 35 | 36 | let j: number; 37 | 38 | for (j = 0; j < predictiveAnalysis.scalabilityChart.length; j++) { 39 | let responsePoint = { 40 | x: parseFloat(predictiveAnalysis.scalabilityChart[j].p), 41 | y: parseFloat(predictiveAnalysis.scalabilityChart[j].Xp) 42 | }; 43 | 44 | formattedPrediction.push(responsePoint); 45 | } 46 | 47 | console.log('formattedBenchmark', formattedBenchmark); 48 | console.log('formattedPrediction', formattedPrediction); 49 | 50 | await commit('setBenchmark', formattedBenchmark); 51 | await commit('setCapacityPrediction', formattedPrediction); 52 | }; 53 | 54 | const predictLatency = async ({ 55 | commit 56 | }: ActionContext): Promise => { 57 | const response = await apolloClient.mutate({ 58 | mutation: MUTATION_PREDICT_LATENCY, 59 | variables: {} 60 | }); 61 | 62 | let predictiveAnalysis = JSON.parse(response.data.predictLatency); 63 | console.log('Response:', predictiveAnalysis); 64 | 65 | let formattedPrediction: object[] = []; 66 | 67 | let i: number; 68 | 69 | for (i = 0; i < predictiveAnalysis.length; i++) { 70 | let responsePoint = { 71 | x: parseFloat(predictiveAnalysis[i].p), 72 | y: parseFloat(predictiveAnalysis[i].Rt) 73 | }; 74 | 75 | formattedPrediction.push(responsePoint); 76 | } 77 | 78 | console.log('formattedPrediction', formattedPrediction); 79 | await commit('setLatencyPrediction', formattedPrediction); 80 | }; 81 | 82 | export const actions: ActionTree = { 83 | predictCapacity, 84 | predictLatency 85 | }; 86 | -------------------------------------------------------------------------------- /client/src/store/modules/prediction/getters.ts: -------------------------------------------------------------------------------- 1 | import { GetterTree } from 'vuex'; 2 | import { RootState } from '@/store/types'; 3 | import { PredictionState } from './types'; 4 | 5 | const getBenchmark = (state: PredictionState): any => { 6 | return state.benchmark; 7 | }; 8 | 9 | const getCapacityPrediction = (state: PredictionState): any => { 10 | return state.capacityPrediction; 11 | }; 12 | 13 | const getLatencyPrediction = (state: PredictionState): any => { 14 | return state.latencyPrediction; 15 | }; 16 | 17 | const getNodes = (state: PredictionState): any => { 18 | return state.capacityPrediction.map(function(e) { 19 | return e.x; 20 | }); 21 | }; 22 | 23 | export const getters: GetterTree = { 24 | getNodes, 25 | getBenchmark, 26 | getCapacityPrediction, 27 | getLatencyPrediction 28 | }; 29 | -------------------------------------------------------------------------------- /client/src/store/modules/prediction/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex'; 2 | 3 | import { RootState } from '@/store/types'; 4 | import { PredictionState } from './types'; 5 | 6 | import { state } from './state'; 7 | import { getters } from './getters'; 8 | import { mutations } from './mutations'; 9 | import { actions } from './actions'; 10 | 11 | const namespaced: boolean = true; 12 | 13 | export const Prediction: Module = { 14 | state, 15 | getters, 16 | mutations, 17 | actions, 18 | namespaced 19 | }; 20 | -------------------------------------------------------------------------------- /client/src/store/modules/prediction/mutations.ts: -------------------------------------------------------------------------------- 1 | import { MutationTree } from 'vuex'; 2 | import { PredictionState } from '@/store/modules/prediction/types'; 3 | 4 | const setBenchmark = (state: PredictionState, payload: any) => { 5 | state.benchmark = payload; 6 | }; 7 | 8 | const setCapacityPrediction = (state: PredictionState, payload: any) => { 9 | state.capacityPrediction = payload; 10 | }; 11 | 12 | const setLatencyPrediction = (state: PredictionState, payload: any) => { 13 | state.latencyPrediction = payload; 14 | }; 15 | 16 | export const mutations: MutationTree = { 17 | setBenchmark, 18 | setCapacityPrediction, 19 | setLatencyPrediction 20 | }; 21 | -------------------------------------------------------------------------------- /client/src/store/modules/prediction/state.ts: -------------------------------------------------------------------------------- 1 | import { PredictionState } from './types'; 2 | 3 | export const state: PredictionState = { 4 | benchmark: [], 5 | capacityPrediction: [], 6 | latencyPrediction: [] 7 | }; 8 | -------------------------------------------------------------------------------- /client/src/store/modules/prediction/types.ts: -------------------------------------------------------------------------------- 1 | export interface Point { 2 | x: number; 3 | y: number; 4 | } 5 | 6 | export interface PredictionState { 7 | benchmark: Array; 8 | capacityPrediction: Array; 9 | latencyPrediction: Array; 10 | } 11 | -------------------------------------------------------------------------------- /client/src/store/types.ts: -------------------------------------------------------------------------------- 1 | import { PredictionState } from './modules/prediction/types'; 2 | 3 | export interface RootState { 4 | Prediction: PredictionState; 5 | } 6 | -------------------------------------------------------------------------------- /client/src/style/bulma.custom.scss: -------------------------------------------------------------------------------- 1 | @import "~bulmaswatch/materia/_variables.scss"; 2 | @import "@/style/bulma.variables.scss"; 3 | 4 | @import "~bulma/sass/utilities/_all.sass"; 5 | @import "~bulma/sass/base/_all.sass"; 6 | @import "~bulma/sass/components/breadcrumb.sass"; 7 | @import "~bulma/sass/components/card.sass"; 8 | @import "~bulma/sass/components/level.sass"; 9 | @import "~bulma/sass/components/message.sass"; 10 | @import "~bulma/sass/components/navbar.sass"; 11 | @import "~bulma/sass/components/modal.sass"; 12 | @import "~bulma/sass/elements/button.sass"; 13 | @import "~bulma/sass/elements/container.sass"; 14 | @import "~bulma/sass/elements/notification.sass"; 15 | @import "~bulma/sass/elements/title.sass"; 16 | @import "~bulma/sass/elements/tag.sass"; 17 | @import "~bulma/sass/form/_all.sass"; 18 | @import "~bulma/sass/grid/_all.sass"; 19 | @import "~bulma/sass/layout/hero.sass"; 20 | @import "~bulma/sass/layout/section.sass"; 21 | 22 | @import "~bulmaswatch/materia/_overrides.scss"; -------------------------------------------------------------------------------- /client/src/style/bulma.variables.scss: -------------------------------------------------------------------------------- 1 | @import "@/style/variables.scss"; 2 | 3 | @import "~bulma/sass/utilities/initial-variables.sass"; 4 | @import "~bulma/sass/utilities/functions.sass"; 5 | @import "~bulma/sass/utilities/derived-variables.sass"; 6 | 7 | @import "@/style/mixin.scss"; -------------------------------------------------------------------------------- /client/src/style/global.scss: -------------------------------------------------------------------------------- 1 | @import "./bulma.custom.scss"; 2 | @import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&display=swap"); 3 | @import url("https://use.fontawesome.com/releases/v5.9.0/css/all.css"); 4 | 5 | html, 6 | body { 7 | font-family: "Nunito Sans", serif; 8 | font-weight: 200; 9 | color: $primary-text-color; 10 | background-color: $background-color; 11 | overflow-x: hidden; 12 | } 13 | 14 | h1 { 15 | font-weight: 700; 16 | @include font-smoothing(on); 17 | } 18 | 19 | h2, 20 | h3, 21 | h4, 22 | h5  { 23 | font-weight: 400; 24 | @include font-smoothing(on); 25 | } 26 | 27 | h2 { 28 | i.fas { 29 | width: 48px; 30 | text-align: center; 31 | } 32 | } 33 | 34 | a { 35 | color: $primary; 36 | transform: translateZ(0); 37 | transition: 0.3s ease; 38 | 39 | &:hover { 40 | color: $accent-color; 41 | } 42 | } 43 | 44 | p { 45 | color: $secondary-text-color; 46 | font-weight: 300; 47 | @include font-smoothing(on); 48 | } 49 | 50 | hr { 51 | border-top: 1px solid #fff; 52 | border-bottom: 1px solid $background-color-dark; 53 | } 54 | 55 | .button { 56 | font-weight: 700; 57 | } -------------------------------------------------------------------------------- /client/src/style/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin font-smoothing($value: on) { 2 | @if $value == on { 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | } 6 | @else { 7 | -webkit-font-smoothing: subpixel-antialiased; 8 | -moz-osx-font-smoothing: auto; 9 | } 10 | } 11 | 12 | @mixin for-phone-only { 13 | @media (max-width: 599px) { @content; } 14 | } 15 | @mixin for-tablet-portrait-up { 16 | @media (min-width: 600px) { @content; } 17 | } 18 | @mixin for-tablet-landscape-up { 19 | @media (min-width: 900px) { @content; } 20 | } 21 | @mixin for-desktop-up { 22 | @media (min-width: 1200px) { @content; } 23 | } 24 | @mixin for-big-desktop-up { 25 | @media (min-width: 1800px) { @content; } 26 | } 27 | -------------------------------------------------------------------------------- /client/src/style/variables.scss: -------------------------------------------------------------------------------- 1 | // Brand 2 | $primary-color-darker: #145CA4; 3 | $primary-color-dark: #1976D2; 4 | $primary-color: #353434; 5 | $primary-color-light: #BBDEFB; 6 | $accent-color: #8BC34A; 7 | $accent-color-light: #A4D070; 8 | $primary-text-color: #3D4752; 9 | $secondary-text-color: #4B5865; 10 | $alt-text-color: #FFF; 11 | $cta-color: #ffb347; 12 | 13 | 14 | // Backgrounds 15 | $background-color: #d9d6d6; 16 | $background-color-light: #FEFEFE; 17 | $background-color-dark: #EAEAEA; 18 | 19 | // Alerts 20 | $info-primary-color-dark: #0288D1; 21 | $info-primary-color: #03A9F4; 22 | $info-primary-color-light: #B3E5FC; 23 | 24 | $success-primary-color-dark: #689F38; 25 | $success-primary-color: #8BC34A; 26 | $success-primary-color-light: #DCEDC8; 27 | 28 | $warning-primary-color-dark: #e5ce00; 29 | $warning-primary-color: #FFEB3B; 30 | $warning-primary-color-light: #FFF9C4; 31 | 32 | $danger-primary-color-dark: #D32F2F; 33 | $danger-primary-color: #F44336; 34 | $danger-primary-color-light: #FFCDD2; 35 | 36 | // Bulma Override 37 | $primary: $primary-color; 38 | $info: $info-primary-color; 39 | $success: $success-primary-color; 40 | $warning: $warning-primary-color; 41 | $danger: $danger-primary-color; 42 | 43 | $navbar-background-color: transparent; 44 | $navbar-box-shadow-color: transparent; -------------------------------------------------------------------------------- /client/src/utils/SLOTypes.ts: -------------------------------------------------------------------------------- 1 | export const SLOTypes: Array = [ 2 | { text: 'Must be equal to', type: 'EqualTo', symbol: '==', id: 0 }, 3 | { text: 'Must not be equal to', type: 'NotEqualTo', symbol: '!=', id: 1 }, 4 | { text: 'Must be smaller than', type: 'SmallerThan', symbol: '<', id: 2 }, 5 | { 6 | text: 'Must be smaller or equal to', 7 | type: 'SmallerOrEqualTo', 8 | symbol: '<=', 9 | id: 3 10 | }, 11 | { text: 'Must be greater than', type: 'GreaterThan', symbol: '>', id: 4 }, 12 | { 13 | text: 'Must be greater than or equal to', 14 | type: 'GreaterOrEqualTo', 15 | symbol: '>=', 16 | id: 5 17 | } 18 | ]; 19 | 20 | export function typeIdToTypeName(id: number) { 21 | const sloType: SLOType | undefined = SLOTypes.find(slo => { 22 | return slo.id === id; 23 | }); 24 | 25 | if (sloType) { 26 | return sloType.type; 27 | } 28 | } 29 | 30 | export function typeIdToTypeText(id: number) { 31 | const sloType: SLOType | undefined = SLOTypes.find(slo => { 32 | return slo.id === id; 33 | }); 34 | 35 | if (sloType) { 36 | return sloType.text; 37 | } 38 | } 39 | 40 | export function typeIdToTypeSymbol(id: number) { 41 | const sloType: SLOType | undefined = SLOTypes.find(slo => { 42 | return slo.id === id; 43 | }); 44 | 45 | if (sloType) { 46 | return sloType.symbol; 47 | } 48 | } 49 | 50 | declare global { 51 | interface SLOType { 52 | id: number; 53 | text: string; 54 | symbol: string; 55 | type: string; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /client/src/utils/uuidGenerator.ts: -------------------------------------------------------------------------------- 1 | function generateUuid() { 2 | return ( 3 | Date.now() 4 | .toString() 5 | .substring(4, 13) + 6 | Math.random() 7 | .toString(36) 8 | .substring(2, 15) 9 | ); 10 | } 11 | 12 | export default generateUuid; 13 | -------------------------------------------------------------------------------- /client/src/views/home/Home.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/views/home/chart.js: -------------------------------------------------------------------------------- 1 | import { Scatter, mixins } from 'vue-chartjs'; 2 | const { reactiveProp } = mixins; 3 | 4 | export default { 5 | extends: Scatter, 6 | props: ['chartData', 'options'], 7 | mixins: [reactiveProp], 8 | mounted() { 9 | this.renderChart(this.chartData, this.options); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /client/src/views/home/home.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 |
8 |
9 |
10 |
11 |
-------------------------------------------------------------------------------- /client/src/views/home/home.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | background: #fff; 3 | 4 | &__simulation { 5 | position: relative; 6 | color: $primary-text-color; 7 | background: #fafafa; 8 | } 9 | 10 | &__chart { 11 | color: $alt-text-color; 12 | 13 | h2 { 14 | padding: 20px 25px; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/src/views/home/home.ts: -------------------------------------------------------------------------------- 1 | import { Component, Vue } from 'vue-property-decorator'; 2 | import { Action, Getter, State } from 'vuex-class'; 3 | import { PredictionState } from '@/store/modules/prediction/types'; 4 | 5 | //@ts-ignore 6 | import chart from '@/views/home/chart.js'; 7 | 8 | @Component({ 9 | components: { 10 | chart 11 | } 12 | }) 13 | export default class Home extends Vue { 14 | @State('Prediction') Prediction!: PredictionState; 15 | @Action('Prediction/predictCapacity') predictCapacity!: any; 16 | @Action('Prediction/predictLatency') predictLatency!: any; 17 | @Getter('Prediction/getNodes') getNodes!: any; 18 | @Getter('Prediction/getBenchmark') getBenchmark!: any; 19 | @Getter('Prediction/getCapacityPrediction') getCapacityPrediction!: any; 20 | @Getter('Prediction/getLatencyPrediction') getLatencyPrediction!: any; 21 | 22 | chartWidth: number = 640; 23 | 24 | chartHeight: number = 480; 25 | 26 | getRandomInt() { 27 | return Math.floor(Math.random() * (50 - 5 + 1)) + 5; 28 | } 29 | 30 | chartData(): object { 31 | return { 32 | labels: this.getNodes, 33 | datasets: [ 34 | { 35 | type: 'scatter', 36 | label: 'Performance Benchmark', 37 | data: this.getBenchmark, 38 | fill: false, 39 | backgroundColor: '#fff', 40 | borderColor: '#fff', 41 | borderWidth: 1, 42 | pointRadius: 3, 43 | pointHoverRadius: 10 44 | }, 45 | { 46 | type: 'line', 47 | label: 'Capacity Prediction', 48 | yAxisID: 'A', 49 | data: this.getCapacityPrediction, 50 | fill: false, 51 | borderColor: '#e6007a', 52 | pointRadius: 0, 53 | borderWidth: 1, 54 | showLine: true 55 | }, 56 | { 57 | type: 'line', 58 | label: 'Latency Prediction', 59 | yAxisID: 'B', 60 | data: this.getLatencyPrediction, 61 | fill: false, 62 | borderColor: '#fc0', 63 | pointRadius: 0, 64 | borderWidth: 1, 65 | showLine: true 66 | } 67 | ] 68 | }; 69 | } 70 | 71 | chartOptions(): object { 72 | return { 73 | fontColor: 'white', 74 | responsive: true, 75 | title: { 76 | display: true 77 | }, 78 | legend: { 79 | labels: { 80 | fontColor: 'white' 81 | } 82 | }, 83 | scales: { 84 | xAxes: [ 85 | { 86 | gridLines: { 87 | display: false 88 | }, 89 | scaleLabel: { 90 | display: true, 91 | labelString: 'Nodes', 92 | fontColor: 'white' 93 | }, 94 | ticks: { 95 | fontColor: 'white', 96 | padding: 10 97 | } 98 | } 99 | ], 100 | yAxes: [ 101 | { 102 | id: 'A', 103 | scaleLabel: { 104 | display: true, 105 | labelString: 'Throughput', 106 | fontColor: 'white' 107 | }, 108 | ticks: { 109 | fontColor: 'white', 110 | padding: 10, 111 | callback: function(value, index, values) { 112 | return value + ' tps'; 113 | } 114 | } 115 | }, 116 | { 117 | id: 'B', 118 | position: 'right', 119 | scaleLabel: { 120 | display: true, 121 | labelString: 'Latency', 122 | fontColor: 'white' 123 | }, 124 | ticks: { 125 | fontColor: 'white', 126 | padding: 10, 127 | callback: function(value, index, values) { 128 | return value + ' s'; 129 | } 130 | } 131 | } 132 | ] 133 | } 134 | }; 135 | } 136 | 137 | async willitscale() { 138 | await this.predictCapacity(); 139 | await this.predictLatency(); 140 | } 141 | 142 | mounted() { 143 | this.willitscale(); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /client/tests/e2e/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['cypress'], 3 | env: { 4 | mocha: true, 5 | 'cypress/globals': true 6 | }, 7 | rules: { 8 | strict: 'off' 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /client/tests/e2e/plugins/index.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/guides/guides/plugins-guide.html 2 | 3 | // if you need a custom webpack configuration you can uncomment the following import 4 | // and then use the `file:preprocessor` event 5 | // as explained in the cypress docs 6 | // https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples 7 | 8 | /* eslint-disable import/no-extraneous-dependencies, global-require, arrow-body-style */ 9 | // const webpack = require('@cypress/webpack-preprocessor') 10 | 11 | module.exports = (on, config) => { 12 | // on('file:preprocessor', webpack({ 13 | // webpackOptions: require('@vue/cli-service/webpack.config'), 14 | // watchOptions: {} 15 | // })) 16 | 17 | return Object.assign({}, config, { 18 | fixturesFolder: 'tests/e2e/fixtures', 19 | integrationFolder: 'tests/e2e/specs', 20 | screenshotsFolder: 'tests/e2e/screenshots', 21 | videosFolder: 'tests/e2e/videos', 22 | supportFile: 'tests/e2e/support/index.js' 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /client/tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe('My First Test', () => { 4 | it('Visits the app root url', () => { 5 | cy.visit('/'); 6 | cy.contains('h1', 'Welcome to Your Vue.js + TypeScript App'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /client/tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /client/tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /client/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /client/tests/unit/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { shallowMount } from "@vue/test-utils"; 3 | import Home from "@/components/home/Home.vue"; 4 | 5 | describe("Home.vue", () => { 6 | it("renders props.msg when passed", () => { 7 | const replicant = "Roy Batty"; 8 | const wrapper = shallowMount(Home, { 9 | propsData: { replicant } 10 | }); 11 | expect(wrapper.text()).to.include(replicant); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "noImplicitAny": false, 14 | "baseUrl": ".", 15 | "types": [ 16 | "webpack-env", 17 | "mocha", 18 | "chai" 19 | ], 20 | "paths": { 21 | "@/*": [ 22 | "src/*" 23 | ] 24 | }, 25 | "lib": [ 26 | "esnext", 27 | "dom", 28 | "dom.iterable", 29 | "scripthost" 30 | ] 31 | }, 32 | "include": [ 33 | "src/**/*.ts", 34 | "src/**/*.d.ts", 35 | "src/**/*.tsx", 36 | "src/**/*.vue", 37 | "tests/**/*.ts", 38 | "tests/**/*.tsx", "src/views/home/chart.js" 39 | ], 40 | "exclude": [ 41 | "node_modules" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /client/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | css: { 3 | loaderOptions: { 4 | sass: { 5 | data: ` 6 | @import "@/style/bulma.variables.scss"; 7 | ` 8 | } 9 | } 10 | }, 11 | devServer: { 12 | disableHostCheck: true 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /k8s/kubernetes.deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: willitscale-r-server 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: willitscale-r-server 10 | template: 11 | metadata: 12 | labels: 13 | app: willitscale-r-server 14 | spec: 15 | containers: 16 | - name: willitscale-r-server 17 | image: stacktical/willitscale-r-server:1.1 18 | ports: 19 | - containerPort: 6311 20 | --- 21 | apiVersion: apps/v1 22 | kind: Deployment 23 | metadata: 24 | name: willitscale-api 25 | spec: 26 | replicas: 1 27 | selector: 28 | matchLabels: 29 | app: willitscale-api 30 | template: 31 | metadata: 32 | labels: 33 | app: willitscale-api 34 | spec: 35 | containers: 36 | - name: willitscale-api 37 | image: stacktical/willitscale-api:1.0 38 | ports: 39 | - containerPort: 10000 40 | env: 41 | - name: SERVICE_R_HOST 42 | value: "willitscale-r-server" 43 | --- 44 | apiVersion: v1 45 | kind: Service 46 | metadata: 47 | name: willitscale-api 48 | spec: 49 | selector: 50 | app: willitscale-api 51 | ports: 52 | - protocol: TCP 53 | port: 10000 54 | targetPort: 10000 55 | --- 56 | apiVersion: v1 57 | kind: Service 58 | metadata: 59 | name: willitscale-r-server 60 | spec: 61 | selector: 62 | app: willitscale-r-server 63 | ports: 64 | - protocol: TCP 65 | port: 10001 66 | targetPort: 6311 67 | -------------------------------------------------------------------------------- /r-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM r-base:3.5.3 2 | 3 | ADD install-packages.R install-packages.R 4 | 5 | ADD run-rserve.R run-rserve.R 6 | 7 | RUN Rscript install-packages.R 8 | 9 | ADD Rserv.conf /etc/Rserv.conf 10 | 11 | EXPOSE 6311 12 | 13 | RUN touch /rserve.log 14 | 15 | CMD Rscript run-rserve.R 16 | -------------------------------------------------------------------------------- /r-server/Rserv.conf: -------------------------------------------------------------------------------- 1 | remote enable 2 | auth disable 3 | fileio enable 4 | maxinbuf 500000 5 | #interactive yes 6 | -------------------------------------------------------------------------------- /r-server/install-packages.R: -------------------------------------------------------------------------------- 1 | install.packages("versions") 2 | 3 | install.packages("ggplot2") 4 | 5 | library("versions") 6 | install.versions("jsonlite","1.3") 7 | 8 | install.packages("nlmrt") 9 | 10 | install.packages("nls2") 11 | 12 | install.packages("Rserve") 13 | 14 | install.packages("Hmisc") 15 | -------------------------------------------------------------------------------- /r-server/run-rserve.R: -------------------------------------------------------------------------------- 1 | library(Rserve) 2 | run.Rserve(debug = TRUE, 6311,args = NULL, config.file = "/etc/Rserv.conf") 3 | -------------------------------------------------------------------------------- /r-server/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | 4 | [program:r_serve] 5 | command=R CMD BATCH /run-rserve.R 6 | autostart=true 7 | redirect_stderr=true 8 | stdout_logfile=/dev/stderr 9 | autorestart=unexpected 10 | exitcodes=0 11 | -------------------------------------------------------------------------------- /resources/grant_w3f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacktical/willitscale-polkadot/1a052a093d4c23870e1822d1d9a3ac9e68cba835/resources/grant_w3f.png -------------------------------------------------------------------------------- /resources/research_m-schaffer_tuw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacktical/willitscale-polkadot/1a052a093d4c23870e1822d1d9a3ac9e68cba835/resources/research_m-schaffer_tuw.jpg -------------------------------------------------------------------------------- /resources/stacktical_logo_v2-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacktical/willitscale-polkadot/1a052a093d4c23870e1822d1d9a3ac9e68cba835/resources/stacktical_logo_v2-dark.png -------------------------------------------------------------------------------- /resources/willitscale-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacktical/willitscale-polkadot/1a052a093d4c23870e1822d1d9a3ac9e68cba835/resources/willitscale-api.png -------------------------------------------------------------------------------- /resources/willitscale-client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacktical/willitscale-polkadot/1a052a093d4c23870e1822d1d9a3ac9e68cba835/resources/willitscale-client.png -------------------------------------------------------------------------------- /skaffold.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: skaffold/v2beta5 2 | kind: Config 3 | metadata: 4 | name: willitscale-polkadot 5 | build: 6 | local: 7 | push: false 8 | artifacts: 9 | - image: stacktical/willitscale-client 10 | context: client 11 | - image: stacktical/willitscale-api 12 | context: api 13 | - image: stacktical/willitscale-r-server 14 | context: r-server 15 | deploy: 16 | kubectl: 17 | manifests: 18 | - k8s/*.yaml 19 | portForward: 20 | - resourceType: service 21 | resourceName: willitscale-api 22 | - resourceType: service 23 | resourceName: willitscale-r-server 24 | --------------------------------------------------------------------------------